Commit e8ed93cf authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add membership type (member/non_member) to registration wizard with price differentiation

- Step 2 now shows member vs non-member toggle + membership_id field
- Price resolution checks base_prices.metadata->membership_type first,
  falls back to generic price if no membership-specific price exists
- Membership type saved to participant record
- Review step shows membership status
- Computed property selectedProgramFee updates live when membership changes
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent b01d4577
......@@ -47,6 +47,8 @@ class NewRegistrationWizard extends Component
public string $participant_phone = '';
public string $participant_national_id = '';
public string $participant_medical_notes = '';
public string $membership_type = 'non_member';
public string $membership_id = '';
// Step 3: Program selection
public ?int $selected_activity_id = null;
......@@ -109,6 +111,8 @@ private function rulesForStep(int $step): array
'participant_phone' => 'nullable|string|max:20',
'participant_national_id' => 'nullable|string|max:14',
'participant_medical_notes' => 'nullable|string|max:1000',
'membership_type' => 'required|in:member,non_member',
'membership_id' => 'required_if:membership_type,member|nullable|string|max:50',
],
3 => [
'selected_activity_id' => 'required|exists:activities,id',
......@@ -137,6 +141,9 @@ public function messages(): array
'selected_activity_id.exists' => 'النشاط المختار غير موجود',
'selected_program_id.required' => 'يرجى اختيار البرنامج',
'selected_program_id.exists' => 'البرنامج المختار غير موجود',
'membership_type.required' => 'نوع العضوية مطلوب',
'membership_type.in' => 'نوع العضوية غير صالح',
'membership_id.required_if' => 'رقم عضوية النادي مطلوب للأعضاء',
'payment_method.required_if' => 'يرجى اختيار طريقة الدفع',
'payment_method.in' => 'طريقة الدفع غير صالحة',
];
......@@ -273,6 +280,8 @@ public function confirm(): void
'registration_source' => 'walk_in',
'primary_guardian_id' => $guardian->id,
'primary_activity_id' => $this->selected_activity_id,
'membership_type' => $this->membership_type,
'membership_id' => $this->membership_type === 'member' ? $this->membership_id : null,
], $actor);
// 5. Link guardian to participant via pivot
......@@ -372,16 +381,34 @@ public function confirm(): void
private function resolveProgramFee(TrainingProgram $program): int
{
$basePrice = BasePrice::where('priceable_type', TrainingProgram::class)
$query = BasePrice::where('priceable_type', TrainingProgram::class)
->where('priceable_id', $program->id)
->where('is_active', true)
->where('effective_from', '<=', now())
->where(fn ($q) => $q->whereNull('effective_to')->orWhere('effective_to', '>=', now()))
->forBranch($this->branchId)
->forBranch($this->branchId);
// Try membership-specific price first
$specificPrice = (clone $query)
->whereJsonContains('metadata->membership_type', $this->membership_type)
->orderByDesc('priority')
->first();
return $basePrice?->amount ?? 0;
if ($specificPrice) {
return $specificPrice->amount;
}
// Fall back to generic price (no membership_type in metadata)
$genericPrice = $query
->where(fn ($q) => $q
->whereNull('metadata->membership_type')
->orWhereRaw("metadata->>'membership_type' = ''")
->orWhereRaw("metadata = '{}'::jsonb")
)
->orderByDesc('priority')
->first();
return $genericPrice?->amount ?? 0;
}
public function getSelectedProgramProperty(): ?TrainingProgram
......@@ -400,11 +427,7 @@ public function getSelectedProgramFeeProperty(): int
}
$program = TrainingProgram::find($this->selected_program_id);
if (!$program) {
return 0;
}
return $this->resolveProgramFee($program);
return $program ? $this->resolveProgramFee($program) : 0;
}
public function render()
......
......@@ -251,6 +251,41 @@ class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2
@error('participant_national_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Membership Type --}}
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نوع العضوية') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 gap-3">
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="non_member" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-amber-500 peer-checked:bg-amber-50 peer-checked:text-amber-700
hover:border-gray-400">
{{ __('غير عضو') }}
</div>
</label>
<label class="relative cursor-pointer">
<input type="radio" wire:model.live="membership_type" value="member" class="peer sr-only">
<div class="px-4 py-3 min-h-16 flex items-center justify-center border border-gray-300 rounded-lg text-center text-base font-medium transition-all
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:text-green-700
hover:border-gray-400">
{{ __('عضو نادي') }}
</div>
</label>
</div>
@error('membership_type') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Membership ID (only for members) --}}
@if($membership_type === 'member')
<div class="sm:col-span-2">
<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"
class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-lg"
placeholder="{{ __('أدخل رقم العضوية') }}">
@error('membership_id') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
</div>
@endif
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ملاحظات طبية') }}</label>
<textarea wire:model="participant_medical_notes" rows="3"
......@@ -423,6 +458,15 @@ class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-blue-600 text-white
<span class="font-medium text-gray-800 ms-1" dir="ltr">{{ $participant_national_id }}</span>
</div>
@endif
<div>
<span class="text-gray-500">{{ __('العضوية') }}:</span>
<span class="font-medium ms-1 {{ $membership_type === 'member' ? 'text-green-700' : 'text-amber-700' }}">
{{ $membership_type === 'member' ? __('عضو نادي') : __('غير عضو') }}
</span>
@if($membership_type === 'member' && $membership_id)
<span class="text-gray-500 ms-1">({{ $membership_id }})</span>
@endif
</div>
@if($participant_medical_notes)
<div class="col-span-2">
<span class="text-gray-500">{{ __('ملاحظات طبية') }}:</span>
......
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