Commit 22d54f2b authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add member/non-member pricing fields to program form

Prices are stored as base_prices with metadata.membership_type.
Shows two fields: member price and non-member price (EGP).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 0618b998
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace App\Livewire\Programs; namespace App\Livewire\Programs;
use App\Domain\Identity\Models\Branch; use App\Domain\Identity\Models\Branch;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Training\Models\Activity; use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram; use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingProgramService; use App\Domain\Training\Services\TrainingProgramService;
...@@ -48,6 +49,10 @@ class ProgramForm extends Component ...@@ -48,6 +49,10 @@ class ProgramForm extends Component
public string $status = 'draft'; public string $status = 'draft';
public bool $featured = false; public bool $featured = false;
// Pricing
public string $member_price = '';
public string $non_member_price = '';
public function mount(?TrainingProgram $program = null): void public function mount(?TrainingProgram $program = null): void
{ {
if ($program && $program->exists) { if ($program && $program->exists) {
...@@ -82,6 +87,21 @@ public function mount(?TrainingProgram $program = null): void ...@@ -82,6 +87,21 @@ public function mount(?TrainingProgram $program = null): void
$this->refund_policy = $program->refund_policy ?? ''; $this->refund_policy = $program->refund_policy ?? '';
$this->status = $program->status->value ?? $program->status; $this->status = $program->status->value ?? $program->status;
$this->featured = $program->featured; $this->featured = $program->featured;
// Load existing prices
$prices = BasePrice::where('priceable_type', TrainingProgram::class)
->where('priceable_id', $program->id)
->where('is_active', true)
->get();
foreach ($prices as $price) {
$meta = $price->metadata ?? [];
if (($meta['membership_type'] ?? null) === 'member') {
$this->member_price = number_format($price->amount / 100, 2, '.', '');
} elseif (($meta['membership_type'] ?? null) === 'non_member') {
$this->non_member_price = number_format($price->amount / 100, 2, '.', '');
}
}
} }
} }
...@@ -116,6 +136,8 @@ public function rules(): array ...@@ -116,6 +136,8 @@ public function rules(): array
'cancellation_policy' => 'nullable|string', 'cancellation_policy' => 'nullable|string',
'refund_policy' => 'nullable|string', 'refund_policy' => 'nullable|string',
'featured' => 'boolean', 'featured' => 'boolean',
'member_price' => 'nullable|numeric|min:0',
'non_member_price' => 'nullable|numeric|min:0',
]; ];
} }
...@@ -148,6 +170,10 @@ public function messages(): array ...@@ -148,6 +170,10 @@ public function messages(): array
'program_end_date.after_or_equal' => 'تاريخ انتهاء البرنامج يجب أن يكون بعد تاريخ البداية', 'program_end_date.after_or_equal' => 'تاريخ انتهاء البرنامج يجب أن يكون بعد تاريخ البداية',
'renewal_policy.required' => 'سياسة التجديد مطلوبة', 'renewal_policy.required' => 'سياسة التجديد مطلوبة',
'renewal_policy.in' => 'سياسة التجديد غير صالحة', 'renewal_policy.in' => 'سياسة التجديد غير صالحة',
'member_price.numeric' => 'سعر العضو يجب أن يكون رقم',
'member_price.min' => 'سعر العضو يجب ألا يكون سالب',
'non_member_price.numeric' => 'سعر غير العضو يجب أن يكون رقم',
'non_member_price.min' => 'سعر غير العضو يجب ألا يكون سالب',
]; ];
} }
...@@ -189,9 +215,11 @@ public function save(TrainingProgramService $service): void ...@@ -189,9 +215,11 @@ public function save(TrainingProgramService $service): void
if ($this->editing) { if ($this->editing) {
$service->update($this->program, $data); $service->update($this->program, $data);
$this->savePrices($this->program);
session()->flash('success', __('تم تحديث البرنامج بنجاح')); session()->flash('success', __('تم تحديث البرنامج بنجاح'));
} else { } else {
$service->create($data, auth()->user()); $program = $service->create($data, auth()->user());
$this->savePrices($program);
session()->flash('success', __('تم إنشاء البرنامج بنجاح')); session()->flash('success', __('تم إنشاء البرنامج بنجاح'));
} }
...@@ -201,6 +229,47 @@ public function save(TrainingProgramService $service): void ...@@ -201,6 +229,47 @@ public function save(TrainingProgramService $service): void
} }
} }
private function savePrices(TrainingProgram $program): void
{
$types = [
'member' => $this->member_price,
'non_member' => $this->non_member_price,
];
foreach ($types as $membershipType => $priceDisplay) {
$amount = $priceDisplay ? (int) round((float) $priceDisplay * 100) : null;
$existing = BasePrice::where('priceable_type', TrainingProgram::class)
->where('priceable_id', $program->id)
->where('metadata->membership_type', $membershipType)
->first();
if ($amount && $amount > 0) {
if ($existing) {
$existing->update(['amount' => $amount, 'is_active' => true]);
} else {
BasePrice::create([
'academy_id' => $program->academy_id,
'priceable_type' => TrainingProgram::class,
'priceable_id' => $program->id,
'branch_id' => $program->branch_id,
'name_ar' => $membershipType === 'member' ? 'سعر العضو' : 'سعر غير العضو',
'name' => $membershipType === 'member' ? 'Member Price' : 'Non-Member Price',
'amount' => $amount,
'currency' => 'EGP',
'effective_from' => now()->toDateString(),
'is_active' => true,
'priority' => $membershipType === 'member' ? 1 : 2,
'created_by' => auth()->id(),
'metadata' => ['membership_type' => $membershipType],
]);
}
} elseif ($existing) {
$existing->update(['is_active' => false]);
}
}
}
public function render() public function render()
{ {
return view('livewire.programs.program-form', [ return view('livewire.programs.program-form', [
......
...@@ -176,6 +176,29 @@ class="text-sm text-gray-600 hover:text-gray-800">{{ __('← العودة للق ...@@ -176,6 +176,29 @@ class="text-sm text-gray-600 hover:text-gray-800">{{ __('← العودة للق
</div> </div>
</div> </div>
{{-- Pricing --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4 border-b border-gray-100 pb-2">{{ __('الأسعار') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر العضو (ج.م)') }}</label>
<input type="number" wire:model="member_price" dir="ltr" step="0.01" min="0"
placeholder="0.00"
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('member_price') border-red-500 @enderror">
@error('member_price') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
<p class="text-xs text-gray-500 mt-1">{{ __('السعر لأعضاء النادي') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سعر غير العضو (ج.م)') }}</label>
<input type="number" wire:model="non_member_price" dir="ltr" step="0.01" min="0"
placeholder="0.00"
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('non_member_price') border-red-500 @enderror">
@error('non_member_price') <p class="text-red-500 text-xs mt-1">{{ $message }}</p> @enderror
<p class="text-xs text-gray-500 mt-1">{{ __('السعر لغير الأعضاء') }}</p>
</div>
</div>
</div>
{{-- Submit --}} {{-- Submit --}}
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button type="submit" wire:loading.attr="disabled" wire:target="save" <button type="submit" wire:loading.attr="disabled" wire:target="save"
......
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