payments()->sum('amount'); $contractValue = (float) $contract->total_value; $contract->paid_amount = $totalPaid; if ($totalPaid > $contractValue) { $contract->remaining_amount = 0; $contract->excess_amount = $totalPaid - $contractValue; } else { $contract->remaining_amount = $contractValue - $totalPaid; $contract->excess_amount = 0; } $contract->saveQuietly(); } /** * Tự động khấu trừ tiền dư vào đợt thanh toán tiếp theo. */ private function applySurplusToNextInstallment(Contract $contract): void { if (self::$handlingSurplus) { return; } $excess = (float) $contract->excess_amount; if ($excess <= 0) { return; } // Tìm đợt tiếp theo chưa thanh toán đủ (hoặc chưa có payment nào) $nextItem = PaymentScheduleItem::query() ->whereHas('schedule', fn ($q) => $q->where('contract_id', $contract->id)) ->whereNotNull('amount') ->orderBy('installment_no') ->get() ->first(function ($item) use ($contract) { $paidForItem = (float) $contract->payments() ->where('schedule_item_id', $item->id) ->sum('amount'); return $paidForItem < (float) $item->amount; }); if (! $nextItem) { return; } $paidForItem = (float) $contract->payments() ->where('schedule_item_id', $nextItem->id) ->sum('amount'); $remainingForItem = (float) $nextItem->amount - $paidForItem; if ($remainingForItem <= 0) { return; } $applyAmount = min($excess, $remainingForItem); self::$handlingSurplus = true; Payment::create([ 'contract_id' => $contract->id, 'schedule_item_id' => $nextItem->id, 'amount' => $applyAmount, 'paid_date' => now(), 'method' => 'Tự động khấu trừ', 'receipt_number' => 'AUTO-SURPLUS-' . now()->format('YmdHis'), 'metadata' => ['auto_surplus' => true, 'source' => 'excess_amount'], ]); self::$handlingSurplus = false; } public function created(Payment $payment): void { if ($payment->contract) { $this->recalculateContract($payment->contract); $this->applySurplusToNextInstallment($payment->contract); } } public function updated(Payment $payment): void { if ($payment->contract) { $this->recalculateContract($payment->contract); $this->applySurplusToNextInstallment($payment->contract); } if ($payment->wasChanged('contract_id') && $payment->getOriginal('contract_id')) { $oldContract = Contract::find($payment->getOriginal('contract_id')); if ($oldContract) { $this->recalculateContract($oldContract); } } } public function deleted(Payment $payment): void { if ($payment->contract) { $this->recalculateContract($payment->contract); } } }