Andrew Wallo 4 месяцев назад
Родитель
Сommit
00ca86bdaa

+ 10
- 0
app/Filament/Company/Resources/Purchases/BillResource/Pages/PayBills.php Просмотреть файл

@@ -66,6 +66,16 @@ class PayBills extends ListRecords
66 66
         return [
67 67
             Actions\Action::make('processPayments')
68 68
                 ->color('primary')
69
+                ->requiresConfirmation()
70
+                ->modalHeading('Confirm payments')
71
+                ->modalDescription(function () {
72
+                    $billCount = collect($this->paymentAmounts)->filter(fn ($amount) => $amount > 0)->count();
73
+                    $totalAmount = array_sum($this->paymentAmounts);
74
+                    $currencyCode = $this->getTableFilterState('currency_code')['value'];
75
+                    $totalFormatted = CurrencyConverter::formatCentsToMoney($totalAmount, $currencyCode, true);
76
+
77
+                    return "You are about to pay {$billCount} " . Str::plural('bill', $billCount) . " for a total of {$totalFormatted}. This action cannot be undone.";
78
+                })
69 79
                 ->action(function () {
70 80
                     $data = $this->data;
71 81
                     $tableRecords = $this->getTableRecords();

+ 2
- 2
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ListInvoices.php Просмотреть файл

@@ -7,8 +7,8 @@ use App\Enums\Accounting\InvoiceStatus;
7 7
 use App\Filament\Company\Resources\Sales\InvoiceResource;
8 8
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
9 9
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages\ViewRecurringInvoice;
10
+use App\Filament\Infolists\Components\BannerEntry;
10 11
 use App\Models\Accounting\RecurringInvoice;
11
-use CodeWithDennis\SimpleAlert\Components\Infolists\SimpleAlert;
12 12
 use Filament\Actions;
13 13
 use Filament\Infolists\Components\Actions\Action;
14 14
 use Filament\Infolists\Infolist;
@@ -37,7 +37,7 @@ class ListInvoices extends ListRecords
37 37
     {
38 38
         return $infolist
39 39
             ->schema([
40
-                SimpleAlert::make('recurringInvoiceFilter')
40
+                BannerEntry::make('recurringInvoiceFilter')
41 41
                     ->info()
42 42
                     ->title(function () {
43 43
                         if (empty($this->recurringInvoice)) {

+ 59
- 51
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/RecordPayments.php Просмотреть файл

@@ -45,7 +45,6 @@ class RecordPayments extends ListRecords
45 45
 
46 46
     public ?array $data = [];
47 47
 
48
-    // New property for allocation amount
49 48
     public ?int $allocationAmount = null;
50 49
 
51 50
     #[Url(as: 'invoice_id')]
@@ -94,6 +93,16 @@ class RecordPayments extends ListRecords
94 93
         return [
95 94
             Actions\Action::make('processPayments')
96 95
                 ->color('primary')
96
+                ->requiresConfirmation()
97
+                ->modalHeading('Confirm payments')
98
+                ->modalDescription(function () {
99
+                    $invoiceCount = collect($this->paymentAmounts)->filter(fn ($amount) => $amount > 0)->count();
100
+                    $totalAmount = array_sum($this->paymentAmounts);
101
+                    $currencyCode = $this->getTableFilterState('currency_code')['value'];
102
+                    $totalFormatted = CurrencyConverter::formatCentsToMoney($totalAmount, $currencyCode, true);
103
+
104
+                    return "You are about to pay {$invoiceCount} " . Str::plural('invoice', $invoiceCount) . " for a total of {$totalFormatted}. This action cannot be undone.";
105
+                })
97 106
                 ->action(function () {
98 107
                     $data = $this->data;
99 108
                     $tableRecords = $this->getTableRecords();
@@ -201,29 +210,25 @@ class RecordPayments extends ListRecords
201 210
                             ->options(PaymentMethod::class)
202 211
                             ->default(PaymentMethod::BankPayment)
203 212
                             ->softRequired(),
204
-                        // Allocation amount field with suffix action
205 213
                         Forms\Components\TextInput::make('allocation_amount')
206 214
                             ->label('Allocate Payment Amount')
207
-                            ->live()
208 215
                             ->default(array_sum($this->paymentAmounts))
209 216
                             ->money($this->getTableFilterState('currency_code')['value'])
217
+                            ->extraAlpineAttributes([
218
+                                'x-on:keydown.enter.prevent' => '$refs.allocate.click()',
219
+                            ])
210 220
                             ->suffixAction(
211 221
                                 Forms\Components\Actions\Action::make('allocate')
212 222
                                     ->icon('heroicon-m-calculator')
223
+                                    ->extraAttributes([
224
+                                        'x-ref' => 'allocate',
225
+                                    ])
213 226
                                     ->action(function ($state) {
214 227
                                         $this->allocationAmount = CurrencyConverter::convertToCents($state, 'USD');
215 228
                                         if ($this->allocationAmount && $this->hasSelectedClient()) {
216 229
                                             $this->allocateOldestFirst($this->getTableRecords(), $this->allocationAmount);
217
-
218
-                                            $amountFormatted = CurrencyConverter::formatCentsToMoney($this->allocationAmount, 'USD', true);
219
-
220
-                                            Notification::make()
221
-                                                ->title('Payment allocated')
222
-                                                ->body("Allocated {$amountFormatted} across invoices")
223
-                                                ->success()
224
-                                                ->send();
225 230
                                         }
226
-                                    })
231
+                                    }),
227 232
                             ),
228 233
                     ]),
229 234
             ])->statePath('data');
@@ -240,6 +245,8 @@ class RecordPayments extends ListRecords
240 245
             ->recordClasses(['is-spreadsheet'])
241 246
             ->defaultSort('due_date')
242 247
             ->paginated(false)
248
+            ->emptyStateHeading('No client selected')
249
+            ->emptyStateDescription('Select a client from the filters above to view and process invoice payments.')
243 250
             ->columns([
244 251
                 TextColumn::make('client.name')
245 252
                     ->label('Client')
@@ -335,28 +342,6 @@ class RecordPayments extends ListRecords
335 342
                                 return $activeCurrency && $activeCurrency !== $bankAccountCurrency;
336 343
                             }),
337 344
                     ]),
338
-                // New allocation status column
339
-                TextColumn::make('allocation_status')
340
-                    ->label('Status')
341
-                    ->getStateUsing(function (Invoice $record) {
342
-                        $paymentAmount = $this->paymentAmounts[$record->id] ?? 0;
343
-
344
-                        if ($paymentAmount <= 0) {
345
-                            return 'No payment';
346
-                        }
347
-
348
-                        if ($paymentAmount >= $record->amount_due) {
349
-                            return 'Full payment';
350
-                        }
351
-
352
-                        return 'Partial payment';
353
-                    })
354
-                    ->badge()
355
-                    ->color(fn (string $state): string => match ($state) {
356
-                        'Full payment' => 'success',
357
-                        'Partial payment' => 'warning',
358
-                        default => 'gray',
359
-                    }),
360 345
             ])
361 346
             ->bulkActions([
362 347
                 Tables\Actions\BulkAction::make('setFullAmounts')
@@ -419,6 +404,46 @@ class RecordPayments extends ListRecords
419 404
 
420 405
                         return $query->where('client_id', $data['value']);
421 406
                     }),
407
+                Tables\Filters\Filter::make('invoice_lookup')
408
+                    ->label('Find Invoice')
409
+                    ->form([
410
+                        Forms\Components\TextInput::make('invoice_number')
411
+                            ->label('Invoice Number')
412
+                            ->placeholder('Enter invoice number')
413
+                            ->suffixAction(
414
+                                Forms\Components\Actions\Action::make('findInvoice')
415
+                                    ->icon('heroicon-m-magnifying-glass')
416
+                                    ->keyBindings(['enter'])
417
+                                    ->action(function ($state, Forms\Set $set) {
418
+                                        if (blank($state)) {
419
+                                            return;
420
+                                        }
421
+
422
+                                        $invoice = Invoice::byNumber($state)
423
+                                            ->unpaid()
424
+                                            ->first();
425
+
426
+                                        if ($invoice) {
427
+                                            $set('tableFilters.client_id.value', $invoice->client_id, true);
428
+                                            $this->paymentAmounts[$invoice->id] = $invoice->amount_due;
429
+
430
+                                            Notification::make()
431
+                                                ->title('Invoice found')
432
+                                                ->body("Found invoice {$invoice->invoice_number} for {$invoice->client->name}")
433
+                                                ->success()
434
+                                                ->send();
435
+                                        } else {
436
+                                            Notification::make()
437
+                                                ->title('Invoice not found')
438
+                                                ->body("No unpaid invoice found with number: {$state}")
439
+                                                ->warning()
440
+                                                ->send();
441
+                                        }
442
+                                    })
443
+                            ),
444
+                    ])
445
+                    ->query(null)
446
+                    ->indicateUsing(null),
422 447
                 Tables\Filters\SelectFilter::make('status')
423 448
                     ->multiple()
424 449
                     ->options(InvoiceStatus::getUnpaidOptions()),
@@ -440,21 +465,6 @@ class RecordPayments extends ListRecords
440 465
         return CurrencyConverter::formatCentsToMoney($total, $currencyCode, true);
441 466
     }
442 467
 
443
-    #[Computed]
444
-    public function allocationVariance(): string
445
-    {
446
-        if (! $this->allocationAmount) {
447
-            return '$0.00';
448
-        }
449
-
450
-        $totalAllocated = array_sum($this->paymentAmounts);
451
-        $variance = $this->allocationAmount - $totalAllocated;
452
-
453
-        $currencyCode = $this->getTableFilterState('currency_code')['value'];
454
-
455
-        return CurrencyConverter::formatCentsToMoney($variance, $currencyCode, true);
456
-    }
457
-
458 468
     public function getSelectedBankAccount(): BankAccount
459 469
     {
460 470
         $bankAccountId = $this->data['bank_account_id'];
@@ -472,8 +482,6 @@ class RecordPayments extends ListRecords
472 482
         $visibleInvoiceKeys = array_flip($visibleInvoiceIds);
473 483
 
474 484
         $this->paymentAmounts = array_intersect_key($this->paymentAmounts, $visibleInvoiceKeys);
475
-
476
-        // Reset allocation when client changes
477 485
         $this->allocationAmount = null;
478 486
     }
479 487
 }

