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
1be9b982
Commit
1be9b982
authored
May 18, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
sports paymert
parent
00f555fb
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
782 additions
and
32 deletions
+782
-32
AccountCodes.php
app/Modules/Accounting/AccountCodes.php
+2
-0
PlayerController.php
app/Modules/SportsActivity/Controllers/PlayerController.php
+12
-2
PlayerDocumentController.php
...s/SportsActivity/Controllers/PlayerDocumentController.php
+96
-0
SubscriptionController.php
...les/SportsActivity/Controllers/SubscriptionController.php
+33
-15
Routes.php
app/Modules/SportsActivity/Routes.php
+4
-0
SaPaymentService.php
app/Modules/SportsActivity/Services/SaPaymentService.php
+197
-0
documents.php
app/Modules/SportsActivity/Views/players/documents.php
+22
-2
show.php
app/Modules/SportsActivity/Views/players/show.php
+111
-6
show.php
app/Modules/SportsActivity/Views/subscriptions/show.php
+119
-7
bootstrap.php
app/Modules/SportsActivity/bootstrap.php
+67
-0
SaMedicalExpiryJob.php
cron/jobs/SaMedicalExpiryJob.php
+76
-0
Phase_70_027_add_medical_enhancements.php
...base/migrations/Phase_70_027_add_medical_enhancements.php
+9
-0
Phase_70_028_add_payment_columns_to_sa.php
...ase/migrations/Phase_70_028_add_payment_columns_to_sa.php
+25
-0
Phase_70_029_add_hourly_booking_account.php
...se/migrations/Phase_70_029_add_hourly_booking_account.php
+9
-0
No files found.
app/Modules/Accounting/AccountCodes.php
View file @
1be9b982
...
...
@@ -97,6 +97,7 @@ final class AccountCodes
// Activities & Sports (4105)
const
ACTIVITY_SUBSCRIPTION
=
'410501'
;
const
ACADEMY_REVENUE
=
'410502'
;
const
HOURLY_BOOKING_REVENUE
=
'410503'
;
// Single-level revenue accounts
const
FINE_REVENUE
=
'4106'
;
...
...
@@ -153,6 +154,7 @@ final class AccountCodes
'annual_subscription'
=>
self
::
ANNUAL_SUBSCRIPTION
,
'development_fee'
=>
self
::
DEVELOPMENT_FEE
,
'activity_subscription'
=>
self
::
ACTIVITY_SUBSCRIPTION
,
'hourly_booking'
=>
self
::
HOURLY_BOOKING_REVENUE
,
'down_payment'
=>
self
::
ACCOUNTS_RECEIVABLE
,
'installment'
=>
self
::
ACCOUNTS_RECEIVABLE
,
'fine'
=>
self
::
FINE_REVENUE
,
...
...
app/Modules/SportsActivity/Controllers/PlayerController.php
View file @
1be9b982
...
...
@@ -176,9 +176,19 @@ class PlayerController extends Controller
[(
int
)
$id
]
);
$medicalHistory
=
$db
->
select
(
"SELECT * FROM sa_player_documents WHERE player_id = ? AND document_type = 'medical_cert' ORDER BY created_at DESC"
,
[(
int
)
$id
]
);
$employee
=
App
::
getInstance
()
->
currentEmployee
();
$canApproveMedical
=
$employee
&&
method_exists
(
$employee
,
'hasPermission'
)
&&
$employee
->
hasPermission
(
'sa.medical.approve'
);
return
$this
->
view
(
'SportsActivity.Views.players.show'
,
[
'player'
=>
$player
,
'documents'
=>
$documents
,
'player'
=>
$player
,
'documents'
=>
$documents
,
'medicalHistory'
=>
$medicalHistory
,
'canApproveMedical'
=>
$canApproveMedical
,
]);
}
...
...
app/Modules/SportsActivity/Controllers/PlayerDocumentController.php
View file @
1be9b982
...
...
@@ -186,4 +186,100 @@ class PlayerDocumentController extends Controller
return
$this
->
redirect
(
'/sa/players/'
.
$pid
.
'/documents'
)
->
withSuccess
(
'تم رفض المستند'
);
}
/**
* Approve a medical document with conditional status.
*/
public
function
approveConditional
(
Request
$request
,
string
$pid
,
string
$id
)
:
Response
{
$db
=
App
::
getInstance
()
->
db
();
$session
=
App
::
getInstance
()
->
session
();
$player
=
$db
->
selectOne
(
"SELECT * FROM sa_players WHERE id = ?"
,
[(
int
)
$pid
]);
if
(
!
$player
)
{
return
$this
->
redirect
(
'/sa/players'
)
->
withError
(
'اللاعب غير موجود'
);
}
$document
=
$db
->
selectOne
(
"SELECT * FROM sa_player_documents WHERE id = ? AND player_id = ?"
,
[(
int
)
$id
,
(
int
)
$pid
]
);
if
(
!
$document
)
{
return
$this
->
redirect
(
'/sa/players/'
.
$pid
.
'/documents'
)
->
withError
(
'المستند غير موجود'
);
}
$expiryDate
=
trim
((
string
)
$request
->
post
(
'expiry_date'
,
''
));
$conditionalNotes
=
trim
((
string
)
$request
->
post
(
'conditional_notes'
,
''
));
if
(
$expiryDate
===
''
)
{
return
$this
->
redirect
(
'/sa/players/'
.
$pid
.
'/documents'
)
->
withError
(
'تاريخ انتهاء الصلاحية مطلوب'
);
}
if
(
$conditionalNotes
===
''
)
{
return
$this
->
redirect
(
'/sa/players/'
.
$pid
.
'/documents'
)
->
withError
(
'ملاحظات الاعتماد المشروط مطلوبة'
);
}
$db
->
update
(
'sa_player_documents'
,
[
'approval_status'
=>
'approved'
,
'conditional_notes'
=>
$conditionalNotes
,
'approved_by'
=>
(
int
)
(
$session
->
get
(
'employee_id'
)
??
0
),
'approved_at'
=>
now
(),
'expiry_date'
=>
$expiryDate
,
'updated_at'
=>
now
(),
],
'id = ?'
,
[(
int
)
$id
]);
$db
->
update
(
'sa_players'
,
[
'medical_status'
=>
'conditional'
,
'medical_expiry_date'
=>
$expiryDate
,
'updated_at'
=>
now
(),
],
'id = ?'
,
[(
int
)
$pid
]);
return
$this
->
redirect
(
'/sa/players/'
.
$pid
.
'/documents'
)
->
withSuccess
(
'تم الاعتماد المشروط وتحديث الحالة الطبية'
);
}
/**
* Update medical details directly from player screen.
*/
public
function
updateMedical
(
Request
$request
,
string
$pid
)
:
Response
{
$db
=
App
::
getInstance
()
->
db
();
$player
=
$db
->
selectOne
(
"SELECT * FROM sa_players WHERE id = ?"
,
[(
int
)
$pid
]);
if
(
!
$player
)
{
return
$this
->
redirect
(
'/sa/players'
)
->
withError
(
'اللاعب غير موجود'
);
}
$medicalStatus
=
trim
((
string
)
$request
->
post
(
'medical_status'
,
''
));
$expiryDate
=
trim
((
string
)
$request
->
post
(
'medical_expiry_date'
,
''
));
$doctorName
=
trim
((
string
)
$request
->
post
(
'doctor_name'
,
''
));
$clinicName
=
trim
((
string
)
$request
->
post
(
'clinic_name'
,
''
));
$examDate
=
trim
((
string
)
$request
->
post
(
'exam_date'
,
''
));
$validStatuses
=
[
'pending'
,
'fit'
,
'conditional'
,
'unfit'
,
'expired'
];
if
(
!
in_array
(
$medicalStatus
,
$validStatuses
,
true
))
{
return
$this
->
redirect
(
'/sa/players/'
.
$pid
)
->
withError
(
'حالة طبية غير صالحة'
);
}
$db
->
update
(
'sa_players'
,
[
'medical_status'
=>
$medicalStatus
,
'medical_expiry_date'
=>
$expiryDate
?:
null
,
'updated_at'
=>
now
(),
],
'id = ?'
,
[(
int
)
$pid
]);
$latestDoc
=
$db
->
selectOne
(
"SELECT id FROM sa_player_documents WHERE player_id = ? AND document_type = 'medical_cert' ORDER BY created_at DESC LIMIT 1"
,
[(
int
)
$pid
]
);
if
(
$latestDoc
)
{
$docUpdate
=
[
'updated_at'
=>
now
()];
if
(
$examDate
!==
''
)
$docUpdate
[
'exam_date'
]
=
$examDate
;
if
(
$expiryDate
!==
''
)
$docUpdate
[
'expiry_date'
]
=
$expiryDate
;
if
(
$doctorName
!==
''
)
$docUpdate
[
'doctor_name'
]
=
$doctorName
;
if
(
$clinicName
!==
''
)
$docUpdate
[
'clinic_name'
]
=
$clinicName
;
$db
->
update
(
'sa_player_documents'
,
$docUpdate
,
'id = ?'
,
[(
int
)
$latestDoc
[
'id'
]]);
}
return
$this
->
redirect
(
'/sa/players/'
.
$pid
)
->
withSuccess
(
'تم تحديث البيانات الطبية'
);
}
}
app/Modules/SportsActivity/Controllers/SubscriptionController.php
View file @
1be9b982
...
...
@@ -11,6 +11,7 @@ use App\Core\Pagination;
use
App\Modules\SportsActivity\Models\Subscription
;
use
App\Modules\SportsActivity\Models\Group
;
use
App\Modules\SportsActivity\Services\SubscriptionGeneratorService
;
use
App\Modules\SportsActivity\Services\SaPaymentService
;
class
SubscriptionController
extends
Controller
{
...
...
@@ -132,29 +133,46 @@ class SubscriptionController extends Controller
}
/**
*
Mark subscription as paid
.
*
Process subscription payment via PaymentService (creates receipt + accounting entry)
.
*/
public
function
pay
(
Request
$request
,
string
$id
)
:
Response
{
$db
=
App
::
getInstance
()
->
db
();
$paymentMethod
=
trim
((
string
)
$request
->
post
(
'payment_method'
,
'cash'
));
if
(
!
in_array
(
$paymentMethod
,
[
'cash'
,
'check'
,
'visa'
,
'bank_transfer'
],
true
))
{
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withError
(
'طريقة الدفع غير صالحة'
);
}
$subscription
=
$db
->
selectOne
(
"SELECT * FROM sa_subscriptions WHERE id = ?"
,
[(
int
)
$id
]
);
$extra
=
[
'check_number'
=>
$request
->
post
(
'check_number'
),
'check_bank'
=>
$request
->
post
(
'check_bank'
),
'check_date'
=>
$request
->
post
(
'check_date'
),
'visa_reference'
=>
$request
->
post
(
'visa_reference'
),
'transfer_reference'
=>
$request
->
post
(
'transfer_reference'
),
'transfer_bank'
=>
$request
->
post
(
'transfer_bank'
),
];
if
(
!
$subscription
)
{
return
$this
->
redirect
(
'/sa/subscriptions'
)
->
withError
(
'الاشتراك غير موجود'
);
$result
=
SaPaymentService
::
paySubscription
((
int
)
$id
,
$paymentMethod
,
$extra
);
if
(
!
$result
[
'success'
])
{
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withError
(
$result
[
'error'
]
??
'فشل تسجيل الدفع'
);
}
$db
->
update
(
'sa_subscriptions'
,
[
'payment_status'
=>
'paid'
,
'paid_at'
=>
date
(
'Y-m-d H:i:s'
),
'paid_amount'
=>
(
float
)
$subscription
[
'final_amount'
],
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
$id
]);
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withSuccess
(
'تم الدفع بنجاح — إيصال رقم: '
.
(
$result
[
'receipt_number'
]
??
''
));
}
/**
* Void a subscription payment.
*/
public
function
void
(
Request
$request
,
string
$id
)
:
Response
{
$result
=
SaPaymentService
::
voidSubscriptionPayment
((
int
)
$id
);
if
(
!
$result
[
'success'
])
{
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withError
(
$result
[
'error'
]
??
'فشل إلغاء الدفع'
);
}
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withSuccess
(
'تم
تسجيل
الدفع بنجاح'
);
return
$this
->
redirect
(
'/sa/subscriptions/'
.
$id
)
->
withSuccess
(
'تم
إلغاء
الدفع بنجاح'
);
}
/**
...
...
app/Modules/SportsActivity/Routes.php
View file @
1be9b982
...
...
@@ -56,8 +56,11 @@ return [
// Player Documents
[
'GET'
,
'/sa/players/{pid:\d+}/documents'
,
'SportsActivity\Controllers\PlayerDocumentController@index'
,
[
'auth'
],
'sa.player.view'
],
[
'POST'
,
'/sa/players/{pid:\d+}/documents'
,
'SportsActivity\Controllers\PlayerDocumentController@upload'
,
[
'auth'
,
'csrf'
],
'sa.player.manage'
],
[
'POST'
,
'/sa/players/{pid:\d+}/documents/upload'
,
'SportsActivity\Controllers\PlayerDocumentController@upload'
,
[
'auth'
,
'csrf'
],
'sa.player.manage'
],
[
'POST'
,
'/sa/players/{pid:\d+}/documents/{id:\d+}/approve'
,
'SportsActivity\Controllers\PlayerDocumentController@approve'
,
[
'auth'
,
'csrf'
],
'sa.medical.approve'
],
[
'POST'
,
'/sa/players/{pid:\d+}/documents/{id:\d+}/reject'
,
'SportsActivity\Controllers\PlayerDocumentController@reject'
,
[
'auth'
,
'csrf'
],
'sa.medical.approve'
],
[
'POST'
,
'/sa/players/{pid:\d+}/documents/{id:\d+}/approve-conditional'
,
'SportsActivity\Controllers\PlayerDocumentController@approveConditional'
,
[
'auth'
,
'csrf'
],
'sa.medical.approve'
],
[
'POST'
,
'/sa/players/{pid:\d+}/medical/update'
,
'SportsActivity\Controllers\PlayerDocumentController@updateMedical'
,
[
'auth'
,
'csrf'
],
'sa.medical.approve'
],
// Academies
[
'GET'
,
'/sa/academies'
,
'SportsActivity\Controllers\AcademyController@index'
,
[
'auth'
],
'sa.academy.view'
],
...
...
@@ -120,6 +123,7 @@ return [
[
'POST'
,
'/sa/subscriptions/generate'
,
'SportsActivity\Controllers\SubscriptionController@generate'
,
[
'auth'
,
'csrf'
],
'sa.subscription.generate'
],
[
'GET'
,
'/sa/subscriptions/{id:\d+}'
,
'SportsActivity\Controllers\SubscriptionController@show'
,
[
'auth'
],
'sa.subscription.view'
],
[
'POST'
,
'/sa/subscriptions/{id:\d+}/pay'
,
'SportsActivity\Controllers\SubscriptionController@pay'
,
[
'auth'
,
'csrf'
],
'sa.subscription.collect'
],
[
'POST'
,
'/sa/subscriptions/{id:\d+}/void'
,
'SportsActivity\Controllers\SubscriptionController@void'
,
[
'auth'
,
'csrf'
],
'sa.subscription.collect'
],
[
'POST'
,
'/sa/subscriptions/{id:\d+}/exempt'
,
'SportsActivity\Controllers\SubscriptionController@exempt'
,
[
'auth'
,
'csrf'
],
'sa.subscription.exempt'
],
// Attendance
...
...
app/Modules/SportsActivity/Services/SaPaymentService.php
0 → 100644
View file @
1be9b982
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\SportsActivity\Services
;
use
App\Core\App
;
use
App\Core\Logger
;
use
App\Modules\Payments\Services\PaymentService
;
final
class
SaPaymentService
{
public
static
function
paySubscription
(
int
$subscriptionId
,
string
$paymentMethod
,
array
$extra
=
[])
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$subscription
=
$db
->
selectOne
(
"SELECT s.*, p.full_name_ar as player_name, p.member_id, p.player_type,
g.name_ar as group_name
FROM sa_subscriptions s
LEFT JOIN sa_players p ON p.id = s.player_id
LEFT JOIN sa_groups g ON g.id = s.group_id
WHERE s.id = ?"
,
[
$subscriptionId
]
);
if
(
!
$subscription
)
{
return
[
'success'
=>
false
,
'error'
=>
'الاشتراك غير موجود'
];
}
if
(
$subscription
[
'payment_status'
]
===
'paid'
)
{
return
[
'success'
=>
false
,
'error'
=>
'الاشتراك مدفوع بالفعل'
];
}
$amount
=
(
string
)
(
$subscription
[
'final_amount'
]
??
'0.00'
);
if
(
bccomp
(
$amount
,
'0.01'
,
2
)
<
0
)
{
return
[
'success'
=>
false
,
'error'
=>
'مبلغ الاشتراك غير صالح'
];
}
$memberId
=
!
empty
(
$subscription
[
'member_id'
])
?
(
int
)
$subscription
[
'member_id'
]
:
null
;
$description
=
'اشتراك نشاط رياضي'
;
if
(
!
empty
(
$subscription
[
'group_name'
]))
{
$description
.=
' — '
.
$subscription
[
'group_name'
];
}
$paymentData
=
[
'member_id'
=>
$memberId
,
'amount'
=>
$amount
,
'payment_type'
=>
'activity_subscription'
,
'payment_method'
=>
$paymentMethod
,
'related_entity_type'
=>
'sa_subscriptions'
,
'related_entity_id'
=>
$subscriptionId
,
'description'
=>
$description
,
'check_number'
=>
$extra
[
'check_number'
]
??
null
,
'check_bank'
=>
$extra
[
'check_bank'
]
??
null
,
'check_date'
=>
$extra
[
'check_date'
]
??
null
,
'visa_reference'
=>
$extra
[
'visa_reference'
]
??
null
,
'transfer_reference'
=>
$extra
[
'transfer_reference'
]
??
null
,
'transfer_bank'
=>
$extra
[
'transfer_bank'
]
??
null
,
];
if
(
$memberId
===
null
&&
!
empty
(
$subscription
[
'player_name'
]))
{
$paymentData
[
'guest_name'
]
=
$subscription
[
'player_name'
];
}
$result
=
PaymentService
::
processPayment
(
$paymentData
);
if
(
!
$result
[
'success'
])
{
return
$result
;
}
$db
->
update
(
'sa_subscriptions'
,
[
'payment_status'
=>
'paid'
,
'paid_at'
=>
date
(
'Y-m-d H:i:s'
),
'paid_amount'
=>
(
float
)
$amount
,
'payment_id'
=>
$result
[
'payment_id'
],
'receipt_id'
=>
$result
[
'receipt_id'
],
'receipt_number'
=>
$result
[
'receipt_number'
],
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[
$subscriptionId
]);
return
[
'success'
=>
true
,
'payment_id'
=>
$result
[
'payment_id'
],
'receipt_id'
=>
$result
[
'receipt_id'
],
'receipt_number'
=>
$result
[
'receipt_number'
],
'amount'
=>
$amount
,
];
}
public
static
function
payBooking
(
int
$bookingId
,
string
$paymentMethod
,
array
$extra
=
[])
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$booking
=
$db
->
selectOne
(
"SELECT b.*, p.full_name_ar as booker_name_resolved, p.member_id, p.player_type
FROM sa_bookings b
LEFT JOIN sa_players p ON p.id = b.booker_id AND b.booker_type = 'player'
WHERE b.id = ?"
,
[
$bookingId
]
);
if
(
!
$booking
)
{
return
[
'success'
=>
false
,
'error'
=>
'الحجز غير موجود'
];
}
if
(
$booking
[
'payment_status'
]
===
'paid'
)
{
return
[
'success'
=>
false
,
'error'
=>
'الحجز مدفوع بالفعل'
];
}
$amount
=
(
string
)
(
$booking
[
'total_amount'
]
??
'0.00'
);
if
(
bccomp
(
$amount
,
'0.01'
,
2
)
<
0
)
{
return
[
'success'
=>
false
,
'error'
=>
'مبلغ الحجز غير صالح'
];
}
$memberId
=
!
empty
(
$booking
[
'member_id'
])
?
(
int
)
$booking
[
'member_id'
]
:
null
;
$paymentType
=
(
$booking
[
'booking_type'
]
===
'hourly'
)
?
'hourly_booking'
:
'activity_subscription'
;
$paymentData
=
[
'member_id'
=>
$memberId
,
'amount'
=>
$amount
,
'payment_type'
=>
$paymentType
,
'payment_method'
=>
$paymentMethod
,
'related_entity_type'
=>
'sa_bookings'
,
'related_entity_id'
=>
$bookingId
,
'description'
=>
'حجز رياضي — '
.
(
$booking
[
'booker_name'
]
??
$booking
[
'booker_name_resolved'
]
??
''
),
'check_number'
=>
$extra
[
'check_number'
]
??
null
,
'check_bank'
=>
$extra
[
'check_bank'
]
??
null
,
'check_date'
=>
$extra
[
'check_date'
]
??
null
,
'visa_reference'
=>
$extra
[
'visa_reference'
]
??
null
,
'transfer_reference'
=>
$extra
[
'transfer_reference'
]
??
null
,
'transfer_bank'
=>
$extra
[
'transfer_bank'
]
??
null
,
];
if
(
$memberId
===
null
&&
!
empty
(
$booking
[
'booker_name'
]))
{
$paymentData
[
'guest_name'
]
=
$booking
[
'booker_name'
];
}
$result
=
PaymentService
::
processPayment
(
$paymentData
);
if
(
!
$result
[
'success'
])
{
return
$result
;
}
$db
->
update
(
'sa_bookings'
,
[
'payment_status'
=>
'paid'
,
'payment_id'
=>
$result
[
'payment_id'
],
'receipt_id'
=>
$result
[
'receipt_id'
],
'receipt_number'
=>
$result
[
'receipt_number'
],
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[
$bookingId
]);
return
[
'success'
=>
true
,
'payment_id'
=>
$result
[
'payment_id'
],
'receipt_id'
=>
$result
[
'receipt_id'
],
'receipt_number'
=>
$result
[
'receipt_number'
],
'amount'
=>
$amount
,
];
}
public
static
function
voidSubscriptionPayment
(
int
$subscriptionId
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$subscription
=
$db
->
selectOne
(
"SELECT * FROM sa_subscriptions WHERE id = ?"
,
[
$subscriptionId
]
);
if
(
!
$subscription
)
{
return
[
'success'
=>
false
,
'error'
=>
'الاشتراك غير موجود'
];
}
$paymentId
=
(
int
)
(
$subscription
[
'payment_id'
]
??
0
);
if
(
$paymentId
<
1
)
{
return
[
'success'
=>
false
,
'error'
=>
'لا يوجد دفعة مرتبطة'
];
}
$result
=
PaymentService
::
voidPayment
(
$paymentId
,
'إلغاء اشتراك نشاط رياضي'
);
if
(
!
$result
[
'success'
])
{
return
$result
;
}
$db
->
update
(
'sa_subscriptions'
,
[
'payment_status'
=>
'unpaid'
,
'paid_at'
=>
null
,
'paid_amount'
=>
null
,
'payment_id'
=>
null
,
'receipt_id'
=>
null
,
'receipt_number'
=>
null
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[
$subscriptionId
]);
return
[
'success'
=>
true
];
}
}
app/Modules/SportsActivity/Views/players/documents.php
View file @
1be9b982
...
...
@@ -58,6 +58,9 @@ $approvalColors = ['pending' => '#D97706', 'approved' => '#059669', 'rejected' =
<?php
if
(
$aStatus
===
'approved'
&&
!
empty
(
$doc
[
'expiry_date'
]))
:
?>
<div
style=
"font-size:11px;color:#059669;margin-top:4px;"
>
صالح حتى:
<?=
e
(
$doc
[
'expiry_date'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
$aStatus
===
'approved'
&&
!
empty
(
$doc
[
'conditional_notes'
]))
:
?>
<div
style=
"font-size:11px;color:#F59E0B;margin-top:4px;"
>
مشروط:
<?=
e
(
$doc
[
'conditional_notes'
])
?>
</div>
<?php
endif
;
?>
</td>
<td
style=
"font-size:13px;"
>
<?=
e
(
$doc
[
'created_at'
]
??
''
)
?>
</td>
<td>
...
...
@@ -75,16 +78,33 @@ $approvalColors = ['pending' => '#D97706', 'approved' => '#059669', 'rejected' =
<form
method=
"POST"
action=
"/sa/players/
<?=
(
int
)
$player
[
'id'
]
?>
/documents/
<?=
(
int
)
$doc
[
'id'
]
?>
/approve"
style=
"display:inline-flex;gap:4px;align-items:center;"
>
<?=
csrf_field
()
?>
<input
type=
"date"
name=
"expiry_date"
class=
"form-input"
style=
"width:140px;height:30px;font-size:12px;"
required
title=
"تاريخ انتهاء الصلاحية"
>
<button
type=
"submit"
class=
"btn btn-sm"
style=
"background:#059669;color:#fff;padding:4px 8px;font-size:11px;"
title=
"اعتماد"
>
<button
type=
"submit"
class=
"btn btn-sm"
style=
"background:#059669;color:#fff;padding:4px 8px;font-size:11px;"
title=
"اعتماد
لائق
"
>
<i
data-lucide=
"check"
style=
"width:12px;height:12px;"
></i>
</button>
</form>
<!-- Reject Form -->
<!-- Conditional Approve Button -->
<button
type=
"button"
class=
"btn btn-sm"
style=
"background:#F59E0B;color:#fff;padding:4px 8px;font-size:11px;"
title=
"اعتماد مشروط"
onclick=
"document.getElementById('cond_form_
<?=
(
int
)
$doc
[
'id'
]
?>
').style.display='block'"
>
<i
data-lucide=
"alert-triangle"
style=
"width:12px;height:12px;"
></i>
</button>
<!-- Reject Button -->
<button
type=
"button"
class=
"btn btn-sm"
style=
"background:#DC2626;color:#fff;padding:4px 8px;font-size:11px;"
title=
"رفض"
onclick=
"document.getElementById('reject_form_
<?=
(
int
)
$doc
[
'id'
]
?>
').style.display='block'"
>
<i
data-lucide=
"x"
style=
"width:12px;height:12px;"
></i>
</button>
</div>
<!-- Hidden Conditional Approve Form -->
<form
method=
"POST"
action=
"/sa/players/
<?=
(
int
)
$player
[
'id'
]
?>
/documents/
<?=
(
int
)
$doc
[
'id'
]
?>
/approve-conditional"
id=
"cond_form_
<?=
(
int
)
$doc
[
'id'
]
?>
"
style=
"display:none;margin-top:8px;padding:10px;background:#FFFBEB;border-radius:6px;"
>
<?=
csrf_field
()
?>
<div
style=
"font-size:11px;color:#92400E;font-weight:600;margin-bottom:6px;"
>
اعتماد مشروط
</div>
<div
style=
"display:flex;gap:4px;flex-wrap:wrap;align-items:center;margin-bottom:6px;"
>
<input
type=
"date"
name=
"expiry_date"
class=
"form-input"
style=
"width:140px;height:30px;font-size:12px;"
required
title=
"تاريخ الانتهاء"
>
</div>
<div
style=
"margin-bottom:6px;"
>
<textarea
name=
"conditional_notes"
class=
"form-input"
style=
"width:100%;height:50px;font-size:12px;resize:vertical;"
placeholder=
"ملاحظات الاعتماد المشروط..."
required
></textarea>
</div>
<button
type=
"submit"
class=
"btn btn-sm"
style=
"background:#F59E0B;color:#fff;padding:4px 10px;font-size:11px;"
>
اعتماد مشروط
</button>
</form>
<!-- Hidden Reject Form -->
<form
method=
"POST"
action=
"/sa/players/
<?=
(
int
)
$player
[
'id'
]
?>
/documents/
<?=
(
int
)
$doc
[
'id'
]
?>
/reject"
id=
"reject_form_
<?=
(
int
)
$doc
[
'id'
]
?>
"
style=
"display:none;margin-top:8px;"
>
<?=
csrf_field
()
?>
...
...
app/Modules/SportsActivity/Views/players/show.php
View file @
1be9b982
...
...
@@ -120,6 +120,111 @@ $relationLabels = ['father' => 'أب', 'mother' => 'أم', 'brother' => 'أخ',
</div>
<?php
endif
;
?>
<!-- Medical History Timeline + Inline Edit -->
<div
class=
"card"
style=
"margin-bottom:20px;"
>
<div
style=
"padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;"
>
<h3
style=
"margin:0;font-size:16px;font-weight:600;"
>
<i
data-lucide=
"heart-pulse"
style=
"width:18px;height:18px;vertical-align:middle;margin-left:6px;color:#DC2626;"
></i>
السجل الطبي
</h3>
<?php
if
(
!
empty
(
$canApproveMedical
))
:
?>
<button
type=
"button"
class=
"btn btn-sm btn-outline"
onclick=
"document.getElementById('medicalEditForm').style.display = document.getElementById('medicalEditForm').style.display === 'none' ? 'block' : 'none';"
>
<i
data-lucide=
"edit-3"
style=
"width:14px;height:14px;vertical-align:middle;margin-left:4px;"
></i>
تعديل الحالة
</button>
<?php
endif
;
?>
</div>
<!-- Inline Medical Edit Form -->
<?php
if
(
!
empty
(
$canApproveMedical
))
:
?>
<div
id=
"medicalEditForm"
style=
"display:none;padding:20px;background:#F9FAFB;border-bottom:1px solid #E5E7EB;"
>
<form
method=
"POST"
action=
"/sa/players/
<?=
(
int
)
$player
[
'id'
]
?>
/medical/update"
>
<?=
csrf_field
()
?>
<div
style=
"display:grid;grid-template-columns:repeat(auto-fit, minmax(180px, 1fr));gap:12px;margin-bottom:12px;"
>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
الحالة الطبية
</label>
<select
name=
"medical_status"
class=
"form-input"
style=
"font-size:13px;"
>
<?php
foreach
(
$medLabels
as
$key
=>
$label
)
:
?>
<option
value=
"
<?=
$key
?>
"
<?=
(
$player
[
'medical_status'
]
??
''
)
===
$key
?
'selected'
:
''
?>
>
<?=
e
(
$label
)
?>
</option>
<?php
endforeach
;
?>
</select>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
تاريخ انتهاء الصلاحية
</label>
<input
type=
"date"
name=
"medical_expiry_date"
class=
"form-input"
value=
"
<?=
e
(
$player
[
'medical_expiry_date'
]
??
''
)
?>
"
style=
"font-size:13px;"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
تاريخ الكشف
</label>
<input
type=
"date"
name=
"exam_date"
class=
"form-input"
style=
"font-size:13px;"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
اسم الطبيب
</label>
<input
type=
"text"
name=
"doctor_name"
class=
"form-input"
placeholder=
"د. ..."
style=
"font-size:13px;"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
العيادة / المستشفى
</label>
<input
type=
"text"
name=
"clinic_name"
class=
"form-input"
placeholder=
"اسم العيادة"
style=
"font-size:13px;"
>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-sm btn-primary"
>
<i
data-lucide=
"save"
style=
"width:14px;height:14px;vertical-align:middle;margin-left:4px;"
></i>
حفظ التعديلات
</button>
</form>
</div>
<?php
endif
;
?>
<!-- Timeline -->
<?php
if
(
!
empty
(
$medicalHistory
))
:
?>
<div
style=
"padding:20px;"
>
<?php
$approvalLabels
=
[
'pending'
=>
'في الانتظار'
,
'approved'
=>
'معتمد'
,
'rejected'
=>
'مرفوض'
,
'expired'
=>
'منتهي'
];
$approvalColors
=
[
'pending'
=>
'#D97706'
,
'approved'
=>
'#059669'
,
'rejected'
=>
'#DC2626'
,
'expired'
=>
'#6B7280'
];
?>
<?php
foreach
(
$medicalHistory
as
$idx
=>
$doc
)
:
?>
<?php
$aStatus
=
$doc
[
'approval_status'
]
??
'pending'
;
$aColor
=
$approvalColors
[
$aStatus
]
??
'#6B7280'
;
?>
<div
style=
"display:flex;gap:16px;padding-bottom:16px;
<?=
$idx
<
count
(
$medicalHistory
)
-
1
?
'border-bottom:1px dashed #E5E7EB;margin-bottom:16px;'
:
''
?>
"
>
<div
style=
"width:10px;height:10px;border-radius:50%;background:
<?=
$aColor
?>
;margin-top:6px;flex-shrink:0;"
></div>
<div
style=
"flex:1;"
>
<div
style=
"display:flex;align-items:center;gap:8px;margin-bottom:6px;"
>
<span
style=
"padding:2px 8px;border-radius:8px;font-size:11px;font-weight:600;background:
<?=
$aColor
?>
15;color:
<?=
$aColor
?>
;"
>
<?=
e
(
$approvalLabels
[
$aStatus
]
??
$aStatus
)
?>
</span>
<span
style=
"font-size:12px;color:#9CA3AF;"
>
<?=
e
(
$doc
[
'created_at'
]
??
''
)
?>
</span>
</div>
<div
style=
"display:grid;grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));gap:8px;font-size:13px;color:#374151;"
>
<?php
if
(
!
empty
(
$doc
[
'exam_date'
]))
:
?>
<div><span
style=
"color:#9CA3AF;"
>
تاريخ الكشف:
</span>
<?=
e
(
$doc
[
'exam_date'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
!
empty
(
$doc
[
'expiry_date'
]))
:
?>
<div><span
style=
"color:#9CA3AF;"
>
ينتهي:
</span>
<?=
e
(
$doc
[
'expiry_date'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
!
empty
(
$doc
[
'doctor_name'
]))
:
?>
<div><span
style=
"color:#9CA3AF;"
>
الطبيب:
</span>
<?=
e
(
$doc
[
'doctor_name'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
!
empty
(
$doc
[
'clinic_name'
]))
:
?>
<div><span
style=
"color:#9CA3AF;"
>
العيادة:
</span>
<?=
e
(
$doc
[
'clinic_name'
])
?>
</div>
<?php
endif
;
?>
</div>
<?php
if
(
!
empty
(
$doc
[
'conditional_notes'
]))
:
?>
<div
style=
"margin-top:6px;padding:8px 12px;background:#FEF3C7;border-radius:6px;font-size:12px;color:#92400E;"
>
<strong>
ملاحظات مشروطة:
</strong>
<?=
e
(
$doc
[
'conditional_notes'
])
?>
</div>
<?php
endif
;
?>
<?php
if
(
!
empty
(
$doc
[
'rejection_reason'
]))
:
?>
<div
style=
"margin-top:6px;padding:8px 12px;background:#FEE2E2;border-radius:6px;font-size:12px;color:#991B1B;"
>
<strong>
سبب الرفض:
</strong>
<?=
e
(
$doc
[
'rejection_reason'
])
?>
</div>
<?php
endif
;
?>
</div>
</div>
<?php
endforeach
;
?>
</div>
<?php
else
:
?>
<div
style=
"padding:40px 20px;text-align:center;color:#9CA3AF;"
>
لا يوجد سجل طبي بعد.
</div>
<?php
endif
;
?>
</div>
<!-- Documents Section -->
<div
class=
"card"
>
<div
style=
"padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;"
>
...
...
@@ -151,13 +256,13 @@ $relationLabels = ['father' => 'أب', 'mother' => 'أم', 'brother' => 'أخ',
</td>
<td>
<?php
$approvalLabels
=
[
'pending'
=>
'في الانتظار'
,
'approved'
=>
'معتمد'
,
'rejected'
=>
'مرفوض
'
];
$approvalColors
=
[
'pending'
=>
'#D97706'
,
'approved'
=>
'#059669'
,
'rejected'
=>
'#DC2626
'
];
$aStatus
=
$doc
[
'approval_status'
]
??
'pending'
;
$aColor
=
$approvalColors
[
$aStatus
]
??
'#6B7280'
;
$approvalLabels
2
=
[
'pending'
=>
'في الانتظار'
,
'approved'
=>
'معتمد'
,
'rejected'
=>
'مرفوض'
,
'expired'
=>
'منتهي
'
];
$approvalColors
2
=
[
'pending'
=>
'#D97706'
,
'approved'
=>
'#059669'
,
'rejected'
=>
'#DC2626'
,
'expired'
=>
'#6B7280
'
];
$aStatus
2
=
$doc
[
'approval_status'
]
??
'pending'
;
$aColor
2
=
$approvalColors2
[
$aStatus2
]
??
'#6B7280'
;
?>
<span
style=
"padding:2px 8px;border-radius:8px;font-size:11px;font-weight:600;background:
<?=
$aColor
?>
15;color:
<?=
$aColor
?>
;"
>
<?=
e
(
$approvalLabels
[
$aStatus
]
??
$aStatus
)
?>
<span
style=
"padding:2px 8px;border-radius:8px;font-size:11px;font-weight:600;background:
<?=
$aColor
2
?>
15;color:
<?=
$aColor2
?>
;"
>
<?=
e
(
$approvalLabels
2
[
$aStatus2
]
??
$aStatus2
)
?>
</span>
</td>
<td
style=
"font-size:13px;"
>
<?=
e
(
$doc
[
'created_at'
]
??
''
)
?>
</td>
...
...
app/Modules/SportsActivity/Views/subscriptions/show.php
View file @
1be9b982
...
...
@@ -77,19 +77,110 @@ $statusStyle = $statusColors[$sub['payment_status'] ?? ''] ?? 'background:#F3F4F
</div>
</div>
<!-- Receipt Info (if paid) -->
<?php
if
(
$sub
[
'payment_status'
]
===
'paid'
&&
!
empty
(
$sub
[
'receipt_number'
]))
:
?>
<div
class=
"card"
style=
"margin-bottom:20px;padding:20px;background:#ECFDF5;border:1px solid #A7F3D0;"
>
<div
style=
"display:flex;align-items:center;justify-content:space-between;"
>
<div>
<div
style=
"font-size:13px;color:#065F46;font-weight:600;margin-bottom:4px;"
>
<i
data-lucide=
"check-circle"
style=
"width:16px;height:16px;vertical-align:middle;margin-left:4px;"
></i>
تم الدفع — إيصال رقم:
<?=
e
(
$sub
[
'receipt_number'
])
?>
</div>
<div
style=
"font-size:12px;color:#047857;"
>
بتاريخ
<?=
e
(
$sub
[
'paid_at'
]
??
''
)
?>
</div>
</div>
<div
style=
"display:flex;gap:8px;"
>
<?php
if
(
!
empty
(
$sub
[
'receipt_id'
]))
:
?>
<a
href=
"/receipts/
<?=
(
int
)
$sub
[
'receipt_id'
]
?>
/print"
target=
"_blank"
class=
"btn btn-sm btn-primary"
>
<i
data-lucide=
"printer"
style=
"width:14px;height:14px;vertical-align:middle;margin-left:4px;"
></i>
طباعة الإيصال
</a>
<?php
endif
;
?>
<form
method=
"POST"
action=
"/sa/subscriptions/
<?=
(
int
)
$sub
[
'id'
]
?>
/void"
style=
"display:inline;"
>
<?=
csrf_field
()
?>
<button
type=
"submit"
class=
"btn btn-sm btn-outline"
style=
"color:#DC2626;border-color:#DC2626;"
onclick=
"return confirm('هل أنت متأكد من إلغاء هذه الدفعة؟');"
>
<i
data-lucide=
"x-circle"
style=
"width:14px;height:14px;vertical-align:middle;margin-left:4px;"
></i>
إلغاء الدفع
</button>
</form>
</div>
</div>
</div>
<?php
endif
;
?>
<!-- Actions -->
<?php
if
(
in_array
(
$sub
[
'payment_status'
]
??
''
,
[
'unpaid'
,
'overdue'
,
'partial'
],
true
))
:
?>
<div
style=
"display:flex;gap:15px;flex-wrap:wrap;"
>
<!-- Pay Button -->
<div
class=
"card"
style=
"flex:1;min-width:280px;padding:20px;"
>
<h4
style=
"margin:0 0 15px;font-size:15px;"
>
تسجيل الدفع
</h4>
<form
method=
"POST"
action=
"/sa/subscriptions/
<?=
(
int
)
$sub
[
'id'
]
?>
/pay"
>
<!-- Payment Form -->
<div
class=
"card"
style=
"flex:2;min-width:360px;padding:20px;"
>
<h4
style=
"margin:0 0 15px;font-size:15px;"
>
<i
data-lucide=
"credit-card"
style=
"width:18px;height:18px;vertical-align:middle;margin-left:6px;"
></i>
تسجيل الدفع
</h4>
<form
method=
"POST"
action=
"/sa/subscriptions/
<?=
(
int
)
$sub
[
'id'
]
?>
/pay"
id=
"payForm"
>
<?=
csrf_field
()
?>
<p
style=
"color:#6B7280;font-size:13px;margin-bottom:15px;"
>
سيتم تسجيل دفع المبلغ الكامل:
<strong
>
<?=
money
((
float
)
(
$sub
[
'final_amount'
]
??
0
))
?>
</strong>
المبلغ المطلوب:
<strong
style=
"font-size:15px;color:#059669;"
>
<?=
money
((
float
)
(
$sub
[
'final_amount'
]
??
0
))
?>
</strong>
</p>
<button
type=
"submit"
class=
"btn btn-primary"
onclick=
"return confirm('تأكيد تسجيل الدفع؟');"
>
<i
data-lucide=
"check-circle"
style=
"width:16px;height:16px;vertical-align:middle;margin-left:4px;"
></i>
تأكيد الدفع
<!-- Payment Method -->
<div
style=
"margin-bottom:15px;"
>
<label
class=
"form-label"
style=
"font-size:12px;font-weight:600;"
>
طريقة الدفع
</label>
<div
style=
"display:flex;gap:8px;flex-wrap:wrap;margin-top:6px;"
>
<label
style=
"display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #E5E7EB;border-radius:8px;cursor:pointer;font-size:13px;transition:all .15s;"
class=
"method-option"
>
<input
type=
"radio"
name=
"payment_method"
value=
"cash"
checked
style=
"accent-color:#059669;"
>
نقدي
</label>
<label
style=
"display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #E5E7EB;border-radius:8px;cursor:pointer;font-size:13px;transition:all .15s;"
class=
"method-option"
>
<input
type=
"radio"
name=
"payment_method"
value=
"visa"
style=
"accent-color:#059669;"
>
فيزا
</label>
<label
style=
"display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #E5E7EB;border-radius:8px;cursor:pointer;font-size:13px;transition:all .15s;"
class=
"method-option"
>
<input
type=
"radio"
name=
"payment_method"
value=
"check"
style=
"accent-color:#059669;"
>
شيك
</label>
<label
style=
"display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #E5E7EB;border-radius:8px;cursor:pointer;font-size:13px;transition:all .15s;"
class=
"method-option"
>
<input
type=
"radio"
name=
"payment_method"
value=
"bank_transfer"
style=
"accent-color:#059669;"
>
تحويل بنكي
</label>
</div>
</div>
<!-- Check Fields -->
<div
id=
"checkFields"
style=
"display:none;margin-bottom:15px;padding:12px;background:#F9FAFB;border-radius:8px;"
>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:10px;"
>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
رقم الشيك
</label>
<input
type=
"text"
name=
"check_number"
class=
"form-input"
placeholder=
"رقم الشيك"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
البنك
</label>
<input
type=
"text"
name=
"check_bank"
class=
"form-input"
placeholder=
"اسم البنك"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
تاريخ الاستحقاق
</label>
<input
type=
"date"
name=
"check_date"
class=
"form-input"
>
</div>
</div>
</div>
<!-- Visa Field -->
<div
id=
"visaFields"
style=
"display:none;margin-bottom:15px;padding:12px;background:#F9FAFB;border-radius:8px;"
>
<label
class=
"form-label"
style=
"font-size:11px;"
>
رقم مرجع الفيزا
</label>
<input
type=
"text"
name=
"visa_reference"
class=
"form-input"
placeholder=
"Visa Reference"
>
</div>
<!-- Transfer Fields -->
<div
id=
"transferFields"
style=
"display:none;margin-bottom:15px;padding:12px;background:#F9FAFB;border-radius:8px;"
>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:10px;"
>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
رقم مرجع التحويل
</label>
<input
type=
"text"
name=
"transfer_reference"
class=
"form-input"
placeholder=
"Transfer Reference"
>
</div>
<div>
<label
class=
"form-label"
style=
"font-size:11px;"
>
البنك
</label>
<input
type=
"text"
name=
"transfer_bank"
class=
"form-input"
placeholder=
"اسم البنك"
>
</div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
style=
"width:100%;padding:10px;"
onclick=
"return confirm('تأكيد الدفع؟');"
>
<i
data-lucide=
"banknote"
style=
"width:16px;height:16px;vertical-align:middle;margin-left:4px;"
></i>
ادفع الآن
</button>
</form>
</div>
...
...
@@ -114,6 +205,27 @@ $statusStyle = $statusColors[$sub['payment_status'] ?? ''] ?? 'background:#F3F4F
<script>
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
if
(
typeof
lucide
!==
'undefined'
)
{
lucide
.
createIcons
();
}
const
radios
=
document
.
querySelectorAll
(
'input[name="payment_method"]'
);
const
checkFields
=
document
.
getElementById
(
'checkFields'
);
const
visaFields
=
document
.
getElementById
(
'visaFields'
);
const
transferFields
=
document
.
getElementById
(
'transferFields'
);
function
toggleFields
()
{
const
selected
=
document
.
querySelector
(
'input[name="payment_method"]:checked'
)?.
value
||
'cash'
;
checkFields
.
style
.
display
=
selected
===
'check'
?
'block'
:
'none'
;
visaFields
.
style
.
display
=
selected
===
'visa'
?
'block'
:
'none'
;
transferFields
.
style
.
display
=
selected
===
'bank_transfer'
?
'block'
:
'none'
;
document
.
querySelectorAll
(
'.method-option'
).
forEach
(
function
(
el
)
{
const
radio
=
el
.
querySelector
(
'input[type="radio"]'
);
el
.
style
.
borderColor
=
radio
.
checked
?
'#059669'
:
'#E5E7EB'
;
el
.
style
.
background
=
radio
.
checked
?
'#ECFDF5'
:
'#fff'
;
});
}
radios
.
forEach
(
function
(
r
)
{
r
.
addEventListener
(
'change'
,
toggleFields
);
});
toggleFields
();
});
</script>
<?php
$__template
->
endSection
();
?>
app/Modules/SportsActivity/bootstrap.php
View file @
1be9b982
...
...
@@ -3,6 +3,9 @@ declare(strict_types=1);
use
App\Core\Registries\MenuRegistry
;
use
App\Core\Registries\PermissionRegistry
;
use
App\Core\EventBus
;
use
App\Core\App
;
use
App\Core\Logger
;
MenuRegistry
::
register
(
'sports_activity'
,
[
'label_ar'
=>
'الأنشطة الرياضية'
,
...
...
@@ -71,3 +74,67 @@ PermissionRegistry::register('sports_activity', [
'sa.waitlist.manage'
=>
[
'ar'
=>
'إدارة قائمة الانتظار'
,
'en'
=>
'Manage Waitlist'
],
'sa.pool-grid.manage'
=>
[
'ar'
=>
'إدارة شبكة حمام السباحة'
,
'en'
=>
'Manage Pool Grid'
],
]);
// ─── Event Listeners ────────────────────────────────────────────────────────
EventBus
::
listen
(
'payment_request.completed'
,
function
(
array
$data
)
:
void
{
$entityType
=
$data
[
'related_entity_type'
]
??
''
;
try
{
$db
=
App
::
getInstance
()
->
db
();
if
(
$entityType
===
'sa_subscriptions'
)
{
$db
->
update
(
'sa_subscriptions'
,
[
'payment_status'
=>
'paid'
,
'paid_at'
=>
date
(
'Y-m-d H:i:s'
),
'paid_amount'
=>
$data
[
'amount'
]
??
0
,
'payment_id'
=>
$data
[
'payment_id'
]
??
null
,
'receipt_id'
=>
$data
[
'receipt_id'
]
??
null
,
'receipt_number'
=>
$data
[
'receipt_number'
]
??
null
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
(
$data
[
'related_entity_id'
]
??
0
)]);
}
elseif
(
$entityType
===
'sa_bookings'
)
{
$db
->
update
(
'sa_bookings'
,
[
'payment_status'
=>
'paid'
,
'payment_id'
=>
$data
[
'payment_id'
]
??
null
,
'receipt_id'
=>
$data
[
'receipt_id'
]
??
null
,
'receipt_number'
=>
$data
[
'receipt_number'
]
??
null
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
(
$data
[
'related_entity_id'
]
??
0
)]);
}
}
catch
(
\Throwable
$e
)
{
Logger
::
error
(
'SA payment_request.completed listener failed: '
.
$e
->
getMessage
());
}
},
60
);
EventBus
::
listen
(
'payment.voided'
,
function
(
array
$data
)
:
void
{
try
{
$db
=
App
::
getInstance
()
->
db
();
$paymentId
=
(
int
)
(
$data
[
'payment_id'
]
??
0
);
if
(
$paymentId
<
1
)
return
;
$sub
=
$db
->
selectOne
(
"SELECT id FROM sa_subscriptions WHERE payment_id = ?"
,
[
$paymentId
]);
if
(
$sub
)
{
$db
->
update
(
'sa_subscriptions'
,
[
'payment_status'
=>
'unpaid'
,
'paid_at'
=>
null
,
'paid_amount'
=>
null
,
'payment_id'
=>
null
,
'receipt_id'
=>
null
,
'receipt_number'
=>
null
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
$sub
[
'id'
]]);
}
$bk
=
$db
->
selectOne
(
"SELECT id FROM sa_bookings WHERE payment_id = ?"
,
[
$paymentId
]);
if
(
$bk
)
{
$db
->
update
(
'sa_bookings'
,
[
'payment_status'
=>
'unpaid'
,
'payment_id'
=>
null
,
'receipt_id'
=>
null
,
'receipt_number'
=>
null
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
$bk
[
'id'
]]);
}
}
catch
(
\Throwable
$e
)
{
Logger
::
error
(
'SA payment.voided listener failed: '
.
$e
->
getMessage
());
}
},
60
);
cron/jobs/SaMedicalExpiryJob.php
0 → 100644
View file @
1be9b982
<?php
declare
(
strict_types
=
1
);
namespace
CronJobs
;
use
App\Core\Database
;
use
App\Core\EventBus
;
use
App\Core\Logger
;
class
SaMedicalExpiryJob
{
private
Database
$db
;
public
function
__construct
(
Database
$db
)
{
$this
->
db
=
$db
;
}
public
function
shouldRun
()
:
bool
{
return
true
;
}
public
function
run
()
:
array
{
$today
=
date
(
'Y-m-d'
);
$thirtyDaysLater
=
date
(
'Y-m-d'
,
strtotime
(
'+30 days'
));
$notified
=
0
;
$expired
=
0
;
$docsExpired
=
0
;
$expiring
=
$this
->
db
->
select
(
"
SELECT id, full_name_ar, phone, medical_expiry_date
FROM sa_players
WHERE is_archived = 0
AND medical_expiry_date IS NOT NULL
AND medical_expiry_date BETWEEN ? AND ?
AND medical_status IN ('fit', 'conditional')
"
,
[
$today
,
$thirtyDaysLater
]);
foreach
(
$expiring
as
$player
)
{
EventBus
::
dispatch
(
'sa.player.medical_expiry_reminder'
,
[
'player_id'
=>
(
int
)
$player
[
'id'
],
'player_name'
=>
$player
[
'full_name_ar'
],
'phone'
=>
$player
[
'phone'
],
'expiry_date'
=>
$player
[
'medical_expiry_date'
],
]);
$notified
++
;
}
$expiredPlayers
=
$this
->
db
->
select
(
"
SELECT id FROM sa_players
WHERE is_archived = 0
AND medical_expiry_date IS NOT NULL
AND medical_expiry_date < ?
AND medical_status IN ('fit', 'conditional')
"
,
[
$today
]);
foreach
(
$expiredPlayers
as
$ep
)
{
$this
->
db
->
update
(
'sa_players'
,
[
'medical_status'
=>
'expired'
,
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
],
'id = ?'
,
[(
int
)
$ep
[
'id'
]]);
$expired
++
;
}
$stmt
=
$this
->
db
->
query
(
"
UPDATE sa_player_documents
SET approval_status = 'expired', updated_at = ?
WHERE document_type = 'medical_cert'
AND approval_status = 'approved'
AND expiry_date IS NOT NULL
AND expiry_date < ?
"
,
[
date
(
'Y-m-d H:i:s'
),
$today
]);
$docsExpired
=
$stmt
->
rowCount
();
Logger
::
info
(
"SA Medical expiry:
{
$notified
}
reminders,
{
$expired
}
players expired,
{
$docsExpired
}
docs expired"
);
return
[
'reminders_sent'
=>
$notified
,
'players_expired'
=>
$expired
,
'docs_expired'
=>
$docsExpired
];
}
}
database/migrations/Phase_70_027_add_medical_enhancements.php
0 → 100644
View file @
1be9b982
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"ALTER TABLE `sa_player_documents`
ADD COLUMN `conditional_notes` TEXT NULL AFTER `rejection_reason`;"
,
'down'
=>
"ALTER TABLE `sa_player_documents`
DROP COLUMN `conditional_notes`;"
,
];
database/migrations/Phase_70_028_add_payment_columns_to_sa.php
0 → 100644
View file @
1be9b982
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"ALTER TABLE `sa_subscriptions`
ADD COLUMN `payment_id` BIGINT UNSIGNED NULL AFTER `paid_amount`,
ADD COLUMN `receipt_id` BIGINT UNSIGNED NULL AFTER `payment_id`,
ADD COLUMN `receipt_number` VARCHAR(50) NULL AFTER `receipt_id`,
ADD INDEX `idx_sa_sub_payment` (`payment_id`);
ALTER TABLE `sa_bookings`
ADD COLUMN `payment_id` BIGINT UNSIGNED NULL AFTER `payment_status`,
ADD COLUMN `receipt_id` BIGINT UNSIGNED NULL AFTER `payment_id`,
ADD COLUMN `receipt_number` VARCHAR(50) NULL AFTER `receipt_id`,
ADD INDEX `idx_sa_bk_payment` (`payment_id`);"
,
'down'
=>
"ALTER TABLE `sa_subscriptions`
DROP INDEX `idx_sa_sub_payment`,
DROP COLUMN `receipt_number`,
DROP COLUMN `receipt_id`,
DROP COLUMN `payment_id`;
ALTER TABLE `sa_bookings`
DROP INDEX `idx_sa_bk_payment`,
DROP COLUMN `receipt_number`,
DROP COLUMN `receipt_id`,
DROP COLUMN `payment_id`;"
,
];
database/migrations/Phase_70_029_add_hourly_booking_account.php
0 → 100644
View file @
1be9b982
<?php
declare
(
strict_types
=
1
);
return
[
'up'
=>
"INSERT INTO `accounts` (`code`, `name_ar`, `name_en`, `parent_code`, `level`, `account_type`, `is_header`, `is_active`, `created_at`)
VALUES ('410503', 'إيراد حجز ساعي', 'Hourly Booking Revenue', '4105', 5, 'revenue', 0, 1, NOW())
ON DUPLICATE KEY UPDATE `name_ar` = VALUES(`name_ar`);"
,
'down'
=>
"DELETE FROM `accounts` WHERE `code` = '410503';"
,
];
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