Andrew Wallo 10 月之前
父節點
當前提交
dc731f5ebc

+ 3
- 147
app/Filament/Company/Resources/Sales/InvoiceResource.php 查看文件

@@ -321,91 +321,6 @@ class InvoiceResource extends Resource
321 321
                                     ->columnSpan(2)
322 322
                                     ->view('filament.forms.components.invoice-totals'),
323 323
                             ]),
324
-                        //                        Forms\Components\Repeater::make('lineItems')
325
-                        //                            ->relationship()
326
-                        //                            ->columns(8)
327
-                        //                            ->schema([
328
-                        //                                Forms\Components\Select::make('offering_id')
329
-                        //                                    ->relationship('offering', 'name')
330
-                        //                                    ->preload()
331
-                        //                                    ->columnSpan(2)
332
-                        //                                    ->searchable()
333
-                        //                                    ->required()
334
-                        //                                    ->live()
335
-                        //                                    ->afterStateUpdated(function (Forms\Set $set, $state) {
336
-                        //                                        $offeringId = $state;
337
-                        //                                        $offeringRecord = Offering::with('salesTaxes')->find($offeringId);
338
-                        //
339
-                        //                                        if ($offeringRecord) {
340
-                        //                                            $set('description', $offeringRecord->description);
341
-                        //                                            $set('unit_price', $offeringRecord->price);
342
-                        //                                            $set('total', $offeringRecord->price);
343
-                        //
344
-                        //                                            $salesTaxes = $offeringRecord->salesTaxes->map(function ($tax) {
345
-                        //                                                return [
346
-                        //                                                    'id' => $tax->id,
347
-                        //                                                    'amount' => null, // Amount will be calculated dynamically
348
-                        //                                                ];
349
-                        //                                            })->toArray();
350
-                        //
351
-                        //                                            $set('taxes', $salesTaxes);
352
-                        //                                        }
353
-                        //                                    }),
354
-                        //                                Forms\Components\TextInput::make('description')
355
-                        //                                    ->columnSpan(3)
356
-                        //                                    ->required(),
357
-                        //                                Forms\Components\TextInput::make('quantity')
358
-                        //                                    ->required()
359
-                        //                                    ->numeric()
360
-                        //                                    ->live()
361
-                        //                                    ->default(1),
362
-                        //                                Forms\Components\TextInput::make('unit_price')
363
-                        //                                    ->live()
364
-                        //                                    ->numeric()
365
-                        //                                    ->default(0),
366
-                        //                                Forms\Components\Placeholder::make('total')
367
-                        //                                    ->content(function (Forms\Get $get) {
368
-                        //                                        $quantity = $get('quantity');
369
-                        //                                        $unitPrice = $get('unit_price');
370
-                        //
371
-                        //                                        if ($quantity && $unitPrice) {
372
-                        //                                            return $quantity * $unitPrice;
373
-                        //                                        }
374
-                        //                                    }),
375
-                        //                                TableRepeater::make('taxes')
376
-                        //                                    ->relationship()
377
-                        //                                    ->columnSpanFull()
378
-                        //                                    ->columnStart(6)
379
-                        //                                    ->headers([
380
-                        //                                        Header::make('')->width('200px'),
381
-                        //                                        Header::make('')->width('50px')->align('right'),
382
-                        //                                    ])
383
-                        //                                    ->defaultItems(0)
384
-                        //                                    ->schema([
385
-                        //                                        Forms\Components\Select::make('id') // The ID of the adjustment being attached.
386
-                        //                                            ->label('Tax Adjustment')
387
-                        //                                            ->options(
388
-                        //                                                Adjustment::query()
389
-                        //                                                    ->where('category', AdjustmentCategory::Tax)
390
-                        //                                                    ->pluck('name', 'id')
391
-                        //                                            )
392
-                        //                                            ->preload()
393
-                        //                                            ->searchable()
394
-                        //                                            ->required()
395
-                        //                                            ->live(),
396
-                        //                                        Forms\Components\Placeholder::make('amount')
397
-                        //                                            ->hiddenLabel()
398
-                        //                                            ->content(function (Forms\Get $get) {
399
-                        //                                                $quantity = $get('../../quantity') ?? 0; // Get parent quantity
400
-                        //                                                $unitPrice = $get('../../unit_price') ?? 0; // Get parent unit price
401
-                        //                                                $rate = Adjustment::find($get('id'))->rate ?? 0;
402
-                        //
403
-                        //                                                $total = $quantity * $unitPrice;
404
-                        //
405
-                        //                                                return $total * ($rate / 100);
406
-                        //                                            }),
407
-                        //                                    ]),
408
-                        //                            ]),
409 324
                         Forms\Components\Textarea::make('terms')
410 325
                             ->columnSpanFull(),
411 326
                     ]),
@@ -495,68 +410,9 @@ class InvoiceResource extends Resource
495 410
                     Tables\Actions\EditAction::make(),
496 411
                     Tables\Actions\ViewAction::make(),
497 412
                     Tables\Actions\DeleteAction::make(),
