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

+ 25
- 0
app/Filament/Company/Resources/Common/OfferingResource.php Просмотреть файл

8
 use App\Enums\Accounting\AdjustmentType;
8
 use App\Enums\Accounting\AdjustmentType;
9
 use App\Enums\Common\OfferingType;
9
 use App\Enums\Common\OfferingType;
10
 use App\Filament\Company\Resources\Common\OfferingResource\Pages;
10
 use App\Filament\Company\Resources\Common\OfferingResource\Pages;
11
+use App\Filament\Forms\Components\Banner;
11
 use App\Filament\Forms\Components\CreateAccountSelect;
12
 use App\Filament\Forms\Components\CreateAccountSelect;
12
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
13
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
13
 use App\Models\Common\Offering;
14
 use App\Models\Common\Offering;
18
 use Filament\Tables;
19
 use Filament\Tables;
19
 use Filament\Tables\Table;
20
 use Filament\Tables\Table;
20
 use Illuminate\Database\Eloquent\Builder;
21
 use Illuminate\Database\Eloquent\Builder;
22
+use Illuminate\Support\HtmlString;
21
 use Illuminate\Support\Str;
23
 use Illuminate\Support\Str;
22
 use JaOcero\RadioDeck\Forms\Components\RadioDeck;
24
 use JaOcero\RadioDeck\Forms\Components\RadioDeck;
23
 
25
 
31
     {
33
     {
32
         return $form
34
         return $form
33
             ->schema([
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
                 Forms\Components\Section::make('General')
59
                 Forms\Components\Section::make('General')
35
                     ->schema([
60
                     ->schema([
36
                         RadioDeck::make('type')
61
                         RadioDeck::make('type')

+ 27
- 1
app/Filament/Company/Resources/Sales/RecurringInvoiceResource/Pages/ViewRecurringInvoice.php Просмотреть файл

20
 use Filament\Support\Enums\IconPosition;
20
 use Filament\Support\Enums\IconPosition;
21
 use Filament\Support\Enums\IconSize;
21
 use Filament\Support\Enums\IconSize;
22
 use Filament\Support\Enums\MaxWidth;
22
 use Filament\Support\Enums\MaxWidth;
23
+use Illuminate\Support\HtmlString;
23
 use Illuminate\Support\Str;
24
 use Illuminate\Support\Str;
24
 
25
 
25
 class ViewRecurringInvoice extends ViewRecord
26
 class ViewRecurringInvoice extends ViewRecord
59
     {
60
     {
60
         return $infolist
61
         return $infolist
61
             ->schema([
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
                 BannerEntry::make('scheduleIsNotSet')
88
                 BannerEntry::make('scheduleIsNotSet')
63
                     ->info()
89
                     ->info()
64
                     ->title('Schedule not set')
90
                     ->title('Schedule not set')
73
                     ->info()
99
                     ->info()
74
                     ->title('Ready to Approve')
100
                     ->title('Ready to Approve')
75
                     ->description('This recurring invoice is ready for approval. Review the details, and approve it when you’re ready to start generating invoices.')
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
                     ->columnSpanFull()
103
                     ->columnSpanFull()
78
                     ->actions([
104
                     ->actions([
79
                         RecurringInvoice::getApproveDraftAction(Action::class)
105
                         RecurringInvoice::getApproveDraftAction(Action::class)

+ 24
- 5
app/Models/Accounting/RecurringInvoice.php Просмотреть файл

17
 use App\Enums\Accounting\Month;
17
 use App\Enums\Accounting\Month;
18
 use App\Enums\Accounting\RecurringInvoiceStatus;
18
 use App\Enums\Accounting\RecurringInvoiceStatus;
19
 use App\Enums\Setting\PaymentTerms;
19
 use App\Enums\Setting\PaymentTerms;
20
+use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages\ViewRecurringInvoice;
20
 use App\Filament\Forms\Components\Banner;
21
 use App\Filament\Forms\Components\Banner;
21
 use App\Filament\Forms\Components\CustomSection;
22
 use App\Filament\Forms\Components\CustomSection;
22
 use App\Models\Common\Client;
23
 use App\Models\Common\Client;
28
 use Filament\Actions\MountableAction;
29
 use Filament\Actions\MountableAction;
29
 use Filament\Forms;
30
 use Filament\Forms;
30
 use Filament\Forms\Form;
31
 use Filament\Forms\Form;
32
+use Filament\Notifications\Notification;
31
 use Guava\FilamentClusters\Forms\Cluster;
33
 use Guava\FilamentClusters\Forms\Cluster;
32
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
34
 use Illuminate\Database\Eloquent\Attributes\CollectedBy;
33
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
35
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
35
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
37
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
36
 use Illuminate\Database\Eloquent\Relations\HasMany;
38
 use Illuminate\Database\Eloquent\Relations\HasMany;
37
 use Illuminate\Support\Carbon;
39
 use Illuminate\Support\Carbon;
40
+use Livewire\Component;
38
 
41
 
39
 #[CollectedBy(DocumentCollection::class)]
42
 #[CollectedBy(DocumentCollection::class)]
40
 #[ObservedBy(RecurringInvoiceObserver::class)]
43
 #[ObservedBy(RecurringInvoiceObserver::class)]
557
     {
560
     {
558
         return $action::make('approveDraft')
561
         return $action::make('approveDraft')
559
             ->label('Approve')
562
             ->label('Approve')
560
-            ->icon('heroicon-o-check-circle')
563
+            ->icon('heroicon-m-check-circle')
561
             ->visible(function (self $record) {
564
             ->visible(function (self $record) {
562
                 return $record->canBeApproved();
565
                 return $record->canBeApproved();
563
             })
566
             })
567
+            ->requiresConfirmation()
564
             ->databaseTransaction()
568
             ->databaseTransaction()
565
             ->successNotificationTitle('Recurring invoice approved')
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
     {
111
     {
112
         return $this->adjustments()->where('category', AdjustmentCategory::Discount)->where('type', AdjustmentType::Purchase);
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
         }
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
      * Handle the Adjustment "saving" event.
58
      * Handle the Adjustment "saving" event.
44
      */
59
      */

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