Commit 00d84740 authored by DevPilot's avatar DevPilot

docs: extreme-detail reference for PlayerAffairs

يوثق كل شاشات الموديول (تقييمات، لياقة، إصابات، الملفات) وأهم نقطة فيه:
جدول players هنا منفصل تمامًا عن sa_players بتاع SportsActivity — مفيش
مفتاح مشترك ولا مزامنة. بيوثق كمان الإصلاحات اللي اتعملت قبل كده في نفس
الجلسة لصلاحيات الأزرار، وموضوع تضارب شاشتي اعتماد الشهادات الطبية.
parent ea7b4757
# PlayerAffairs Module
35 routes across 6 controllers: Player CRUD/card/medical/enrollment, Player API, Medical Approvals, Attendance, Evaluations, Injuries, Fitness Tests.
## Read this first: PlayerAffairs' "player" is not SportsActivity's `sa_players`
This is the single most important fact about this module. `PlayerAffairs\Models\Player` reads/writes the **`players`** table; `SportsActivity\Models\Player` reads/writes **`sa_players`**. These are two genuinely separate, independently-built systems — near-identical column-for-column clones (name, national ID, guardian info, medical status, card status) that grew apart after the newer SportsActivity rewrite. **There is no shared ID, no FK, no sync between them.** A person can be registered as a player in both systems simultaneously, as two completely unrelated rows with two unrelated IDs, two unrelated card statuses, and no cross-reference anywhere. If you're ever unsure which "player" a screen means, check which table its queries touch — this document names the table for everything below.
Evaluations, fitness tests, injuries, and player progression (this whole module, essentially) are built **exclusively** on `players` — none of it has any presence in SportsActivity. The one place the two systems actually collide is medical records, covered at the end of this chapter.
## Player Evaluations (`/evaluations`)
One permission (`player.evaluate`) gates creating, viewing, *and* approving — there's no separate approval permission, so anyone who can submit an evaluation can also approve it, including their own.
A player is scored against a configurable set of criteria defined per-discipline (name, max score, weight). The overall score is a weighted average normalized to 0–10, bucketed into a skill level (elite ≥8, advanced ≥6, intermediate ≥3, else beginner). The system then tries to **suggest** a training group using a fuzzy keyword match between the skill bucket and group names (e.g. "elite" matches group names containing "نخبة"/"elite"/"level_4") — this is a plain string heuristic, not a real taxonomy, so a group named something the keyword list doesn't anticipate simply won't be suggested. **The suggestion is stored but nothing ever acts on it or displays it** — it's dead weight on the evaluation record.
The progression chart (Chart.js line chart of score over time) only appears once a player has 2+ prior evaluations **in that same discipline** — evaluations don't blend across disciplines. `draft` is a defined status nothing ever produces; every evaluation is created as `submitted` and can only move to `approved`, never back.
## Fitness Tests (`/fitness-tests`)
Immutable once saved — there's no edit or delete route at all. BMI is auto-computed from height/weight if both are given; fitness level is derived from the overall score, not entered directly, and clearance-for-activity defaults from that level but **can be manually overridden with no consistency check** — a "poor" test can still be marked cleared. The actual test battery (times, VO2max, flexibility, whatever) is entirely unstructured free-form key/value pairs, not real columns.
The "is this player currently fit" check looks only at their single most recent test — it's computed on demand for a dashboard badge and **never writes back to any medical or card status**, so a player can be fitness-flagged unfit while everything else about them still shows active. The overdue-test finder exists as a method but nothing calls it anywhere — no cron, no notification, dead code.
## Injuries (`/injuries`)
**A severe or critical injury automatically suspends the player's card** the moment it's reported (only if the card was active) — this is a real, load-bearing side effect that silently locks gate/facility access the instant a coach logs a bad injury. **There is no corresponding automatic reactivation when the injury is later marked recovered** — a card auto-suspended this way stays suspended forever unless a staff member manually reactivates it from the player profile. Worth knowing before assuming clearing an injury restores access on its own.
Clearance to return requires zero injuries currently `active`/`recovering` — a `chronic` injury does **not** block clearance, so a player can be simultaneously chronic and cleared. Status can be changed to any other status directly from a dropdown with no guard against skipping states and no confirmation.
This subsystem has **no connection whatsoever** to `player_medical_records`, `medical_status`, or anything in `sa_players` — its only cross-system touchpoint is the card-suspend side effect described above, and that only ever touches `players.card_status`, which SportsActivity's parallel medical/card concepts never see.
## Player Progression — fully dead code
Not reachable from any route, view, cron, or event listener anywhere in the codebase — no controller ever calls it. If it were wired up, it would let someone manually promote/demote a player's level, but nothing currently triggers that from evaluation results, fitness tests, or anything else. Worth knowing so you don't go looking for a "level up" screen that doesn't exist yet, despite the underlying table and service being fully built.
Also dead: the `player_disciplines` table and its `addDiscipline()` method — the actual "which disciplines is this player in" shown on a profile comes entirely from academy enrollments, a separate and actually-used mechanism.
The same certificate-validity rule (recreational→12 months, academy→6, international→3) is independently duplicated in three different places in the code — changing how long a certificate type is valid for means finding and editing all three, or they'll drift out of sync.
## Players listing/CRUD (`/players`)
Server-side validation is thin — only name, player type, birth date, and gender (if given) are actually checked. National ID format/checksum validation is client-side only, advisory; a form can be submitted with an invalid or duplicate national ID with nothing blocking it. Card management here is a flat three-state toggle (activate/suspend/revoke) with no physical card, no QR code, no print flow at all — a completely different, much simpler concept than SportsActivity's full `sa_player_cards` lifecycle.
Academy enrollment has **no duplicate-enrollment guard** — a player can end up enrolled in the same academy/level twice with nothing to stop it.
**The player list's own filter bar only exposes card status as a dropdown.** Medical status, discipline, and academy filters all exist in the underlying search code but have no control anywhere on the screen — reachable only by hand-editing the URL query string.
## Fixed this session — permission mismatches hiding buttons across the module
Nearly every write-action button in this module (add player, edit player, edit link on the profile, new evaluation, new injury, new fitness test) was gated on `sa.player.manage` — a permission that belongs to and is registered by **SportsActivity**, not this module. A staff member correctly granted this module's own permissions (`player.register`, `player.edit`, `player.evaluate`, `player.manage_injuries`, `player.manage_fitness`) never saw their own buttons; only someone additionally granted the unrelated SportsActivity permission could act at all.
Separately, and more strikingly: the medical-approvals screen's approve/reject buttons were gated on `sa.medical.approve` — a permission whose own registration comment in the codebase reads *"(ملغي) اعتماد الشهادات الطبية — استخدم medical.board.approve"* ("(deprecated) — use medical.board.approve instead"). The route itself actually requires `player.approve_medical`, a live, correctly-registered permission entirely disconnected from the deprecated one the button was checking. Every button in the module now checks the same permission its route actually requires.
## The one real collision with SportsActivity: shared medical records table
`player_medical_records` was originally built exclusively for this module's `players` table, then later retrofitted with a `source` column and a nullable `sa_player_id` so SportsActivity could use it too. The result: **two separate, independently-built approval screens can both act on the same record** — this module's own Medical Approvals screen (which doesn't filter by source, so it lists SportsActivity-sourced records too, rendering them with a blank player name since the join to `players` can't match a null `player_id`) and the newer, correctly source-aware Medical Board screen. Approving from the wrong one can leave a record marked approved while the actual `sa_players.medical_status` it was meant to update stays untouched. If a medical record's approval status and its player's actual medical status ever look inconsistent, this is why — check which screen it was approved from.
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