Commit 44998953 authored by Administrator's avatar Administrator

Update 1 files via Son of Anton

parent c4a7701a
......@@ -5,91 +5,267 @@ namespace App\Modules\Spouses\Services;
use App\Core\App;
use App\Modules\Spouses\Models\Spouse;
use App\Modules\Pricing\Services\PricingEngine;
use App\Modules\Rules\Services\RuleEngine;
/**
* Spouse Fee Calculator — implements ALL fee rules from club regulations.
*
* Fee Structure:
* ══════════════════════════════════════════════════════════════════
* 1st Spouse (during creation): FREE — included in base membership value
* 1st Spouse (added later, basis member): 15% of membership value + 570 form
* 1st Spouse (added later, acquired member): 50% of membership value + 570 form
* Foreign Spouse: 15% of membership value
* 2nd Spouse: 10% + 150 EGP/year (from marriage or acquisition, whichever later)
* 3rd Spouse: 20% + 200 EGP/year (from marriage or acquisition, whichever later)
* 4th Spouse: 30% + 300 EGP/year (from marriage or acquisition, whichever later)
* Partial year counts as full year.
* Late addition (after membership creation): +570 EGP form fee + annual subscription
* ══════════════════════════════════════════════════════════════════
*/
final class SpouseFeeCalculator
{
/**
* Calculate the full fee breakdown for adding a spouse to a member.
* Calculate the complete fee for adding a spouse.
*/
public static function calculate(int $memberId, array $spouseData): array
{
$db = App::getInstance()->db();
// Load member
$member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [$memberId]);
if (!$member) {
return ['error' => 'العضو غير موجود', 'fee' => '0.00'];
return ['error' => 'العضو غير موجود', 'fee' => '0.00', 'total' => '0.00'];
}
$membershipValue = $member['membership_value'] ?? '0.00';
if (bccomp($membershipValue, '0.00', 2) <= 0) {
return ['error' => 'قيمة العضوية غير محددة', 'fee' => '0.00'];
return ['error' => 'قيمة العضوية غير محددة', 'fee' => '0.00', 'total' => '0.00'];
}
// Determine spouse order
$currentCount = Spouse::countActiveForMember($memberId);
$spouseOrder = $currentCount + 1;
$existingCount = Spouse::countActiveForMember($memberId);
$spouseOrder = $existingCount + 1;
// Get nationality
$nationality = $spouseData['nationality'] ?? 'مصري';
// Is this a late addition? (membership already active/created)
$isLateAddition = !in_array($member['status'], ['potential']);
$formFee = $isLateAddition ? '570.00' : '0.00';
// Get marriage date
$marriageDate = $spouseData['marriage_date'] ?? date('Y-m-d');
// Is the member "acquired" (مكتسب) or "basis" (أساس)?
// Acquired = got membership through transfer/separation/divorce/waiver
$isAcquiredMember = self::isAcquiredMember($memberId);
// Get membership acquisition date (member created_at or a specific field)
$acquisitionDate = $member['created_at'] ? substr($member['created_at'], 0, 10) : date('Y-m-d');
// Spouse nationality
$nationality = trim($spouseData['nationality'] ?? 'مصري');
$isForeign = ($nationality !== 'مصري' && $nationality !== '' && $nationality !== 'Egyptian');
// Determine member type (base or acquired) - for now default to 'base'
// In future phases, this will come from transfer/separation history
$memberType = 'base';
// Marriage date (for per-year calculation)
$marriageDate = $spouseData['marriage_date'] ?? null;
// Membership acquisition date
$memberCreatedDate = substr($member['created_at'] ?? date('Y-m-d'), 0, 10);
// Calculate fees based on spouse order
$percentageFee = '0.00';
$percentage = '0.00';
$annualPerYear = '0.00';
$yearCount = 0;
$yearlyTotal = '0.00';
$ruleApplied = '';
if ($spouseOrder === 1) {
// ── 1st Spouse ──
if (!$isLateAddition) {
// During creation: FREE (included in base)
$percentage = '0.00';
$ruleApplied = 'الزوجة الأولى — مشمولة في القيمة الأساسية';
} elseif ($isForeign) {
// Foreign spouse: 15%
$percentage = '15.00';
$ruleApplied = 'زوج/ة أجنبي — 15%';
} elseif ($isAcquiredMember) {
// Acquired member adding 1st spouse: 50%
$percentage = '50.00';
$ruleApplied = 'إضافة زوج/ة لعضو مكتسب العضوية — 50%';
} else {
// Basis member adding 1st spouse late: 15%
$percentage = '15.00';
$ruleApplied = 'إضافة زوج/ة لعضو أساس العضوية — 15%';
}
$percentageFee = bcdiv(bcmul($membershipValue, $percentage, 4), '100', 2);
} elseif ($spouseOrder === 2) {
// ── 2nd Spouse ──
$percentage = '10.00';
$annualPerYear = '150.00';
$ruleApplied = 'الزوجة الثانية — 10% + 150 ج.م/سنة';
$percentageFee = bcdiv(bcmul($membershipValue, $percentage, 4), '100', 2);
$yearCount = self::calculateYears($marriageDate, $memberCreatedDate);
$yearlyTotal = bcmul($annualPerYear, (string) $yearCount, 2);
} elseif ($spouseOrder === 3) {
// ── 3rd Spouse ──
$percentage = '20.00';
$annualPerYear = '200.00';
$ruleApplied = 'الزوجة الثالثة — 20% + 200 ج.م/سنة';
$percentageFee = bcdiv(bcmul($membershipValue, $percentage, 4), '100', 2);
$yearCount = self::calculateYears($marriageDate, $memberCreatedDate);
$yearlyTotal = bcmul($annualPerYear, (string) $yearCount, 2);
} elseif ($spouseOrder >= 4) {
// ── 4th Spouse ──
$percentage = '30.00';
$annualPerYear = '300.00';
$ruleApplied = 'الزوجة الرابعة — 30% + 300 ج.م/سنة';
$percentageFee = bcdiv(bcmul($membershipValue, $percentage, 4), '100', 2);
$yearCount = self::calculateYears($marriageDate, $memberCreatedDate);
$yearlyTotal = bcmul($annualPerYear, (string) $yearCount, 2);
// Determine spouse age for classification
$spouseDob = $spouseData['date_of_birth'] ?? null;
$spouseAge = 0;
if ($spouseDob) {
$age = age_from_dob($spouseDob);
$spouseAge = $age['years'];
}
// Classification: working if >= 21, dependent if < 21
$workingAgeThreshold = RuleEngine::getValue('SPOUSE_WORKING_AGE_THRESHOLD', 'threshold') ?? 21;
$classification = $spouseAge >= $workingAgeThreshold ? 'working' : 'dependent';
// Calculate fee using PricingEngine
$feeResult = PricingEngine::calculateSpouseFee(
$membershipValue,
$spouseOrder,
$nationality,
$marriageDate,
$acquisitionDate,
$memberType
);
// Add form fee if this is a post-creation addition
$formFee = '0.00';
if ($member['status'] !== 'potential') {
$formFeeData = RuleEngine::get('FORM_ADDITION_FEE');
$formFee = $formFeeData['amount'] ?? '570.00';
// Override for foreign spouse regardless of order
if ($isForeign && $spouseOrder === 1) {
// Already handled above
} elseif ($isForeign && $spouseOrder > 1) {
// Foreign spouse who is 2nd/3rd/4th still pays normal 2nd/3rd/4th rates
// The 15% foreign rule is for 1st spouse only
}
// Total addition fee (percentage + yearly)
$additionFee = bcadd($percentageFee, $yearlyTotal, 2);
// Grand total (addition fee + form fee)
$totalFee = bcadd($additionFee, $formFee, 2);
return [
'spouse_order' => $spouseOrder,
'classification' => $classification,
'membership_value' => $membershipValue,
'nationality' => $nationality,
'marriage_date' => $marriageDate,
'acquisition_date' => $acquisitionDate,
'member_type' => $memberType,
'percentage_fee' => $feeResult['percentage_fee'] ?? '0.00',
'annual_fee' => $feeResult['annual_fee'] ?? '0.00',
'years_count' => $feeResult['years_count'] ?? 0,
'spouse_fee' => $feeResult['total'] ?? '0.00',
'is_late_addition' => $isLateAddition,
'is_acquired' => $isAcquiredMember,
'is_foreign' => $isForeign,
'percentage' => $percentage,
'percentage_fee' => $percentageFee,
'annual_per_year' => $annualPerYear,
'year_count' => $yearCount,
'yearly_total' => $yearlyTotal,
'addition_fee' => $additionFee,
'form_fee' => $formFee,
'total_fee' => bcadd($feeResult['total'] ?? '0.00', $formFee, 2),
'rule_applied' => $feeResult['rule_applied'] ?? 'unknown',
'error' => $feeResult['error'] ?? null,
'total_fee' => $totalFee,
'rule_applied' => $ruleApplied,
'error' => null,
'breakdown' => self::buildBreakdown(
$spouseOrder, $percentage, $percentageFee,
$annualPerYear, $yearCount, $yearlyTotal,
$formFee, $totalFee, $ruleApplied
),
];
}
/**
* Calculate number of years from marriage date or acquisition date (whichever is later).
* Partial year counts as full year (كسر السنة سنة كاملة).
*/
private static function calculateYears(?string $marriageDate, string $memberCreatedDate): int
{
if (!$marriageDate) {
return 1; // Default to 1 year if no marriage date
}
// Use whichever is LATER: marriage date or membership acquisition date
$marriageTs = strtotime($marriageDate);
$memberTs = strtotime($memberCreatedDate);
$startTs = max($marriageTs, $memberTs);
$startDate = date('Y-m-d', $startTs);
$now = new \DateTime();
$start = new \DateTime($startDate);
if ($now <= $start) {
return 1; // Minimum 1 year
}
$diff = $now->diff($start);
$years = $diff->y;
// Partial year counts as full year (كسر السنة سنة كاملة)
if ($diff->m > 0 || $diff->d > 0) {
$years++;
}
return max(1, $years); // Minimum 1 year
}
/**
* Check if a member is "acquired" (مكتسب العضوية) — got membership through transfer/separation.
*/
private static function isAcquiredMember(int $memberId): bool
{
$db = App::getInstance()->db();
// Check if this member was created through a transfer, divorce, death, or waiver
try {
// Check transfer_requests where this member is the target
$transfer = $db->selectOne(
"SELECT id FROM transfer_requests WHERE target_member_id = ? AND status = 'completed' LIMIT 1",
[$memberId]
);
if ($transfer) return true;
// Check divorce cases
$divorce = $db->selectOne(
"SELECT id FROM divorce_cases WHERE spouse_new_member_id = ? AND status = 'completed' LIMIT 1",
[$memberId]
);
if ($divorce) return true;
// Check death transfers
$death = $db->selectOne(
"SELECT id FROM death_cases WHERE transferred_to_member_id = ? AND status = 'completed' LIMIT 1",
[$memberId]
);
if ($death) return true;
// Check waivers
$waiver = $db->selectOne(
"SELECT id FROM waiver_requests WHERE target_member_id = ? AND status = 'completed' LIMIT 1",
[$memberId]
);
if ($waiver) return true;
} catch (\Throwable $e) {
// Tables might not exist yet
}
return false;
}
/**
* Build human-readable breakdown of fee calculation.
*/
private static function buildBreakdown(
int $order, string $pct, string $pctFee,
string $annual, int $years, string $yearlyTotal,
string $formFee, string $totalFee, string $rule
): array {
$lines = [];
$lines[] = "القاعدة: {$rule}";
if (bccomp($pctFee, '0', 2) > 0) {
$lines[] = "نسبة {$pct}% من قيمة العضوية = " . money($pctFee);
}
if (bccomp($annual, '0', 2) > 0 && $years > 0) {
$lines[] = "{$annual} ج.م × {$years} سنة = " . money($yearlyTotal);
$lines[] = "(من تاريخ الزواج أو اكتساب العضوية أيهما لاحق — كسر السنة سنة كاملة)";
}
if (bccomp($formFee, '0', 2) > 0) {
$lines[] = "رسوم استمارة إضافة: " . money($formFee);
}
$lines[] = "الإجمالي: " . money($totalFee);
return $lines;
}
}
\ No newline at end of file
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