Andrew Wallo 6 个月前
父节点
当前提交
4d8b73e4fa

+ 25
- 0
app/Filament/Company/Resources/Common/OfferingResource.php 查看文件

@@ -8,6 +8,7 @@ use App\Enums\Accounting\AdjustmentCategory;
8 8
 use App\Enums\Accounting\AdjustmentType;
9 9
 use App\Enums\Common\OfferingType;
10 10
 use App\Filament\Company\Resources\Common\OfferingResource\Pages;
11
+use App\Filament\Forms\Components\Banner;
11 12
 use App\Filament\Forms\Components\CreateAccountSelect;
12 13
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
13 14
 use App\Models\Common\Offering;
@@ -18,6 +19,7 @@ use Filament\Resources\Resource;
18 19
 use Filament\Tables;
19 20
 use Filament\Tables\Table;
20 21
 use Illuminate\Database\Eloquent\Builder;
22
+use Illuminate\Support\HtmlString;
21 23
 use Illuminate\Support\Str;
22 24
 use JaOcero\RadioDeck\Forms\Components\RadioDeck;
23 25
 
@@ -31,6 +33,29 @@ class OfferingResource extends Resource
31 33
     {
32 34
         return $form
33 35
             ->schema([
36
+                Banner::make('inactiveAdjustments')
37
+                    ->label('Inactive adjustments')
38
+                    ->warning()
39
+                    ->icon('heroicon-o-exclamation-triangle')
40
+                    ->visible(fn (Offering $record) => $record->hasInactiveAdjustments())
41
+                    ->columnSpanFull()
42
+                    ->description(function (Offering $record) {
43
+                        $inactiveAdjustments = collect();
44
+
45
+                        foreach ($record->adjustments as $adjustment) {
46
+                            if ($adjustment->isInactive() && $inactiveAdjustments->doesntContain($adjustment->name)) {
47
+                                $inactiveAdjustments->push($adjustment->name);
48
+                            }
49
+                        }
50
+
51
+                        $adjustmentsList = $inactiveAdjustments->map(static function ($name) {
52
+                            return "<span class='font-medium'>{$name}</span>";
53
+                        })->join(', ');
54
+
55
+                        $output = "<p class='text-sm'>This offering contains inactive adjustments that need to be addressed: {$adjustmentsList}</p>";
56
+
57
+                        return new HtmlString($output);
58
+                    }),
34 59
                 Forms\Components\Section::make('General')
35 60
                     ->schema([
36 61
                         RadioDeck::make('type')

+ 27
- 1
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php 查看文件

@@ -20,6 +20,7 @@ use Filament\Support\Enums\FontWeight;
20 20
 use Filament\Support\Enums\IconPosition;
21 21
 use Filament\Support\Enums\IconSize;
22 22
 use Filament\Support\Enums\MaxWidth;
23
+use Illuminate\Support\HtmlString;
23 24
 use Illuminate\Support\Str;
24 25
 
25 26
 class ViewRecurringInvoice extends ViewRecord
@@ -59,6 +60,31 @@ class ViewRecurringInvoice extends ViewRecord
59 60
     {
60 61
         return $infolist
61 62
             ->schema([
63
+                BannerEntry::make('inactiveAdjustments')
64
+                    ->label('Inactive adjustments')
65
+                    ->warning()
66
+                    ->icon('heroicon-o-exclamation-triangle')
67
+                    ->visible(fn (RecurringInvoice $record) => $record->hasInactiveAdjustments() && $record->canBeApproved())
68
+                    ->columnSpanFull()
69
+                    ->description(function (RecurringInvoice $record) {
70
+                        $inactiveAdjustments = collect();
71
+
72
+                        foreach ($record->lineItems as $lineItem) {
73
+                            foreach ($lineItem->adjustments as $adjustment) {
74
+                                if ($adjustment->isInactive() && $inactiveAdjustments->doesntContain($adjustment->name)) {
75
+                                    $inactiveAdjustments->push($adjustment->name);
76
+                                }
77
+                            }
78
+                        }
79
+
80
+                        $adjustmentsList = $inactiveAdjustments->map(static function ($name) {
81
+                            return "<span class='font-medium'>{$name}</span>";
82
+                        })->join(', ');
83
+
84
+                        $output = "<p class='text-sm'>This recurring invoice contains inactive adjustments that need to be addressed before approval: {$adjustmentsList}</p>";
85
+
86
+                        return new HtmlString($output);
87
+                    }),
62 88
                 BannerEntry::make('scheduleIsNotSet')
63 89
                     ->info()
64 90
                     ->title('Schedule not set')
@@ -73,7 +99,7 @@ class ViewRecurringInvoice extends ViewRecord
73 99
                     ->info()
74 100
                     ->title('Ready to Approve')
75 101
                     ->description('This recurring invoice is ready for approval. Review the details, and approve it when you’re ready to start generating invoices.')
76
-                    ->visible(fn (RecurringInvoice $record) => $record->canBeApproved())
102
+                    ->visible(fn (RecurringInvoice $record) => $record->canBeApproved() && ! $record->hasInactiveAdjustments())
77 103
                     ->columnSpanFull()
78 104
                     ->actions([
79 105
                         RecurringInvoice::getApproveDraftAction(Action::class)

+ 24
- 5
app/Models/Accounting/RecurringInvoice.php 查看文件

@@ -17,6 +17,7 @@ use App\Enums\Accounting\InvoiceStatus;
17 17
 use App\Enums\Accounting\Month;
18 18
 use App\Enums\Accounting\RecurringInvoiceStatus;
19 19
 use App\Enums\Setting\PaymentTerms;
20
+use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages\ViewRecurringInvoice;
20 21
 use App\Filament\Forms\Components\Banner;
21 22
 use App\Filament\Forms\Components\CustomSection;
22 23
 use App\Models\Common\Client;
@@ -28,6 +29,7 @@ use Filament\Actions\Action;
28 29
 use Filament\Actions\MountableAction;
29 30
 use Filament\Forms;
30 31
 use Filament\Forms\Form;
32
+use Filament\Notifications\Notification;
31 33
 use Guava\FilamentClusters\Forms\Cluster;
32 34
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
33 35
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
@@ -35,6 +37,7 @@ use Illuminate\Database\Eloquent\Model;
35 37
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
36 38
 use Illuminate\Database\Eloquent\Relations\HasMany;
37 39
 use Illuminate\Support\Carbon;
40
+use Livewire\Component;
38 41
 
39 42
 #[CollectedBy(DocumentCollection::class)]
40 43
 #[ObservedBy(RecurringInvoiceObserver::class)]
@@ -557,16 +560,32 @@ class RecurringInvoice extends Document
557 560
     {
558 561
         return $action::make('approveDraft')
559 562
             ->label('Approve')
560
-            ->icon('heroicon-o-check-circle')
563
+            ->icon('heroicon-m-check-circle')
561 564
             ->visible(function (self $record) {
562 565
                 return $record->canBeApproved();
563 566
             })
567
+            ->requiresConfirmation()
564 568
             ->databaseTransaction()
565 569
             ->successNotificationTitle('Recurring invoice approved')
566
-            ->action(function (self $record, MountableAction $action) {
567
-                $record->approveDraft();
568
-
569
-                $action->success();
570
+            ->action(function (self $record, MountableAction $action, Component $livewire) {
571
+                if ($record->hasInactiveAdjustments()) {
572
+                    $isViewPage = $livewire instanceof ViewRecurringInvoice;
573
+
574
+                    if (! $isViewPage) {
575
+                        redirect(ViewRecurringInvoice::getUrl(['record' => $record->id]));
576
+                    } else {
577
+                        Notification::make()
578
+                            ->warning()
579
+                            ->title('Cannot approve recurring invoice')
580
+                            ->body('This recurring invoice has inactive adjustments that must be addressed first.')
581
+                            ->persistent()
582
+                            ->send();
583
+                    }
584
+                } else {
585
+                    $record->approveDraft();
586
+
587
+                    $action->success();
588
+                }
570 589
             });
571 590
     }
572 591
 

+ 7
- 0
app/Models/Common/Offering.php 查看文件

@@ -111,4 +111,11 @@ class Offering extends Model
111 111
     {
112 112
         return $this->adjustments()->where('category', AdjustmentCategory::Discount)->where('type', AdjustmentType::Purchase);
113 113
     }
114
+
115
+    public function hasInactiveAdjustments(): bool
116
+    {
117
+        return $this->adjustments->contains(function (Adjustment $adjustment) {
118
+            return $adjustment->isInactive();
119
+        });
120
+    }
114 121
 }

+ 15
- 0
app/Observers/AdjustmentObserver.php 查看文件

@@ -39,6 +39,21 @@ class AdjustmentObserver
39 39
         }
40 40
     }
41 41
 
42
+    public function saved(Adjustment $adjustment): void
43
+    {
44
+        if ($adjustment->wasChanged('status') || $adjustment->wasRecentlyCreated) {
45
+            if ($adjustment->isInactive()) {
46
+                $adjustment->account?->update([
47
+                    'archived' => true,
48
+                ]);
49
+            } else {
50
+                $adjustment->account?->update([
51
+                    'archived' => false,
52
+                ]);
53
+            }
54
+        }
55
+    }
56
+
42 57
     /**
43 58
      * Handle the Adjustment "saving" event.
44 59
      */

正在加载...
取消
保存