Commit 9044313e authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(payments): actually update subscription/fine/installment records when paid from process page

The generic payment process page (linked from waiver "الانتقال إلى السداد") was
recording payments in the payments table but never updating the source records
(subscriptions.paid_amount, fines.paid_amount, installment_schedule.paid_amount).
This caused the debt check to keep showing debts as unpaid after payment.

Added handleSubscriptionPayment, handleFinePayment, and handleInstallmentPayment
methods that mark the underlying records as paid (oldest first).

Also fixed waiver debt check using wrong fines status ('pending' instead of
'imposed'/'appeal_upheld') which is what fines actually use.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent c8fae48c
......@@ -192,6 +192,21 @@ class PaymentController extends Controller
$this->handleMembershipPayment((int) $memberId, $paymentType, $amount);
}
// Annual subscription paid → update subscriptions table
if ($paymentType === 'annual_subscription') {
$this->handleSubscriptionPayment((int) $memberId, $amount, $result['payment_id']);
}
// Fine paid → update fines table
if ($paymentType === 'fine') {
$this->handleFinePayment((int) $memberId, $amount, $result['payment_id']);
}
// Installment paid → update installment_schedule
if ($paymentType === 'installment') {
$this->handleInstallmentPayment((int) $memberId, $amount, $result['payment_id']);
}
return $this->redirect("/members/{$memberId}")
->withSuccess('تم تسجيل الدفع — ' . money($amount) . ' — إيصال: ' . $result['receipt_number']);
}
......@@ -294,6 +309,136 @@ class PaymentController extends Controller
}
}
/**
* Mark pending subscriptions as paid (oldest first) up to the paid amount.
*/
private function handleSubscriptionPayment(int $memberId, string $amount, int $paymentId): void
{
$db = App::getInstance()->db();
$subs = $db->select(
"SELECT id, total_amount, fine_amount, paid_amount, financial_year, person_name
FROM subscriptions WHERE member_id = ? AND status IN ('pending','overdue')
ORDER BY financial_year ASC, id ASC",
[$memberId]
);
$remaining = $amount;
$ts = date('Y-m-d H:i:s');
foreach ($subs as $sub) {
if (bccomp($remaining, '0.01', 2) < 0) break;
$due = bcsub(bcadd($sub['total_amount'], $sub['fine_amount'], 2), $sub['paid_amount'], 2);
if (bccomp($due, '0', 2) <= 0) continue;
$pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
$newPaid = bcadd($sub['paid_amount'], $pay, 2);
$totalDue = bcadd($sub['total_amount'], $sub['fine_amount'], 2);
$status = bccomp($newPaid, $totalDue, 2) >= 0 ? 'paid' : 'partial';
$db->update('subscriptions', [
'paid_amount' => $newPaid,
'payment_id' => $paymentId,
'status' => $status,
'paid_at' => $status === 'paid' ? $ts : null,
'updated_at' => $ts,
], '`id` = ?', [(int) $sub['id']]);
$remaining = bcsub($remaining, $pay, 2);
if ($status === 'paid') {
EventBus::dispatch('subscription.paid', [
'subscription_id' => (int) $sub['id'],
'member_id' => $memberId,
'year' => $sub['financial_year'],
'amount' => $pay,
]);
}
}
}
/**
* Mark pending fines as paid (oldest first) up to the paid amount.
*/
private function handleFinePayment(int $memberId, string $amount, int $paymentId): void
{
$db = App::getInstance()->db();
$fines = $db->select(
"SELECT id, amount as fine_amount, paid_amount FROM fines
WHERE member_id = ? AND status IN ('imposed','pending','appeal_upheld') AND (amount - paid_amount) > 0
ORDER BY id ASC",
[$memberId]
);
$remaining = $amount;
$ts = date('Y-m-d H:i:s');
foreach ($fines as $fine) {
if (bccomp($remaining, '0.01', 2) < 0) break;
$due = bcsub($fine['fine_amount'], $fine['paid_amount'], 2);
if (bccomp($due, '0', 2) <= 0) continue;
$pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
$newPaid = bcadd($fine['paid_amount'], $pay, 2);
$status = bccomp($newPaid, $fine['fine_amount'], 2) >= 0 ? 'paid' : 'imposed';
$db->update('fines', [
'paid_amount' => $newPaid,
'payment_id' => $paymentId,
'status' => $status,
'paid_at' => $status === 'paid' ? $ts : null,
'updated_at' => $ts,
], '`id` = ?', [(int) $fine['id']]);
$remaining = bcsub($remaining, $pay, 2);
if ($status === 'paid') {
EventBus::dispatch('fine.paid', [
'fine_id' => (int) $fine['id'],
'member_id' => $memberId,
'amount' => $pay,
]);
}
}
}
/**
* Mark next pending installment(s) as paid.
*/
private function handleInstallmentPayment(int $memberId, string $amount, int $paymentId): void
{
$db = App::getInstance()->db();
$installments = $db->select(
"SELECT s.id, s.amount as installment_amount, s.paid_amount, s.due_date, p.id as plan_id
FROM installment_schedule s JOIN installment_plans p ON p.id = s.installment_plan_id
WHERE p.member_id = ? AND p.status IN ('active','overdue') AND s.status IN ('pending','overdue')
ORDER BY s.due_date ASC, s.installment_number ASC",
[$memberId]
);
$remaining = $amount;
$ts = date('Y-m-d H:i:s');
foreach ($installments as $inst) {
if (bccomp($remaining, '0.01', 2) < 0) break;
$due = bcsub($inst['installment_amount'], $inst['paid_amount'], 2);
if (bccomp($due, '0', 2) <= 0) continue;
$pay = bccomp($remaining, $due, 2) >= 0 ? $due : $remaining;
$newPaid = bcadd($inst['paid_amount'], $pay, 2);
$status = bccomp($newPaid, $inst['installment_amount'], 2) >= 0 ? 'paid' : 'partial';
$db->update('installment_schedule', [
'paid_amount' => $newPaid,
'payment_id' => $paymentId,
'status' => $status,
'paid_at' => $status === 'paid' ? $ts : null,
'updated_at' => $ts,
], '`id` = ?', [(int) $inst['id']]);
$remaining = bcsub($remaining, $pay, 2);
}
}
/**
* Show single payment details.
*/
......
......@@ -113,7 +113,7 @@ final class WaiverProcessor
// 2. Fines on main member
$fines = $db->select(
"SELECT id, (amount - paid_amount) as due, created_at FROM fines WHERE member_id = ? AND status = 'pending' AND (amount - paid_amount) > 0",
"SELECT id, (amount - paid_amount) as due, created_at FROM fines WHERE member_id = ? AND status IN ('imposed','pending','appeal_upheld') AND (amount - paid_amount) > 0",
[$memberId]
);
foreach ($fines as $fine) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment