allocations()->sum('amount'); } public function creditedTotal(Invoice $invoice): int { return -(int) $invoice->creditNotes()->sum('total'); } /** * Recalculate an invoice exclusively from durable credits and payment * allocations. Callers that change allocations must hold the invoice lock. */ public function recalculate(Invoice $invoice): void { $allocated = $this->allocatedTotal($invoice); $credited = $this->creditedTotal($invoice); $due = max(0, (int) $invoice->total - $allocated - $credited); $invoice->due_amount = $due; $invoice->base_due_amount = (int) round($due * $invoice->exchange_rate); if ($due === 0) { $invoice->status = Invoice::STATUS_COMPLETED; $invoice->paid_status = Invoice::STATUS_PAID; $invoice->overdue = false; } else { $invoice->status = $invoice->getPreviousStatus(); $invoice->paid_status = $allocated > 0 ? Invoice::STATUS_PARTIALLY_PAID : Invoice::STATUS_UNPAID; } $invoice->save(); } }