toDateString(); $dryRun = (bool) $this->option('dry-run'); $overdueByStatus = Invoice::query() ->whereNotNull('due_date') ->whereDate('due_date', '<', $today) ->selectRaw('status, count(*) as total') ->groupBy('status') ->orderBy('status') ->get(); if ($overdueByStatus->isNotEmpty()) { $this->line('invoices:check-overdue | Past-due invoices by current status:'); foreach ($overdueByStatus as $row) { $label = InvoiceStatus::tryFrom((int) $row->status)?->label() ?? 'Không xác định'; $this->line(" - {$label} ({$row->status}): {$row->total}"); } } $invoices = Invoice::query() ->with('order:id,status,customer_id') ->whereNotNull('due_date') ->whereDate('due_date', '<', $today) ->whereIn('status', [InvoiceStatus::UNPAID->value, InvoiceStatus::OVERDUE->value]) ->get(); $this->info("invoices:check-overdue | Found {$invoices->count()} invoice(s) due before {$today}."); $updatedInvoices = 0; foreach ($invoices as $invoice) { $needsInvoiceUpdate = (int) $invoice->status !== InvoiceStatus::OVERDUE->value; $this->line(sprintf( ' [%s] Invoice #%s due %s%s', $dryRun ? 'dry-run' : 'check', $invoice->invoice_number, $invoice->due_date?->format('Y-m-d') ?? '-', $invoice->order_id ? " | order #{$invoice->order_id}" : '' )); if ($dryRun || ! $needsInvoiceUpdate) { continue; } // Chỉ đổi trạng thái hoá đơn. Trước đây command còn đẩy order về PENDING, nhưng hoá đơn // gia hạn dùng lại order_id gốc nên một kỳ gia hạn quá hạn sẽ kéo đơn đang chạy về // "Chờ xử lý" — vừa hiển thị sai, vừa khiến hasPendingOrder() khoá khách đặt đơn mới. DB::transaction(function () use ($invoice, &$updatedInvoices) { $invoice->update(['status' => InvoiceStatus::OVERDUE->value]); $updatedInvoices++; }); Log::info('Invoice marked overdue', [ 'invoice_id' => $invoice->id, 'invoice_number' => $invoice->invoice_number, 'order_id' => $invoice->order_id, ]); } $this->info("invoices:check-overdue | Updated {$updatedInvoices} invoice(s)."); return Command::SUCCESS; } }