瀏覽代碼

refactor: invoice preview and currency logic

3.x
wallo 2 年之前
父節點
當前提交
7fb533b4cc

+ 7
- 0
app/Enums/PaymentTerms.php 查看文件

@@ -41,4 +41,11 @@ enum PaymentTerms: string implements HasLabel
41 41
             self::Net90 => 90,
42 42
         };
43 43
     }
44
+
45
+    public function getDueDate(): string
46
+    {
47
+        $days = $this->getDays() ?? 0;
48
+
49
+        return now()->addDays($days)->format('M d, Y');
50
+    }
44 51
 }

+ 3
- 1
app/Events/DefaultCurrencyChanged.php 查看文件

@@ -9,7 +9,9 @@ use Illuminate\Queue\SerializesModels;
9 9
 
10 10
 class DefaultCurrencyChanged
11 11
 {
12
-    use Dispatchable, InteractsWithSockets, SerializesModels;
12
+    use Dispatchable;
13
+    use InteractsWithSockets;
14
+    use SerializesModels;
13 15
 
14 16
     public Currency $currency;
15 17
 

+ 63
- 24
app/Filament/Company/Pages/Setting/Invoice.php 查看文件

@@ -6,7 +6,7 @@ use App\Enums\{DocumentType, Font, PaymentTerms, Template};
6 6
 use App\Models\Setting\DocumentDefault as InvoiceModel;
7 7
 use Filament\Actions\{Action, ActionGroup};
8 8
 use Filament\Forms\Components\{Checkbox, ColorPicker, Component, FileUpload, Group, Section, Select, TextInput, Textarea, ViewField};
9
-use Filament\Forms\{Form, Get};
9
+use Filament\Forms\{Form, Get, Set};
10 10
 use Filament\Notifications\Notification;
11 11
 use Filament\Pages\Concerns\InteractsWithFormActions;
12 12
 use Filament\Pages\Page;
@@ -119,6 +119,7 @@ class Invoice extends Page
119 119
     public function form(Form $form): Form
120 120
     {
121 121
         return $form
122
+            ->live()
122 123
             ->schema([
123 124
                 $this->getGeneralSection(),
124 125
                 $this->getContentSection(),
@@ -135,17 +136,15 @@ class Invoice extends Page
135 136
             ->schema([
136 137
                 TextInput::make('number_prefix')
137 138
                     ->label('Number Prefix')
138
-                    ->live()
139
-                    ->required(),
139
+                    ->nullable(),
140 140
                 Select::make('number_digits')
141 141
                     ->label('Number Digits')
142 142
                     ->options(InvoiceModel::availableNumberDigits())
143
+                    ->selectablePlaceholder(false)
143 144
                     ->native(false)
144
-                    ->live()
145
-                    ->required(),
145
+                    ->rule('required'),
146 146
                 TextInput::make('number_next')
147 147
                     ->label('Next Number')
148
-                    ->live()
149 148
                     ->maxLength(static fn (Get $get) => $get('number_digits'))
150 149
                     ->suffix(static function (Get $get, $state) {
151 150
                         $number_prefix = $get('number_prefix');
@@ -154,13 +153,13 @@ class Invoice extends Page
154 153
 
155 154
                         return InvoiceModel::getNumberNext(true, true, $number_prefix, $number_digits, $number_next);
156 155
                     })
157
-                    ->required(),
156
+                    ->rule('required'),
158 157
                 Select::make('payment_terms')
159 158
                     ->label('Payment Terms')
160 159
                     ->options(PaymentTerms::class)
160
+                    ->selectablePlaceholder(false)
161 161
                     ->native(false)
162
-                    ->live()
163
-                    ->required(),
162
+                    ->rule('required'),
164 163
             ])->columns();
165 164
     }
166 165
 
@@ -170,19 +169,15 @@ class Invoice extends Page
170 169
             ->schema([
171 170
                 TextInput::make('header')
172 171
                     ->label('Header')
173
-                    ->live()
174
-                    ->required(),
172
+                    ->nullable(),
175 173
                 TextInput::make('subheader')
176 174
                     ->label('Subheader')
177
-                    ->live()
178 175
                     ->nullable(),
179 176
                 Textarea::make('terms')
180 177
                     ->label('Terms')
181
-                    ->live()
182 178
                     ->nullable(),
183 179
                 Textarea::make('footer')
184 180
                     ->label('Footer / Notes')
185
-                    ->live()
186 181
                     ->nullable(),
187 182
             ])->columns();
188 183
     }
@@ -193,7 +188,6 @@ class Invoice extends Page
193 188
             ->description('Choose the template and edit the column names.')
194 189
             ->schema([
195 190
                 Group::make()
196
-                    ->live()
197 191
                     ->schema([
198 192
                         FileUpload::make('logo')
199 193
                             ->label('Logo')
@@ -231,12 +225,24 @@ class Invoice extends Page
231 225
                             ->label('Template')
232 226
                             ->native(false)
233 227
                             ->options(Template::class)
234
-                            ->required(),
228
+                            ->selectablePlaceholder(false)
229
+                            ->rule('required'),
235 230
                         Select::make('item_name.option')
236 231
                             ->label('Item Name')
237 232
                             ->native(false)
238
-                            ->required()
239
-                            ->options(InvoiceModel::getAvailableItemNameOptions()),
233
+                            ->options(InvoiceModel::getAvailableItemNameOptions())
234
+                            ->selectablePlaceholder(false)
235
+                            ->afterStateUpdated(static function (Get $get, Set $set, $state, $old) {
236
+                                if ($state !== 'other' && $old === 'other' && filled($get('item_name.custom'))) {
237
+                                    $set('item_name.old_custom', $get('item_name.custom'));
238
+                                    $set('item_name.custom', null);
239
+                                }
240
+
241
+                                if ($state === 'other' && $old !== 'other') {
242
+                                    $set('item_name.custom', $get('item_name.old_custom'));
243
+                                }
244
+                            })
245
+                            ->rule('required'),
240 246
                         TextInput::make('item_name.custom')
241 247
                             ->hiddenLabel()
242 248
                             ->disabled(static fn (callable $get) => $get('item_name.option') !== 'other')
@@ -244,8 +250,19 @@ class Invoice extends Page
244 250
                         Select::make('unit_name.option')
245 251
                             ->label('Unit Name')
246 252
                             ->native(false)
247
-                            ->required()
248
-                            ->options(InvoiceModel::getAvailableUnitNameOptions()),
253
+                            ->options(InvoiceModel::getAvailableUnitNameOptions())
254
+                            ->selectablePlaceholder(false)
255
+                            ->afterStateUpdated(static function (Get $get, Set $set, $state, $old) {
256
+                                if ($state !== 'other' && $old === 'other' && filled($get('unit_name.custom'))) {
257
+                                    $set('unit_name.old_custom', $get('unit_name.custom'));
258
+                                    $set('unit_name.custom', null);
259
+                                }
260
+
261
+                                if ($state === 'other' && $old !== 'other') {
262
+                                    $set('unit_name.custom', $get('unit_name.old_custom'));
263
+                                }
264
+                            })
265
+                            ->rule('required'),
249 266
                         TextInput::make('unit_name.custom')
250 267
                             ->hiddenLabel()
251 268
                             ->disabled(static fn (callable $get) => $get('unit_name.option') !== 'other')
@@ -253,8 +270,19 @@ class Invoice extends Page
253 270
                         Select::make('price_name.option')
254 271
                             ->label('Price Name')
255 272
                             ->native(false)
256
-                            ->required()
257
-                            ->options(InvoiceModel::getAvailablePriceNameOptions()),
273
+                            ->options(InvoiceModel::getAvailablePriceNameOptions())
274
+                            ->afterStateUpdated(static function (Get $get, Set $set, $state, $old) {
275
+                                if ($state !== 'other' && $old === 'other' && filled($get('price_name.custom'))) {
276
+                                    $set('price_name.old_custom', $get('price_name.custom'));
277
+                                    $set('price_name.custom', null);
278
+                                }
279
+
280
+                                if ($state === 'other' && $old !== 'other') {
281
+                                    $set('price_name.custom', $get('price_name.old_custom'));
282
+                                }
283
+                            })
284
+                            ->selectablePlaceholder(false)
285
+                            ->rule('required'),
258 286
                         TextInput::make('price_name.custom')
259 287
                             ->hiddenLabel()
260 288
                             ->disabled(static fn (callable $get) => $get('price_name.option') !== 'other')
@@ -262,8 +290,19 @@ class Invoice extends Page
262 290
                         Select::make('amount_name.option')
263 291
                             ->label('Amount Name')
264 292
                             ->native(false)
265
-                            ->required()
266
-                            ->options(InvoiceModel::getAvailableAmountNameOptions()),
293
+                            ->options(InvoiceModel::getAvailableAmountNameOptions())
294
+                            ->selectablePlaceholder(false)
295
+                            ->afterStateUpdated(static function (Get $get, Set $set, $state, $old) {
296
+                                if ($state !== 'other' && $old === 'other' && filled($get('amount_name.custom'))) {
297
+                                    $set('amount_name.old_custom', $get('amount_name.custom'));
298
+                                    $set('amount_name.custom', null);
299
+                                }
300
+
301
+                                if ($state === 'other' && $old !== 'other') {
302
+                                    $set('amount_name.custom', $get('amount_name.old_custom'));
303
+                                }
304
+                            })
305
+                            ->rule('required'),
267 306
                         TextInput::make('amount_name.custom')
268 307
                             ->hiddenLabel()
269 308
                             ->disabled(static fn (callable $get) => $get('amount_name.option') !== 'other')

+ 12
- 10
app/Filament/Company/Resources/Setting/CurrencyResource.php 查看文件

@@ -43,7 +43,12 @@ class CurrencyResource extends Resource
43 43
                             ->required()
44 44
                             ->hidden(static fn (Forms\Get $get, $state): bool => $get('enabled') && $state !== null)
45 45
                             ->afterStateUpdated(static function (Forms\Set $set, $state) {
46
+                                $fields = ['name', 'rate', 'precision', 'symbol', 'symbol_first', 'decimal_mark', 'thousands_separator'];
47
+
46 48
                                 if ($state === null) {
49
+                                    foreach ($fields as $field) {
50
+                                        $set($field, null);
51
+                                    }
47 52
                                     return;
48 53
                                 }
49 54
 
@@ -56,18 +61,15 @@ class CurrencyResource extends Resource
56 61
 
57 62
                                 $rate = $defaultCurrencyCode ? $currencyService->getCachedExchangeRate($defaultCurrencyCode, $code) : 1;
58 63
 
59
-                                $set('name', $selectedCurrencyCode['name'] ?? '');
60
-                                $set('rate', $rate ?? '');
61
-                                $set('precision', $selectedCurrencyCode['precision'] ?? '');
62
-                                $set('symbol', $selectedCurrencyCode['symbol'] ?? '');
63
-                                $set('symbol_first', $selectedCurrencyCode['symbol_first'] ?? '');
64
-                                $set('decimal_mark', $selectedCurrencyCode['decimal_mark'] ?? '');
65
-                                $set('thousands_separator', $selectedCurrencyCode['thousands_separator'] ?? '');
64
+                                foreach ($fields as $field) {
65
+                                    $set($field, $selectedCurrencyCode[$field] ?? ($field === 'rate' ? $rate : ''));
66
+                                }
66 67
                             }),
67 68
                         Forms\Components\TextInput::make('code')
68 69
                             ->label('Code')
69
-                            ->hidden(static fn (Forms\Get $get): bool => !($get('enabled') && $get('code') !== null))
70
+                            ->hidden(static fn (Forms\Get $get): bool => ! ($get('enabled') && $get('code') !== null))
70 71
                             ->disabled(static fn (Forms\Get $get): bool => $get('enabled'))
72
+                            ->dehydrated()
71 73
                             ->required(),
72 74
                         Forms\Components\TextInput::make('name')
73 75
                             ->label('Name')
@@ -94,7 +96,7 @@ class CurrencyResource extends Resource
94 96
                             ->label('Symbol Position')
95 97
                             ->native(false)
96 98
                             ->selectablePlaceholder(false)
97
-                            ->formatStateUsing(static fn($state) => isset($state) ? (int) $state : null)
99
+                            ->formatStateUsing(static fn ($state) => isset($state) ? (int) $state : null)
98 100
                             ->boolean('Before Amount', 'After Amount', 'Select a symbol position...')
99 101
                             ->required(),
100 102
                         Forms\Components\TextInput::make('decimal_mark')
@@ -120,7 +122,7 @@ class CurrencyResource extends Resource
120 122
                             ->offColor('danger')
121 123
                             ->onColor('primary')
122 124
                             ->afterStateUpdated(static function (Forms\Set $set, Forms\Get $get, $state) {
123
-                                $enabledState = (bool)$state;
125
+                                $enabledState = (bool) $state;
124 126
                                 $code = $get('code');
125 127
 
126 128
                                 $defaultCurrencyCode = Currency::getDefaultCurrencyCode();

+ 1
- 1
app/Filament/Company/Resources/Setting/CurrencyResource/Pages/EditCurrency.php 查看文件

@@ -40,7 +40,7 @@ class EditCurrency extends EditRecord
40 40
     /**
41 41
      * @throws Halt
42 42
      */
43
-    protected function handleRecordUpdate(Model|Currency $record, array $data): Model|Currency
43
+    protected function handleRecordUpdate(Model | Currency $record, array $data): Model | Currency
44 44
     {
45 45
         $user = Auth::user();
46 46
 

+ 18
- 33
app/View/Models/InvoiceViewModel.php 查看文件

@@ -95,15 +95,12 @@ class InvoiceViewModel
95 95
 
96 96
     public function payment_terms(): string
97 97
     {
98
-        return $this->data['payment_terms'] ?? $this->invoice->payment_terms ?? PaymentTerms::DEFAULT;
98
+        return $this->data['payment_terms'] ?? $this->invoice->payment_terms?->value ?? PaymentTerms::DEFAULT;
99 99
     }
100 100
 
101 101
     public function invoice_due_date(): string
102 102
     {
103
-        $enumPaymentTerms = PaymentTerms::tryFrom($this->payment_terms());
104
-        $days = $enumPaymentTerms ? $enumPaymentTerms->getDays() : 0;
105
-
106
-        return now()->addDays($days)->format('M d, Y');
103
+        return PaymentTerms::tryFrom($this->payment_terms())?->getDueDate();
107 104
     }
108 105
 
109 106
     // Invoice header related methods
@@ -146,49 +143,38 @@ class InvoiceViewModel
146 143
         return $this->data['terms'] ?? $this->invoice->terms ?? 'Payment is due within thirty (30) days from the date of invoice. Any discrepancies should be reported within fourteen (14) days of receipt.';
147 144
     }
148 145
 
149
-    // Invoice column related methods
150
-    public function item_name(): string
146
+    public function getItemColumnName(string $column, string $default): string
151 147
     {
152
-        $custom_item_name = $this->data['item_name']['custom'] ?? null;
148
+        $custom = $this->data[$column]['custom'] ?? $this->invoice->{$column . '_custom'} ?? null;
153 149
 
154
-        if ($custom_item_name) {
155
-            return $custom_item_name;
150
+        if ($custom) {
151
+            return $custom;
156 152
         }
157 153
 
158
-        return ucwords($this->data['item_name']['option']) ?? ucwords($this->invoice->item_name_option) ?? $this->invoice->item_name_custom ?? 'Items';
154
+        $option = $this->data[$column]['option'] ?? $this->invoice->{$column . '_option'} ?? null;
155
+
156
+        return $option ? ucwords($option) : $default;
159 157
     }
160 158
 
161
-    public function unit_name(): string
159
+    // Invoice column related methods
160
+    public function item_name(): string
162 161
     {
163
-        $custom_unit_name = $this->data['unit_name']['custom'] ?? null;
164
-
165
-        if ($custom_unit_name) {
166
-            return $custom_unit_name;
167
-        }
162
+        return $this->getItemColumnName('item_name', 'Items');
163
+    }
168 164
 
169
-        return ucwords($this->data['unit_name']['option']) ?? ucwords($this->invoice->unit_name_option) ?? $this->invoice->unit_name_custom ?? 'Quantity';
165
+    public function unit_name(): string
166
+    {
167
+        return $this->getItemColumnName('unit_name', 'Quantity');
170 168
     }
171 169
 
172 170
     public function price_name(): string
173 171
     {
174
-        $custom_price_name = $this->data['price_name']['custom'] ?? null;
175
-
176
-        if ($custom_price_name) {
177
-            return $custom_price_name;
178
-        }
179
-
180
-        return ucwords($this->data['price_name']['option']) ?? ucwords($this->invoice->price_name_option) ?? $this->invoice->price_name_custom ?? 'Price';
172
+        return $this->getItemColumnName('price_name', 'Price');
181 173
     }
182 174
 
183 175
     public function amount_name(): string
184 176
     {
185
-        $custom_amount_name = $this->data['amount_name']['custom'] ?? $this->invoice->amount_name_custom ?? null;
186
-
187
-        if ($custom_amount_name) {
188
-            return $custom_amount_name;
189
-        }
190
-
191
-        return ucwords($this->data['amount_name']['option']) ?? ucwords($this->invoice->amount_name_option) ?? 'Amount';
177
+        return $this->getItemColumnName('amount_name', 'Amount');
192 178
     }
193 179
 
194 180
     public function buildViewData(): array
@@ -208,7 +194,6 @@ class InvoiceViewModel
208 194
             'number_next' => $this->number_next(),
209 195
             'invoice_number' => $this->invoice_number(),
210 196
             'invoice_date' => $this->invoice_date(),
211
-            'payment_terms' => $this->payment_terms(),
212 197
             'invoice_due_date' => $this->invoice_due_date(),
213 198
             'header' => $this->header(),
214 199
             'subheader' => $this->subheader(),

+ 2
- 2
database/migrations/2023_09_12_032057_create_document_defaults_table.php 查看文件

@@ -18,11 +18,11 @@ return new class extends Migration
18 18
             $table->string('type');
19 19
             $table->string('logo')->nullable();
20 20
             $table->boolean('show_logo')->default(false);
21
-            $table->string('number_prefix');
21
+            $table->string('number_prefix')->nullable();
22 22
             $table->unsignedTinyInteger('number_digits')->default(5);
23 23
             $table->unsignedBigInteger('number_next')->default(1);
24 24
             $table->string('payment_terms')->default(PaymentTerms::DEFAULT);
25
-            $table->string('header');
25
+            $table->string('header')->nullable();
26 26
             $table->string('subheader')->nullable();
27 27
             $table->text('terms')->nullable();
28 28
             $table->text('footer')->nullable();

Loading…
取消
儲存