+ 11
- 0
app/Models/Accounting/Invoice.php Просмотреть файл

@@ -186,6 +186,17 @@ class Invoice extends Document
186 186
         return $query->whereIn('status', InvoiceStatus::unpaidStatuses());
187 187
     }
188 188
 
189
+    // TODO: Consider storing the numeric part of the invoice number separately
190
+    public function scopeByNumber(Builder $query, string $number): Builder
191
+    {
192
+        $invoicePrefix = DocumentDefault::invoice()->first()->number_prefix ?? '';
193
+
194
+        return $query->where(function ($q) use ($number, $invoicePrefix) {
195
+            $q->where('invoice_number', $number)
196
+                ->orWhere('invoice_number', $invoicePrefix . $number);
197
+        });
198
+    }
199
+
189 200
     public function scopeOverdue(Builder $query): Builder
190 201
     {
191 202
         return $query

+ 1
- 1
app/Models/Setting/DocumentDefault.php Просмотреть файл

@@ -75,7 +75,7 @@ class DocumentDefault extends Model
75 75
 
76 76
     public function scopeType(Builder $query, string | DocumentType $type): Builder
77 77
     {
78
-        return $query->where($this->qualifyColumn('type'), $type);
78
+        return $query->where('type', $type);
79 79
     }
80 80
 
81 81
     public function scopeInvoice(Builder $query): Builder

+ 6
- 1
app/Providers/Filament/CompanyPanelProvider.php Просмотреть файл

@@ -284,7 +284,12 @@ class CompanyPanelProvider extends PanelProvider
284 284
             $table
285 285
                 ->paginationPageOptions([5, 10, 25, 50, 100])
286 286
                 ->filtersFormWidth(MaxWidth::Small)
287
-                ->filtersTriggerAction(fn (Tables\Actions\Action $action) => $action->slideOver());
287
+                ->filtersTriggerAction(
288
+                    fn (Tables\Actions\Action $action) => $action
289
+                        ->button()
290
+                        ->label('Filters')
291
+                        ->slideOver()
292
+                );
288 293
         });
289 294
 
290 295
         Tables\Columns\TextColumn::configureUsing(function (Tables\Columns\TextColumn $column): void {

Загрузка…
Отмена
Сохранить