inspiren-sem-tool/app/Console/Commands/RepairLinkedInvoicePaymentItems.php

120 lines
4.7 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\ClientInvoicePayment;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class RepairLinkedInvoicePaymentItems extends Command
{
protected $signature = 'customer:repair-linked-invoice-payment-items
{--invoice-id=* : Only repair these referenced linked invoice IDs}
{--dry-run : Show changes without saving them}';
protected $description = 'Move media item date ranges and spending to management items on invoices referenced by linked_invoice_id, then remove media items.';
public function handle(): int
{
$dryRun = (bool) $this->option('dry-run');
$invoiceIds = collect($this->option('invoice-id'))
->filter(fn ($id) => is_numeric($id))
->map(fn ($id) => (int) $id)
->values()
->all();
$merged = 0;
$removed = 0;
$skipped = 0;
ClientInvoicePayment::query()
->with([
'invoice:id,invoice_no,linked_invoice_id',
'items' => fn ($query) => $query->orderBy('id'),
])
->whereIn('client_invoice_id', function ($query) use ($invoiceIds) {
$query
->select('linked_invoice_id')
->distinct()
->from('client_invoices')
->whereNotNull('linked_invoice_id');
if ($invoiceIds !== []) {
$query->whereIn('linked_invoice_id', $invoiceIds);
}
})
->whereHas('items', fn (Builder $query) => $query->where('billing_item_types_id', 1))
->orderBy('id')
->chunkById(100, function ($payments) use ($dryRun, &$merged, &$removed, &$skipped) {
foreach ($payments as $payment) {
$mediaItems = $payment->items->where('billing_item_types_id', 1)->values();
$managementItems = $payment->items->where('billing_item_types_id', 2)->values();
if ($managementItems->count() !== 1) {
$skipped += $mediaItems->count();
$this->warn(sprintf(
'Skipping payment %d on invoice %s: expected 1 management item, found %d.',
$payment->id,
$payment->invoice?->invoice_no ?? $payment->client_invoice_id,
$managementItems->count(),
));
continue;
}
$managementItem = $managementItems->first();
$startDate = $mediaItems
->map(fn ($item) => $item->start_date?->toDateString())
->filter()
->min();
$endDate = $mediaItems
->map(fn ($item) => $item->end_date?->toDateString())
->filter()
->max();
$spending = (float) $managementItem->spending
+ $mediaItems->sum(fn ($item) => (float) $item->spending);
$this->line(sprintf(
'%s payment %d: %d media item%s -> management item %d (%s to %s, spending RM %.2f).',
$dryRun ? 'Would merge' : 'Merging',
$payment->id,
$mediaItems->count(),
$mediaItems->count() === 1 ? '' : 's',
$managementItem->id,
$startDate ?? 'null',
$endDate ?? 'null',
$spending,
));
if (! $dryRun) {
DB::transaction(function () use ($managementItem, $mediaItems, $startDate, $endDate, $spending) {
$managementItem->forceFill([
'start_date' => $startDate,
'end_date' => $endDate,
'spending' => $spending,
])->save();
$mediaItems->each->delete();
});
}
$merged += $mediaItems->count();
$removed += $mediaItems->count();
}
});
$this->info(sprintf(
'Done. %d media item%s merged into management, %d media item%s removed, %d skipped.',
$merged,
$merged === 1 ? '' : 's',
$removed,
$removed === 1 ? '' : 's',
$skipped,
));
return self::SUCCESS;
}
}