option('dry-run'); $spendCache = []; $updated = 0; $skipped = 0; $failed = 0; ClientInvoicePaymentItem::query() ->with('payment.invoice.client') ->whereNotNull('start_date') ->whereNotNull('end_date') ->orderBy('id') ->chunkById(50, function ($items) use ($adsService, $dryRun, &$spendCache, &$updated, &$skipped, &$failed) { foreach ($items as $item) { $client = $item->payment?->invoice?->client; if ($client === null || empty($client->customer_id)) { $skipped++; $this->warn("Skipping item {$item->id}: missing client/customer ID."); continue; } $startDate = $item->start_date?->format('Y-m-d'); $endDate = $item->end_date?->format('Y-m-d'); if ($startDate === null || $endDate === null) { $skipped++; continue; } $cacheKey = implode('|', [$client->customer_id, $startDate, $endDate]); try { if (! array_key_exists($cacheKey, $spendCache)) { $spendCache[$cacheKey] = $this->spendForDateRange( $adsService, $client->customer_id, $startDate, $endDate, ); } $spending = $spendCache[$cacheKey]; if (! $dryRun) { $item->forceFill([ 'spending' => $spending, ])->save(); } $updated++; $this->line(sprintf( '%s item %d: RM %.2f (%s to %s)', $dryRun ? 'Calculated' : 'Updated', $item->id, $spending, $startDate, $endDate, )); } catch (\Throwable $exception) { $failed++; Log::error('Unable to calculate invoice payment item spending.', [ 'client_invoice_payment_item_id' => $item->id, 'customer_id' => $client->customer_id, 'start_date' => $startDate, 'end_date' => $endDate, 'message' => $exception->getMessage(), ]); $this->error("Failed item {$item->id}: {$exception->getMessage()}"); } } }); $this->info("Done. {$updated} calculated, {$skipped} skipped, {$failed} failed."); return $failed > 0 ? self::FAILURE : self::SUCCESS; } private function spendForDateRange( GoogleAdsService $adsService, string $customerId, string $startDate, string $endDate, ): float { $campaigns = $adsService->listCampaigns($customerId); $spending = 0.0; foreach ($campaigns as $campaign) { $metrics = $adsService->listCampaignsMetricsById( $customerId, (string) $campaign['id'], $startDate, $endDate, ); $spending += array_sum(array_map( fn (array $metric): float => (float) ($metric['actual_spend'] ?? 0), $metrics, )); } return round($spending, 6); } }