Commit 4e5b3052 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Simplify membership to member/non_member with club membership_id

- membership_type now only allows 'member' or 'non_member'
- Members must provide membership_id (club card number)
- membership_id is unique per branch (same card can't register twice)
- Shows membership_id input conditionally when type is 'member'
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 50381478
...@@ -4,9 +4,14 @@ ...@@ -4,9 +4,14 @@
enum MembershipType: string enum MembershipType: string
{ {
case Basic = 'basic'; case Member = 'member';
case Silver = 'silver'; case NonMember = 'non_member';
case Gold = 'gold';
case Platinum = 'platinum'; public function label(): string
case Custom = 'custom'; {
return match ($this) {
self::Member => 'عضو',
self::NonMember => 'غير عضو',
};
}
} }
...@@ -48,6 +48,7 @@ class Participant extends Model ...@@ -48,6 +48,7 @@ class Participant extends Model
'status_changed_at', 'status_changed_at',
'status_reason', 'status_reason',
'membership_type', 'membership_type',
'membership_id',
'membership_expires_at', 'membership_expires_at',
'total_paid', 'total_paid',
'outstanding_balance', 'outstanding_balance',
......
...@@ -74,6 +74,7 @@ public function register(array $data, User $actor): Participant ...@@ -74,6 +74,7 @@ public function register(array $data, User $actor): Participant
'status' => 'registered', 'status' => 'registered',
'status_changed_at' => now(), 'status_changed_at' => now(),
'membership_type' => $data['membership_type'] ?? null, 'membership_type' => $data['membership_type'] ?? null,
'membership_id' => $data['membership_id'] ?? null,
'notes' => $data['notes'] ?? null, 'notes' => $data['notes'] ?? null,
'metadata' => $data['metadata'] ?? [], 'metadata' => $data['metadata'] ?? [],
'created_by' => $actor->id, 'created_by' => $actor->id,
......
...@@ -36,6 +36,7 @@ class ParticipantForm extends Component ...@@ -36,6 +36,7 @@ class ParticipantForm extends Component
public ?int $primary_guardian_id = null; public ?int $primary_guardian_id = null;
public ?string $skill_level = null; public ?string $skill_level = null;
public ?string $membership_type = null; public ?string $membership_type = null;
public string $membership_id = '';
public bool $medical_clearance = false; public bool $medical_clearance = false;
public ?string $medical_clearance_expires = null; public ?string $medical_clearance_expires = null;
public ?int $jersey_number = null; public ?int $jersey_number = null;
...@@ -63,6 +64,7 @@ public function mount(?Participant $participant = null): void ...@@ -63,6 +64,7 @@ public function mount(?Participant $participant = null): void
$this->primary_guardian_id = $participant->primary_guardian_id; $this->primary_guardian_id = $participant->primary_guardian_id;
$this->skill_level = $participant->skill_level?->value ?? $participant->skill_level; $this->skill_level = $participant->skill_level?->value ?? $participant->skill_level;
$this->membership_type = $participant->membership_type?->value ?? $participant->membership_type; $this->membership_type = $participant->membership_type?->value ?? $participant->membership_type;
$this->membership_id = $participant->membership_id ?? '';
$this->medical_clearance = $participant->medical_clearance; $this->medical_clearance = $participant->medical_clearance;
$this->medical_clearance_expires = $participant->medical_clearance_expires?->format('Y-m-d'); $this->medical_clearance_expires = $participant->medical_clearance_expires?->format('Y-m-d');
$this->jersey_number = $participant->jersey_number; $this->jersey_number = $participant->jersey_number;
...@@ -83,7 +85,10 @@ public function rules(): array ...@@ -83,7 +85,10 @@ public function rules(): array
'primary_activity_id' => 'nullable|exists:activities,id', 'primary_activity_id' => 'nullable|exists:activities,id',
'primary_guardian_id' => 'nullable|exists:guardians,id', 'primary_guardian_id' => 'nullable|exists:guardians,id',
'skill_level' => 'nullable|in:beginner,intermediate,advanced,professional', 'skill_level' => 'nullable|in:beginner,intermediate,advanced,professional',
'membership_type' => 'nullable|in:basic,silver,gold,platinum,custom', 'membership_type' => 'nullable|in:member,non_member',
'membership_id' => $this->membership_type === 'member'
? 'required|string|max:50'
: 'nullable|string|max:50',
'medical_clearance' => 'boolean', 'medical_clearance' => 'boolean',
'medical_clearance_expires' => 'nullable|date', 'medical_clearance_expires' => 'nullable|date',
'jersey_number' => 'nullable|integer|min:0|max:999', 'jersey_number' => 'nullable|integer|min:0|max:999',
...@@ -120,6 +125,8 @@ public function messages(): array ...@@ -120,6 +125,8 @@ public function messages(): array
'registration_source.in' => 'مصدر التسجيل غير صالح', 'registration_source.in' => 'مصدر التسجيل غير صالح',
'skill_level.in' => 'المستوى غير صالح', 'skill_level.in' => 'المستوى غير صالح',
'membership_type.in' => 'نوع العضوية غير صالح', 'membership_type.in' => 'نوع العضوية غير صالح',
'membership_id.required' => 'رقم عضوية النادي مطلوب للأعضاء',
'membership_id.max' => 'رقم العضوية يجب ألا يتجاوز 50 حرف',
'dominant_hand.in' => 'اليد المسيطرة غير صالحة', 'dominant_hand.in' => 'اليد المسيطرة غير صالحة',
'dominant_foot.in' => 'القدم المسيطرة غير صالحة', 'dominant_foot.in' => 'القدم المسيطرة غير صالحة',
'height_cm.min' => 'الطول يجب أن يكون 50 سم على الأقل', 'height_cm.min' => 'الطول يجب أن يكون 50 سم على الأقل',
...@@ -138,13 +145,28 @@ public function save(ParticipantService $service): void ...@@ -138,13 +145,28 @@ public function save(ParticipantService $service): void
{ {
$this->validate(); $this->validate();
// Validate membership_id uniqueness per branch
if ($this->membership_type === 'member' && $this->membership_id) {
$branchId = $this->branch_id ?? auth()->user()->branch_id;
$exists = Participant::where('branch_id', $branchId)
->where('membership_id', $this->membership_id)
->when($this->editing, fn ($q) => $q->where('id', '!=', $this->participant->id))
->exists();
if ($exists) {
$this->addError('membership_id', 'رقم العضوية مستخدم بالفعل لمشترك آخر');
return;
}
}
try { try {
if ($this->editing) { if ($this->editing) {
$service->update($this->participant, [ $service->update($this->participant, [
'primary_activity_id' => $this->primary_activity_id, 'primary_activity_id' => $this->primary_activity_id,
'primary_guardian_id' => $this->primary_guardian_id, 'primary_guardian_id' => $this->primary_guardian_id,
'skill_level' => $this->skill_level, 'skill_level' => $this->skill_level,
'membership_type' => $this->membership_type, 'membership_type' => $this->membership_type ?: null,
'membership_id' => $this->membership_type === 'member' ? $this->membership_id : null,
'medical_clearance' => $this->medical_clearance, 'medical_clearance' => $this->medical_clearance,
'medical_clearance_expires' => $this->medical_clearance_expires ?: null, 'medical_clearance_expires' => $this->medical_clearance_expires ?: null,
'jersey_number' => $this->jersey_number, 'jersey_number' => $this->jersey_number,
...@@ -165,7 +187,8 @@ public function save(ParticipantService $service): void ...@@ -165,7 +187,8 @@ public function save(ParticipantService $service): void
'primary_activity_id' => $this->primary_activity_id, 'primary_activity_id' => $this->primary_activity_id,
'primary_guardian_id' => $this->primary_guardian_id, 'primary_guardian_id' => $this->primary_guardian_id,
'skill_level' => $this->skill_level, 'skill_level' => $this->skill_level,
'membership_type' => $this->membership_type, 'membership_type' => $this->membership_type ?: null,
'membership_id' => $this->membership_type === 'member' ? $this->membership_id : null,
'medical_clearance' => $this->medical_clearance, 'medical_clearance' => $this->medical_clearance,
'medical_clearance_expires' => $this->medical_clearance_expires ?: null, 'medical_clearance_expires' => $this->medical_clearance_expires ?: null,
'jersey_number' => $this->jersey_number, 'jersey_number' => $this->jersey_number,
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// 1. Drop old constraint
DB::statement("ALTER TABLE participants DROP CONSTRAINT IF EXISTS participants_membership_type_check");
// 2. Reset any existing membership_type values to null (clean slate)
DB::table('participants')->whereNotNull('membership_type')->update(['membership_type' => null]);
// 3. Add new constraint: member or non_member only
DB::statement("ALTER TABLE participants ADD CONSTRAINT participants_membership_type_check CHECK ((membership_type IS NULL) OR (membership_type IN ('member', 'non_member')))");
// 4. Add membership_id column (club membership number)
Schema::table('participants', function (Blueprint $table) {
$table->string('membership_id', 50)->nullable()->after('membership_type');
});
// 5. Unique membership_id per branch (can't reuse same club ID within a branch)
DB::statement("CREATE UNIQUE INDEX participants_branch_membership_id_unique ON participants (branch_id, membership_id) WHERE membership_id IS NOT NULL AND deleted_at IS NULL");
}
public function down(): void
{
DB::statement("DROP INDEX IF EXISTS participants_branch_membership_id_unique");
Schema::table('participants', function (Blueprint $table) {
$table->dropColumn('membership_id');
});
DB::statement("ALTER TABLE participants DROP CONSTRAINT IF EXISTS participants_membership_type_check");
DB::statement("ALTER TABLE participants ADD CONSTRAINT participants_membership_type_check CHECK ((membership_type IS NULL) OR ((membership_type)::text = ANY ((ARRAY['basic'::character varying, 'silver'::character varying, 'gold'::character varying, 'platinum'::character varying, 'custom'::character varying])::text[])))");
}
};
...@@ -140,17 +140,23 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -140,17 +140,23 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع العضوية') }}</label> <label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع العضوية') }} <span class="text-red-500">*</span></label>
<select wire:model="membership_type" <select wire:model.live="membership_type"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">{{ __('— غير محدد —') }}</option> <option value="">{{ __('— غير محدد —') }}</option>
<option value="basic">{{ __('أساسي') }}</option> <option value="member">{{ __('عضو') }}</option>
<option value="silver">{{ __('فضي') }}</option> <option value="non_member">{{ __('غير عضو') }}</option>
<option value="gold">{{ __('ذهبي') }}</option>
<option value="platinum">{{ __('بلاتيني') }}</option>
<option value="custom">{{ __('مخصص') }}</option>
</select> </select>
</div> </div>
@if($membership_type === 'member')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رقم عضوية النادي') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="membership_id" dir="ltr"
placeholder="{{ __('أدخل رقم العضوية') }}"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 @error('membership_id') border-red-500 @enderror">
@error('membership_id') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
</div>
@endif
</div> </div>
</div> </div>
......
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