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
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$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)
$excludeIds = array_merge([$primarySpouseId], array_map('intval', $secondaryIds));
$excludeIds = array_filter($excludeIds);
......@@ -98,6 +125,7 @@ class DeathController extends Controller
'spouse_count' => $spouseCount,
'child_count' => $childCount,
'temp_count' => $tempCount,
'current_fy_covered' => false,
];
}
......@@ -159,14 +187,21 @@ class DeathController extends Controller
$member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [(int) $memberId]);
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(
"SELECT financial_year, COUNT(*) as cnt, SUM(total_amount - paid_amount) as total_due
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
ORDER BY financial_year ASC",
[(int) $memberId]
[(int) $memberId, $currentFY]
);
$totalOverdue = '0.00';
foreach ($overdueSubscriptions as $row) {
......@@ -532,9 +567,12 @@ class DeathController extends Controller
$breakdown = [
'رسوم نقل العضوية' . $multiplierLabel . ':',
' رسوم استمارة: ' . 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) {
$breakdown[] = ' - ' . $familySub['spouse_count'] . ' زوجة إضافية: ' . money(bcmul($familySub['spouse_sub'], (string) $familySub['spouse_count'], 2));
}
......@@ -545,6 +583,7 @@ class DeathController extends Controller
$breakdown[] = ' - ' . $familySub['temp_count'] . ' مؤقتين: ' . money(bcmul($familySub['temp_sub'], (string) $familySub['temp_count'], 2));
}
$breakdown[] = ' - رسوم تنمية: ' . money($familySub['dev_fee']);
}
if ($secondaryCount > 0) {
$breakdown[] = ' مجموع الأساس (' . (1 + $secondaryCount) . ' عضويات): ' . money($baseFees);
}
......@@ -1045,8 +1084,9 @@ class DeathController extends Controller
$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)
// If current FY was already covered by the deceased, transfer those subscriptions instead
$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);
......@@ -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');
$currentMonth = (int) date('n');
......@@ -1203,6 +1243,28 @@ class DeathController extends Controller
$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)
$existing = $db->selectOne(
"SELECT id, status FROM subscriptions WHERE member_id = ? AND financial_year = ? AND person_type = 'member'",
......
......@@ -207,6 +207,9 @@
<h4 style="margin:0 0 15px;color:#D97706;">رسوم الإجراء (رسوم استمارة + اشتراك سنوي للعضوية الجديدة)</h4>
<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>
<?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 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): ?>
......@@ -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>
<?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>
<?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>
</table>
<p style="font-size:11px;color:#6B7280;margin:0;">* سيتم إضافة رسوم مجلس الأمناء بعد اعتماد مجلس الإدارة — النسبة تُحسب من قيمة الخطة الحالية</p>
......
......@@ -191,6 +191,9 @@ $canApprove = can('transfer.approve');
?>
<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>
<?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>
<?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;">
......@@ -201,6 +204,7 @@ $canApprove = can('transfer.approve');
+ تنمية: <?= money($familySub['dev_fee']) ?>)
</td></tr>
<?php endif; ?>
<?php endif; ?>
<tr>
<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