Commit 53d68f25 authored by Fares's avatar Fares

feat(seasonal): add inline family members to seasonal registration form

The seasonal membership form now allows adding spouses and children
directly during registration (not just selecting pre-existing ones).
New family members are created as actual spouse/child records and
included in the pricing calculation. The price preview API also
reflects inline additions in real-time.

All discounts work correctly with inline members:
- Parent + child <15: 500 EGP off
- Husband + wife: 1000 EGP off
- Family of 5+: 1000 EGP off
- No qualification: 10% off
- VAT applied on all amounts
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 3fe065b2
......@@ -130,6 +130,49 @@ class SeasonalController extends Controller
}
}
// Add new inline spouses
$newSpouses = (array) ($data['new_spouses'] ?? []);
$createdSpouseIds = [];
foreach ($newSpouses as $ns) {
$spName = trim($ns['name'] ?? '');
if ($spName === '') continue;
$sp = \App\Modules\Spouses\Models\Spouse::create([
'member_id' => (int) $memberId,
'full_name_ar' => $spName,
'spouse_order' => \App\Modules\Spouses\Models\Spouse::countActiveForMember((int) $memberId) + 1,
'status' => 'active',
'join_date' => date('Y-m-d'),
]);
$createdSpouseIds[] = (int) $sp->id;
$items[] = ['person_type' => 'spouse', 'name' => $spName, 'age' => null];
}
// Add new inline children
$newChildren = (array) ($data['new_children'] ?? []);
$createdChildIds = [];
foreach ($newChildren as $nc) {
$chName = trim($nc['name'] ?? '');
$chDob = $nc['dob'] ?? '';
if ($chName === '' || !$chDob || !strtotime($chDob)) continue;
$childAge = (int) ((time() - strtotime($chDob)) / 31557600);
$childMonths = (int) (((time() - strtotime($chDob)) % 31557600) / 2629800);
$ch = \App\Modules\Children\Models\Child::create([
'member_id' => (int) $memberId,
'child_order' => \App\Modules\Children\Models\Child::countActiveForMember((int) $memberId) + 1,
'full_name_ar' => $chName,
'date_of_birth' => $chDob,
'age_years' => $childAge,
'age_months' => $childMonths,
'gender' => 'male',
'relationship' => 'son',
'classification' => 'included',
'status' => 'active',
'join_date' => date('Y-m-d'),
]);
$createdChildIds[] = (int) $ch->id;
$items[] = ['person_type' => 'child', 'name' => $chName, 'age' => $childAge];
}
// Calculate pricing
$pricing = SeasonalPricingService::calculate($items, $nationalityType, $durationMonths, $hasQualification);
......@@ -255,6 +298,22 @@ class SeasonalController extends Controller
if ($ch) $items[] = ['person_type' => 'child', 'name' => $ch['full_name_ar'], 'age' => (int) $ch['age']];
}
// New inline spouses
$newSpousesCount = (int) $request->get('new_spouses_count', 0);
for ($i = 0; $i < $newSpousesCount; $i++) {
$items[] = ['person_type' => 'spouse', 'name' => 'زوجة جديدة', 'age' => null];
}
// New inline children (with DOBs for age calculation)
$newChildDobs = array_filter(explode(',', $request->get('new_children_dobs', '')));
foreach ($newChildDobs as $dob) {
$dob = trim($dob);
if ($dob && strtotime($dob)) {
$childAge = (int) ((time() - strtotime($dob)) / 31557600);
$items[] = ['person_type' => 'child', 'name' => 'ابن/ة جديد/ة', 'age' => $childAge];
}
}
$pricing = SeasonalPricingService::calculate($items, $nationalityType, $durationMonths, $hasQualification);
return $this->json($pricing);
......
......@@ -49,11 +49,11 @@
<!-- Family Members Selection -->
<div class="card" style="padding:20px;margin-bottom:20px;">
<h3 style="margin:0 0 15px;color:#0D7377;">2. أفراد الأسرة المشتركون</h3>
<p style="color:#6B7280;font-size:13px;margin:0 0 15px;">العضو نفسه مشمول تلقائياً. اختر من أفراد الأسرة للاشتراك معه.</p>
<p style="color:#6B7280;font-size:13px;margin:0 0 15px;">العضو نفسه مشمول تلقائياً. أضف أفراد الأسرة للاشتراك معه — سيتم حساب الرسوم لكل فرد حسب فئته العمرية.</p>
<?php if (!empty($spouses)): ?>
<div style="margin-bottom:15px;">
<div style="font-size:12px;color:#0D7377;font-weight:700;margin-bottom:8px;">الزوجات</div>
<div style="font-size:12px;color:#0D7377;font-weight:700;margin-bottom:8px;">الزوجات المسجلة</div>
<?php foreach ($spouses as $sp): ?>
<label style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:#F9FAFB;border-radius:8px;margin-bottom:4px;cursor:pointer;">
<input type="checkbox" name="spouses[]" value="<?= (int) $sp['id'] ?>" onchange="updatePricing()">
......@@ -65,7 +65,7 @@
<?php if (!empty($children)): ?>
<div style="margin-bottom:15px;">
<div style="font-size:12px;color:#0D7377;font-weight:700;margin-bottom:8px;">الأبناء</div>
<div style="font-size:12px;color:#0D7377;font-weight:700;margin-bottom:8px;">الأبناء المسجلون</div>
<?php foreach ($children as $ch): ?>
<label style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:#F9FAFB;border-radius:8px;margin-bottom:4px;cursor:pointer;">
<input type="checkbox" name="children[]" value="<?= (int) $ch['id'] ?>" onchange="updatePricing()">
......@@ -75,9 +75,23 @@
</div>
<?php endif; ?>
<?php if (empty($spouses) && empty($children)): ?>
<p style="color:#9CA3AF;font-size:13px;">لا يوجد أفراد أسرة مسجلون.</p>
<?php endif; ?>
<!-- Add New Spouses -->
<div style="margin-bottom:15px;border-top:1px solid #E5E7EB;padding-top:15px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<div style="font-size:12px;color:#0D7377;font-weight:700;">إضافة زوجة</div>
<button type="button" onclick="addNewSpouse()" class="btn btn-outline" style="font-size:12px;padding:4px 12px;">+ إضافة</button>
</div>
<div id="newSpousesContainer"></div>
</div>
<!-- Add New Children -->
<div style="border-top:1px solid #E5E7EB;padding-top:15px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<div style="font-size:12px;color:#0D7377;font-weight:700;">إضافة أبناء</div>
<button type="button" onclick="addNewChild()" class="btn btn-outline" style="font-size:12px;padding:4px 12px;">+ إضافة</button>
</div>
<div id="newChildrenContainer"></div>
</div>
</div>
<!-- Pricing Preview -->
......@@ -156,6 +170,34 @@
<script>
const memberId = <?= (int) $member['id'] ?>;
let debounceTimer = null;
let spouseCount = 0;
let childCount = 0;
function addNewSpouse() {
spouseCount++;
const html = '<div style="display:grid;grid-template-columns:1fr auto;gap:8px;padding:8px 12px;background:#F0FDF4;border-radius:8px;margin-bottom:6px;" id="new-spouse-' + spouseCount + '">'
+ '<input type="text" name="new_spouses[' + spouseCount + '][name]" class="form-input" placeholder="اسم الزوجة بالكامل" required style="font-size:14px;">'
+ '<button type="button" onclick="removeRow(\'new-spouse-' + spouseCount + '\')" style="background:#FEE2E2;border:1px solid #FECACA;border-radius:6px;padding:6px 10px;cursor:pointer;color:#DC2626;font-weight:700;">✕</button>'
+ '</div>';
document.getElementById('newSpousesContainer').insertAdjacentHTML('beforeend', html);
updatePricing();
}
function addNewChild() {
childCount++;
const html = '<div style="display:grid;grid-template-columns:1fr 140px auto;gap:8px;padding:8px 12px;background:#F0FDF4;border-radius:8px;margin-bottom:6px;" id="new-child-' + childCount + '">'
+ '<input type="text" name="new_children[' + childCount + '][name]" class="form-input" placeholder="اسم الابن/ة بالكامل" required style="font-size:14px;">'
+ '<input type="date" name="new_children[' + childCount + '][dob]" class="form-input" required style="font-size:13px;" onchange="updatePricing()">'
+ '<button type="button" onclick="removeRow(\'new-child-' + childCount + '\')" style="background:#FEE2E2;border:1px solid #FECACA;border-radius:6px;padding:6px 10px;cursor:pointer;color:#DC2626;font-weight:700;">✕</button>'
+ '</div>';
document.getElementById('newChildrenContainer').insertAdjacentHTML('beforeend', html);
updatePricing();
}
function removeRow(id) {
document.getElementById(id).remove();
updatePricing();
}
function updatePricing() {
clearTimeout(debounceTimer);
......@@ -183,9 +225,16 @@ function fetchPricing() {
const spouses = [...document.querySelectorAll('[name="spouses[]"]:checked')].map(e => e.value).join(',');
const children = [...document.querySelectorAll('[name="children[]"]:checked')].map(e => e.value).join(',');
// Collect new inline spouses count
const newSpouseNames = [...document.querySelectorAll('[name^="new_spouses"][name$="[name]"]')].map(e => e.value.trim()).filter(n => n);
// Collect new inline children DOBs
const newChildDobs = [...document.querySelectorAll('[name^="new_children"][name$="[dob]"]')].map(e => e.value).filter(d => d);
const url = '/api/seasonal/price-preview?nationality_type=' + nat + '&duration_months=' + dur
+ '&member_id=' + memberId + '&has_qualification=' + hasQual
+ '&spouses=' + spouses + '&children=' + children;
+ '&spouses=' + spouses + '&children=' + children
+ '&new_spouses_count=' + newSpouseNames.length
+ '&new_children_dobs=' + encodeURIComponent(newChildDobs.join(','));
fetch(url, {credentials: 'same-origin'})
.then(r => r.json())
......
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