Commit 9ae45ad7 authored by Fares's avatar Fares

feat(transfers): enhance child separation dependent inputs with full fields

Form now collects for spouses: date_of_birth, nationality, marriage_date,
join_date, payment_date. For children: date_of_birth, join_date.

Validates spouse minimum age (SPOUSE_MIN_AGE rule, default 21) and
requires marriage_date. Non-Egyptian spouses get addition_fee calculated
as SPOUSE_FOREIGN_FEE% of current plan price at completion time.

TransferProcessor now uses submitted dates instead of defaults and stores
nationality and addition_fee on the spouse record.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent ffbaa774
...@@ -241,10 +241,39 @@ class TransferController extends Controller ...@@ -241,10 +241,39 @@ class TransferController extends Controller
if ($notes) $notesPayload['user_notes'] = $notes; if ($notes) $notesPayload['user_notes'] = $notes;
$notesJson = json_encode($notesPayload, JSON_UNESCAPED_UNICODE); $notesJson = json_encode($notesPayload, JSON_UNESCAPED_UNICODE);
} elseif ($transferType === 'child_separation' && ($depSpouses > 0 || $depChildren > 0 || $depTemps > 0)) { } elseif ($transferType === 'child_separation' && ($depSpouses > 0 || $depChildren > 0 || $depTemps > 0)) {
$rawSpouses = $request->post('dep_spouses', []);
$rawChildren = $request->post('dep_children', []);
$rawTemps = $request->post('dep_temps', []);
// Validate spouses: min age, required fields
$spouseMinAge = (int) (RuleEngine::getValue('SPOUSE_MIN_AGE', 'value') ?? 21);
foreach ($rawSpouses as $idx => $sp) {
if (empty($sp['full_name_ar'])) continue;
$spDob = $sp['date_of_birth'] ?? null;
if (!$spDob) {
$spNid = $sp['national_id'] ?? '';
if (strlen($spNid) === 14) {
$parsed = NationalIdParser::parse($spNid);
if ($parsed['is_valid']) $spDob = $parsed['dob'];
}
}
if ($spDob) {
$age = (int) date_diff(date_create($spDob), date_create('today'))->y;
if ($age < $spouseMinAge) {
return $this->redirect("/transfers/create/{$memberId}")
->withError('الزوج/ة رقم ' . ($idx + 1) . ' يجب أن يكون عمره ' . $spouseMinAge . ' سنة على الأقل');
}
}
if (empty($sp['marriage_date'])) {
return $this->redirect("/transfers/create/{$memberId}")
->withError('يجب إدخال تاريخ الزواج للزوج/ة رقم ' . ($idx + 1));
}
}
$dependentsData = [ $dependentsData = [
'spouses' => $request->post('dep_spouses', []), 'spouses' => $rawSpouses,
'children' => $request->post('dep_children', []), 'children' => $rawChildren,
'temporary_members' => $request->post('dep_temps', []), 'temporary_members' => $rawTemps,
]; ];
$notesPayload = ['dependents_data' => $dependentsData]; $notesPayload = ['dependents_data' => $dependentsData];
if ($notes) $notesPayload['user_notes'] = $notes; if ($notes) $notesPayload['user_notes'] = $notes;
......
...@@ -205,15 +205,44 @@ final class TransferProcessor ...@@ -205,15 +205,44 @@ final class TransferProcessor
if (empty($sp['full_name_ar'])) continue; if (empty($sp['full_name_ar'])) continue;
$spOrder++; $spOrder++;
$spNid = !empty($sp['national_id']) ? (string) $sp['national_id'] : null; $spNid = !empty($sp['national_id']) ? (string) $sp['national_id'] : null;
$spDob = null; $spDob = !empty($sp['date_of_birth']) ? $sp['date_of_birth'] : null;
$spGender = 'female'; $spGender = 'female';
if ($spNid && strlen($spNid) === 14) { if ($spNid && strlen($spNid) === 14) {
$spParsed = NationalIdParser::parse($spNid); $spParsed = NationalIdParser::parse($spNid);
if ($spParsed['is_valid']) { if ($spParsed['is_valid']) {
$spDob = $spParsed['dob']; if (!$spDob) $spDob = $spParsed['dob'];
$spGender = $spParsed['gender'] ?? 'female'; $spGender = $spParsed['gender'] ?? 'female';
} }
} }
$spNationality = trim($sp['nationality'] ?? 'مصري');
$spJoinDate = !empty($sp['join_date']) ? $sp['join_date'] : $today;
// Calculate addition_fee for non-Egyptian spouses (SPOUSE_FOREIGN_FEE % of current plan price)
$spAdditionFee = '0.00';
$isForeign = ($spNationality !== 'مصري' && $spNationality !== '' && $spNationality !== 'Egyptian');
if ($isForeign) {
try {
$foreignRule = \App\Modules\Rules\Services\RuleEngine::get('SPOUSE_FOREIGN_FEE');
$foreignPct = $foreignRule['percentage'] ?? '15.00';
$newMemberRow = $db->selectOne("SELECT branch_id, qualification_id, membership_type FROM members WHERE id = ?", [$newMemberId]);
$brId = (int) ($newMemberRow['branch_id'] ?? 1);
$qId = !empty($newMemberRow['qualification_id']) ? (int) $newMemberRow['qualification_id'] : null;
$mType = $newMemberRow['membership_type'] ?? 'working';
$planPrice = '0.00';
if ($qId) {
$pr = $db->selectOne("SELECT price FROM pricing_configs WHERE branch_id = ? AND qualification_id = ? AND membership_type = ? AND is_active = 1 AND effective_from <= CURDATE() AND (effective_to IS NULL OR effective_to >= CURDATE()) ORDER BY effective_from DESC LIMIT 1", [$brId, $qId, $mType]);
$planPrice = $pr['price'] ?? '0.00';
}
if (bccomp($planPrice, '0.01', 2) < 0) {
$pr = $db->selectOne("SELECT price FROM pricing_configs WHERE branch_id = ? AND membership_type = ? AND is_active = 1 AND effective_from <= CURDATE() AND (effective_to IS NULL OR effective_to >= CURDATE()) ORDER BY price DESC LIMIT 1", [$brId, $mType]);
$planPrice = $pr['price'] ?? '0.00';
}
if (bccomp($planPrice, '0.01', 2) >= 0) {
$spAdditionFee = bcdiv(bcmul($planPrice, $foreignPct, 4), '100', 2);
}
} catch (\Throwable $e) {}
}
$db->insert('spouses', [ $db->insert('spouses', [
'member_id' => $newMemberId, 'member_id' => $newMemberId,
'spouse_order' => $spOrder, 'spouse_order' => $spOrder,
...@@ -221,8 +250,10 @@ final class TransferProcessor ...@@ -221,8 +250,10 @@ final class TransferProcessor
'national_id' => $spNid, 'national_id' => $spNid,
'date_of_birth'=> $spDob ?: '1900-01-01', 'date_of_birth'=> $spDob ?: '1900-01-01',
'gender' => $spGender, 'gender' => $spGender,
'nationality' => $spNationality ?: 'مصري',
'marriage_date'=> $sp['marriage_date'] ?? $today, 'marriage_date'=> $sp['marriage_date'] ?? $today,
'join_date' => $today, 'join_date' => $spJoinDate,
'addition_fee' => $spAdditionFee,
'status' => 'active', 'status' => 'active',
'is_archived' => 0, 'is_archived' => 0,
'created_at' => $ts, 'created_at' => $ts,
...@@ -246,6 +277,7 @@ final class TransferProcessor ...@@ -246,6 +277,7 @@ final class TransferProcessor
$chGender = $chParsed['gender'] ?? $chGender; $chGender = $chParsed['gender'] ?? $chGender;
} }
} }
$chJoinDate = !empty($ch['join_date']) ? $ch['join_date'] : null;
$db->insert('children', [ $db->insert('children', [
'member_id' => $newMemberId, 'member_id' => $newMemberId,
'child_order' => $chOrder, 'child_order' => $chOrder,
...@@ -253,6 +285,7 @@ final class TransferProcessor ...@@ -253,6 +285,7 @@ final class TransferProcessor
'national_id' => $chNid, 'national_id' => $chNid,
'date_of_birth' => $chDob ?: '1900-01-01', 'date_of_birth' => $chDob ?: '1900-01-01',
'gender' => $chGender, 'gender' => $chGender,
'join_date' => $chJoinDate,
'classification' => 'active', 'classification' => 'active',
'status' => 'active', 'status' => 'active',
'is_archived' => 0, 'is_archived' => 0,
...@@ -267,13 +300,13 @@ final class TransferProcessor ...@@ -267,13 +300,13 @@ final class TransferProcessor
foreach ($tempsArr as $tm) { foreach ($tempsArr as $tm) {
if (empty($tm['full_name_ar'])) continue; if (empty($tm['full_name_ar'])) continue;
$tmNid = !empty($tm['national_id']) ? (string) $tm['national_id'] : null; $tmNid = !empty($tm['national_id']) ? (string) $tm['national_id'] : null;
$tmDob = null; $tmDob = !empty($tm['date_of_birth']) ? $tm['date_of_birth'] : null;
$tmGender = 'male'; $tmGender = $tm['gender'] ?? 'male';
if ($tmNid && strlen($tmNid) === 14) { if ($tmNid && strlen($tmNid) === 14) {
$tmParsed = NationalIdParser::parse($tmNid); $tmParsed = NationalIdParser::parse($tmNid);
if ($tmParsed['is_valid']) { if ($tmParsed['is_valid']) {
$tmDob = $tmParsed['dob']; if (!$tmDob) $tmDob = $tmParsed['dob'];
$tmGender = $tmParsed['gender'] ?? 'male'; $tmGender = $tmParsed['gender'] ?? $tmGender;
} }
} }
$db->insert('temporary_members', [ $db->insert('temporary_members', [
......
...@@ -478,14 +478,26 @@ ...@@ -478,14 +478,26 @@
if (count === 0) return ''; if (count === 0) return '';
var html = '<div style="margin-bottom:12px;"><strong style="font-size:13px;color:#7C3AED;">' + label + ' (' + count + ')</strong></div>'; var html = '<div style="margin-bottom:12px;"><strong style="font-size:13px;color:#7C3AED;">' + label + ' (' + count + ')</strong></div>';
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
html += '<div style="display:flex;gap:10px;margin-bottom:8px;align-items:center;">'; html += '<div style="border:1px solid #E5E7EB;border-radius:8px;padding:12px;margin-bottom:10px;">';
html += '<span style="font-size:12px;color:#6B7280;min-width:20px;">' + (i+1) + '.</span>'; html += '<div style="font-size:12px;color:#6B7280;margin-bottom:8px;font-weight:600;">' + (i+1) + '. ' + label + '</div>';
html += '<input type="text" name="' + prefix + '[' + i + '][full_name_ar]" class="form-input" style="flex:2;" placeholder="الاسم بالكامل *" required>'; html += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">';
html += '<input type="text" name="' + prefix + '[' + i + '][national_id]" class="form-input" style="flex:1;direction:ltr;text-align:left;" placeholder="الرقم القومي" maxlength="14">'; html += '<input type="text" name="' + prefix + '[' + i + '][full_name_ar]" class="form-input" placeholder="الاسم بالكامل *" required>';
if (fields.indexOf('gender') !== -1) { html += '<input type="text" name="' + prefix + '[' + i + '][national_id]" class="form-input" style="direction:ltr;text-align:left;" placeholder="الرقم القومي (14 رقم)" maxlength="14">';
html += '<select name="' + prefix + '[' + i + '][gender]" class="form-select" style="flex:0.7;"><option value="male">ذكر</option><option value="female">أنثى</option></select>'; if (prefix === 'dep_spouses') {
} html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الميلاد *</label><input type="date" name="' + prefix + '[' + i + '][date_of_birth]" class="form-input" required></div>';
html += '</div>'; html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">الجنسية *</label><select name="' + prefix + '[' + i + '][nationality]" class="form-select"><option value="مصري">مصري</option><option value="أجنبي">أجنبي (غير مصري)</option></select></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الزواج *</label><input type="date" name="' + prefix + '[' + i + '][marriage_date]" class="form-input" required></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الانضمام *</label><input type="date" name="' + prefix + '[' + i + '][join_date]" class="form-input" value="' + (new Date().toISOString().split('T')[0]) + '" required></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ السداد</label><input type="date" name="' + prefix + '[' + i + '][payment_date]" class="form-input" value="' + (new Date().toISOString().split('T')[0]) + '"></div>';
} else if (prefix === 'dep_children') {
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الميلاد *</label><input type="date" name="' + prefix + '[' + i + '][date_of_birth]" class="form-input" required></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">النوع *</label><select name="' + prefix + '[' + i + '][gender]" class="form-select"><option value="male">ذكر</option><option value="female">أنثى</option></select></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الانضمام</label><input type="date" name="' + prefix + '[' + i + '][join_date]" class="form-input" value="' + (new Date().toISOString().split('T')[0]) + '"></div>';
} else {
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">تاريخ الميلاد</label><input type="date" name="' + prefix + '[' + i + '][date_of_birth]" class="form-input"></div>';
html += '<div class="form-group" style="margin:0;"><label style="font-size:11px;color:#6B7280;">النوع</label><select name="' + prefix + '[' + i + '][gender]" class="form-select"><option value="male">ذكر</option><option value="female">أنثى</option></select></div>';
}
html += '</div></div>';
} }
return html; return html;
} }
......
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