Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
44998953
Commit
44998953
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 1 files via Son of Anton
parent
c4a7701a
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
230 additions
and
54 deletions
+230
-54
SpouseFeeCalculator.php
app/Modules/Spouses/Services/SpouseFeeCalculator.php
+230
-54
No files found.
app/Modules/Spouses/Services/SpouseFeeCalculator.php
View file @
44998953
...
@@ -5,91 +5,267 @@ namespace App\Modules\Spouses\Services;
...
@@ -5,91 +5,267 @@ namespace App\Modules\Spouses\Services;
use
App\Core\App
;
use
App\Core\App
;
use
App\Modules\Spouses\Models\Spouse
;
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
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
public
static
function
calculate
(
int
$memberId
,
array
$spouseData
)
:
array
{
{
$db
=
App
::
getInstance
()
->
db
();
$db
=
App
::
getInstance
()
->
db
();
// Load member
$member
=
$db
->
selectOne
(
"SELECT * FROM members WHERE id = ? AND is_archived = 0"
,
[
$memberId
]);
$member
=
$db
->
selectOne
(
"SELECT * FROM members WHERE id = ? AND is_archived = 0"
,
[
$memberId
]);
if
(
!
$member
)
{
if
(
!
$member
)
{
return
[
'error'
=>
'العضو غير موجود'
,
'fee'
=>
'0.00'
];
return
[
'error'
=>
'العضو غير موجود'
,
'fee'
=>
'0.00'
,
'total'
=>
'0.00'
];
}
}
$membershipValue
=
$member
[
'membership_value'
]
??
'0.00'
;
$membershipValue
=
$member
[
'membership_value'
]
??
'0.00'
;
if
(
bccomp
(
$membershipValue
,
'0.00'
,
2
)
<=
0
)
{
if
(
bccomp
(
$membershipValue
,
'0.00'
,
2
)
<=
0
)
{
return
[
'error'
=>
'قيمة العضوية غير محددة'
,
'fee'
=>
'0.00'
];
return
[
'error'
=>
'قيمة العضوية غير محددة'
,
'fee'
=>
'0.00'
,
'total'
=>
'0.00'
];
}
}
// Determine spouse order
// Determine spouse order
$
current
Count
=
Spouse
::
countActiveForMember
(
$memberId
);
$
existing
Count
=
Spouse
::
countActiveForMember
(
$memberId
);
$spouseOrder
=
$
current
Count
+
1
;
$spouseOrder
=
$
existing
Count
+
1
;
// Get nationality
// Is this a late addition? (membership already active/created)
$nationality
=
$spouseData
[
'nationality'
]
??
'مصري'
;
$isLateAddition
=
!
in_array
(
$member
[
'status'
],
[
'potential'
]);
$formFee
=
$isLateAddition
?
'570.00'
:
'0.00'
;
// Get marriage date
// Is the member "acquired" (مكتسب) or "basis" (أساس)?
$marriageDate
=
$spouseData
[
'marriage_date'
]
??
date
(
'Y-m-d'
);
// Acquired = got membership through transfer/separation/divorce/waiver
$isAcquiredMember
=
self
::
isAcquiredMember
(
$memberId
);
// Get membership acquisition date (member created_at or a specific field)
// Spouse nationality
$acquisitionDate
=
$member
[
'created_at'
]
?
substr
(
$member
[
'created_at'
],
0
,
10
)
:
date
(
'Y-m-d'
);
$nationality
=
trim
(
$spouseData
[
'nationality'
]
??
'مصري'
);
$isForeign
=
(
$nationality
!==
'مصري'
&&
$nationality
!==
''
&&
$nationality
!==
'Egyptian'
);
// Determine member type (base or acquired) - for now default to 'base'
// Marriage date (for per-year calculation)
// In future phases, this will come from transfer/separation history
$marriageDate
=
$spouseData
[
'marriage_date'
]
??
null
;
$memberType
=
'base'
;
// 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
// Override for foreign spouse regardless of order
$workingAgeThreshold
=
RuleEngine
::
getValue
(
'SPOUSE_WORKING_AGE_THRESHOLD'
,
'threshold'
)
??
21
;
if
(
$isForeign
&&
$spouseOrder
===
1
)
{
$classification
=
$spouseAge
>=
$workingAgeThreshold
?
'working'
:
'dependent'
;
// Already handled above
}
elseif
(
$isForeign
&&
$spouseOrder
>
1
)
{
// Calculate fee using PricingEngine
// Foreign spouse who is 2nd/3rd/4th still pays normal 2nd/3rd/4th rates
$feeResult
=
PricingEngine
::
calculateSpouseFee
(
// The 15% foreign rule is for 1st spouse only
$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'
;
}
}
// Total addition fee (percentage + yearly)
$additionFee
=
bcadd
(
$percentageFee
,
$yearlyTotal
,
2
);
// Grand total (addition fee + form fee)
$totalFee
=
bcadd
(
$additionFee
,
$formFee
,
2
);
return
[
return
[
'spouse_order'
=>
$spouseOrder
,
'spouse_order'
=>
$spouseOrder
,
'classification'
=>
$classification
,
'membership_value'
=>
$membershipValue
,
'membership_value'
=>
$membershipValue
,
'nationality'
=>
$nationality
,
'is_late_addition'
=>
$isLateAddition
,
'marriage_date'
=>
$marriageDate
,
'is_acquired'
=>
$isAcquiredMember
,
'acquisition_date'
=>
$acquisitionDate
,
'is_foreign'
=>
$isForeign
,
'member_type'
=>
$memberType
,
'percentage'
=>
$percentage
,
'percentage_fee'
=>
$feeResult
[
'percentage_fee'
]
??
'0.00'
,
'percentage_fee'
=>
$percentageFee
,
'annual_fee'
=>
$feeResult
[
'annual_fee'
]
??
'0.00'
,
'annual_per_year'
=>
$annualPerYear
,
'years_count'
=>
$feeResult
[
'years_count'
]
??
0
,
'year_count'
=>
$yearCount
,
'spouse_fee'
=>
$feeResult
[
'total'
]
??
'0.00'
,
'yearly_total'
=>
$yearlyTotal
,
'addition_fee'
=>
$additionFee
,
'form_fee'
=>
$formFee
,
'form_fee'
=>
$formFee
,
'total_fee'
=>
bcadd
(
$feeResult
[
'total'
]
??
'0.00'
,
$formFee
,
2
),
'total_fee'
=>
$totalFee
,
'rule_applied'
=>
$feeResult
[
'rule_applied'
]
??
'unknown'
,
'rule_applied'
=>
$ruleApplied
,
'error'
=>
$feeResult
[
'error'
]
??
null
,
'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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment