Files
portal-api/app/Console/Commands/CheckOverdueInvoices.php
T
2026-09-09 16:56:22 +07:00

94 lines
3.5 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Enums\Invoice\InvoiceStatus;
use App\Enums\Order\OrderStatus;
use App\Models\Invoice;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CheckOverdueInvoices extends Command
{
protected $signature = 'invoices:check-overdue {--dry-run : Preview invoices without updating data}';
protected $description = 'Mark unpaid invoices past due date as overdue and keep related orders unpaid.';
public function handle(): int
{
$today = now()->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()
// customer_id must be selected too: OrderObserver::updated() reads it when
// logging the status change below, and a NULL there violates log.customer_id NOT NULL.
->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;
$updatedOrders = 0;
foreach ($invoices as $invoice) {
$needsInvoiceUpdate = (int) $invoice->status !== InvoiceStatus::OVERDUE->value;
$order = $invoice->order;
$needsOrderUpdate = $order
&& ! in_array((int) $order->status, [OrderStatus::PENDING->value, OrderStatus::CANCELLED->value], true);
$this->line(sprintf(
' [%s] Invoice #%s due %s%s',
$dryRun ? 'dry-run' : 'check',
$invoice->invoice_number,
$invoice->due_date?->format('Y-m-d') ?? '-',
$order ? " | order #{$invoice->order_id}" : ''
));
if ($dryRun || (! $needsInvoiceUpdate && ! $needsOrderUpdate)) {
continue;
}
DB::transaction(function () use ($invoice, $order, $needsInvoiceUpdate, $needsOrderUpdate, &$updatedInvoices, &$updatedOrders) {
if ($needsInvoiceUpdate) {
$invoice->update(['status' => InvoiceStatus::OVERDUE->value]);
$updatedInvoices++;
}
if ($needsOrderUpdate && $order) {
$order->update(['status' => OrderStatus::PENDING->value]);
$updatedOrders++;
}
});
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), {$updatedOrders} order(s).");
return Command::SUCCESS;
}
}