Commit 19e94971 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix invoices: show client name + number, fix CSV export

- InvoiceService::create() now auto-generates number if not provided
- Invoice list/show views fall back to billable.person.name_ar when
  contact_name is null (matches print view behavior)
- Fix participant link to use model route binding (uuid) not raw ID
- CSV export no longer restricts to current month by default — exports
  all invoices matching the active filters (status, search)
- CreateInvoiceWizard falls back to participantName for contact_name
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 7aebbe35
......@@ -16,8 +16,12 @@ class InvoiceService
public function create(array $data, array $items, User $creator): Invoice
{
return DB::transaction(function () use ($data, $items, $creator) {
$academyId = $data['academy_id'] ?? app('current_academy')->id;
$invoice = Invoice::create([
...$data,
'academy_id' => $academyId,
'number' => $data['number'] ?? $this->generateNumber($academyId),
'issue_date' => $data['issue_date'] ?? now()->toDateString(),
'due_date' => $data['due_date'] ?? $data['issue_date'] ?? now()->toDateString(),
'status' => InvoiceStatus::Draft,
......
......@@ -7,7 +7,6 @@
use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Models\Enrollment;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpFoundation\StreamedResponse;
......@@ -87,24 +86,35 @@ public function invoices(Request $request): StreamedResponse
Gate::authorize('invoices.list');
$branchId = session('active_branch_id');
$from = $request->get('from', now()->startOfMonth()->toDateString());
$to = $request->get('to', now()->toDateString());
$query = Invoice::query()
->whereBetween('created_at', [$from, $to])
$query = Invoice::with(['billable.person'])
->when($branchId, fn ($q) => $q->whereHasMorph('billable', [\App\Domain\Participant\Models\Participant::class], fn ($pq) => $pq->where('branch_id', $branchId)))
->when($request->status, fn ($q, $s) => $q->where('status', $s))
->when($request->search, function ($q) use ($request) {
$search = $request->search;
$q->where(function ($q2) use ($search) {
$q2->where('number', 'ilike', "%{$search}%")
->orWhere('contact_name', 'ilike', "%{$search}%");
});
})
->when($request->from, fn ($q, $from) => $q->where('created_at', '>=', $from))
->when($request->to, fn ($q, $to) => $q->where('created_at', '<=', $to . ' 23:59:59'))
->orderByDesc('created_at');
return $this->streamCsv('invoices', [
'رقم الفاتورة', 'المشترك', 'الإجمالي', 'المدفوع', 'المستحق', 'الحالة', 'التاريخ', 'تاريخ الاستحقاق',
], $query, function ($inv) {
$name = $inv->contact_name
?? $inv->billable?->person?->name_ar
?? $inv->billable?->name_ar
?? '';
return [
$inv->number ?? '',
$inv->contact_name ?? '',
$name,
number_format($inv->total_amount / 100, 2),
number_format($inv->paid_amount / 100, 2),
number_format(($inv->total_amount - $inv->paid_amount) / 100, 2),
$inv->status ?? '',
$inv->status?->value ?? $inv->status ?? '',
$inv->created_at?->format('Y-m-d'),
$inv->due_date?->format('Y-m-d') ?? '',
];
......
......@@ -176,7 +176,7 @@ public function confirm(): void
$data = [
'billable_type' => \App\Domain\Participant\Models\Participant::class,
'billable_id' => $this->participantId,
'contact_name' => $this->contactName ?: null,
'contact_name' => $this->contactName ?: $this->participantName,
'contact_phone' => $this->contactPhone ?: null,
'subtotal_amount' => $subtotal,
'discount_amount' => $discount,
......
......@@ -41,7 +41,7 @@ public function render()
{
$branchId = $this->getActiveBranchId();
$query = Invoice::query()
$query = Invoice::with(['billable.person'])
->when($branchId, fn ($q) => $q->whereHasMorph('billable', [Participant::class], fn ($pq) => $pq->where('branch_id', $branchId)))
->when($this->search, function ($q) {
$search = $this->search;
......@@ -51,7 +51,7 @@ public function render()
});
})
->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderByDesc('issue_date');
->orderByDesc('created_at');
$this->applyRoleScope($query);
......
......@@ -2,7 +2,7 @@
<div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('الفواتير') }}</h1>
<div class="flex items-center gap-2">
<a href="{{ route('export.invoices', ['status' => $status ?? '']) }}"
<a href="{{ route('export.invoices', array_filter(['status' => $status ?? '', 'search' => $search ?? ''])) }}"
class="inline-flex items-center gap-2 px-3 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 text-sm font-medium">
<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="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
{{ __('تصدير CSV') }}
......@@ -66,13 +66,19 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
@forelse($invoices as $invoice)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-mono text-gray-600" dir="ltr">
{{ $invoice->number }}
{{ $invoice->number ?? '—' }}
</td>
<td class="px-4 py-3">
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant')
<a href="{{ route('participants.show', $invoice->billable_id) }}" wire:navigate class="text-blue-600 hover:text-blue-800 hover:underline font-medium">{{ $invoice->contact_name }}</a>
@php
$displayName = $invoice->contact_name
?? $invoice->billable?->person?->name_ar
?? $invoice->billable?->name_ar
?? '—';
@endphp
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant' && $invoice->billable)
<a href="{{ route('participants.show', $invoice->billable) }}" wire:navigate class="text-blue-600 hover:text-blue-800 hover:underline font-medium">{{ $displayName }}</a>
@else
<span class="font-medium text-gray-800">{{ $invoice->contact_name }}</span>
<span class="font-medium text-gray-800">{{ $displayName }}</span>
@endif
</td>
<td class="px-4 py-3 text-center font-medium" dir="ltr">
......
......@@ -63,13 +63,19 @@ class="text-gray-600 hover:text-gray-800 text-sm font-medium">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
<div>
<p class="text-gray-500">{{ __('العميل') }}</p>
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant')
<a href="{{ route('participants.show', $invoice->billable_id) }}" wire:navigate class="text-blue-600 hover:text-blue-800 hover:underline font-medium">{{ $invoice->contact_name }}</a>
@php
$clientName = $invoice->contact_name
?? $invoice->billable?->person?->name_ar
?? $invoice->billable?->name_ar
?? '—';
@endphp
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant' && $invoice->billable)
<a href="{{ route('participants.show', $invoice->billable) }}" wire:navigate class="text-blue-600 hover:text-blue-800 hover:underline font-medium">{{ $clientName }}</a>
@else
<p class="font-medium text-gray-800">{{ $invoice->contact_name }}</p>
<p class="font-medium text-gray-800">{{ $clientName }}</p>
@endif
@if($invoice->contact_phone)
<p class="text-gray-500" dir="ltr">{{ $invoice->contact_phone }}</p>
@if($invoice->contact_phone || $invoice->billable?->person?->phone)
<p class="text-gray-500" dir="ltr">{{ $invoice->contact_phone ?? $invoice->billable?->person?->phone }}</p>
@endif
</div>
<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