Commit 9cde9287 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(death): skip current FY annual subscription when member already has it covered

When a member dies in the same financial year they already have subscriptions for,
don't charge the annual subscription again in the death fee — the man died before
using it. Only earlier years' overdue subscriptions must be settled first.

Changes:
- calculateFamilyAnnualSub returns 0 if current FY member subscription already exists
- createNewSubscriptions transfers existing FY subscriptions from deceased to new member
  instead of creating duplicate paid rows
- Overdue check on create page excludes current FY (only blocks for previous years)
- Views show "معفى — السنة المالية الحالية مغطاة" when current FY is covered
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent a4fa3aed
...@@ -54,6 +54,33 @@ class DeathController extends Controller ...@@ -54,6 +54,33 @@ class DeathController extends Controller
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE'); $devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00'; $devFee = $devFeeData['amount'] ?? '35.00';
// If the deceased member already has current FY subscriptions, don't charge again
$currentMonth = (int) date('n');
$currentYear = (int) date('Y');
$financialYear = $currentMonth >= 7
? $currentYear . '/' . ($currentYear + 1)
: ($currentYear - 1) . '/' . $currentYear;
$existingCurrentFY = $db->selectOne(
"SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member' LIMIT 1",
[$memberId, $financialYear]
);
if ($existingCurrentFY) {
// Current year already covered — no annual sub needed
return [
'annual_sub_total' => '0.00',
'member_sub' => $memberSub,
'spouse_sub' => $spouseSub,
'child_sub' => $childSub,
'temp_sub' => $tempSub,
'dev_fee' => '0.00',
'spouse_count' => 0,
'child_count' => 0,
'temp_count' => 0,
'current_fy_covered' => true,
];
}
// Count remaining spouses (exclude primary who becomes member, exclude secondaries who get own memberships) // Count remaining spouses (exclude primary who becomes member, exclude secondaries who get own memberships)
$excludeIds = array_merge([$primarySpouseId], array_map('intval', $secondaryIds)); $excludeIds = array_merge([$primarySpouseId], array_map('intval', $secondaryIds));
$excludeIds = array_filter($excludeIds); $excludeIds = array_filter($excludeIds);
...@@ -98,6 +125,7 @@ class DeathController extends Controller ...@@ -98,6 +125,7 @@ class DeathController extends Controller
'spouse_count' => $spouseCount, 'spouse_count' => $spouseCount,
'child_count' => $childCount, 'child_count' => $childCount,
'temp_count' => $tempCount, 'temp_count' => $tempCount,
'current_fy_covered' => false,
]; ];
} }
...@@ -159,14 +187,21 @@ class DeathController extends Controller ...@@ -159,14 +187,21 @@ class DeathController extends Controller
$member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [(int) $memberId]); $member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [(int) $memberId]);
if (!$member) return $this->redirect('/members')->withError('العضو غير موجود'); if (!$member) return $this->redirect('/members')->withError('العضو غير موجود');
// Check for overdue/unpaid subscriptions // Check for overdue/unpaid subscriptions from PREVIOUS years only
// Current FY is exempt: member died before using the membership this year
$currentMonth = (int) date('n');
$currentYear = (int) date('Y');
$currentFY = $currentMonth >= 7
? $currentYear . '/' . ($currentYear + 1)
: ($currentYear - 1) . '/' . $currentYear;
$overdueSubscriptions = $db->select( $overdueSubscriptions = $db->select(
"SELECT financial_year, COUNT(*) as cnt, SUM(total_amount - paid_amount) as total_due "SELECT financial_year, COUNT(*) as cnt, SUM(total_amount - paid_amount) as total_due
FROM subscriptions FROM subscriptions
WHERE member_id = ? AND status IN ('pending', 'overdue') WHERE member_id = ? AND status IN ('pending', 'overdue') AND financial_year != ?
GROUP BY financial_year GROUP BY financial_year
ORDER BY financial_year ASC", ORDER BY financial_year ASC",
[(int) $memberId] [(int) $memberId, $currentFY]
); );
$totalOverdue = '0.00'; $totalOverdue = '0.00';
foreach ($overdueSubscriptions as $row) { foreach ($overdueSubscriptions as $row) {
...@@ -532,9 +567,12 @@ class DeathController extends Controller ...@@ -532,9 +567,12 @@ class DeathController extends Controller
$breakdown = [ $breakdown = [
'رسوم نقل العضوية' . $multiplierLabel . ':', 'رسوم نقل العضوية' . $multiplierLabel . ':',
' رسوم استمارة: ' . money($fees['formFee']), ' رسوم استمارة: ' . money($fees['formFee']),
' اشتراك سنوي (العضوية الجديدة): ' . money($annualSubTotal),
' - العضو الجديد (الزوجة): ' . money($familySub['member_sub']),
]; ];
if (!empty($familySub['current_fy_covered'])) {
$breakdown[] = ' اشتراك سنوي: معفى (السنة المالية الحالية مغطاة — المتوفى سدد/مسجل بالفعل)';
} else {
$breakdown[] = ' اشتراك سنوي (العضوية الجديدة): ' . money($annualSubTotal);
$breakdown[] = ' - العضو الجديد (الزوجة): ' . money($familySub['member_sub']);
if ($familySub['spouse_count'] > 0) { if ($familySub['spouse_count'] > 0) {
$breakdown[] = ' - ' . $familySub['spouse_count'] . ' زوجة إضافية: ' . money(bcmul($familySub['spouse_sub'], (string) $familySub['spouse_count'], 2)); $breakdown[] = ' - ' . $familySub['spouse_count'] . ' زوجة إضافية: ' . money(bcmul($familySub['spouse_sub'], (string) $familySub['spouse_count'], 2));
} }
...@@ -545,6 +583,7 @@ class DeathController extends Controller ...@@ -545,6 +583,7 @@ class DeathController extends Controller
$breakdown[] = ' - ' . $familySub['temp_count'] . ' مؤقتين: ' . money(bcmul($familySub['temp_sub'], (string) $familySub['temp_count'], 2)); $breakdown[] = ' - ' . $familySub['temp_count'] . ' مؤقتين: ' . money(bcmul($familySub['temp_sub'], (string) $familySub['temp_count'], 2));
} }
$breakdown[] = ' - رسوم تنمية: ' . money($familySub['dev_fee']); $breakdown[] = ' - رسوم تنمية: ' . money($familySub['dev_fee']);
}
if ($secondaryCount > 0) { if ($secondaryCount > 0) {
$breakdown[] = ' مجموع الأساس (' . (1 + $secondaryCount) . ' عضويات): ' . money($baseFees); $breakdown[] = ' مجموع الأساس (' . (1 + $secondaryCount) . ' عضويات): ' . money($baseFees);
} }
...@@ -1045,8 +1084,9 @@ class DeathController extends Controller ...@@ -1045,8 +1084,9 @@ class DeathController extends Controller
$this->transferActiveData($db, (int) $case['member_id'], $newMemberId, $caseId); $this->transferActiveData($db, (int) $case['member_id'], $newMemberId, $caseId);
// STEP 4: Create annual subscriptions for new membership (marked as paid — fee collected with death fee) // STEP 4: Create annual subscriptions for new membership (marked as paid — fee collected with death fee)
// If current FY was already covered by the deceased, transfer those subscriptions instead
$deathPaymentId = $deathPaymentCheck ? (int) $deathPaymentCheck['id'] : null; $deathPaymentId = $deathPaymentCheck ? (int) $deathPaymentCheck['id'] : null;
$this->createNewSubscriptions($db, $newMemberId, $caseId, $deathPaymentId); $this->createNewSubscriptions($db, $newMemberId, $caseId, $deathPaymentId, (int) $case['member_id']);
ArchiveService::recordNumberTransfer($inheritedNumber, 'death_transfer', 'members', $newMemberId); ArchiveService::recordNumberTransfer($inheritedNumber, 'death_transfer', 'members', $newMemberId);
...@@ -1181,7 +1221,7 @@ class DeathController extends Controller ...@@ -1181,7 +1221,7 @@ class DeathController extends Controller
} }
} }
private function createNewSubscriptions($db, int $newMemberId, int $caseId, ?int $paymentId = null): void private function createNewSubscriptions($db, int $newMemberId, int $caseId, ?int $paymentId = null, int $deceasedMemberId = 0): void
{ {
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$currentMonth = (int) date('n'); $currentMonth = (int) date('n');
...@@ -1203,6 +1243,28 @@ class DeathController extends Controller ...@@ -1203,6 +1243,28 @@ class DeathController extends Controller
$memberTotal = bcadd($memberSub, $devFee, 2); $memberTotal = bcadd($memberSub, $devFee, 2);
// If deceased already had current FY subscriptions, transfer them to new member
if ($deceasedMemberId > 0) {
$deceasedHasCurrentFY = $db->selectOne(
"SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member' LIMIT 1",
[$deceasedMemberId, $financialYear]
);
if ($deceasedHasCurrentFY) {
// Transfer all current FY subscriptions from deceased to new member
$db->query(
"UPDATE subscriptions SET member_id = ?, person_id = CASE WHEN person_type = 'member' THEN ? ELSE person_id END, updated_at = ? WHERE member_id = ? AND financial_year = ?",
[$newMemberId, $newMemberId, $now, $deceasedMemberId, $financialYear]
);
// Remove the transferred primary-spouse row (she's now the member, not a spouse)
$db->query(
"DELETE FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'spouse' AND person_id IN (SELECT id FROM spouses WHERE member_id = ? AND status = 'transferred')",
[$newMemberId, $financialYear, $newMemberId]
);
DeathAuditService::log($caseId, 'subscriptions_transferred', 'تم نقل اشتراكات السنة المالية الحالية ' . $financialYear . ' من العضوية الأصلية (بدون رسوم إضافية — السنة مغطاة)', 'members', $newMemberId);
return;
}
}
// Member subscription (marked as paid — fee included in death transfer payment) // Member subscription (marked as paid — fee included in death transfer payment)
$existing = $db->selectOne( $existing = $db->selectOne(
"SELECT id, status FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member'", "SELECT id, status FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member'",
......
...@@ -207,6 +207,9 @@ ...@@ -207,6 +207,9 @@
<h4 style="margin:0 0 15px;color:#D97706;">رسوم الإجراء (رسوم استمارة + اشتراك سنوي للعضوية الجديدة)</h4> <h4 style="margin:0 0 15px;color:#D97706;">رسوم الإجراء (رسوم استمارة + اشتراك سنوي للعضوية الجديدة)</h4>
<table style="font-size:14px;margin-bottom:15px;" id="fee-table"> <table style="font-size:14px;margin-bottom:15px;" id="fee-table">
<tr><td style="padding:4px 20px 4px 0;color:#6B7280;">رسوم استمارة</td><td style="font-weight:600;"><?= money($form_fee) ?></td></tr> <tr><td style="padding:4px 20px 4px 0;color:#6B7280;">رسوم استمارة</td><td style="font-weight:600;"><?= money($form_fee) ?></td></tr>
<?php if (!empty($family_sub['current_fy_covered'])): ?>
<tr><td style="padding:4px 20px 4px 0;color:#059669;">اشتراك سنوي</td><td style="font-weight:600;color:#059669;">معفى — السنة المالية الحالية مغطاة بالفعل</td></tr>
<?php else: ?>
<tr><td colspan="2" style="padding:8px 0 4px;font-weight:600;color:#0D7377;">الاشتراك السنوي (<?= money($annual_sub) ?>):</td></tr> <tr><td colspan="2" style="padding:8px 0 4px;font-weight:600;color:#0D7377;">الاشتراك السنوي (<?= money($annual_sub) ?>):</td></tr>
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- العضو الجديد (الزوجة)</td><td style="font-size:13px;"><?= money($family_sub['member_sub']) ?></td></tr> <tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- العضو الجديد (الزوجة)</td><td style="font-size:13px;"><?= money($family_sub['member_sub']) ?></td></tr>
<?php if ($family_sub['spouse_count'] > 0): ?> <?php if ($family_sub['spouse_count'] > 0): ?>
...@@ -219,6 +222,7 @@ ...@@ -219,6 +222,7 @@
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- <?= (int) $family_sub['temp_count'] ?> مؤقتين</td><td style="font-size:13px;"><?= money(bcmul($family_sub['temp_sub'], (string) $family_sub['temp_count'], 2)) ?></td></tr> <tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- <?= (int) $family_sub['temp_count'] ?> مؤقتين</td><td style="font-size:13px;"><?= money(bcmul($family_sub['temp_sub'], (string) $family_sub['temp_count'], 2)) ?></td></tr>
<?php endif; ?> <?php endif; ?>
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- رسوم تنمية</td><td style="font-size:13px;"><?= money($family_sub['dev_fee']) ?></td></tr> <tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- رسوم تنمية</td><td style="font-size:13px;"><?= money($family_sub['dev_fee']) ?></td></tr>
<?php endif; ?>
<tr style="border-top:2px solid #D97706;" id="fee-total-row"><td style="padding:8px 20px 4px 0;font-weight:700;">الإجمالي</td><td style="font-weight:700;font-size:18px;color:#DC2626;"><?= money($total_fee) ?></td></tr> <tr style="border-top:2px solid #D97706;" id="fee-total-row"><td style="padding:8px 20px 4px 0;font-weight:700;">الإجمالي</td><td style="font-weight:700;font-size:18px;color:#DC2626;"><?= money($total_fee) ?></td></tr>
</table> </table>
<p style="font-size:11px;color:#6B7280;margin:0;">* سيتم إضافة رسوم مجلس الأمناء بعد اعتماد مجلس الإدارة — النسبة تُحسب من قيمة الخطة الحالية</p> <p style="font-size:11px;color:#6B7280;margin:0;">* سيتم إضافة رسوم مجلس الأمناء بعد اعتماد مجلس الإدارة — النسبة تُحسب من قيمة الخطة الحالية</p>
......
...@@ -191,6 +191,9 @@ $canApprove = can('transfer.approve'); ...@@ -191,6 +191,9 @@ $canApprove = can('transfer.approve');
?> ?>
<table style="font-size:13px;max-width:500px;"> <table style="font-size:13px;max-width:500px;">
<tr><td style="padding:4px 20px 4px 0;color:#6B7280;">رسوم استمارة (<?= $multiplier ?> × <?= money($fees['formFee']) ?>)</td><td style="font-weight:600;"><?= money(bcmul($fees['formFee'], (string) $multiplier, 2)) ?></td></tr> <tr><td style="padding:4px 20px 4px 0;color:#6B7280;">رسوم استمارة (<?= $multiplier ?> × <?= money($fees['formFee']) ?>)</td><td style="font-weight:600;"><?= money(bcmul($fees['formFee'], (string) $multiplier, 2)) ?></td></tr>
<?php if (!empty($familySub['current_fy_covered'])): ?>
<tr><td style="padding:4px 20px 4px 0;color:#059669;">اشتراك سنوي</td><td style="font-weight:600;color:#059669;">معفى — السنة المالية الحالية مغطاة</td></tr>
<?php else: ?>
<tr><td style="padding:4px 20px 4px 0;color:#6B7280;">اشتراك سنوي (العضوية الجديدة) × <?= $multiplier ?></td><td style="font-weight:600;"><?= money(bcmul($familySub['annual_sub_total'], (string) $multiplier, 2)) ?></td></tr> <tr><td style="padding:4px 20px 4px 0;color:#6B7280;">اشتراك سنوي (العضوية الجديدة) × <?= $multiplier ?></td><td style="font-weight:600;"><?= money(bcmul($familySub['annual_sub_total'], (string) $multiplier, 2)) ?></td></tr>
<?php if ($familySub['child_count'] > 0 || $familySub['temp_count'] > 0 || $familySub['spouse_count'] > 0): ?> <?php if ($familySub['child_count'] > 0 || $familySub['temp_count'] > 0 || $familySub['spouse_count'] > 0): ?>
<tr><td colspan="2" style="padding:2px 40px 2px 0;color:#9CA3AF;font-size:11px;"> <tr><td colspan="2" style="padding:2px 40px 2px 0;color:#9CA3AF;font-size:11px;">
...@@ -201,6 +204,7 @@ $canApprove = can('transfer.approve'); ...@@ -201,6 +204,7 @@ $canApprove = can('transfer.approve');
+ تنمية: <?= money($familySub['dev_fee']) ?>) + تنمية: <?= money($familySub['dev_fee']) ?>)
</td></tr> </td></tr>
<?php endif; ?> <?php endif; ?>
<?php endif; ?>
<tr> <tr>
<td style="padding:4px 20px 4px 0;color:#6B7280;"> <td style="padding:4px 20px 4px 0;color:#6B7280;">
رسوم مجلس الأمناء رسوم مجلس الأمناء
......
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