Commit 2e4f8575 authored by Fares's avatar Fares

fix(waiver): fix 4 issues after waiver completion

1. Subscriptions now marked as paid: after waiver completes, sync
   subscriptions for target member and mark current FY as paid since
   the fee was already collected in the waiver payment.

2. Membership date: set created_at to today on waiver completion so the
   member profile shows the actual membership start date.

3. Total paid shows correctly: BillingService now recognizes
   activated_by_payment_id as proof that form_fee and membership_fee
   are paid (covers waiver_fee and separation_fee payment types).

4. Waiver list shows names: fix $r['member_name'] → $r['source_name']
   to match the SQL alias from WaiverRequest::search().
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 0a2372df
...@@ -330,11 +330,17 @@ final class BillingService ...@@ -330,11 +330,17 @@ final class BillingService
$formFeePaid = false; $formFeePaid = false;
$formFeePending = false; $formFeePending = false;
try { try {
// Members activated via waiver/transfer: form fee is included in that payment
if (!empty($member['activated_by_payment_id'])) {
$formFeePaid = true;
}
if (!$formFeePaid) {
$ff = $db->selectOne( $ff = $db->selectOne(
"SELECT id FROM payments WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 LIMIT 1", "SELECT id FROM payments WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 LIMIT 1",
[$memberId] [$memberId]
); );
$formFeePaid = ($ff !== null); $formFeePaid = ($ff !== null);
}
if (!$formFeePaid) { if (!$formFeePaid) {
$ffReq = $db->selectOne( $ffReq = $db->selectOne(
"SELECT id, status FROM payment_requests WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1", "SELECT id, status FROM payment_requests WHERE member_id = ? AND payment_type = 'form_fee' AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1",
...@@ -367,20 +373,26 @@ final class BillingService ...@@ -367,20 +373,26 @@ final class BillingService
$membershipPaid = false; $membershipPaid = false;
$membershipPending = false; $membershipPending = false;
try { try {
// Members activated via waiver/transfer already paid via that fee
if (!empty($member['activated_by_payment_id'])) {
$membershipPaid = true;
}
if (!$membershipPaid) {
$mp = $db->selectOne( $mp = $db->selectOne(
"SELECT id FROM payments WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 LIMIT 1", "SELECT id FROM payments WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment','waiver_fee','separation_fee') AND is_voided = 0 LIMIT 1",
[$memberId] [$memberId]
); );
$membershipPaid = ($mp !== null); $membershipPaid = ($mp !== null);
}
if (!$membershipPaid) { if (!$membershipPaid) {
$mpReq = $db->selectOne( $mpReq = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1", "SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment','waiver_fee','separation_fee') AND is_voided = 0 AND status IN ('pending','processing') LIMIT 1",
[$memberId] [$memberId]
); );
$membershipPending = ($mpReq !== null); $membershipPending = ($mpReq !== null);
if (!$membershipPending) { if (!$membershipPending) {
$mpCompleted = $db->selectOne( $mpCompleted = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND is_voided = 0 AND status = 'completed' LIMIT 1", "SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment','waiver_fee','separation_fee') AND is_voided = 0 AND status = 'completed' LIMIT 1",
[$memberId] [$memberId]
); );
$membershipPaid = ($mpCompleted !== null); $membershipPaid = ($mpCompleted !== null);
......
...@@ -7,6 +7,7 @@ use App\Core\App; ...@@ -7,6 +7,7 @@ use App\Core\App;
use App\Core\EventBus; use App\Core\EventBus;
use App\Core\Logger; use App\Core\Logger;
use App\Modules\Archive\Services\ArchiveService; use App\Modules\Archive\Services\ArchiveService;
use App\Modules\Subscriptions\Services\SubscriptionSyncService;
final class WaiverProcessor final class WaiverProcessor
{ {
...@@ -56,6 +57,7 @@ final class WaiverProcessor ...@@ -56,6 +57,7 @@ final class WaiverProcessor
'membership_type' => $sourceMember['membership_type'] ?? 'working', 'membership_type' => $sourceMember['membership_type'] ?? 'working',
'activated_by_payment_id' => $waiverPaymentId, 'activated_by_payment_id' => $waiverPaymentId,
'activated_at' => date('Y-m-d H:i:s'), 'activated_at' => date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $waiver['target_member_id']]); ], '`id` = ?', [(int) $waiver['target_member_id']]);
...@@ -69,10 +71,44 @@ final class WaiverProcessor ...@@ -69,10 +71,44 @@ final class WaiverProcessor
$db->commit(); $db->commit();
// Generate subscription rows for target member and mark as paid
$targetMemberId = (int) $waiver['target_member_id'];
try {
SubscriptionSyncService::syncForMember($targetMemberId);
// Mark current year subscriptions as paid (fee was collected in waiver payment)
$now = new \DateTime();
$month = (int) $now->format('n');
$year = (int) $now->format('Y');
$fyStart = $month >= 7 ? $year : $year - 1;
$currentFy = $fyStart . '/' . ($fyStart + 1);
$pendingSubs = $db->select(
"SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND status IN ('pending','overdue')",
[$targetMemberId, $currentFy]
);
$ts = date('Y-m-d H:i:s');
$paidByEmp = $employee ? (int) $employee->id : null;
foreach ($pendingSubs as $ps) {
$sub = $db->selectOne("SELECT total_amount, fine_amount, development_fee FROM subscriptions WHERE id = ?", [(int) $ps['id']]);
$paidAmount = bcadd(bcadd($sub['total_amount'], $sub['fine_amount'] ?? '0', 2), $sub['development_fee'] ?? '0', 2);
$db->update('subscriptions', [
'paid_amount' => $paidAmount,
'payment_id' => $waiverPaymentId,
'status' => 'paid',
'paid_at' => $ts,
'paid_by' => $paidByEmp,
'updated_at' => $ts,
], '`id` = ?', [(int) $ps['id']]);
}
} catch (\Throwable $e) {
Logger::warning("Waiver subscription sync failed: " . $e->getMessage());
}
EventBus::dispatch('waiver.completed', [ EventBus::dispatch('waiver.completed', [
'waiver_id' => $waiverId, 'waiver_id' => $waiverId,
'source_member_id' => (int) $waiver['source_member_id'], 'source_member_id' => (int) $waiver['source_member_id'],
'target_member_id' => (int) $waiver['target_member_id'], 'target_member_id' => $targetMemberId,
'membership_number'=> $waiver['membership_number'], 'membership_number'=> $waiver['membership_number'],
'fee_amount' => $waiver['waiver_fee_amount'] ?? '0.00', 'fee_amount' => $waiver['waiver_fee_amount'] ?? '0.00',
'fee_percentage' => $waiver['waiver_fee_percentage'] ?? '30', 'fee_percentage' => $waiver['waiver_fee_percentage'] ?? '30',
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<?php foreach ($rows as $r): ?> <?php foreach ($rows as $r): ?>
<tr> <tr>
<td><?= (int) $r['id'] ?></td> <td><?= (int) $r['id'] ?></td>
<td><a href="/members/<?= (int) $r['source_member_id'] ?>" style="color:#0D7377;font-weight:600;"><?= e($r['member_name'] ?? '') ?></a></td> <td><a href="/members/<?= (int) $r['source_member_id'] ?>" style="color:#0D7377;font-weight:600;"><?= e($r['source_name'] ?? '') ?></a></td>
<td style="font-weight:600;"><?= e($r['membership_number'] ?? '—') ?></td> <td style="font-weight:600;"><?= e($r['membership_number'] ?? '—') ?></td>
<td><?= money($r['membership_value_at_waiver'] ?? '0') ?></td> <td><?= money($r['membership_value_at_waiver'] ?? '0') ?></td>
<td><?= e($r['waiver_fee_percentage'] ?? '30') ?>%</td> <td><?= e($r['waiver_fee_percentage'] ?? '30') ?>%</td>
......
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