Commit a6123dbb authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(sports): refactor registration wizard + add coach assessment + swimming coaches

- Registration wizard simplified: flat 100 EGP per person (no card/form fees)
- Desk flow: enter data → pay → photo → select disciplines → print form
- Group assignment removed from registration (done by coaches now)
- New Coach Assessment Wizard at /sa/coach-assessment for skill evaluation
- New Swimming Coaches section at /sa/swimming/coaches (freelance-focused)
- Added assessment columns (skill_level, notes, assessed_at) to sa_group_players
- Added selected_disciplines JSON to sa_registrations
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 4dc3ccbf
This diff is collapsed.
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\SportsActivity\Services\CoachAssessmentService;
class CoachAssessmentController extends Controller
{
public function index(Request $request): Response
{
$players = CoachAssessmentService::getPendingPlayers();
$disciplines = App::getInstance()->db()->select(
"SELECT id, name_ar FROM sa_disciplines WHERE is_active = 1 AND is_archived = 0 ORDER BY name_ar"
);
return $this->view('SportsActivity.Views.assessment.index', [
'players' => $players,
'disciplines' => $disciplines,
]);
}
public function assess(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$registrationId = (int) $id;
$registration = $db->selectOne(
"SELECT r.*, p.full_name_ar, p.full_name_en, p.national_id as player_nid,
p.date_of_birth, p.gender, p.phone, p.photo_path, p.player_type as p_type,
p.medical_status, r.selected_disciplines
FROM sa_registrations r
INNER JOIN sa_players p ON p.id = r.player_id
WHERE r.id = ?",
[$registrationId]
);
if (!$registration) {
return $this->redirect('/sa/coach-assessment')->withError('التسجيل غير موجود');
}
$groups = CoachAssessmentService::getAvailableGroups();
return $this->view('SportsActivity.Views.assessment.assess', [
'registration' => $registration,
'groups' => $groups,
]);
}
public function submit(Request $request, string $id): Response
{
$registrationId = (int) $id;
$groupId = (int) $request->post('group_id', 0);
$skillLevel = trim((string) $request->post('skill_level', ''));
$notes = trim((string) $request->post('assessment_notes', ''));
if ($groupId === 0) {
return $this->redirect('/sa/coach-assessment/' . $id)->withError('يجب اختيار المجموعة');
}
if ($skillLevel === '') {
return $this->redirect('/sa/coach-assessment/' . $id)->withError('يجب تحديد المستوى');
}
$employee = App::getInstance()->currentEmployee();
$assessedBy = $employee ? (int) $employee->id : 0;
$result = CoachAssessmentService::assess($registrationId, $groupId, $skillLevel, $notes, $assessedBy);
if (!$result['success']) {
return $this->redirect('/sa/coach-assessment/' . $id)->withError($result['error']);
}
return $this->redirect('/sa/coach-assessment')
->withSuccess('تم تقييم اللاعب وتعيينه في مجموعة "' . $result['group_name'] . '" بنجاح');
}
}
......@@ -126,32 +126,13 @@ class RegistrationWizardController extends Controller
return $this->redirect('/sa/registration')->withError('التسجيل غير موجود');
}
$groups = $db->select(
"SELECT g.*, p.name_ar as program_name, p.discipline_id, d.name_ar as discipline_name,
p.monthly_fee_member, p.monthly_fee_nonmember
FROM sa_groups g
LEFT JOIN sa_programs p ON p.id = g.program_id
LEFT JOIN sa_disciplines d ON d.id = p.discipline_id
WHERE g.status = 'active' AND g.is_archived = 0 AND g.is_full = 0
ORDER BY d.name_ar ASC, g.name_ar ASC"
);
$selectedGroup = null;
if ($registration['group_id']) {
$selectedGroup = $db->selectOne(
"SELECT g.*, p.name_ar as program_name, d.name_ar as discipline_name
FROM sa_groups g
LEFT JOIN sa_programs p ON p.id = g.program_id
LEFT JOIN sa_disciplines d ON d.id = p.discipline_id
WHERE g.id = ?",
[(int) $registration['group_id']]
$disciplines = $db->select(
"SELECT id, name_ar, icon FROM sa_disciplines WHERE is_active = 1 AND is_archived = 0 ORDER BY sort_order ASC, name_ar ASC"
);
}
return $this->view('SportsActivity.Views.registration.wizard', [
'registration' => $registration,
'groups' => $groups,
'selectedGroup' => $selectedGroup,
'disciplines' => $disciplines,
'step' => $this->determineStep($registration),
]);
}
......@@ -187,19 +168,17 @@ class RegistrationWizardController extends Controller
public function selectActivity(Request $request, string $id): Response
{
$registrationId = (int) $id;
$programId = (int) $request->post('program_id', 0);
$groupId = (int) $request->post('group_id', 0);
$months = max(1, (int) $request->post('months', 1));
$hasSibling = (bool) $request->post('has_sibling', false);
if ($programId > 0) {
$result = RegistrationWizardService::selectProgram($registrationId, $programId, $months, $hasSibling);
} elseif ($groupId > 0) {
$result = RegistrationWizardService::selectGroup($registrationId, $groupId, $months, $hasSibling);
} else {
return $this->json(['success' => false, 'error' => 'اختر برنامج أو مجموعة']);
$disciplineIds = $request->post('discipline_ids', []);
if (is_string($disciplineIds)) {
$disciplineIds = json_decode($disciplineIds, true) ?: [];
}
if (empty($disciplineIds)) {
return $this->json(['success' => false, 'error' => 'يجب اختيار نشاط واحد على الأقل']);
}
$result = RegistrationWizardService::saveSelectedDisciplines($registrationId, $disciplineIds);
return $this->json($result);
}
......@@ -402,18 +381,14 @@ class RegistrationWizardController extends Controller
private function determineStep(array $registration): int
{
// Step 1: Pay form fee (استمارة اشتراك)
// Step 1: Pay 100 EGP (single registration fee)
// Step 2: Photo capture
// Step 3: Select activity/group
// Step 4: Pay subscription
// Step 5: Complete (print/card)
if ($registration['status'] === 'completed' || $registration['payment_status'] === 'paid') {
return 5;
}
if ($registration['status'] === 'pending_payment') {
// Step 3: Select disciplines of interest
// Step 4: Complete (print form + generate card)
if ($registration['status'] === 'completed' || ($registration['payment_status'] ?? '') === 'paid') {
return 4;
}
if (!empty($registration['group_id'])) {
if (!empty($registration['selected_disciplines'])) {
return 4;
}
if ((int) $registration['photo_captured'] === 1) {
......
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Controllers\Swimming;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
class SwimmingCoachController extends Controller
{
public function index(Request $request): Response
{
$db = App::getInstance()->db();
$search = trim((string) $request->get('search', ''));
$filter = trim((string) $request->get('filter', ''));
$sql = "SELECT c.*, cd.specialization_level,
(SELECT COUNT(*) FROM sa_group_coaches gc
INNER JOIN sa_groups g ON g.id = gc.group_id AND g.status = 'active'
WHERE gc.coach_id = c.id) as active_groups
FROM sa_coaches c
INNER JOIN sa_coach_disciplines cd ON cd.coach_id = c.id
INNER JOIN sa_disciplines d ON d.id = cd.discipline_id
WHERE d.code = 'SWIMMING' AND c.is_active = 1 AND c.is_archived = 0";
$params = [];
if ($search !== '') {
$sql .= " AND (c.full_name_ar LIKE ? OR c.phone LIKE ? OR c.code LIKE ?)";
$params[] = "%{$search}%";
$params[] = "%{$search}%";
$params[] = "%{$search}%";
}
if ($filter !== '' && in_array($filter, ['freelance', 'staff', 'contract'])) {
$sql .= " AND c.employment_type = ?";
$params[] = $filter;
}
$sql .= " ORDER BY c.full_name_ar ASC";
$coaches = $db->select($sql, $params);
return $this->view('SportsActivity.Views.swimming.coaches.index', [
'coaches' => $coaches,
'search' => $search,
'filter' => $filter,
]);
}
public function create(Request $request): Response
{
return $this->view('SportsActivity.Views.swimming.coaches.form', [
'coach' => null,
]);
}
public function store(Request $request): Response
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$fullNameAr = trim((string) $request->post('full_name_ar', ''));
$fullNameEn = trim((string) $request->post('full_name_en', ''));
$nationalId = trim((string) $request->post('national_id', ''));
$phone = trim((string) $request->post('phone', ''));
$employmentType = trim((string) $request->post('employment_type', 'freelance'));
if ($fullNameAr === '') {
return $this->redirect('/sa/swimming/coaches/create')->withError('الاسم بالعربي مطلوب');
}
if ($nationalId !== '') {
$existing = $db->selectOne(
"SELECT id FROM sa_coaches WHERE national_id = ? AND is_archived = 0",
[$nationalId]
);
if ($existing) {
return $this->redirect('/sa/swimming/coaches/create')
->withError('يوجد مدرب بنفس الرقم القومي — كود: ' . ($existing['code'] ?? $existing['id']));
}
}
$code = self::generateCode($db);
$coachId = $db->insert('sa_coaches', [
'code' => $code,
'full_name_ar' => $fullNameAr,
'full_name_en' => $fullNameEn ?: null,
'national_id' => $nationalId ?: null,
'phone' => $phone ?: null,
'coach_type' => 'independent',
'employment_type' => $employmentType,
'is_active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $employee ? (int) $employee->id : null,
]);
$swimmingDiscipline = $db->selectOne(
"SELECT id FROM sa_disciplines WHERE code = 'SWIMMING' AND is_archived = 0"
);
if ($swimmingDiscipline) {
$db->insert('sa_coach_disciplines', [
'coach_id' => $coachId,
'discipline_id' => (int) $swimmingDiscipline['id'],
'specialization_level' => 'primary',
'created_at' => date('Y-m-d H:i:s'),
]);
}
return $this->redirect('/sa/swimming/coaches')->withSuccess('تم إضافة مدرب السباحة "' . $fullNameAr . '" بنجاح');
}
public function show(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$coach = $db->selectOne(
"SELECT c.* FROM sa_coaches c WHERE c.id = ? AND c.is_archived = 0",
[(int) $id]
);
if (!$coach) {
return $this->redirect('/sa/swimming/coaches')->withError('المدرب غير موجود');
}
$laneBookings = $db->select(
"SELECT zb.*, f.name_ar as facility_name
FROM sa_pool_zone_bookings zb
LEFT JOIN sa_facilities f ON f.id = zb.facility_id
WHERE zb.coach_id = ?
ORDER BY zb.booking_date DESC, zb.start_time DESC
LIMIT 20",
[(int) $id]
);
$groups = $db->select(
"SELECT g.name_ar, g.code, gc.role
FROM sa_group_coaches gc
INNER JOIN sa_groups g ON g.id = gc.group_id AND g.status = 'active'
WHERE gc.coach_id = ?",
[(int) $id]
);
return $this->view('SportsActivity.Views.swimming.coaches.show', [
'coach' => $coach,
'laneBookings' => $laneBookings,
'groups' => $groups,
]);
}
public function edit(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$coach = $db->selectOne(
"SELECT * FROM sa_coaches WHERE id = ? AND is_archived = 0",
[(int) $id]
);
if (!$coach) {
return $this->redirect('/sa/swimming/coaches')->withError('المدرب غير موجود');
}
return $this->view('SportsActivity.Views.swimming.coaches.form', [
'coach' => $coach,
]);
}
public function update(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$coach = $db->selectOne("SELECT * FROM sa_coaches WHERE id = ? AND is_archived = 0", [(int) $id]);
if (!$coach) {
return $this->redirect('/sa/swimming/coaches')->withError('المدرب غير موجود');
}
$fullNameAr = trim((string) $request->post('full_name_ar', ''));
$fullNameEn = trim((string) $request->post('full_name_en', ''));
$phone = trim((string) $request->post('phone', ''));
$employmentType = trim((string) $request->post('employment_type', 'freelance'));
if ($fullNameAr === '') {
return $this->redirect('/sa/swimming/coaches/' . $id . '/edit')->withError('الاسم بالعربي مطلوب');
}
$db->update('sa_coaches', [
'full_name_ar' => $fullNameAr,
'full_name_en' => $fullNameEn ?: null,
'phone' => $phone ?: null,
'employment_type' => $employmentType,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [(int) $id]);
return $this->redirect('/sa/swimming/coaches')->withSuccess('تم تحديث بيانات المدرب بنجاح');
}
private static function generateCode($db): string
{
$row = $db->selectOne(
"SELECT MAX(CAST(SUBSTRING(code, 12) AS UNSIGNED)) as max_num FROM sa_coaches WHERE code LIKE 'SWIM-COACH-%'"
);
$next = ((int) ($row['max_num'] ?? 0)) + 1;
return 'SWIM-COACH-' . str_pad((string) $next, 3, '0', STR_PAD_LEFT);
}
}
......@@ -310,8 +310,19 @@ return [
['POST', '/api/sa/subscriptions/pause', 'SportsActivity\Controllers\Api\SubscriptionPreviewApiController@pause', ['auth', 'csrf'], 'sa.subscription.generate'],
['POST', '/api/sa/subscriptions/unpause', 'SportsActivity\Controllers\Api\SubscriptionPreviewApiController@unpause', ['auth', 'csrf'], 'sa.subscription.generate'],
// ─── Coach Assessment Wizard ───────────────────────────────────────────────
['GET', '/sa/coach-assessment', 'SportsActivity\Controllers\CoachAssessmentController@index', ['auth'], 'sa.coach_assessment.view'],
['GET', '/sa/coach-assessment/{id:\d+}', 'SportsActivity\Controllers\CoachAssessmentController@assess', ['auth'], 'sa.coach_assessment.manage'],
['POST', '/sa/coach-assessment/{id:\d+}/submit', 'SportsActivity\Controllers\CoachAssessmentController@submit', ['auth', 'csrf'], 'sa.coach_assessment.manage'],
// ─── Swimming Section ───────────────────────────────────────────────────────
['GET', '/sa/swimming', 'SportsActivity\Controllers\Swimming\SwimmingDashboardController@index', ['auth'], 'sa.swimming.dashboard'],
['GET', '/sa/swimming/coaches', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@index', ['auth'], 'sa.swimming.coach_manage'],
['GET', '/sa/swimming/coaches/create', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@create', ['auth'], 'sa.swimming.coach_manage'],
['POST', '/sa/swimming/coaches', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@store', ['auth', 'csrf'], 'sa.swimming.coach_manage'],
['GET', '/sa/swimming/coaches/{id:\d+}', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@show', ['auth'], 'sa.swimming.coach_manage'],
['GET', '/sa/swimming/coaches/{id:\d+}/edit', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@edit', ['auth'], 'sa.swimming.coach_manage'],
['POST', '/sa/swimming/coaches/{id:\d+}', 'SportsActivity\Controllers\Swimming\SwimmingCoachController@update', ['auth', 'csrf'], 'sa.swimming.coach_manage'],
['GET', '/sa/swimming/register', 'SportsActivity\Controllers\Swimming\SwimmingRegistrationController@index', ['auth'], 'sa.swimming.register'],
['POST', '/sa/swimming/register/lookup', 'SportsActivity\Controllers\Swimming\SwimmingRegistrationController@lookup', ['auth', 'csrf'], 'sa.swimming.register'],
['GET', '/sa/swimming/register/{id:\d+}', 'SportsActivity\Controllers\Swimming\SwimmingRegistrationController@step', ['auth'], 'sa.swimming.register'],
......
<?php
declare(strict_types=1);
namespace App\Modules\SportsActivity\Services;
use App\Core\App;
use App\Core\EventBus;
use App\Modules\Cashier\Services\PaymentRequestService;
final class CoachAssessmentService
{
public static function getPendingPlayers(?int $coachId = null): array
{
$db = App::getInstance()->db();
$sql = "SELECT r.id as registration_id, r.registration_number, r.selected_disciplines,
r.player_type, r.created_at as registration_date,
p.id as player_id, p.full_name_ar, p.full_name_en, p.national_id,
p.date_of_birth, p.gender, p.phone, p.photo_path, p.medical_status
FROM sa_registrations r
INNER JOIN sa_players p ON p.id = r.player_id
WHERE r.status = 'completed' AND r.payment_status = 'paid'
AND NOT EXISTS (
SELECT 1 FROM sa_group_players gp
WHERE gp.player_id = r.player_id AND gp.status IN ('active','pending_payment')
)
ORDER BY r.created_at DESC";
return $db->select($sql);
}
public static function getAvailableGroups(?int $disciplineId = null): array
{
$db = App::getInstance()->db();
$sql = "SELECT g.id, g.name_ar, g.code, g.current_count, g.max_capacity, g.is_full,
p.id as program_id, p.name_ar as program_name,
p.monthly_fee_member, p.monthly_fee_nonmember,
d.id as discipline_id, d.name_ar as discipline_name,
c.full_name_ar as coach_name
FROM sa_groups g
LEFT JOIN sa_programs p ON p.id = g.program_id
LEFT JOIN sa_disciplines d ON d.id = p.discipline_id
LEFT JOIN sa_coaches c ON c.id = g.coach_id
WHERE g.status = 'active' AND g.is_archived = 0 AND g.is_full = 0";
$params = [];
if ($disciplineId) {
$sql .= " AND d.id = ?";
$params[] = $disciplineId;
}
$sql .= " ORDER BY d.name_ar ASC, g.name_ar ASC";
return $db->select($sql, $params);
}
public static function assess(int $registrationId, int $groupId, string $skillLevel, string $notes, int $assessedBy): array
{
$db = App::getInstance()->db();
$employee = App::getInstance()->currentEmployee();
$registration = $db->selectOne(
"SELECT r.*, p.full_name_ar, p.player_type, p.member_id
FROM sa_registrations r
INNER JOIN sa_players p ON p.id = r.player_id
WHERE r.id = ? AND r.payment_status = 'paid'",
[$registrationId]
);
if (!$registration) {
return ['success' => false, 'error' => 'التسجيل غير موجود أو لم يتم الدفع'];
}
$playerId = (int) $registration['player_id'];
$existing = $db->selectOne(
"SELECT id FROM sa_group_players WHERE group_id = ? AND player_id = ? AND status IN ('active','pending_payment')",
[$groupId, $playerId]
);
if ($existing) {
return ['success' => false, 'error' => 'اللاعب مسجل بالفعل في هذه المجموعة'];
}
$group = $db->selectOne(
"SELECT g.*, p.monthly_fee_member, p.monthly_fee_nonmember, p.name_ar as program_name
FROM sa_groups g
LEFT JOIN sa_programs p ON p.id = g.program_id
WHERE g.id = ? AND g.status = 'active' AND g.is_archived = 0",
[$groupId]
);
if (!$group) {
return ['success' => false, 'error' => 'المجموعة غير موجودة أو غير نشطة'];
}
if ((int) $group['current_count'] >= (int) $group['max_capacity']) {
return ['success' => false, 'error' => 'المجموعة ممتلئة — السعة القصوى ' . $group['max_capacity']];
}
$playerType = $registration['player_type'] ?? 'non_member';
$monthlyFee = $playerType === 'member'
? (float) ($group['monthly_fee_member'] ?? 0)
: (float) ($group['monthly_fee_nonmember'] ?? 0);
$db->beginTransaction();
try {
$enrollmentId = $db->insert('sa_group_players', [
'group_id' => $groupId,
'player_id' => $playerId,
'enrolled_at' => date('Y-m-d'),
'status' => 'active',
'assessed_by' => $assessedBy,
'assessment_notes' => $notes ?: null,
'skill_level' => $skillLevel,
'assessed_at' => date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'created_by' => $employee ? (int) $employee->id : $assessedBy,
]);
$newCount = (int) $group['current_count'] + 1;
$db->update('sa_groups', [
'current_count' => $newCount,
'is_full' => $newCount >= (int) $group['max_capacity'] ? 1 : 0,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$groupId]);
$db->update('sa_registrations', [
'group_id' => $groupId,
'status' => 'assessed',
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$registrationId]);
if ($monthlyFee > 0) {
$periodStart = date('Y-m-01');
$periodEnd = date('Y-m-t');
$db->insert('sa_subscriptions', [
'subscription_number' => self::generateSubNumber(),
'player_id' => $playerId,
'group_id' => $groupId,
'period_start' => $periodStart,
'period_end' => $periodEnd,
'amount' => $monthlyFee,
'final_amount' => $monthlyFee,
'payment_status' => 'unpaid',
'auto_generated' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
return ['success' => false, 'error' => 'فشل التقييم: ' . $e->getMessage()];
}
EventBus::dispatch('sa.player.assessed', [
'registration_id' => $registrationId,
'player_id' => $playerId,
'group_id' => $groupId,
'skill_level' => $skillLevel,
'assessed_by' => $assessedBy,
]);
return [
'success' => true,
'enrollment_id' => $enrollmentId,
'group_name' => $group['name_ar'],
'monthly_fee' => $monthlyFee,
];
}
private static function generateSubNumber(): string
{
$db = App::getInstance()->db();
$prefix = 'SUB-' . date('Y') . '-';
$row = $db->selectOne(
"SELECT MAX(CAST(SUBSTRING(subscription_number, " . (strlen($prefix) + 1) . ") AS UNSIGNED)) as max_num
FROM sa_subscriptions WHERE subscription_number LIKE ?",
[$prefix . '%']
);
$next = ((int) ($row['max_num'] ?? 0)) + 1;
return $prefix . str_pad((string) $next, 6, '0', STR_PAD_LEFT);
}
}
......@@ -119,10 +119,10 @@ final class RegistrationWizardService
'national_id' => $nationalId ?: null,
'status' => 'in_progress',
'registration_fee' => $formAlreadyPaid ? 0 : $fees['registration_fee'],
'card_fee' => $fees['card_fee'],
'form_fee' => $fees['form_fee'],
'total_fees' => ($formAlreadyPaid ? 0 : $fees['registration_fee']) + $fees['card_fee'] + $fees['form_fee'],
'form_payment_status' => $formAlreadyPaid ? 'paid' : 'unpaid',
'card_fee' => 0,
'form_fee' => 0,
'total_fees' => $formAlreadyPaid ? 0 : $fees['total_fees'],
'form_payment_status' => 'unpaid',
'branch_id' => $branch ? (int) $branch['id'] : null,
'created_by' => $employee ? (int) $employee->id : null,
'created_at' => date('Y-m-d H:i:s'),
......@@ -280,23 +280,11 @@ final class RegistrationWizardService
public static function calculateFees(string $playerType): array
{
$db = App::getInstance()->db();
$regFeeKey = $playerType === 'member' ? 'sa.registration_fee_member' : 'sa.registration_fee_nonmember';
$regFeeRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", [$regFeeKey]);
$cardFeeRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['sa.card_fee']);
$formFeeRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['sa.form_fee']);
$registrationFee = (float) ($regFeeRow['config_value'] ?? ($playerType === 'member' ? '50.00' : '100.00'));
$cardFee = (float) ($cardFeeRow['config_value'] ?? '25.00');
$formFee = (float) ($formFeeRow['config_value'] ?? '10.00');
return [
'registration_fee' => $registrationFee,
'card_fee' => $cardFee,
'form_fee' => $formFee,
'total_fees' => $registrationFee + $cardFee + $formFee,
'registration_fee' => 100.0,
'card_fee' => 0.0,
'form_fee' => 0.0,
'total_fees' => 100.0,
];
}
......@@ -319,20 +307,20 @@ final class RegistrationWizardService
return ['success' => true, 'already_paid' => true];
}
$formFee = (float) $registration['registration_fee'];
if ($formFee <= 0) {
return ['success' => false, 'error' => 'رسوم الاستمارة غير محددة'];
$totalFee = (float) $registration['total_fees'];
if ($totalFee <= 0) {
$totalFee = 100.0;
}
$memberId = (int) ($registration['member_id'] ?? 0);
$description = 'استمارة اشتراك نشاط رياضي — ' . ($registration['full_name_ar'] ?? '');
$description = 'رسوم تسجيل نشاط رياضي (100 ج.م) — ' . ($registration['full_name_ar'] ?? '');
$result = PaymentRequestService::createRequest([
'member_id' => $memberId,
'payment_type' => 'sa_form_fee',
'amount' => (string) $formFee,
'payment_type' => 'sa_registration_fee',
'amount' => (string) $totalFee,
'description_ar' => $description,
'related_entity_type' => 'sa_registration_form',
'related_entity_type' => 'sa_registrations',
'related_entity_id' => $registrationId,
]);
......@@ -350,70 +338,46 @@ final class RegistrationWizardService
'success' => true,
'request_id' => $result['request_id'],
'request_number' => $result['request_number'],
'amount' => $formFee,
'amount' => $totalFee,
];
}
public static function submitToPaymentQueue(int $registrationId): array
public static function saveSelectedDisciplines(int $registrationId, array $disciplineIds): array
{
$db = App::getInstance()->db();
$registration = $db->selectOne(
"SELECT r.*, p.full_name_ar, p.member_id
FROM sa_registrations r
INNER JOIN sa_players p ON p.id = r.player_id
WHERE r.id = ? AND r.status = 'in_progress'",
"SELECT * FROM sa_registrations WHERE id = ? AND status = 'in_progress'",
[$registrationId]
);
if (!$registration) {
return ['success' => false, 'error' => 'التسجيل غير موجود أو مكتمل'];
}
if ((int) $registration['photo_captured'] === 0) {
return ['success' => false, 'error' => 'يجب التقاط الصورة أولاً'];
if (empty($disciplineIds)) {
return ['success' => false, 'error' => 'يجب اختيار نشاط واحد على الأقل'];
}
if (empty($registration['group_id'])) {
return ['success' => false, 'error' => 'يجب اختيار النشاط أولاً'];
}
$subscriptionAmount = (float) ($registration['subscription_amount'] ?? 0);
$cardFee = (float) $registration['card_fee'];
$formFee = (float) $registration['form_fee'];
$totalSubscription = $subscriptionAmount + $cardFee + $formFee;
if ($totalSubscription <= 0) {
return ['success' => false, 'error' => 'إجمالي الرسوم غير صالح'];
}
$memberId = (int) ($registration['member_id'] ?? 0);
$description = 'اشتراك نشاط رياضي — ' . ($registration['full_name_ar'] ?? '');
$result = PaymentRequestService::createRequest([
'member_id' => $memberId,
'payment_type' => 'sports_subscription',
'amount' => (string) $totalSubscription,
'description_ar' => $description,
'related_entity_type' => 'sa_registrations',
'related_entity_id' => $registrationId,
]);
$disciplines = $db->select(
"SELECT id, name_ar FROM sa_disciplines WHERE id IN (" . implode(',', array_map('intval', $disciplineIds)) . ") AND is_active = 1 AND is_archived = 0"
);
if (!$result['success']) {
return $result;
$selected = [];
foreach ($disciplines as $d) {
$selected[] = ['id' => (int) $d['id'], 'name' => $d['name_ar']];
}
$db->update('sa_registrations', [
'status' => 'pending_payment',
'payment_request_id' => (int) $result['request_id'],
'payment_status' => 'pending',
'selected_disciplines' => json_encode($selected, JSON_UNESCAPED_UNICODE),
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$registrationId]);
return [
'success' => true,
'request_id' => $result['request_id'],
'request_number' => $result['request_number'],
'amount' => $totalSubscription,
];
return ['success' => true, 'selected' => $selected];
}
public static function submitToPaymentQueue(int $registrationId): array
{
return ['success' => false, 'error' => 'تم إلغاء هذه الخطوة — الدفع يتم في خطوة الاستمارة'];
}
public static function generateCard(int $registrationId): array
......
This diff is collapsed.
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>تقييم اللاعبين — المدربين<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div class="card" style="margin-bottom:16px;">
<div style="padding:16px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="clipboard-check" style="width:20px;height:20px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:16px;">لاعبين في انتظار التقييم الفني</h3>
<span style="margin-right:auto;background:#FEF3C7;color:#92400E;padding:3px 10px;border-radius:12px;font-size:12px;font-weight:700;"><?= count($players) ?></span>
</div>
<?php if (empty($players)): ?>
<div style="padding:60px 24px;text-align:center;color:#6B7280;">
<i data-lucide="check-circle" style="width:48px;height:48px;color:#D1D5DB;display:block;margin:0 auto 15px;"></i>
<div style="font-size:15px;">لا يوجد لاعبين في انتظار التقييم</div>
<div style="font-size:13px;margin-top:8px;">جميع اللاعبين المسجلين تم تعيينهم في مجموعات</div>
</div>
<?php else: ?>
<div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:14px;">
<thead>
<tr style="background:#F9FAFB;border-bottom:1px solid #E5E7EB;">
<th style="padding:12px 16px;text-align:right;font-weight:600;color:#374151;">اللاعب</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;color:#374151;">النوع</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;color:#374151;">الأنشطة المطلوبة</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;color:#374151;">تاريخ التسجيل</th>
<th style="padding:12px 16px;text-align:center;font-weight:600;color:#374151;">إجراء</th>
</tr>
</thead>
<tbody>
<?php foreach ($players as $p): ?>
<tr style="border-bottom:1px solid #F3F4F6;">
<td style="padding:12px 16px;">
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:40px;height:40px;border-radius:8px;overflow:hidden;background:#F3F4F6;flex-shrink:0;">
<?php if (!empty($p['photo_path'])): ?>
<img src="/<?= e($p['photo_path']) ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else: ?>
<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#9CA3AF;"><i data-lucide="user" style="width:18px;height:18px;"></i></div>
<?php endif; ?>
</div>
<div>
<div style="font-weight:600;"><?= e($p['full_name_ar']) ?></div>
<div style="font-size:12px;color:#6B7280;"><?= e($p['national_id'] ?? '—') ?></div>
</div>
</div>
</td>
<td style="padding:12px 16px;">
<span style="padding:3px 8px;border-radius:6px;font-size:11px;font-weight:600;background:<?= $p['player_type'] === 'member' ? '#ECFDF5' : '#FEF3C7' ?>;color:<?= $p['player_type'] === 'member' ? '#059669' : '#D97706' ?>;">
<?= $p['player_type'] === 'member' ? 'عضو' : 'غير عضو' ?>
</span>
</td>
<td style="padding:12px 16px;">
<?php
$disciplines = [];
if (!empty($p['selected_disciplines'])) {
$decoded = json_decode($p['selected_disciplines'], true);
if (is_array($decoded)) {
$disciplines = $decoded;
}
}
if (!empty($disciplines)):
foreach ($disciplines as $disc):
?>
<span style="display:inline-block;padding:2px 8px;border-radius:4px;background:#EFF6FF;color:#2563EB;font-size:11px;font-weight:600;margin-left:4px;"><?= e($disc['name'] ?? $disc) ?></span>
<?php endforeach; else: ?>
<span style="color:#9CA3AF;font-size:12px;">لم يحدد</span>
<?php endif; ?>
</td>
<td style="padding:12px 16px;font-size:13px;color:#6B7280;direction:ltr;text-align:right;">
<?= e(substr($p['registration_date'], 0, 10)) ?>
</td>
<td style="padding:12px 16px;text-align:center;">
<a href="/sa/coach-assessment/<?= (int) $p['registration_id'] ?>" class="btn btn-sm btn-primary" style="padding:8px 16px;font-size:13px;">
<i data-lucide="clipboard-check" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i> تقييم
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?><?= $coach ? 'تعديل مدرب: ' . e($coach['full_name_ar']) : 'إضافة مدرب سباحة' ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/swimming/coaches" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<form method="POST" action="<?= $coach ? '/sa/swimming/coaches/' . (int) $coach['id'] : '/sa/swimming/coaches' ?>">
<?= csrf_field() ?>
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="waves" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:15px;">بيانات المدرب</h3>
</div>
<div style="padding:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div class="form-group">
<label class="form-label">الاسم بالعربي <span style="color:#DC2626;">*</span></label>
<input type="text" name="full_name_ar" value="<?= e(old('full_name_ar') ?: ($coach['full_name_ar'] ?? '')) ?>" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label">الاسم بالإنجليزي</label>
<input type="text" name="full_name_en" value="<?= e(old('full_name_en') ?: ($coach['full_name_en'] ?? '')) ?>" class="form-input" style="direction:ltr;text-align:left;">
</div>
<div class="form-group">
<label class="form-label">الرقم القومي</label>
<input type="text" name="national_id" value="<?= e(old('national_id') ?: ($coach['national_id'] ?? '')) ?>" class="form-input" maxlength="14" style="direction:ltr;text-align:left;" <?= $coach ? 'readonly' : '' ?>>
</div>
<div class="form-group">
<label class="form-label">الهاتف</label>
<input type="text" name="phone" value="<?= e(old('phone') ?: ($coach['phone'] ?? '')) ?>" class="form-input" style="direction:ltr;text-align:left;">
</div>
<div class="form-group">
<label class="form-label">نوع التوظيف <span style="color:#DC2626;">*</span></label>
<select name="employment_type" class="form-select" required>
<?php $et = old('employment_type') ?: ($coach['employment_type'] ?? 'freelance'); ?>
<option value="freelance" <?= $et === 'freelance' ? 'selected' : '' ?>>مستقل (Freelance)</option>
<option value="staff" <?= $et === 'staff' ? 'selected' : '' ?>>موظف (Staff)</option>
<option value="contract" <?= $et === 'contract' ? 'selected' : '' ?>>تعاقد (Contract)</option>
</select>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" style="padding:14px 30px;font-size:15px;">
<i data-lucide="check" style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> <?= $coach ? 'حفظ التعديلات' : 'إضافة المدرب' ?>
</button>
</form>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>مدربين السباحة<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/swimming/coaches/create" class="btn btn-primary"><i data-lucide="plus" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> إضافة مدرب</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:16px;padding:14px 16px;">
<form method="GET" action="/sa/swimming/coaches" style="display:flex;gap:10px;align-items:end;flex-wrap:wrap;">
<div style="flex:1;min-width:200px;">
<input type="text" name="search" value="<?= e($search) ?>" class="form-input" placeholder="بحث بالاسم أو الهاتف..." style="padding:10px 14px;">
</div>
<select name="filter" class="form-select" style="min-width:150px;padding:10px 14px;">
<option value="">— جميع الأنواع —</option>
<option value="freelance" <?= $filter === 'freelance' ? 'selected' : '' ?>>مستقل (freelance)</option>
<option value="staff" <?= $filter === 'staff' ? 'selected' : '' ?>>موظف (staff)</option>
<option value="contract" <?= $filter === 'contract' ? 'selected' : '' ?>>تعاقد (contract)</option>
</select>
<button type="submit" class="btn btn-outline" style="padding:10px 18px;">
<i data-lucide="search" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> بحث
</button>
</form>
</div>
<!-- Coaches List -->
<div class="card">
<div style="padding:14px 16px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="waves" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;font-size:15px;color:#0D7377;">مدربين السباحة</h3>
<span style="margin-right:auto;background:#EFF6FF;color:#2563EB;padding:3px 10px;border-radius:12px;font-size:12px;font-weight:700;"><?= count($coaches) ?></span>
</div>
<?php if (empty($coaches)): ?>
<div style="padding:50px 24px;text-align:center;color:#6B7280;">
<i data-lucide="waves" style="width:48px;height:48px;color:#D1D5DB;display:block;margin:0 auto 15px;"></i>
<div style="font-size:15px;">لا يوجد مدربين سباحة</div>
</div>
<?php else: ?>
<div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:14px;">
<thead>
<tr style="background:#F9FAFB;border-bottom:1px solid #E5E7EB;">
<th style="padding:12px 16px;text-align:right;font-weight:600;">الكود</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;">الاسم</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;">الهاتف</th>
<th style="padding:12px 16px;text-align:right;font-weight:600;">نوع التوظيف</th>
<th style="padding:12px 16px;text-align:center;font-weight:600;">مجموعات نشطة</th>
<th style="padding:12px 16px;text-align:center;font-weight:600;">إجراءات</th>
</tr>
</thead>
<tbody>
<?php foreach ($coaches as $c): ?>
<tr style="border-bottom:1px solid #F3F4F6;">
<td style="padding:12px 16px;font-size:12px;direction:ltr;text-align:right;color:#6B7280;"><?= e($c['code']) ?></td>
<td style="padding:12px 16px;font-weight:600;"><?= e($c['full_name_ar']) ?></td>
<td style="padding:12px 16px;direction:ltr;text-align:right;color:#6B7280;"><?= e($c['phone'] ?? '—') ?></td>
<td style="padding:12px 16px;">
<?php
$types = ['freelance' => 'مستقل', 'staff' => 'موظف', 'contract' => 'تعاقد'];
$colors = ['freelance' => '#7C3AED', 'staff' => '#059669', 'contract' => '#D97706'];
$t = $c['employment_type'] ?? 'freelance';
?>
<span style="padding:3px 8px;border-radius:6px;font-size:11px;font-weight:600;background:<?= $colors[$t] ?? '#6B7280' ?>15;color:<?= $colors[$t] ?? '#6B7280' ?>;">
<?= $types[$t] ?? $t ?>
</span>
</td>
<td style="padding:12px 16px;text-align:center;font-weight:600;"><?= (int) ($c['active_groups'] ?? 0) ?></td>
<td style="padding:12px 16px;text-align:center;">
<a href="/sa/swimming/coaches/<?= (int) $c['id'] ?>" class="btn btn-sm btn-outline" style="padding:6px 12px;font-size:12px;">عرض</a>
<a href="/sa/swimming/coaches/<?= (int) $c['id'] ?>/edit" class="btn btn-sm btn-outline" style="padding:6px 12px;font-size:12px;margin-right:4px;">تعديل</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>مدرب: <?= e($coach['full_name_ar']) ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/sa/swimming/coaches/<?= (int) $coach['id'] ?>/edit" class="btn btn-outline"><i data-lucide="edit" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> تعديل</a>
<a href="/sa/swimming/coaches" class="btn btn-outline"><i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Coach Info -->
<div class="card" style="margin-bottom:16px;">
<div style="padding:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:16px;">
<div>
<div style="font-size:12px;color:#6B7280;">الكود</div>
<div style="font-size:15px;font-weight:600;direction:ltr;text-align:right;"><?= e($coach['code']) ?></div>
</div>
<div>
<div style="font-size:12px;color:#6B7280;">الهاتف</div>
<div style="font-size:15px;font-weight:600;direction:ltr;text-align:right;"><?= e($coach['phone'] ?? '—') ?></div>
</div>
<div>
<div style="font-size:12px;color:#6B7280;">نوع التوظيف</div>
<?php $types = ['freelance' => 'مستقل', 'staff' => 'موظف', 'contract' => 'تعاقد']; ?>
<div style="font-size:15px;font-weight:600;"><?= $types[$coach['employment_type'] ?? ''] ?? ($coach['employment_type'] ?? '—') ?></div>
</div>
<div>
<div style="font-size:12px;color:#6B7280;">الرقم القومي</div>
<div style="font-size:15px;font-weight:600;direction:ltr;text-align:right;"><?= e($coach['national_id'] ?? '—') ?></div>
</div>
</div>
</div>
</div>
<!-- Active Groups -->
<div class="card" style="margin-bottom:16px;">
<div style="padding:14px 16px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:15px;font-weight:600;"><i data-lucide="users" style="width:16px;height:16px;vertical-align:middle;margin-left:6px;color:#0D7377;"></i> المجموعات النشطة</h3>
</div>
<?php if (empty($groups)): ?>
<div style="padding:24px;text-align:center;color:#6B7280;font-size:13px;">لا يوجد مجموعات نشطة</div>
<?php else: ?>
<div style="padding:0;">
<?php foreach ($groups as $g): ?>
<div style="padding:12px 16px;border-bottom:1px solid #F3F4F6;display:flex;align-items:center;justify-content:space-between;">
<div>
<div style="font-weight:600;font-size:14px;"><?= e($g['name_ar']) ?></div>
<div style="font-size:12px;color:#6B7280;"><?= e($g['code']) ?></div>
</div>
<span style="padding:3px 8px;border-radius:6px;font-size:11px;font-weight:600;background:#ECFDF5;color:#059669;"><?= e($g['role'] ?? 'مدرب') ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Lane Bookings -->
<div class="card">
<div style="padding:14px 16px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;font-size:15px;font-weight:600;"><i data-lucide="calendar" style="width:16px;height:16px;vertical-align:middle;margin-left:6px;color:#0D7377;"></i> حجوزات الحارات</h3>
</div>
<?php if (empty($laneBookings)): ?>
<div style="padding:24px;text-align:center;color:#6B7280;font-size:13px;">لا توجد حجوزات</div>
<?php else: ?>
<div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<tr style="background:#F9FAFB;">
<th style="padding:10px 14px;text-align:right;">المرفق</th>
<th style="padding:10px 14px;text-align:right;">التاريخ</th>
<th style="padding:10px 14px;text-align:right;">الوقت</th>
<th style="padding:10px 14px;text-align:center;">الإشغال</th>
</tr>
</thead>
<tbody>
<?php foreach ($laneBookings as $b): ?>
<tr style="border-bottom:1px solid #F3F4F6;">
<td style="padding:10px 14px;"><?= e($b['facility_name'] ?? '—') ?></td>
<td style="padding:10px 14px;direction:ltr;text-align:right;"><?= e($b['booking_date'] ?? '') ?></td>
<td style="padding:10px 14px;direction:ltr;text-align:right;"><?= e(substr($b['start_time'] ?? '', 0, 5)) ?> - <?= e(substr($b['end_time'] ?? '', 0, 5)) ?></td>
<td style="padding:10px 14px;text-align:center;"><?= (int) ($b['current_occupancy'] ?? 0) ?>/<?= (int) ($b['max_occupancy'] ?? 0) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
......@@ -45,8 +45,11 @@ MenuRegistry::register('sports_activity', [
['label_ar' => 'إيجارات اللوكرات', 'label_en' => 'Locker Rentals', 'route' => '/sa/locker-rentals', 'permission' => 'sa.locker_rental.view','order' => 21],
['label_ar' => 'تذاكر السباحة', 'label_en' => 'Pool Tickets', 'route' => '/sa/pool-tickets', 'permission' => 'sa.pool_ticket.view', 'order' => 22],
['label_ar' => 'الألعاب الترفيهية','label_en' => 'Recreational Games','route' => '/sa/games', 'permission' => 'sa.game.view', 'order' => 23],
['label_ar' => '── تقييم المدربين ──','label_en' => '── Coach Assessment ──','route' => '#', 'permission' => 'sa.coach_assessment.view','order' => 25],
['label_ar' => 'تقييم اللاعبين', 'label_en' => 'Player Assessment','route' => '/sa/coach-assessment','permission' => 'sa.coach_assessment.view','order' => 26],
['label_ar' => '── السباحة ──', 'label_en' => '── Swimming ──', 'route' => '#', 'permission' => 'sa.swimming.dashboard','order' => 30],
['label_ar' => 'لوحة تحكم السباحة','label_en' => 'Swimming Dashboard','route' => '/sa/swimming', 'permission' => 'sa.swimming.dashboard','order' => 31],
['label_ar' => 'مدربين السباحة', 'label_en' => 'Swimming Coaches','route' => '/sa/swimming/coaches','permission' => 'sa.swimming.coach_manage','order' => 31.5],
['label_ar' => 'تسجيل لاعب سباحة', 'label_en' => 'Register Swimmer','route' => '/sa/swimming/register','permission' => 'sa.swimming.register','order' => 32],
['label_ar' => 'تعيين في مجموعة', 'label_en' => 'Assign to Group', 'route' => '/sa/swimming/assign','permission' => 'sa.swimming.assign', 'order' => 33],
],
......@@ -119,6 +122,9 @@ PermissionRegistry::register('sports_activity', [
'sa.institution.view' => ['ar' => 'عرض المؤسسات', 'en' => 'View Institutions'],
'sa.institution.manage' => ['ar' => 'إدارة المؤسسات', 'en' => 'Manage Institutions'],
'sa.enrollment.manage' => ['ar' => 'إدارة تسجيلات اللاعبين', 'en' => 'Manage Player Enrollments'],
'sa.coach_assessment.view' => ['ar' => 'عرض تقييم اللاعبين', 'en' => 'View Player Assessments'],
'sa.coach_assessment.manage' => ['ar' => 'إدارة تقييم اللاعبين', 'en' => 'Manage Player Assessments'],
'sa.swimming.coach_manage' => ['ar' => 'إدارة مدربين السباحة', 'en' => 'Manage Swimming Coaches'],
]);
// ─── Event Listeners ────────────────────────────────────────────────────────
......
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
// 1. Update system_config: flat 100 EGP, no card/form fees
$db->query(
"UPDATE system_config SET config_value = '100.00' WHERE config_key = 'sa.registration_fee_member'"
);
$db->query(
"UPDATE system_config SET config_value = '100.00' WHERE config_key = 'sa.registration_fee_nonmember'"
);
$db->query(
"UPDATE system_config SET config_value = '0.00' WHERE config_key = 'sa.card_fee'"
);
$db->query(
"UPDATE system_config SET config_value = '0.00' WHERE config_key = 'sa.form_fee'"
);
// 2. Add selected_disciplines JSON to sa_registrations
$col = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sa_registrations' AND column_name = 'selected_disciplines'"
);
if (!$col) {
$db->query("ALTER TABLE sa_registrations ADD COLUMN selected_disciplines JSON NULL AFTER group_id");
}
// 3. Add assessment columns to sa_group_players
$col2 = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sa_group_players' AND column_name = 'assessed_by'"
);
if (!$col2) {
$db->query("ALTER TABLE sa_group_players ADD COLUMN assessed_by BIGINT UNSIGNED NULL AFTER created_by");
$db->query("ALTER TABLE sa_group_players ADD COLUMN assessment_notes TEXT NULL AFTER assessed_by");
$db->query("ALTER TABLE sa_group_players ADD COLUMN skill_level VARCHAR(20) NULL AFTER assessment_notes");
$db->query("ALTER TABLE sa_group_players ADD COLUMN assessed_at TIMESTAMP NULL AFTER skill_level");
}
// 4. Add sa.coach_assessment permission keys
$existing = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'sa.swimming.coach_manage'"
);
if (!$existing) {
$db->insert('system_config', [
'config_key' => 'sa.swimming.coach_manage',
'config_value' => '1',
'description' => 'Swimming Coach Management permission flag',
]);
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment