Commit e4340b36 authored by Fares's avatar Fares

feat(death,waiver): enhance death module with 6 changes; fix waiver post-completion issues

Death module:
- Annual subscription now includes ALL family members (spouses, children, temps + dev fee)
- Temporary members distribution UI added to create form
- Payment receipt created in primary spouse's name (not deceased)
- Form number field added to membership application
- Deceased member name stored on new membership record
- Full Arabic audit trail logging on completion

Waiver module:
- Mark ALL family subscriptions as paid after completion (not just member)
- Set activated_by_payment_id on all dependents
- Copy membership_value from source to target member
- Add transferred_from_waiver_id and waived_from_member_name to members
- Display waiver source badge and archive section on member profile
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9a8823b4
......@@ -64,13 +64,13 @@
</div>
</div>
<!-- Primary member death: spouse selection + children assignment -->
<!-- Primary member death: spouse selection + children + temps assignment -->
<div id="primary-death-section" style="display:none;">
<?php if (count($spouses) > 0): ?>
<div class="card" style="margin-bottom:20px;border:2px solid #0D7377;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;background:#F0FDFA;">
<h3 style="margin:0;color:#0D7377;font-size:15px;">نقل العضوية — اختيار الزوجة الأساسية</h3>
<p style="margin:5px 0 0;font-size:12px;color:#6B7280;">الزوجة الأساسية ترث نفس رقم العضوية وتملأ استمارة جديدة (570 + اشتراك سنوي)</p>
<p style="margin:5px 0 0;font-size:12px;color:#6B7280;">الزوجة الأساسية ترث نفس رقم العضوية وتملأ استمارة جديدة</p>
</div>
<div style="padding:20px;">
<div class="form-group" style="margin-bottom:15px;">
......@@ -85,7 +85,7 @@
<?php if (count($spouses) > 1): ?>
<div class="form-group" style="margin-bottom:15px;">
<label class="form-label">الزوجات الإضافيات (عضويات منفصلة — 570 + اشتراك لكل واحدة)</label>
<label class="form-label">الزوجات الإضافيات (عضويات منفصلة)</label>
<?php foreach ($spouses as $s): ?>
<label style="display:flex;align-items:center;gap:8px;padding:5px 0;font-size:14px;" class="secondary-spouse-label" data-id="<?= (int) $s['id'] ?>">
<input type="checkbox" name="secondary_spouse_ids[]" value="<?= (int) $s['id'] ?>" class="secondary-spouse-cb">
......@@ -118,18 +118,52 @@
</div>
</div>
<?php endif; ?>
<?php if (count($temps) > 0): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#1F2937;font-size:15px;">توزيع الأعضاء المؤقتين</h3>
<p style="margin:5px 0 0;font-size:12px;color:#6B7280;">حدد لأي عضوية ينتقل كل عضو مؤقت (الافتراضي: الزوجة الأساسية)</p>
</div>
<div style="padding:20px;">
<?php foreach ($temps as $t): ?>
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid #F3F4F6;">
<span style="font-weight:600;min-width:150px;"><?= e($t['full_name_ar']) ?></span>
<select name="temps_assignment[<?= (int) $t['id'] ?>]" class="form-select" style="max-width:250px;">
<option value="primary">الزوجة الأساسية</option>
<?php foreach ($spouses as $s): ?>
<option value="<?= (int) $s['id'] ?>"><?= e($s['full_name_ar']) ?> (منفصلة)</option>
<?php endforeach; ?>
</select>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<!-- 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;">رسوم استمارة (570)</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($annual_sub) ?></td></tr>
<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>
<?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>
<?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>
<?php endif; ?>
<?php if ($family_sub['temp_count'] > 0): ?>
<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>
<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>
</div>
</div>
......
......@@ -6,11 +6,30 @@
<strong style="color:#0D7377;">نقل عضوية بسبب وفاة</strong>
الزوجة <?= e($spouse['full_name_ar'] ?? '—') ?> ترث العضوية رقم <?= e($member['membership_number'] ?? '—') ?>
<br><small style="color:#6B7280;">يجب ملء جميع البيانات المطلوبة لاستمارة العضوية الجديدة</small>
<br><small style="color:#DC2626;font-weight:600;">العضو المتوفى: <?= e($member['full_name_ar']) ?></small>
</div>
<form method="POST" action="/death/<?= (int) $case['id'] ?>/fill-form">
<?= csrf_field() ?>
<!-- Form Number & Deceased Info -->
<div class="card" style="margin-bottom:20px;border:2px solid #F59E0B;">
<div style="padding:15px 20px;border-bottom:1px solid #FDE68A;background:#FFFBEB;">
<h3 style="margin:0;color:#D97706;font-size:15px;">بيانات الاستمارة</h3>
</div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group">
<label class="form-label">رقم الاستمارة <span style="color:#DC2626;">*</span></label>
<input type="text" name="form_number" class="form-input" placeholder="رقم الاستمارة الورقية" style="direction:ltr;text-align:left;">
</div>
<div class="form-group">
<label class="form-label">اسم العضو المتوفى</label>
<input type="text" value="<?= e($member['full_name_ar']) ?>" class="form-input" readonly style="background:#F3F4F6;font-weight:600;">
<input type="hidden" name="deceased_member_name" value="<?= e($member['full_name_ar']) ?>">
</div>
</div>
</div>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;"><h3 style="margin:0;color:#0D7377;">البيانات الشخصية</h3></div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
......
......@@ -180,12 +180,19 @@ $canApprove = can('transfer.approve');
$secondaryCount = count(json_decode($case['secondary_spouses_json'], true));
}
$multiplier = 1 + $secondaryCount;
$baseFeePerSpouse = bcadd($fees['formFee'], $fees['annualSub'], 2);
$baseFeeTotal = bcmul($baseFeePerSpouse, (string) $multiplier, 2);
?>
<table style="font-size:13px;max-width:400px;">
<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['annualSub']) ?>)</td><td style="font-weight:600;"><?= money(bcmul($fees['annualSub'], (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 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; ?>
<?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']) ?>)
</td></tr>
<?php endif; ?>
<tr>
<td style="padding:4px 20px 4px 0;color:#6B7280;">
رسوم مجلس الأمناء
......@@ -262,6 +269,27 @@ $canApprove = can('transfer.approve');
</form>
<?php endif; ?>
<!-- Audit Trail (displayed when case is completed) -->
<?php
$notesData = !empty($case['notes']) ? json_decode($case['notes'], true) : [];
$auditLog = is_array($notesData) ? ($notesData['audit_log'] ?? []) : [];
?>
<?php if (!empty($auditLog)): ?>
<div class="card" style="margin-bottom:20px;margin-top:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;background:#F9FAFB;">
<h3 style="margin:0;color:#1F2937;font-size:15px;">سجل العمليات (Audit Trail)</h3>
</div>
<div style="padding:20px;">
<?php foreach ($auditLog as $entry): ?>
<div style="padding:6px 0;font-size:13px;border-bottom:1px solid #F3F4F6;display:flex;align-items:start;gap:8px;">
<span style="color:#0D7377;font-size:11px;margin-top:2px;"></span>
<span><?= e($entry) ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php $__template->endSection(); ?>
<?php $__template->section('scripts'); ?>
......
......@@ -255,6 +255,23 @@ class MemberController extends Controller
);
}
// Check if this member was created via waiver transfer
$waiverTransfer = null;
$waiverArchiveSnapshot = null;
if (!empty($member->transferred_from_waiver_id)) {
$waiverTransfer = $db->selectOne(
"SELECT wr.id, wr.source_member_id, wr.membership_number, wr.waiver_fee_amount, wr.waiver_fee_percentage,
wr.archive_snapshot_id, wr.created_at as waiver_date,
m.full_name_ar as source_member_name, m.id as source_member_id_ref
FROM waiver_requests wr LEFT JOIN members m ON m.id = wr.source_member_id
WHERE wr.id = ?",
[(int) $member->transferred_from_waiver_id]
);
if ($waiverTransfer && $waiverTransfer['archive_snapshot_id']) {
$waiverArchiveSnapshot = \App\Modules\Archive\Services\ArchiveService::getSnapshot((int) $waiverTransfer['archive_snapshot_id']);
}
}
$instRateData = RuleEngine::get('INSTALLMENT_INTEREST_RATE');
$instMonthsData = RuleEngine::get('INSTALLMENT_MAX_MONTHS');
......@@ -276,6 +293,8 @@ class MemberController extends Controller
'cancelledRequests' => $cancelledRequests,
'isSuperAdmin' => self::isSuperAdmin(),
'divorceTransfer' => $divorceTransfer,
'waiverTransfer' => $waiverTransfer,
'waiverArchiveSnapshot' => $waiverArchiveSnapshot,
'installmentPlan' => $member->status === 'pending_cheques'
? $db->selectOne("SELECT id, number_of_months FROM installment_plans WHERE member_id = ? AND status = 'active' ORDER BY id DESC LIMIT 1", [(int) $id])
: null,
......
This diff is collapsed.
......@@ -52,13 +52,15 @@ final class WaiverProcessor
], '`id` = ?', [(int) $waiver['source_member_id']]);
$db->update('members', [
'membership_number' => $waiver['membership_number'],
'status' => 'active',
'membership_type' => $sourceMember['membership_type'] ?? 'working',
'activated_by_payment_id' => $waiverPaymentId,
'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'),
'membership_number' => $waiver['membership_number'],
'status' => 'active',
'membership_type' => $sourceMember['membership_type'] ?? 'working',
'membership_value' => $sourceMember['membership_value'] ?? '0.00',
'activated_by_payment_id' => $waiverPaymentId,
'activated_at' => date('Y-m-d H:i:s'),
'transferred_from_waiver_id' => $waiverId,
'waived_from_member_name' => $sourceMember['full_name_ar'] ?? null,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $waiver['target_member_id']]);
ArchiveService::recordNumberTransfer($waiver['membership_number'], 'waiver', 'members', (int) $waiver['target_member_id']);
......@@ -71,12 +73,13 @@ final class WaiverProcessor
$db->commit();
// Generate subscription rows for target member and mark as paid
// Generate subscription rows for target member and mark ALL family as paid
$targetMemberId = (int) $waiver['target_member_id'];
try {
SubscriptionSyncService::syncForMember($targetMemberId);
// Mark current year subscriptions as paid (fee was collected in waiver payment)
// Mark current year subscriptions as paid for ALL family members
// (member + spouses + children + temporary — the waiver fee includes annual subscription)
$now = new \DateTime();
$month = (int) $now->format('n');
$year = (int) $now->format('Y');
......@@ -84,13 +87,12 @@ final class WaiverProcessor
$currentFy = $fyStart . '/' . ($fyStart + 1);
$pendingSubs = $db->select(
"SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND status IN ('pending','overdue')",
"SELECT id, total_amount, fine_amount, development_fee 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']]);
foreach ($pendingSubs as $sub) {
$paidAmount = bcadd(bcadd($sub['total_amount'], $sub['fine_amount'] ?? '0', 2), $sub['development_fee'] ?? '0', 2);
$db->update('subscriptions', [
'paid_amount' => $paidAmount,
......@@ -99,8 +101,22 @@ final class WaiverProcessor
'paid_at' => $ts,
'paid_by' => $paidByEmp,
'updated_at' => $ts,
], '`id` = ?', [(int) $ps['id']]);
], '`id` = ?', [(int) $sub['id']]);
}
// Also update dependent records to mark them as activated
$db->query(
"UPDATE spouses SET activated_by_payment_id = ?, updated_at = ? WHERE member_id = ? AND is_archived = 0 AND activated_by_payment_id IS NULL",
[$waiverPaymentId, $ts, $targetMemberId]
);
$db->query(
"UPDATE children SET activated_by_payment_id = ?, updated_at = ? WHERE member_id = ? AND is_archived = 0 AND activated_by_payment_id IS NULL",
[$waiverPaymentId, $ts, $targetMemberId]
);
$db->query(
"UPDATE temporary_members SET activated_by_payment_id = ?, updated_at = ? WHERE member_id = ? AND is_archived = 0 AND activated_by_payment_id IS NULL",
[$waiverPaymentId, $ts, $targetMemberId]
);
} catch (\Throwable $e) {
Logger::warning("Waiver subscription sync failed: " . $e->getMessage());
}
......
<?php
declare(strict_types=1);
return [
'up' => "
ALTER TABLE members ADD COLUMN form_number VARCHAR(50) NULL AFTER original_membership_number;
ALTER TABLE members ADD COLUMN deceased_member_name VARCHAR(255) NULL AFTER form_number
",
'down' => "
ALTER TABLE members DROP COLUMN IF EXISTS form_number;
ALTER TABLE members DROP COLUMN IF EXISTS deceased_member_name
",
];
<?php
declare(strict_types=1);
return [
'up' => "
ALTER TABLE members ADD COLUMN transferred_from_waiver_id BIGINT UNSIGNED NULL AFTER transferred_from_death_id;
ALTER TABLE members ADD COLUMN waived_from_member_name VARCHAR(255) NULL AFTER transferred_from_waiver_id
",
'down' => "
ALTER TABLE members DROP COLUMN IF EXISTS waived_from_member_name;
ALTER TABLE members DROP COLUMN IF EXISTS transferred_from_waiver_id
",
];
......@@ -597,6 +597,8 @@ These modules have the highest change risk due to many downstream consumers:
| activated_at | Reports | Cashier/bootstrap (on activation) |
| special_discount_id | BillingService, Members/show | Members/applyDiscount, Members/saveFillForm |
| transferred_from_divorce_id | Members/show (origin display) | Divorce/DivorceController |
| transferred_from_waiver_id | Members/show (origin display + archive) | Waiver/WaiverProcessor |
| waived_from_member_name | Members/show (source name display) | Waiver/WaiverProcessor |
**Events dispatched and full listener chains:**
......
# Death Module — Architecture Map
> **Last updated:** 2026-06-10
> **Last updated:** 2026-07-21
> **Status:** Living document — incrementally updated as new information is discovered
---
......@@ -12,10 +12,14 @@ The Death module manages **membership transfer upon death** of a member or their
- Transferring membership to the primary spouse (inherits SAME membership number)
- Creating separate new memberships for secondary spouses (get NEW numbers)
- Assigning children to the new primary member or secondary spouses
- Fee collection (form fee + annual subscription per spouse receiving membership)
- Assigning temporary members to the new primary member or secondary spouses
- Fee collection (form fee + annual subscription for ALL family members)
- Board review & trustee fee calculation before payment
- Form fill step (wife fills full membership application form before transfer completes)
- Death certificate documentation
- Form number tracking (رقم الاستمارة) and deceased member name on new membership
- Death certificate & inheritance notice documentation
- Archive snapshot of deceased member
- Full Arabic audit trail logging of all operations during completion
It does **NOT** directly manage:
- Separation/transfer logic for non-death scenarios (Transfers module)
......@@ -83,13 +87,14 @@ app/Modules/Death/
## 4. Death Case Status Lifecycle
```
recorded → fee_paid → pending_form_fill → completed
(primary_member only)
recorded → board_review → board_approved → fee_paid → pending_form_fill → completed
(primary_member only)
→ rejected (if board rejects)
recorded → completed (for spouse/child death — no fee, simpler flow)
```
**Status values:** recorded, fee_paid, pending_form_fill, completed
**Status values:** recorded, board_review, board_approved, rejected, fee_paid, pending_form_fill, completed
**Key invariant:** For `primary_member` death, status must pass through `pending_form_fill` to ensure the receiving spouse fills out a full membership application before transfer completes.
......@@ -170,12 +175,19 @@ recorded → completed (for spouse/child death — no fee, simpler flow)
```
For primary_member death:
Form Fee = SVC_TRANSFER_FORM (default 570 EGP)
Annual Sub = SVC_ANNUAL_MEMBER + DEVELOPMENT_FEE (default 492 + 35 = 527 EGP)
Annual Sub = SVC_ANNUAL_MEMBER + (spouses × SVC_ANNUAL_SPOUSE) + (children × SVC_ANNUAL_CHILD)
+ (temps × SVC_ANNUAL_TEMP) + DEVELOPMENT_FEE (once)
Example: member + 1 spouse + 2 children + 1 temp = 492 + 492 + 222×2 + 222 + 35 = 1,685 EGP
Base per membership = Form Fee + Annual Sub (all family)
Total Base = Base × (1 + number_of_secondary_spouses)
Trustee Fee = percentage of membership_value OR fixed amount (set during board approval)
Total = (Form Fee + Annual Sub) × (1 + number_of_secondary_spouses)
Grand Total = Total Base + Trustee Fee
Primary spouse: pays form_fee + annual_sub
Each secondary spouse: pays form_fee + annual_sub additionally
Payment receipt is created in the PRIMARY SPOUSE's name (not the deceased).
For spouse/child death: fee_amount = 0 (no transfer)
```
......@@ -213,7 +225,7 @@ For spouse/child death: fee_amount = 0 (no transfer)
| Payments | PaymentService (processPayment for direct pay) |
| Cashier | PaymentRequestService (queue payment for cashier processing) |
| Rules | RuleEngine (DEVELOPMENT_FEE) |
| ServiceCatalog | ServicePrice (SVC_TRANSFER_FORM, SVC_ANNUAL_MEMBER) |
| ServiceCatalog | ServicePrice (SVC_TRANSFER_FORM, SVC_ANNUAL_MEMBER, SVC_ANNUAL_SPOUSE, SVC_ANNUAL_CHILD, SVC_ANNUAL_TEMP) |
### 8.2 Other modules that IMPORT FROM Death:
......@@ -242,7 +254,10 @@ The Death module has **no own permissions**. It reuses Transfers module permissi
| Service Catalog Codes | Purpose |
|----------------------|---------|
| SVC_TRANSFER_FORM | Form fee (default 570 EGP) |
| SVC_ANNUAL_MEMBER | Annual subscription base (default 492 EGP) |
| SVC_ANNUAL_MEMBER | Annual subscription base — member (default 492 EGP) |
| SVC_ANNUAL_SPOUSE | Annual subscription — spouse (default 492 EGP) |
| SVC_ANNUAL_CHILD | Annual subscription — child (default 222 EGP) |
| SVC_ANNUAL_TEMP | Annual subscription — temp member (default 222 EGP) |
| Rule Engine Keys | Purpose |
|-----------------|---------|
......@@ -292,7 +307,7 @@ The Death module has **no own permissions**. It reuses Transfers module permissi
3. **No soft delete on model**: DeathCase records are permanent (no is_archived column)
4. **Form fill is mandatory for primary death**: Cannot complete without `primary_spouse_form_filled = 1`
5. **Children default to primary**: If `children_assignment_json` is empty, ALL children go to new primary member
6. **Temporary members always go to primary**: No assignment choice for temporary_members
6. **Temporary members can be assigned**: Distribution UI (like children) allows assigning temps to primary or secondary spouses
7. **activated_by_payment_id**: New member records link to the death_fee payment ID for audit trail
8. **Spouse/child death is simpler**: Just archives the dependent record, no membership transfer
9. **Dual payment paths**: Payment via Cashier queue (normal) or direct via `/pay` endpoint
......
# Waiver Module — Architecture Map
> **Last updated:** 2026-07-02 (Round 3: fully comprehensive debt check covering sales/seasonal/all payment types, board sees ALL dependents not just excess, per-individual detailed receipts with audit trail)
> **Last updated:** 2026-07-21 (Round 4: post-completion subscription marking for ALL family, waiver source tracking on members, membership_value transfer, archive display on member profile)
> **Status:** Living document — incrementally updated as new information is discovered
---
......
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