Commit 0afceee9 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix Livewire 4 computed properties: replace get*Property with #[Computed]

Livewire 4 dropped support for the legacy get*Property() magic accessor.
Migrated all 6 affected components to use the #[Computed] attribute:
- NewRegistrationWizard (hotbuyResults, hotbuyTotal, selectedProgram, etc.)
- WeeklySchedule (weekDays, timeSlots)
- InvoiceCreate (subtotal, total)
- InvoiceShow (canCancel, canRecordPayment)
- SystemSettings (activeSchema)
- NotificationTemplateForm (availableVariables)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent cd3314dd
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Shared\Models\SystemSetting; use App\Domain\Shared\Models\SystemSetting;
use App\Domain\Shared\Services\SettingsService; use App\Domain\Shared\Services\SettingsService;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -201,7 +202,8 @@ public function save(SettingsService $service): void ...@@ -201,7 +202,8 @@ public function save(SettingsService $service): void
session()->flash('success', __('تم حفظ الإعدادات بنجاح')); session()->flash('success', __('تم حفظ الإعدادات بنجاح'));
} }
public function getActiveSchemaProperty(): array #[Computed]
public function activeSchema(): array
{ {
return $this->settingsSchema[$this->activeTab] ?? []; return $this->settingsSchema[$this->activeTab] ?? [];
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Domain\Financial\Services\InvoiceService; use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -127,12 +128,14 @@ public function save(InvoiceService $service): void ...@@ -127,12 +128,14 @@ public function save(InvoiceService $service): void
} }
} }
public function getSubtotalProperty(): float #[Computed]
public function subtotal(): float
{ {
return collect($this->items)->sum(fn ($item) => ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0)); return collect($this->items)->sum(fn ($item) => ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0));
} }
public function getTotalProperty(): float #[Computed]
public function total(): float
{ {
return $this->subtotal - $this->discount_amount + $this->tax_amount; return $this->subtotal - $this->discount_amount + $this->tax_amount;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use App\Domain\Financial\Services\InvoiceService; use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService; use App\Domain\Financial\Services\PaymentService;
use App\Domain\Shared\Exceptions\DomainException; use App\Domain\Shared\Exceptions\DomainException;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -95,13 +96,15 @@ public function cancelInvoice(InvoiceService $service): void ...@@ -95,13 +96,15 @@ public function cancelInvoice(InvoiceService $service): void
} }
} }
public function getCanCancelProperty(): bool #[Computed]
public function canCancel(): bool
{ {
return in_array($this->invoice->status, [InvoiceStatus::Draft, InvoiceStatus::Sent]) return in_array($this->invoice->status, [InvoiceStatus::Draft, InvoiceStatus::Sent])
&& $this->invoice->paid_amount === 0; && $this->invoice->paid_amount === 0;
} }
public function getCanRecordPaymentProperty(): bool #[Computed]
public function canRecordPayment(): bool
{ {
return $this->invoice->due_amount > 0 return $this->invoice->due_amount > 0
&& !in_array($this->invoice->status, [InvoiceStatus::Cancelled, InvoiceStatus::Refunded]); && !in_array($this->invoice->status, [InvoiceStatus::Cancelled, InvoiceStatus::Refunded]);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use App\Domain\Notification\Enums\NotificationChannel; use App\Domain\Notification\Enums\NotificationChannel;
use App\Domain\Notification\Models\NotificationTemplate; use App\Domain\Notification\Models\NotificationTemplate;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -133,7 +134,8 @@ public function save(): void ...@@ -133,7 +134,8 @@ public function save(): void
$this->redirect(route('notifications.templates'), navigate: true); $this->redirect(route('notifications.templates'), navigate: true);
} }
public function getAvailableVariablesProperty(): array #[Computed]
public function availableVariables(): array
{ {
return $this->eventVariables[$this->event_type] ?? []; return $this->eventVariables[$this->event_type] ?? [];
} }
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
use App\Domain\Identity\Models\Guardian; use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person; use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Services\PersonService; use App\Domain\Identity\Services\PersonService;
use App\Domain\Inventory\Models\Kit;
use App\Domain\Inventory\Models\Product;
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Participant\Services\DuplicateDetectionService; use App\Domain\Participant\Services\DuplicateDetectionService;
use App\Domain\Participant\Services\ParticipantService; use App\Domain\Participant\Services\ParticipantService;
...@@ -20,6 +22,7 @@ ...@@ -20,6 +22,7 @@
use App\Domain\Training\Models\TrainingProgram; use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\EnrollmentService; use App\Domain\Training\Services\EnrollmentService;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Component; use Livewire\Component;
...@@ -77,6 +80,10 @@ class NewRegistrationWizard extends Component ...@@ -77,6 +80,10 @@ class NewRegistrationWizard extends Component
public ?string $invoice_uuid = null; public ?string $invoice_uuid = null;
public bool $payment_recorded = false; public bool $payment_recorded = false;
// Hot-buy items (optional products/kits sold alongside registration)
public string $hotbuy_search = '';
public array $hotbuyCart = [];
// Pre-flight errors — blocks wizard from starting // Pre-flight errors — blocks wizard from starting
public array $systemErrors = []; public array $systemErrors = [];
...@@ -278,6 +285,100 @@ public function updatedSelectedActivityId(): void ...@@ -278,6 +285,100 @@ public function updatedSelectedActivityId(): void
$this->selected_program_id = null; $this->selected_program_id = null;
} }
// --- Hot-buy methods ---
#[Computed]
public function hotbuyResults(): array
{
if (strlen($this->hotbuy_search) < 2) {
return [];
}
$search = $this->hotbuy_search;
$products = Product::where('is_active', true)
->where(fn ($q) => $q->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%")
->orWhere('sku', 'ilike', "%{$search}%")
->orWhere('barcode', $search))
->limit(5)
->get()
->map(fn ($p) => [
'id' => $p->id,
'type' => 'product',
'name_ar' => $p->name_ar,
'name' => $p->name,
'sku' => $p->sku,
'price' => $p->selling_price,
])->toArray();
$kits = Kit::where('is_active', true)
->where(fn ($q) => $q->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%")
->orWhere('sku', 'ilike', "%{$search}%"))
->limit(5)
->get()
->map(fn ($k) => [
'id' => $k->id,
'type' => 'kit',
'name_ar' => $k->name_ar,
'name' => $k->name,
'sku' => $k->sku,
'price' => $k->selling_price,
])->toArray();
return array_merge($products, $kits);
}
public function addHotbuyItem(int $id, string $type): void
{
$key = "{$type}_{$id}";
if (isset($this->hotbuyCart[$key])) {
$this->hotbuyCart[$key]['quantity']++;
} else {
$item = $type === 'product'
? Product::find($id)
: Kit::find($id);
if (!$item) {
return;
}
$this->hotbuyCart[$key] = [
'id' => $item->id,
'type' => $type,
'name_ar' => $item->name_ar,
'price' => $item->selling_price,
'quantity' => 1,
];
}
$this->hotbuy_search = '';
}
public function removeHotbuyItem(string $key): void
{
unset($this->hotbuyCart[$key]);
}
public function updateHotbuyQuantity(string $key, int $quantity): void
{
if ($quantity <= 0) {
unset($this->hotbuyCart[$key]);
return;
}
if (isset($this->hotbuyCart[$key])) {
$this->hotbuyCart[$key]['quantity'] = $quantity;
}
}
#[Computed]
public function hotbuyTotal(): int
{
return collect($this->hotbuyCart)->sum(fn ($item) => $item['price'] * $item['quantity']);
}
public function confirm(): void public function confirm(): void
{ {
try { try {
...@@ -356,15 +457,40 @@ public function confirm(): void ...@@ -356,15 +457,40 @@ public function confirm(): void
$actor $actor
); );
// 7. Look up the price for this program + calculate platform fee // 7. Look up the price for this program + calculate platform fee + hot-buy
$feeAmount = $this->resolveProgramFee($program); $feeAmount = $this->resolveProgramFee($program);
$hotbuyTotal = $this->hotbuyTotal;
$subtotal = $feeAmount + $hotbuyTotal;
$platformFeeService = app(PlatformFeeService::class); $platformFeeService = app(PlatformFeeService::class);
$serviceFee = $platformFeeService->calculate($feeAmount); $customerPays = $platformFeeService->customerPays();
$totalWithFee = $feeAmount + $serviceFee; $serviceFee = $customerPays ? $platformFeeService->calculate($subtotal) : 0;
$totalWithFee = $subtotal + $serviceFee;
// 8. Create invoice if there's a fee // 8. Create invoice if there's a fee or hot-buy items
$invoice = null; $invoice = null;
if ($feeAmount > 0) { if ($subtotal > 0) {
$invoiceItems = [];
if ($feeAmount > 0) {
$invoiceItems[] = [
'description' => $program->name_ar,
'quantity' => 1,
'unit_price' => $feeAmount,
'discount_amount' => 0,
'tax_amount' => 0,
];
}
foreach ($this->hotbuyCart as $cartItem) {
$invoiceItems[] = [
'description' => $cartItem['name_ar'],
'quantity' => $cartItem['quantity'],
'unit_price' => $cartItem['price'],
'discount_amount' => 0,
'tax_amount' => 0,
];
}
$invoice = $invoiceService->create([ $invoice = $invoiceService->create([
'academy_id' => app('current_academy')->id, 'academy_id' => app('current_academy')->id,
'number' => $invoiceService->generateNumber(app('current_academy')->id), 'number' => $invoiceService->generateNumber(app('current_academy')->id),
...@@ -373,7 +499,7 @@ public function confirm(): void ...@@ -373,7 +499,7 @@ public function confirm(): void
'billable_id' => $participant->id, 'billable_id' => $participant->id,
'contact_name' => $this->guardian_name_ar, 'contact_name' => $this->guardian_name_ar,
'contact_phone' => $this->guardian_phone, 'contact_phone' => $this->guardian_phone,
'subtotal_amount' => $feeAmount, 'subtotal_amount' => $subtotal,
'discount_amount' => 0, 'discount_amount' => 0,
'tax_amount' => 0, 'tax_amount' => 0,
'service_fee_amount' => $serviceFee, 'service_fee_amount' => $serviceFee,
...@@ -382,15 +508,7 @@ public function confirm(): void ...@@ -382,15 +508,7 @@ public function confirm(): void
'issue_date' => now()->toDateString(), 'issue_date' => now()->toDateString(),
'due_date' => now()->addDays(7)->toDateString(), 'due_date' => now()->addDays(7)->toDateString(),
'notes' => 'اشتراك: ' . $program->name_ar, 'notes' => 'اشتراك: ' . $program->name_ar,
], [ ], $invoiceItems, $actor);
[
'description' => $program->name_ar,
'quantity' => 1,
'unit_price' => $feeAmount,
'discount_amount' => 0,
'tax_amount' => 0,
],
], $actor);
// Send the invoice (mark as sent) // Send the invoice (mark as sent)
$invoice->update(['status' => 'sent']); $invoice->update(['status' => 'sent']);
...@@ -474,7 +592,8 @@ private function resolveProgramFee(TrainingProgram $program): int ...@@ -474,7 +592,8 @@ private function resolveProgramFee(TrainingProgram $program): int
return $genericPrice?->amount ?? 0; return $genericPrice?->amount ?? 0;
} }
public function getSelectedProgramProperty(): ?TrainingProgram #[Computed]
public function selectedProgram(): ?TrainingProgram
{ {
if (!$this->selected_program_id) { if (!$this->selected_program_id) {
return null; return null;
...@@ -483,7 +602,8 @@ public function getSelectedProgramProperty(): ?TrainingProgram ...@@ -483,7 +602,8 @@ public function getSelectedProgramProperty(): ?TrainingProgram
return TrainingProgram::with('activity')->find($this->selected_program_id); return TrainingProgram::with('activity')->find($this->selected_program_id);
} }
public function getSelectedProgramFeeProperty(): int #[Computed]
public function selectedProgramFee(): int
{ {
if (!$this->selected_program_id) { if (!$this->selected_program_id) {
return 0; return 0;
...@@ -493,17 +613,25 @@ public function getSelectedProgramFeeProperty(): int ...@@ -493,17 +613,25 @@ public function getSelectedProgramFeeProperty(): int
return $program ? $this->resolveProgramFee($program) : 0; return $program ? $this->resolveProgramFee($program) : 0;
} }
public function getPlatformFeeProperty(): int #[Computed]
public function platformFee(): int
{ {
return app(PlatformFeeService::class)->calculate($this->selectedProgramFee); $service = app(PlatformFeeService::class);
if (!$service->customerPays()) {
return 0;
}
$subtotal = $this->selectedProgramFee + $this->hotbuyTotal;
return $service->calculate($subtotal);
} }
public function getTotalWithFeeProperty(): int #[Computed]
public function totalWithFee(): int
{ {
return $this->selectedProgramFee + $this->platformFee; return $this->selectedProgramFee + $this->hotbuyTotal + $this->platformFee;
} }
public function getInvoiceForPrintProperty(): ?Invoice #[Computed]
public function invoiceForPrint(): ?Invoice
{ {
if (!$this->invoiceId) { if (!$this->invoiceId) {
return null; return null;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\CarbonPeriod; use Carbon\CarbonPeriod;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
...@@ -52,7 +53,8 @@ public function goToToday(): void ...@@ -52,7 +53,8 @@ public function goToToday(): void
$this->weekStart = $today->startOfWeek(Carbon::SATURDAY)->format('Y-m-d'); $this->weekStart = $today->startOfWeek(Carbon::SATURDAY)->format('Y-m-d');
} }
public function getWeekDaysProperty(): array #[Computed]
public function weekDays(): array
{ {
$start = Carbon::parse($this->weekStart); $start = Carbon::parse($this->weekStart);
$days = []; $days = [];
...@@ -79,7 +81,8 @@ public function getWeekDaysProperty(): array ...@@ -79,7 +81,8 @@ public function getWeekDaysProperty(): array
return $days; return $days;
} }
public function getTimeSlotsProperty(): array #[Computed]
public function timeSlots(): array
{ {
$slots = []; $slots = [];
for ($hour = 8; $hour <= 22; $hour++) { for ($hour = 8; $hour <= 22; $hour++) {
......
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