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

sdgjdykdyr

parent 4461e7e6
...@@ -36,7 +36,22 @@ final class ChildFeeCalculator ...@@ -36,7 +36,22 @@ final class ChildFeeCalculator
$totalChildren = Child::countActiveForMember($memberId); $totalChildren = Child::countActiveForMember($memberId);
$childOrder = $totalChildren + 1; $childOrder = $totalChildren + 1;
$relationship = $childData['relationship'] ?? '';
// Stepchildren (ابناء الزوجة/الزوج) always cost 10% — never counted as free
if ($relationship === 'stepchild') {
$stepData = RuleEngine::get('STEPCHILD_FEE');
$stepPct = $stepData['percentage'] ?? '10.00';
$stepFee = bcmul($membershipValue, bcdiv($stepPct, '100', 4), 2);
$feeResult = [
'fee' => $stepFee,
'rule_applied' => 'STEPCHILD_FEE',
'percentage' => $stepPct,
'classification' => 'dependent_with_fee',
];
} else {
$feeResult = PricingEngine::calculateChildFee($membershipValue, $childAge, $childOrder); $feeResult = PricingEngine::calculateChildFee($membershipValue, $childAge, $childOrder);
}
$classification = $feeResult['classification'] ?? 'included'; $classification = $feeResult['classification'] ?? 'included';
if ($classification === 'not_accepted') { if ($classification === 'not_accepted') {
......
...@@ -338,10 +338,10 @@ if ($formFilled && !$bill['membership_paid'] && !in_array($member->status, ['act ...@@ -338,10 +338,10 @@ if ($formFilled && !$bill['membership_paid'] && !in_array($member->status, ['act
foreach ($bill['items'] as $item) { foreach ($bill['items'] as $item) {
if (in_array($item['type'] ?? '', ['spouse_fee', 'child_fee', 'temp_fee'], true) && !$item['included'] && !$item['paid']) { if (in_array($item['type'] ?? '', ['spouse_fee', 'child_fee', 'temp_fee'], true) && !$item['included'] && !$item['paid']) {
$inQueue = !empty($item['in_queue']); $inQueue = !empty($item['in_queue']) || !empty($pendingMembership);
$statusText = $inQueue ? ' — في انتظار الخزينة' : ''; $statusText = $inQueue ? ' — في انتظار الخزينة' : '';
$statusColor = $inQueue ? '#D97706' : '#DC2626'; $statusColor = $inQueue ? '#D97706' : '#DC2626';
$stepEntry = ['icon' => $inQueue ? '💳' : '⚠', 'text' => 'لم يتم سداد: ' . $item['label'] . $statusText, 'color' => $statusColor, 'done' => false]; $stepEntry = ['icon' => $inQueue ? '💳' : '⚠', 'text' => ($inQueue ? '' : 'لم يتم سداد: ') . $item['label'] . $statusText, 'color' => $statusColor, 'done' => false];
if (!$inQueue && empty($pendingMembership) && !empty($item['entity_type']) && !empty($item['entity_id']) && bccomp($item['amount'], '0.01', 2) >= 0) { if (!$inQueue && empty($pendingMembership) && !empty($item['entity_type']) && !empty($item['entity_id']) && bccomp($item['amount'], '0.01', 2) >= 0) {
$stepEntry['entity_type'] = $item['entity_type']; $stepEntry['entity_type'] = $item['entity_type'];
$stepEntry['entity_id'] = $item['entity_id']; $stepEntry['entity_id'] = $item['entity_id'];
...@@ -543,8 +543,7 @@ $categoryLabels = [ ...@@ -543,8 +543,7 @@ $categoryLabels = [
'parent' => 'والد/ة', 'special_needs' => 'ذوي احتياجات خاصة', 'unmarried_daughter' => 'ابنة غير متزوجة', 'parent' => 'والد/ة', 'special_needs' => 'ذوي احتياجات خاصة', 'unmarried_daughter' => 'ابنة غير متزوجة',
'sister' => 'شقيقة', 'stepchild' => 'ابن/ة زوج', 'orphan' => 'يتيم', 'disabled_sibling' => 'شقيق معاق', 'nanny' => 'مربية', 'sister' => 'شقيقة', 'stepchild' => 'ابن/ة زوج', 'orphan' => 'يتيم', 'disabled_sibling' => 'شقيق معاق', 'nanny' => 'مربية',
]; ];
$membershipTypeLabels = ['working' => 'عاملة', 'seasonal' => 'موسمية', 'sports' => 'رياضية', 'honorary' => 'شرفية', 'foreign' => 'أجنبية']; $childClassLabels = ['included' => 'تابع مشمول', 'dependent_with_fee' => 'تابع برسوم', 'temporary' => 'تابع مؤقت', 'frozen' => 'مجمد'];
$membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'working'] ?? 'عاملة';
?> ?>
<div class="card" style="margin-bottom:20px;"> <div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;background:linear-gradient(135deg, #F0FDF4, #ECFDF5);border-bottom:2px solid #A7F3D0;display:flex;justify-content:space-between;align-items:center;"> <div style="padding:15px 20px;background:linear-gradient(135deg, #F0FDF4, #ECFDF5);border-bottom:2px solid #A7F3D0;display:flex;justify-content:space-between;align-items:center;">
...@@ -572,7 +571,10 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -572,7 +571,10 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<tr> <tr>
<td><?= (int) $s['spouse_order'] ?></td> <td><?= (int) $s['spouse_order'] ?></td>
<td style="font-weight:600;"><?= e($s['full_name_ar']) ?></td> <td style="font-weight:600;"><?= e($s['full_name_ar']) ?></td>
<td><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;"><?= $membershipTypeLabel ?></span></td> <td><?php
$sFee = $s['addition_fee'] ?? '0.00';
$sTypeLabel = (bccomp($sFee, '0', 2) <= 0 && (int) $s['spouse_order'] === 1) ? 'تابع مشمول' : 'تابع برسوم';
?><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;"><?= $sTypeLabel ?></span></td>
<td style="direction:ltr;text-align:right;font-size:12px;"><?= e($s['national_id'] ?? '—') ?></td> <td style="direction:ltr;text-align:right;font-size:12px;"><?= e($s['national_id'] ?? '—') ?></td>
<td style="font-size:12px;"><?= $s['join_date'] ? e($s['join_date']) : '<span style="color:#D97706;">لم يُحدد بعد</span>' ?></td> <td style="font-size:12px;"><?= $s['join_date'] ? e($s['join_date']) : '<span style="color:#D97706;">لم يُحدد بعد</span>' ?></td>
<td style="font-weight:600;"><?php <td style="font-weight:600;"><?php
...@@ -583,10 +585,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -583,10 +585,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<td><?php <td><?php
$sKey = 'spouses:' . (int) $s['id']; $sKey = 'spouses:' . (int) $s['id'];
$sInQueue = isset($pendingAdditions[$sKey]); $sInQueue = isset($pendingAdditions[$sKey]);
if ($sInQueue): ?> $sInCombined = ($s['status'] === 'pending_payment' && !empty($pendingMembership));
if ($sInQueue || $sInCombined): ?>
<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($s['status']) { 'active' => '#059669', 'pending_payment' => '#D97706', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($s['status']) { 'active' => 'نشط', 'pending_payment' => 'في انتظار الدفع', 'inactive' => 'غير نشط', default => $s['status'] } ?></span> <span style="color:<?= match($s['status']) { 'active' => '#059669', 'pending_payment' => '#D97706', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($s['status']) { 'active' => 'نشط', 'pending_payment' => 'لم يتم السداد', 'inactive' => 'غير نشط', default => $s['status'] } ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
...@@ -607,7 +610,7 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -607,7 +610,7 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<tr> <tr>
<td><?= (int) $c['child_order'] ?></td> <td><?= (int) $c['child_order'] ?></td>
<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;"><?= $membershipTypeLabel ?></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><?= (int) ($c['age_years'] ?? 0) ?></td>
<td style="font-size:12px;"><?= match($c['classification'] ?? '') { <td style="font-size:12px;"><?= match($c['classification'] ?? '') {
...@@ -624,10 +627,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -624,10 +627,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<td><?php <td><?php
$cKey = 'children:' . (int) $c['id']; $cKey = 'children:' . (int) $c['id'];
$cInQueue = isset($pendingAdditions[$cKey]); $cInQueue = isset($pendingAdditions[$cKey]);
if ($cInQueue): ?> $cInCombined = (($c['status'] ?? '') === 'pending_payment' && !empty($pendingMembership));
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', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($c['status'] ?? '') { 'active' => 'نشط', 'pending_payment' => 'لم يتم السداد', 'frozen' => 'مجمد', 'inactive' => 'غير نشط', default => $c['status'] ?? '—' } ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
...@@ -647,7 +651,7 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -647,7 +651,7 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<?php foreach ($temporaries as $t): ?> <?php foreach ($temporaries as $t): ?>
<tr> <tr>
<td style="font-weight:600;"><?= e($t['full_name_ar']) ?></td> <td style="font-weight:600;"><?= e($t['full_name_ar']) ?></td>
<td><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;"><?= $membershipTypeLabel ?></span></td> <td><span style="background:#E0F2FE;color:#0369A1;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;">تابع مؤقت</span></td>
<td style="font-size:12px;"><?= $categoryLabels[$t['category']] ?? e($t['category']) ?></td> <td style="font-size:12px;"><?= $categoryLabels[$t['category']] ?? e($t['category']) ?></td>
<td><?= $t['gender'] === 'male' ? 'ذكر' : 'أنثى' ?></td> <td><?= $t['gender'] === 'male' ? 'ذكر' : 'أنثى' ?></td>
<td><?= (int) ($t['age_years'] ?? 0) ?></td> <td><?= (int) ($t['age_years'] ?? 0) ?></td>
...@@ -658,10 +662,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin ...@@ -658,10 +662,11 @@ $membershipTypeLabel = $membershipTypeLabels[$member->membership_type ?? 'workin
<td><?php <td><?php
$tKey = 'temporary_members:' . (int) $t['id']; $tKey = 'temporary_members:' . (int) $t['id'];
$tInQueue = isset($pendingAdditions[$tKey]); $tInQueue = isset($pendingAdditions[$tKey]);
if ($tInQueue): ?> $tInCombined = (($t['status'] ?? '') === 'pending_payment' && !empty($pendingMembership));
if ($tInQueue || $tInCombined): ?>
<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($t['status'] ?? '') { 'active' => '#059669', 'pending_payment' => '#D97706', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($t['status'] ?? '') { 'active' => 'نشط', 'pending_payment' => 'في انتظار الدفع', 'inactive' => 'غير نشط', default => $t['status'] ?? '—' } ?></span> <span style="color:<?= match($t['status'] ?? '') { 'active' => '#059669', 'pending_payment' => '#D97706', default => '#DC2626' } ?>;font-weight:600;font-size:12px;">&#x25cf; <?= match($t['status'] ?? '') { 'active' => 'نشط', 'pending_payment' => 'لم يتم السداد', 'frozen' => 'مجمد', 'inactive' => 'غير نشط', default => $t['status'] ?? '—' } ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
......
...@@ -104,15 +104,20 @@ final class PricingEngine ...@@ -104,15 +104,20 @@ final class PricingEngine
$percentageFee = bcmul($membershipValue, bcdiv($pct, '100', 4), 2); $percentageFee = bcmul($membershipValue, bcdiv($pct, '100', 4), 2);
$startDate = max($marriageDate, $membershipAcquisitionDate); // Annual fine only applies if marriage date is BEFORE membership date
$start = new \DateTime($startDate); $yearsCount = 0;
$now = new \DateTime(); $marriageTs = strtotime($marriageDate);
$diff = $now->diff($start); $memberTs = strtotime($membershipAcquisitionDate);
if ($marriageTs && $memberTs && $marriageTs < $memberTs) {
$start = new \DateTime($marriageDate);
$end = new \DateTime($membershipAcquisitionDate);
$diff = $end->diff($start);
$yearsCount = $diff->y; $yearsCount = $diff->y;
if ($diff->m > 0 || $diff->d > 0) { if ($diff->m > 0 || $diff->d > 0) {
$yearsCount++; $yearsCount++;
} }
$yearsCount = max(1, $yearsCount); $yearsCount = max(1, $yearsCount);
}
$annualFee = bcmul((string) $yearsCount, $annualFlat, 2); $annualFee = bcmul((string) $yearsCount, $annualFlat, 2);
$total = bcadd($percentageFee, $annualFee, 2); $total = bcadd($percentageFee, $annualFee, 2);
......
...@@ -31,15 +31,11 @@ class CatalogController extends Controller ...@@ -31,15 +31,11 @@ class CatalogController extends Controller
if (!$service) { if (!$service) {
return $this->redirect('/catalog')->withError('الخدمة غير موجودة'); return $this->redirect('/catalog')->withError('الخدمة غير موجودة');
} }
$data = $this->validate($request->all(), [ $data = $request->all();
'base_amount' => 'nullable|numeric|min:0',
'percentage' => 'nullable|numeric|min:0|max:100',
'annual_amount' => 'nullable|numeric|min:0',
]);
$service->update([ $service->update([
'base_amount' => $data['base_amount'] ?? null, 'base_amount' => ($data['base_amount'] ?? '') !== '' ? $data['base_amount'] : null,
'percentage' => $data['percentage'] ?? null, 'percentage' => ($data['percentage'] ?? '') !== '' ? $data['percentage'] : null,
'annual_amount' => $data['annual_amount'] ?? null, 'annual_amount' => ($data['annual_amount'] ?? '') !== '' ? $data['annual_amount'] : null,
]); ]);
return $this->redirect('/catalog')->withSuccess('تم تحديث الخدمة بنجاح'); return $this->redirect('/catalog')->withSuccess('تم تحديث الخدمة بنجاح');
} }
......
...@@ -156,25 +156,25 @@ final class SpouseFeeCalculator ...@@ -156,25 +156,25 @@ final class SpouseFeeCalculator
private static function calculateYears(?string $marriageDate, string $memberCreatedDate): int private static function calculateYears(?string $marriageDate, string $memberCreatedDate): int
{ {
if (!$marriageDate) { if (!$marriageDate) {
return 1; return 0;
} }
$marriageTs = strtotime($marriageDate); $marriageTs = strtotime($marriageDate);
$memberTs = strtotime($memberCreatedDate); $memberTs = strtotime($memberCreatedDate);
if (!$marriageTs || !$memberTs) { if (!$marriageTs || !$memberTs) {
return 1; return 0;
} }
$startTs = max($marriageTs, $memberTs); // Annual fine only applies if marriage is BEFORE membership acquisition
$start = new \DateTime(date('Y-m-d', $startTs)); if ($marriageTs >= $memberTs) {
$now = new \DateTime(); return 0;
if ($now <= $start) {
return 1;
} }
$diff = $now->diff($start); $start = new \DateTime(date('Y-m-d', $marriageTs));
$end = new \DateTime(date('Y-m-d', $memberTs));
$diff = $end->diff($start);
$years = $diff->y; $years = $diff->y;
if ($diff->m > 0 || $diff->d > 0) { if ($diff->m > 0 || $diff->d > 0) {
...@@ -230,8 +230,8 @@ final class SpouseFeeCalculator ...@@ -230,8 +230,8 @@ final class SpouseFeeCalculator
$lines[] = "📊 نسبة {$pct}% × " . money($membershipValue) . ' = ' . money($pctFee); $lines[] = "📊 نسبة {$pct}% × " . money($membershipValue) . ' = ' . money($pctFee);
} }
if (bccomp($annual, '0', 2) > 0 && $years > 0) { if (bccomp($annual, '0', 2) > 0 && $years > 0) {
$lines[] = "📅 رسوم سنوية: {$annual} ج.م × {$years} سنة = " . money($yearlyTotal); $lines[] = "📅 غرامة زواج سابق للعضوية: {$annual} ج.م × {$years} سنة = " . money($yearlyTotal);
$lines[] = ' (من تاريخ الزواج أو اكتساب العضوية — أيهما لاحق — كسر السنة سنة كاملة)'; $lines[] = ' (تحسب من تاريخ الزواج حتى تاريخ اكتساب العضوية — كسر السنة سنة كاملة)';
} }
if (bccomp($formFee, '0', 2) > 0) { if (bccomp($formFee, '0', 2) > 0) {
$lines[] = '📝 رسوم استمارة إضافة: ' . money($formFee); $lines[] = '📝 رسوم استمارة إضافة: ' . money($formFee);
......
...@@ -61,6 +61,17 @@ return function (\App\Core\Database $db) { ...@@ -61,6 +61,17 @@ return function (\App\Core\Database $db) {
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
$newRules = [ $newRules = [
// ── Stepchild fee (always charged, not counted in free children) ──
[
'rule_code' => 'STEPCHILD_FEE',
'category' => 'children_fee',
'name_ar' => 'رسوم ابن/ابنة الزوج أو الزوجة (دائماً)',
'name_en' => 'Stepchild Fee (always charged)',
'data_type' => 'percentage',
'current_value_json' => '{"percentage":"10.00"}',
'parameters_json' => '{"percentage":"decimal"}',
],
// ── Spouse limits ── // ── Spouse limits ──
[ [
'rule_code' => 'MAX_SPOUSES_MALE_MEMBER', 'rule_code' => 'MAX_SPOUSES_MALE_MEMBER',
......
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