Commit 7f4dbddd authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add annual products with dual pricing and installment plans

- New migration: billing_cycle (one_time/annual), member_price, non_member_price on products; product_installment_plans table (per-product, per-tier plan templates)
- ProductInstallmentPlan model with scopeActive/forTier, downPaymentAmount/regularInstallmentAmount helpers
- Product model: priceForTier(), isAnnual(), installmentPlans() relationship
- ProductForm: annual billing section with member/non-member dual prices and installment plan builder (add/remove rows, upsert on save)
- Registration wizard: essentialProducts() and hotbuyResults() now return billing_cycle and plans[]; addHotbuyItem() carries plans into cart; setHotbuyPlan() selects a plan per cart item; confirm() generates PaymentPlan + Installment rows for annual items with a selected plan
- Wizard blade: annual cart items show purple "سنوي" badge, plan pills (full payment + per-plan buttons), and installment breakdown preview on selection
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent b9239f68
......@@ -36,6 +36,9 @@ class Product extends Model
'track_inventory',
'is_active',
'is_essential',
'billing_cycle',
'member_price',
'non_member_price',
'min_stock_level',
'max_stock_level',
'weight_grams',
......@@ -53,6 +56,8 @@ protected function casts(): array
'type' => ProductType::class,
'selling_price' => 'integer',
'cost_price' => 'integer',
'member_price' => 'integer',
'non_member_price' => 'integer',
'track_inventory' => 'boolean',
'is_active' => 'boolean',
'is_essential' => 'boolean',
......@@ -66,6 +71,22 @@ protected function casts(): array
];
}
public function priceForTier(string $membershipType): int
{
if ($membershipType === 'member' && $this->member_price !== null) {
return $this->member_price;
}
if ($membershipType === 'non_member' && $this->non_member_price !== null) {
return $this->non_member_price;
}
return $this->selling_price;
}
public function isAnnual(): bool
{
return $this->billing_cycle === 'annual';
}
public function category(): BelongsTo
{
return $this->belongsTo(ProductCategory::class, 'category_id');
......@@ -86,6 +107,11 @@ public function inventoryLevels(): HasMany
return $this->hasMany(InventoryLevel::class);
}
public function installmentPlans(): HasMany
{
return $this->hasMany(ProductInstallmentPlan::class);
}
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
......
<?php
namespace App\Domain\Inventory\Models;
use App\Domain\Shared\Traits\BelongsToAcademy;
use App\Domain\Shared\Traits\HasUuid;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductInstallmentPlan extends Model
{
use BelongsToAcademy, HasUuid;
protected $fillable = [
'academy_id',
'product_id',
'membership_tier',
'label_ar',
'installments',
'frequency',
'down_payment_pct',
'is_active',
'sort_order',
];
protected function casts(): array
{
return [
'installments' => 'integer',
'down_payment_pct' => 'integer',
'is_active' => 'boolean',
'sort_order' => 'integer',
];
}
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeForTier(Builder $query, string $tier): Builder
{
return $query->where(fn ($q) =>
$q->where('membership_tier', $tier)->orWhere('membership_tier', 'any')
);
}
/** Calculate down payment amount in piasters */
public function downPaymentAmount(int $totalPiasters): int
{
if ($this->down_payment_pct <= 0) {
return (int) ceil($totalPiasters / $this->installments);
}
return (int) ceil($totalPiasters * $this->down_payment_pct / 100);
}
/** Calculate per-installment amount (after down payment) in piasters */
public function regularInstallmentAmount(int $totalPiasters): int
{
$remaining = $this->installments - 1;
if ($remaining <= 0) {
return 0;
}
$afterDown = $totalPiasters - $this->downPaymentAmount($totalPiasters);
return (int) ceil($afterDown / $remaining);
}
public function frequencyLabel(): string
{
return match ($this->frequency) {
'weekly' => 'أسبوعي',
'biweekly' => 'نصف شهري',
'monthly' => 'شهري',
'quarterly' => 'ربع سنوي',
default => $this->frequency,
};
}
public function tierLabel(): string
{
return match ($this->membership_tier) {
'member' => 'أعضاء',
'non_member' => 'غير أعضاء',
default => 'الجميع',
};
}
}
This diff is collapsed.
......@@ -2,9 +2,12 @@
namespace App\Livewire\Receptionist;
use App\Domain\Financial\Models\Installment;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\PaymentPlan;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Inventory\Models\ProductInstallmentPlan;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
......@@ -567,18 +570,33 @@ public function effectiveTotal(): int
#[Computed]
public function essentialProducts(): array
{
$tier = $this->membership_type;
return Product::where('is_active', true)
->where('is_essential', true)
->with(['installmentPlans' => fn ($q) => $q->active()->forTier($tier)->orderBy('sort_order')])
->orderBy('sort_order')
->orderBy('name_ar')
->get()
->map(fn ($p) => [
'id' => $p->id,
'type' => 'product',
'name_ar' => $p->name_ar,
'name' => $p->name,
'sku' => $p->sku,
'price' => $p->selling_price,
'id' => $p->id,
'type' => 'product',
'name_ar' => $p->name_ar,
'name' => $p->name,
'sku' => $p->sku,
'billing_cycle'=> $p->billing_cycle,
'price' => $p->priceForTier($tier),
'member_price' => $p->member_price,
'non_member_price' => $p->non_member_price,
'selling_price'=> $p->selling_price,
'plans' => $p->installmentPlans->map(fn ($pl) => [
'id' => $pl->id,
'label_ar' => $pl->label_ar,
'installments' => $pl->installments,
'frequency' => $pl->frequency,
'frequency_label' => $pl->frequencyLabel(),
'down_payment_pct' => $pl->down_payment_pct,
])->toArray(),
])->toArray();
}
......@@ -591,20 +609,32 @@ public function hotbuyResults(): array
$search = $this->hotbuy_search;
$tier = $this->membership_type;
$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))
->with(['installmentPlans' => fn ($q) => $q->active()->forTier($tier)->orderBy('sort_order')])
->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,
'id' => $p->id,
'type' => 'product',
'name_ar' => $p->name_ar,
'name' => $p->name,
'sku' => $p->sku,
'billing_cycle' => $p->billing_cycle ?? 'one_time',
'price' => $p->priceForTier($tier),
'plans' => $p->installmentPlans->map(fn ($pl) => [
'id' => $pl->id,
'label_ar' => $pl->label_ar,
'installments' => $pl->installments,
'frequency' => $pl->frequency,
'frequency_label' => $pl->frequencyLabel(),
'down_payment_pct' => $pl->down_payment_pct,
])->toArray(),
])->toArray();
$kits = Kit::where('is_active', true)
......@@ -640,18 +670,50 @@ public function addHotbuyItem(int $id, string $type): void
return;
}
$price = ($type === 'product')
? $item->priceForTier($this->membership_type)
: $item->selling_price;
$plans = [];
if ($type === 'product' && ($item->billing_cycle ?? 'one_time') === 'annual') {
$tier = $this->membership_type;
$plans = $item->installmentPlans()
->active()
->forTier($tier)
->orderBy('sort_order')
->get()
->map(fn ($pl) => [
'id' => $pl->id,
'label_ar' => $pl->label_ar,
'installments' => $pl->installments,
'frequency' => $pl->frequency,
'frequency_label' => $pl->frequencyLabel(),
'down_payment_pct' => $pl->down_payment_pct,
])->toArray();
}
$this->hotbuyCart[$key] = [
'id' => $item->id,
'type' => $type,
'name_ar' => $item->name_ar,
'price' => $item->selling_price,
'quantity' => 1,
'id' => $item->id,
'type' => $type,
'name_ar' => $item->name_ar,
'price' => $price,
'billing_cycle' => $type === 'product' ? ($item->billing_cycle ?? 'one_time') : 'one_time',
'quantity' => 1,
'plan_id' => null,
'plans' => $plans,
];
}
$this->hotbuy_search = '';
}
public function setHotbuyPlan(string $key, ?int $planId): void
{
if (isset($this->hotbuyCart[$key])) {
$this->hotbuyCart[$key]['plan_id'] = $planId;
}
}
public function removeHotbuyItem(string $key): void
{
unset($this->hotbuyCart[$key]);
......@@ -963,6 +1025,55 @@ public function confirm(): void
$this->invoice_number = $invoice->number;
$this->invoiceId = $invoice->id;
// 8b. Create installment payment plans for annual products
foreach ($this->hotbuyCart as $cartItem) {
if (empty($cartItem['plan_id'])) {
continue;
}
$planTemplate = ProductInstallmentPlan::find($cartItem['plan_id']);
if (!$planTemplate) {
continue;
}
$itemTotal = $cartItem['price'] * $cartItem['quantity'];
$downAmount = $planTemplate->downPaymentAmount($itemTotal);
$regularAmount = $planTemplate->regularInstallmentAmount($itemTotal);
$remaining = $planTemplate->installments - 1;
$paymentPlan = PaymentPlan::create([
'academy_id' => app('current_academy')->id,
'invoice_id' => $invoice->id,
'status' => 'active',
'total_installments' => $planTemplate->installments,
'paid_installments' => 0,
'installment_amount' => $regularAmount,
'frequency' => $planTemplate->frequency,
'start_date' => now()->toDateString(),
'next_due_date' => now()->toDateString(),
'notes' => $planTemplate->label_ar . ' — ' . $cartItem['name_ar'],
]);
// Generate installment schedule
$dueDate = now();
for ($seq = 1; $seq <= $planTemplate->installments; $seq++) {
$amount = ($seq === 1) ? $downAmount : $regularAmount;
Installment::create([
'payment_plan_id' => $paymentPlan->id,
'sequence' => $seq,
'amount' => $amount,
'due_date' => $dueDate->toDateString(),
'status' => 'pending',
]);
// Advance due date by frequency
$dueDate = match ($planTemplate->frequency) {
'weekly' => $dueDate->addWeek(),
'biweekly' => $dueDate->addWeeks(2),
'quarterly' => $dueDate->addMonths(3),
default => $dueDate->addMonth(),
};
}
}
// 9. Record payment(s) if paying now
if ($this->pay_now) {
$basePaymentData = [
......
<?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
{
if (!Schema::hasColumn('products', 'billing_cycle')) {
Schema::table('products', function (Blueprint $table) {
$table->string('billing_cycle', 20)->default('one_time')->after('is_essential');
$table->bigInteger('member_price')->nullable()->after('billing_cycle');
$table->bigInteger('non_member_price')->nullable()->after('member_price');
});
DB::statement("ALTER TABLE products ADD CONSTRAINT products_billing_cycle_check CHECK (billing_cycle IN ('one_time', 'annual'))");
}
if (!Schema::hasTable('product_installment_plans')) {
Schema::create('product_installment_plans', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->foreignId('academy_id')->constrained()->cascadeOnDelete();
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
$table->string('membership_tier', 20)->default('any');
$table->string('label_ar');
$table->unsignedSmallInteger('installments');
$table->string('frequency', 20)->default('monthly');
$table->unsignedTinyInteger('down_payment_pct')->default(0);
$table->boolean('is_active')->default(true);
$table->unsignedSmallInteger('sort_order')->default(0);
$table->timestamps();
$table->index(['product_id', 'membership_tier', 'is_active']);
$table->index('academy_id');
});
DB::statement("ALTER TABLE product_installment_plans ADD CONSTRAINT product_installment_plans_tier_check CHECK (membership_tier IN ('member', 'non_member', 'any'))");
DB::statement("ALTER TABLE product_installment_plans ADD CONSTRAINT product_installment_plans_frequency_check CHECK (frequency IN ('weekly', 'biweekly', 'monthly', 'quarterly'))");
}
}
public function down(): void
{
Schema::dropIfExists('product_installment_plans');
if (Schema::hasColumn('products', 'billing_cycle')) {
DB::statement('ALTER TABLE products DROP CONSTRAINT IF EXISTS products_billing_cycle_check');
Schema::table('products', function (Blueprint $table) {
$table->dropColumn(['billing_cycle', 'member_price', 'non_member_price']);
});
}
}
};
......@@ -168,6 +168,132 @@ class="mt-0.5 w-4 h-4 rounded border-amber-400 text-amber-600 focus:ring-amber-5
</div>
</div>
{{-- Annual Billing Section --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6" x-data>
<div class="flex items-center justify-between mb-4">
<h2 class="text-base sm:text-lg font-semibold text-gray-700">{{ __('الفوترة السنوية والأقساط') }}</h2>
<span class="text-xs text-gray-400">{{ __('للخدمات السنوية كالتأمين والزي وما شابه') }}</span>
</div>
{{-- Billing Cycle Toggle --}}
<div class="flex gap-3 mb-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" wire:model.live="billing_cycle" value="one_time" class="text-blue-600">
<span class="text-sm font-medium text-gray-700">{{ __('دفعة واحدة') }}</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" wire:model.live="billing_cycle" value="annual" class="text-purple-600">
<span class="text-sm font-medium text-purple-700">{{ __('سنوي (مع دعم الأقساط)') }}</span>
</label>
</div>
@if($billing_cycle === 'annual')
{{-- Dual pricing --}}
<div class="p-4 bg-purple-50 border border-purple-200 rounded-xl mb-6">
<h3 class="text-sm font-semibold text-purple-800 mb-3">{{ __('الأسعار حسب نوع العضوية') }}</h3>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('السعر الأساسي (ج.م)') }} *</label>
<input type="number" wire:model="selling_price" step="0.01" min="0" dir="ltr"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 @error('selling_price') border-red-500 @enderror"
placeholder="{{ __('افتراضي') }}">
<p class="text-xs text-gray-400 mt-0.5">{{ __('يُستخدم إذا لم يحدد سعر خاص') }}</p>
</div>
<div>
<label class="block text-xs font-medium text-green-700 mb-1">{{ __('سعر الأعضاء (ج.م)') }}</label>
<input type="number" wire:model="member_price" step="0.01" min="0" dir="ltr"
class="w-full text-sm px-3 py-2 border border-green-300 rounded-lg focus:ring-2 focus:ring-green-500 @error('member_price') border-red-500 @enderror"
placeholder="{{ __('اتركه فارغاً للسعر الأساسي') }}">
@error('member_price') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-xs font-medium text-orange-700 mb-1">{{ __('سعر غير الأعضاء (ج.م)') }}</label>
<input type="number" wire:model="non_member_price" step="0.01" min="0" dir="ltr"
class="w-full text-sm px-3 py-2 border border-orange-300 rounded-lg focus:ring-2 focus:ring-orange-500 @error('non_member_price') border-red-500 @enderror"
placeholder="{{ __('اتركه فارغاً للسعر الأساسي') }}">
@error('non_member_price') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
</div>
</div>
{{-- Installment Plan Builder --}}
<div>
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-gray-700">{{ __('خطط التقسيط') }}</h3>
<button type="button" wire:click="addPlanRow"
class="inline-flex items-center gap-1.5 px-3 py-1.5 bg-purple-600 text-white text-xs font-medium rounded-lg hover:bg-purple-700">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ __('إضافة خطة') }}
</button>
</div>
@if(count($planRows) === 0)
<div class="py-6 text-center border-2 border-dashed border-gray-200 rounded-xl">
<p class="text-sm text-gray-400">{{ __('لا توجد خطط تقسيط — اضغط "إضافة خطة" لإنشاء واحدة') }}</p>
</div>
@else
<div class="space-y-3">
@foreach($planRows as $i => $row)
<div class="p-4 bg-gray-50 border border-gray-200 rounded-xl">
<div class="flex items-center justify-between mb-3">
<span class="text-xs font-semibold text-gray-500 uppercase">{{ __('خطة') }} {{ $i + 1 }}</span>
<button type="button" wire:click="removePlanRow({{ $i }})"
class="text-red-500 hover:text-red-700 text-xs">{{ __('حذف') }}</button>
</div>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{{-- Label --}}
<div class="col-span-2 sm:col-span-3 lg:col-span-2">
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('اسم الخطة') }} *</label>
<input type="text" wire:model="planRows.{{ $i }}.label_ar"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
placeholder="{{ __('مثال: 3 أقساط شهرية للأعضاء') }}">
@error("planRows.{$i}.label_ar") <p class="mt-0.5 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Tier --}}
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('للعضوية') }}</label>
<select wire:model="planRows.{{ $i }}.tier"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
<option value="any">{{ __('الجميع') }}</option>
<option value="member">{{ __('أعضاء فقط') }}</option>
<option value="non_member">{{ __('غير أعضاء') }}</option>
</select>
</div>
{{-- Installments count --}}
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('عدد الأقساط') }}</label>
<input type="number" wire:model="planRows.{{ $i }}.installments" min="2" max="24" dir="ltr"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
@error("planRows.{$i}.installments") <p class="mt-0.5 text-xs text-red-600">{{ $message }}</p> @enderror
</div>
{{-- Frequency --}}
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('الدورية') }}</label>
<select wire:model="planRows.{{ $i }}.frequency"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500">
<option value="monthly">{{ __('شهري') }}</option>
<option value="quarterly">{{ __('ربع سنوي') }}</option>
<option value="biweekly">{{ __('نصف شهري') }}</option>
<option value="weekly">{{ __('أسبوعي') }}</option>
</select>
</div>
{{-- Down payment % --}}
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('المقدم (%)') }}</label>
<input type="number" wire:model="planRows.{{ $i }}.down_payment_pct" min="0" max="100" dir="ltr"
class="w-full text-sm px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
placeholder="0">
<p class="text-xs text-gray-400 mt-0.5">{{ __('0 = موزع بالتساوي') }}</p>
</div>
</div>
</div>
@endforeach
</div>
@endif
</div>
@endif
</div>
{{-- Actions --}}
<div class="flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3">
<a href="{{ route('inventory.products') }}" wire:navigate
......
......@@ -756,26 +756,101 @@ class="w-full flex items-center justify-between px-4 py-3 text-start hover:bg-am
@endif
</div>
@if(count($hotbuyCart) > 0)
<div class="space-y-2">
<div class="space-y-3">
@foreach($hotbuyCart as $key => $cartItem)
<div class="flex items-center justify-between bg-white p-3 rounded-lg border border-gray-200">
<div class="flex-1">
<p class="text-sm font-medium text-gray-800">{{ $cartItem['name_ar'] }}</p>
<p class="text-xs text-gray-500" dir="ltr">{{ number_format($cartItem['price'] / 100, 2) }} {{ __('ج.م') }}</p>
@php
$isAnnual = ($cartItem['billing_cycle'] ?? 'one_time') === 'annual';
$cartPlans = ($isAnnual && $cartItem['type'] === 'product') ? ($cartItem['plans'] ?? []) : [];
@endphp
<div class="bg-white rounded-xl border {{ $isAnnual ? 'border-purple-200' : 'border-gray-200' }} overflow-hidden">
<div class="flex items-center justify-between p-3">
<div class="flex-1">
<div class="flex items-center gap-2">
<p class="text-sm font-medium text-gray-800">{{ $cartItem['name_ar'] }}</p>
@if($isAnnual)
<span class="px-1.5 py-0.5 text-xs rounded bg-purple-100 text-purple-700 font-medium">{{ __('سنوي') }}</span>
@endif
</div>
<p class="text-xs text-gray-500 mt-0.5" dir="ltr">{{ number_format($cartItem['price'] / 100, 2) }} {{ __('ج.م') }}
@if($isAnnual) × 1 {{ __('سنة') }} @endif
</p>
</div>
<div class="flex items-center gap-2">
@if(!$isAnnual)
<button type="button" wire:click="updateHotbuyQuantity('{{ $key }}', {{ $cartItem['quantity'] - 1 }})"
class="w-8 h-8 rounded-full bg-gray-200 text-gray-700 flex items-center justify-center hover:bg-gray-300 text-lg font-bold"></button>
<span class="w-8 text-center text-sm font-bold">{{ $cartItem['quantity'] }}</span>
<button type="button" wire:click="updateHotbuyQuantity('{{ $key }}', {{ $cartItem['quantity'] + 1 }})"
class="w-8 h-8 rounded-full bg-gray-200 text-gray-700 flex items-center justify-center hover:bg-gray-300 text-lg font-bold">+</button>
@endif
<button type="button" wire:click="removeHotbuyItem('{{ $key }}')"
class="w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-center hover:bg-red-200 {{ $isAnnual ? '' : 'ms-2' }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<div class="flex items-center gap-2">
<button type="button" wire:click="updateHotbuyQuantity('{{ $key }}', {{ $cartItem['quantity'] - 1 }})"
class="w-8 h-8 rounded-full bg-gray-200 text-gray-700 flex items-center justify-center hover:bg-gray-300 text-lg font-bold"></button>
<span class="w-8 text-center text-sm font-bold">{{ $cartItem['quantity'] }}</span>
<button type="button" wire:click="updateHotbuyQuantity('{{ $key }}', {{ $cartItem['quantity'] + 1 }})"
class="w-8 h-8 rounded-full bg-gray-200 text-gray-700 flex items-center justify-center hover:bg-gray-300 text-lg font-bold">+</button>
<button type="button" wire:click="removeHotbuyItem('{{ $key }}')"
class="w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-center hover:bg-red-200 ms-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
{{-- Installment plan picker for annual products --}}
@if($isAnnual)
<div class="px-3 pb-3 border-t border-purple-100 pt-2 bg-purple-50">
<p class="text-xs font-medium text-purple-700 mb-2">{{ __('طريقة الدفع') }}</p>
<div class="flex flex-wrap gap-2">
{{-- Full payment option --}}
<button type="button" wire:click="setHotbuyPlan('{{ $key }}', null)"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors
{{ is_null($cartItem['plan_id']) ? 'bg-purple-600 text-white border-purple-700' : 'bg-white text-gray-700 border-gray-300 hover:border-purple-400' }}">
{{ __('دفع كامل') }}
<span class="opacity-75 ms-1" dir="ltr">{{ number_format($cartItem['price'] / 100, 2) }}</span>
</button>
@foreach($cartPlans as $plan)
@php
$planTotal = $cartItem['price'];
$downPct = $plan['down_payment_pct'];
$downAmt = $downPct > 0
? (int) ceil($planTotal * $downPct / 100)
: (int) ceil($planTotal / $plan['installments']);
@endphp
<button type="button" wire:click="setHotbuyPlan('{{ $key }}', {{ $plan['id'] }})"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors
{{ $cartItem['plan_id'] === $plan['id'] ? 'bg-purple-600 text-white border-purple-700' : 'bg-white text-gray-700 border-gray-300 hover:border-purple-400' }}">
{{ $plan['label_ar'] }}
<span class="opacity-75 ms-1" dir="ltr">{{ number_format($downAmt / 100, 2) }} {{ __('مقدم') }}</span>
</button>
@endforeach
@if(count($cartPlans) === 0)
<span class="text-xs text-gray-400 italic">{{ __('لا توجد خطط تقسيط محددة') }}</span>
@endif
</div>
@if(!is_null($cartItem['plan_id']))
@php
$selPlan = collect($cartPlans)->firstWhere('id', $cartItem['plan_id']);
@endphp
@if($selPlan)
<div class="mt-2 p-2 bg-white rounded-lg border border-purple-200 text-xs text-purple-800">
@php
$tp = $cartItem['price'];
$dp = $selPlan['down_payment_pct'] > 0
? (int) ceil($tp * $selPlan['down_payment_pct'] / 100)
: (int) ceil($tp / $selPlan['installments']);
$rem = $selPlan['installments'] - 1;
$regular = $rem > 0 ? (int) ceil(($tp - $dp) / $rem) : 0;
@endphp
<span class="font-semibold">{{ $selPlan['label_ar'] }}</span>:
{{ __('مقدم') }} <span dir="ltr">{{ number_format($dp / 100, 2) }} {{ __('ج.م') }}</span>
@if($rem > 0)
+ {{ $rem }} {{ __('قسط') }} × <span dir="ltr">{{ number_format($regular / 100, 2) }} {{ __('ج.م') }}</span>
({{ $selPlan['frequency_label'] }})
@endif
</div>
@endif
@endif
</div>
@endif
</div>
@endforeach
</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