Commit 50a1481e authored by Administrator's avatar Administrator

Update 1 files via Son of Anton

parent ca7312ce
......@@ -7,141 +7,143 @@ use App\Core\App;
use App\Modules\Spouses\Models\Spouse;
/**
* Spouse Fee Calculator — implements ALL fee rules from club regulations.
* Spouse Fee Calculator
*
* 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
* ══════════════════════════════════════════════════════════════════
* RULES FROM CLUB REGULATIONS:
* ═══════════════════════════════════════════════════════════════
* 1st Spouse (same form / initial creation): FREE (included in membership value)
* 1st Spouse (added later, أساسي basis): 15% of membership value + 570 form
* 1st Spouse (added later, مكتسب acquired): 50% of membership value + 570 form
* Foreign Spouse (1st): 15% of membership value
* 2nd Spouse: 10% of membership value + 150 EGP × years
* 3rd Spouse: 20% of membership value + 200 EGP × years
* 4th Spouse: 30% of membership value + 300 EGP × years
* Years = from marriage date or membership acquisition, whichever LATER
* Partial year = full year (كسر السنة سنة كاملة)
* Late addition = +570 EGP form fee
* ═══════════════════════════════════════════════════════════════
*/
final class SpouseFeeCalculator
{
/**
* Calculate the complete fee for adding a spouse.
*/
public static function calculate(int $memberId, array $spouseData): array
{
$db = App::getInstance()->db();
$member = $db->selectOne("SELECT * FROM members WHERE id = ? AND is_archived = 0", [$memberId]);
if (!$member) {
return ['error' => 'العضو غير موجود', 'fee' => '0.00', 'total' => '0.00'];
return self::error('العضو غير موجود');
}
$membershipValue = $member['membership_value'] ?? '0.00';
if (bccomp($membershipValue, '0.00', 2) <= 0) {
return ['error' => 'قيمة العضوية غير محددة', 'fee' => '0.00', 'total' => '0.00'];
// ── HARD BLOCK: No membership value = no spouse ──
if (bccomp($membershipValue, '0.01', 2) < 0) {
return self::error('يجب تحديد قيمة العضوية أولاً (ملء الاستمارة واختيار المؤهل)');
}
// Determine spouse order
// ── Determine spouse order ──
$existingCount = Spouse::countActiveForMember($memberId);
$spouseOrder = $existingCount + 1;
// Is this a late addition? (membership already active/created)
$isLateAddition = !in_array($member['status'], ['potential']);
// ── Is this during initial creation or later? ──
// "initial" = member status is still potential AND no membership_fee/down_payment paid yet
$isInitialCreation = in_array($member['status'] ?? '', ['potential', 'under_review']);
// Even during initial creation, only the FIRST spouse is free
// 2nd/3rd/4th ALWAYS have fees
$isFirstSpouseDuringCreation = ($spouseOrder === 1 && $isInitialCreation);
// Late addition = member is already active/accepted = must pay form fee
$isLateAddition = !in_array($member['status'] ?? '', ['potential', 'under_review']);
$formFee = $isLateAddition ? '570.00' : '0.00';
// Is the member "acquired" (مكتسب) or "basis" (أساس)?
// Acquired = got membership through transfer/separation/divorce/waiver
// ── Is the member acquired (مكتسب)? ──
$isAcquiredMember = self::isAcquiredMember($memberId);
// Spouse nationality
// ── Nationality check ──
$nationality = trim($spouseData['nationality'] ?? 'مصري');
$isForeign = ($nationality !== 'مصري' && $nationality !== '' && $nationality !== 'Egyptian');
// Marriage date (for per-year calculation)
// ── 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';
// ── Calculate based on spouse order ──
$percentage = '0.00';
$percentageFee = '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);
}
// 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
switch (true) {
// ═══ 1ST SPOUSE ═══
case ($spouseOrder === 1):
if ($isFirstSpouseDuringCreation && !$isForeign) {
// Included in base price — truly free
$percentage = '0.00';
$ruleApplied = 'الزوجة الأولى — مشمولة في قيمة العضوية الأساسية (بدون رسوم إضافية)';
} elseif ($isForeign) {
$percentage = '15.00';
$ruleApplied = 'زوج/ة أجنبي — 15% من قيمة العضوية';
} elseif ($isAcquiredMember) {
$percentage = '50.00';
$ruleApplied = 'إضافة زوج/ة لعضو مكتسب العضوية (فصل/طلاق/وفاة/تنازل) — 50% من قيمة العضوية';
} else {
$percentage = '15.00';
$ruleApplied = 'إضافة زوج/ة لعضو أساس العضوية (إضافة لاحقة) — 15% من قيمة العضوية';
}
$percentageFee = bcdiv(bcmul($membershipValue, $percentage, 4), '100', 2);
break;
// ═══ 2ND SPOUSE ═══
case ($spouseOrder === 2):
$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);
break;
// ═══ 3RD SPOUSE ═══
case ($spouseOrder === 3):
$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);
break;
// ═══ 4TH SPOUSE ═══
case ($spouseOrder >= 4):
$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);
break;
}
// Total addition fee (percentage + yearly)
// ── Total ──
$additionFee = bcadd($percentageFee, $yearlyTotal, 2);
// Grand total (addition fee + form fee)
$totalFee = bcadd($additionFee, $formFee, 2);
// ── SANITY CHECK: 2nd+ spouse must NEVER be free ──
if ($spouseOrder >= 2 && bccomp($additionFee, '0.01', 2) < 0) {
// This should never happen if membership_value > 0
return self::error(
'خطأ في حساب الرسوم — الزوجة رقم ' . $spouseOrder .
' يجب أن تكون برسوم. قيمة العضوية: ' . money($membershipValue)
);
}
return [
'spouse_order' => $spouseOrder,
'membership_value' => $membershipValue,
'is_initial' => $isInitialCreation,
'is_late_addition' => $isLateAddition,
'is_acquired' => $isAcquiredMember,
'is_foreign' => $isForeign,
......@@ -156,116 +158,134 @@ final class SpouseFeeCalculator
'rule_applied' => $ruleApplied,
'error' => null,
'breakdown' => self::buildBreakdown(
$spouseOrder, $percentage, $percentageFee,
$spouseOrder, $membershipValue, $percentage, $percentageFee,
$annualPerYear, $yearCount, $yearlyTotal,
$formFee, $totalFee, $ruleApplied
$formFee, $totalFee, $ruleApplied, $isFirstSpouseDuringCreation
),
];
}
/**
* Calculate number of years from marriage date or acquisition date (whichever is later).
* Partial year counts as full year (كسر السنة سنة كاملة).
* Years from marriage date or membership acquisition (whichever later).
* Partial year = full year.
*/
private static function calculateYears(?string $marriageDate, string $memberCreatedDate): int
{
if (!$marriageDate) {
return 1; // Default to 1 year if no marriage date
return 1;
}
// 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);
if (!$marriageTs || !$memberTs) {
return 1;
}
// Use whichever is LATER
$startTs = max($marriageTs, $memberTs);
$start = new \DateTime(date('Y-m-d', $startTs));
$now = new \DateTime();
$start = new \DateTime($startDate);
if ($now <= $start) {
return 1; // Minimum 1 year
return 1;
}
$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
return max(1, $years);
}
/**
* Check if a member is "acquired" (مكتسب العضوية) — got membership through transfer/separation.
* Check if member got membership through transfer/separation/divorce/death/waiver.
*/
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;
$tables = [
['transfer_requests', 'target_member_id'],
['divorce_cases', 'spouse_new_member_id'],
['death_cases', 'transferred_to_member_id'],
['waiver_requests', 'target_member_id'],
];
// 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
foreach ($tables as [$table, $column]) {
try {
if (!$db->tableExists($table)) continue;
$row = $db->selectOne(
"SELECT id FROM `{$table}` WHERE `{$column}` = ? AND status = 'completed' LIMIT 1",
[$memberId]
);
if ($row) return true;
} catch (\Throwable $e) {
continue;
}
}
return false;
}
/**
* Build human-readable breakdown of fee calculation.
* Build detailed Arabic breakdown.
*/
private static function buildBreakdown(
int $order, string $pct, string $pctFee,
int $order, string $membershipValue, string $pct, string $pctFee,
string $annual, int $years, string $yearlyTotal,
string $formFee, string $totalFee, string $rule
string $formFee, string $totalFee, string $rule, bool $isFirstFree
): array {
$lines = [];
$lines[] = "القاعدة: {$rule}";
if (bccomp($pctFee, '0', 2) > 0) {
$lines[] = "نسبة {$pct}% من قيمة العضوية = " . money($pctFee);
}
$lines[] = '📋 القاعدة المطبقة: ' . $rule;
$lines[] = '💰 قيمة العضوية: ' . money($membershipValue);
if ($isFirstFree) {
$lines[] = '✅ الزوجة الأولى مشمولة في قيمة العضوية — بدون رسوم إضافية';
} else {
if (bccomp($pctFee, '0', 2) > 0) {
$lines[] = "📊 نسبة {$pct}% × " . money($membershipValue) . ' = ' . money($pctFee);
}
if (bccomp($annual, '0', 2) > 0 && $years > 0) {
$lines[] = "{$annual} ج.م × {$years} سنة = " . money($yearlyTotal);
$lines[] = "(من تاريخ الزواج أو اكتساب العضوية أيهما لاحق — كسر السنة سنة كاملة)";
}
if (bccomp($annual, '0', 2) > 0 && $years > 0) {
$lines[] = "📅 رسوم سنوية: {$annual} ج.م × {$years} سنة = " . money($yearlyTotal);
$lines[] = ' (من تاريخ الزواج أو اكتساب العضوية — أيهما لاحق — كسر السنة سنة كاملة)';
}
if (bccomp($formFee, '0', 2) > 0) {
$lines[] = "رسوم استمارة إضافة: " . money($formFee);
if (bccomp($formFee, '0', 2) > 0) {
$lines[] = '📝 رسوم استمارة إضافة: ' . money($formFee);
}
}
$lines[] = "الإجمالي: " . money($totalFee);
$lines[] = '═══════════════════════════';
$lines[] = '💵 الإجمالي المطلوب: ' . money($totalFee);
return $lines;
}
/**
* Return error result.
*/
private static function error(string $message): array
{
return [
'spouse_order' => 0,
'membership_value' => '0.00',
'percentage' => '0.00',
'percentage_fee' => '0.00',
'annual_per_year' => '0.00',
'year_count' => 0,
'yearly_total' => '0.00',
'addition_fee' => '0.00',
'form_fee' => '0.00',
'total_fee' => '0.00',
'rule_applied' => '',
'error' => $message,
'breakdown' => ['❌ ' . $message],
];
}
}
\ 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