File: /home/xedaptot/ai.naniguide.com/app/Services/Ads/AdCampaignService.php
<?php
namespace App\Services\Ads;
use App\Model\AdAudience;
use App\Model\AdCampaign;
use App\Model\AdCreative;
use App\Model\AdPlatform;
use App\Model\LocalCustomer;
use Illuminate\Support\Str;
class AdCampaignService
{
public function create(LocalCustomer $customer, array $data): AdCampaign
{
$campaign = new AdCampaign();
$campaign->uid = uniqid();
$campaign->customer_id = $customer->id;
$campaign->name = $data['name'];
$campaign->objective = $data['objective'];
$campaign->status = AdCampaign::STATUS_DRAFT;
if (isset($data['daily_budget'])) {
$campaign->daily_budget = $data['daily_budget'];
}
if (isset($data['lifetime_budget'])) {
$campaign->lifetime_budget = $data['lifetime_budget'];
}
if (isset($data['budget_currency'])) {
$campaign->budget_currency = $data['budget_currency'];
}
if (isset($data['bid_strategy'])) {
$campaign->bid_strategy = $data['bid_strategy'];
}
if (isset($data['schedule_start'])) {
$campaign->schedule_start = $data['schedule_start'];
}
if (isset($data['schedule_end'])) {
$campaign->schedule_end = $data['schedule_end'];
}
if (isset($data['utm_campaign'])) {
$campaign->utm_campaign = $data['utm_campaign'];
}
// R5.1 — wizard-driven creative + audience selection. FormRequest
// already enforces tenant scope on the UIDs; here we just resolve
// them to row IDs and set the FKs. Both nullable: empty wizard
// step yields null binding.
if (!empty($data['ad_creative_uid'])) {
$creative = AdCreative::where('uid', $data['ad_creative_uid'])
->where('customer_id', $customer->id)
->first();
if ($creative !== null) {
$campaign->ad_creative_id = $creative->id;
}
}
if (!empty($data['ad_audience_uid'])) {
$audience = AdAudience::where('uid', $data['ad_audience_uid'])
->where('customer_id', $customer->id)
->first();
if ($audience !== null) {
$campaign->ad_audience_id = $audience->id;
}
}
$campaign->save();
// R5.1 — wizard-driven multi-platform attach. FormRequest already
// tenant-scoped each UID; we attach pivot rows after save() so the
// belongsToMany sync has a campaign id to anchor on.
if (!empty($data['ad_platform_uids']) && is_array($data['ad_platform_uids'])) {
$platformIds = AdPlatform::whereIn('uid', $data['ad_platform_uids'])
->where('customer_id', $customer->id)
->where('status', AdPlatform::STATUS_ACTIVE)
->pluck('id')
->all();
if ($platformIds !== []) {
$campaign->platforms()->sync($platformIds);
}
}
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.created',
status: 'success',
title: 'Campaign created: ' . $campaign->name,
platform: null,
);
return $campaign;
}
public function update(AdCampaign $campaign, array $data): AdCampaign
{
$fillable = ['name', 'objective', 'daily_budget', 'lifetime_budget',
'budget_currency', 'bid_strategy', 'bid_amount',
'schedule_start', 'schedule_end', 'utm_source', 'utm_medium', 'utm_campaign'];
foreach ($fillable as $field) {
if (array_key_exists($field, $data)) {
$campaign->{$field} = $data[$field];
}
}
$campaign->save();
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.updated',
status: 'success',
title: 'Campaign updated: ' . $campaign->name,
);
return $campaign;
}
public function delete(AdCampaign $campaign): void
{
$name = $campaign->name;
$campaign->delete();
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.deleted',
status: 'info',
title: 'Campaign deleted: ' . $name,
);
}
public function copy(AdCampaign $campaign): AdCampaign
{
$copy = $campaign->replicate(['uid', 'published_at']);
$copy->uid = uniqid();
$copy->name = $campaign->name . ' (Copy)';
$copy->status = AdCampaign::STATUS_DRAFT;
$copy->published_at = null;
$copy->save();
AdActivityLogger::log(
entity: $copy,
action: 'campaign.copied',
status: 'success',
title: 'Campaign copied from: ' . $campaign->name,
);
return $copy;
}
public function archive(AdCampaign $campaign): void
{
$campaign->status = AdCampaign::STATUS_ARCHIVED;
$campaign->save();
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.archived',
status: 'info',
title: 'Campaign archived: ' . $campaign->name,
);
}
public function pause(AdCampaign $campaign): void
{
$campaign->status = AdCampaign::STATUS_PAUSED;
$campaign->save();
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.paused',
status: 'info',
title: 'Campaign paused: ' . $campaign->name,
);
}
public function resume(AdCampaign $campaign): void
{
$campaign->status = AdCampaign::STATUS_ACTIVE;
$campaign->save();
AdActivityLogger::log(
entity: $campaign,
action: 'campaign.resumed',
status: 'success',
title: 'Campaign resumed: ' . $campaign->name,
);
}
}