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/Model/AdCampaign.php
<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Library\Traits\HasUid;

/**
 * @property int $id
 * @property string $uid
 * @property int $customer_id
 * @property string $name
 * @property string|null $objective
 * @property int|null $ad_creative_id
 * @property int|null $mail_list_id
 * @property string $status
 */
class AdCampaign extends Model
{
    use HasFactory;
    use HasUid;
    use SoftDeletes;

    // Objectives
    const OBJECTIVE_AWARENESS = 'awareness';
    const OBJECTIVE_TRAFFIC = 'traffic';
    const OBJECTIVE_ENGAGEMENT = 'engagement';
    const OBJECTIVE_LEADS = 'leads';
    const OBJECTIVE_SALES = 'sales';
    const OBJECTIVE_APP_INSTALLS = 'app_installs';

    const OBJECTIVES = [
        self::OBJECTIVE_AWARENESS,
        self::OBJECTIVE_TRAFFIC,
        self::OBJECTIVE_ENGAGEMENT,
        self::OBJECTIVE_LEADS,
        self::OBJECTIVE_SALES,
        self::OBJECTIVE_APP_INSTALLS,
    ];

    const OBJECTIVE_LABELS = [
        self::OBJECTIVE_AWARENESS => 'Brand Awareness',
        self::OBJECTIVE_TRAFFIC => 'Website Traffic',
        self::OBJECTIVE_ENGAGEMENT => 'Engagement',
        self::OBJECTIVE_LEADS => 'Lead Generation',
        self::OBJECTIVE_SALES => 'Sales / Conversions',
        self::OBJECTIVE_APP_INSTALLS => 'App Installs',
    ];

    // Statuses
    const STATUS_DRAFT = 'draft';
    const STATUS_REVIEW = 'review';
    const STATUS_ACTIVE = 'active';
    const STATUS_PAUSED = 'paused';
    const STATUS_COMPLETED = 'completed';
    const STATUS_ERROR = 'error';
    const STATUS_ARCHIVED = 'archived';

    const STATUSES = [
        self::STATUS_DRAFT,
        self::STATUS_REVIEW,
        self::STATUS_ACTIVE,
        self::STATUS_PAUSED,
        self::STATUS_COMPLETED,
        self::STATUS_ERROR,
        self::STATUS_ARCHIVED,
    ];

    const STATUS_BADGE_MAP = [
        self::STATUS_DRAFT => 'orange',
        self::STATUS_REVIEW => 'blue',
        self::STATUS_ACTIVE => 'green',
        self::STATUS_PAUSED => 'orange',
        self::STATUS_COMPLETED => 'green',
        self::STATUS_ERROR => 'red',
        self::STATUS_ARCHIVED => 'blue',
    ];

    // Bid strategies
    const BID_AUTO = 'auto';
    const BID_LOWEST_COST = 'lowest_cost';
    const BID_COST_CAP = 'cost_cap';
    const BID_BID_CAP = 'bid_cap';

    const BID_STRATEGIES = [
        self::BID_AUTO,
        self::BID_LOWEST_COST,
        self::BID_COST_CAP,
        self::BID_BID_CAP,
    ];

    protected $table = 'ad_campaigns';

    protected $fillable = [
        'uid',
        'customer_id',
        'name',
        'objective',
        'ad_creative_id',
        'ad_audience_id',
        'mail_list_id',
        'schedule_start',
        'schedule_end',
        'daily_budget',
        'lifetime_budget',
        'budget_currency',
        'bid_strategy',
        'bid_amount',
        'utm_source',
        'utm_medium',
        'utm_campaign',
        'settings',
        'published_at',
    ];

    protected $casts = [
        'settings' => 'json',
        'schedule_start' => 'datetime',
        'schedule_end' => 'datetime',
        'published_at' => 'datetime',
        'daily_budget' => 'decimal:2',
        'lifetime_budget' => 'decimal:2',
        'bid_amount' => 'decimal:2',
    ];

    // ── Relations ──────────────────────────────────────────────────────────

    public function customer()
    {
        return $this->belongsTo(Customer::class, 'customer_id');
    }

    public function creative()
    {
        return $this->belongsTo(AdCreative::class, 'ad_creative_id');
    }

    /**
     * R5.1 — direct audience binding (separate from mail_list which targets
     * an Acelle list).  Nullable: legacy campaigns leave it null.
     */
    public function audience()
    {
        return $this->belongsTo(AdAudience::class, 'ad_audience_id');
    }

    public function mailList()
    {
        return $this->belongsTo(MailList::class, 'mail_list_id');
    }

    public function campaignPlatforms()
    {
        return $this->hasMany(AdCampaignPlatform::class, 'ad_campaign_id');
    }

    public function platforms()
    {
        return $this->belongsToMany(AdPlatform::class, 'ad_campaign_platforms', 'ad_campaign_id', 'ad_platform_id')
            ->withPivot('status', 'remote_campaign_id', 'remote_adset_id', 'remote_ad_id')
            ->withTimestamps();
    }

    public function activityLogs()
    {
        return $this->morphMany(AdActivityLog::class, 'loggable');
    }

    // ── Scopes ─────────────────────────────────────────────────────────────

    public function scopeOfCustomer($query, $customerId)
    {
        return $query->where('customer_id', $customerId);
    }

    public function scopeActive($query)
    {
        return $query->where('status', self::STATUS_ACTIVE);
    }

    public function scopeDraft($query)
    {
        return $query->where('status', self::STATUS_DRAFT);
    }

    public function scopeSearch($query, $keyword)
    {
        if (!$keyword) {
            return $query;
        }

        return $query->where('name', 'like', '%' . $keyword . '%');
    }

    public function scopeOfStatus($query, $status)
    {
        if (!$status) {
            return $query;
        }

        return $query->where('status', $status);
    }

    public function scopeOfObjective($query, $objective)
    {
        if (!$objective) {
            return $query;
        }

        return $query->where('objective', $objective);
    }

    // ── Accessors ──────────────────────────────────────────────────────────

    public function getObjectiveLabelAttribute(): string
    {
        return self::OBJECTIVE_LABELS[$this->objective] ?? ucfirst($this->objective);
    }

    public function getStatusBadgeAttribute(): string
    {
        return self::STATUS_BADGE_MAP[$this->status] ?? 'blue';
    }

    public function getBudgetDisplayAttribute(): string
    {
        if ($this->daily_budget) {
            return $this->budget_currency . ' ' . number_format($this->daily_budget, 2) . '/day';
        }
        if ($this->lifetime_budget) {
            return $this->budget_currency . ' ' . number_format($this->lifetime_budget, 2) . ' total';
        }

        return '-';
    }

    // ── Helpers ─────────────────────────────────────────────────────────────

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

    public function isActive(): bool
    {
        return $this->status === self::STATUS_ACTIVE;
    }

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

    public function isArchived(): bool
    {
        return $this->status === self::STATUS_ARCHIVED;
    }

    public function getPlatformCount(): int
    {
        return $this->campaignPlatforms()->count();
    }

    // ── Factory ─────────────────────────────────────────────────────────────

    protected static function newFactory()
    {
        return \Database\Factories\AdCampaignFactory::new();
    }
}