498
-                    Tables\Actions\ReplicateAction::make()
499
-                        ->label('Duplicate')
500
-                        ->excludeAttributes(['status', 'amount_paid', 'amount_due', 'created_by', 'updated_by', 'created_at', 'updated_at', 'invoice_number', 'date', 'due_date'])
501
-                        ->modal(false)
502
-                        ->beforeReplicaSaved(function (Invoice $original, Invoice $replica) {
503
-                            $replica->status = InvoiceStatus::Draft;
504
-                            $replica->invoice_number = Invoice::getNextDocumentNumber();
505
-                            $replica->date = now();
506
-                            $replica->due_date = now()->addDays($original->company->defaultInvoice->payment_terms->getDays());
507
-                        })
508
-                        ->databaseTransaction()
509
-                        ->after(function (Invoice $original, Invoice $replica) {
510
-                            $original->lineItems->each(function (DocumentLineItem $lineItem) use ($replica) {
511
-                                $replicaLineItem = $lineItem->replicate([
512
-                                    'documentable_id',
513
-                                    'documentable_type',
514
-                                    'subtotal',
515
-                                    'total',
516
-                                    'created_by',
517
-                                    'updated_by',
518
-                                    'created_at',
519
-                                    'updated_at',
520
-                                ]);
521
-
522
-                                $replicaLineItem->documentable_id = $replica->id;
523
-                                $replicaLineItem->documentable_type = $replica->getMorphClass();
524
-
525
-                                $replicaLineItem->save();
526
-
527
-                                $replicaLineItem->adjustments()->sync($lineItem->adjustments->pluck('id'));
528
-                            });
529
-                        })
530
-                        ->successRedirectUrl(function (Invoice $replica) {
531
-                            return InvoiceResource::getUrl('edit', ['record' => $replica]);
532
-                        }),
533
-                    Tables\Actions\Action::make('approveDraft')
534
-                        ->label('Approve')
535
-                        ->icon('heroicon-o-check-circle')
536
-                        ->visible(function (Invoice $record) {
537
-                            return $record->isDraft();
538
-                        })
539
-                        ->databaseTransaction()
540
-                        ->successNotificationTitle('Invoice Approved')
541
-                        ->action(function (Invoice $record, Tables\Actions\Action $action) {
542
-                            $record->approveDraft();
543
-
544
-                            $action->success();
545
-                        }),
546
-                    Tables\Actions\Action::make('markAsSent')
547
-                        ->label('Mark as Sent')
548
-                        ->icon('heroicon-o-paper-airplane')
549
-                        ->visible(function (Invoice $record) {
550
-                            return $record->status === InvoiceStatus::Unsent;
551
-                        })
552
-                        ->successNotificationTitle('Invoice Sent')
553
-                        ->action(function (Invoice $record, Tables\Actions\Action $action) {
554
-                            $record->update([
555
-                                'status' => InvoiceStatus::Sent,
556
-                            ]);
557
-
558
-                            $action->success();
559
-                        }),
413
+                    Invoice::getReplicateAction(Tables\Actions\ReplicateAction::class),
414
+                    Invoice::getApproveDraftAction(Tables\Actions\Action::class),
415
+                    Invoice::getMarkAsSentAction(Tables\Actions\Action::class),
560 416
                     Tables\Actions\Action::make('recordPayment')
561 417
                         ->label(fn (Invoice $record) => $record->status === InvoiceStatus::Overpaid ? 'Refund Overpayment' : 'Record Payment')
562 418
                         ->stickyModalHeader()

+ 23
- 0
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ViewInvoice.php 查看文件

@@ -6,11 +6,14 @@ use App\Filament\Company\Resources\Sales\ClientResource;
6 6
 use App\Filament\Company\Resources\Sales\InvoiceResource;
7 7
 use App\Models\Accounting\Invoice;
8 8
 use Carbon\CarbonInterface;
9
+use Filament\Actions;
9 10
 use Filament\Infolists\Components\Section;
10 11
 use Filament\Infolists\Components\TextEntry;
11 12
 use Filament\Infolists\Infolist;
12 13
 use Filament\Resources\Pages\ViewRecord;
13 14
 use Filament\Support\Enums\FontWeight;
15
+use Filament\Support\Enums\IconPosition;
16
+use Filament\Support\Enums\IconSize;
14 17
 use Illuminate\Support\Carbon;
15 18
 
16 19
 class ViewInvoice extends ViewRecord
@@ -21,6 +24,26 @@ class ViewInvoice extends ViewRecord
21 24
         'refresh' => '$refresh',
22 25
     ];
23 26
 
27
+    protected function getHeaderActions(): array
28
+    {
29
+        return [
30
+            Actions\ActionGroup::make([
31
+                Actions\EditAction::make(),
32
+                Actions\DeleteAction::make(),
33
+                Invoice::getApproveDraftAction(),
34
+                Invoice::getMarkAsSentAction(),
35
+                Invoice::getReplicateAction(),
36
+            ])
37
+                ->label('Actions')
38
+                ->button()
39
+                ->outlined()
40
+                ->dropdownPlacement('bottom-end')
41
+                ->icon('heroicon-c-chevron-down')
42
+                ->iconSize(IconSize::Small)
43
+                ->iconPosition(IconPosition::After),
44
+        ];
45
+    }
46
+
24 47
     public function infolist(Infolist $infolist): Infolist
