Commit 86da35b4 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix POS payment collection + retroactive wizard reliability

Payment Collection (CollectPaymentWizard):
- Remove branch filter from participant search (POS sells cross-branch)
- Include Draft status in outstanding invoice query
- Add invoice search mode (search by invoice number/contact name)
- Allow walk-in invoices to be found and paid directly
- Accept inactive/frozen/registered participants (not just active)

Retroactive Enrollment Wizard:
- Fix "paid outside system" to use PaymentService (creates proper Payment
  + Transaction records for financial reports)
- Add server-side validation in confirm() before DB transaction
- Add national_id duplicate check to prevent duplicate participants
- Add price=0 guard (show error if no base price and no override)
- Fix rounding loss: remainder goes to last month's invoice
- Pass skip_auto_invoice to prevent double invoice creation

POS Partial Payment:
- Enable allows_partial_payment on all existing products (migration)
  so the deposit/partial payment UI appears at checkout
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f1f30cc6
...@@ -100,8 +100,8 @@ public function enroll(Participant $participant, TrainingGroup $group, User $act ...@@ -100,8 +100,8 @@ public function enroll(Participant $participant, TrainingGroup $group, User $act
$participant->update(['status' => 'active']); $participant->update(['status' => 'active']);
} }
// Auto-create invoice if program has a price (skip if invoice already provided via options) // Auto-create invoice if program has a price (skip if invoice already provided or explicitly skipped)
if (empty($options['invoice_id']) && (bool) $this->settings->get('auto_invoice_on_enrollment', false)) { if (empty($options['invoice_id']) && empty($options['skip_auto_invoice']) && (bool) $this->settings->get('auto_invoice_on_enrollment', false)) {
$this->createEnrollmentInvoice($enrollment, $participant, $group, $actor); $this->createEnrollmentInvoice($enrollment, $participant, $group, $actor);
} }
......
...@@ -32,6 +32,7 @@ class CollectPaymentWizard extends Component ...@@ -32,6 +32,7 @@ class CollectPaymentWizard extends Component
// Step 1: Search // Step 1: Search
public string $search = ''; public string $search = '';
public string $searchMode = 'participant'; // 'participant' or 'invoice'
public ?int $selected_participant_id = null; public ?int $selected_participant_id = null;
public ?string $selected_participant_name = null; public ?string $selected_participant_name = null;
...@@ -77,9 +78,9 @@ public function mount(?string $participant = null): void ...@@ -77,9 +78,9 @@ public function mount(?string $participant = null): void
public function rules(): array public function rules(): array
{ {
return match ($this->currentStep) { return match ($this->currentStep) {
1 => [ 1 => $this->searchMode === 'participant'
'selected_participant_id' => 'required|exists:participants,id', ? ['selected_participant_id' => 'required|exists:participants,id']
], : ['selected_invoice_id' => 'required|exists:invoices,id'],
2 => [ 2 => [
'selected_invoice_id' => 'required|exists:invoices,id', 'selected_invoice_id' => 'required|exists:invoices,id',
], ],
...@@ -107,6 +108,36 @@ public function messages(): array ...@@ -107,6 +108,36 @@ public function messages(): array
]; ];
} }
public function switchSearchMode(string $mode): void
{
$this->searchMode = $mode;
$this->search = '';
$this->selected_participant_id = null;
$this->selected_participant_name = null;
$this->selected_invoice_id = null;
}
public function selectInvoiceDirectly(int $id): void
{
$invoice = Invoice::find($id);
if (!$invoice || $invoice->due_amount <= 0) {
return;
}
if ($invoice->billable_type === Participant::class && $invoice->billable_id) {
$participant = Participant::with('person')->find($invoice->billable_id);
$this->selected_participant_id = $participant?->id;
$this->selected_participant_name = $participant?->person?->name_ar ?? $invoice->contact_name ?? '';
} else {
$this->selected_participant_id = null;
$this->selected_participant_name = $invoice->contact_name ?? __('عميل عابر');
}
$this->selected_invoice_id = $invoice->id;
$this->payment_amount_display = number_format($invoice->due_amount / 100, 2, '.', '');
$this->currentStep = 3;
}
public function selectParticipant(int $id, string $name): void public function selectParticipant(int $id, string $name): void
{ {
$this->selected_participant_id = $id; $this->selected_participant_id = $id;
...@@ -410,26 +441,46 @@ public function confirm(PaymentService $service): void ...@@ -410,26 +441,46 @@ public function confirm(PaymentService $service): void
public function render() public function render()
{ {
$searchResults = collect(); $searchResults = collect();
$invoiceSearchResults = collect();
if (strlen($this->search) >= 2) { if (strlen($this->search) >= 2) {
$searchResults = Participant::query() if ($this->searchMode === 'participant') {
->with('person') $searchResults = Participant::query()
->where('branch_id', $this->branchId) ->with('person')
->where('status', 'active') ->whereIn('status', ['active', 'registered', 'frozen', 'inactive'])
->where(function ($q) { ->where(function ($q) {
$search = $this->search; $search = $this->search;
$q->where('participant_number', 'ilike', "%{$search}%") $q->where('participant_number', 'ilike', "%{$search}%")
->orWhereHas('person', function ($pq) use ($search) { ->orWhereHas('person', function ($pq) use ($search) {
$pq->where('name_ar', 'ilike', "%{$search}%") $pq->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%") ->orWhere('name', 'ilike', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%") ->orWhere('phone', 'like', "%{$search}%")
->orWhere('national_id', 'like', "%{$search}%"); ->orWhere('national_id', 'like', "%{$search}%");
}); });
}) })
->limit(10) ->limit(10)
->get(); ->get();
} else {
$invoiceSearchResults = Invoice::query()
->whereIn('status', [
InvoiceStatus::Sent,
InvoiceStatus::PartiallyPaid,
InvoiceStatus::Overdue,
InvoiceStatus::Draft,
])
->where('due_amount', '>', 0)
->where(function ($q) {
$search = $this->search;
$q->where('number', 'ilike', "%{$search}%")
->orWhere('contact_name', 'ilike', "%{$search}%");
})
->orderBy('due_date')
->limit(10)
->get();
}
} }
// Outstanding invoices for selected participant // Outstanding invoices for selected participant (all branches — POS can sell cross-branch)
$invoices = collect(); $invoices = collect();
$upcomingPayments = collect(); $upcomingPayments = collect();
if ($this->selected_participant_id) { if ($this->selected_participant_id) {
...@@ -439,6 +490,7 @@ public function render() ...@@ -439,6 +490,7 @@ public function render()
InvoiceStatus::Sent, InvoiceStatus::Sent,
InvoiceStatus::PartiallyPaid, InvoiceStatus::PartiallyPaid,
InvoiceStatus::Overdue, InvoiceStatus::Overdue,
InvoiceStatus::Draft,
]) ])
->where('due_amount', '>', 0) ->where('due_amount', '>', 0)
->orderBy('due_date') ->orderBy('due_date')
...@@ -490,6 +542,7 @@ public function render() ...@@ -490,6 +542,7 @@ public function render()
return view('livewire.receptionist.collect-payment-wizard', [ return view('livewire.receptionist.collect-payment-wizard', [
'searchResults' => $searchResults, 'searchResults' => $searchResults,
'invoiceSearchResults' => $invoiceSearchResults,
'invoices' => $invoices, 'invoices' => $invoices,
'upcomingPayments' => $upcomingPayments, 'upcomingPayments' => $upcomingPayments,
'selectedInvoice' => $selectedInvoice, 'selectedInvoice' => $selectedInvoice,
......
...@@ -251,8 +251,36 @@ public function goToStep(int $step): void ...@@ -251,8 +251,36 @@ public function goToStep(int $step): void
public function confirm(): void public function confirm(): void
{ {
$this->validate([
'participant_name_ar' => 'required|string|min:3|max:100',
'guardian_name_ar' => 'required|string|min:3|max:100',
'guardian_phone' => 'required|string|min:10|max:20',
'selected_program_id' => 'required|exists:training_programs,id',
'actual_start_date' => 'required|date|before_or_equal:today',
'payment_method' => 'required_if:pay_now,true|in:cash,card,bank_transfer,wallet,online,cheque,other',
]);
$actor = auth()->user(); $actor = auth()->user();
// Duplicate NID check
if ($this->participant_national_id) {
$existingPerson = Person::where('national_id', $this->participant_national_id)->first();
if ($existingPerson) {
session()->flash('error', __('يوجد شخص مسجل بنفس الرقم القومي: ') . $existingPerson->name_ar);
return;
}
}
// Price validation — don't allow 0-amount invoices unless explicitly overridden
$effectivePrice = $this->monthlyPrice;
if ($this->priceOverrideEnabled && $this->priceOverrideInput !== '' && $actor->is_super_admin) {
$effectivePrice = max(0, (int) round((float) $this->priceOverrideInput * 100));
}
if ($effectivePrice <= 0 && $this->unpaidMonthsCount > 0 && !$this->priceOverrideEnabled) {
session()->flash('error', __('لا يوجد سعر محدد لهذا البرنامج — يرجى إضافة سعر أو استخدام تعديل السعر'));
return;
}
try { try {
DB::transaction(function () use ($actor) { DB::transaction(function () use ($actor) {
$personService = app(PersonService::class); $personService = app(PersonService::class);
...@@ -314,6 +342,7 @@ public function confirm(): void ...@@ -314,6 +342,7 @@ public function confirm(): void
]); ]);
// 6. Enroll in program with retroactive start date // 6. Enroll in program with retroactive start date
// Pass skip_auto_invoice to prevent auto_invoice_on_enrollment from creating a duplicate
$program = TrainingProgram::findOrFail($this->selected_program_id); $program = TrainingProgram::findOrFail($this->selected_program_id);
$enrollment = $enrollmentService->enrollInProgram( $enrollment = $enrollmentService->enrollInProgram(
$participant, $participant,
...@@ -322,6 +351,7 @@ public function confirm(): void ...@@ -322,6 +351,7 @@ public function confirm(): void
[ [
'start_date' => $this->actual_start_date, 'start_date' => $this->actual_start_date,
'payment_status' => 'pending', 'payment_status' => 'pending',
'skip_auto_invoice' => true,
] ]
); );
...@@ -352,6 +382,14 @@ public function confirm(): void ...@@ -352,6 +382,14 @@ public function confirm(): void
$paymentsRecorded = 0; $paymentsRecorded = 0;
$totalRetroactiveRevenue = 0; $totalRetroactiveRevenue = 0;
$unpaidIndex = 0;
$unpaidTotal = collect($this->monthStatuses)->filter(fn ($s) => $s === 'unpaid')->count();
$remainder = 0;
if ($overrideActive && $unpaidTotal > 0) {
$overrideTotalCalc = max(0, (int) round((float) $this->priceOverrideInput * 100));
$remainder = $overrideTotalCalc - ($perMonth * $unpaidTotal);
}
foreach ($this->monthStatuses as $monthKey => $status) { foreach ($this->monthStatuses as $monthKey => $status) {
$monthDate = Carbon::parse($monthKey . '-01'); $monthDate = Carbon::parse($monthKey . '-01');
...@@ -380,28 +418,35 @@ public function confirm(): void ...@@ -380,28 +418,35 @@ public function confirm(): void
], ],
], $actor); ], $actor);
$invoice->update([ $invoice->update(['status' => InvoiceStatus::Sent]);
'status' => InvoiceStatus::Paid,
'paid_amount' => $perMonth, $paymentService->recordPayment([
'due_amount' => 0, 'invoice_id' => $invoice->id,
'paid_at' => $monthDate->copy()->addDays(1), 'branch_id' => $this->branchId,
'metadata' => array_merge($invoice->metadata ?? [], [ 'amount' => $perMonth,
'paid_outside_system' => true, 'method' => 'cash',
'recorded_by' => $actor->name, 'direction' => 'inbound',
'recorded_at' => now()->toIso8601String(), 'currency' => 'EGP',
]), 'payment_date' => $monthDate->copy()->addDays(1)->toDateString(),
]); 'notes' => 'مدفوع خارج السيستم — ' . $this->getArabicMonth($monthDate),
], $actor);
$totalRetroactiveRevenue += $perMonth; $totalRetroactiveRevenue += $perMonth;
$invoicesCreated++; $invoicesCreated++;
$paymentsRecorded++;
} elseif ($status === 'unpaid') { } elseif ($status === 'unpaid') {
$unpaidIndex++;
$thisMonthAmount = $perMonth;
if ($unpaidIndex === $unpaidTotal && $remainder > 0) {
$thisMonthAmount += $remainder;
}
$invoice = $invoiceService->create([ $invoice = $invoiceService->create([
'academy_id' => $participant->academy_id, 'academy_id' => $participant->academy_id,
'branch_id' => $this->branchId, 'branch_id' => $this->branchId,
'billable_type' => $participant->getMorphClass(), 'billable_type' => $participant->getMorphClass(),
'billable_id' => $participant->id, 'billable_id' => $participant->id,
'total_amount' => $perMonth, 'total_amount' => $thisMonthAmount,
'subtotal_amount' => $perMonth, 'subtotal_amount' => $thisMonthAmount,
'discount_amount' => 0, 'discount_amount' => 0,
'tax_amount' => 0, 'tax_amount' => 0,
'service_fee_amount' => 0, 'service_fee_amount' => 0,
...@@ -413,21 +458,21 @@ public function confirm(): void ...@@ -413,21 +458,21 @@ public function confirm(): void
[ [
'description' => 'اشتراك: ' . $program->name_ar . ' (' . $this->getArabicMonth($monthDate) . ')', 'description' => 'اشتراك: ' . $program->name_ar . ' (' . $this->getArabicMonth($monthDate) . ')',
'quantity' => 1, 'quantity' => 1,
'unit_price' => $perMonth, 'unit_price' => $thisMonthAmount,
'discount_amount' => 0, 'discount_amount' => 0,
'tax_amount' => 0, 'tax_amount' => 0,
], ],
], $actor); ], $actor);
$invoice->update(['status' => InvoiceStatus::Sent]); $invoice->update(['status' => InvoiceStatus::Sent]);
$totalRetroactiveRevenue += $perMonth; $totalRetroactiveRevenue += $thisMonthAmount;
$invoicesCreated++; $invoicesCreated++;
if ($this->pay_now && $perMonth > 0) { if ($this->pay_now && $thisMonthAmount > 0) {
$paymentService->recordPayment([ $paymentService->recordPayment([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'branch_id' => $this->branchId, 'branch_id' => $this->branchId,
'amount' => $perMonth, 'amount' => $thisMonthAmount,
'method' => $this->payment_method, 'method' => $this->payment_method,
'direction' => 'inbound', 'direction' => 'inbound',
'currency' => 'EGP', 'currency' => 'EGP',
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::table('products')->update(['allows_partial_payment' => true]);
}
public function down(): void
{
DB::table('products')->update(['allows_partial_payment' => false]);
}
};
...@@ -73,88 +73,141 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs ...@@ -73,88 +73,141 @@ class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'curs
{{-- Step Content --}} {{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
{{-- Step 1: Search Participant --}} {{-- Step 1: Search Participant or Invoice --}}
@if($currentStep === 1) @if($currentStep === 1)
<div> <div>
<h2 class="text-lg font-semibold text-gray-800 mb-6">{{ __('بحث عن المشترك') }}</h2> <h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('بحث عن المشترك أو الفاتورة') }}</h2>
{{-- Search Mode Tabs --}}
<div class="flex gap-2 mb-4">
<button wire:click="switchSearchMode('participant')"
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors
{{ $searchMode === 'participant' ? 'bg-amber-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }}">
{{ __('بحث بالمشترك') }}
</button>
<button wire:click="switchSearchMode('invoice')"
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors
{{ $searchMode === 'invoice' ? 'bg-amber-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }}">
{{ __('بحث برقم الفاتورة') }}
</button>
</div>
{{-- Search Input --}} {{-- Search Input --}}
<div class="mb-6"> <div class="mb-6">
<input type="text" wire:model.live.debounce.300ms="search" <input type="text" wire:model.live.debounce.300ms="search"
class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 text-lg" class="w-full px-4 py-3 min-h-16 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-amber-500 text-lg"
placeholder="{{ __('بحث بالاسم، الهاتف، الرقم القومي، أو رقم المشترك...') }}"> placeholder="{{ $searchMode === 'participant' ? __('بحث بالاسم، الهاتف، الرقم القومي، أو رقم المشترك...') : __('بحث برقم الفاتورة أو اسم العميل...') }}">
</div> </div>
{{-- Selected Participant Indicator --}} @if($searchMode === 'participant')
@if($selected_participant_id) {{-- Selected Participant Indicator --}}
<div class="mb-4 p-4 bg-amber-50 border border-amber-200 rounded-xl flex items-center justify-between"> @if($selected_participant_id)
<div class="flex items-center gap-3"> <div class="mb-4 p-4 bg-amber-50 border border-amber-200 rounded-xl flex items-center justify-between">
<div class="w-10 h-10 rounded-full bg-amber-200 flex items-center justify-center"> <div class="flex items-center gap-3">
<svg class="w-5 h-5 text-amber-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <div class="w-10 h-10 rounded-full bg-amber-200 flex items-center justify-center">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> <svg class="w-5 h-5 text-amber-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<span class="font-medium text-amber-800">{{ $selected_participant_name }}</span>
</div> </div>
<span class="font-medium text-amber-800">{{ $selected_participant_name }}</span> <button wire:click="$set('selected_participant_id', null)" class="text-amber-600 hover:text-amber-800 text-sm">
{{ __('تغيير') }}
</button>
</div> </div>
<button wire:click="$set('selected_participant_id', null)" class="text-amber-600 hover:text-amber-800 text-sm"> @endif
{{ __('تغيير') }}
</button>
</div>
@endif
{{-- Search Results --}} {{-- Participant Search Results --}}
@if(strlen($search) >= 2 && !$selected_participant_id) @if(strlen($search) >= 2 && !$selected_participant_id)
<div class="space-y-2" wire:loading.class="opacity-50"> <div class="space-y-2" wire:loading.class="opacity-50">
@forelse($searchResults as $result) @forelse($searchResults as $result)
<button wire:click="selectParticipant({{ $result->id }}, '{{ $result->person?->name_ar }}')" <button wire:click="selectParticipant({{ $result->id }}, '{{ addslashes($result->person?->name_ar) }}')"
class="w-full p-4 min-h-16 border border-gray-200 rounded-xl text-start hover:border-amber-300 hover:bg-amber-50 transition-all flex items-center justify-between"> class="w-full p-4 min-h-16 border border-gray-200 rounded-xl text-start hover:border-amber-300 hover:bg-amber-50 transition-all flex items-center justify-between">
<div> <div>
<p class="font-medium text-gray-800">{{ $result->person?->name_ar }}</p> <p class="font-medium text-gray-800">{{ $result->person?->name_ar }}</p>
<div class="flex items-center gap-3 mt-1 text-xs text-gray-500"> <div class="flex items-center gap-3 mt-1 text-xs text-gray-500">
@if($result->participant_number) @if($result->participant_number)
<span dir="ltr">{{ $result->participant_number }}</span> <span dir="ltr">{{ $result->participant_number }}</span>
@endif @endif
@if($result->person?->phone) @if($result->person?->phone)
<span dir="ltr">{{ $result->person->phone }}</span> <span dir="ltr">{{ $result->person->phone }}</span>
@endif @endif
</div>
</div> </div>
<span class="px-2 py-0.5 text-xs rounded-full
{{ $result->status->value === 'active' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600' }}">
{{ $result->status->value === 'active' ? __('نشط') : __($result->status->value) }}
</span>
</button>
@empty
<div class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p>{{ __('لا توجد نتائج') }}</p>
</div> </div>
<span class="px-2 py-0.5 text-xs rounded-full @endforelse
{{ $result->status->value === 'active' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600' }}"> </div>
{{ $result->status->value === 'active' ? __('نشط') : __($result->status->value) }} @elseif(strlen($search) < 2 && !$selected_participant_id)
</span> <div class="text-center py-8 text-gray-400">
</button> <svg class="w-16 h-16 mx-auto text-gray-200 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@empty
<div class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg> </svg>
<p>{{ __('لا توجد نتائج') }}</p> <p class="text-sm">{{ __('اكتب حرفين على الأقل للبحث') }}</p>
</div> </div>
@endforelse @endif
</div>
@elseif(strlen($search) < 2 && !$selected_participant_id)
<div class="text-center py-8 text-gray-400">
<svg class="w-16 h-16 mx-auto text-gray-200 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p class="text-sm">{{ __('اكتب حرفين على الأقل للبحث') }}</p>
</div>
@endif
@error('selected_participant_id') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror @error('selected_participant_id') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror
<div class="flex justify-end mt-8"> <div class="flex justify-end mt-8">
<button wire:click="nextStep" wire:loading.attr="disabled" <button wire:click="nextStep" wire:loading.attr="disabled"
@if(!$selected_participant_id) disabled @endif @if(!$selected_participant_id) disabled @endif
class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white rounded-lg hover:bg-amber-700 text-base font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> class="inline-flex items-center gap-2 px-6 py-3 min-h-16 bg-amber-600 text-white rounded-lg hover:bg-amber-700 text-base font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
<span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span> <span wire:loading.remove wire:target="nextStep">{{ __('التالي') }}</span>
<span wire:loading wire:target="nextStep">{{ __('جارٍ...') }}</span> <span wire:loading wire:target="nextStep">{{ __('جارٍ...') }}</span>
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
</button>
</div>
@else
{{-- Invoice Search Results --}}
@if(strlen($search) >= 2)
<div class="space-y-2" wire:loading.class="opacity-50">
@forelse($invoiceSearchResults as $inv)
<button wire:click="selectInvoiceDirectly({{ $inv->id }})"
class="w-full p-4 border border-gray-200 rounded-xl text-start hover:border-amber-300 hover:bg-amber-50 transition-all">
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-gray-800" dir="ltr">{{ $inv->number }}</p>
<p class="text-sm text-gray-500 mt-1">{{ $inv->contact_name ?? __('عميل عابر') }}</p>
</div>
<div class="text-end">
<p class="font-bold text-red-600">{{ number_format($inv->due_amount / 100, 2) }} {{ __('ج.م') }}</p>
<p class="text-xs text-gray-400 mt-0.5">{{ __('من أصل') }} {{ number_format($inv->total_amount / 100, 2) }}</p>
</div>
</div>
</button>
@empty
<div class="text-center py-8 text-gray-500">
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<p>{{ __('لا توجد فواتير مستحقة بهذا الرقم') }}</p>
</div>
@endforelse
</div>
@else
<div class="text-center py-8 text-gray-400">
<svg class="w-16 h-16 mx-auto text-gray-200 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg> </svg>
</button> <p class="text-sm">{{ __('اكتب رقم الفاتورة أو اسم العميل للبحث') }}</p>
</div> </div>
@endif
@error('selected_invoice_id') <p class="mt-2 text-sm text-red-600">{{ $message }}</p> @enderror
@endif
</div> </div>
@endif @endif
......
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