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; ...@@ -19,7 +19,10 @@ $memberId = $memberId ?? 0;
}; };
?></td> ?></td>
<td style="font-size:13px;"><?= e($c['date_of_birth']) ?></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 <td style="font-size:12px;"><?php
echo match ($c['classification'] ?? '') { echo match ($c['classification'] ?? '') {
'included' => '<span style="color:#059669;">مشمول</span>', 'included' => '<span style="color:#059669;">مشمول</span>',
...@@ -31,13 +34,16 @@ $memberId = $memberId ?? 0; ...@@ -31,13 +34,16 @@ $memberId = $memberId ?? 0;
}; };
?></td> ?></td>
<td style="font-weight:600;"><?= money($c['addition_fee'] ?? '0') ?></td> <td style="font-weight:600;"><?= money($c['addition_fee'] ?? '0') ?></td>
<td> <td><?php
<?php if ($c['is_frozen'] ?? false): ?> $st = $c['status'] ?? '';
<span style="color:#DC2626;font-weight:600;">● مجمد</span> echo match($st) {
<?php else: ?> 'active' => '<span style="color:#059669;font-weight:600;">● نشط</span>',
<span style="color:<?= ($c['status'] ?? '') === 'active' ? '#059669' : '#DC2626' ?>;font-weight:600;"><?= ($c['status'] ?? '') === 'active' ? 'نشط' : ($c['status'] ?? '') ?></span> 'separated' => '<span style="color:#6B7280;font-weight:600;">● منفصل</span>',
<?php endif; ?> 'frozen' => '<span style="color:#DC2626;font-weight:600;">● مجمد</span>',
</td> 'pending_payment' => '<span style="color:#D97706;font-weight:600;">● لم يتم السداد</span>',
default => '<span style="color:#DC2626;font-weight:600;">● ' . e($st) . '</span>',
};
?></td>
<td> <td>
<div style="display:flex;gap:5px;"> <div style="display:flex;gap:5px;">
<a href="/members/<?= (int) $memberId ?>/children/<?= (int) $c['id'] ?>" class="btn btn-sm btn-outline">عرض</a> <a href="/members/<?= (int) $memberId ?>/children/<?= (int) $c['id'] ?>" class="btn btn-sm btn-outline">عرض</a>
......
...@@ -15,7 +15,15 @@ ...@@ -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;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->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;"><?= 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;"><?= $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->getRelationshipLabel()) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الكلية / المدرسة</td><td style="padding:6px 0;"><?= e($child->school_faculty ?: '—') ?></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 @@ ...@@ -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> <tr><td style="padding:6px 0;color:#6B7280;">رقم الإيصال</td><td style="padding:6px 0;"><?= e($child->fee_receipt_number ?: '—') ?></td></tr>
</table> </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')): ?> <?php if (can('child.freeze')): ?>
<div style="margin-top:20px;padding:15px;background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;"> <div style="margin-top:20px;padding:15px;background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;">
<strong style="color:#DC2626;">⚠ هذا الابن بلغ سن 25 ويجب تجميد عضويته</strong> <strong style="color:#DC2626;">⚠ هذا الابن بلغ سن 25 ويجب تجميد عضويته</strong>
......
...@@ -171,7 +171,7 @@ class MemberController extends Controller ...@@ -171,7 +171,7 @@ class MemberController extends Controller
} }
try { try {
$children = $db->select( $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 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 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 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 ...@@ -179,7 +179,7 @@ class MemberController extends Controller
ORDER BY c.child_order", [(int) $id] ORDER BY c.child_order", [(int) $id]
); );
} catch (\Throwable $e) { } 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 { try {
$temporaries = $db->select( $temporaries = $db->select(
......
...@@ -23,14 +23,13 @@ final class AutoFreezeService ...@@ -23,14 +23,13 @@ final class AutoFreezeService
$children = $db->select( $children = $db->select(
"SELECT id, date_of_birth FROM children "SELECT id, date_of_birth FROM children
WHERE gender = 'male' WHERE is_archived = 0
AND is_frozen = 0 AND status = 'active'
AND is_archived = 0 AND date_of_birth IS NOT NULL"
AND status = 'active'"
); );
$processed = count($children); $processed = count($children);
$frozenCount = 0; $separatedCount = 0;
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$today = new \DateTimeImmutable('today'); $today = new \DateTimeImmutable('today');
...@@ -42,20 +41,22 @@ final class AutoFreezeService ...@@ -42,20 +41,22 @@ final class AutoFreezeService
$db->update( $db->update(
'children', 'children',
[ [
'is_frozen' => 1, 'status' => 'separated',
'frozen_at' => $now, 'classification' => 'separated',
'frozen_reason' => 'بلوغ سن 25 عام - يجب التحويل لعضوية مستقلة', 'is_frozen' => 1,
'frozen_at' => $now,
'frozen_reason' => 'فصل وجوبي — بلوغ سن 25',
], ],
'id = ?', 'id = ?',
[$child['id']] [$child['id']]
); );
$frozenCount++; $separatedCount++;
} }
} }
return [ return [
'frozen_count' => $frozenCount, 'separated_count' => $separatedCount,
'processed' => $processed, 'processed' => $processed,
]; ];
} }
......
...@@ -832,7 +832,14 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' = ...@@ -832,7 +832,14 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
<td style="font-weight:600;"><?= e($c['full_name_ar']) ?></td> <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><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><?= $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['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['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> <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' = ...@@ -848,7 +855,7 @@ $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' =
if ($cInQueue || $cInCombined): ?> if ($cInQueue || $cInCombined): ?>
<span style="color:#D97706;font-weight:600;font-size:12px;">&#x1f4b3; في الخزينة</span> <span style="color:#D97706;font-weight:600;font-size:12px;">&#x1f4b3; في الخزينة</span>
<?php else: ?> <?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; ?> <?php endif; ?>
</td> </td>
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
......
...@@ -22,6 +22,11 @@ class AgeMonitorJob ...@@ -22,6 +22,11 @@ class AgeMonitorJob
{ {
$processed = 0; $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) // Children approaching 18 (3 months ahead)
$approaching18 = $this->db->select( $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 "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 ...@@ -54,21 +59,22 @@ class AgeMonitorJob
$processed++; $processed++;
} }
// Auto-freeze males at 25 // Auto-separate children at 25 (mandatory separation)
$toFreeze = $this->db->select( $toSeparate = $this->db->select(
"SELECT c.id, c.full_name_ar FROM children c "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)" AND c.date_of_birth <= DATE_SUB(CURDATE(), INTERVAL 25 YEAR)"
); );
foreach ($toFreeze as $child) { foreach ($toSeparate as $child) {
$this->db->update('children', [ $this->db->update('children', [
'is_frozen' => 1, 'status' => 'separated',
'frozen_at' => date('Y-m-d H:i:s'), 'classification' => 'separated',
'frozen_reason' => 'تجميد تلقائي — بلوغ سن 25', 'is_frozen' => 1,
'status' => 'frozen', 'frozen_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'), 'frozen_reason' => 'فصل وجوبي — بلوغ سن 25',
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $child['id']]); ], '`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++; $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