Commit 4eea7873 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(members): add Archive screen for browsing archived members

New member-centric archive view inside شئون العضوية:
- Searchable listing with filters (name, number, NID, phone, status, date, operator)
- Detailed show page with member data, dependents, financials, number chain
- Linked member navigation (old  new after transfer/waiver/death)
- Audit trail display with before/after field changes
- Sidebar with archive info, snapshots, and number chain timeline
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 552ca604
<?php
declare(strict_types=1);
namespace App\Modules\Members\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\Archive\Services\ArchiveService;
class MemberArchiveController extends Controller
{
public function index(Request $request): Response
{
$db = App::getInstance()->db();
$filters = [
'search' => trim((string) $request->get('q', '')),
'status' => $request->get('status', ''),
'date_from' => $request->get('date_from', ''),
'date_to' => $request->get('date_to', ''),
'number' => trim((string) $request->get('number', '')),
'nid' => trim((string) $request->get('nid', '')),
'phone' => trim((string) $request->get('phone', '')),
'operator' => trim((string) $request->get('operator', '')),
];
$where = ['m.is_archived = 1'];
$params = [];
if ($filters['search'] !== '') {
$where[] = "(m.full_name_ar LIKE ? OR m.full_name_en LIKE ?)";
$params[] = '%' . $filters['search'] . '%';
$params[] = '%' . $filters['search'] . '%';
}
if ($filters['status'] !== '') {
$where[] = "m.status = ?";
$params[] = $filters['status'];
}
if ($filters['date_from'] !== '') {
$where[] = "m.archived_at >= ?";
$params[] = $filters['date_from'] . ' 00:00:00';
}
if ($filters['date_to'] !== '') {
$where[] = "m.archived_at <= ?";
$params[] = $filters['date_to'] . ' 23:59:59';
}
if ($filters['number'] !== '') {
$where[] = "(a.membership_number = ? OR m.membership_number = ?)";
$params[] = $filters['number'];
$params[] = $filters['number'];
}
if ($filters['nid'] !== '') {
$where[] = "m.national_id LIKE ?";
$params[] = '%' . $filters['nid'] . '%';
}
if ($filters['phone'] !== '') {
$where[] = "(m.phone_mobile LIKE ? OR m.phone_home LIKE ?)";
$params[] = '%' . $filters['phone'] . '%';
$params[] = '%' . $filters['phone'] . '%';
}
if ($filters['operator'] !== '') {
$where[] = "e.full_name_ar LIKE ?";
$params[] = '%' . $filters['operator'] . '%';
}
$whereClause = implode(' AND ', $where);
$page = max(1, (int) $request->get('page', 1));
$perPage = 25;
$offset = ($page - 1) * $perPage;
$countSql = "SELECT COUNT(*) as total FROM members m
LEFT JOIN archive_snapshots a ON a.entity_type = 'members' AND a.entity_id = m.id
LEFT JOIN employees e ON e.id = m.archived_by
WHERE {$whereClause}";
$total = (int) ($db->selectOne($countSql, $params)['total'] ?? 0);
$sql = "SELECT m.id, m.full_name_ar, m.national_id, m.phone_mobile, m.membership_type, m.status,
m.archived_at, m.archived_by,
a.membership_number as archived_membership_number, a.snapshot_reason, a.id as snapshot_id,
e.full_name_ar as operator_name
FROM members m
LEFT JOIN archive_snapshots a ON a.entity_type = 'members' AND a.entity_id = m.id
LEFT JOIN employees e ON e.id = m.archived_by
WHERE {$whereClause}
GROUP BY m.id
ORDER BY m.archived_at DESC
LIMIT {$perPage} OFFSET {$offset}";
$rows = $db->select($sql, $params);
$lastPage = max(1, (int) ceil($total / $perPage));
$pagination = [
'current_page' => $page,
'last_page' => $lastPage,
'total' => $total,
'has_prev' => $page > 1,
'has_next' => $page < $lastPage,
'prev_page' => $page - 1,
'next_page' => $page + 1,
'pages' => self::paginateRange($page, $lastPage),
];
return $this->view('Members.Views.archive_index', [
'rows' => $rows,
'pagination' => $pagination,
'filters' => $filters,
'total' => $total,
]);
}
public function show(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 1", [(int) $id]);
if (!$member) return $this->redirect('/members/archive')->withError('العضو المؤرشف غير موجود');
// Get all snapshots for this member
$snapshots = $db->select(
"SELECT * FROM archive_snapshots WHERE entity_type = 'members' AND entity_id = ? ORDER BY snapshot_taken_at DESC",
[(int) $id]
);
// Primary snapshot (most recent)
$primarySnapshot = null;
if (!empty($snapshots)) {
$primarySnapshot = ArchiveService::getSnapshot((int) $snapshots[0]['id']);
}
// Get number chain if membership number exists in snapshot
$numberChain = [];
$membershipNumber = $primarySnapshot['membership_number'] ?? null;
if ($membershipNumber) {
$numberChain = ArchiveService::getNumberChain($membershipNumber);
}
// Get linked new member (who received the number)
$linkedMember = null;
if ($membershipNumber) {
$currentHolder = ArchiveService::getCurrentHolder($membershipNumber);
if ($currentHolder && (int) $currentHolder['holder_entity_id'] !== (int) $id) {
$linkedMember = $db->selectOne(
"SELECT id, full_name_ar, membership_number, status FROM members WHERE id = ?",
[(int) $currentHolder['holder_entity_id']]
);
}
}
// Get audit trail for this member
$auditTrail = $db->select(
"SELECT * FROM audit_trail WHERE entity_type = 'members' AND entity_id = ? ORDER BY created_at DESC LIMIT 50",
[(int) $id]
);
// Dependents from snapshot related_data or live data
$dependents = [
'spouses' => $db->select("SELECT * FROM spouses WHERE member_id = ?", [(int) $id]),
'children' => $db->select("SELECT * FROM children WHERE member_id = ?", [(int) $id]),
'temporary' => $db->select("SELECT * FROM temporary_members WHERE member_id = ?", [(int) $id]),
];
// Operator info
$operator = null;
if ($member['archived_by']) {
$operator = $db->selectOne("SELECT id, full_name_ar FROM employees WHERE id = ?", [(int) $member['archived_by']]);
}
return $this->view('Members.Views.archive_show', [
'member' => $member,
'snapshots' => $snapshots,
'primarySnapshot' => $primarySnapshot,
'numberChain' => $numberChain,
'linkedMember' => $linkedMember,
'auditTrail' => $auditTrail,
'dependents' => $dependents,
'operator' => $operator,
'membershipNumber' => $membershipNumber,
]);
}
private static function paginateRange(int $current, int $last): array
{
if ($last <= 7) return range(1, $last);
$pages = [1];
$start = max(2, $current - 2);
$end = min($last - 1, $current + 2);
if ($start > 2) $pages[] = '...';
for ($i = $start; $i <= $end; $i++) $pages[] = $i;
if ($end < $last - 1) $pages[] = '...';
$pages[] = $last;
return $pages;
}
}
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
return [ return [
['GET', '/members/archive', 'Members\Controllers\MemberArchiveController@index', ['auth'], 'member.archive'],
['GET', '/members/archive/{id:\d+}', 'Members\Controllers\MemberArchiveController@show', ['auth'], 'member.archive'],
['GET', '/members', 'Members\Controllers\MemberController@index', ['auth'], 'member.view'], ['GET', '/members', 'Members\Controllers\MemberController@index', ['auth'], 'member.view'],
['GET', '/members/create', 'Members\Controllers\MemberController@create', ['auth'], 'member.create'], ['GET', '/members/create', 'Members\Controllers\MemberController@create', ['auth'], 'member.create'],
['POST', '/members', 'Members\Controllers\MemberController@store', ['auth', 'csrf'], 'member.create'], ['POST', '/members', 'Members\Controllers\MemberController@store', ['auth', 'csrf'], 'member.create'],
......
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>أرشيف الأعضاء<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<?php
$statusLabels = ['deceased' => 'وفاة', 'transferred' => 'تحويل', 'waived' => 'تنازل', 'divorced' => 'طلاق', 'cancelled' => 'إلغاء'];
$statusColors = ['deceased' => '#6B7280', 'transferred' => '#0284C7', 'waived' => '#7C3AED', 'divorced' => '#DC2626', 'cancelled' => '#D97706'];
?>
<!-- Header -->
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
<div>
<h2 style="margin:0;color:#1A1A2E;">أرشيف الأعضاء</h2>
<p style="margin:4px 0 0;font-size:13px;color:#6B7280;">جميع العضويات المؤرشفة — وفاة، تنازل، تحويل، طلاق، إلغاء (<?= number_format($total) ?> سجل)</p>
</div>
</div>
<!-- Search & Filters -->
<div class="card" style="margin-bottom:20px;padding:15px;">
<form method="GET" action="/members/archive" style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;">
<div>
<label class="form-label" style="font-size:12px;">اسم العضو</label>
<input type="text" name="q" value="<?= e($filters['search'] ?? '') ?>" placeholder="بحث بالاسم..." class="form-input" style="min-width:150px;">
</div>
<div>
<label class="form-label" style="font-size:12px;">رقم العضوية</label>
<input type="text" name="number" value="<?= e($filters['number'] ?? '') ?>" placeholder="1001" class="form-input" style="min-width:100px;">
</div>
<div>
<label class="form-label" style="font-size:12px;">الرقم القومي</label>
<input type="text" name="nid" value="<?= e($filters['nid'] ?? '') ?>" placeholder="الرقم القومي" class="form-input" style="min-width:130px;">
</div>
<div>
<label class="form-label" style="font-size:12px;">الهاتف</label>
<input type="text" name="phone" value="<?= e($filters['phone'] ?? '') ?>" placeholder="رقم الهاتف" class="form-input" style="min-width:110px;">
</div>
<div>
<label class="form-label" style="font-size:12px;">نوع الأرشفة</label>
<select name="status" class="form-select" style="min-width:100px;">
<option value="">الكل</option>
<?php foreach ($statusLabels as $k => $v): ?>
<option value="<?= e($k) ?>" <?= ($filters['status'] ?? '') === $k ? 'selected' : '' ?>><?= e($v) ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="form-label" style="font-size:12px;">من تاريخ</label>
<input type="date" name="date_from" value="<?= e($filters['date_from'] ?? '') ?>" class="form-input">
</div>
<div>
<label class="form-label" style="font-size:12px;">إلى تاريخ</label>
<input type="date" name="date_to" value="<?= e($filters['date_to'] ?? '') ?>" class="form-input">
</div>
<div>
<label class="form-label" style="font-size:12px;">الموظف المنفذ</label>
<input type="text" name="operator" value="<?= e($filters['operator'] ?? '') ?>" placeholder="اسم الموظف" class="form-input" style="min-width:120px;">
</div>
<button type="submit" class="btn btn-primary">بحث</button>
<a href="/members/archive" class="btn btn-outline" style="color:#6B7280;">مسح</a>
</form>
</div>
<!-- Results Table -->
<div class="card">
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th style="width:50px;">#</th>
<th>العضو</th>
<th>رقم العضوية</th>
<th>الرقم القومي</th>
<th>نوع الأرشفة</th>
<th>السبب</th>
<th>تاريخ الأرشفة</th>
<th>بواسطة</th>
<th style="width:100px;">الإجراءات</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): ?>
<?php $color = $statusColors[$r['status']] ?? '#6B7280'; ?>
<tr>
<td><?= (int) $r['id'] ?></td>
<td>
<a href="/members/archive/<?= (int) $r['id'] ?>" style="color:#0D7377;font-weight:600;text-decoration:none;">
<?= e($r['full_name_ar'] ?? '') ?>
</a>
<?php if ($r['phone_mobile']): ?>
<div style="font-size:11px;color:#6B7280;"><?= e($r['phone_mobile']) ?></div>
<?php endif; ?>
</td>
<td>
<?php if ($r['archived_membership_number']): ?>
<a href="/archive/number-chain/<?= urlencode($r['archived_membership_number']) ?>" style="color:#0D7377;font-weight:700;"><?= e($r['archived_membership_number']) ?></a>
<?php else: ?>
<span style="color:#9CA3AF;"></span>
<?php endif; ?>
</td>
<td style="font-size:12px;font-family:monospace;"><?= e($r['national_id'] ?? '—') ?></td>
<td>
<span style="color:<?= $color ?>;font-weight:600;font-size:13px;display:inline-flex;align-items:center;gap:4px;">
<span style="width:8px;height:8px;border-radius:50%;background:<?= $color ?>;display:inline-block;"></span>
<?= e($statusLabels[$r['status']] ?? $r['status']) ?>
</span>
</td>
<td style="font-size:12px;color:#6B7280;"><?= e($r['snapshot_reason'] ?? '—') ?></td>
<td style="font-size:12px;white-space:nowrap;"><?= $r['archived_at'] ? arabic_date($r['archived_at']) : '—' ?></td>
<td style="font-size:12px;"><?= e($r['operator_name'] ?? 'النظام') ?></td>
<td>
<a href="/members/archive/<?= (int) $r['id'] ?>" class="btn btn-sm btn-outline">عرض</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($rows)): ?>
<tr><td colspan="9" style="text-align:center;padding:50px;color:#6B7280;">لا توجد سجلات مؤرشفة مطابقة لمعايير البحث</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if (($pagination['last_page'] ?? 1) > 1): ?>
<div style="padding:15px;display:flex;justify-content:center;">
<nav>
<ul style="display:flex;gap:5px;list-style:none;padding:0;margin:0;">
<?php if ($pagination['has_prev'] ?? false): ?>
<li><a href="?page=<?= $pagination['prev_page'] ?>&<?= http_build_query(array_diff_key($filters, ['page' => ''])) ?>" class="btn btn-sm btn-outline">السابق</a></li>
<?php endif; ?>
<?php foreach ($pagination['pages'] ?? [] as $p): ?>
<?php if ($p === '...'): ?>
<li style="padding:6px 10px;color:#6B7280;">...</li>
<?php else: ?>
<li><a href="?page=<?= $p ?>&<?= http_build_query(array_diff_key($filters, ['page' => ''])) ?>" class="btn btn-sm <?= $p === ($pagination['current_page'] ?? 1) ? 'btn-primary' : 'btn-outline' ?>"><?= $p ?></a></li>
<?php endif; ?>
<?php endforeach; ?>
<?php if ($pagination['has_next'] ?? false): ?>
<li><a href="?page=<?= $pagination['next_page'] ?>&<?= http_build_query(array_diff_key($filters, ['page' => ''])) ?>" class="btn btn-sm btn-outline">التالي</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
<?php endif; ?>
</div>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>أرشيف — <?= e($member['full_name_ar'] ?? '') ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/members/archive" class="btn btn-outline">← رجوع للأرشيف</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<?php
$statusLabels = ['deceased' => 'وفاة', 'transferred' => 'تحويل', 'waived' => 'تنازل', 'divorced' => 'طلاق', 'cancelled' => 'إلغاء'];
$statusColors = ['deceased' => '#6B7280', 'transferred' => '#0284C7', 'waived' => '#7C3AED', 'divorced' => '#DC2626', 'cancelled' => '#D97706'];
$color = $statusColors[$member['status']] ?? '#6B7280';
$data = $primarySnapshot['full_data'] ?? [];
$related = $primarySnapshot['related_data'] ?? [];
?>
<!-- Status Banner -->
<div style="padding:15px 20px;background:<?= $color ?>15;border:2px solid <?= $color ?>;border-radius:8px;margin-bottom:20px;display:flex;justify-content:space-between;align-items:center;">
<div style="display:flex;align-items:center;gap:12px;">
<span style="width:12px;height:12px;border-radius:50%;background:<?= $color ?>;"></span>
<span style="font-weight:700;font-size:16px;color:<?= $color ?>;">مؤرشف — <?= e($statusLabels[$member['status']] ?? $member['status']) ?></span>
</div>
<div style="text-align:left;font-size:13px;color:#6B7280;">
<div>تاريخ الأرشفة: <strong><?= $member['archived_at'] ? arabic_date($member['archived_at']) : '—' ?></strong></div>
<?php if ($operator): ?>
<div>بواسطة: <strong><?= e($operator['full_name_ar']) ?></strong></div>
<?php endif; ?>
</div>
</div>
<div style="display:grid;grid-template-columns:2fr 1fr;gap:20px;">
<div>
<!-- Member Data -->
<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;">
<table style="width:100%;font-size:14px;">
<tr><td style="padding:6px 0;color:#6B7280;width:30%;">رقم العضوية</td><td style="padding:6px 0;font-weight:700;font-size:16px;"><?= e($membershipNumber ?? ($data['membership_number'] ?? '—')) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الاسم بالكامل</td><td style="padding:6px 0;font-weight:600;"><?= e($member['full_name_ar'] ?? '') ?></td></tr>
<?php if (!empty($member['full_name_en'])): ?><tr><td style="padding:6px 0;color:#6B7280;">الاسم بالإنجليزية</td><td style="padding:6px 0;"><?= e($member['full_name_en']) ?></td></tr><?php endif; ?>
<tr><td style="padding:6px 0;color:#6B7280;">الرقم القومي</td><td style="padding:6px 0;font-family:monospace;"><?= e($member['national_id'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">تاريخ الميلاد</td><td style="padding:6px 0;"><?= e($member['birth_date'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">النوع</td><td style="padding:6px 0;"><?= ($member['gender'] ?? '') === 'male' ? 'ذكر' : (($member['gender'] ?? '') === 'female' ? 'أنثى' : '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">نوع العضوية</td><td style="padding:6px 0;"><?= e($member['membership_type'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الهاتف</td><td style="padding:6px 0;"><?= e($member['phone_mobile'] ?? '—') ?></td></tr>
<?php if (!empty($member['phone_home'])): ?><tr><td style="padding:6px 0;color:#6B7280;">هاتف المنزل</td><td style="padding:6px 0;"><?= e($member['phone_home']) ?></td></tr><?php endif; ?>
<?php if (!empty($member['email'])): ?><tr><td style="padding:6px 0;color:#6B7280;">البريد</td><td style="padding:6px 0;"><?= e($member['email']) ?></td></tr><?php endif; ?>
<?php if (!empty($member['address'])): ?><tr><td style="padding:6px 0;color:#6B7280;">العنوان</td><td style="padding:6px 0;"><?= e($member['address']) ?></td></tr><?php endif; ?>
<?php if (!empty($member['occupation'])): ?><tr><td style="padding:6px 0;color:#6B7280;">الوظيفة</td><td style="padding:6px 0;"><?= e($member['occupation']) ?></td></tr><?php endif; ?>
<tr><td style="padding:6px 0;color:#6B7280;">تاريخ الإنشاء</td><td style="padding:6px 0;"><?= e($member['created_at'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">آخر تعديل</td><td style="padding:6px 0;"><?= e($member['updated_at'] ?? '—') ?></td></tr>
</table>
</div>
</div>
<!-- Dependents -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#0D7377;">التابعون (<?= count($dependents['spouses'] ?? []) + count($dependents['children'] ?? []) + count($dependents['temporary'] ?? []) ?>)</h3>
</div>
<div style="padding:20px;">
<?php if (!empty($dependents['spouses'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">الزوجات (<?= count($dependents['spouses']) ?>)</h4>
<table class="data-table" style="margin-bottom:15px;font-size:13px;">
<thead><tr><th>الاسم</th><th>الرقم القومي</th><th>الحالة</th></tr></thead>
<tbody>
<?php foreach ($dependents['spouses'] as $s): ?>
<tr>
<td><?= e($s['full_name_ar'] ?? '') ?></td>
<td style="font-family:monospace;"><?= e($s['national_id'] ?? '—') ?></td>
<td><?= ($s['is_archived'] ?? 0) ? '<span style="color:#DC2626;">مؤرشف</span>' : '<span style="color:#059669;">فعال</span>' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($dependents['children'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">الأبناء (<?= count($dependents['children']) ?>)</h4>
<table class="data-table" style="margin-bottom:15px;font-size:13px;">
<thead><tr><th>الاسم</th><th>تاريخ الميلاد</th><th>النوع</th><th>الحالة</th></tr></thead>
<tbody>
<?php foreach ($dependents['children'] as $c): ?>
<tr>
<td><?= e($c['full_name_ar'] ?? '') ?></td>
<td><?= e($c['birth_date'] ?? '—') ?></td>
<td><?= ($c['gender'] ?? '') === 'male' ? 'ذكر' : 'أنثى' ?></td>
<td><?= ($c['is_archived'] ?? 0) ? '<span style="color:#DC2626;">مؤرشف</span>' : '<span style="color:#059669;">فعال</span>' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($dependents['temporary'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">الأعضاء المؤقتون (<?= count($dependents['temporary']) ?>)</h4>
<table class="data-table" style="font-size:13px;">
<thead><tr><th>الاسم</th><th>الرقم القومي</th><th>الحالة</th></tr></thead>
<tbody>
<?php foreach ($dependents['temporary'] as $t): ?>
<tr>
<td><?= e($t['full_name_ar'] ?? '') ?></td>
<td style="font-family:monospace;"><?= e($t['national_id'] ?? '—') ?></td>
<td><?= ($t['is_archived'] ?? 0) ? '<span style="color:#DC2626;">مؤرشف</span>' : '<span style="color:#059669;">فعال</span>' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (empty($dependents['spouses']) && empty($dependents['children']) && empty($dependents['temporary'])): ?>
<div style="text-align:center;padding:20px;color:#6B7280;">لا يوجد تابعون مسجلون</div>
<?php endif; ?>
</div>
</div>
<!-- Financial Data from Snapshot -->
<?php if (!empty($related['subscriptions']) || !empty($related['fines']) || !empty($related['payments'])): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#D97706;">البيانات المالية (وقت الأرشفة)</h3>
</div>
<div style="padding:20px;">
<?php if (!empty($related['subscriptions'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">الاشتراكات (<?= count($related['subscriptions']) ?>)</h4>
<table class="data-table" style="margin-bottom:15px;font-size:12px;">
<thead><tr><th>السنة</th><th>الحالة</th><th>المبلغ</th><th>المدفوع</th></tr></thead>
<tbody>
<?php foreach (array_slice($related['subscriptions'], 0, 10) as $sub): ?>
<tr>
<td><?= e($sub['year'] ?? $sub['subscription_year'] ?? '—') ?></td>
<td><?= e($sub['status'] ?? '—') ?></td>
<td style="text-align:left;"><?= money($sub['total_amount'] ?? '0') ?></td>
<td style="text-align:left;"><?= money($sub['paid_amount'] ?? '0') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($related['fines'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">الغرامات (<?= count($related['fines']) ?>)</h4>
<table class="data-table" style="margin-bottom:15px;font-size:12px;">
<thead><tr><th>السبب</th><th>المبلغ</th><th>الحالة</th></tr></thead>
<tbody>
<?php foreach (array_slice($related['fines'], 0, 10) as $fine): ?>
<tr>
<td><?= e($fine['reason'] ?? '—') ?></td>
<td style="text-align:left;"><?= money($fine['amount'] ?? '0') ?></td>
<td><?= e($fine['status'] ?? '—') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if (!empty($related['payments'])): ?>
<h4 style="margin:0 0 10px;font-size:14px;color:#374151;">المدفوعات (<?= count($related['payments']) ?>)</h4>
<div style="font-size:12px;color:#6B7280;">عدد المدفوعات المحفوظة: <?= count($related['payments']) ?><a href="/archive/<?= (int) ($snapshots[0]['id'] ?? 0) ?>" style="color:#0D7377;">عرض التفاصيل في اللقطة</a></div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- Audit Trail -->
<?php if (!empty($auditTrail)): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#1A1A2E;">سجل التعديلات (Audit Trail)</h3>
</div>
<div style="padding:20px;max-height:400px;overflow-y:auto;">
<?php foreach ($auditTrail as $log): ?>
<div style="padding:10px 0;border-bottom:1px solid #F3F4F6;font-size:13px;">
<div style="display:flex;justify-content:space-between;margin-bottom:4px;">
<strong style="color:#374151;"><?= e($log['action'] ?? '') ?></strong>
<span style="font-size:11px;color:#9CA3AF;"><?= e($log['created_at'] ?? '') ?></span>
</div>
<div style="color:#6B7280;font-size:12px;">
<?php if ($log['employee_name']): ?>
بواسطة: <?= e($log['employee_name']) ?>
<?php endif; ?>
<?php if ($log['changed_fields_json']): ?>
<?php $changes = json_decode($log['changed_fields_json'], true) ?? []; ?>
<?php if (!empty($changes)): ?>
<div style="margin-top:4px;padding:6px 10px;background:#F9FAFB;border-radius:4px;">
<?php foreach (array_slice($changes, 0, 5) as $field => $vals): ?>
<div style="margin-bottom:2px;">
<span style="color:#6B7280;"><?= e($field) ?>:</span>
<span style="color:#DC2626;text-decoration:line-through;"><?= e(is_array($vals) ? ($vals['old'] ?? '') : '') ?></span>
<span style="color:#059669;"><?= e(is_array($vals) ? ($vals['new'] ?? '') : (string) $vals) ?></span>
</div>
<?php endforeach; ?>
<?php if (count($changes) > 5): ?>
<div style="color:#9CA3AF;">+ <?= count($changes) - 5 ?> تعديلات أخرى</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<div>
<!-- Sidebar: Archive Info + Links -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:12px 15px;border-bottom:1px solid #E5E7EB;">
<h4 style="margin:0;font-size:14px;color:#1A1A2E;">معلومات الأرشفة</h4>
</div>
<div style="padding:15px;">
<table style="width:100%;font-size:13px;">
<tr><td style="padding:4px 0;color:#6B7280;">السبب</td><td style="padding:4px 0;font-weight:600;color:<?= $color ?>;"><?= e($statusLabels[$member['status']] ?? $member['status']) ?></td></tr>
<tr><td style="padding:4px 0;color:#6B7280;">التاريخ</td><td style="padding:4px 0;"><?= $member['archived_at'] ? arabic_date($member['archived_at']) : '—' ?></td></tr>
<tr><td style="padding:4px 0;color:#6B7280;">المنفذ</td><td style="padding:4px 0;"><?= e($operator['full_name_ar'] ?? 'النظام') ?></td></tr>
<tr><td style="padding:4px 0;color:#6B7280;">عدد اللقطات</td><td style="padding:4px 0;"><?= count($snapshots) ?></td></tr>
</table>
</div>
</div>
<!-- Linked Member (who received the number) -->
<?php if ($linkedMember): ?>
<div class="card" style="margin-bottom:20px;border:2px solid #059669;">
<div style="padding:12px 15px;border-bottom:1px solid #E5E7EB;background:#F0FDF4;">
<h4 style="margin:0;font-size:14px;color:#059669;">العضو الجديد (المنقول إليه)</h4>
</div>
<div style="padding:15px;">
<div style="font-weight:700;font-size:15px;margin-bottom:5px;"><?= e($linkedMember['full_name_ar']) ?></div>
<div style="font-size:12px;color:#6B7280;">رقم العضوية: <strong><?= e($linkedMember['membership_number'] ?? '—') ?></strong></div>
<div style="font-size:12px;color:#6B7280;">الحالة: <?= e($linkedMember['status']) ?></div>
<a href="/members/<?= (int) $linkedMember['id'] ?>" class="btn btn-sm btn-primary" style="margin-top:10px;width:100%;text-align:center;">الانتقال لسجل العضو الجديد</a>
</div>
</div>
<?php endif; ?>
<!-- Number Chain -->
<?php if (!empty($numberChain)): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:12px 15px;border-bottom:1px solid #E5E7EB;">
<h4 style="margin:0;font-size:14px;color:#0D7377;">سلسلة رقم العضوية: <?= e($membershipNumber ?? '') ?></h4>
</div>
<div style="padding:15px;">
<?php foreach ($numberChain as $i => $link): ?>
<div style="padding:8px 0;<?= $i < count($numberChain) - 1 ? 'border-bottom:1px solid #F3F4F6;' : '' ?>display:flex;gap:8px;align-items:start;">
<div style="flex-shrink:0;width:24px;height:24px;border-radius:50%;background:<?= $link['held_until'] === null ? '#059669' : '#E5E7EB' ?>;display:flex;align-items:center;justify-content:center;color:<?= $link['held_until'] === null ? '#fff' : '#6B7280' ?>;font-weight:700;font-size:11px;"><?= $i + 1 ?></div>
<div style="flex:1;font-size:12px;">
<div style="font-weight:600;color:#374151;"><?= e($link['holder_type']) ?></div>
<div style="color:#6B7280;">عضو #<?= (int) $link['holder_entity_id'] ?></div>
<div style="color:#9CA3AF;font-size:11px;">من: <?= e(mb_substr($link['held_from'], 0, 10)) ?><?= $link['held_until'] ? ' — إلى: ' . e(mb_substr($link['held_until'], 0, 10)) : ' — حتى الآن' ?></div>
</div>
</div>
<?php endforeach; ?>
<a href="/archive/number-chain/<?= urlencode($membershipNumber ?? '') ?>" class="btn btn-sm btn-outline" style="width:100%;text-align:center;margin-top:10px;">عرض السلسلة الكاملة</a>
</div>
</div>
<?php endif; ?>
<!-- Snapshots -->
<?php if (!empty($snapshots)): ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:12px 15px;border-bottom:1px solid #E5E7EB;">
<h4 style="margin:0;font-size:14px;color:#1A1A2E;">اللقطات الأرشيفية (<?= count($snapshots) ?>)</h4>
</div>
<div style="padding:15px;">
<?php foreach ($snapshots as $s): ?>
<div style="padding:8px 0;border-bottom:1px solid #F3F4F6;font-size:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<div>
<strong style="color:#374151;"><?= e($s['snapshot_reason']) ?></strong>
<div style="color:#9CA3AF;margin-top:2px;"><?= e(mb_substr($s['snapshot_taken_at'], 0, 16)) ?></div>
</div>
<a href="/archive/<?= (int) $s['id'] ?>" class="btn btn-sm btn-outline">عرض</a>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- Quick Links -->
<div class="card">
<div style="padding:15px;display:flex;flex-direction:column;gap:8px;">
<?php if (!empty($snapshots[0]['id'])): ?>
<a href="/archive/<?= (int) $snapshots[0]['id'] ?>" class="btn btn-outline" style="text-align:center;">عرض اللقطة الكاملة (JSON)</a>
<?php endif; ?>
<a href="/audit/entity/members/<?= (int) $member['id'] ?>" class="btn btn-outline" style="text-align:center;">سجل المراجعة الكامل</a>
</div>
</div>
</div>
</div>
<?php $__template->endSection(); ?>
...@@ -38,6 +38,8 @@ MenuRegistry::register('membership', [ ...@@ -38,6 +38,8 @@ MenuRegistry::register('membership', [
['label_ar' => 'حالات الطلاق', 'label_en' => 'Divorce Cases', 'route' => '/divorce', 'permission' => 'transfer.view', 'order' => 41], ['label_ar' => 'حالات الطلاق', 'label_en' => 'Divorce Cases', 'route' => '/divorce', 'permission' => 'transfer.view', 'order' => 41],
['label_ar' => 'حالات الوفاة', 'label_en' => 'Death Cases', 'route' => '/death', 'permission' => 'transfer.view', 'order' => 42], ['label_ar' => 'حالات الوفاة', 'label_en' => 'Death Cases', 'route' => '/death', 'permission' => 'transfer.view', 'order' => 42],
['label_ar' => 'طلبات التنازل', 'label_en' => 'Waiver Requests', 'route' => '/waivers', 'permission' => 'waiver.view', 'order' => 43], ['label_ar' => 'طلبات التنازل', 'label_en' => 'Waiver Requests', 'route' => '/waivers', 'permission' => 'waiver.view', 'order' => 43],
// ── Archive ─────────────────────────────────
['label_ar' => 'الأرشيف', 'label_en' => 'Archive', 'route' => '/members/archive', 'permission' => 'member.archive', 'order' => 45],
// ── Reports ───────────────────────────────── // ── Reports ─────────────────────────────────
['label_ar' => 'التقارير', 'label_en' => 'Reports', 'route' => '/reports', 'permission' => 'member.reports', 'order' => 50], ['label_ar' => 'التقارير', 'label_en' => 'Reports', 'route' => '/reports', 'permission' => 'member.reports', 'order' => 50],
], ],
......
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