Commit d48cc140 authored by Fares's avatar Fares

fix(death): exclude deceased from annual sub; use current plan price for trustee fee

The deceased member should not be charged annual subscription — the primary
spouse becomes the new member. Trustee fee percentage now uses the current
membership plan price from pricing_configs (e.g. 150,000) instead of the
historical membership_value stored at enrollment time (e.g. 114,000).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent a5b513bb
......@@ -31,9 +31,19 @@ class DeathController extends Controller
}
/**
* Calculate full annual subscription for all family members of a membership.
* Calculate annual subscription for the NEW membership after death transfer.
*
* The deceased member is dead — we do NOT charge subscription for him.
* The primary spouse BECOMES the new member (1 × member_sub).
* Remaining spouses (minus primary, minus secondaries who get own memberships)
* stay as spouse dependents of the new member.
* Children and temps assigned to primary are counted.
*
* @param int $memberId Deceased member's ID (to count current dependents)
* @param int $primarySpouseId The spouse who becomes new member (excluded from spouse count)
* @param array $secondaryIds Spouses getting separate memberships (excluded from spouse count)
*/
private static function calculateFamilyAnnualSub(int $memberId): array
private static function calculateFamilyAnnualSub(int $memberId, int $primarySpouseId = 0, array $secondaryIds = []): array
{
$db = App::getInstance()->db();
$memberSub = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
......@@ -43,10 +53,24 @@ class DeathController extends Controller
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$spouseCount = (int) ($db->selectOne(
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'",
[$memberId]
)['cnt'] ?? 0);
// 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);
$spouseCount = 0;
if (!empty($excludeIds)) {
$placeholders = implode(',', array_fill(0, count($excludeIds), '?'));
$params = array_merge([$memberId], $excludeIds);
$spouseCount = (int) ($db->selectOne(
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active' AND id NOT IN ({$placeholders})",
$params
)['cnt'] ?? 0);
} else {
$spouseCount = (int) ($db->selectOne(
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'",
[$memberId]
)['cnt'] ?? 0);
}
$childCount = (int) ($db->selectOne(
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active'",
[$memberId]
......@@ -56,6 +80,7 @@ class DeathController extends Controller
[$memberId]
)['cnt'] ?? 0);
// 1 × member (the primary spouse IS the new member) + remaining spouses + children + temps
$familyTotal = $memberSub;
$familyTotal = bcadd($familyTotal, bcmul($spouseSub, (string) $spouseCount, 2), 2);
$familyTotal = bcadd($familyTotal, bcmul($childSub, (string) $childCount, 2), 2);
......@@ -136,7 +161,10 @@ class DeathController extends Controller
$children = $db->select("SELECT * FROM children WHERE member_id = ? AND is_archived = 0 ORDER BY child_order", [(int) $memberId]);
$temps = $db->select("SELECT * FROM temporary_members WHERE member_id = ? AND is_archived = 0 AND status = 'active' ORDER BY id", [(int) $memberId]);
$fees = self::getFees();
$familySub = self::calculateFamilyAnnualSub((int) $memberId);
// Preview: assume primary spouse becomes member, no secondaries yet
// Pass all spouses IDs to exclude (since user picks one as primary and others may be secondary)
// Worst case: show 1 member + children + temps (no extra spouses)
$familySub = self::calculateFamilyAnnualSub((int) $memberId, 0, array_column($spouses, 'id'));
return $this->view('Death.Views.create', [
'member' => $member,
......@@ -189,10 +217,6 @@ class DeathController extends Controller
$inheritancePath = $inheritanceUpload['path'];
}
$fees = self::getFees();
$familySub = self::calculateFamilyAnnualSub((int) $memberId);
$totalFee = bcadd($fees['formFee'], $familySub['annual_sub_total'], 2);
$initialStatus = ($deceasedType === 'primary_member') ? 'board_review' : 'recorded';
$caseData = [
......@@ -203,7 +227,7 @@ class DeathController extends Controller
'death_certificate_path' => $certPath,
'inheritance_notice_path' => $inheritancePath,
'same_membership_number' => ($deceasedType === 'primary_member') ? 1 : 0,
'fee_amount' => $totalFee,
'fee_amount' => '0.00',
'status' => $initialStatus,
'notes' => $notes ?: null,
];
......@@ -218,6 +242,11 @@ class DeathController extends Controller
return $this->redirect("/death/create/{$memberId}")->withError('يجب اختيار الزوجة الأساسية لنقل العضوية');
}
// Calculate correct fee: primary spouse becomes member, exclude primary+secondaries from spouse count
$fees = self::getFees();
$familySub = self::calculateFamilyAnnualSub((int) $memberId, $primarySpouseId, $secondarySpouseIds);
$totalFee = bcadd($fees['formFee'], $familySub['annual_sub_total'], 2);
$caseData['spouse_id'] = $primarySpouseId;
$caseData['primary_spouse_id'] = $primarySpouseId;
$caseData['secondary_spouses_json'] = !empty($secondarySpouseIds) ? json_encode(array_map('intval', $secondarySpouseIds)) : null;
......@@ -234,8 +263,8 @@ class DeathController extends Controller
$secondaryCount = count($secondarySpouseIds);
if ($secondaryCount > 0) {
$totalFee = bcmul($totalFee, (string) (1 + $secondaryCount), 2);
$caseData['fee_amount'] = $totalFee;
}
$caseData['fee_amount'] = $totalFee;
} elseif ($deceasedType === 'spouse') {
$spouseId = $request->post('spouse_id') ? (int) $request->post('spouse_id') : null;
$caseData['spouse_id'] = $spouseId;
......@@ -268,9 +297,9 @@ class DeathController extends Controller
if (!$case) return $this->redirect('/death')->withError('الحالة غير موجودة');
$fees = self::getFees();
$familySub = self::calculateFamilyAnnualSub((int) $case['member_id']);
$primarySpouse = null;
$secondarySpouses = [];
$secondaryIds = [];
$newMember = null;
$paymentRequest = null;
$boardApprover = null;
......@@ -279,12 +308,17 @@ class DeathController extends Controller
$primarySpouse = $db->selectOne("SELECT * FROM spouses WHERE id = ?", [(int) $case['primary_spouse_id']]);
}
if (!empty($case['secondary_spouses_json'])) {
$ids = json_decode($case['secondary_spouses_json'], true);
if ($ids) {
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$secondarySpouses = $db->select("SELECT * FROM spouses WHERE id IN ({$placeholders})", $ids);
$secondaryIds = json_decode($case['secondary_spouses_json'], true) ?: [];
if ($secondaryIds) {
$placeholders = implode(',', array_fill(0, count($secondaryIds), '?'));
$secondarySpouses = $db->select("SELECT * FROM spouses WHERE id IN ({$placeholders})", $secondaryIds);
}
}
$familySub = self::calculateFamilyAnnualSub(
(int) $case['member_id'],
(int) ($case['primary_spouse_id'] ?? 0),
$secondaryIds
);
if ($case['transferred_to_member_id']) {
$newMember = $db->selectOne("SELECT id, full_name_ar, membership_number, status FROM members WHERE id = ?", [(int) $case['transferred_to_member_id']]);
}
......@@ -332,18 +366,36 @@ class DeathController extends Controller
return $this->redirect("/death/{$id}")->withError('قيمة رسوم مجلس الأمناء غير صحيحة');
}
// Trustee fee: percentage is applied to CURRENT membership plan price (not old stored value)
$memberRow = $db->selectOne("SELECT * FROM members WHERE id = ?", [(int) $case['member_id']]);
$boardFeeAmount = '0.00';
$currentPlanPrice = '0.00';
if ($feeMethod === 'percentage') {
$memberRow = $db->selectOne("SELECT membership_value FROM members WHERE id = ?", [(int) $case['member_id']]);
$membershipValue = (string) ($memberRow['membership_value'] ?? '0');
$boardFeeAmount = bcdiv(bcmul($membershipValue, $feeValue, 6), '100', 2);
$branchId = (int) ($memberRow['branch_id'] ?? 1);
$qualId = !empty($memberRow['qualification_id']) ? (int) $memberRow['qualification_id'] : null;
if ($qualId) {
$qual = $db->selectOne("SELECT code FROM qualifications WHERE id = ?", [$qualId]);
if ($qual && $qual['code']) {
$priceInfo = \App\Modules\Pricing\Services\PricingEngine::getMembershipPrice($branchId, $qual['code']);
$currentPlanPrice = $priceInfo['price'] ?? '0.00';
}
}
if (bccomp($currentPlanPrice, '0.01', 2) < 0) {
$pricing = $db->selectOne(
"SELECT price FROM pricing_configs WHERE branch_id = ? AND membership_type = 'working' AND is_active = 1 AND effective_from <= CURDATE() AND (effective_to IS NULL OR effective_to >= CURDATE()) ORDER BY price DESC LIMIT 1",
[$branchId]
);
$currentPlanPrice = $pricing['price'] ?? ($memberRow['membership_value'] ?? '0.00');
}
$boardFeeAmount = bcdiv(bcmul($currentPlanPrice, $feeValue, 6), '100', 2);
} else {
$boardFeeAmount = bcadd($feeValue, '0', 2);
}
// Calculate family annual subscription
// Calculate family annual subscription (primary spouse becomes member, exclude secondaries)
$fees = self::getFees();
$familySub = self::calculateFamilyAnnualSub((int) $case['member_id']);
$secondaryIds = !empty($case['secondary_spouses_json']) ? (json_decode($case['secondary_spouses_json'], true) ?: []) : [];
$familySub = self::calculateFamilyAnnualSub((int) $case['member_id'], (int) ($case['primary_spouse_id'] ?? 0), $secondaryIds);
$annualSubTotal = $familySub['annual_sub_total'];
$secondaryCount = 0;
......@@ -375,11 +427,11 @@ class DeathController extends Controller
$breakdown = [
'رسوم نقل العضوية' . $multiplierLabel . ':',
' رسوم استمارة: ' . money($fees['formFee']),
' اشتراك سنوي (جميع الأفراد): ' . money($annualSubTotal),
' - العضو: ' . money($familySub['member_sub']),
' اشتراك سنوي (العضوية الجديدة): ' . money($annualSubTotal),
' - العضو الجديد (الزوجة): ' . money($familySub['member_sub']),
];
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));
}
if ($familySub['child_count'] > 0) {
$breakdown[] = ' - ' . $familySub['child_count'] . ' أبناء: ' . money(bcmul($familySub['child_sub'], (string) $familySub['child_count'], 2));
......@@ -392,7 +444,7 @@ class DeathController extends Controller
$breakdown[] = ' مجموع الأساس (' . (1 + $secondaryCount) . ' عضويات): ' . money($baseFees);
}
$trusteeLabel = $feeMethod === 'percentage'
? 'رسوم مجلس الأمناء (' . $feeValue . '% من قيمة العضوية)'
? 'رسوم مجلس الأمناء (' . $feeValue . '% من قيمة الخطة الحالية ' . money($currentPlanPrice) . ')'
: 'رسوم مجلس الأمناء (مبلغ ثابت)';
$breakdown[] = $trusteeLabel . ': ' . money($boardFeeAmount);
$breakdown[] = '═══════════════════════════';
......
......@@ -146,13 +146,13 @@
<!-- Fee Summary -->
<div id="fee-section" style="display:none;">
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<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">
<tr><td style="padding:4px 20px 4px 0;color:#6B7280;">رسوم استمارة</td><td style="font-weight:600;"><?= money($form_fee) ?></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): ?>
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- <?= (int) $family_sub['spouse_count'] ?> زوجة</td><td style="font-size:13px;"><?= money(bcmul($family_sub['spouse_sub'], (string) $family_sub['spouse_count'], 2)) ?></td></tr>
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- <?= (int) $family_sub['spouse_count'] ?> زوجة إضافية</td><td style="font-size:13px;"><?= money(bcmul($family_sub['spouse_sub'], (string) $family_sub['spouse_count'], 2)) ?></td></tr>
<?php endif; ?>
<?php if ($family_sub['child_count'] > 0): ?>
<tr><td style="padding:2px 30px 2px 0;color:#6B7280;font-size:13px;">- <?= (int) $family_sub['child_count'] ?> أبناء</td><td style="font-size:13px;"><?= money(bcmul($family_sub['child_sub'], (string) $family_sub['child_count'], 2)) ?></td></tr>
......@@ -163,7 +163,7 @@
<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 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>
<p style="font-size:11px;color:#6B7280;margin:0;">* سيتم إضافة رسوم مجلس الأمناء بعد اعتماد مجلس الإدارة — النسبة تُحسب من قيمة الخطة الحالية</p>
</div>
</div>
......
......@@ -183,11 +183,11 @@ $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>
<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['spouse_count'] > 0 || $familySub['child_count'] > 0 || $familySub['temp_count'] > 0): ?>
<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;">
(عضو: <?= money($familySub['member_sub']) ?>
<?php if ($familySub['spouse_count'] > 0): ?> + <?= $familySub['spouse_count'] ?> زوجة: <?= money(bcmul($familySub['spouse_sub'], (string) $familySub['spouse_count'], 2)) ?><?php endif; ?>
(العضو الجديد: <?= money($familySub['member_sub']) ?>
<?php if ($familySub['spouse_count'] > 0): ?> + <?= $familySub['spouse_count'] ?> زوجة إضافية: <?= money(bcmul($familySub['spouse_sub'], (string) $familySub['spouse_count'], 2)) ?><?php endif; ?>
<?php if ($familySub['child_count'] > 0): ?> + <?= $familySub['child_count'] ?> أبناء: <?= money(bcmul($familySub['child_sub'], (string) $familySub['child_count'], 2)) ?><?php endif; ?>
<?php if ($familySub['temp_count'] > 0): ?> + <?= $familySub['temp_count'] ?> مؤقتين: <?= money(bcmul($familySub['temp_sub'], (string) $familySub['temp_count'], 2)) ?><?php endif; ?>
+ تنمية: <?= money($familySub['dev_fee']) ?>)
......
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