Commit 110d7771 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix children age display: compute dynamically from DOB, auto-separate at 25

- MemberController: SELECT TIMESTAMPDIFF for age_years/age_months in children query
- children-table.php: compute age from DOB in view (fallback for static column)
- show.php (member): same dynamic age + add 'separated' status translation
- show.php (child): compute age from DOB dynamically, fix 25+ threshold check
- AgeMonitorJob: add daily age recalculation for all children, change auto-freeze
  to auto-separate (status='separated', classification='separated')
- AutoFreezeService: update processAutoFreeze() to separate instead of just freeze
- DB fixes: corrected DOBs from NIDs, updated all ages, separated 3 children >= 25
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 7e3ac060
......@@ -19,7 +19,10 @@ $memberId = $memberId ?? 0;
};
?></td>
<td style="font-size:13px;"><?= e($c['date_of_birth']) ?></td>
<td><?= (int) ($c['age_years'] ?? 0) ?></td>
<td><?php
$dob = $c['date_of_birth'] ?? null;
echo $dob && strtotime($dob) > 0 ? (int) ((time() - strtotime($dob)) / 31557600) : 0;
?></td>
<td style="font-size:12px;"><?php
echo match ($c['classification'] ?? '') {
'included' => '<span style="color:#059669;">مشمول</span>',
......@@ -31,13 +34,16 @@ $memberId = $memberId ?? 0;
};
?></td>
<td style="font-weight:600;"><?= money($c['addition_fee'] ?? '0') ?></td>
<td>
<?php if ($c['is_frozen'] ?? false): ?>
<span style="color:#DC2626;font-weight:600;">● مجمد</span>
<?php else: ?>
<span style="color:<?= ($c['status'] ?? '') === 'active' ? '#059669' : '#DC2626' ?>;font-weight:600;"><?= ($c['status'] ?? '') === 'active' ? 'نشط' : ($c['status'] ?? '') ?></span>
<?php endif; ?>
</td>
<td><?php
$st = $c['status'] ?? '';
echo match($st) {
'active' => '<span style="color:#059669;font-weight:600;">● نشط</span>',
'separated' => '<span style="color:#6B7280;font-weight:600;">● منفصل</span>',
'frozen' => '<span style="color:#DC2626;font-weight:600;">● مجمد</span>',
'pending_payment' => '<span style="color:#D97706;font-weight:600;">● لم يتم السداد</span>',
default => '<span style="color:#DC2626;font-weight:600;">● ' . e($st) . '</span>',
};
?></td>
<td>
<div style="display:flex;gap:5px;">
<a href="/members/<?= (int) $memberId ?>/children/<?= (int) $c['id'] ?>" class="btn btn-sm btn-outline">عرض</a>
......
......@@ -15,7 +15,15 @@
<tr><td style="padding:6px 0;color:#6B7280;">الرقم القومي</td><td style="padding:6px 0;direction:ltr;text-align:right;"><?= e($child->national_id ?: '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">شهادة الميلاد</td><td style="padding:6px 0;"><?= e($child->birth_certificate_number ?: '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">تاريخ الميلاد</td><td style="padding:6px 0;"><?= e($child->date_of_birth) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">السن</td><td style="padding:6px 0;"><?= (int) $child->age_years ?> سنة <?= $child->age_months ? 'و ' . (int) $child->age_months . ' شهر' : '' ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">السن</td><td style="padding:6px 0;"><?php
$cDob = $child->date_of_birth ?? null;
$cAgeY = 0; $cAgeM = 0;
if ($cDob && strtotime($cDob) > 0) {
$d = (new \DateTime($cDob))->diff(new \DateTime());
$cAgeY = $d->y; $cAgeM = $d->m;
}
echo $cAgeY . ' سنة' . ($cAgeM ? ' و ' . $cAgeM . ' شهر' : '');
?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">النوع</td><td style="padding:6px 0;"><?= $child->gender === 'male' ? 'ذكر' : 'أنثى' ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">نوع القرابة</td><td style="padding:6px 0;"><?= e($child->getRelationshipLabel()) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الكلية / المدرسة</td><td style="padding:6px 0;"><?= e($child->school_faculty ?: '—') ?></td></tr>
......@@ -35,7 +43,7 @@
<tr><td style="padding:6px 0;color:#6B7280;">رقم الإيصال</td><td style="padding:6px 0;"><?= e($child->fee_receipt_number ?: '—') ?></td></tr>
</table>
<?php if ($child->status === 'active' && !$child->is_frozen && $child->gender === 'male' && (int) $child->age_years >= 25): ?>
<?php if ($child->status === 'active' && !$child->is_frozen && $cAgeY >= 25): ?>
<?php if (can('child.freeze')): ?>
<div style="margin-top:20px;padding:15px;background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;">
<strong style="color:#DC2626;">⚠ هذا الابن بلغ سن 25 ويجب تجميد عضويته</strong>
......
......@@ -171,7 +171,7 @@ class MemberController extends Controller
}
try {
$children = $db->select(
"SELECT c.*, pr.created_at AS due_date, p.payment_date
"SELECT c.*, TIMESTAMPDIFF(YEAR, c.date_of_birth, CURDATE()) as age_years, TIMESTAMPDIFF(MONTH, c.date_of_birth, CURDATE()) % 12 as age_months, pr.created_at AS due_date, p.payment_date
FROM children c
LEFT JOIN payment_requests pr ON pr.related_entity_type = 'children' AND pr.related_entity_id = c.id AND pr.payment_type = 'addition_fee' AND pr.is_voided = 0
LEFT JOIN payments p ON p.id = c.activated_by_payment_id AND p.is_voided = 0
......@@ -179,7 +179,7 @@ class MemberController extends Controller
ORDER BY c.child_order", [(int) $id]
);
} catch (\Throwable $e) {
try { $children = $db->select("SELECT * FROM children WHERE member_id = ? AND is_archived = 0 ORDER BY child_order", [(int) $id]); } catch (\Throwable $e2) {}
try { $children = $db->select("SELECT *, TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()) as age_years, TIMESTAMPDIFF(MONTH, date_of_birth, CURDATE()) % 12 as age_months FROM children WHERE member_id = ? AND is_archived = 0 ORDER BY child_order", [(int) $id]); } catch (\Throwable $e2) {}
}
try {
$temporaries = $db->select(
......
......@@ -23,14 +23,13 @@ final class AutoFreezeService
$children = $db->select(
"SELECT id, date_of_birth FROM children
WHERE gender = 'male'
AND is_frozen = 0
AND is_archived = 0
AND status = 'active'"
WHERE is_archived = 0
AND status = 'active'
AND date_of_birth IS NOT NULL"
);
$processed = count($children);
$frozenCount = 0;
$separatedCount = 0;
$now = date('Y-m-d H:i:s');
$today = new \DateTimeImmutable('today');
......@@ -42,19 +41,21 @@ final class AutoFreezeService
$db->update(
'children',
[
'status' => 'separated',
'classification' => 'separated',
'is_frozen' => 1,
'frozen_at' => $now,
'frozen_reason' => 'بلوغ سن 25 عام - يجب التحويل لعضوية مستقلة',
'frozen_reason' => 'فصل وجوبي — بلوغ سن 25',
],
'id = ?',
[$child['id']]
);
$frozenCount++;
$separatedCount++;
}
}
return [
'frozen_count' => $frozenCount,
'separated_count' => $separatedCount,
'processed' => $processed,
];
}
......
......@@ -832,7 +832,14 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
<td style="font-weight:600;"><?= e($c['full_name_ar']) ?></td>
<td><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;"><?= $childClassLabels[$c['classification'] ?? 'included'] ?? 'تابع' ?></span></td>
<td><?= $c['gender'] === 'male' ? 'ذكر' : 'أنثى' ?></td>
<td><?= (int) ($c['age_years'] ?? 0) ?></td>
<td><?php
$cDob = $c['date_of_birth'] ?? null;
$cAge = ($c['age_years'] ?? 0);
if (!$cAge && $cDob && strtotime($cDob) > 0) {
$cAge = (int) ((time() - strtotime($cDob)) / 31557600);
}
echo (int) $cAge;
?></td>
<td style="font-size:12px;"><?= !empty($c['join_date']) ? e($c['join_date']) : '<span style="color:#D97706;">—</span>' ?></td>
<td style="font-size:12px;"><?= !empty($c['due_date']) ? e(substr($c['due_date'], 0, 10)) : '<span style="color:#9CA3AF;">—</span>' ?></td>
<td style="font-size:12px;"><?= !empty($c['payment_date']) ? e($c['payment_date']) : '<span style="color:#9CA3AF;">—</span>' ?></td>
......@@ -848,7 +855,7 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
if ($cInQueue || $cInCombined): ?>
<span style="color:#D97706;font-weight:600;font-size:12px;">&#x1f4b3; في الخزينة</span>
<?php else: ?>
<span style="color:<?= match($c['status'] ?? '') { 'active' => '#059669', 'pending_payment' => '#D97706', 'frozen' => '#6B7280', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($c['status'] ?? '') { 'active' => 'نشط', 'pending_payment' => 'لم يتم السداد', 'frozen' => 'مجمد', 'inactive' => 'غير نشط', default => $c['status'] ?? '—' } ?></span>
<span style="color:<?= match($c['status'] ?? '') { 'active' => '#059669', 'pending_payment' => '#D97706', 'frozen' => '#6B7280', 'separated' => '#6B7280', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($c['status'] ?? '') { 'active' => 'نشط', 'pending_payment' => 'لم يتم السداد', 'frozen' => 'مجمد', 'separated' => 'منفصل', 'inactive' => 'غير نشط', default => $c['status'] ?? '—' } ?></span>
<?php endif; ?>
</td>
<td style="white-space:nowrap;">
......
......@@ -22,6 +22,11 @@ class AgeMonitorJob
{
$processed = 0;
// Update all children ages from DOB
$this->db->query(
"UPDATE children SET age_years = TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()), age_months = TIMESTAMPDIFF(MONTH, date_of_birth, CURDATE()) % 12 WHERE date_of_birth IS NOT NULL AND date_of_birth > '1950-01-01' AND is_archived = 0"
);
// Children approaching 18 (3 months ahead)
$approaching18 = $this->db->select(
"SELECT c.id, c.full_name_ar, c.date_of_birth, m.id as member_id, m.full_name_ar as member_name, m.phone_mobile
......@@ -54,21 +59,22 @@ class AgeMonitorJob
$processed++;
}
// Auto-freeze males at 25
$toFreeze = $this->db->select(
// Auto-separate children at 25 (mandatory separation)
$toSeparate = $this->db->select(
"SELECT c.id, c.full_name_ar FROM children c
WHERE c.is_archived = 0 AND c.status = 'active' AND c.is_frozen = 0 AND c.gender = 'male'
WHERE c.is_archived = 0 AND c.status = 'active'
AND c.date_of_birth <= DATE_SUB(CURDATE(), INTERVAL 25 YEAR)"
);
foreach ($toFreeze as $child) {
foreach ($toSeparate as $child) {
$this->db->update('children', [
'status' => 'separated',
'classification' => 'separated',
'is_frozen' => 1,
'frozen_at' => date('Y-m-d H:i:s'),
'frozen_reason' => 'تجميد تلقائي — بلوغ سن 25',
'status' => 'frozen',
'frozen_reason' => 'فصل وجوبي — بلوغ سن 25',
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $child['id']]);
Logger::info("Auto-frozen child #{$child['id']}: {$child['full_name_ar']}");
Logger::info("Auto-separated child #{$child['id']}: {$child['full_name_ar']}");
$processed++;
}
......
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