HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/database/seeders/Demo/Stage10_CampaignsSeeder.php
<?php

namespace Database\Seeders\Demo;

use App\Model\Campaign;
use App\Model\CampaignsListsSegment;
use App\Model\Email;
use App\Model\MailList;
use App\Model\Sender;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Stage 10 — campaigns + their email rows + list/segment associations.
 *
 * Per customer:
 *   - Creates `campaigns` count from DemoConfig with a realistic mix of statuses:
 *       60% done   — already sent
 *       15% new    — draft
 *       10% sending — currently sending
 *       10% scheduled
 *        5% error  — archive_status=error
 *   - Each campaign points to one of the customer's lists (random)
 *   - Each campaign creates a sibling Email row with subject/from/html/etc.
 *
 * Stores results in self::$created[customer_key] = [Campaign, ...] so the
 * tracking-logs stage can build open/click/bounce rows for each "done" campaign.
 */
class Stage10_CampaignsSeeder extends AbstractDemoSeeder
{
    protected int $fakerOffset = 10;

    /** customer_key → Campaign[] */
    public static array $created = [];

    public function run(): void
    {
        $f = $this->faker();
        $customers = Stage03_CustomersSeeder::$created;
        $total = 0;

        foreach (DemoConfig::customers() as $cfg) {
            $customer = $customers[$cfg['key']] ?? null;
            $lists = Stage07_MailListsSeeder::$created[$cfg['key']] ?? [];
            if (!$customer || empty($lists)) continue;

            self::$created[$cfg['key']] = [];
            $verifiedSenders = Sender::where('customer_id', $customer->id)->verified()->get();
            $defaultSender = $verifiedSenders->first();

            for ($i = 0; $i < ($cfg['campaigns'] ?? 0); $i++) {
                $list = $lists[array_rand($lists)];
                $status = $this->pickStatus($f);
                $isError = $status === '__error__';
                $realStatus = $isError ? Campaign::STATUS_DONE : $status;

                // Email row first
                $email = new Email([
                    'type'        => Email::TYPE_REGULAR,
                    'subject'     => DemoFaker::campaignSubject(),
                    'from_email'  => $defaultSender ? $defaultSender->email : $list->from_email,
                    'from_name'   => $defaultSender ? $defaultSender->name  : $list->from_name,
                    'reply_to'    => $list->from_email,
                    'html'        => $this->randomHtml($f),
                    'plain'       => $this->randomPlain($f),
                    'preheader'   => $f->sentence(8),
                    'sign_dkim'   => true,
                    'track_open'  => true,
                    'track_click' => true,
                    'customer_id' => $customer->id,
                ]);
                $email->skip_failed_message = false;
                $email->fillDeliveryStatuses($email->getDefaultDeliveryStatuses());
                $email->save();

                // Assign a real template so reports / variant thumbnails /
                // preview routes render actual content (mirrors what the
                // campaign wizard does via templateChoose → setTemplate).
                $this->assignRandomTemplate($email);

                // Campaign row
                $campaign = new Campaign();
                $campaign->email_id            = $email->id;
                $campaign->customer_id         = $customer->id;
                $campaign->default_mail_list_id = $list->id;
                $campaign->name                = $f->randomElement(['Spring', 'Summer', 'Black Friday', 'Welcome', 'Promo', 'Holiday', 'Newsletter']) . ' ' . $f->randomElement(['#', 'Edition ', 'Wave ']) . $f->numberBetween(1, 50);
                $campaign->status              = $realStatus;
                $campaign->is_error            = $isError ? 1 : 0;
                $campaign->is_paused           = $f->boolean(5);
                $campaign->last_error          = $isError ? $f->randomElement([
                    'SMTP authentication failed',
                    'No verified sender found',
                    'Sending server quota exceeded',
                    'Connection timeout to SMTP server',
                ]) : null;
                if (in_array($realStatus, [Campaign::STATUS_DONE, Campaign::STATUS_SENDING], true)) {
                    // Realistic time distribution that ALSO keeps the
                    // "Engagement over time" chart populated on the default
                    // 7-day window. We bias 50% of done campaigns into the
                    // last 7 days, 30% into 8-30 days, 20% historical.
                    $bucket = $f->numberBetween(1, 100);
                    if ($bucket <= 50) {
                        // last 7 days — gives the 7d chart real curves
                        $campaign->run_at = now()->subHours($f->numberBetween(2, 168));
                    } elseif ($bucket <= 80) {
                        // 8-30 days — fills the 30d chart
                        $campaign->run_at = now()->subDays($f->numberBetween(8, 30));
                    } else {
                        // historical 31-180 days — for "all time" reports
                        $campaign->run_at = now()->subDays($f->numberBetween(31, 180));
                    }
                    $campaign->delivery_at = $campaign->run_at;
                }
                if ($realStatus === Campaign::STATUS_SCHEDULED) {
                    $campaign->run_at = now()->addDays($f->numberBetween(1, 30));
                }
                if ($realStatus === Campaign::STATUS_DONE) {
                    $campaign->archive_status = $isError ? Campaign::ARCHIVE_STATUS_ERROR : Campaign::ARCHIVE_STATUS_YES;
                }
                // Spread create dates across last 30 days. For sent / sending
                // campaigns clamp ≤ run_at so created_at always precedes the
                // send (run_at can be deeper than 30 days for the historical
                // bucket — there created_at falls right before run_at).
                $campaignCreatedAt = now()->subDays(mt_rand(0, 30))->subSeconds(mt_rand(0, 86399));
                if ($campaign->run_at && $campaign->run_at->lt($campaignCreatedAt)) {
                    $campaignCreatedAt = $campaign->run_at->copy()->subMinutes(mt_rand(5, 1440));
                }
                $campaign->created_at = $campaignCreatedAt;
                $campaign->updated_at = $campaignCreatedAt;
                $campaign->save();

                // Pivot: campaigns_lists_segments
                CampaignsListsSegment::create([
                    'campaign_id'  => $campaign->id,
                    'mail_list_id' => $list->id,
                    'segment_id'   => null,
                    'customer_id'  => $customer->id,
                ]);

                self::$created[$cfg['key']][] = $campaign;
                $total++;
            }
            $this->log("customer {$cfg['key']}: {$cfg['campaigns']} campaigns");
        }
        $this->log("seeded {$total} campaigns");
    }

    protected function pickStatus($f): string
    {
        $r = $f->numberBetween(1, 100);
        if ($r <= 60) return Campaign::STATUS_DONE;
        if ($r <= 75) return Campaign::STATUS_NEW;       // draft
        if ($r <= 85) return Campaign::STATUS_SENDING;
        if ($r <= 95) return Campaign::STATUS_SCHEDULED;
        return '__error__';                              // → done + is_error=1
    }

    protected function randomHtml($f): string
    {
        $title = $f->sentence(4);
        $body  = '<p>' . implode('</p><p>', $f->paragraphs(3)) . '</p>';
        return "<!DOCTYPE html><html><body><h1>{$title}</h1>{$body}<p><a href=\"https://acelle-demo.com/cta\">Read more</a></p></body></html>";
    }

    protected function randomPlain($f): string
    {
        return $f->paragraph(8);
    }
}