Commit 5b4e277d authored by Mahmoud Aglan's avatar Mahmoud Aglan

dryjkftykft

parent 5ff91171
...@@ -72,8 +72,11 @@ final class ChildFeeCalculator ...@@ -72,8 +72,11 @@ final class ChildFeeCalculator
$breakdown[] = '👶 الترتيب: #' . $childOrder . ' — السن: ' . $childAge . ' سنة'; $breakdown[] = '👶 الترتيب: #' . $childOrder . ' — السن: ' . $childAge . ' سنة';
$breakdown[] = '📌 التصنيف: ' . ($classLabels[$classification] ?? $classification); $breakdown[] = '📌 التصنيف: ' . ($classLabels[$classification] ?? $classification);
if ($classification === 'included') { if ($classification === 'included' && bccomp($formFee, '0', 2) <= 0) {
$breakdown[] = '✅ مشمول في قيمة العضوية — بدون رسوم إضافية'; $breakdown[] = '✅ مشمول في قيمة العضوية — بدون رسوم إضافية';
} elseif ($classification === 'included' && bccomp($formFee, '0', 2) > 0) {
$breakdown[] = '✅ مشمول (بدون رسوم عضوية)';
$breakdown[] = '📝 رسوم استمارة إضافة: ' . money($formFee);
} else { } else {
if (bccomp($childFee, '0', 2) > 0) { if (bccomp($childFee, '0', 2) > 0) {
$breakdown[] = '📊 نسبة ' . $percentage . '% × ' . money($membershipValue) . ' = ' . money($childFee); $breakdown[] = '📊 نسبة ' . $percentage . '% × ' . money($membershipValue) . ' = ' . money($childFee);
......
...@@ -10,26 +10,31 @@ use App\Modules\ServiceCatalog\Models\ServicePrice; ...@@ -10,26 +10,31 @@ use App\Modules\ServiceCatalog\Models\ServicePrice;
/** /**
* Determines whether a dependant addition needs a form fee (570 EGP). * Determines whether a dependant addition needs a form fee (570 EGP).
* *
* Business rule: * Business rule — the boundary is the MEMBERSHIP NUMBER:
* - During initial membership creation (member status = potential / under_review), * - Before the member gets a membership number (hasn't paid membership yet),
* all additions are on the same initial form — no additional form fee. * all additions are on the initial 505 EGP form — no extra form fee.
* - After activation (member has membership number), any addition/modification/removal * This covers all pre-activation statuses: potential, under_review,
* requires a 570 EGP form fee PER PERSON. No session sharing. * interview_scheduled, accepted, payment_pending.
* - After activation (member has a membership number), every addition costs
* an additional 570 EGP form fee PER PERSON on top of the dependant's own fees.
* Free dependants (1st spouse, first 2 children under 18) remain free — only
* the 570 form fee applies, and free slots are still free (no 570 either).
*/ */
final class FormFeeService final class FormFeeService
{ {
/** /**
* Check if the member is still on the initial membership form. * Check if the member is still on the initial membership form.
* True = no membership number yet = pre-activation = all on same 505 form.
*/ */
public static function isOnInitialForm(array $member): bool public static function isOnInitialForm(array $member): bool
{ {
return in_array($member['status'] ?? '', ['potential', 'under_review'], true); return empty($member['membership_number']);
} }
/** /**
* Get the form fee that should be charged for an addition. * Get the form fee for adding a dependant.
* Returns '0.00' if on initial form. * Returns '0.00' if still on initial form (no membership number).
* Returns 570 EGP (or configured amount) per person after activation. * Returns 570 EGP per person after activation.
*/ */
public static function getFormFee(int $memberId, array $member): string public static function getFormFee(int $memberId, array $member): string
{ {
...@@ -42,31 +47,43 @@ final class FormFeeService ...@@ -42,31 +47,43 @@ final class FormFeeService
} }
/** /**
* Check if this is a "free" spouse slot on the initial form. * Check if this spouse is in a "free" slot (zero dependant fee).
* Rule: first N spouses are free (default N=1) during initial creation. * Free slots apply ALWAYS — on initial form AND after activation.
* The only difference: after activation the 570 form fee is added.
* On initial form: free slot = no fee at all.
* After activation: free slot = 0 dependant fee + 570 form fee.
*/ */
public static function isSpouseFreeOnInitialForm(int $memberId, int $spouseOrder, array $member): bool public static function isSpouseFreeSlot(int $memberId, int $spouseOrder): bool
{ {
if (!self::isOnInitialForm($member)) {
return false;
}
$maxFree = (int) (RuleEngine::getValue('INITIAL_FREE_SPOUSES_COUNT', 'value') ?? 1); $maxFree = (int) (RuleEngine::getValue('INITIAL_FREE_SPOUSES_COUNT', 'value') ?? 1);
return $spouseOrder <= $maxFree; return $spouseOrder <= $maxFree;
} }
/** /**
* Check if this child is in a "free" slot on the initial form. * @deprecated Use isSpouseFreeSlot() instead
* Rule: first N children under max age are free (default N=2, age<18).
* Uses INITIAL_FREE_CHILDREN_COUNT for the initial form specifically,
* and CHILD_INCLUDED_MAX_COUNT / CHILD_INCLUDED_MAX_AGE for general classification.
*/ */
public static function isChildFreeOnInitialForm(int $memberId, int $childOrder, int $childAge, array $member): bool public static function isSpouseFreeOnInitialForm(int $memberId, int $spouseOrder, array $member): bool
{
return self::isSpouseFreeSlot($memberId, $spouseOrder);
}
/**
* Check if this child is in a "free" slot (zero dependant fee).
* Free slots apply ALWAYS. After activation: 0 dependant fee + 570 form fee.
* On initial form: no fee at all.
*/
public static function isChildFreeSlot(int $memberId, int $childOrder, int $childAge): bool
{ {
if (!self::isOnInitialForm($member)) {
return false;
}
$maxFreeCount = (int) (RuleEngine::getValue('INITIAL_FREE_CHILDREN_COUNT', 'value') ?? 2); $maxFreeCount = (int) (RuleEngine::getValue('INITIAL_FREE_CHILDREN_COUNT', 'value') ?? 2);
$maxFreeAge = (int) (RuleEngine::getValue('CHILD_INCLUDED_MAX_AGE', 'value') ?? 18); $maxFreeAge = (int) (RuleEngine::getValue('CHILD_INCLUDED_MAX_AGE', 'value') ?? 18);
return $childOrder <= $maxFreeCount && $childAge < $maxFreeAge; return $childOrder <= $maxFreeCount && $childAge < $maxFreeAge;
} }
/**
* @deprecated Use isChildFreeSlot() instead
*/
public static function isChildFreeOnInitialForm(int $memberId, int $childOrder, int $childAge, array $member): bool
{
return self::isChildFreeSlot($memberId, $childOrder, $childAge);
}
} }
...@@ -42,7 +42,7 @@ final class SpouseFeeCalculator ...@@ -42,7 +42,7 @@ final class SpouseFeeCalculator
$spouseOrder = $existingCount + 1; $spouseOrder = $existingCount + 1;
$isInitialCreation = FormFeeService::isOnInitialForm($member); $isInitialCreation = FormFeeService::isOnInitialForm($member);
$isFirstFreeSlot = FormFeeService::isSpouseFreeOnInitialForm($memberId, $spouseOrder, $member); $isFreeSlot = FormFeeService::isSpouseFreeSlot($memberId, $spouseOrder);
$formFee = FormFeeService::getFormFee($memberId, $member); $formFee = FormFeeService::getFormFee($memberId, $member);
...@@ -63,9 +63,13 @@ final class SpouseFeeCalculator ...@@ -63,9 +63,13 @@ final class SpouseFeeCalculator
switch (true) { switch (true) {
case ($spouseOrder === 1): case ($spouseOrder === 1):
if ($isFirstFreeSlot && !$isForeign) { if ($isFreeSlot && !$isForeign) {
$percentage = '0.00'; $percentage = '0.00';
$ruleApplied = 'الزوجة الأولى — مشمولة في قيمة العضوية الأساسية (بدون رسوم إضافية)'; if ($isInitialCreation) {
$ruleApplied = 'الزوجة الأولى — مشمولة في الاستمارة الأولى (بدون رسوم)';
} else {
$ruleApplied = 'الزوجة الأولى — مشمولة (رسوم الاستمارة فقط ' . money($formFee) . ')';
}
} elseif ($isForeign) { } elseif ($isForeign) {
$data = RuleEngine::get('SPOUSE_FOREIGN_FEE'); $data = RuleEngine::get('SPOUSE_FOREIGN_FEE');
$percentage = $data['percentage'] ?? '15.00'; $percentage = $data['percentage'] ?? '15.00';
...@@ -143,7 +147,7 @@ final class SpouseFeeCalculator ...@@ -143,7 +147,7 @@ final class SpouseFeeCalculator
'breakdown' => self::buildBreakdown( 'breakdown' => self::buildBreakdown(
$spouseOrder, $membershipValue, $percentage, $percentageFee, $spouseOrder, $membershipValue, $percentage, $percentageFee,
$annualPerYear, $yearCount, $yearlyTotal, $annualPerYear, $yearCount, $yearlyTotal,
$formFee, $totalFee, $ruleApplied, $isFirstFreeSlot && !$isForeign $formFee, $totalFee, $ruleApplied, $isFreeSlot && !$isForeign
), ),
]; ];
} }
...@@ -215,8 +219,11 @@ final class SpouseFeeCalculator ...@@ -215,8 +219,11 @@ final class SpouseFeeCalculator
$lines[] = '📋 القاعدة المطبقة: ' . $rule; $lines[] = '📋 القاعدة المطبقة: ' . $rule;
$lines[] = '💰 قيمة العضوية: ' . money($membershipValue); $lines[] = '💰 قيمة العضوية: ' . money($membershipValue);
if ($isFirstFree) { if ($isFirstFree && bccomp($formFee, '0', 2) <= 0) {
$lines[] = '✅ الزوجة الأولى مشمولة في قيمة العضوية — بدون رسوم إضافية'; $lines[] = '✅ الزوجة الأولى مشمولة في الاستمارة — بدون رسوم إضافية';
} elseif ($isFirstFree && bccomp($formFee, '0', 2) > 0) {
$lines[] = '✅ الزوجة الأولى مشمولة (بدون رسوم عضوية)';
$lines[] = '📝 رسوم استمارة إضافة: ' . money($formFee);
} else { } else {
if (bccomp($pctFee, '0', 2) > 0) { if (bccomp($pctFee, '0', 2) > 0) {
$lines[] = "📊 نسبة {$pct}% × " . money($membershipValue) . ' = ' . money($pctFee); $lines[] = "📊 نسبة {$pct}% × " . money($membershipValue) . ' = ' . money($pctFee);
......
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