Commit 21dcc185 authored by Fares's avatar Fares

feat(members): unified timeline in insurance record for all procedures

The سجل العمليات التفصيلي now shows step-by-step entries for ALL
procedure types — death, waiver, and transfer/separation — not just
death cases. Waiver and transfer timelines are reconstructed from
request data (submission, approval, payment, data transfer, completion).

Timeline entries are sorted chronologically and color-coded by type.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 84a4f410
......@@ -945,12 +945,137 @@ class MemberController extends Controller
[$memberId]
);
// Build unified timeline from ALL procedure data
$timeline = [];
// Death audit logs (already detailed)
foreach ($deathAuditLogs as $log) {
$timeline[] = [
'type' => 'death',
'icon' => '&#x1f54a;',
'description' => $log['action_description_ar'],
'date' => $log['performed_at'],
'performer' => $log['performer_name'] ?? null,
'entity' => !empty($log['affected_entity_type']) ? $log['affected_entity_type'] . ' #' . $log['affected_entity_id'] : null,
];
}
// Waiver timeline (reconstruct from request data)
foreach ($waiverRequests as $wr) {
$isSource = ((int) $wr['source_member_id'] === $memberId);
$roleLabel = $isSource ? '(المتنازل)' : '(المتنازل إليه)';
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f4dd;',
'description' => 'تقديم طلب تنازل #' . $wr['id'] . ' — ' . $roleLabel . ' — رقم العضوية: ' . $wr['membership_number'],
'date' => $wr['created_at'], 'performer' => null, 'entity' => 'waiver_requests #' . $wr['id'],
];
if (!empty($wr['approved_at'])) {
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x2705;',
'description' => 'اعتماد طلب التنازل #' . $wr['id'] . ' من مجلس الإدارة',
'date' => $wr['approved_at'], 'performer' => $wr['approved_by_name'] ?? null, 'entity' => null,
];
}
// Fee payment
$waiverPayment = $db->selectOne(
"SELECT payment_date, amount FROM payments WHERE payment_type = 'waiver_fee' AND related_entity_type = 'waiver_requests' AND related_entity_id = ? AND is_voided = 0 LIMIT 1",
[(int) $wr['id']]
);
if ($waiverPayment) {
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f4b0;',
'description' => 'سداد رسوم التنازل #' . $wr['id'] . ' — ' . money($waiverPayment['amount']),
'date' => $waiverPayment['payment_date'], 'performer' => null, 'entity' => null,
];
}
if ($wr['status'] === 'completed') {
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f4e6;',
'description' => 'أخذ لقطة أرشيف للعضوية الأصلية (العضو المتنازل: ' . ($wr['source_member_name'] ?? '') . ')',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => 'archive_snapshots',
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f46a;',
'description' => 'نقل التابعين (الزوجات، الأبناء، الأعضاء المؤقتين) إلى العضوية الجديدة',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => 'members #' . ($wr['target_member_id'] ?? ''),
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f4b3;',
'description' => 'نقل البيانات المالية (المدفوعات، الاشتراكات، الأقساط، الغرامات) إلى العضوية الجديدة',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => null,
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f4c4;',
'description' => 'نقل المستندات والمرفقات إلى العضوية الجديدة',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => null,
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x2728;',
'description' => 'تفعيل العضوية الجديدة باسم: ' . ($wr['target_member_name'] ?? '—') . ' — رقم العضوية: ' . $wr['membership_number'],
'date' => $wr['updated_at'], 'performer' => null, 'entity' => 'members #' . ($wr['target_member_id'] ?? ''),
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x1f512;',
'description' => 'أرشفة العضوية الأصلية (المتنازل: ' . ($wr['source_member_name'] ?? '') . ') — الحالة: متنازل',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => 'members #' . $wr['source_member_id'],
];
$timeline[] = [
'type' => 'waiver', 'icon' => '&#x2705;',
'description' => 'إتمام عملية التنازل #' . $wr['id'] . ' بنجاح',
'date' => $wr['updated_at'], 'performer' => null, 'entity' => null,
];
}
}
// Transfer timeline (reconstruct from request data)
foreach ($transferRequests as $tr) {
$isSource = ((int) $tr['source_member_id'] === $memberId);
$trTypeLabel = match($tr['transfer_type'] ?? '') {
'full_transfer' => 'نقل كامل', 'child_separation' => 'فصل أبناء',
'child_mandatory_25' => 'فصل إلزامي', default => $tr['transfer_type'] ?? '',
};
$roleLabel = $isSource ? '(العضوية الأصلية)' : '(العضوية الجديدة)';
$timeline[] = [
'type' => 'transfer', 'icon' => '&#x1f504;',
'description' => 'تقديم طلب ' . $trTypeLabel . ' #' . $tr['id'] . ' — ' . $roleLabel,
'date' => $tr['created_at'], 'performer' => null, 'entity' => 'transfer_requests #' . $tr['id'],
];
if (!empty($tr['approved_at'])) {
$timeline[] = [
'type' => 'transfer', 'icon' => '&#x2705;',
'description' => 'اعتماد طلب ' . $trTypeLabel . ' #' . $tr['id'],
'date' => $tr['approved_at'], 'performer' => $tr['approved_by_name'] ?? null, 'entity' => null,
];
}
$trPayment = $db->selectOne(
"SELECT payment_date, amount FROM payments WHERE payment_type IN ('transfer_fee','separation_fee') AND related_entity_type = 'transfer_requests' AND related_entity_id = ? AND is_voided = 0 LIMIT 1",
[(int) $tr['id']]
);
if ($trPayment) {
$timeline[] = [
'type' => 'transfer', 'icon' => '&#x1f4b0;',
'description' => 'سداد رسوم ' . $trTypeLabel . ' #' . $tr['id'] . ' — ' . money($trPayment['amount']),
'date' => $trPayment['payment_date'], 'performer' => null, 'entity' => null,
];
}
if ($tr['status'] === 'completed' && !empty($tr['completed_at'])) {
$timeline[] = [
'type' => 'transfer', 'icon' => '&#x2705;',
'description' => 'إتمام عملية ' . $trTypeLabel . ' #' . $tr['id'] . ' بنجاح',
'date' => $tr['completed_at'], 'performer' => null, 'entity' => null,
];
}
}
// Sort timeline by date
usort($timeline, fn($a, $b) => strcmp($a['date'] ?? '', $b['date'] ?? ''));
return $this->view('Members.Views.insurance-record', [
'member' => (object) $member,
'deathCases' => $deathCases,
'waiverRequests' => $waiverRequests,
'transferRequests' => $transferRequests,
'deathAuditLogs' => $deathAuditLogs,
'timeline' => $timeline,
'procedurePayments' => $procedurePayments,
'snapshots' => $snapshots,
]);
......
......@@ -248,37 +248,37 @@ if (!$hasAny):
</div>
<?php endif; ?>
<!-- ═══════ DEATH AUDIT TIMELINE ═══════ -->
<?php if (!empty($deathAuditLogs)): ?>
<!-- ═══════ UNIFIED TIMELINE ═══════ -->
<?php if (!empty($timeline)): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;background:#F9FAFB;">
<h3 style="margin:0;font-size:16px;color:#1F2937;">&#x23f3; سجل العمليات التفصيلي — حالات الوفاة</h3>
<h3 style="margin:0;font-size:16px;color:#1F2937;">&#x23f3; سجل العمليات التفصيلي (<?= count($timeline) ?> عملية)</h3>
</div>
<div style="padding:20px;">
<div style="position:relative;padding-right:30px;">
<div style="position:absolute;right:12px;top:0;bottom:0;width:2px;background:#E5E7EB;"></div>
<?php foreach ($deathAuditLogs as $log):
$iconMap = [
'snapshot_taken' => '&#x1f4f7;', 'member_archived' => '&#x1f4e6;',
'new_member_created' => '&#x2728;', 'children_transferred' => '&#x1f46a;',
'temps_transferred' => '&#x1f465;', 'secondary_member_created' => '&#x1f4cc;',
'installments_transferred' => '&#x1f4b3;', 'documents_linked' => '&#x1f4c4;',
'subscriptions_created' => '&#x1f4c5;', 'transfer_completed' => '&#x2705;',
'case_completed' => '&#x2705;', 'spouse_deceased' => '&#x1f54a;', 'child_deceased' => '&#x1f54a;',
];
$icon = $iconMap[$log['action_type']] ?? '&#x25cf;';
<?php foreach ($timeline as $entry):
$borderColor = match($entry['type']) {
'death' => '#6B7280', 'waiver' => '#7C3AED', 'transfer' => '#0284C7', default => '#3B82F6',
};
$typeBadge = match($entry['type']) {
'death' => '<span style="background:#F3F4F6;color:#374151;padding:1px 5px;border-radius:3px;font-size:9px;margin-left:6px;">وفاة</span>',
'waiver' => '<span style="background:#F3E8FF;color:#6B21A8;padding:1px 5px;border-radius:3px;font-size:9px;margin-left:6px;">تنازل</span>',
'transfer' => '<span style="background:#DBEAFE;color:#1E40AF;padding:1px 5px;border-radius:3px;font-size:9px;margin-left:6px;">نقل/فصل</span>',
default => '',
};
?>
<div style="position:relative;margin-bottom:16px;padding-right:25px;">
<div style="position:absolute;right:-6px;top:4px;width:24px;height:24px;background:#fff;border:2px solid #3B82F6;border-radius:50%;text-align:center;line-height:20px;font-size:12px;"><?= $icon ?></div>
<div style="position:absolute;right:-6px;top:4px;width:24px;height:24px;background:#fff;border:2px solid <?= $borderColor ?>;border-radius:50%;text-align:center;line-height:20px;font-size:12px;"><?= $entry['icon'] ?></div>
<div style="background:#F9FAFB;border:1px solid #E5E7EB;border-radius:8px;padding:10px 14px;">
<div style="font-weight:600;color:#1F2937;margin-bottom:3px;font-size:13px;"><?= e($log['action_description_ar']) ?></div>
<div style="font-weight:600;color:#1F2937;margin-bottom:3px;font-size:13px;"><?= e($entry['description']) ?> <?= $typeBadge ?></div>
<div style="font-size:11px;color:#6B7280;display:flex;gap:12px;flex-wrap:wrap;">
<span><?= e($log['performed_at']) ?></span>
<?php if (!empty($log['performer_name'])): ?>
<span>بواسطة: <?= e($log['performer_name']) ?></span>
<span><?= e($entry['date'] ?? '') ?></span>
<?php if (!empty($entry['performer'])): ?>
<span>بواسطة: <?= e($entry['performer']) ?></span>
<?php endif; ?>
<?php if (!empty($log['affected_entity_type'])): ?>
<span style="color:#9CA3AF;"><?= e($log['affected_entity_type']) ?> #<?= (int) $log['affected_entity_id'] ?></span>
<?php if (!empty($entry['entity'])): ?>
<span style="color:#9CA3AF;"><?= e($entry['entity']) ?></span>
<?php endif; ?>
</div>
</div>
......
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