<?php$__template->section('title');?>مرآة: <?=e($discipline['name_ar']??'')?> - جميع المرافق<?php$__template->endSection();?>
<?php$__template->section('page_actions');?>
<ahref="/sa/mirror"class="btn btn-outline"><idata-lucide="arrow-right"style="width:16px;height:16px;vertical-align:middle;margin-left:4px;"></i> كل المرافق</a>
<span><spanstyle="display:inline-block;width:14px;height:14px;background:#CFFAFE;border-radius:3px;vertical-align:middle;margin-left:4px;"></span> دخول حر</span>
ALTER TABLE sa_facility_units ADD COLUMN expected_capacity INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Expected number of people per booking (e.g., 5-a-side pitch = 10)' AFTER max_capacity
",
'down'=>"
ALTER TABLE sa_facility_units DROP COLUMN expected_capacity
أنا عاوز أوضح بشكل دقيق المفروض منظومة النشاط الرياضي في النادي تكون شغالة إزاي، وبعد كده يتم تحليل النظام الحالي ومقارنته بالنظام المطلوب، مع توضيح الفروقات والنواقص بشكل واضح.
1. منظومة المرافق والحجوزات الحرة
كل نشاط رياضي داخل النادي يحتوي على مرافق مرتبطة به، وكل مرفق له:
* مواعيد تشغيل يومية.
* أسعار مختلفة للساعة حسب:
* الوقت.
* نوع العميل (عضو / غير عضو / مؤسسة).
لكن أسعار الساعات دي تُستخدم فقط في حالة:
* الحجز الحر.
* الحجز الفردي.
* الحجز المؤسسي.
* أي استخدام غير مرتبط ببرنامج تدريبي أو تمرين مجدول.
وفي نفس الوقت، أي حجز يتم على المرفق لازم يظهر داخل “مراية المرفق” بشكل واضح على الـ Timeline، مع توضيح:
* الوقت المحجوز.
* الجهة أو الشخص الحاجز.
* نوع الحجز.
2. منظومة الأكاديميات والبرامج التدريبية
النادي يحتوي على أكاديميات رياضية مسؤولة عن إدارة التدريبات داخل النادي.
كل أكاديمية ممكن:
* تكون مسؤولة عن رياضة واحدة أو أكثر.
* تقدم عدة برامج تدريبية لكل رياضة.
وكل برنامج تدريبي يكون له:
* سعر للأعضاء.
* سعر لغير الأعضاء.
بعد إنشاء البرنامج التدريبي، يتم إنشاء مجموعات تدريبية مرتبطة به، وكل مجموعة:
* يتم تحديد أيام وساعات تدريبها.
* يتم حجز مواعيدها على المرفق الخاص بها.
ومن المهم جدًا إن النظام يسمح بوجود أكثر من مجموعة تدريبية في نفس الوقت على نفس المرفق إذا كانت سعة المرفق تسمح بذلك (Overlapping Groups).
ولازم مراية المرفق تعرض هذا الأمر بشكل بصري واضح واحترافي على الـ Timeline الخاص بالمرفق، مع مراعاة الـ Capacity الخاصة بالمرفق سواء كانت:
* مجموعات تدريبية.
* حجوزات فردية.
* حجوزات مؤسسية.
* حجوزات حرة.
3. مفهوم “مراية المرفق”
المراية هي مجرد Visualization للمرافق والحجوزات، ويجب أن تكون:
* قابلة للتصنيف حسب الرياضة.
* قابلة للفلترة بشكل عملي وسريع.
على سبيل المثال:
لو موظف يريد حجز Bowling أو PlayStation، لا يصح أن يدخل على كل مرفق بشكل منفصل ليرى المتاح.
المطلوب هو عرض Visualized محترم وواضح يبين:
* كل المرافق الخاصة بالنشاط.
* المتاح والمحجوز.
* الفترات الزمنية.
* السعة الحالية.
بشكل سريع وسهل الاستخدام.
4. منظومة حمامات السباحة
حمامات السباحة يجب أن تتبع نفس المنظومة العامة للمرافق، ولكن بطريقة عرض وحجز مختلفة.
كل حمام سباحة يعتبر “مرفق” عادي داخل النظام، لكن شاشة الحجز الخاصة به تكون بنظام Grid كامل بدل الـ Timeline التقليدي.
الهدف من الـ Grid هو دعم جميع الـ Edge Cases المطلوبة.
ويجب أن يدعم النظام:
* حجز حارات.
* حجز Cells أو مناطق محددة داخل الحمام.
* تحديد أيام ومواعيد لكل مساحة.
* تحديد نوع الإتاحة:
* متاح للحجز الحر.
* متاح للأعضاء فقط.
* متاح للجميع.
وفي بعض الحالات تكون المساحة “غير محجوزة” ولكنها “متاحة للاستخدام الحر” خلال فترة معينة، بحيث أي شخص يستطيع دفع تذكرة دخول واستخدامها.
مع مراعاة:
* وجود أسعار مختلفة للأعضاء وغير الأعضاء.
* عدم وجود مفهوم “فرد يحجز حمام سباحة بالكامل لنفسه”، لأن طبيعة استخدام حمامات السباحة مختلفة عن الملاعب والمرافق الفردية.
5. Wizard مكتب التسجيل
مطلوب تبسيط الـ Wizard الخاص بمكتب التسجيل لأقصى درجة ممكنة.
الـ Workflow المطلوب يكون كالتالي:
* الموظف يدخل بيانات اللاعب.
* يرفع صورة اللاعب.
* يرفع الشهادة الطبية (إن وجدت).
* يختار الرياضة.
* يختار البرنامج التدريبي.
بعدها النظام يقوم تلقائيًا بـ:
* حساب الرسوم.
* إضافة المبلغ على الخزنة.
* إدخال اللاعب في آخر مجموعة غير مكتملة داخل البرنامج التدريبي المختار.
ولو كل المجموعات الحالية مكتملة:
* النظام يفتح مجموعة جديدة تلقائيًا.
* ويتم إضافة اللاعب إليها مباشرة.
وفي نفس الوقت:
* المدرب يجب أن يمتلك صلاحية نقل أي لاعب يدويًا من مجموعة إلى أخرى.
* حتى لو هذا النقل تسبب في تجاوز العدد المسموح للمجموعة (Overloading Group).
في نقطة محورية مهمة جدًا لازم تكون واضحة في المنظومة، وهي إن أي مرفق داخل النادي في النهاية بيكون محجوز لواحدة من 3 حالات فقط:
* فرد / حجز حر.
* مجموعة تدريبية.
* مؤسسة أو جهة.
لكن حتى في حالة “الحجز الفردي” أو “حجز المؤسسة”، الحجز في الحقيقة لا يخص شخصًا واحدًا فقط، بل يخص عددًا معينًا من الأشخاص المسموح لهم بالدخول واستخدام المرفق.
يعني مثلًا:
لو شخص قام بحجز ملعب كرة خماسي لمدة ساعة، فمن الطبيعي إن الاستخدام الفعلي للمرفق سيكون لـ 10 أفراد، وليس لشخص واحد فقط.
لذلك النظام يجب أن يدعم أكثر من طريقة لإدارة الأشخاص المرتبطين بالحجز:
1. تسجيل كامل للأفراد
يتم إدخال:
* أسماء اللاعبين.
* أرقامهم القومية أو بياناتهم الكاملة.
وفي هذه الحالة يصبح كل شخص مسجل داخل النظام ويمكن التحقق منه عند الدخول.
2. تسجيل جزئي للأفراد
في بعض الحالات قد لا تتوفر كل البيانات، لذلك النظام يجب أن يسمح بإدخال:
* أسماء الأشخاص فقط.
بدون الحاجة لبيانات كاملة أو رقم قومي.
3. نظام الـ Passes
في حالة عدم توفر أي بيانات عن الأفراد، النظام يجب أن يسمح بإنشاء عدد محدد من الـ Passes المرتبطة بالحجز.
مثال:
* حجز ملعب كرة خماسي = 10 Passes.
* حجز Paddle زوجي = 4 Passes.
* حجز Bowling لحارتين = عدد محدد حسب الحجز.
وفي هذه الحالة:
* موظف البوابة يتأكد فقط إن عدد الداخلين لا يتجاوز عدد الـ Passes المسموح بها.
* لا يجوز دخول عدد أكبر من العدد المحدد.
* ويُفضل أيضًا تسجيل عدد من تم استخدامه فعليًا من الـ Passes.
وهذا المفهوم يجب أن يطبق على:
* الملاعب.
* حمامات السباحة.
* البولينج.
* البادل.
* البلايستيشن.
* أي مرفق ترفيهي أو رياضي داخل النادي.
لأن الحجز في النهاية ليس “حجز شخص”، بل هو:
“حجز Capacity / حق استخدام لمجموعة من الأشخاص خلال فترة زمنية محددة”.
ولذلك يجب أن تكون منظومة:
* الحجوزات.
* البوابات.
* الـ Access Control.
* والمرايات الخاصة بالمرافق.
كلها مترابطة مع مفهوم:
عدد الأشخاص المسموح لهم بالدخول الفعلي بناءً على نوع الحجز وسعته.
لازم يكون في مفهوم أساسي داخل تعريف أي مرفق في النظام وهو:
* Expected Capacity
أو
* Perfect Capacity
وهو العدد الطبيعي أو المتوقع للأشخاص الذين يستخدمون هذا المرفق في الحجز الواحد.
القيمة دي يتم تحديدها أثناء إنشاء المرفق نفسه، وتصبح جزء أساسي من إعداداته.
أمثلة:
* ملعب كرة خماسي → Expected Capacity = 10
* جهاز PlayStation → Expected Capacity = 2
* حارة Bowling → Expected Capacity = 6
* ملعب Paddle → Expected Capacity = 4
* طاولة Billiard → Expected Capacity = 2
وأهمية الرقم ده إنه يستخدم تلقائيًا داخل النظام في حالات الحجز الحر أو الحجز غير التدريبي.
يعني:
لو عميل قام بحجز ملعب كرة خماسي لمدة ساعة، فالنظام تلقائيًا:
* يتوقع دخول 10 أفراد.
* يطلب إما:
* تسجيل أسماء اللاعبين.
* أو إنشاء 10 Passes للدخول.
ولو تم تسجيل عدد أقل أو أكبر من الـ Expected Capacity، النظام:
* إما يعطي Warning.
* أو يطلب تأكيد إداري حسب صلاحيات المستخدم.
أما في حالة البرامج التدريبية والمجموعات:
فالـ Expected Capacity لا يكون شرطًا ثابتًا، لأن:
* المدرب قد يسمح بزيادة العدد.
* بعض المرافق تتحمل أكثر من مجموعة في نفس الوقت.
* وبعض التدريبات طبيعتها مختلفة.
لذلك:
الـ Expected Capacity الخاصة بالمرفق تستخدم بشكل أساسي في:
* الحجوزات الحرة.
* الحجوزات الفردية.
* الحجوزات المؤسسية.
* وتنظيم الدخول من البوابات.
ويجب أن تكون القيمة:
* قابلة للتعديل.
* ظاهرة بوضوح داخل إعدادات المرفق.
* ومستخدمة تلقائيًا في الـ Wizard الخاص بالحجز وإنشاء الـ Passes.
The current implementation has the **infrastructure** (tables, models, services, routes) but the **behavior** described in the requirements is NOT met. Here's the brutal truth:
| Requirement | Current Reality |
|-------------|----------------|
| Mirror filtered/categorized by sport | Flat grid of all facilities. No grouping, no filter, no discipline tabs. You click one-by-one. |
| Organization booking with separate pricing | Only `member` / `guest`. No organization concept anywhere in booking flow. |
| Wizard: pick sport → pick program → auto-assign group | Wizard makes user manually browse ALL groups (flat list), pick one themselves. No sport→program flow. |
| Auto-create group when full | Hard error "المجموعة ممتلئة". Suggests waitlist. Dead end. |
| Coach force-enroll past capacity | Hard block at `max_capacity`. No override. No bypass. |
| Coach transfer to full group | `GroupTransferService` hard blocks at line 40 if target full. No force flag. |
| Pool: open-access ticket zones | Pool grid only supports cell-level reserved bookings. No concept of "open zone pay-a-ticket-and-swim". |
| Mirror shows WHO booked + type clearly | Shows `booker_name` for hourly and `group_name` for training. But no organization, no color distinction by booker type. |
| Expected Capacity per facility unit | Does NOT exist. `max_capacity` on `sa_facility_units` is for concurrent booking slots, NOT for "how many people use this facility per booking". |
| Passes system for bookings | Does NOT exist. `sa_booking_participants` has individual names but no concept of anonymous numbered passes. |
| Participant registration (full/partial/passes) | Only basic `participant_name` + `player_id`. No national_id on participants. No pass-code system. No gate integration for bookings. |
| Booking = capacity for group of people | System treats `participant_count` as just a number for pricing. No enforcement at gates. No pass issuance. |
---
## What's Actually Working Correctly
1.**Facility model has `discipline_id`** — the link exists in DB, just unused in mirror UI.
2.**Mirror timeline renders correctly** per single facility — shows units as rows, time as columns, color-coded cells.
3.**Pool grid** works for cell-level assignment (training, blocked, maintenance, hourly).
| 3 | البرنامج | Pick program (shows price for member/non-member). System auto-assigns group. |
| 4 | الدفع | Total fees calculated → send to cashier |
| 5 | الاستلام | Print form + generate card |
**`RegistrationWizardController.php`**:
-`wizardStep()` — restructure: load disciplines for step 2, programs for step 3.
- New endpoint: `getPrograms(Request, string $disciplineId): Response` — returns programs for selected discipline as JSON.
-`selectActivity()` — accept `program_id` instead of `group_id`. Internally resolve group via auto-assign.
-`determineStep()` — rewrite logic:
- No photo + no medical → step 1
- No discipline selected → step 2
- No program selected → step 3
- Not paid → step 4
- Paid → step 5
**`RegistrationWizardService.php`**:
-`selectGroup()` → refactor to `selectProgram(int $registrationId, int $programId, int $months, bool $hasSibling)`:
- Call `EnrollmentService::findOrCreateAvailableGroup($programId)` (from GAP 4).
- Use returned group for enrollment.
- Rest of logic (fee calc, discount, etc.) stays same.
-`startRegistration()` — accept photo + medical doc in same call (or keep separate endpoint but UI presents as one step).
**`sa_registrations` table**:
- May need `discipline_id` column if not present (check — might already have via `group_id` → `program` → `discipline`).
**`Views/registration/wizard.php`** — major rewrite:
- Step 1: merge current step 1 (form fee) + step 2 (photo) into single panel. Player fills data, captures photo, uploads medical cert — one "next" button.
- Step 2: NEW — show discipline cards (football, swimming, basketball, etc.) as big clickable tiles.
- Step 3: NEW — show programs under selected discipline with prices. No groups visible to user.
- Step 4: show fee breakdown + "إرسال للخزنة" button. Group is auto-assigned in background.
1.**Auto-group has no schedule**: If source group has no `sa_group_schedule` entries, new group won't have any either. Warn coach.
2.**Force-enroll audit**: Add `force_enrolled = 1` flag on `sa_group_players` for tracking.
3.**Pool ticket occupancy drift**: Cron job to reconcile `current_occupancy` against active tickets daily.
4.**Old wizard registrations**: Existing in-progress registrations use old flow. `determineStep()` must handle NULL `discipline_id` gracefully.
5.**Multi-facility mirror performance**: Lazy-load per facility via AJAX or limit per page.
6.**Pass expiry**: Passes should auto-expire when booking time window ends. Cron or check-on-scan.
7.**Expected capacity override**: User can book with MORE or FEWER than expected capacity. System warns but doesn't block. Only generates passes equal to actual `participant_count` entered (which defaults to `expected_capacity`).
8.**Gate without passes**: If booking uses `participant_mode = 'full'` (names registered), gate validates by player card/NID against `sa_booking_participants`. No passes needed.
9.**Organization + passes**: An org booking for a 5-a-side pitch still gets 10 passes. The org contact is the booker, but the 10 people entering are different. System must support this.
10.**Mixed mode**: A booking starts with passes, but later some participants' names are collected at the gate. Consider allowing pass → name upgrade (optional, not blocking).