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

Add pay-N-installments option in receptionist wizard

When a product has an installment plan selected, the receptionist
can now choose how many installments (1, 2, etc.) to collect at
the counter. The payment total adjusts dynamically, and paid
installments are marked as 'paid' immediately on confirm.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent bd1af213
......@@ -133,6 +133,7 @@ class NewRegistrationWizard extends Component
// Hot-buy items (optional products/kits sold alongside registration)
public string $hotbuy_search = '';
public array $hotbuyCart = [];
public array $hotbuyInstallmentsToPay = []; // key => number of installments to pay now
// Pre-flight errors — blocks wizard from starting
public array $systemErrors = [];
......@@ -713,12 +714,32 @@ public function setHotbuyPlan(string $key, ?int $planId): void
{
if (isset($this->hotbuyCart[$key])) {
$this->hotbuyCart[$key]['plan_id'] = $planId;
// Reset installments-to-pay to 1 (down payment) when plan changes
if ($planId) {
$this->hotbuyInstallmentsToPay[$key] = 1;
} else {
unset($this->hotbuyInstallmentsToPay[$key]);
}
}
}
public function setInstallmentsToPay(string $key, int $count): void
{
if (!isset($this->hotbuyCart[$key]) || empty($this->hotbuyCart[$key]['plan_id'])) {
return;
}
$planId = $this->hotbuyCart[$key]['plan_id'];
$plan = ProductInstallmentPlan::find($planId);
if (!$plan) {
return;
}
$this->hotbuyInstallmentsToPay[$key] = max(1, min($count, $plan->installments));
}
public function removeHotbuyItem(string $key): void
{
unset($this->hotbuyCart[$key]);
unset($this->hotbuyInstallmentsToPay[$key]);
}
public function updateHotbuyQuantity(string $key, int $quantity): void
......@@ -738,6 +759,26 @@ public function hotbuyTotal(): int
return collect($this->hotbuyCart)->sum(fn ($item) => $item['price'] * $item['quantity']);
}
#[Computed]
public function hotbuyDueNow(): int
{
$total = 0;
foreach ($this->hotbuyCart as $key => $item) {
$itemTotal = $item['price'] * $item['quantity'];
if (!empty($item['plan_id']) && isset($this->hotbuyInstallmentsToPay[$key])) {
$plan = ProductInstallmentPlan::find($item['plan_id']);
if ($plan) {
$schedule = $plan->buildSchedule($itemTotal);
$toPay = min($this->hotbuyInstallmentsToPay[$key], count($schedule));
$total += array_sum(array_slice($schedule, 0, $toPay));
continue;
}
}
$total += $itemTotal;
}
return $total;
}
// --- Proration ---
#[Computed]
......@@ -785,14 +826,14 @@ public function platformFee(): int
if (!$service->customerPays()) {
return 0;
}
$subtotal = $this->proratedProgramFee->proratedAmount + $this->hotbuyTotal;
$subtotal = $this->proratedProgramFee->proratedAmount + $this->hotbuyDueNow;
return $service->calculate($subtotal);
}
#[Computed]
public function totalWithFee(): int
{
return $this->proratedProgramFee->proratedAmount + $this->hotbuyTotal + $this->platformFee;
return $this->proratedProgramFee->proratedAmount + $this->hotbuyDueNow + $this->platformFee;
}
#[Computed]
......@@ -1036,7 +1077,7 @@ public function confirm(): void
$this->invoiceId = $invoice->id;
// 8b. Create installment payment plans for annual products
foreach ($this->hotbuyCart as $cartItem) {
foreach ($this->hotbuyCart as $cartKey => $cartItem) {
if (empty($cartItem['plan_id'])) {
continue;
}
......@@ -1048,13 +1089,14 @@ public function confirm(): void
$itemTotal = $cartItem['price'] * $cartItem['quantity'];
$schedule = $planTemplate->buildSchedule($itemTotal);
$regularAmount = count($schedule) > 1 ? ($schedule[1] ?? $schedule[0]) : $schedule[0];
$installmentsToPay = $this->hotbuyInstallmentsToPay[$cartKey] ?? 1;
$paymentPlan = PaymentPlan::create([
'academy_id' => app('current_academy')->id,
'invoice_id' => $invoice->id,
'status' => 'active',
'total_installments' => $planTemplate->installments,
'paid_installments' => 0,
'paid_installments' => $this->pay_now ? $installmentsToPay : 0,
'installment_amount' => $regularAmount,
'frequency' => $planTemplate->frequency,
'start_date' => now()->toDateString(),
......@@ -1066,12 +1108,14 @@ public function confirm(): void
$dueDate = now();
foreach ($schedule as $idx => $amount) {
$seq = $idx + 1;
$isPaidNow = $this->pay_now && $seq <= $installmentsToPay;
Installment::create([
'payment_plan_id' => $paymentPlan->id,
'sequence' => $seq,
'amount' => $amount,
'due_date' => $dueDate->toDateString(),
'status' => 'pending',
'status' => $isPaidNow ? 'paid' : 'pending',
'paid_at' => $isPaidNow ? now() : null,
]);
$dueDate = match ($planTemplate->frequency) {
'weekly' => $dueDate->addWeek(),
......@@ -1080,6 +1124,17 @@ public function confirm(): void
default => $dueDate->addMonth(),
};
}
// Update next_due_date to the first unpaid installment
if ($this->pay_now && $installmentsToPay > 0) {
$nextDue = $paymentPlan->installments()
->where('status', 'pending')
->orderBy('sequence')
->value('due_date');
if ($nextDue) {
$paymentPlan->update(['next_due_date' => $nextDue]);
}
}
}
// 9. Record payment(s) if paying now
......@@ -1096,9 +1151,9 @@ public function confirm(): void
];
if ($this->split_payment) {
// Two payments: amount1 + remainder
// Two payments: amount1 + remainder of what's due now
$amount1 = (int) round((float) $this->split_amount1_input * 100);
$amount2 = $invoice->total_amount - $amount1;
$amount2 = $this->effectiveTotal - $amount1;
if ($amount1 > 0) {
$paymentService->recordPayment(array_merge($basePaymentData, [
......@@ -1131,11 +1186,12 @@ public function confirm(): void
]), $actor);
}
} else {
// Full payment
// Full payment (= effectiveTotal which accounts for installment selections)
$fullPayAmount = $this->effectiveTotal;
$paymentService->recordPayment(array_merge($basePaymentData, [
'reference' => 'PAY-' . now()->format('YmdHis') . '-' . $participant->id,
'method' => $this->payment_method,
'amount' => $invoice->total_amount,
'amount' => $fullPayAmount,
'notes' => $this->buildPaymentNotes($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name, $program->name_ar),
'gateway_data' => $this->buildGatewayData($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name),
]), $actor);
......
......@@ -886,6 +886,32 @@ class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-colors
@endif
@endif
</div>
{{-- How many installments to pay now --}}
@php
$currentToPay = $this->hotbuyInstallmentsToPay[$key] ?? 1;
$payNowSum = array_sum(array_slice($slots, 0, $currentToPay));
@endphp
<div class="mt-2 p-2 bg-green-50 rounded-lg border border-green-200">
<p class="text-xs font-medium text-green-800 mb-1.5">{{ __('عدد الأقساط المدفوعة الآن') }}</p>
<div class="flex flex-wrap gap-1.5">
@for($qi = 1; $qi <= $n; $qi++)
@php $qSum = array_sum(array_slice($slots, 0, $qi)); @endphp
<button type="button" wire:click="setInstallmentsToPay('{{ $key }}', {{ $qi }})"
class="px-2.5 py-1 text-xs font-medium rounded-md border transition-colors
{{ $currentToPay === $qi ? 'bg-green-600 text-white border-green-700' : 'bg-white text-gray-700 border-gray-300 hover:border-green-400' }}">
{{ $qi }} {{ $qi === 1 ? __('قسط') : __('أقساط') }}
<span class="opacity-75 ms-0.5" dir="ltr">({{ number_format($qSum / 100, 2) }})</span>
</button>
@endfor
</div>
<p class="text-[10px] text-green-600 mt-1.5">
{{ __('المطلوب دفعه الآن') }}: <strong dir="ltr">{{ number_format($payNowSum / 100, 2) }} {{ __('ج.م') }}</strong>
@if($currentToPay < $n)
{{ __('المتبقي') }}: <span dir="ltr">{{ number_format(($tp - $payNowSum) / 100, 2) }} {{ __('ج.م') }}</span> ({{ $n - $currentToPay }} {{ __('أقساط') }})
@endif
</p>
</div>
@endif
@endif
</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