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/hi.naniguide.com/app/Models/Campaign.php
<?php

namespace App\Models;

use App\Services\BounceProcessingService;
use App\Services\CampaignAnalyticsService;
use App\Services\CampaignSendingService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Traits\WorkspaceScoped;

class Campaign extends Model
{
    use HasFactory, SoftDeletes, WorkspaceScoped;

    protected $fillable = [
        'customer_id',
        'list_id',
        'template_id',
        'name',
        'subject',
        'from_name',
        'from_email',
        'reply_to',
        'type',
        'status',
        'html_content',
        'plain_text_content',
        'template_data',
        'scheduled_at',
        'send_at',
        'started_at',
        'finished_at',
        'total_recipients',
        'sent_count',
        'delivered_count',
        'opened_count',
        'clicked_count',
        'failed_count',
        'failure_reason',
        'bounced_count',
        'unsubscribed_count',
        'complained_count',
        'replied_count',
        'open_rate',
        'click_rate',
        'bounce_rate',
        'track_opens',
        'track_clicks',
        'tracking_domain_id',
        'bounce_server_id',
        'sending_domain_id',
        'delivery_server_id',
        'reply_server_id',
        'segments',
        'settings',
    ];

    protected function casts(): array
    {
        return [
            'template_data' => 'array',
            'segments' => 'array',
            'settings' => 'array',
            'scheduled_at' => 'datetime',
            'send_at' => 'datetime',
            'started_at' => 'datetime',
            'finished_at' => 'datetime',
            'open_rate' => 'decimal:2',
            'click_rate' => 'decimal:2',
            'bounce_rate' => 'decimal:2',
            'track_opens' => 'boolean',
            'track_clicks' => 'boolean',
        ];
    }

    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class)->withTrashed();
    }

    public function emailList(): BelongsTo
    {
        return $this->belongsTo(EmailList::class, 'list_id');
    }

    public function template(): BelongsTo
    {
        return $this->belongsTo(Template::class);
    }

    public function trackingDomain(): BelongsTo
    {
        return $this->belongsTo(TrackingDomain::class, 'tracking_domain_id');
    }

    public function bounceServer(): BelongsTo
    {
        return $this->belongsTo(BounceServer::class, 'bounce_server_id');
    }

    public function deliveryServer(): BelongsTo
    {
        return $this->belongsTo(DeliveryServer::class, 'delivery_server_id');
    }

    public function replyServer(): BelongsTo
    {
        return $this->belongsTo(ReplyServer::class, 'reply_server_id');
    }

    public function sendingDomain(): BelongsTo
    {
        return $this->belongsTo(SendingDomain::class, 'sending_domain_id');
    }

    public function bounceLogs(): HasMany
    {
        return $this->hasMany(BounceLog::class);
    }

    public function complaints(): HasMany
    {
        return $this->hasMany(Complaint::class);
    }

    public function tracking(): HasMany
    {
        return $this->hasMany(CampaignTracking::class);
    }

    public function variants(): HasMany
    {
        return $this->hasMany(CampaignVariant::class);
    }

    public function recipients(): HasMany
    {
        return $this->hasMany(CampaignRecipient::class);
    }

    public function replies(): HasMany
    {
        return $this->hasMany(CampaignReply::class);
    }

    public function logs(): HasMany
    {
        return $this->hasMany(CampaignLog::class);
    }

    public function hasAbTest(): bool
    {
        return $this->variants()->count() > 0;
    }

    public function isDraft(): bool
    {
        return $this->status === 'draft';
    }

    public function isScheduled(): bool
    {
        return $this->status === 'scheduled';
    }

    public function isQueued(): bool
    {
        return $this->status === 'queued';
    }

    public function isRunning(): bool
    {
        return $this->status === 'running';
    }

    public function isPaused(): bool
    {
        return $this->status === 'paused';
    }

    public function isStopped(): bool
    {
        return $this->status === 'stopped';
    }

    public function isCompleted(): bool
    {
        return $this->status === 'completed';
    }

    public function isFailed(): bool
    {
        return $this->status === 'failed';
    }

    public function canStart(): bool
    {
        return in_array($this->status, ['draft', 'scheduled']);
    }

    public function canPause(): bool
    {
        return $this->status === 'running';
    }

    public function canResume(): bool
    {
        return $this->status === 'paused';
    }

    public function canStop(): bool
    {
        return in_array($this->status, ['running', 'paused']);
    }

    public function incrementSentCount(): void
    {
        $this->increment('sent_count');
    }

    public function incrementOpenedCount(): void
    {
        $this->increment('opened_count');
        $this->updateRates();
    }

    public function incrementClickedCount(): void
    {
        $this->increment('clicked_count');
        $this->updateRates();
    }

    public function incrementFailedCount(): void
    {
        $this->increment('failed_count');
    }

    public function incrementBouncedCount(): void
    {
        $this->increment('bounced_count');
        $this->updateRates();
    }

    public function incrementRepliedCount(): void
    {
        $this->increment('replied_count');
    }

    protected function updateRates(): void
    {
        // Get unique opens from recipient statuses for accurate rate calculation
        $recipientStats = $this->recipients()
            ->selectRaw("
                SUM(CASE WHEN status = 'opened' OR status = 'clicked' THEN 1 ELSE 0 END) as unique_opens,
                SUM(CASE WHEN status = 'clicked' THEN 1 ELSE 0 END) as unique_clicks,
                SUM(CASE WHEN status = 'bounced' THEN 1 ELSE 0 END) as bounced
            ")
            ->first();
        
        $delivered = max(0, $this->sent_count - ($recipientStats->bounced ?? 0));
        
        if ($delivered > 0) {
            $this->open_rate = round((($recipientStats->unique_opens ?? 0) / $delivered) * 100, 2);
            $this->click_rate = round((($recipientStats->unique_clicks ?? 0) / $delivered) * 100, 2);
            $this->bounce_rate = round(($this->bounced_count / $this->sent_count) * 100, 2);
            $this->saveQuietly();
        }
    }

    public function checkCompletion(): void
    {
        $pendingCount = $this->recipients()
            ->where('status', 'pending')
            ->count();

        if ($pendingCount === 0 && $this->isRunning()) {
            $this->update([
                'status' => 'completed',
                'finished_at' => now(),
            ]);
        }
    }

    /**
     * Sync campaign statistics from recipient statuses.
     * This ensures sent_count, bounced_count, etc. match actual recipient statuses.
     */
    public function syncStats(): void
    {
        $stats = $this->recipients()
            ->selectRaw("
                COUNT(*) as total,
                SUM(CASE WHEN status = 'sent' OR status = 'opened' OR status = 'clicked' THEN 1 ELSE 0 END) as sent,
                SUM(CASE WHEN status = 'opened' OR status = 'clicked' THEN 1 ELSE 0 END) as opened,
                SUM(CASE WHEN status = 'clicked' THEN 1 ELSE 0 END) as clicked,
                SUM(CASE WHEN replied_at IS NOT NULL THEN 1 ELSE 0 END) as replied,
                SUM(CASE WHEN status = 'bounced' THEN 1 ELSE 0 END) as bounced,
                SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
                SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
            ")
            ->first();

        $this->update([
            'sent_count' => $stats->sent ?? 0,
            'opened_count' => $stats->opened ?? 0,
            'clicked_count' => $stats->clicked ?? 0,
            'replied_count' => $stats->replied ?? 0,
            'bounced_count' => $stats->bounced ?? 0,
            'failed_count' => $stats->failed ?? 0,
            'total_recipients' => $stats->total ?? 0,
        ]);

        $this->updateRates();
    }
}