504 lines
21 KiB
PHP
504 lines
21 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Client;
|
|
use App\Models\ClientProjectActivities;
|
|
use App\Models\ClientUserAssignation;
|
|
use App\Models\ClientInvoiceAdjustment;
|
|
use App\Models\ClientCustomer;
|
|
use App\Models\GoogleCampaignMetric;
|
|
use App\Models\User;
|
|
use App\Models\ClientInvoice;
|
|
use App\Services\GoogleAdsService;
|
|
use App\Services\UserHierarchyService;
|
|
use Carbon\Carbon;
|
|
use Inertia\Inertia;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log as FacadesLog;
|
|
use Rap2hpoutre\FastExcel\FastExcel;
|
|
|
|
class GoogleAdsController extends Controller
|
|
{
|
|
protected $adsService;
|
|
private const GOOGLE_COMPANY_SYNC_LOCK = 'google-ads:get-company-details:running';
|
|
|
|
public function __construct(
|
|
GoogleAdsService $adsService,
|
|
private UserHierarchyService $hierarchyService,
|
|
)
|
|
{
|
|
$this->adsService = $adsService;
|
|
}
|
|
|
|
// List all accounts under MCC
|
|
public function accounts()
|
|
{
|
|
// $accounts = collect($this->adsService->listAccounts())
|
|
// ->map(function (array $account) {
|
|
// $account['customer_id'] = (string) ($account['customer_id'] ?? $account['id']);
|
|
// return $account;
|
|
// });
|
|
// $customerMap = $accounts->keyBy('customer_id');
|
|
$localClients = $this->hierarchyService
|
|
->scopeClientsVisibleTo(Client::query(), Auth::user())
|
|
->with('assignations.user', 'customers', 'invoices', 'invoiceAdjustments')
|
|
->get();
|
|
$customerMap = $localClients->map(function ($data) {
|
|
$assignedPerson = $data->assignations->firstWhere('role', ClientUserAssignation::ROLE_ASSIGNED_PERSON);
|
|
$salesPerson = $data->assignations->firstWhere('role', ClientUserAssignation::ROLE_SALES_PERSON);
|
|
$data['industry'] = $data->industry;
|
|
$data['sql_acc_code'] = implode(',', $data->customers->pluck('sql_acc_code')->toArray());
|
|
$data['assigned_person'] = $assignedPerson?->user?->name;
|
|
$data['sales_person'] = $salesPerson?->user?->name;
|
|
$data['latest_remaining_amount'] = $data->latestRemainingAmount(function (ClientInvoice $invoice) use ($data) {
|
|
if (empty($invoice->start_date) || empty($invoice->end_date)) {
|
|
return 0;
|
|
}
|
|
|
|
return GoogleCampaignMetric::query()
|
|
->join('google_campaigns', 'google_campaign_metrics.google_campaign_id', '=', 'google_campaigns.id')
|
|
->where('google_campaigns.client_id', $data->id)
|
|
->whereNull('google_campaigns.deleted_at')
|
|
->whereNull('google_campaign_metrics.deleted_at')
|
|
->whereBetween('google_campaign_metrics.date', [
|
|
$invoice->start_date?->toDateString(),
|
|
$invoice->end_date?->toDateString(),
|
|
])
|
|
->sum('google_campaign_metrics.actual_spend');
|
|
});
|
|
return $data;
|
|
});
|
|
return Inertia::render('campaigns/index', [
|
|
'clients' => $customerMap->values()->all(),
|
|
'googleCompanySyncRunning' => Cache::has(self::GOOGLE_COMPANY_SYNC_LOCK),
|
|
]);
|
|
}
|
|
|
|
public function syncGoogleCompanyDetails(): RedirectResponse
|
|
{
|
|
$started = Cache::add(self::GOOGLE_COMPANY_SYNC_LOCK, [
|
|
'user_id' => Auth::id(),
|
|
'started_at' => now()->toDateTimeString(),
|
|
], now()->addHour());
|
|
|
|
if (! $started) {
|
|
return redirect()
|
|
->back()
|
|
->with('message-warning', 'Google account sync is already running. Please wait for it to finish.');
|
|
}
|
|
|
|
try {
|
|
$exitCode = Artisan::call('google-ads:get-company-details');
|
|
|
|
if ($exitCode !== 0) {
|
|
return redirect()
|
|
->back()
|
|
->with('message-error', 'Google account sync failed. Please check the logs.');
|
|
}
|
|
|
|
return redirect()
|
|
->back()
|
|
->with('message-info', 'Google account records have been synced.');
|
|
} finally {
|
|
Cache::forget(self::GOOGLE_COMPANY_SYNC_LOCK);
|
|
}
|
|
}
|
|
|
|
public function show($id)
|
|
{
|
|
// $account = $this->adsService->getAccountDetails($id);
|
|
$client = Client::where('customer_id', $id)->firstOrFail();
|
|
abort_unless($this->hierarchyService->canViewClient(Auth::user(), $client), 403);
|
|
|
|
$account = [
|
|
'id' => $client->customer_id,
|
|
'name' => $client->name,
|
|
'status' => $client->status,
|
|
'time_zone' => $client->time_zone,
|
|
];
|
|
[$localClient, $assignments, $users, $invoices, $activityList, $pendingActivities, $completedActivities, $lifeTimeSpending, $clientAdjustments] =
|
|
$this->hydrateClient($account);
|
|
$account = array_merge($account, [
|
|
'industry' => $localClient->industry,
|
|
'sql_acc_code' => $localClient->sql_acc_code,
|
|
'activities_list' => $activityList,
|
|
]);
|
|
|
|
return Inertia::render('campaigns/show', [
|
|
'id' => $id,
|
|
'client' => $account,
|
|
'invoices' => $invoices,
|
|
'localClientId' => $localClient->id,
|
|
'clientAssignmentRoles' => ClientUserAssignation::roles(),
|
|
'clientAssignments' => $assignments,
|
|
'assignmentUsers' => $users,
|
|
'clientInvoices' => $invoices,
|
|
'clientAdjustments' => $clientAdjustments,
|
|
'pendingActivities' => $pendingActivities,
|
|
'completedActivities' => $completedActivities,
|
|
'lifeTimeSpending' => number_format($lifeTimeSpending, 2, '.', ','),
|
|
]);
|
|
}
|
|
|
|
public function edit($id)
|
|
{
|
|
$account = $this->adsService->getAccountDetails($id);
|
|
[$localClient, $assignments, $users, $invoices] = $this->hydrateClient($account);
|
|
abort_unless($this->hierarchyService->canViewClient(Auth::user(), $localClient), 403);
|
|
|
|
$account = array_merge($account, [
|
|
'name' => $localClient->name,
|
|
'customer_id' => $localClient->customer_id,
|
|
'status' => $localClient->status,
|
|
'time_zone' => $localClient->time_zone,
|
|
'industry' => $localClient->industry,
|
|
'sql_acc_code' => $localClient->sql_acc_code,
|
|
]);
|
|
|
|
return Inertia::render('campaigns/account/edit', [
|
|
'id' => $id,
|
|
'client' => $account,
|
|
'clientAssignmentRoles' => ClientUserAssignation::roles(),
|
|
'clientAssignments' => $assignments,
|
|
'assignmentUsers' => $users,
|
|
'clientInvoices' => $invoices,
|
|
]);
|
|
}
|
|
|
|
public function updateAccount(Request $request, $id)
|
|
{
|
|
[$localClient] = $this->hydrateClient($this->adsService->getAccountDetails($id));
|
|
abort_unless($this->hierarchyService->canViewClient(Auth::user(), $localClient), 403);
|
|
|
|
$validated = $request->validate([
|
|
'name' => ['required', 'string'],
|
|
'customer_id' => ['required', 'string', 'unique:clients,customer_id,'.$localClient->id],
|
|
'industry' => ['nullable', 'string'],
|
|
'sql_acc_code' => ['nullable', 'string'],
|
|
'assigned_person' => ['nullable', 'integer', 'exists:users,id'],
|
|
'sales_person' => ['nullable', 'integer', 'exists:users,id'],
|
|
]);
|
|
|
|
$localClient->update([
|
|
'name' => $validated['name'],
|
|
'customer_id' => $validated['customer_id'],
|
|
'industry' => $validated['industry'] ?? null,
|
|
'sql_acc_code' => $validated['sql_acc_code'] ?? null,
|
|
]);
|
|
|
|
$localClient->customers()->delete();
|
|
|
|
if (! empty($validated['sql_acc_code'])) {
|
|
collect(explode(',', $validated['sql_acc_code']))
|
|
->map(fn (string $sqlAccCode) => trim($sqlAccCode))
|
|
->filter()
|
|
->unique()
|
|
->each(function (string $sqlAccCode) use ($localClient) {
|
|
ClientCustomer::create([
|
|
'client_id' => $localClient->id,
|
|
'sql_acc_code' => $sqlAccCode,
|
|
]);
|
|
});
|
|
}
|
|
|
|
$assignmentValues = [
|
|
ClientUserAssignation::ROLE_ASSIGNED_PERSON => $validated['assigned_person'] ?? null,
|
|
ClientUserAssignation::ROLE_SALES_PERSON => $validated['sales_person'] ?? null,
|
|
];
|
|
|
|
foreach ($assignmentValues as $role => $userId) {
|
|
if ($userId === null) {
|
|
$localClient->assignations()->where('role', $role)->delete();
|
|
continue;
|
|
}
|
|
|
|
$localClient->assignations()->updateOrCreate(
|
|
['role' => $role],
|
|
['user_id' => $userId]
|
|
);
|
|
}
|
|
|
|
return redirect()
|
|
->route('google-ads.accounts.edit', ['id' => $localClient->customer_id])
|
|
->with('message-info', 'Account details updated.');
|
|
}
|
|
|
|
public function campaigns($id)
|
|
{
|
|
$campaigns = $this->adsService->listCampaigns($id);
|
|
return response()->json($campaigns);
|
|
}
|
|
|
|
public function listCampaignsMetrics($id, $startDate, $endDate)
|
|
{
|
|
$campaigns = $this->adsService->listCampaignsMetrics($id, $startDate, $endDate);
|
|
return response()->json($campaigns);
|
|
}
|
|
|
|
private function hydrateClient(array $account): array
|
|
{
|
|
$localClient = Client::updateOrCreate(
|
|
['customer_id' => $account['id']],
|
|
[
|
|
'name' => $account['name'],
|
|
'status' => $account['status'],
|
|
'time_zone' => $account['time_zone'],
|
|
]
|
|
);
|
|
// dd($localClient);
|
|
$localClient->load(['assignations.user', 'invoices']);
|
|
|
|
$assignments = $localClient->assignations
|
|
->mapWithKeys(function (ClientUserAssignation $assignation) {
|
|
return [$assignation->role => $assignation->user_id];
|
|
})
|
|
->toArray();
|
|
|
|
$users = User::orderBy('name')
|
|
->get(['id', 'name', 'email'])
|
|
->map(function (User $user) {
|
|
return [
|
|
'value' => (string) $user->id,
|
|
'label' => trim($user->name.' ('.$user->email.')'),
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
$clientAdjustments = ClientInvoiceAdjustment::query()
|
|
->where('client_id', $localClient->id)
|
|
->orderByDesc('created_at')
|
|
->get()
|
|
->map(function (ClientInvoiceAdjustment $adjustment) {
|
|
return [
|
|
'id' => $adjustment->id,
|
|
'client_id' => $adjustment->client_id,
|
|
'entry_type' => $adjustment->entry_type,
|
|
'amount' => $adjustment->amount,
|
|
'remark' => $adjustment->remark,
|
|
'created_at' => $adjustment->created_at?->toDateTimeString(),
|
|
];
|
|
})
|
|
->values()
|
|
->all();
|
|
|
|
$invoices = $localClient->invoices
|
|
->map(function ($invoice) use ($account) {
|
|
$campaigns = $this->adsService->listCampaigns($account['id']);
|
|
$totalInvoiceSpend = 0;
|
|
FacadesLog::info('Hydrated client data', [
|
|
'campaigns' => $campaigns,
|
|
]);
|
|
foreach ($campaigns as $campaign) {
|
|
FacadesLog::info('Hydrated client data', [
|
|
'campaigns' => $campaign['id'],
|
|
]);
|
|
|
|
if (empty($invoice->start_date) || empty($invoice->end_date)) {
|
|
continue;
|
|
} else {
|
|
$metrics = $this->adsService->listCampaignsMetricsById(
|
|
$account['id'],
|
|
$campaign['id'],
|
|
$invoice->start_date?->toDateString() ?? null,
|
|
$invoice->end_date?->toDateString() ?? null
|
|
);
|
|
FacadesLog::info('Hydrated client data', [
|
|
'metrics' => $metrics,
|
|
]);
|
|
$totalSpend = array_sum(array_column($metrics, 'actual_spend'));
|
|
$totalInvoiceSpend += $totalSpend;
|
|
}
|
|
}
|
|
return [
|
|
'id' => $invoice->id,
|
|
'client_id' => $invoice->client_id,
|
|
'invoice_no' => $invoice->invoice_no,
|
|
'linked_invoice_id' => $invoice->linked_invoice_id,
|
|
'is_credit_card' => $invoice->is_credit_card,
|
|
'is_paid' => $invoice->is_paid,
|
|
'start_date' => $invoice->start_date?->toDateString(),
|
|
'end_date' => $invoice->end_date?->toDateString(),
|
|
'payment_no' => $invoice->payment_no,
|
|
'amount' => $invoice->amount,
|
|
'total_spend' => number_format($totalInvoiceSpend, 2, '.', ''),
|
|
'management_fee' => $invoice->management_fee,
|
|
'management_fee_amount' => $invoice->management_fee_amount,
|
|
'management_fee_tax' => $invoice->management_fee_tax,
|
|
'media_fee' => $invoice->media_fee,
|
|
'media_fee_amount' => $invoice->media_fee_amount,
|
|
'media_fee_tax' => $invoice->media_fee_tax,
|
|
'tax_percent' => $invoice->tax_percent,
|
|
'nett_amount' => $invoice->nett_amount,
|
|
'total_spending' => $invoice->total_spending,
|
|
];
|
|
})
|
|
->toArray();
|
|
|
|
// dd($invoices);
|
|
|
|
$campaigns = $this->adsService->listCampaigns($localClient->customer_id);
|
|
|
|
$lifeTimeSpend = 0;
|
|
|
|
if (! empty($campaigns)) {
|
|
foreach ($campaigns as $campaign) {
|
|
$metrics = $this->adsService->listCampaignsMetricsById(
|
|
$localClient->customer_id,
|
|
$campaign['id'],
|
|
'1970-01-01', // Start date far in the past to capture all historical data
|
|
now()->toDateString() // End date as today`
|
|
);
|
|
$totalSpend = array_sum(array_column($metrics, 'actual_spend'));
|
|
$lifeTimeSpend += number_format($totalSpend, 2, '.', '');
|
|
}
|
|
}
|
|
|
|
$activities = $localClient->activitiesList()
|
|
->orderByDesc('created_at')
|
|
->get()
|
|
->map(function (ClientProjectActivities $activity) {
|
|
return [
|
|
'id' => $activity->id,
|
|
'activity_no' => $activity->activity_no,
|
|
'activity_type' => $activity->activity_type,
|
|
'task_description' => $activity->task_description,
|
|
'estimated_completed_at' => data_get($activity, 'estimated_completed_at'),
|
|
'completed_at' => data_get($activity, 'completed_at'),
|
|
'notification_sent_at' => data_get($activity, 'notification_sent_at'),
|
|
'notification_status' => data_get($activity, 'notification_status'),
|
|
'note_to_customer' => data_get($activity, 'note_to_customer'),
|
|
'assignation' => null,
|
|
];
|
|
});
|
|
|
|
$activitiesList = $activities->values()->all();
|
|
$pendingActivities = $activities
|
|
->filter(function (array $activity) {
|
|
return empty($activity['completed_at']) && ! empty($activity['estimated_completed_at']);
|
|
})
|
|
->values()
|
|
->all();
|
|
$completedActivities = $activities
|
|
->filter(function (array $activity) {
|
|
return ! empty($activity['completed_at']);
|
|
})
|
|
->values()
|
|
->all();
|
|
FacadesLog::info('Hydrated client data', [
|
|
'localClient' => $localClient->toArray(),
|
|
'assignments' => $assignments,
|
|
'users' => $users,
|
|
'invoices' => $invoices,
|
|
]);
|
|
return [
|
|
$localClient,
|
|
$assignments,
|
|
$users,
|
|
$invoices,
|
|
$activitiesList,
|
|
$pendingActivities,
|
|
$completedActivities,
|
|
$lifeTimeSpend,
|
|
$clientAdjustments,
|
|
];
|
|
}
|
|
|
|
public function insertCSVDataToDB()
|
|
{
|
|
$collection = (new FastExcel)->import(storage_path('app/public/csv/Fixed_HJ.csv'));
|
|
$array = $collection->toArray();
|
|
foreach ($array as $row) {
|
|
$startDate = Carbon::parse($row['start_date'])->format('Y-m-d');
|
|
$endDate = Carbon::parse($row['end_date'])->format('Y-m-d');
|
|
$client = Client::where('customer_id', str_replace('-', '', $row['customer_id']))->first();
|
|
// dd($row);
|
|
if ($client) {
|
|
$salesUser = User::where('name', $row['sales'])->first();
|
|
$pic = User::where('name', $row['pic'])->first();
|
|
if ($pic) {
|
|
ClientUserAssignation::updateOrCreate(
|
|
[
|
|
'client_id' => $client->id,
|
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
|
],
|
|
[
|
|
'user_id' => $pic->id,
|
|
]
|
|
);
|
|
}
|
|
if ($salesUser) {
|
|
ClientUserAssignation::updateOrCreate(
|
|
[
|
|
'client_id' => $client->id,
|
|
'role' => ClientUserAssignation::ROLE_SALES_PERSON,
|
|
],
|
|
[
|
|
'user_id' => $salesUser->id,
|
|
]
|
|
);
|
|
}
|
|
$row['client_id'] = $client->id;
|
|
$spend = 0;
|
|
if ($client->status != 'CANCELED') {
|
|
$campaigns = $this->adsService->listCampaigns($row['customer_id']);
|
|
FacadesLog::info('Hydrated client data', [
|
|
'campaigns' => $campaigns,
|
|
]);
|
|
foreach ($campaigns as $campaign) {
|
|
FacadesLog::info('Hydrated client data', [
|
|
'campaigns' => $campaign['id'],
|
|
]);
|
|
|
|
if (empty($startDate) || empty($endDate)) {
|
|
continue;
|
|
} else {
|
|
$metrics = $this->adsService->listCampaignsMetricsById(
|
|
$row['customer_id'],
|
|
$campaign['id'],
|
|
$startDate,
|
|
$endDate
|
|
);
|
|
FacadesLog::info('Hydrated client data', [
|
|
'metrics' => $metrics,
|
|
]);
|
|
$totalSpend = array_sum(array_column($metrics, 'actual_spend'));
|
|
$spend += $totalSpend;
|
|
}
|
|
}
|
|
}
|
|
$managementFee = intval($row['management_fee']);
|
|
$mediaFee = intval($row['media_fee']);
|
|
$managementFeeAmount = $managementFee > 0 ? $managementFee / 1.08 : 0;
|
|
$mediaFeeAmount = $mediaFee > 0 ? $mediaFee / 1.08 : 0;
|
|
|
|
ClientInvoice::updateOrCreate(
|
|
['invoice_no' => $row['invoice_no']],
|
|
[
|
|
'client_id' => $row['client_id'],
|
|
'is_credit_card' => $mediaFee == 0 ? 1 : 0,
|
|
'start_date' => $startDate,
|
|
'end_date' => $endDate,
|
|
'management_fee' => $managementFee,
|
|
'management_fee_amount' => $managementFeeAmount,
|
|
'management_fee_tax' => $managementFee - $managementFeeAmount,
|
|
'media_fee' => $mediaFee,
|
|
'media_fee_amount' => $mediaFeeAmount,
|
|
'media_fee_tax' => $mediaFee - $mediaFeeAmount,
|
|
'tax_percent' => 8,
|
|
'nett_amount' => $mediaFeeAmount,
|
|
'total_spending' => $spend,
|
|
]
|
|
);
|
|
} else {
|
|
FacadesLog::warning('Client not found for customer_id: '.str_replace('-', '', $row['customer_id']));
|
|
continue; // Skip this row if client not found
|
|
}
|
|
}
|
|
}
|
|
}
|