25 48
     {
26 49
         return $infolist

+ 78
- 0
app/Models/Accounting/Invoice.php 查看文件

@@ -9,8 +9,12 @@ use App\Concerns\CompanyOwned;
9 9
 use App\Enums\Accounting\InvoiceStatus;
10 10
 use App\Enums\Accounting\JournalEntryType;
11 11
 use App\Enums\Accounting\TransactionType;
12
+use App\Filament\Company\Resources\Sales\InvoiceResource;
12 13
 use App\Models\Common\Client;
13 14
 use App\Observers\InvoiceObserver;
15
+use Filament\Actions\Action;
16
+use Filament\Actions\MountableAction;
17
+use Filament\Actions\ReplicateAction;
14 18
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
15 19
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
16 20
 use Illuminate\Database\Eloquent\Casts\Attribute;
@@ -253,4 +257,78 @@ class Invoice extends Model
253 257
             'status' => InvoiceStatus::Unsent,
254 258
         ]);
255 259
     }
260
+
261
+    public static function getApproveDraftAction(string $action = Action::class): MountableAction
262
+    {
263
+        return $action::make('approveDraft')
264
+            ->label('Approve')
265
+            ->icon('heroicon-o-check-circle')
266
+            ->visible(function (Invoice $record) {
267
+                return $record->isDraft();
268
+            })
269
+            ->databaseTransaction()
270
+            ->successNotificationTitle('Invoice Approved')
271
+            ->action(function (Invoice $record, MountableAction $action) {
272
+                $record->approveDraft();
273
+
274
+                $action->success();
275
+            });
276
+    }
277
+
278
+    public static function getMarkAsSentAction(string $action = Action::class): MountableAction
279
+    {
280
+        return $action::make('markAsSent')
281
+            ->label('Mark as Sent')
282
+            ->icon('heroicon-o-paper-airplane')
283
+            ->visible(function (Invoice $record) {
284
+                return ! $record->last_sent;
285
+            })
286
+            ->successNotificationTitle('Invoice Sent')
287
+            ->action(function (Invoice $record, MountableAction $action) {
288
+                $record->update([
289
+                    'status' => InvoiceStatus::Sent,
290
+                    'last_sent' => now(),
291
+                ]);
292
+
293
+                $action->success();
294
+            });
295
+    }
296
+
297
+    public static function getReplicateAction(string $action = ReplicateAction::class): MountableAction
298
+    {
299
+        return $action::make()
300
+            ->excludeAttributes(['status', 'amount_paid', 'amount_due', 'created_by', 'updated_by', 'created_at', 'updated_at', 'invoice_number', 'date', 'due_date'])
301
+            ->modal(false)
302
+            ->beforeReplicaSaved(function (Invoice $original, Invoice $replica) {
303
+                $replica->status = InvoiceStatus::Draft;
304
+                $replica->invoice_number = Invoice::getNextDocumentNumber();
305
+                $replica->date = now();
306
+                $replica->due_date = now()->addDays($original->company->defaultInvoice->payment_terms->getDays());
307
+            })
308
+            ->databaseTransaction()
309
+            ->after(function (Invoice $original, Invoice $replica) {
310
+                $original->lineItems->each(function (DocumentLineItem $lineItem) use ($replica) {
311
+                    $replicaLineItem = $lineItem->replicate([
312
+                        'documentable_id',
313
+                        'documentable_type',
314
+                        'subtotal',
315
+                        'total',
316
+                        'created_by',
317
+                        'updated_by',
318
+                        'created_at',
319
+                        'updated_at',
320
+                    ]);
321
+
322
+                    $replicaLineItem->documentable_id = $replica->id;
323
+                    $replicaLineItem->documentable_type = $replica->getMorphClass();
324
+
325
+                    $replicaLineItem->save();
326
+
327
+                    $replicaLineItem->adjustments()->sync($lineItem->adjustments->pluck('id'));
328
+                });
329
+            })
330
+            ->successRedirectUrl(function (Invoice $replica) {
331
+                return InvoiceResource::getUrl('edit', ['record' => $replica]);
332
+            });
333
+    }
256 334
 }

+ 1
- 6
app/Observers/InvoiceObserver.php 查看文件

@@ -23,12 +23,7 @@ class InvoiceObserver
23 23
      */
24 24
     public function updated(Invoice $invoice): void
25 25
     {
26
-        if ($invoice->wasChanged('status')) {
27
-            match ($invoice->status) {
28
-                InvoiceStatus::Sent => $invoice->updateQuietly(['last_sent' => now()]),
29
-                default => null,
30
-            };
31
-        }
26
+        //
32 27
     }
33 28
 
34 29
     public function deleting(Invoice $invoice): void

Loading…
取消
儲存