121 lines
3.6 KiB
PHP
121 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Observers;
|
|
|
|
use App\Models\Payment;
|
|
use App\Models\Contract;
|
|
use App\Models\PaymentScheduleItem;
|
|
|
|
class PaymentObserver
|
|
{
|
|
private static bool $handlingSurplus = false;
|
|
|
|
/**
|
|
* Tính toán lại tài chính hợp đồng sau mỗi thay đổi payment.
|
|
*/
|
|
private function recalculateContract(Contract $contract): void
|
|
{
|
|
$totalPaid = (float) $contract->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);
|
|
}
|
|
}
|
|
}
|