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
d41f51be
Commit
d41f51be
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 20 files via Son of Anton
parent
cb11b4e5
Changes
20
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1689 additions
and
0 deletions
+1689
-0
ChildController.php
app/Modules/Children/Controllers/ChildController.php
+279
-0
Child.php
app/Modules/Children/Models/Child.php
+120
-0
Routes.php
app/Modules/Children/Routes.php
+13
-0
ChildFeeCalculator.php
app/Modules/Children/Services/ChildFeeCalculator.php
+86
-0
children-table.php
app/Modules/Children/Views/_partials/children-table.php
+53
-0
create.php
app/Modules/Children/Views/create.php
+142
-0
edit.php
app/Modules/Children/Views/edit.php
+29
-0
show.php
app/Modules/Children/Views/show.php
+52
-0
bootstrap.php
app/Modules/Children/bootstrap.php
+14
-0
SpouseController.php
app/Modules/Spouses/Controllers/SpouseController.php
+287
-0
Spouse.php
app/Modules/Spouses/Models/Spouse.php
+131
-0
Routes.php
app/Modules/Spouses/Routes.php
+12
-0
SpouseFeeCalculator.php
app/Modules/Spouses/Services/SpouseFeeCalculator.php
+95
-0
spouse-table.php
app/Modules/Spouses/Views/_partials/spouse-table.php
+33
-0
create.php
app/Modules/Spouses/Views/create.php
+160
-0
edit.php
app/Modules/Spouses/Views/edit.php
+39
-0
show.php
app/Modules/Spouses/Views/show.php
+36
-0
bootstrap.php
app/Modules/Spouses/bootstrap.php
+12
-0
Phase_09_001_create_spouses_table.php
database/migrations/Phase_09_001_create_spouses_table.php
+48
-0
Phase_09_002_create_children_table.php
database/migrations/Phase_09_002_create_children_table.php
+48
-0
No files found.
app/Modules/Children/Controllers/ChildController.php
0 → 100644
View file @
d41f51be
This diff is collapsed.
Click to expand it.
app/Modules/Children/Models/Child.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Children\Models
;
use
App\Core\Model
;
use
App\Core\App
;
class
Child
extends
Model
{
protected
static
string
$table
=
'children'
;
protected
static
string
$primaryKey
=
'id'
;
protected
static
bool
$timestamps
=
true
;
protected
static
bool
$softDelete
=
true
;
protected
static
bool
$dispatchEvents
=
true
;
protected
static
array
$fillable
=
[
'member_id'
,
'child_order'
,
'full_name_ar'
,
'full_name_en'
,
'national_id'
,
'birth_certificate_number'
,
'date_of_birth'
,
'age_years'
,
'age_months'
,
'gender'
,
'relationship'
,
'school_faculty'
,
'nationality'
,
'classification'
,
'addition_fee'
,
'fee_receipt_number'
,
'status'
,
'is_frozen'
,
'frozen_at'
,
'frozen_reason'
,
'photo_path'
,
'remarks'
,
];
public
static
function
getForMember
(
int
$memberId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT * FROM children WHERE member_id = ? AND is_archived = 0 ORDER BY child_order ASC"
,
[
$memberId
]
);
}
public
static
function
countActiveForMember
(
int
$memberId
)
:
int
{
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active'"
,
[
$memberId
]
);
return
(
int
)
(
$row
[
'cnt'
]
??
0
);
}
public
static
function
countActiveUnder18ForMember
(
int
$memberId
)
:
int
{
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM children WHERE member_id = ? AND is_archived = 0 AND status = 'active' AND age_years < 18"
,
[
$memberId
]
);
return
(
int
)
(
$row
[
'cnt'
]
??
0
);
}
public
static
function
getNextOrder
(
int
$memberId
)
:
int
{
return
self
::
countActiveForMember
(
$memberId
)
+
1
;
}
public
static
function
nidExistsElsewhere
(
string
$nid
,
?
int
$excludeChildId
=
null
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
$memberDup
=
$db
->
selectOne
(
"SELECT id, full_name_ar, membership_number FROM members WHERE national_id = ? AND is_archived = 0"
,
[
$nid
]
);
if
(
$memberDup
)
return
[
'type'
=>
'member'
,
'data'
=>
$memberDup
];
$spouseDup
=
$db
->
selectOne
(
"SELECT s.id, s.full_name_ar, m.membership_number FROM spouses s JOIN members m ON m.id = s.member_id WHERE s.national_id = ? AND s.is_archived = 0"
,
[
$nid
]
);
if
(
$spouseDup
)
return
[
'type'
=>
'spouse'
,
'data'
=>
$spouseDup
];
$sql
=
"SELECT c.id, c.full_name_ar, m.membership_number FROM children c JOIN members m ON m.id = c.member_id WHERE c.national_id = ? AND c.is_archived = 0"
;
$params
=
[
$nid
];
if
(
$excludeChildId
)
{
$sql
.=
" AND c.id != ?"
;
$params
[]
=
$excludeChildId
;
}
$childDup
=
$db
->
selectOne
(
$sql
,
$params
);
if
(
$childDup
)
return
[
'type'
=>
'child'
,
'data'
=>
$childDup
];
return
null
;
}
public
function
getClassificationLabel
()
:
string
{
return
match
(
$this
->
classification
)
{
'included'
=>
'مشمول (بدون رسوم)'
,
'dependent_with_fee'
=>
'تابع (برسوم)'
,
'temporary'
=>
'مؤقت'
,
'frozen'
=>
'مجمد'
,
'separated'
=>
'منفصل'
,
default
=>
$this
->
classification
,
};
}
public
function
getRelationshipLabel
()
:
string
{
return
match
(
$this
->
relationship
)
{
'son'
=>
'ابن'
,
'daughter'
=>
'ابنة'
,
'stepchild'
=>
'ابن/ابنة الزوج'
,
default
=>
$this
->
relationship
,
};
}
public
function
getStatusLabel
()
:
string
{
return
match
(
$this
->
status
)
{
'active'
=>
'نشط'
,
'inactive'
=>
'غير نشط'
,
'frozen'
=>
'مجمد'
,
'separated'
=>
'منفصل'
,
default
=>
$this
->
status
,
};
}
}
\ No newline at end of file
app/Modules/Children/Routes.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/members/{memberId}/children/create'
,
'Children\Controllers\ChildController@create'
,
[
'auth'
],
'child.add'
],
[
'POST'
,
'/members/{memberId}/children'
,
'Children\Controllers\ChildController@store'
,
[
'auth'
],
'child.add'
],
[
'GET'
,
'/members/{memberId}/children/{id}'
,
'Children\Controllers\ChildController@show'
,
[
'auth'
],
'child.view'
],
[
'GET'
,
'/members/{memberId}/children/{id}/edit'
,
'Children\Controllers\ChildController@edit'
,
[
'auth'
],
'child.edit'
],
[
'POST'
,
'/members/{memberId}/children/{id}'
,
'Children\Controllers\ChildController@update'
,
[
'auth'
],
'child.edit'
],
[
'POST'
,
'/members/{memberId}/children/{id}/archive'
,
'Children\Controllers\ChildController@archive'
,
[
'auth'
],
'child.remove'
],
[
'POST'
,
'/members/{memberId}/children/{id}/freeze'
,
'Children\Controllers\ChildController@freeze'
,
[
'auth'
],
'child.freeze'
],
[
'POST'
,
'/api/children/calculate-fee'
,
'Children\Controllers\ChildController@calculateFee'
,
[
'auth'
],
'child.add'
],
];
\ No newline at end of file
app/Modules/Children/Services/ChildFeeCalculator.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Children\Services
;
use
App\Core\App
;
use
App\Modules\Children\Models\Child
;
use
App\Modules\Pricing\Services\PricingEngine
;
use
App\Modules\Rules\Services\RuleEngine
;
final
class
ChildFeeCalculator
{
/**
* Calculate the fee and classification for adding a child.
*/
public
static
function
calculate
(
int
$memberId
,
array
$childData
)
:
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'
,
'classification'
=>
'included'
];
}
$membershipValue
=
$member
[
'membership_value'
]
??
'0.00'
;
if
(
bccomp
(
$membershipValue
,
'0.00'
,
2
)
<=
0
)
{
return
[
'error'
=>
'قيمة العضوية غير محددة'
,
'fee'
=>
'0.00'
,
'classification'
=>
'included'
];
}
// Get child age
$childDob
=
$childData
[
'date_of_birth'
]
??
null
;
$childAge
=
0
;
if
(
$childDob
)
{
$age
=
age_from_dob
(
$childDob
);
$childAge
=
$age
[
'years'
];
}
// Count existing children under 18 to determine order
$childrenUnder18
=
Child
::
countActiveUnder18ForMember
(
$memberId
);
$totalChildren
=
Child
::
countActiveForMember
(
$memberId
);
$childOrder
=
$totalChildren
+
1
;
// Use PricingEngine
$feeResult
=
PricingEngine
::
calculateChildFee
(
$membershipValue
,
$childAge
,
$childOrder
);
// Add form fee if post-creation
$formFee
=
'0.00'
;
if
(
$member
[
'status'
]
!==
'potential'
)
{
$formFeeData
=
RuleEngine
::
get
(
'FORM_ADDITION_FEE'
);
$formFee
=
$formFeeData
[
'amount'
]
??
'570.00'
;
}
// Determine classification
$classification
=
$feeResult
[
'classification'
]
??
'included'
;
if
(
$classification
===
'not_accepted'
)
{
return
[
'child_order'
=>
$childOrder
,
'child_age'
=>
$childAge
,
'membership_value'
=>
$membershipValue
,
'classification'
=>
'not_accepted'
,
'fee'
=>
'0.00'
,
'percentage'
=>
'0.00'
,
'form_fee'
=>
'0.00'
,
'total_fee'
=>
'0.00'
,
'rule_applied'
=>
$feeResult
[
'rule_applied'
]
??
''
,
'error'
=>
$feeResult
[
'error'
]
??
'سن الابن/الابنة يتجاوز الحد المسموح'
,
];
}
$childFee
=
$feeResult
[
'fee'
]
??
'0.00'
;
return
[
'child_order'
=>
$childOrder
,
'child_age'
=>
$childAge
,
'children_under_18'
=>
$childrenUnder18
,
'membership_value'
=>
$membershipValue
,
'classification'
=>
$classification
,
'fee'
=>
$childFee
,
'percentage'
=>
$feeResult
[
'percentage'
]
??
'0.00'
,
'form_fee'
=>
$formFee
,
'total_fee'
=>
bcadd
(
$childFee
,
$formFee
,
2
),
'rule_applied'
=>
$feeResult
[
'rule_applied'
]
??
''
,
'error'
=>
null
,
];
}
}
\ No newline at end of file
app/Modules/Children/Views/_partials/children-table.php
0 → 100644
View file @
d41f51be
<?php
$children
=
$children
??
[];
$memberId
=
$memberId
??
0
;
?>
<?php
if
(
!
empty
(
$children
))
:
?>
<table
class=
"data-table"
>
<thead>
<tr><th>
#
</th><th>
الاسم
</th><th>
النوع
</th><th>
القرابة
</th><th>
تاريخ الميلاد
</th><th>
السن
</th><th>
التصنيف
</th><th>
رسوم
</th><th>
الحالة
</th><th>
الإجراءات
</th></tr>
</thead>
<tbody>
<?php
foreach
(
$children
as
$c
)
:
?>
<tr>
<td>
<?=
(
int
)
$c
[
'child_order'
]
?>
</td>
<td><a
href=
"/members/
<?=
(
int
)
$memberId
?>
/children/
<?=
(
int
)
$c
[
'id'
]
?>
"
style=
"color:#0D7377;font-weight:600;"
>
<?=
e
(
$c
[
'full_name_ar'
])
?>
</a></td>
<td>
<?=
$c
[
'gender'
]
===
'male'
?
'ذكر'
:
'أنثى'
?>
</td>
<td
style=
"font-size:13px;"
>
<?php
echo
match
(
$c
[
'relationship'
]
??
''
)
{
'son'
=>
'ابن'
,
'daughter'
=>
'ابنة'
,
'stepchild'
=>
'ابن/ابنة زوج'
,
default
=>
$c
[
'relationship'
]
??
''
};
?>
</td>
<td
style=
"font-size:13px;"
>
<?=
e
(
$c
[
'date_of_birth'
])
?>
</td>
<td>
<?=
(
int
)
(
$c
[
'age_years'
]
??
0
)
?>
</td>
<td
style=
"font-size:12px;"
>
<?php
echo
match
(
$c
[
'classification'
]
??
''
)
{
'included'
=>
'<span style="color:#059669;">مشمول</span>'
,
'dependent_with_fee'
=>
'<span style="color:#D97706;">تابع برسوم</span>'
,
'temporary'
=>
'<span style="color:#0284C7;">مؤقت</span>'
,
'frozen'
=>
'<span style="color:#DC2626;">مجمد</span>'
,
'separated'
=>
'<span style="color:#6B7280;">منفصل</span>'
,
default
=>
e
(
$c
[
'classification'
]
??
''
),
};
?>
</td>
<td
style=
"font-weight:600;"
>
<?=
money
(
$c
[
'addition_fee'
]
??
'0'
)
?>
</td>
<td>
<?php
if
(
$c
[
'is_frozen'
]
??
false
)
:
?>
<span
style=
"color:#DC2626;font-weight:600;"
>
● مجمد
</span>
<?php
else
:
?>
<span
style=
"color:
<?=
(
$c
[
'status'
]
??
''
)
===
'active'
?
'#059669'
:
'#DC2626'
?>
;font-weight:600;"
>
●
<?=
(
$c
[
'status'
]
??
''
)
===
'active'
?
'نشط'
:
(
$c
[
'status'
]
??
''
)
?>
</span>
<?php
endif
;
?>
</td>
<td>
<div
style=
"display:flex;gap:5px;"
>
<a
href=
"/members/
<?=
(
int
)
$memberId
?>
/children/
<?=
(
int
)
$c
[
'id'
]
?>
"
class=
"btn btn-sm btn-outline"
>
عرض
</a>
<a
href=
"/members/
<?=
(
int
)
$memberId
?>
/children/
<?=
(
int
)
$c
[
'id'
]
?>
/edit"
class=
"btn btn-sm btn-outline"
>
تعديل
</a>
</div>
</td>
</tr>
<?php
endforeach
;
?>
</tbody>
</table>
<?php
else
:
?>
<p
style=
"color:#6B7280;text-align:center;padding:20px;"
>
لا يوجد أبناء مسجلون
</p>
<?php
endif
;
?>
\ No newline at end of file
app/Modules/Children/Views/create.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إضافة ابن/ابنة —
<?=
e
(
$member
[
'full_name_ar'
])
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
class=
"card"
style=
"margin-bottom:15px;padding:15px;display:flex;justify-content:space-between;align-items:center;"
>
<div>
<strong>
العضو:
</strong>
<?=
e
(
$member
[
'full_name_ar'
])
?>
|
<strong>
رقم العضوية:
</strong>
<?=
e
(
$member
[
'membership_number'
]
??
'لم يُحدد'
)
?>
|
<strong>
ترتيب الابن:
</strong>
#
<?=
(
int
)
$childOrder
?>
</div>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
← العودة للعضو
</a>
</div>
<form
method=
"POST"
action=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/children"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"margin-bottom:20px;"
>
<div
style=
"padding:15px 20px;border-bottom:1px solid #E5E7EB;"
><h3
style=
"margin:0;color:#0D7377;"
>
بيانات الابن/الابنة
</h3></div>
<div
style=
"padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
الاسم بالكامل (عربي)
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"text"
name=
"full_name_ar"
value=
"
<?=
e
(
old
(
'full_name_ar'
))
?>
"
class=
"form-input"
required
minlength=
"5"
maxlength=
"200"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالإنجليزي
</label>
<input
type=
"text"
name=
"full_name_en"
value=
"
<?=
e
(
old
(
'full_name_en'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
نوع القرابة
<span
style=
"color:#DC2626;"
>
*
</span></label>
<select
name=
"relationship"
class=
"form-select"
required
>
<option
value=
""
>
-- اختر --
</option>
<option
value=
"son"
<?=
old
(
'relationship'
)
===
'son'
?
'selected'
:
''
?>
>
ابن
</option>
<option
value=
"daughter"
<?=
old
(
'relationship'
)
===
'daughter'
?
'selected'
:
''
?>
>
ابنة
</option>
<option
value=
"stepchild"
<?=
old
(
'relationship'
)
===
'stepchild'
?
'selected'
:
''
?>
>
ابن/ابنة الزوج
</option>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الرقم القومي
<small
style=
"color:#6B7280;"
>
(مطلوب فوق 16 سنة)
</small></label>
<input
type=
"text"
name=
"national_id"
id=
"child_nid"
value=
"
<?=
e
(
old
(
'national_id'
))
?>
"
class=
"form-input"
maxlength=
"14"
style=
"direction:ltr;text-align:left;"
>
<div
id=
"child-nid-feedback"
style=
"margin-top:5px;font-size:12px;"
></div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
رقم شهادة الميلاد
<small
style=
"color:#6B7280;"
>
(تحت 16 سنة)
</small></label>
<input
type=
"text"
name=
"birth_certificate_number"
value=
"
<?=
e
(
old
(
'birth_certificate_number'
))
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
تاريخ الميلاد
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"date"
name=
"date_of_birth"
id=
"child_dob"
value=
"
<?=
e
(
old
(
'date_of_birth'
))
?>
"
class=
"form-input"
required
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
السن
</label>
<input
type=
"text"
id=
"child_age_display"
class=
"form-input"
style=
"background:#F3F4F6;"
readonly
>
<input
type=
"hidden"
name=
"age_years"
id=
"child_age_years"
>
<input
type=
"hidden"
name=
"age_months"
id=
"child_age_months"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
النوع
<span
style=
"color:#DC2626;"
>
*
</span></label>
<select
name=
"gender"
id=
"child_gender"
class=
"form-select"
required
>
<option
value=
""
>
-- اختر --
</option>
<option
value=
"male"
<?=
old
(
'gender'
)
===
'male'
?
'selected'
:
''
?>
>
ذكر
</option>
<option
value=
"female"
<?=
old
(
'gender'
)
===
'female'
?
'selected'
:
''
?>
>
أنثى
</option>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الكلية / المدرسة
</label>
<input
type=
"text"
name=
"school_faculty"
value=
"
<?=
e
(
old
(
'school_faculty'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
ملاحظات
</label>
<textarea
name=
"remarks"
class=
"form-textarea"
rows=
"2"
>
<?=
e
(
old
(
'remarks'
))
?>
</textarea>
</div>
</div>
</div>
<div
style=
"display:flex;gap:10px;"
>
<button
type=
"submit"
class=
"btn btn-primary"
>
إضافة الابن/الابنة
</button>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
إلغاء
</a>
</div>
</form>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'scripts'
);
?>
<script>
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
var
nidInput
=
document
.
getElementById
(
'child_nid'
);
var
dobInput
=
document
.
getElementById
(
'child_dob'
);
var
ageDisplay
=
document
.
getElementById
(
'child_age_display'
);
var
ageYears
=
document
.
getElementById
(
'child_age_years'
);
var
ageMonths
=
document
.
getElementById
(
'child_age_months'
);
var
genderSelect
=
document
.
getElementById
(
'child_gender'
);
var
feedback
=
document
.
getElementById
(
'child-nid-feedback'
);
nidInput
.
addEventListener
(
'input'
,
function
()
{
var
val
=
this
.
value
.
replace
(
/
\D
/g
,
''
);
this
.
value
=
val
;
if
(
val
.
length
===
14
)
{
var
formData
=
new
FormData
();
formData
.
append
(
'national_id'
,
val
);
var
csrfToken
=
document
.
querySelector
(
'input[name="_csrf_token"]'
);
if
(
csrfToken
)
formData
.
append
(
'_csrf_token'
,
csrfToken
.
value
);
fetch
(
'/api/members/parse-nid'
,
{
method
:
'POST'
,
body
:
formData
})
.
then
(
function
(
r
)
{
return
r
.
json
();
})
.
then
(
function
(
data
)
{
var
p
=
data
.
parsed
;
if
(
p
&&
p
.
is_valid
)
{
dobInput
.
value
=
p
.
dob
;
ageDisplay
.
value
=
p
.
age_years
+
' سنة و '
+
p
.
age_months
+
' شهر'
;
ageYears
.
value
=
p
.
age_years
;
ageMonths
.
value
=
p
.
age_months
;
genderSelect
.
value
=
p
.
gender
;
feedback
.
innerHTML
=
'<span style="color:#059669;">✓ صالح</span>'
;
if
(
data
.
duplicate
)
{
feedback
.
innerHTML
+=
'<br><span style="color:#DC2626;">✖ مسجل: '
+
data
.
duplicate
.
full_name_ar
+
'</span>'
;
}
}
else
{
feedback
.
innerHTML
=
'<span style="color:#DC2626;">✖ '
+
(
p
.
errors
?
p
.
errors
.
join
(
' | '
)
:
'غير صالح'
)
+
'</span>'
;
}
})
.
catch
(
function
()
{
feedback
.
innerHTML
=
'<span style="color:#DC2626;">خطأ</span>'
;
});
}
else
{
feedback
.
innerHTML
=
''
;
}
});
// Manual DOB age calculation
dobInput
.
addEventListener
(
'change'
,
function
()
{
if
(
this
.
value
&&
!
nidInput
.
value
)
{
var
dob
=
new
Date
(
this
.
value
);
var
now
=
new
Date
();
var
years
=
now
.
getFullYear
()
-
dob
.
getFullYear
();
var
months
=
now
.
getMonth
()
-
dob
.
getMonth
();
if
(
months
<
0
||
(
months
===
0
&&
now
.
getDate
()
<
dob
.
getDate
()))
{
years
--
;
months
+=
12
;
}
if
(
now
.
getDate
()
<
dob
.
getDate
())
months
--
;
if
(
months
<
0
)
months
=
0
;
ageDisplay
.
value
=
years
+
' سنة و '
+
months
+
' شهر'
;
ageYears
.
value
=
years
;
ageMonths
.
value
=
months
;
}
});
});
</script>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Children/Views/edit.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
تعديل بيانات الابن:
<?=
e
(
$child
->
full_name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<form
method=
"POST"
action=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/children/
<?=
(
int
)
$child
->
id
?>
"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"padding:20px;margin-bottom:20px;"
>
<h3
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات الأساسية (للقراءة فقط)
</h3>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
><label
class=
"form-label"
>
الاسم
</label><input
type=
"text"
value=
"
<?=
e
(
$child
->
full_name_ar
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الرقم القومي
</label><input
type=
"text"
value=
"
<?=
e
(
$child
->
national_id
?:
'—'
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;direction:ltr;text-align:left;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
تاريخ الميلاد
</label><input
type=
"text"
value=
"
<?=
e
(
$child
->
date_of_birth
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
السن
</label><input
type=
"text"
value=
"
<?=
(
int
)
$child
->
age_years
?>
سنة"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
النوع
</label><input
type=
"text"
value=
"
<?=
$child
->
gender
===
'male'
?
'ذكر'
:
'أنثى'
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
نوع القرابة
</label><input
type=
"text"
value=
"
<?=
e
(
$child
->
getRelationshipLabel
())
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
التصنيف
</label><input
type=
"text"
value=
"
<?=
e
(
$child
->
getClassificationLabel
())
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
</div>
</div>
<div
class=
"card"
style=
"padding:20px;margin-bottom:20px;"
>
<h3
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات القابلة للتعديل
</h3>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
><label
class=
"form-label"
>
الاسم بالإنجليزي
</label><input
type=
"text"
name=
"full_name_en"
value=
"
<?=
e
(
$child
->
full_name_en
??
''
)
?>
"
class=
"form-input"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الكلية / المدرسة
</label><input
type=
"text"
name=
"school_faculty"
value=
"
<?=
e
(
$child
->
school_faculty
??
''
)
?>
"
class=
"form-input"
></div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
><label
class=
"form-label"
>
ملاحظات
</label><textarea
name=
"remarks"
class=
"form-textarea"
rows=
"2"
>
<?=
e
(
$child
->
remarks
??
''
)
?>
</textarea></div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
>
حفظ التعديلات
</button>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
إلغاء
</a>
</form>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Children/Views/show.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
بيانات الابن:
<?=
e
(
$child
->
full_name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'page_actions'
);
?>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/children/
<?=
(
int
)
$child
->
id
?>
/edit"
class=
"btn btn-outline"
>
تعديل
</a>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
← العودة للعضو
</a>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:20px;"
>
<div
class=
"card"
style=
"padding:20px;"
>
<h4
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات الشخصية
</h4>
<table
style=
"width:100%;font-size:14px;"
>
<tr><td
style=
"padding:6px 0;color:#6B7280;width:40%;"
>
الاسم
</td><td
style=
"padding:6px 0;font-weight:600;"
>
<?=
e
(
$child
->
full_name_ar
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الرقم القومي
</td><td
style=
"padding:6px 0;direction:ltr;text-align:right;"
>
<?=
e
(
$child
->
national_id
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
شهادة الميلاد
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
birth_certificate_number
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
تاريخ الميلاد
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
date_of_birth
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
السن
</td><td
style=
"padding:6px 0;"
>
<?=
(
int
)
$child
->
age_years
?>
سنة
<?=
$child
->
age_months
?
'و '
.
(
int
)
$child
->
age_months
.
' شهر'
:
''
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
النوع
</td><td
style=
"padding:6px 0;"
>
<?=
$child
->
gender
===
'male'
?
'ذكر'
:
'أنثى'
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
نوع القرابة
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
getRelationshipLabel
())
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الكلية / المدرسة
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
school_faculty
?:
'—'
)
?>
</td></tr>
</table>
</div>
<div
class=
"card"
style=
"padding:20px;"
>
<h4
style=
"color:#0D7377;margin-bottom:15px;"
>
بيانات العضوية
</h4>
<table
style=
"width:100%;font-size:14px;"
>
<tr><td
style=
"padding:6px 0;color:#6B7280;width:40%;"
>
الترتيب
</td><td
style=
"padding:6px 0;font-weight:600;"
>
#
<?=
(
int
)
$child
->
child_order
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
التصنيف
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
getClassificationLabel
())
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الحالة
</td><td
style=
"padding:6px 0;color:
<?=
$child
->
status
===
'active'
?
'#059669'
:
'#DC2626'
?>
;font-weight:600;"
>
●
<?=
e
(
$child
->
getStatusLabel
())
?>
</td></tr>
<?php
if
(
$child
->
is_frozen
)
:
?>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
مجمد منذ
</td><td
style=
"padding:6px 0;color:#DC2626;"
>
<?=
e
(
$child
->
frozen_at
??
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
سبب التجميد
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
frozen_reason
??
'—'
)
?>
</td></tr>
<?php
endif
;
?>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
رسوم الإضافة
</td><td
style=
"padding:6px 0;font-weight:700;color:#0D7377;"
>
<?=
money
(
$child
->
addition_fee
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
رقم الإيصال
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$child
->
fee_receipt_number
?:
'—'
)
?>
</td></tr>
</table>
<?php
if
(
$child
->
status
===
'active'
&&
!
$child
->
is_frozen
&&
$child
->
gender
===
'male'
&&
(
int
)
$child
->
age_years
>=
25
)
:
?>
<div
style=
"margin-top:20px;padding:15px;background:#FEF2F2;border:1px solid #FECACA;border-radius:8px;"
>
<strong
style=
"color:#DC2626;"
>
⚠ هذا الابن بلغ سن 25 ويجب تجميد عضويته
</strong>
<form
method=
"POST"
action=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/children/
<?=
(
int
)
$child
->
id
?>
/freeze"
style=
"margin-top:10px;"
>
<?=
csrf_field
()
?>
<input
type=
"hidden"
name=
"freeze_reason"
value=
"بلوغ سن 25"
>
<button
type=
"submit"
class=
"btn btn-sm"
style=
"background:#DC2626;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;"
>
تجميد العضوية
</button>
</form>
</div>
<?php
endif
;
?>
<?php
if
(
$child
->
remarks
)
:
?>
<div
style=
"margin-top:15px;"
><h4
style=
"color:#6B7280;font-size:13px;"
>
ملاحظات
</h4><p
style=
"font-size:14px;"
>
<?=
nl2br
(
e
(
$child
->
remarks
))
?>
</p></div>
<?php
endif
;
?>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Children/bootstrap.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
use
App\Core\Registries\PermissionRegistry
;
use
App\Core\EventBus
;
PermissionRegistry
::
register
(
'children'
,
[
'child.add'
=>
[
'ar'
=>
'إضافة أبناء'
,
'en'
=>
'Add Children'
],
'child.edit'
=>
[
'ar'
=>
'تعديل بيانات أبناء'
,
'en'
=>
'Edit Children'
],
'child.remove'
=>
[
'ar'
=>
'إزالة أبناء'
,
'en'
=>
'Remove Children'
],
'child.view'
=>
[
'ar'
=>
'عرض بيانات الأبناء'
,
'en'
=>
'View Children'
],
'child.freeze'
=>
[
'ar'
=>
'تجميد عضوية ابن'
,
'en'
=>
'Freeze Child'
],
'child.separate'
=>
[
'ar'
=>
'فصل أبناء'
,
'en'
=>
'Separate Child'
],
]);
\ No newline at end of file
app/Modules/Spouses/Controllers/SpouseController.php
0 → 100644
View file @
d41f51be
This diff is collapsed.
Click to expand it.
app/Modules/Spouses/Models/Spouse.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Spouses\Models
;
use
App\Core\Model
;
use
App\Core\App
;
class
Spouse
extends
Model
{
protected
static
string
$table
=
'spouses'
;
protected
static
string
$primaryKey
=
'id'
;
protected
static
bool
$timestamps
=
true
;
protected
static
bool
$softDelete
=
true
;
protected
static
bool
$dispatchEvents
=
true
;
protected
static
array
$fillable
=
[
'member_id'
,
'spouse_order'
,
'full_name_ar'
,
'full_name_en'
,
'national_id'
,
'passport_number'
,
'date_of_birth'
,
'age_years'
,
'age_months'
,
'gender'
,
'nationality'
,
'religion'
,
'qualification_id'
,
'occupation'
,
'work_address'
,
'work_phone'
,
'mobile'
,
'marriage_date'
,
'join_date'
,
'classification'
,
'addition_fee'
,
'fee_receipt_number'
,
'status'
,
'photo_path'
,
];
public
static
function
getForMember
(
int
$memberId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
return
$db
->
select
(
"SELECT s.*, q.name_ar as qualification_name
FROM spouses s
LEFT JOIN qualifications q ON q.id = s.qualification_id
WHERE s.member_id = ? AND s.is_archived = 0
ORDER BY s.spouse_order ASC"
,
[
$memberId
]
);
}
public
static
function
countActiveForMember
(
int
$memberId
)
:
int
{
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM spouses WHERE member_id = ? AND is_archived = 0 AND status = 'active'"
,
[
$memberId
]
);
return
(
int
)
(
$row
[
'cnt'
]
??
0
);
}
public
static
function
getNextOrder
(
int
$memberId
)
:
int
{
return
self
::
countActiveForMember
(
$memberId
)
+
1
;
}
public
static
function
nidExistsForOtherMember
(
string
$nid
,
int
$excludeMemberId
,
?
int
$excludeSpouseId
=
null
)
:
?
array
{
$db
=
App
::
getInstance
()
->
db
();
// Check members table
$memberDup
=
$db
->
selectOne
(
"SELECT id, full_name_ar, membership_number FROM members WHERE national_id = ? AND id != ? AND is_archived = 0"
,
[
$nid
,
$excludeMemberId
]
);
if
(
$memberDup
)
{
return
[
'type'
=>
'member'
,
'data'
=>
$memberDup
];
}
// Check spouses table
$sql
=
"SELECT s.id, s.full_name_ar, s.member_id, m.membership_number
FROM spouses s
JOIN members m ON m.id = s.member_id
WHERE s.national_id = ? AND s.is_archived = 0"
;
$params
=
[
$nid
];
if
(
$excludeSpouseId
)
{
$sql
.=
" AND s.id != ?"
;
$params
[]
=
$excludeSpouseId
;
}
$spouseDup
=
$db
->
selectOne
(
$sql
,
$params
);
if
(
$spouseDup
)
{
return
[
'type'
=>
'spouse'
,
'data'
=>
$spouseDup
];
}
// Check children table
$childDup
=
$db
->
selectOne
(
"SELECT c.id, c.full_name_ar, c.member_id, m.membership_number
FROM children c
JOIN members m ON m.id = c.member_id
WHERE c.national_id = ? AND c.is_archived = 0"
,
[
$nid
]
);
if
(
$childDup
)
{
return
[
'type'
=>
'child'
,
'data'
=>
$childDup
];
}
return
null
;
}
public
function
getClassificationLabel
()
:
string
{
return
match
(
$this
->
classification
)
{
'working'
=>
'عضو عامل'
,
'dependent'
=>
'عضو تابع'
,
default
=>
$this
->
classification
,
};
}
public
function
getStatusLabel
()
:
string
{
return
match
(
$this
->
status
)
{
'active'
=>
'نشط'
,
'inactive'
=>
'غير نشط'
,
'separated'
=>
'منفصل'
,
'deceased'
=>
'متوفى'
,
default
=>
$this
->
status
,
};
}
public
function
getMemberName
()
:
string
{
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT full_name_ar FROM members WHERE id = ?"
,
[
$this
->
member_id
]);
return
$row
[
'full_name_ar'
]
??
'—'
;
}
public
function
getQualificationName
()
:
string
{
if
(
!
$this
->
qualification_id
)
return
'—'
;
$db
=
App
::
getInstance
()
->
db
();
$row
=
$db
->
selectOne
(
"SELECT name_ar FROM qualifications WHERE id = ?"
,
[
$this
->
qualification_id
]);
return
$row
[
'name_ar'
]
??
'—'
;
}
}
\ No newline at end of file
app/Modules/Spouses/Routes.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
return
[
[
'GET'
,
'/members/{memberId}/spouses/create'
,
'Spouses\Controllers\SpouseController@create'
,
[
'auth'
],
'spouse.add'
],
[
'POST'
,
'/members/{memberId}/spouses'
,
'Spouses\Controllers\SpouseController@store'
,
[
'auth'
],
'spouse.add'
],
[
'GET'
,
'/members/{memberId}/spouses/{id}'
,
'Spouses\Controllers\SpouseController@show'
,
[
'auth'
],
'spouse.view'
],
[
'GET'
,
'/members/{memberId}/spouses/{id}/edit'
,
'Spouses\Controllers\SpouseController@edit'
,
[
'auth'
],
'spouse.edit'
],
[
'POST'
,
'/members/{memberId}/spouses/{id}'
,
'Spouses\Controllers\SpouseController@update'
,
[
'auth'
],
'spouse.edit'
],
[
'POST'
,
'/members/{memberId}/spouses/{id}/archive'
,
'Spouses\Controllers\SpouseController@archive'
,
[
'auth'
],
'spouse.remove'
],
[
'POST'
,
'/api/spouses/calculate-fee'
,
'Spouses\Controllers\SpouseController@calculateFee'
,
[
'auth'
],
'spouse.add'
],
];
\ No newline at end of file
app/Modules/Spouses/Services/SpouseFeeCalculator.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
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
;
final
class
SpouseFeeCalculator
{
/**
* Calculate the full fee breakdown for adding a spouse to a member.
*/
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'
];
}
$membershipValue
=
$member
[
'membership_value'
]
??
'0.00'
;
if
(
bccomp
(
$membershipValue
,
'0.00'
,
2
)
<=
0
)
{
return
[
'error'
=>
'قيمة العضوية غير محددة'
,
'fee'
=>
'0.00'
];
}
// Determine spouse order
$currentCount
=
Spouse
::
countActiveForMember
(
$memberId
);
$spouseOrder
=
$currentCount
+
1
;
// Get nationality
$nationality
=
$spouseData
[
'nationality'
]
??
'مصري'
;
// Get marriage date
$marriageDate
=
$spouseData
[
'marriage_date'
]
??
date
(
'Y-m-d'
);
// 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'
);
// Determine member type (base or acquired) - for now default to 'base'
// In future phases, this will come from transfer/separation history
$memberType
=
'base'
;
// 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'
;
}
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'
,
'form_fee'
=>
$formFee
,
'total_fee'
=>
bcadd
(
$feeResult
[
'total'
]
??
'0.00'
,
$formFee
,
2
),
'rule_applied'
=>
$feeResult
[
'rule_applied'
]
??
'unknown'
,
'error'
=>
$feeResult
[
'error'
]
??
null
,
];
}
}
\ No newline at end of file
app/Modules/Spouses/Views/_partials/spouse-table.php
0 → 100644
View file @
d41f51be
<?php
$spouses
=
$spouses
??
[];
$memberId
=
$memberId
??
0
;
?>
<?php
if
(
!
empty
(
$spouses
))
:
?>
<table
class=
"data-table"
>
<thead>
<tr><th>
#
</th><th>
الاسم
</th><th>
الرقم القومي
</th><th>
تاريخ الميلاد
</th><th>
السن
</th><th>
التصنيف
</th><th>
رسوم
</th><th>
الحالة
</th><th>
الإجراءات
</th></tr>
</thead>
<tbody>
<?php
foreach
(
$spouses
as
$s
)
:
?>
<tr>
<td>
<?=
(
int
)
$s
[
'spouse_order'
]
?>
</td>
<td><a
href=
"/members/
<?=
(
int
)
$memberId
?>
/spouses/
<?=
(
int
)
$s
[
'id'
]
?>
"
style=
"color:#0D7377;font-weight:600;"
>
<?=
e
(
$s
[
'full_name_ar'
])
?>
</a></td>
<td
style=
"direction:ltr;text-align:right;font-size:13px;"
>
<?=
e
(
$s
[
'national_id'
]
??
'—'
)
?>
</td>
<td
style=
"font-size:13px;"
>
<?=
e
(
$s
[
'date_of_birth'
])
?>
</td>
<td>
<?=
(
int
)
(
$s
[
'age_years'
]
??
0
)
?>
</td>
<td>
<?=
(
$s
[
'classification'
]
??
''
)
===
'working'
?
'عامل'
:
'تابع'
?>
</td>
<td
style=
"font-weight:600;"
>
<?=
money
(
$s
[
'addition_fee'
]
??
'0'
)
?>
</td>
<td><span
style=
"color:
<?=
(
$s
[
'status'
]
??
''
)
===
'active'
?
'#059669'
:
'#DC2626'
?>
;font-weight:600;"
>
●
<?=
(
$s
[
'status'
]
??
''
)
===
'active'
?
'نشط'
:
(
$s
[
'status'
]
??
''
)
?>
</span></td>
<td>
<div
style=
"display:flex;gap:5px;"
>
<a
href=
"/members/
<?=
(
int
)
$memberId
?>
/spouses/
<?=
(
int
)
$s
[
'id'
]
?>
"
class=
"btn btn-sm btn-outline"
>
عرض
</a>
<a
href=
"/members/
<?=
(
int
)
$memberId
?>
/spouses/
<?=
(
int
)
$s
[
'id'
]
?>
/edit"
class=
"btn btn-sm btn-outline"
>
تعديل
</a>
</div>
</td>
</tr>
<?php
endforeach
;
?>
</tbody>
</table>
<?php
else
:
?>
<p
style=
"color:#6B7280;text-align:center;padding:20px;"
>
لا توجد زوجات مسجلة
</p>
<?php
endif
;
?>
\ No newline at end of file
app/Modules/Spouses/Views/create.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إضافة زوج/زوجة —
<?=
e
(
$member
[
'full_name_ar'
])
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
class=
"card"
style=
"margin-bottom:15px;padding:15px;display:flex;justify-content:space-between;align-items:center;"
>
<div>
<strong>
العضو:
</strong>
<?=
e
(
$member
[
'full_name_ar'
])
?>
|
<strong>
رقم العضوية:
</strong>
<?=
e
(
$member
[
'membership_number'
]
??
'لم يُحدد'
)
?>
|
<strong>
ترتيب الزوج/الزوجة:
</strong>
#
<?=
(
int
)
$spouseOrder
?>
</div>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
← العودة للعضو
</a>
</div>
<form
method=
"POST"
action=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/spouses"
id=
"spouse-form"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"margin-bottom:20px;"
>
<div
style=
"padding:15px 20px;border-bottom:1px solid #E5E7EB;"
><h3
style=
"margin:0;color:#0D7377;"
>
بيانات الزوج/الزوجة
</h3></div>
<div
style=
"padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
الاسم بالكامل (عربي)
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"text"
name=
"full_name_ar"
value=
"
<?=
e
(
old
(
'full_name_ar'
))
?>
"
class=
"form-input"
required
minlength=
"10"
maxlength=
"200"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الاسم بالإنجليزي
</label>
<input
type=
"text"
name=
"full_name_en"
value=
"
<?=
e
(
old
(
'full_name_en'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الرقم القومي
</label>
<input
type=
"text"
name=
"national_id"
id=
"spouse_nid"
value=
"
<?=
e
(
old
(
'national_id'
))
?>
"
class=
"form-input"
maxlength=
"14"
pattern=
"\d{14}"
style=
"direction:ltr;text-align:left;"
placeholder=
"14 رقم"
>
<div
id=
"nid-feedback"
style=
"margin-top:5px;font-size:12px;"
></div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
رقم جواز السفر
</label>
<input
type=
"text"
name=
"passport_number"
value=
"
<?=
e
(
old
(
'passport_number'
))
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
تاريخ الميلاد
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"date"
name=
"date_of_birth"
id=
"spouse_dob"
value=
"
<?=
e
(
old
(
'date_of_birth'
))
?>
"
class=
"form-input"
required
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
السن
</label>
<input
type=
"text"
id=
"spouse_age"
class=
"form-input"
style=
"background:#F3F4F6;"
readonly
>
<input
type=
"hidden"
name=
"age_years"
id=
"spouse_age_years"
value=
"
<?=
e
(
old
(
'age_years'
))
?>
"
>
<input
type=
"hidden"
name=
"age_months"
id=
"spouse_age_months"
value=
"
<?=
e
(
old
(
'age_months'
))
?>
"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
النوع
<span
style=
"color:#DC2626;"
>
*
</span></label>
<select
name=
"gender"
id=
"spouse_gender"
class=
"form-select"
required
>
<option
value=
""
>
-- اختر --
</option>
<option
value=
"male"
<?=
old
(
'gender'
)
===
'male'
?
'selected'
:
''
?>
>
ذكر
</option>
<option
value=
"female"
<?=
old
(
'gender'
)
===
'female'
?
'selected'
:
''
?>
>
أنثى
</option>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الجنسية
<span
style=
"color:#DC2626;"
>
*
</span></label>
<select
name=
"nationality"
class=
"form-select"
>
<option
value=
"مصري"
selected
>
مصري
</option>
<?php
foreach
(
$countries
as
$c
)
:
?>
<?php
if
(
$c
[
'nationality_ar'
]
!==
'مصري'
)
:
?>
<option
value=
"
<?=
e
(
$c
[
'nationality_ar'
])
?>
"
<?=
old
(
'nationality'
)
===
$c
[
'nationality_ar'
]
?
'selected'
:
''
?>
>
<?=
e
(
$c
[
'nationality_ar'
])
?>
</option>
<?php
endif
;
?>
<?php
endforeach
;
?>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الديانة
</label>
<select
name=
"religion"
class=
"form-select"
>
<option
value=
""
>
-- اختر --
</option>
<option
value=
"muslim"
<?=
old
(
'religion'
)
===
'muslim'
?
'selected'
:
''
?>
>
مسلم
</option>
<option
value=
"christian"
<?=
old
(
'religion'
)
===
'christian'
?
'selected'
:
''
?>
>
مسيحي
</option>
<option
value=
"other"
<?=
old
(
'religion'
)
===
'other'
?
'selected'
:
''
?>
>
أخرى
</option>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
المؤهل الدراسي
</label>
<select
name=
"qualification_id"
class=
"form-select"
>
<option
value=
""
>
-- اختر --
</option>
<?php
foreach
(
$qualifications
as
$q
)
:
?>
<option
value=
"
<?=
(
int
)
$q
[
'id'
]
?>
"
<?=
old
(
'qualification_id'
)
==
$q
[
'id'
]
?
'selected'
:
''
?>
>
<?=
e
(
$q
[
'name_ar'
])
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
الوظيفة
</label>
<input
type=
"text"
name=
"occupation"
value=
"
<?=
e
(
old
(
'occupation'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
رقم المحمول
</label>
<input
type=
"tel"
name=
"mobile"
value=
"
<?=
e
(
old
(
'mobile'
))
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
maxlength=
"11"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
تليفون العمل
</label>
<input
type=
"tel"
name=
"work_phone"
value=
"
<?=
e
(
old
(
'work_phone'
))
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
>
</div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
>
<label
class=
"form-label"
>
عنوان العمل
</label>
<input
type=
"text"
name=
"work_address"
value=
"
<?=
e
(
old
(
'work_address'
))
?>
"
class=
"form-input"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
تاريخ الزواج
<span
style=
"color:#DC2626;"
>
*
</span></label>
<input
type=
"date"
name=
"marriage_date"
value=
"
<?=
e
(
old
(
'marriage_date'
))
?>
"
class=
"form-input"
required
>
</div>
</div>
</div>
<div
style=
"display:flex;gap:10px;"
>
<button
type=
"submit"
class=
"btn btn-primary"
>
إضافة الزوج/الزوجة
</button>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
إلغاء
</a>
</div>
</form>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'scripts'
);
?>
<script>
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
var
nidInput
=
document
.
getElementById
(
'spouse_nid'
);
var
dobInput
=
document
.
getElementById
(
'spouse_dob'
);
var
ageDisplay
=
document
.
getElementById
(
'spouse_age'
);
var
ageYears
=
document
.
getElementById
(
'spouse_age_years'
);
var
ageMonths
=
document
.
getElementById
(
'spouse_age_months'
);
var
genderSelect
=
document
.
getElementById
(
'spouse_gender'
);
var
feedback
=
document
.
getElementById
(
'nid-feedback'
);
nidInput
.
addEventListener
(
'input'
,
function
()
{
var
val
=
this
.
value
.
replace
(
/
\D
/g
,
''
);
this
.
value
=
val
;
if
(
val
.
length
===
14
)
{
var
formData
=
new
FormData
();
formData
.
append
(
'national_id'
,
val
);
var
csrfToken
=
document
.
querySelector
(
'input[name="_csrf_token"]'
);
if
(
csrfToken
)
formData
.
append
(
'_csrf_token'
,
csrfToken
.
value
);
fetch
(
'/api/members/parse-nid'
,
{
method
:
'POST'
,
body
:
formData
})
.
then
(
function
(
r
)
{
return
r
.
json
();
})
.
then
(
function
(
data
)
{
var
p
=
data
.
parsed
;
if
(
p
&&
p
.
is_valid
)
{
dobInput
.
value
=
p
.
dob
;
ageDisplay
.
value
=
p
.
age_years
+
' سنة و '
+
p
.
age_months
+
' شهر'
;
ageYears
.
value
=
p
.
age_years
;
ageMonths
.
value
=
p
.
age_months
;
genderSelect
.
value
=
p
.
gender
;
feedback
.
innerHTML
=
'<span style="color:#059669;">✓ الرقم القومي صالح</span>'
;
if
(
data
.
duplicate
)
{
feedback
.
innerHTML
+=
'<br><span style="color:#DC2626;">✖ الرقم مسجل بالفعل: '
+
data
.
duplicate
.
full_name_ar
+
'</span>'
;
}
}
else
{
feedback
.
innerHTML
=
'<span style="color:#DC2626;">✖ '
+
(
p
.
errors
?
p
.
errors
.
join
(
' | '
)
:
'رقم غير صالح'
)
+
'</span>'
;
}
})
.
catch
(
function
()
{
feedback
.
innerHTML
=
'<span style="color:#DC2626;">خطأ في الاتصال</span>'
;
});
}
else
{
feedback
.
innerHTML
=
''
;
}
});
});
</script>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Spouses/Views/edit.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
تعديل بيانات الزوج:
<?=
e
(
$spouse
->
full_name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<form
method=
"POST"
action=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/spouses/
<?=
(
int
)
$spouse
->
id
?>
"
>
<?=
csrf_field
()
?>
<div
class=
"card"
style=
"padding:20px;margin-bottom:20px;"
>
<h3
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات الأساسية (للقراءة فقط)
</h3>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
><label
class=
"form-label"
>
الاسم
</label><input
type=
"text"
value=
"
<?=
e
(
$spouse
->
full_name_ar
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الرقم القومي
</label><input
type=
"text"
value=
"
<?=
e
(
$spouse
->
national_id
?:
'—'
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;direction:ltr;text-align:left;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
تاريخ الميلاد
</label><input
type=
"text"
value=
"
<?=
e
(
$spouse
->
date_of_birth
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
تاريخ الزواج
</label><input
type=
"text"
value=
"
<?=
e
(
$spouse
->
marriage_date
)
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الترتيب
</label><input
type=
"text"
value=
"
<?=
(
int
)
$spouse
->
spouse_order
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
التصنيف
</label><input
type=
"text"
value=
"
<?=
e
(
$spouse
->
getClassificationLabel
())
?>
"
class=
"form-input"
disabled
style=
"background:#F3F4F6;"
></div>
</div>
</div>
<div
class=
"card"
style=
"padding:20px;margin-bottom:20px;"
>
<h3
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات القابلة للتعديل
</h3>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:15px;"
>
<div
class=
"form-group"
><label
class=
"form-label"
>
الاسم بالإنجليزي
</label><input
type=
"text"
name=
"full_name_en"
value=
"
<?=
e
(
$spouse
->
full_name_en
??
''
)
?>
"
class=
"form-input"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الديانة
</label>
<select
name=
"religion"
class=
"form-select"
><option
value=
""
>
—
</option><option
value=
"muslim"
<?=
$spouse
->
religion
===
'muslim'
?
'selected'
:
''
?>
>
مسلم
</option><option
value=
"christian"
<?=
$spouse
->
religion
===
'christian'
?
'selected'
:
''
?>
>
مسيحي
</option><option
value=
"other"
<?=
$spouse
->
religion
===
'other'
?
'selected'
:
''
?>
>
أخرى
</option></select>
</div>
<div
class=
"form-group"
><label
class=
"form-label"
>
المؤهل الدراسي
</label>
<select
name=
"qualification_id"
class=
"form-select"
><option
value=
""
>
—
</option>
<?php
foreach
(
$qualifications
as
$q
)
:
?>
<option
value=
"
<?=
(
int
)
$q
[
'id'
]
?>
"
<?=
(
int
)
(
$spouse
->
qualification_id
??
0
)
===
(
int
)
$q
[
'id'
]
?
'selected'
:
''
?>
>
<?=
e
(
$q
[
'name_ar'
])
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div
class=
"form-group"
><label
class=
"form-label"
>
الوظيفة
</label><input
type=
"text"
name=
"occupation"
value=
"
<?=
e
(
$spouse
->
occupation
??
''
)
?>
"
class=
"form-input"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
رقم المحمول
</label><input
type=
"tel"
name=
"mobile"
value=
"
<?=
e
(
$spouse
->
mobile
??
''
)
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
></div>
<div
class=
"form-group"
><label
class=
"form-label"
>
تليفون العمل
</label><input
type=
"tel"
name=
"work_phone"
value=
"
<?=
e
(
$spouse
->
work_phone
??
''
)
?>
"
class=
"form-input"
style=
"direction:ltr;text-align:left;"
></div>
<div
class=
"form-group"
style=
"grid-column:1/-1;"
><label
class=
"form-label"
>
عنوان العمل
</label><input
type=
"text"
name=
"work_address"
value=
"
<?=
e
(
$spouse
->
work_address
??
''
)
?>
"
class=
"form-input"
></div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
>
حفظ التعديلات
</button>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
إلغاء
</a>
</form>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Spouses/Views/show.php
0 → 100644
View file @
d41f51be
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
بيانات الزوج:
<?=
e
(
$spouse
->
full_name_ar
)
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'page_actions'
);
?>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
/spouses/
<?=
(
int
)
$spouse
->
id
?>
/edit"
class=
"btn btn-outline"
>
تعديل
</a>
<a
href=
"/members/
<?=
(
int
)
$member
[
'id'
]
?>
"
class=
"btn btn-outline"
>
← العودة للعضو
</a>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:20px;"
>
<div
class=
"card"
style=
"padding:20px;"
>
<h4
style=
"color:#0D7377;margin-bottom:15px;"
>
البيانات الشخصية
</h4>
<table
style=
"width:100%;font-size:14px;"
>
<tr><td
style=
"padding:6px 0;color:#6B7280;width:40%;"
>
الاسم
</td><td
style=
"padding:6px 0;font-weight:600;"
>
<?=
e
(
$spouse
->
full_name_ar
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الرقم القومي
</td><td
style=
"padding:6px 0;direction:ltr;text-align:right;"
>
<?=
e
(
$spouse
->
national_id
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
تاريخ الميلاد
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
date_of_birth
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
السن
</td><td
style=
"padding:6px 0;"
>
<?=
(
int
)
$spouse
->
age_years
?>
سنة
<?=
$spouse
->
age_months
?
'و '
.
(
int
)
$spouse
->
age_months
.
' شهر'
:
''
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
النوع
</td><td
style=
"padding:6px 0;"
>
<?=
$spouse
->
gender
===
'male'
?
'ذكر'
:
'أنثى'
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الجنسية
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
nationality
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
المؤهل
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
getQualificationName
())
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
تاريخ الزواج
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
marriage_date
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
تاريخ الالتحاق
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
join_date
)
?>
</td></tr>
</table>
</div>
<div
class=
"card"
style=
"padding:20px;"
>
<h4
style=
"color:#0D7377;margin-bottom:15px;"
>
بيانات العضوية
</h4>
<table
style=
"width:100%;font-size:14px;"
>
<tr><td
style=
"padding:6px 0;color:#6B7280;width:40%;"
>
الترتيب
</td><td
style=
"padding:6px 0;font-weight:600;"
>
#
<?=
(
int
)
$spouse
->
spouse_order
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
التصنيف
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
getClassificationLabel
())
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الحالة
</td><td
style=
"padding:6px 0;color:
<?=
$spouse
->
status
===
'active'
?
'#059669'
:
'#DC2626'
?>
;font-weight:600;"
>
<?=
e
(
$spouse
->
getStatusLabel
())
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
رسوم الإضافة
</td><td
style=
"padding:6px 0;font-weight:700;color:#0D7377;"
>
<?=
money
(
$spouse
->
addition_fee
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
رقم الإيصال
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
fee_receipt_number
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
الوظيفة
</td><td
style=
"padding:6px 0;"
>
<?=
e
(
$spouse
->
occupation
?:
'—'
)
?>
</td></tr>
<tr><td
style=
"padding:6px 0;color:#6B7280;"
>
المحمول
</td><td
style=
"padding:6px 0;direction:ltr;text-align:right;"
>
<?=
e
(
$spouse
->
mobile
?:
'—'
)
?>
</td></tr>
</table>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
app/Modules/Spouses/bootstrap.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
use
App\Core\Registries\PermissionRegistry
;
use
App\Core\EventBus
;
PermissionRegistry
::
register
(
'spouses'
,
[
'spouse.add'
=>
[
'ar'
=>
'إضافة زوج/زوجة'
,
'en'
=>
'Add Spouse'
],
'spouse.edit'
=>
[
'ar'
=>
'تعديل بيانات زوج'
,
'en'
=>
'Edit Spouse'
],
'spouse.remove'
=>
[
'ar'
=>
'إزالة زوج/زوجة'
,
'en'
=>
'Remove Spouse'
],
'spouse.view'
=>
[
'ar'
=>
'عرض بيانات الأزواج'
,
'en'
=>
'View Spouses'
],
]);
\ No newline at end of file
database/migrations/Phase_09_001_create_spouses_table.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `spouses` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`member_id` BIGINT UNSIGNED NOT NULL,
`spouse_order` INT UNSIGNED NOT NULL DEFAULT 1,
`full_name_ar` VARCHAR(200) NOT NULL,
`full_name_en` VARCHAR(200) NULL,
`national_id` VARCHAR(14) NULL,
`passport_number` VARCHAR(50) NULL,
`date_of_birth` DATE NOT NULL,
`age_years` INT UNSIGNED NULL,
`age_months` INT UNSIGNED NULL,
`gender` VARCHAR(10) NOT NULL,
`nationality` VARCHAR(100) NULL DEFAULT 'مصري',
`religion` VARCHAR(50) NULL,
`qualification_id` BIGINT UNSIGNED NULL,
`occupation` VARCHAR(200) NULL,
`work_address` TEXT NULL,
`work_phone` VARCHAR(20) NULL,
`mobile` VARCHAR(20) NULL,
`marriage_date` DATE NOT NULL,
`join_date` DATE NOT NULL,
`classification` VARCHAR(30) NOT NULL DEFAULT 'working',
`addition_fee` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`fee_receipt_number` VARCHAR(50) NULL,
`status` VARCHAR(50) NOT NULL DEFAULT 'active',
`photo_path` VARCHAR(500) NULL,
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_spouses_member` (`member_id`),
INDEX `idx_spouses_nid` (`national_id`),
INDEX `idx_spouses_status` (`status`),
INDEX `idx_spouses_archived` (`is_archived`),
CONSTRAINT `fk_spouses_member` FOREIGN KEY (`member_id`) REFERENCES `members`(`id`),
CONSTRAINT `fk_spouses_qualification` FOREIGN KEY (`qualification_id`) REFERENCES `qualifications`(`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `spouses`"
,
];
\ No newline at end of file
database/migrations/Phase_09_002_create_children_table.php
0 → 100644
View file @
d41f51be
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"
CREATE TABLE IF NOT EXISTS `children` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`member_id` BIGINT UNSIGNED NOT NULL,
`child_order` INT UNSIGNED NOT NULL DEFAULT 1,
`full_name_ar` VARCHAR(200) NOT NULL,
`full_name_en` VARCHAR(200) NULL,
`national_id` VARCHAR(14) NULL,
`birth_certificate_number` VARCHAR(50) NULL,
`date_of_birth` DATE NOT NULL,
`age_years` INT UNSIGNED NULL,
`age_months` INT UNSIGNED NULL,
`gender` VARCHAR(10) NOT NULL,
`relationship` VARCHAR(30) NOT NULL DEFAULT 'son',
`school_faculty` VARCHAR(200) NULL,
`nationality` VARCHAR(100) NULL DEFAULT 'مصري',
`classification` VARCHAR(50) NOT NULL DEFAULT 'included',
`addition_fee` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
`fee_receipt_number` VARCHAR(50) NULL,
`status` VARCHAR(50) NOT NULL DEFAULT 'active',
`is_frozen` TINYINT(1) NOT NULL DEFAULT 0,
`frozen_at` TIMESTAMP NULL DEFAULT NULL,
`frozen_reason` VARCHAR(200) NULL,
`photo_path` VARCHAR(500) NULL,
`remarks` TEXT NULL,
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_children_member` (`member_id`),
INDEX `idx_children_nid` (`national_id`),
INDEX `idx_children_status` (`status`),
INDEX `idx_children_classification` (`classification`),
INDEX `idx_children_frozen` (`is_frozen`),
INDEX `idx_children_dob` (`date_of_birth`),
INDEX `idx_children_archived` (`is_archived`),
CONSTRAINT `fk_children_member` FOREIGN KEY (`member_id`) REFERENCES `members`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
"
,
'down'
=>
"DROP TABLE IF EXISTS `children`"
,
];
\ 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