Commit 8ae05b0b authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(installments): pass pendingCount to show view; feat(transfers): فصل أبناء employment workflow

- Installments: controller now computes pendingCount before passing to show view,
  fixing Undefined variable crash at show.php:4

- Transfers: add فصل أبناء employment-status workflow
  - Migration Phase_94_002 adds is_employed, graduation_date, work_date,
    date_turned_25, effective_transfer_date columns to transfer_requests
  - SeparationFeeCalculator: new calculateYearsFloor() (floor, never rounds up),
    computeChildSeparationDates() (effective = min(work_date, date_25) or date_25),
    calculateForChildSeparation() (uses floor years + current subscription price)
  - TransferController store(): reads employment fields, computes effective date,
    routes to new calculator for child_separation; saves all new fields
  - calculateFee API: supports transfer_type=child_separation with employment fields
  - create.php: new فصل أبناء section with employment radio, graduation date,
    work date (conditional), auto-computed date_turned_25, effective date display,
    live elapsed-years preview via AJAX
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 8b5a876b
...@@ -72,9 +72,12 @@ class InstallmentController extends Controller ...@@ -72,9 +72,12 @@ class InstallmentController extends Controller
[(int) $id] [(int) $id]
); );
$pendingCount = count(array_filter($schedule, fn($s) => ($s['status'] ?? '') !== 'paid'));
return $this->view('Installments.Views.show', [ return $this->view('Installments.Views.show', [
'plan' => $plan, 'plan' => $plan,
'schedule' => $schedule, 'schedule' => $schedule,
'pendingCount' => $pendingCount,
]); ]);
} }
......
...@@ -89,14 +89,64 @@ class TransferController extends Controller ...@@ -89,14 +89,64 @@ class TransferController extends Controller
} }
} }
// Fetch child record if needed (may have been loaded in age-validation block above)
$child = isset($child) ? $child : ($childId ? $db->selectOne("SELECT * FROM children WHERE id = ?", [$childId]) : null);
// فصل أبناء: read employment fields and compute effective transfer date
$isEmployed = null;
$graduationDate = null;
$workDate = null;
$dateTurned25 = null;
$effectiveTransferDate = null;
if ($transferType === 'child_separation' && $child) {
$isEmployedPost = $request->post('is_employed', '');
if ($isEmployedPost === '') {
return $this->redirect("/transfers/create/{$memberId}")->withError('يجب تحديد حالة التوظيف');
}
$isEmployed = (bool) (int) $isEmployedPost;
$graduationDate = trim($request->post('graduation_date', '')) ?: null;
if ($isEmployed) {
$workDate = trim($request->post('work_date', '')) ?: null;
if (!$workDate) {
return $this->redirect("/transfers/create/{$memberId}")->withError('يجب إدخال تاريخ مباشرة العمل عند اختيار موظف');
}
}
$date25Override = trim($request->post('date_turned_25', '')) ?: null;
if (empty($child['date_of_birth'])) {
return $this->redirect("/transfers/create/{$memberId}")->withError('لا يوجد تاريخ ميلاد للابن — لا يمكن احتساب تاريخ الفصل');
}
$dates = SeparationFeeCalculator::computeChildSeparationDates(
$child['date_of_birth'],
$isEmployed,
$workDate,
$date25Override
);
$dateTurned25 = $dates['date_turned_25'];
$effectiveTransferDate = $dates['effective_transfer_date'];
}
// Calculate fees // Calculate fees
$qualCode = null; $qualCode = null;
if ($childId) {
$child = $child ?? $db->selectOne("SELECT * FROM children WHERE id = ?", [$childId]); if ($transferType === 'child_separation' && $child && $effectiveTransferDate) {
// Use child's qualification if available, otherwise parent's $feeCalc = SeparationFeeCalculator::calculateForChildSeparation(
(int) $memberId,
(int) $childId,
(bool) $isEmployed,
$workDate,
$effectiveTransferDate,
$qualCode
);
} else {
$feeCalc = SeparationFeeCalculator::calculate((int) $memberId, $childId, $qualCode);
} }
$feeCalc = SeparationFeeCalculator::calculate((int) $memberId, $childId, $qualCode);
if (!($feeCalc['success'] ?? false)) { if (!($feeCalc['success'] ?? false)) {
return $this->redirect("/transfers/create/{$memberId}")->withError($feeCalc['error'] ?? 'خطأ في حساب الرسوم'); return $this->redirect("/transfers/create/{$memberId}")->withError($feeCalc['error'] ?? 'خطأ في حساب الرسوم');
} }
...@@ -164,25 +214,30 @@ class TransferController extends Controller ...@@ -164,25 +214,30 @@ class TransferController extends Controller
} }
$transferReq = TransferRequest::create([ $transferReq = TransferRequest::create([
'source_member_id' => (int) $memberId, 'source_member_id' => (int) $memberId,
'transfer_type' => $transferType, 'transfer_type' => $transferType,
'child_id' => $childId, 'child_id' => $childId,
'spouse_id' => $spouseId, 'spouse_id' => $spouseId,
'source_membership_number' => $member['membership_number'], 'source_membership_number' => $member['membership_number'],
'new_membership_value' => $feeCalc['new_membership_value'], 'new_membership_value' => $feeCalc['new_membership_value'] ?? $feeCalc['subscription_price'] ?? '0.00',
'source_companions_count' => $sourceCompanionsCount, 'source_companions_count' => $sourceCompanionsCount,
'target_companions_count' => $targetCompanionsCount, 'target_companions_count' => $targetCompanionsCount,
'companion_surcharge' => $companionSurcharge, 'companion_surcharge' => $companionSurcharge,
'companion_surcharge_breakdown'=> $companionBreakdown, 'companion_surcharge_breakdown'=> $companionBreakdown,
'years_since_acquisition' => $feeCalc['years_since_acquisition'], 'years_since_acquisition' => $feeCalc['years_since_acquisition'],
'qualification_code' => $feeCalc['qualification_code'], 'qualification_code' => $feeCalc['qualification_code'],
'fee_percentage' => $feeCalc['fee_percentage'], 'fee_percentage' => $feeCalc['fee_percentage'],
'separation_fee' => $feeCalc['separation_fee'], 'separation_fee' => $feeCalc['separation_fee'],
'form_fee' => $feeCalc['form_fee'], 'form_fee' => $feeCalc['form_fee'],
'annual_subscription_fee' => $feeCalc['annual_subscription_fee'], 'annual_subscription_fee' => $feeCalc['annual_subscription_fee'],
'total_fee' => $totalWithSurcharge, 'total_fee' => $totalWithSurcharge,
'status' => 'requested', 'status' => 'requested',
'notes' => $notesJson, 'notes' => $notesJson,
'is_employed' => $isEmployed !== null ? ($isEmployed ? 1 : 0) : null,
'graduation_date' => $graduationDate,
'work_date' => $workDate,
'date_turned_25' => $dateTurned25,
'effective_transfer_date' => $effectiveTransferDate,
]); ]);
if (FormBridge::exists('TRANSFER_SEPARATION')) { if (FormBridge::exists('TRANSFER_SEPARATION')) {
...@@ -350,9 +405,38 @@ class TransferController extends Controller ...@@ -350,9 +405,38 @@ class TransferController extends Controller
public function calculateFee(Request $request): Response public function calculateFee(Request $request): Response
{ {
$memberId = (int) $request->post('member_id', 0); $memberId = (int) $request->post('member_id', 0);
$childId = $request->post('child_id') ? (int) $request->post('child_id') : null; $childId = $request->post('child_id') ? (int) $request->post('child_id') : null;
$qualCode = $request->post('qualification_code') ?: null; $qualCode = $request->post('qualification_code') ?: null;
$transferType = $request->post('transfer_type', '');
if ($transferType === 'child_separation' && $childId) {
$db = App::getInstance()->db();
$child = $db->selectOne("SELECT date_of_birth FROM children WHERE id = ?", [$childId]);
$isEmployedPost = $request->post('is_employed', '');
$isEmployed = $isEmployedPost !== '' ? (bool)(int)$isEmployedPost : false;
$workDate = trim($request->post('work_date', '')) ?: null;
$date25Override = trim($request->post('date_turned_25', '')) ?: null;
if ($child && !empty($child['date_of_birth'])) {
$dates = SeparationFeeCalculator::computeChildSeparationDates(
$child['date_of_birth'],
$isEmployed,
$isEmployed ? $workDate : null,
$date25Override
);
$result = SeparationFeeCalculator::calculateForChildSeparation(
$memberId, $childId, $isEmployed,
$isEmployed ? $workDate : null,
$dates['effective_transfer_date'],
$qualCode
);
$result['date_turned_25'] = $dates['date_turned_25'];
$result['effective_transfer_date'] = $dates['effective_transfer_date'];
return $this->json($result);
}
}
$result = SeparationFeeCalculator::calculate($memberId, $childId, $qualCode); $result = SeparationFeeCalculator::calculate($memberId, $childId, $qualCode);
return $this->json($result); return $this->json($result);
......
...@@ -27,6 +27,7 @@ class TransferRequest extends Model ...@@ -27,6 +27,7 @@ class TransferRequest extends Model
'archive_snapshot_id', 'workflow_instance_id', 'archive_snapshot_id', 'workflow_instance_id',
'board_decision_notes', 'approved_by', 'approved_at', 'board_decision_notes', 'approved_by', 'approved_at',
'completed_at', 'status', 'notes', 'completed_at', 'status', 'notes',
'is_employed', 'graduation_date', 'work_date', 'date_turned_25', 'effective_transfer_date',
]; ];
public static function getForMember(int $memberId): array public static function getForMember(int $memberId): array
......
...@@ -102,6 +102,145 @@ final class SeparationFeeCalculator ...@@ -102,6 +102,145 @@ final class SeparationFeeCalculator
return max(1, $diff->y + ($diff->m > 0 || $diff->d > 0 ? 1 : 0)); // partial year rounds up return max(1, $diff->y + ($diff->m > 0 || $diff->d > 0 ? 1 : 0)); // partial year rounds up
} }
/**
* فصل أبناء specific: floors elapsed years (never rounds up partial year).
* Returns elapsed complete years between $acquisitionDate and $effectiveDate.
* Minimum 0 (not 1) — the percentage table covers year 1+.
*/
public static function calculateYearsFloor(string $acquisitionDate, string $effectiveDate): int
{
$from = new \DateTime(substr($acquisitionDate, 0, 10));
$to = new \DateTime(substr($effectiveDate, 0, 10));
$diff = $to->diff($from);
return max(1, $diff->y); // floor: only completed years; min 1 for fee-table lookup
}
/**
* Calculate the effective transfer date for فصل أبناء:
* - If employed: effective = min(work_date, date_turned_25)
* - If not employed (or work_date is null): effective = date_turned_25
*
* date_turned_25 defaults to DOB + 25 years but may be overridden by user.
*
* @param string $dobString Child's date_of_birth (YYYY-MM-DD)
* @param bool $isEmployed
* @param string|null $workDate YYYY-MM-DD, required when employed
* @param string|null $date25Override User-supplied override for date_turned_25
* @return array{date_turned_25: string, effective_transfer_date: string}
*/
public static function computeChildSeparationDates(
string $dobString,
bool $isEmployed,
?string $workDate,
?string $date25Override = null
): array {
$dob = new \DateTime(substr($dobString, 0, 10));
$dob->modify('+25 years');
$dateTurned25 = $date25Override ?? $dob->format('Y-m-d');
if ($isEmployed && $workDate) {
$effective = min($dateTurned25, $workDate);
} else {
$effective = $dateTurned25;
}
return [
'date_turned_25' => $dateTurned25,
'effective_transfer_date' => $effective,
];
}
/**
* Full فصل أبناء fee calculation.
* Uses floor years (not ceil) and effective_transfer_date as the end date.
*/
public static function calculateForChildSeparation(
int $sourceMemberId,
int $childId,
bool $isEmployed,
?string $workDate,
string $effectiveTransferDate,
?string $qualificationCode = null
): array {
$db = App::getInstance()->db();
$member = $db->selectOne("SELECT * FROM members WHERE id = ?", [$sourceMemberId]);
if (!$member) {
return ['error' => 'العضو غير موجود', 'success' => false];
}
// Determine qualification code
if ($qualificationCode === null && !empty($member['qualification_id'])) {
$qual = $db->selectOne("SELECT code FROM qualifications WHERE id = ?", [(int) $member['qualification_id']]);
$qualificationCode = $qual['code'] ?? null;
}
// Current membership subscription price (same lookup as calculate())
$branchId = (int) ($member['branch_id'] ?? 1);
$mType = $member['membership_type'] ?? 'working';
$subscriptionPrice = '0.00';
if ($qualificationCode) {
$priceInfo = PricingEngine::getMembershipPrice($branchId, $qualificationCode);
$subscriptionPrice = $priceInfo['price'] ?? '0.00';
}
if (bccomp($subscriptionPrice, '0.01', 2) < 0) {
$qualId = !empty($member['qualification_id']) ? (int) $member['qualification_id'] : null;
if ($qualId) {
$pricing = $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",
[$branchId, $qualId, $mType]
);
} else {
$pricing = $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 ASC LIMIT 1",
[$branchId, $mType]
);
}
if ($pricing && bccomp($pricing['price'], '0.01', 2) >= 0) {
$subscriptionPrice = $pricing['price'];
}
}
// Elapsed years: floor from father's membership creation to effective_transfer_date
$acquisitionDate = $member['created_at'] ?? $member['form_date'] ?? date('Y-m-d');
$yearsSince = self::calculateYearsFloor($acquisitionDate, $effectiveTransferDate);
$feePercentage = self::getFeePercentageByYear($yearsSince);
// Separation fee = percentage × current subscription price
$separationFee = bcdiv(bcmul($subscriptionPrice, (string) $feePercentage, 4), '100', 2);
// Form fee + annual subscription (same as regular separation)
$formFeeData = RuleEngine::get('FORM_TRANSFER_FEE');
$formFee = $formFeeData['amount'] ?? '570.00';
$annualSub = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$annualSubscriptionFee = bcadd($annualSub, $devFee, 2);
$totalFee = bcadd(bcadd($separationFee, $formFee, 2), $annualSubscriptionFee, 2);
return [
'success' => true,
'source_member_id' => $sourceMemberId,
'child_id' => $childId,
'qualification_code' => $qualificationCode,
'subscription_price' => $subscriptionPrice,
'years_since_acquisition' => $yearsSince,
'fee_percentage' => $feePercentage,
'separation_fee' => $separationFee,
'form_fee' => $formFee,
'annual_subscription_fee' => $annualSubscriptionFee,
'total_fee' => $totalFee,
'acquisition_date' => $acquisitionDate,
'effective_transfer_date' => $effectiveTransferDate,
'is_employed' => $isEmployed,
'work_date' => $workDate,
];
}
public static function calculateAcquiredMemberChildFee(int $childAge, string $membershipValue): array public static function calculateAcquiredMemberChildFee(int $childAge, string $membershipValue): array
{ {
if ($childAge < 12) { if ($childAge < 12) {
......
...@@ -41,6 +41,66 @@ ...@@ -41,6 +41,66 @@
</div> </div>
</div> </div>
<!-- فصل أبناء — Employment & Date Fields -->
<div id="child-separation-section" style="display:none;">
<div class="card" style="margin-bottom:20px;border:2px solid #0D9488;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;background:#F0FDF4;">
<h3 style="margin:0;color:#0D9488;font-size:15px;">بيانات فصل الأبناء</h3>
<p style="margin:5px 0 0;font-size:12px;color:#6B7280;">يتم احتساب تاريخ الفصل الفعلي تلقائياً بناءً على حالة التوظيف وتاريخ بلوغ الـ 25</p>
</div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<!-- Employment Status -->
<div class="form-group" style="grid-column:1/-1;">
<label class="form-label">الحالة الوظيفية <span style="color:#DC2626;">*</span></label>
<div style="display:flex;gap:20px;margin-top:6px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
<input type="radio" name="is_employed" id="is_employed_no" value="0">
<span>غير موظف</span>
</label>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
<input type="radio" name="is_employed" id="is_employed_yes" value="1">
<span>موظف</span>
</label>
</div>
</div>
<!-- Graduation Date (documentation only, not used in calculation) -->
<div class="form-group">
<label class="form-label">تاريخ التخرج <span style="font-size:11px;color:#6B7280;">(توثيق فقط)</span></label>
<input type="date" name="graduation_date" id="graduation_date" class="form-input">
</div>
<!-- Work Date (shown only when employed) -->
<div class="form-group" id="work-date-group" style="display:none;">
<label class="form-label">تاريخ مباشرة العمل <span style="color:#DC2626;">*</span></label>
<input type="date" name="work_date" id="work_date" class="form-input">
<small style="color:#6B7280;font-size:11px;">الفصل يتم عند أبكر التاريخين: العمل أو بلوغ الـ 25</small>
</div>
<!-- date_turned_25 — auto-computed, editable override -->
<div class="form-group">
<label class="form-label">تاريخ بلوغ الـ 25 <span style="font-size:11px;color:#6B7280;">(محسوب تلقائياً — قابل للتعديل)</span></label>
<input type="date" name="date_turned_25" id="date_turned_25" class="form-input" style="background:#F9FAFB;">
<small id="date25-auto-note" style="color:#059669;font-size:11px;display:none;">محسوب تلقائياً من تاريخ الميلاد</small>
</div>
<!-- Effective Transfer Date (read-only display) -->
<div class="form-group">
<label class="form-label">تاريخ الفصل الفعلي</label>
<input type="text" id="effective_transfer_date_display" class="form-input" readonly style="background:#F9FAFB;font-weight:600;color:#0D9488;">
<small style="color:#6B7280;font-size:11px;" id="effective-basis-note"></small>
</div>
<!-- Elapsed years display -->
<div class="form-group" id="elapsed-years-group" style="display:none;">
<label class="form-label">سنوات الاكتساب المكتملة</label>
<div id="elapsed-years-display" style="padding:10px 14px;background:#FEF9C3;border-radius:6px;font-size:14px;font-weight:600;color:#92400E;"></div>
<small style="color:#6B7280;font-size:11px;">محسوب بالتمام فقط — لا يُقرَّب للأعلى</small>
</div>
</div>
</div>
</div>
<!-- Recipient Data (for full_transfer) --> <!-- Recipient Data (for full_transfer) -->
<div id="recipient-section" style="display:none;"> <div id="recipient-section" style="display:none;">
<div class="card" style="margin-bottom:20px;border:2px solid #0D7377;"> <div class="card" style="margin-bottom:20px;border:2px solid #0D7377;">
...@@ -165,6 +225,7 @@ ...@@ -165,6 +225,7 @@
var ageWarning = document.getElementById('age-warning'); var ageWarning = document.getElementById('age-warning');
var recipientSection = document.getElementById('recipient-section'); var recipientSection = document.getElementById('recipient-section');
var companionSection = document.getElementById('companion-section'); var companionSection = document.getElementById('companion-section');
var childSeparationSection = document.getElementById('child-separation-section');
var allOptions = Array.from(childSelect.querySelectorAll('option[data-age]')); var allOptions = Array.from(childSelect.querySelectorAll('option[data-age]'));
var targetSpouses = document.getElementById('target_spouses_count'); var targetSpouses = document.getElementById('target_spouses_count');
var targetChildren = document.getElementById('target_children_count'); var targetChildren = document.getElementById('target_children_count');
...@@ -173,12 +234,80 @@ ...@@ -173,12 +234,80 @@
var sourceSpouses = <?= count($spouses) ?>; var sourceSpouses = <?= count($spouses) ?>;
var sourceChildren = <?= count($children) ?>; var sourceChildren = <?= count($children) ?>;
// فصل أبناء elements
var isEmployedYes = document.getElementById('is_employed_yes');
var isEmployedNo = document.getElementById('is_employed_no');
var workDateGroup = document.getElementById('work-date-group');
var workDateInput = document.getElementById('work_date');
var date25Input = document.getElementById('date_turned_25');
var date25Note = document.getElementById('date25-auto-note');
var effectiveDisplay = document.getElementById('effective_transfer_date_display');
var effectiveBasis = document.getElementById('effective-basis-note');
var elapsedGroup = document.getElementById('elapsed-years-group');
var elapsedDisplay = document.getElementById('elapsed-years-display');
// child DOB lookup — keyed by child_id
var childDobMap = {};
<?php foreach ($children as $c): if (empty($c['date_of_birth'])) continue; ?>
childDobMap[<?= (int)$c['id'] ?>] = '<?= htmlspecialchars(substr($c['date_of_birth'], 0, 10)) ?>';
<?php endforeach; ?>
function addYears(dateStr, years) {
var d = new Date(dateStr);
d.setFullYear(d.getFullYear() + years);
return d.toISOString().slice(0, 10);
}
function isEmployed() {
return isEmployedYes && isEmployedYes.checked;
}
function updateDate25FromChild() {
var cid = parseInt(childSelect.value, 10);
var dob = childDobMap[cid] || null;
if (!dob) { date25Input.value = ''; date25Note.style.display = 'none'; return; }
var d25 = addYears(dob, 25);
// Only auto-fill if user hasn't manually changed it
if (!date25Input.dataset.userOverride) {
date25Input.value = d25;
date25Note.style.display = 'block';
}
updateEffectiveDate();
}
function updateEffectiveDate() {
var d25 = date25Input.value;
var wDate = workDateInput.value;
var emp = isEmployed();
workDateGroup.style.display = emp ? 'block' : 'none';
if (!d25) { effectiveDisplay.value = ''; effectiveBasis.textContent = ''; elapsedGroup.style.display = 'none'; return; }
var effective;
if (emp && wDate) {
effective = wDate < d25 ? wDate : d25;
effectiveBasis.textContent = wDate < d25
? 'الفصل يتم عند تاريخ العمل (أبكر من بلوغ 25)'
: 'الفصل يتم عند بلوغ 25 سنة (قبل أو مع العمل)';
} else {
effective = d25;
effectiveBasis.textContent = emp ? 'أدخل تاريخ العمل لتحديد التاريخ الفعلي' : 'الفصل يتم عند بلوغ 25 سنة';
}
effectiveDisplay.value = effective;
// Show elapsed years info (UI only — server recalculates)
fetchFeePreview();
}
function updateUI() { function updateUI() {
var type = transferType.value; var type = transferType.value;
var isChildSeparation = (type === 'child_separation');
var isChildType = (type === 'child_separation' || type === 'child_mandatory_25'); var isChildType = (type === 'child_separation' || type === 'child_mandatory_25');
var isFullTransfer = (type === 'full_transfer'); var isFullTransfer = (type === 'full_transfer');
childGroup.style.display = isChildType ? 'block' : 'none'; childGroup.style.display = isChildType ? 'block' : 'none';
childSeparationSection.style.display = isChildSeparation ? 'block' : 'none';
recipientSection.style.display = isFullTransfer ? 'block' : 'none'; recipientSection.style.display = isFullTransfer ? 'block' : 'none';
companionSection.style.display = isFullTransfer ? 'block' : 'none'; companionSection.style.display = isFullTransfer ? 'block' : 'none';
...@@ -231,10 +360,20 @@ ...@@ -231,10 +360,20 @@
} }
var memberId = <?= (int) $member['id'] ?>; var memberId = <?= (int) $member['id'] ?>;
var childId = childSelect.value || ''; var childId = childSelect.value || '';
var body = 'member_id=' + memberId + '&child_id=' + childId + '&transfer_type=' + encodeURIComponent(type);
if (type === 'child_separation') {
var emp = isEmployed() ? '1' : '0';
body += '&is_employed=' + emp;
if (workDateInput.value) body += '&work_date=' + encodeURIComponent(workDateInput.value);
if (date25Input.value) body += '&date_turned_25=' + encodeURIComponent(date25Input.value);
}
fetch('/api/transfers/calculate-fee', { fetch('/api/transfers/calculate-fee', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': document.querySelector('[name=_csrf_token]').value}, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': document.querySelector('[name=_csrf_token]').value},
body: 'member_id=' + memberId + '&child_id=' + childId body: body
}) })
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(d) { .then(function(d) {
...@@ -245,16 +384,40 @@ ...@@ -245,16 +384,40 @@
document.getElementById('fp-total').textContent = d.total_fee; document.getElementById('fp-total').textContent = d.total_fee;
document.getElementById('fee-preview').style.display = 'block'; document.getElementById('fee-preview').style.display = 'block';
document.getElementById('fp-surcharge-row').style.display = 'none'; document.getElementById('fp-surcharge-row').style.display = 'none';
if (type === 'child_separation' && d.years_since_acquisition) {
elapsedGroup.style.display = 'block';
elapsedDisplay.textContent = d.years_since_acquisition + ' سنة — نسبة ' + d.fee_percentage + '%';
} else {
elapsedGroup.style.display = 'none';
}
} }
}) })
.catch(function() {}); .catch(function() {});
} }
transferType.addEventListener('change', updateUI); transferType.addEventListener('change', updateUI);
childSelect.addEventListener('change', fetchFeePreview); childSelect.addEventListener('change', function() {
date25Input.removeAttribute('data-user-override');
updateDate25FromChild();
fetchFeePreview();
});
targetSpouses.addEventListener('change', updateChildrenAgeInputs); targetSpouses.addEventListener('change', updateChildrenAgeInputs);
targetChildren.addEventListener('change', updateChildrenAgeInputs); targetChildren.addEventListener('change', updateChildrenAgeInputs);
// فصل أبناء listeners
if (isEmployedYes) {
isEmployedYes.addEventListener('change', updateEffectiveDate);
isEmployedNo.addEventListener('change', updateEffectiveDate);
}
if (workDateInput) workDateInput.addEventListener('change', updateEffectiveDate);
if (date25Input) {
date25Input.addEventListener('change', function() {
this.dataset.userOverride = '1';
updateEffectiveDate();
});
}
// Recipient NID auto-parse // Recipient NID auto-parse
var rNid = document.getElementById('recipientNidInput'); var rNid = document.getElementById('recipientNidInput');
var rDob = document.getElementById('recipientDobInput'); var rDob = document.getElementById('recipientDobInput');
......
<?php
declare(strict_types=1);
return [
'up' => function (\App\Core\Database $db): void {
$cols = [
'is_employed' => "ALTER TABLE transfer_requests ADD COLUMN is_employed TINYINT(1) NULL DEFAULT NULL AFTER notes",
'graduation_date' => "ALTER TABLE transfer_requests ADD COLUMN graduation_date DATE NULL DEFAULT NULL AFTER is_employed",
'work_date' => "ALTER TABLE transfer_requests ADD COLUMN work_date DATE NULL DEFAULT NULL AFTER graduation_date",
'date_turned_25' => "ALTER TABLE transfer_requests ADD COLUMN date_turned_25 DATE NULL DEFAULT NULL AFTER work_date",
'effective_transfer_date'=> "ALTER TABLE transfer_requests ADD COLUMN effective_transfer_date DATE NULL DEFAULT NULL AFTER date_turned_25",
];
foreach ($cols as $col => $sql) {
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.COLUMNS
WHERE table_schema = DATABASE() AND table_name = 'transfer_requests' AND column_name = ?",
[$col]
);
if (!$exists) {
$db->raw($sql);
}
}
},
'down' => "
ALTER TABLE transfer_requests
DROP COLUMN IF EXISTS is_employed,
DROP COLUMN IF EXISTS graduation_date,
DROP COLUMN IF EXISTS work_date,
DROP COLUMN IF EXISTS date_turned_25,
DROP COLUMN IF EXISTS effective_transfer_date;
",
];
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