Commit 25e2be96 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(participants): record shirt name, school and previous clubs

Three things a football academy keeps about a player that had nowhere to live,
found while mapping a 495-row roster exported from a club's previous system.

jersey_name is not the player's name. "Mohamed Sherif Mikkawi" wears "Mikkawi",
and across 495 players not one shirt name equalled the full name — so it cannot
be derived and has to be stored. product_customizations can already ask for it
at the till, but that answer belongs to one sale and freezes onto that invoice;
the club still needs to know what this player's shirt says, before and after any
particular kit is bought.

school_name is asked at registration everywhere and is what an academy plans
term times and school fixtures around.

previous_clubs is free text on purpose. A transfer history is a sentence —
"Wadi Degla then Zed" — not a foreign key, and a clubs table would force the
desk to resolve every spelling before it could record anything at all. The same
roster spells one club four ways.

Full slice: migration, model, service, form, show view. All three nullable and
additive, each guarded by hasColumn, so the migration is safe against every
populated tenant and nothing reads them until something writes them.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 76a9a512
......@@ -42,6 +42,9 @@ class Participant extends Model
'photo_path',
'jersey_number',
'jersey_size',
'jersey_name',
'school_name',
'previous_clubs',
'shoe_size',
'dominant_hand',
'dominant_foot',
......
......@@ -67,6 +67,9 @@ public function register(array $data, User $actor): Participant
'medical_clearance_expires' => $data['medical_clearance_expires'] ?? null,
'jersey_number' => $data['jersey_number'] ?? null,
'jersey_size' => $data['jersey_size'] ?? null,
'jersey_name' => $data['jersey_name'] ?? null,
'school_name' => $data['school_name'] ?? null,
'previous_clubs' => $data['previous_clubs'] ?? null,
'shoe_size' => $data['shoe_size'] ?? null,
'dominant_hand' => $data['dominant_hand'] ?? null,
'dominant_foot' => $data['dominant_foot'] ?? null,
......
......@@ -54,6 +54,9 @@ class ParticipantForm extends Component
public ?string $medical_clearance_expires = null;
public ?int $jersey_number = null;
public string $jersey_size = '';
public string $jersey_name = '';
public string $school_name = '';
public string $previous_clubs = '';
public string $shoe_size = '';
public ?string $dominant_hand = null;
public ?string $dominant_foot = null;
......@@ -93,6 +96,9 @@ public function mount(?Participant $participant = null): void
$this->medical_clearance_expires = $participant->medical_clearance_expires?->format('Y-m-d');
$this->jersey_number = $participant->jersey_number;
$this->jersey_size = $participant->jersey_size ?? '';
$this->jersey_name = $participant->jersey_name ?? '';
$this->school_name = $participant->school_name ?? '';
$this->previous_clubs = $participant->previous_clubs ?? '';
$this->shoe_size = $participant->shoe_size ?? '';
$this->dominant_hand = $participant->dominant_hand;
$this->dominant_foot = $participant->dominant_foot;
......@@ -139,6 +145,9 @@ public function rules(): array
'medical_clearance_expires' => 'nullable|date',
'jersey_number' => 'nullable|integer|min:0|max:999',
'jersey_size' => 'nullable|string|max:5',
'jersey_name' => 'nullable|string|max:50',
'school_name' => 'nullable|string|max:150',
'previous_clubs' => 'nullable|string|max:1000',
'shoe_size' => 'nullable|string|max:5',
'dominant_hand' => 'nullable|in:left,right,both',
'dominant_foot' => 'nullable|in:left,right,both',
......@@ -291,6 +300,9 @@ public function save(ParticipantService $service): void
'medical_clearance_expires' => $this->medical_clearance_expires ?: null,
'jersey_number' => $this->jersey_number,
'jersey_size' => $this->jersey_size ?: null,
'jersey_name' => $this->jersey_name ?: null,
'school_name' => $this->school_name ?: null,
'previous_clubs' => $this->previous_clubs ?: null,
'shoe_size' => $this->shoe_size ?: null,
'dominant_hand' => $this->dominant_hand,
'dominant_foot' => $this->dominant_foot,
......@@ -318,6 +330,9 @@ public function save(ParticipantService $service): void
'medical_clearance_expires' => $this->medical_clearance_expires ?: null,
'jersey_number' => $this->jersey_number,
'jersey_size' => $this->jersey_size ?: null,
'jersey_name' => $this->jersey_name ?: null,
'school_name' => $this->school_name ?: null,
'previous_clubs' => $this->previous_clubs ?: null,
'shoe_size' => $this->shoe_size ?: null,
'dominant_hand' => $this->dominant_hand,
'dominant_foot' => $this->dominant_foot,
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Three things a football academy records about a player that had nowhere to go.
*
* `jersey_name` — the name printed on the back of the shirt. It is not the
* player's name: "Mohamed Sherif Mikkawi" wears "Mikkawi", and in a roster of
* 495 players not one shirt name equalled the full name. `product_customizations`
* can already ask for it at the till, but that is the answer to one sale and
* freezes onto that invoice; the club still needs to know what this player's
* shirt says, before and after any particular kit is bought.
*
* `school_name` — asked at registration everywhere, and the thing an academy
* uses to plan around term times and school fixtures.
*
* `previous_clubs` — free text on purpose. A transfer history is a sentence
* ("Wadi Degla then Zed"), not a foreign key, and inventing a clubs table would
* demand the desk resolve every spelling before it could record anything.
*
* All three are nullable and additive; nothing reads them until something writes
* them, so every existing tenant is unaffected.
*/
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('participants')) {
return;
}
Schema::table('participants', function (Blueprint $table) {
if (! Schema::hasColumn('participants', 'jersey_name')) {
$table->string('jersey_name', 50)->nullable()->after('jersey_size');
}
if (! Schema::hasColumn('participants', 'school_name')) {
$table->string('school_name', 150)->nullable()->after('jersey_name');
}
if (! Schema::hasColumn('participants', 'previous_clubs')) {
$table->text('previous_clubs')->nullable()->after('school_name');
}
});
}
public function down(): void
{
if (! Schema::hasTable('participants')) {
return;
}
Schema::table('participants', function (Blueprint $table) {
foreach (['jersey_name', 'school_name', 'previous_clubs'] as $column) {
if (Schema::hasColumn('participants', $column)) {
$table->dropColumn($column);
}
}
});
}
};
......@@ -275,11 +275,29 @@ class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2
<input type="text" wire:model="jersey_size" dir="ltr" placeholder="XL"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الاسم على القميص') }}</label>
<input type="text" wire:model="jersey_name" maxlength="50" placeholder="{{ __('مثال: صلاح') }}"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('jersey_name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('مقاس الحذاء') }}</label>
<input type="text" wire:model="shoe_size" dir="ltr" placeholder="42"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('المدرسة') }}</label>
<input type="text" wire:model="school_name" maxlength="150"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('school_name') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('الأندية السابقة') }}</label>
<textarea wire:model="previous_clubs" rows="2" maxlength="1000"
class="w-full px-4 py-2.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
@error('previous_clubs') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اليد المسيطرة') }}</label>
<select wire:model="dominant_hand"
......
......@@ -363,10 +363,22 @@ class="pb-3 border-b-2 text-sm font-medium transition whitespace-nowrap">
<dt class="text-sm text-gray-500">{{ __('مقاس القميص') }}</dt>
<dd class="text-sm text-gray-800">{{ $participant->jersey_size ?? '—' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-sm text-gray-500">{{ __('الاسم على القميص') }}</dt>
<dd class="text-sm text-gray-800">{{ $participant->jersey_name ?? '—' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-sm text-gray-500">{{ __('مقاس الحذاء') }}</dt>
<dd class="text-sm text-gray-800">{{ $participant->shoe_size ?? '—' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-sm text-gray-500">{{ __('المدرسة') }}</dt>
<dd class="text-sm text-gray-800">{{ $participant->school_name ?? '—' }}</dd>
</div>
<div class="flex justify-between gap-4">
<dt class="text-sm text-gray-500 shrink-0">{{ __('الأندية السابقة') }}</dt>
<dd class="text-sm text-gray-800 text-end">{{ $participant->previous_clubs ?? '—' }}</dd>
</div>
<div class="flex justify-between">
<dt class="text-sm text-gray-500">{{ __('اليد المسيطرة') }}</dt>
<dd class="text-sm text-gray-800">
......
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