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 ...@@ -16,8 +16,12 @@ class InvoiceService
public function create(array $data, array $items, User $creator): Invoice public function create(array $data, array $items, User $creator): Invoice
{ {
return DB::transaction(function () use ($data, $items, $creator) { return DB::transaction(function () use ($data, $items, $creator) {
$academyId = $data['academy_id'] ?? app('current_academy')->id;
$invoice = Invoice::create([ $invoice = Invoice::create([
...$data, ...$data,
'academy_id' => $academyId,
'number' => $data['number'] ?? $this->generateNumber($academyId),
'issue_date' => $data['issue_date'] ?? now()->toDateString(), 'issue_date' => $data['issue_date'] ?? now()->toDateString(),
'due_date' => $data['due_date'] ?? $data['issue_date'] ?? now()->toDateString(), 'due_date' => $data['due_date'] ?? $data['issue_date'] ?? now()->toDateString(),
'status' => InvoiceStatus::Draft, 'status' => InvoiceStatus::Draft,
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
use App\Domain\Participant\Models\Participant; use App\Domain\Participant\Models\Participant;
use App\Domain\Training\Models\Enrollment; use App\Domain\Training\Models\Enrollment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
...@@ -87,24 +86,35 @@ public function invoices(Request $request): StreamedResponse ...@@ -87,24 +86,35 @@ public function invoices(Request $request): StreamedResponse
Gate::authorize('invoices.list'); Gate::authorize('invoices.list');
$branchId = session('active_branch_id'); $branchId = session('active_branch_id');
$from = $request->get('from', now()->startOfMonth()->toDateString());
$to = $request->get('to', now()->toDateString());
$query = Invoice::query() $query = Invoice::with(['billable.person'])
->whereBetween('created_at', [$from, $to]) ->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->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'); ->orderByDesc('created_at');
return $this->streamCsv('invoices', [ return $this->streamCsv('invoices', [
'رقم الفاتورة', 'المشترك', 'الإجمالي', 'المدفوع', 'المستحق', 'الحالة', 'التاريخ', 'تاريخ الاستحقاق', 'رقم الفاتورة', 'المشترك', 'الإجمالي', 'المدفوع', 'المستحق', 'الحالة', 'التاريخ', 'تاريخ الاستحقاق',
], $query, function ($inv) { ], $query, function ($inv) {
$name = $inv->contact_name
?? $inv->billable?->person?->name_ar
?? $inv->billable?->name_ar
?? '';
return [ return [
$inv->number ?? '', $inv->number ?? '',
$inv->contact_name ?? '', $name,
number_format($inv->total_amount / 100, 2), number_format($inv->total_amount / 100, 2),
number_format($inv->paid_amount / 100, 2), number_format($inv->paid_amount / 100, 2),
number_format(($inv->total_amount - $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->created_at?->format('Y-m-d'),
$inv->due_date?->format('Y-m-d') ?? '', $inv->due_date?->format('Y-m-d') ?? '',
]; ];
......
...@@ -176,7 +176,7 @@ public function confirm(): void ...@@ -176,7 +176,7 @@ public function confirm(): void
$data = [ $data = [
'billable_type' => \App\Domain\Participant\Models\Participant::class, 'billable_type' => \App\Domain\Participant\Models\Participant::class,
'billable_id' => $this->participantId, 'billable_id' => $this->participantId,
'contact_name' => $this->contactName ?: null, 'contact_name' => $this->contactName ?: $this->participantName,
'contact_phone' => $this->contactPhone ?: null, 'contact_phone' => $this->contactPhone ?: null,
'subtotal_amount' => $subtotal, 'subtotal_amount' => $subtotal,
'discount_amount' => $discount, 'discount_amount' => $discount,
......
...@@ -41,7 +41,7 @@ public function render() ...@@ -41,7 +41,7 @@ public function render()
{ {
$branchId = $this->getActiveBranchId(); $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($branchId, fn ($q) => $q->whereHasMorph('billable', [Participant::class], fn ($pq) => $pq->where('branch_id', $branchId)))
->when($this->search, function ($q) { ->when($this->search, function ($q) {
$search = $this->search; $search = $this->search;
...@@ -51,7 +51,7 @@ public function render() ...@@ -51,7 +51,7 @@ public function render()
}); });
}) })
->when($this->status, fn ($q) => $q->where('status', $this->status)) ->when($this->status, fn ($q) => $q->where('status', $this->status))
->orderByDesc('issue_date'); ->orderByDesc('created_at');
$this->applyRoleScope($query); $this->applyRoleScope($query);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('الفواتير') }}</h1> <h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('الفواتير') }}</h1>
<div class="flex items-center gap-2"> <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"> 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> <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') }} {{ __('تصدير CSV') }}
...@@ -66,13 +66,19 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -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) @forelse($invoices as $invoice)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-mono text-gray-600" dir="ltr"> <td class="px-4 py-3 font-mono text-gray-600" dir="ltr">
{{ $invoice->number }} {{ $invoice->number ?? '—' }}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant') @php
<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> $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 @else
<span class="font-medium text-gray-800">{{ $invoice->contact_name }}</span> <span class="font-medium text-gray-800">{{ $displayName }}</span>
@endif @endif
</td> </td>
<td class="px-4 py-3 text-center font-medium" dir="ltr"> <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"> ...@@ -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 class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 text-sm">
<div> <div>
<p class="text-gray-500">{{ __('العميل') }}</p> <p class="text-gray-500">{{ __('العميل') }}</p>
@if($invoice->billable_type === 'App\\Domain\\Participant\\Models\\Participant') @php
<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> $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 @else
<p class="font-medium text-gray-800">{{ $invoice->contact_name }}</p> <p class="font-medium text-gray-800">{{ $clientName }}</p>
@endif @endif
@if($invoice->contact_phone) @if($invoice->contact_phone || $invoice->billable?->person?->phone)
<p class="text-gray-500" dir="ltr">{{ $invoice->contact_phone }}</p> <p class="text-gray-500" dir="ltr">{{ $invoice->contact_phone ?? $invoice->billable?->person?->phone }}</p>
@endif @endif
</div> </div>
<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