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/app/Jobs/ContinueCampaign.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class ContinueCampaign implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public $tries = 1;
    public $maxExceptions = 1;
    public $failOnTimeout = true;
    public $timeout = 60;

    protected $campaignId;
    protected $campaignClass;
    protected $campaignQueue;
    protected $customerId;

    public function __construct(int $campaignId, string $campaignClass, string $campaignQueue, int $customerId)
    {
        $this->campaignId    = $campaignId;
        $this->campaignClass = $campaignClass;
        $this->campaignQueue = $campaignQueue;
        $this->customerId    = $customerId;
    }

    public function handle(): void
    {
        $customer = \App\Model\Customer::find($this->customerId);
        $campaign = $this->campaignClass::on($customer->getDbConnection())->find($this->campaignId);

        if ($campaign->isPaused()) {
            $campaign->logger()->warning('ContinueCampaign: campaign is paused, stopping.');
            return;
        }

        $count = $campaign->countSubscribersToSend();

        if ($count === 0) {
            $campaign->logger()->warning('ContinueCampaign: no contacts left, campaign finishes successfully!');
            $this->transitionStatus($campaign);

            $campaign->debug(function ($info) {
                $finishAt = Carbon::now();
                $info['finish_at']   = $finishAt->toString();
                $info['total_time']  = $finishAt->diffInSeconds(Carbon::parse($info['start_at']), $abs = true);
                return $info;
            });

            return;
        }

        $campaign->logger()->warning("ContinueCampaign: {$count} contacts remaining, dispatching next batch.");

        // run() with $check = false — campaign is already in 'sending' status
        $campaign->run($check = false, $this->campaignQueue);
    }

    /**
     * Single source of truth for campaign status transitions after all
     * subscribers have been sent. Handles both A/B test and normal campaigns.
     */
    public function failed(\Throwable $exception): void
    {
        Log::error('[JOB_FAILED] ' . static::class, [
            'exception' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'campaign_id' => $this->campaignId,
            'campaign_class' => $this->campaignClass,
        ]);
    }

    private function transitionStatus($campaign): void
    {
        if ($campaign->hasAbTest()) {
            $abTest = $campaign->abTest;

            if ($campaign->isSendingWinner()) {
                // Winner rollout finished → mark done
                $campaign->setDone();
            } elseif ($campaign->isSending() && !$campaign->isError()) {
                // Test phase finished → awaiting winner determination
                $campaign->is_error = false;
                $campaign->is_paused = false;
                $campaign->status = \App\Model\Campaign::STATUS_AWAITING_WINNER;
                $campaign->save();

                // Auto-evaluate winner when:
                //   1. Criteria is NOT manual (manual → user picks via UI)
                //   2. winner_wait_hours is explicitly set (not null)
                //      - > 0: dispatch with delay (collect engagement data first)
                //      - = 0: dispatch immediately (pick winner right away)
                //      - null: not configured → stay in AWAITING_WINNER until manual action
                $isAutoEvaluate = $abTest->winner_criteria !== \App\Model\AbTest::CRITERIA_MANUAL
                    && !is_null($abTest->winner_wait_hours);

                if ($isAutoEvaluate) {
                    $waitHours = max(0, (int) $abTest->winner_wait_hours);

                    $job = \App\Jobs\EvaluateAbTestWinner::dispatch($abTest->id)
                        ->onQueue(ACM_QUEUE_TYPE_BATCH);

                    if ($waitHours > 0) {
                        $job->delay(now()->addHours($waitHours));
                    }
                }
            }
        } elseif (!$campaign->isError()) {
            $campaign->setDone();
        }
    }
}