Andrew Wallo 8 个月前
父节点
当前提交
d75d3a7e22

+ 2
- 23
app/Filament/Company/Clusters/Settings/Pages/CompanyProfile.php 查看文件

4
 
4
 
5
 use App\Enums\Setting\EntityType;
5
 use App\Enums\Setting\EntityType;
6
 use App\Filament\Company\Clusters\Settings;
6
 use App\Filament\Company\Clusters\Settings;
7
-use App\Filament\Forms\Components\CountrySelect;
8
-use App\Models\Locale\State;
7
+use App\Filament\Forms\Components\AddressFields;
9
 use App\Models\Setting\CompanyProfile as CompanyProfileModel;
8
 use App\Models\Setting\CompanyProfile as CompanyProfileModel;
10
 use App\Utilities\Localization\Timezone;
9
 use App\Utilities\Localization\Timezone;
11
 use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
10
 use CodeWithDennis\SimpleAlert\Components\Forms\SimpleAlert;
19
 use Filament\Forms\Components\Select;
18
 use Filament\Forms\Components\Select;
20
 use Filament\Forms\Components\TextInput;
19
 use Filament\Forms\Components\TextInput;
21
 use Filament\Forms\Form;
20
 use Filament\Forms\Form;
22
-use Filament\Forms\Get;
23
 use Filament\Notifications\Notification;
21
 use Filament\Notifications\Notification;
24
 use Filament\Pages\Concerns\InteractsWithFormActions;
22
 use Filament\Pages\Concerns\InteractsWithFormActions;
25
 use Filament\Pages\Page;
23
 use Filament\Pages\Page;
204
             ->schema([
202
             ->schema([
205
                 Hidden::make('type')
203
                 Hidden::make('type')
206
                     ->default('general'),
204
                     ->default('general'),
207
-                TextInput::make('address_line_1')
208
-                    ->label('Address Line 1')
209
-                    ->softRequired()
210
-                    ->maxLength(255),
211
-                TextInput::make('address_line_2')
212
-                    ->label('Address Line 2')
213
-                    ->maxLength(255),
214
-                CountrySelect::make('country')
215
-                    ->clearStateField()
205
+                AddressFields::make()
216
                     ->softRequired(),
206
                     ->softRequired(),
217
-                Select::make('state_id')
218
-                    ->localizeLabel('State / Province')
219
-                    ->searchable()
220
-                    ->options(static fn (Get $get) => State::getStateOptions($get('country'))),
221
-                TextInput::make('city')
222
-                    ->localizeLabel('City / Town')
223
-                    ->softRequired()
224
-                    ->maxLength(255),
225
-                TextInput::make('postal_code')
226
-                    ->label('Postal Code / Zip Code')
227
-                    ->maxLength(255),
228
             ])
207
             ])
229
             ->columns(2);
208
             ->columns(2);
230
     }
209
     }

+ 50
- 34
app/Filament/Company/Resources/Purchases/VendorResource.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Purchases;
3
 namespace App\Filament\Company\Resources\Purchases;
4
 
4
 
5
+use App\Enums\Accounting\BillStatus;
5
 use App\Enums\Common\ContractorType;
6
 use App\Enums\Common\ContractorType;
6
 use App\Enums\Common\VendorType;
7
 use App\Enums\Common\VendorType;
7
 use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
8
 use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
8
-use App\Filament\Forms\Components\CountrySelect;
9
+use App\Filament\Forms\Components\AddressFields;
9
 use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
10
 use App\Filament\Forms\Components\CustomSection;
11
 use App\Filament\Forms\Components\CustomSection;
11
 use App\Filament\Forms\Components\PhoneBuilder;
12
 use App\Filament\Forms\Components\PhoneBuilder;
12
 use App\Filament\Tables\Columns;
13
 use App\Filament\Tables\Columns;
13
 use App\Models\Common\Vendor;
14
 use App\Models\Common\Vendor;
14
-use App\Models\Locale\State;
15
+use App\Utilities\Currency\CurrencyConverter;
15
 use Filament\Forms;
16
 use Filament\Forms;
16
 use Filament\Forms\Form;
17
 use Filament\Forms\Form;
17
-use Filament\Forms\Get;
18
 use Filament\Resources\Resource;
18
 use Filament\Resources\Resource;
19
 use Filament\Tables;
19
 use Filament\Tables;
20
 use Filament\Tables\Table;
20
 use Filament\Tables\Table;
21
+use Illuminate\Database\Eloquent\Builder;
21
 
22
 
22
 class VendorResource extends Resource
23
 class VendorResource extends Resource
23
 {
24
 {
45
                                     ->columnSpanFull(),
46
                                     ->columnSpanFull(),
46
                                 CreateCurrencySelect::make('currency_code')
47
                                 CreateCurrencySelect::make('currency_code')
47
                                     ->nullable()
48
                                     ->nullable()
48
-                                    ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
49
+                                    ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
49
                                 Forms\Components\Select::make('contractor_type')
50
                                 Forms\Components\Select::make('contractor_type')
50
                                     ->label('Contractor Type')
51
                                     ->label('Contractor Type')
51
                                     ->required()
52
                                     ->required()
52
                                     ->live()
53
                                     ->live()
53
-                                    ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Contractor)
54
+                                    ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Contractor)
54
                                     ->options(ContractorType::class),
55
                                     ->options(ContractorType::class),
55
                                 Forms\Components\TextInput::make('ssn')
56
                                 Forms\Components\TextInput::make('ssn')
56
                                     ->label('Social Security Number')
57
                                     ->label('Social Security Number')
59
                                     ->mask('999-99-9999')
60
                                     ->mask('999-99-9999')
60
                                     ->stripCharacters('-')
61
                                     ->stripCharacters('-')
61
                                     ->maxLength(11)
62
                                     ->maxLength(11)
62
-                                    ->visible(fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Individual)
63
+                                    ->visible(static fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Individual)
63
                                     ->maxLength(255),
64
                                     ->maxLength(255),
64
                                 Forms\Components\TextInput::make('ein')
65
                                 Forms\Components\TextInput::make('ein')
65
                                     ->label('Employer Identification Number')
66
                                     ->label('Employer Identification Number')
68
                                     ->mask('99-9999999')
69
                                     ->mask('99-9999999')
69
                                     ->stripCharacters('-')
70
                                     ->stripCharacters('-')
70
                                     ->maxLength(10)
71
                                     ->maxLength(10)
71
-                                    ->visible(fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Business)
72
+                                    ->visible(static fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Business)
72
                                     ->maxLength(255),
73
                                     ->maxLength(255),
73
                                 Forms\Components\TextInput::make('account_number')
74
                                 Forms\Components\TextInput::make('account_number')
74
                                     ->maxLength(255),
75
                                     ->maxLength(255),
145
                     ->schema([
146
                     ->schema([
146
                         Forms\Components\Hidden::make('type')
147
                         Forms\Components\Hidden::make('type')
147
                             ->default('general'),
148
                             ->default('general'),
148
-                        Forms\Components\TextInput::make('address_line_1')
149
-                            ->label('Address Line 1')
150
-                            ->required()
151
-                            ->maxLength(255),
152
-                        Forms\Components\TextInput::make('address_line_2')
153
-                            ->label('Address Line 2')
154
-                            ->maxLength(255),
155
-                        CountrySelect::make('country')
156
-                            ->clearStateField()
157
-                            ->required(),
158
-                        Forms\Components\Select::make('state_id')
159
-                            ->localizeLabel('State / Province')
160
-                            ->searchable()
161
-                            ->options(static fn (Get $get) => State::getStateOptions($get('country')))
162
-                            ->nullable(),
163
-                        Forms\Components\TextInput::make('city')
164
-                            ->localizeLabel('City / Town')
165
-                            ->required()
166
-                            ->maxLength(255),
167
-                        Forms\Components\TextInput::make('postal_code')
168
-                            ->label('Postal Code / Zip Code')
169
-                            ->required()
170
-                            ->maxLength(255),
149
+                        AddressFields::make(),
171
                     ])
150
                     ])
172
                     ->columns(2),
151
                     ->columns(2),
173
             ]);
152
             ]);
180
                 Columns::id(),
159
                 Columns::id(),
181
                 Tables\Columns\TextColumn::make('type')
160
                 Tables\Columns\TextColumn::make('type')
182
                     ->badge()
161
                     ->badge()
183
-                    ->searchable(),
162
+                    ->searchable()
163
+                    ->sortable(),
184
                 Tables\Columns\TextColumn::make('name')
164
                 Tables\Columns\TextColumn::make('name')
185
                     ->searchable()
165
                     ->searchable()
186
-                    ->description(fn (Vendor $vendor) => $vendor->contact?->full_name),
166
+                    ->sortable()
167
+                    ->description(static fn (Vendor $vendor) => $vendor->contact?->full_name),
187
                 Tables\Columns\TextColumn::make('contact.email')
168
                 Tables\Columns\TextColumn::make('contact.email')
188
                     ->label('Email')
169
                     ->label('Email')
189
                     ->searchable(),
170
                     ->searchable(),
190
-                Tables\Columns\TextColumn::make('primaryContact.phones')
171
+                Tables\Columns\TextColumn::make('contact.first_available_phone')
191
                     ->label('Phone')
172
                     ->label('Phone')
192
-                    ->state(fn (Vendor $vendor) => $vendor->contact?->first_available_phone),
173
+                    ->state(static fn (Vendor $vendor) => $vendor->contact?->first_available_phone),
174
+                Tables\Columns\TextColumn::make('address.address_string')
175
+                    ->label('Address')
176
+                    ->searchable()
177
+                    ->toggleable(isToggledHiddenByDefault: true)
178
+                    ->listWithLineBreaks(),
179
+                Tables\Columns\TextColumn::make('payable_balance')
180
+                    ->label('Payable Balance')
181
+                    ->getStateUsing(function (Vendor $vendor) {
182
+                        return $vendor->bills()
183
+                            ->outstanding()
184
+                            ->get()
185
+                            ->sumMoneyInDefaultCurrency('amount_due');
186
+                    })
187
+                    ->coloredDescription(function (Vendor $vendor) {
188
+                        $overdue = $vendor->bills()
189
+                            ->where('status', BillStatus::Overdue)
190
+                            ->get()
191
+                            ->sumMoneyInDefaultCurrency('amount_due');
192
+
193
+                        if ($overdue <= 0) {
194
+                            return null;
195
+                        }
196
+
197
+                        $formattedOverdue = CurrencyConverter::formatCentsToMoney($overdue);
198
+
199
+                        return "Overdue: {$formattedOverdue}";
200
+                    })
201
+                    ->sortable(query: function (Builder $query, string $direction) {
202
+                        return $query
203
+                            ->withSum(['bills' => fn (Builder $query) => $query->outstanding()], 'amount_due')
204
+                            ->orderBy('bills_sum_amount_due', $direction);
205
+                    })
206
+                    ->currency(convert: false)
207
+                    ->alignEnd(),
208
+
193
             ])
209
             ])
194
             ->filters([
210
             ->filters([
195
                 //
211
                 //

+ 14
- 59
app/Filament/Company/Resources/Sales/ClientResource.php 查看文件

3
 namespace App\Filament\Company\Resources\Sales;
3
 namespace App\Filament\Company\Resources\Sales;
4
 
4
 
5
 use App\Filament\Company\Resources\Sales\ClientResource\Pages;
5
 use App\Filament\Company\Resources\Sales\ClientResource\Pages;
6
-use App\Filament\Forms\Components\CountrySelect;
6
+use App\Filament\Forms\Components\AddressFields;
7
 use App\Filament\Forms\Components\CreateCurrencySelect;
7
 use App\Filament\Forms\Components\CreateCurrencySelect;
8
 use App\Filament\Forms\Components\CustomSection;
8
 use App\Filament\Forms\Components\CustomSection;
9
 use App\Filament\Forms\Components\PhoneBuilder;
9
 use App\Filament\Forms\Components\PhoneBuilder;
10
 use App\Filament\Tables\Columns;
10
 use App\Filament\Tables\Columns;
11
 use App\Models\Common\Address;
11
 use App\Models\Common\Address;
12
 use App\Models\Common\Client;
12
 use App\Models\Common\Client;
13
-use App\Models\Locale\State;
14
 use App\Utilities\Currency\CurrencyConverter;
13
 use App\Utilities\Currency\CurrencyConverter;
15
 use Filament\Forms;
14
 use Filament\Forms;
16
 use Filament\Forms\Form;
15
 use Filament\Forms\Form;
20
 use Filament\Tables;
19
 use Filament\Tables;
21
 use Filament\Tables\Table;
20
 use Filament\Tables\Table;
22
 use Illuminate\Database\Eloquent\Builder;
21
 use Illuminate\Database\Eloquent\Builder;
23
-use Illuminate\Support\HtmlString;
24
 
22
 
25
 class ClientResource extends Resource
23
 class ClientResource extends Resource
26
 {
24
 {
185
                             ->schema([
183
                             ->schema([
186
                                 Forms\Components\Hidden::make('type')
184
                                 Forms\Components\Hidden::make('type')
187
                                     ->default('billing'),
185
                                     ->default('billing'),
188
-                                Forms\Components\TextInput::make('address_line_1')
189
-                                    ->label('Address Line 1')
190
-                                    ->required()
191
-                                    ->maxLength(255),
192
-                                Forms\Components\TextInput::make('address_line_2')
193
-                                    ->label('Address Line 2')
194
-                                    ->maxLength(255),
195
-                                CountrySelect::make('country')
196
-                                    ->clearStateField()
197
-                                    ->required(),
198
-                                Forms\Components\Select::make('state_id')
199
-                                    ->localizeLabel('State / Province')
200
-                                    ->searchable()
201
-                                    ->options(static fn (Get $get) => State::getStateOptions($get('country')))
202
-                                    ->nullable(),
203
-                                Forms\Components\TextInput::make('city')
204
-                                    ->label('City')
205
-                                    ->required()
206
-                                    ->maxLength(255),
207
-                                Forms\Components\TextInput::make('postal_code')
208
-                                    ->label('Postal Code / Zip Code')
209
-                                    ->required()
210
-                                    ->maxLength(255),
186
+                                AddressFields::make(),
211
                             ])->columns(),
187
                             ])->columns(),
212
                     ])
188
                     ])
213
                     ->columns(1),
189
                     ->columns(1),
260
                                         }
236
                                         }
261
                                     })
237
                                     })
262
                                     ->columnSpanFull(),
238
                                     ->columnSpanFull(),
263
-                                Forms\Components\Grid::make()
264
-                                    ->schema([
265
-                                        Forms\Components\TextInput::make('address_line_1')
266
-                                            ->label('Address Line 1')
267
-                                            ->required()
268
-                                            ->maxLength(255),
269
-                                        Forms\Components\TextInput::make('address_line_2')
270
-                                            ->label('Address Line 2')
271
-                                            ->maxLength(255),
272
-                                        CountrySelect::make('country')
273
-                                            ->clearStateField()
274
-                                            ->required(),
275
-                                        Forms\Components\Select::make('state_id')
276
-                                            ->localizeLabel('State / Province')
277
-                                            ->searchable()
278
-                                            ->options(static fn (Get $get) => State::getStateOptions($get('country')))
279
-                                            ->nullable(),
280
-                                        Forms\Components\TextInput::make('city')
281
-                                            ->label('City')
282
-                                            ->required()
283
-                                            ->maxLength(255),
284
-                                        Forms\Components\TextInput::make('postal_code')
285
-                                            ->label('Postal Code / Zip Code')
286
-                                            ->required()
287
-                                            ->maxLength(255),
288
-                                    ])
289
-                                    ->visible(fn (Get $get) => ! $get('same_as_billing')),
239
+                                AddressFields::make()
240
+                                    ->visible(static fn (Get $get) => ! $get('same_as_billing')),
290
                                 Forms\Components\Textarea::make('notes')
241
                                 Forms\Components\Textarea::make('notes')
291
                                     ->label('Delivery Instructions')
242
                                     ->label('Delivery Instructions')
292
                                     ->maxLength(255)
243
                                     ->maxLength(255)
304
                 Tables\Columns\TextColumn::make('name')
255
                 Tables\Columns\TextColumn::make('name')
305
                     ->searchable()
256
                     ->searchable()
306
                     ->sortable()
257
                     ->sortable()
307
-                    ->description(fn (Client $client) => $client->primaryContact->full_name),
258
+                    ->description(static fn (Client $client) => $client->primaryContact->full_name),
308
                 Tables\Columns\TextColumn::make('primaryContact.email')
259
                 Tables\Columns\TextColumn::make('primaryContact.email')
309
                     ->label('Email')
260
                     ->label('Email')
310
                     ->searchable()
261
                     ->searchable()
312
                 Tables\Columns\TextColumn::make('primaryContact.phones')
263
                 Tables\Columns\TextColumn::make('primaryContact.phones')
313
                     ->label('Phone')
264
                     ->label('Phone')
314
                     ->toggleable()
265
                     ->toggleable()
315
-                    ->state(fn (Client $client) => $client->primaryContact->first_available_phone),
266
+                    ->state(static fn (Client $client) => $client->primaryContact->first_available_phone),
316
                 Tables\Columns\TextColumn::make('billingAddress.address_string')
267
                 Tables\Columns\TextColumn::make('billingAddress.address_string')
317
                     ->label('Billing Address')
268
                     ->label('Billing Address')
318
                     ->searchable()
269
                     ->searchable()
326
                             ->get()
277
                             ->get()
327
                             ->sumMoneyInDefaultCurrency('amount_due');
278
                             ->sumMoneyInDefaultCurrency('amount_due');
328
                     })
279
                     })
329
-                    ->description(function (Client $client) {
280
+                    ->coloredDescription(function (Client $client) {
330
                         $overdue = $client->invoices()
281
                         $overdue = $client->invoices()
331
                             ->overdue()
282
                             ->overdue()
332
                             ->get()
283
                             ->get()
338
 
289
 
339
                         $formattedOverdue = CurrencyConverter::formatCentsToMoney($overdue);
290
                         $formattedOverdue = CurrencyConverter::formatCentsToMoney($overdue);
340
 
291
 
341
-                        return new HtmlString("<span class='text-danger-700 dark:text-danger-400'>Overdue: {$formattedOverdue}</span>");
292
+                        return "Overdue: {$formattedOverdue}";
342
                     })
293
                     })
343
                     ->sortable(query: function (Builder $query, string $direction) {
294
                     ->sortable(query: function (Builder $query, string $direction) {
344
                         return $query
295
                         return $query
345
                             ->withSum(['invoices' => fn (Builder $query) => $query->unpaid()], 'amount_due')
296
                             ->withSum(['invoices' => fn (Builder $query) => $query->unpaid()], 'amount_due')
346
                             ->orderBy('invoices_sum_amount_due', $direction);
297
                             ->orderBy('invoices_sum_amount_due', $direction);
347
                     })
298
                     })
348
-                    ->currency(fn (Client $client) => $client->currency_code, false)
299
+                    ->currency(convert: false)
349
                     ->alignEnd(),
300
                     ->alignEnd(),
350
             ])
301
             ])
351
             ->filters([
302
             ->filters([
352
                 //
303
                 //
353
             ])
304
             ])
354
             ->actions([
305
             ->actions([
355
-                Tables\Actions\EditAction::make(),
306
+                Tables\Actions\ActionGroup::make([
307
+                    Tables\Actions\EditAction::make(),
308
+                    Tables\Actions\ViewAction::make(),
309
+                ]),
356
             ])
310
             ])
357
             ->bulkActions([
311
             ->bulkActions([
358
                 Tables\Actions\BulkActionGroup::make([
312
                 Tables\Actions\BulkActionGroup::make([
373
         return [
327
         return [
374
             'index' => Pages\ListClients::route('/'),
328
             'index' => Pages\ListClients::route('/'),
375
             'create' => Pages\CreateClient::route('/create'),
329
             'create' => Pages\CreateClient::route('/create'),
330
+            'view' => Pages\ViewClient::route('/{record}'),
376
             'edit' => Pages\EditClient::route('/{record}/edit'),
331
             'edit' => Pages\EditClient::route('/{record}/edit'),
377
         ];
332
         ];
378
     }
333
     }

+ 75
- 0
app/Filament/Company/Resources/Sales/ClientResource/Pages/ViewClient.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Sales\ClientResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Sales\ClientResource;
6
+use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers;
7
+use App\Models\Common\Client;
8
+use Filament\Infolists\Components\Section;
9
+use Filament\Infolists\Components\TextEntry;
10
+use Filament\Infolists\Infolist;
11
+use Filament\Resources\Pages\ViewRecord;
12
+use Illuminate\Support\Number;
13
+
14
+class ViewClient extends ViewRecord
15
+{
16
+    protected static string $resource = ClientResource::class;
17
+
18
+    public function getRelationManagers(): array
19
+    {
20
+        return [
21
+            RelationManagers\InvoicesRelationManager::class,
22
+        ];
23
+    }
24
+
25
+    public function infolist(Infolist $infolist): Infolist
26
+    {
27
+        return $infolist
28
+            ->schema([
29
+                Section::make('Financial Overview')
30
+                    ->columns(4)
31
+                    ->schema([
32
+                        TextEntry::make('last_12_months_paid')
33
+                            ->label('Last 12 Months Paid')
34
+                            ->getStateUsing(function (Client $record) {
35
+                                return $record->invoices()
36
+                                    ->whereNotNull('paid_at')
37
+                                    ->where('paid_at', '>=', now()->subMonths(12))
38
+                                    ->get()
39
+                                    ->sumMoneyInDefaultCurrency('total');
40
+                            })
41
+                            ->currency(convert: false),
42
+                        TextEntry::make('total_unpaid')
43
+                            ->label('Total Unpaid')
44
+                            ->getStateUsing(function (Client $record) {
45
+                                return $record->invoices()
46
+                                    ->unpaid()
47
+                                    ->get()
48
+                                    ->sumMoneyInDefaultCurrency('amount_due');
49
+                            })
50
+                            ->currency(convert: false),
51
+                        TextEntry::make('total_overdue')
52
+                            ->label('Total Overdue')
53
+                            ->getStateUsing(function (Client $record) {
54
+                                return $record->invoices()
55
+                                    ->overdue()
56
+                                    ->get()
57
+                                    ->sumMoneyInDefaultCurrency('amount_due');
58
+                            })
59
+                            ->currency(convert: false),
60
+                        TextEntry::make('average_payment_time')
61
+                            ->label('Average Payment Time')
62
+                            ->getStateUsing(function (Client $record) {
63
+                                return $record->invoices()
64
+                                    ->whereNotNull('paid_at')
65
+                                    ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, paid_at)) as avg_days')
66
+                                    ->value('avg_days');
67
+                            })
68
+                            ->suffix(' days')
69
+                            ->formatStateUsing(function ($state) {
70
+                                return Number::format($state ?? 0, maxPrecision: 1);
71
+                            }),
72
+                    ]),
73
+            ]);
74
+    }
75
+}

+ 29
- 0
app/Filament/Company/Resources/Sales/ClientResource/RelationManagers/InvoicesRelationManager.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Sales\ClientResource\RelationManagers;
4
+
5
+use App\Filament\Company\Resources\Sales\InvoiceResource;
6
+use Filament\Resources\RelationManagers\RelationManager;
7
+use Filament\Tables;
8
+use Filament\Tables\Table;
9
+
10
+class InvoicesRelationManager extends RelationManager
11
+{
12
+    protected static string $relationship = 'invoices';
13
+
14
+    protected static bool $isLazy = false;
15
+
16
+    public function isReadOnly(): bool
17
+    {
18
+        return false;
19
+    }
20
+
21
+    public function table(Table $table): Table
22
+    {
23
+        return InvoiceResource::table($table)
24
+            ->headerActions([
25
+                Tables\Actions\CreateAction::make()
26
+                    ->url(InvoiceResource\Pages\CreateInvoice::getUrl(['client' => $this->getOwnerRecord()->getKey()])),
27
+            ]);
28
+    }
29
+}

+ 9
- 5
app/Filament/Company/Resources/Sales/InvoiceResource.php 查看文件

298
         return $table
298
         return $table
299
             ->defaultSort('due_date')
299
             ->defaultSort('due_date')
300
             ->modifyQueryUsing(function (Builder $query, Tables\Contracts\HasTable $livewire) {
300
             ->modifyQueryUsing(function (Builder $query, Tables\Contracts\HasTable $livewire) {
301
-                $recurringInvoiceId = $livewire->recurringInvoice;
301
+                if (property_exists($livewire, 'recurringInvoice')) {
302
+                    $recurringInvoiceId = $livewire->recurringInvoice;
302
 
303
 
303
-                if (! empty($recurringInvoiceId)) {
304
-                    $query->where('recurring_invoice_id', $recurringInvoiceId);
304
+                    if (! empty($recurringInvoiceId)) {
305
+                        $query->where('recurring_invoice_id', $recurringInvoiceId);
306
+                    }
305
                 }
307
                 }
306
 
308
 
307
                 return $query;
309
                 return $query;
388
             ])
390
             ])
389
             ->actions([
391
             ->actions([
390
                 Tables\Actions\ActionGroup::make([
392
                 Tables\Actions\ActionGroup::make([
391
-                    Tables\Actions\EditAction::make(),
392
-                    Tables\Actions\ViewAction::make(),
393
+                    Tables\Actions\EditAction::make()
394
+                        ->url(static fn (Invoice $record) => Pages\EditInvoice::getUrl(['record' => $record])),
395
+                    Tables\Actions\ViewAction::make()
396
+                        ->url(static fn (Invoice $record) => Pages\ViewInvoice::getUrl(['record' => $record])),
393
                     Tables\Actions\DeleteAction::make(),
397
                     Tables\Actions\DeleteAction::make(),
394
                     Invoice::getReplicateAction(Tables\Actions\ReplicateAction::class),
398
                     Invoice::getReplicateAction(Tables\Actions\ReplicateAction::class),
395
                     Invoice::getApproveDraftAction(Tables\Actions\Action::class),
399
                     Invoice::getApproveDraftAction(Tables\Actions\Action::class),

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

17
 
17
 
18
     protected static string $resource = InvoiceResource::class;
18
     protected static string $resource = InvoiceResource::class;
19
 
19
 
20
+    public function mount(): void
21
+    {
22
+        parent::mount();
23
+
24
+        $clientId = request()->query('client');
25
+
26
+        if ($clientId) {
27
+            $this->data['client_id'] = $clientId;
28
+        }
29
+    }
30
+
20
     public function getMaxContentWidth(): MaxWidth | string | null
31
     public function getMaxContentWidth(): MaxWidth | string | null
21
     {
32
     {
22
         return MaxWidth::Full;
33
         return MaxWidth::Full;

+ 58
- 0
app/Filament/Forms/Components/AddressFields.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use Filament\Forms\Components\Field;
6
+use Filament\Forms\Components\Grid;
7
+use Filament\Forms\Components\TextInput;
8
+
9
+class AddressFields extends Grid
10
+{
11
+    protected bool $isSoftRequired = false;
12
+
13
+    protected function setUp(): void
14
+    {
15
+        parent::setUp();
16
+
17
+        $this->schema([
18
+            TextInput::make('address_line_1')
19
+                ->label('Address Line 1')
20
+                ->required()
21
+                ->maxLength(255),
22
+            TextInput::make('address_line_2')
23
+                ->label('Address Line 2')
24
+                ->maxLength(255),
25
+            CountrySelect::make('country')
26
+                ->clearStateField()
27
+                ->required(),
28
+            StateSelect::make('state_id'),
29
+            TextInput::make('city')
30
+                ->label('City')
31
+                ->required()
32
+                ->maxLength(255),
33
+            TextInput::make('postal_code')
34
+                ->label('Postal Code / Zip Code')
35
+                ->maxLength(255),
36
+        ]);
37
+    }
38
+
39
+    public function softRequired(bool $condition = true): static
40
+    {
41
+        $this->setSoftRequired($condition);
42
+
43
+        return $this;
44
+    }
45
+
46
+    protected function setSoftRequired(bool $condition): void
47
+    {
48
+        $this->isSoftRequired = $condition;
49
+
50
+        $childComponents = $this->getChildComponents();
51
+
52
+        foreach ($childComponents as $component) {
53
+            if ($component instanceof Field && $component->isRequired()) {
54
+                $component->markAsRequired(! $condition);
55
+            }
56
+        }
57
+    }
58
+}

+ 20
- 0
app/Filament/Forms/Components/StateSelect.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use App\Models\Locale\State;
6
+use Filament\Forms\Components\Select;
7
+use Filament\Forms\Get;
8
+
9
+class StateSelect extends Select
10
+{
11
+    protected function setUp(): void
12
+    {
13
+        parent::setUp();
14
+
15
+        $this
16
+            ->localizeLabel('State / Province')
17
+            ->searchable()
18
+            ->options(static fn (Get $get) => State::getStateOptions($get('country')));
19
+    }
20
+}

+ 12
- 0
app/Providers/MacroServiceProvider.php 查看文件

20
 use Filament\Infolists\Components\TextEntry;
20
 use Filament\Infolists\Components\TextEntry;
21
 use Filament\Tables\Columns\TextColumn;
21
 use Filament\Tables\Columns\TextColumn;
22
 use Filament\Tables\Contracts\HasTable;
22
 use Filament\Tables\Contracts\HasTable;
23
+use Illuminate\Contracts\Support\Htmlable;
23
 use Illuminate\Support\Carbon;
24
 use Illuminate\Support\Carbon;
25
+use Illuminate\Support\HtmlString;
24
 use Illuminate\Support\ServiceProvider;
26
 use Illuminate\Support\ServiceProvider;
25
 
27
 
26
 class MacroServiceProvider extends ServiceProvider
28
 class MacroServiceProvider extends ServiceProvider
114
                 });
116
                 });
115
         });
117
         });
116
 
118
 
119
+        TextColumn::macro('coloredDescription', function (string | Htmlable | Closure | null $description, string $color = 'danger') {
120
+            $this->description(static function (TextColumn $column) use ($description, $color): Htmlable {
121
+                $description = $column->evaluate($description);
122
+
123
+                return new HtmlString("<span class='text-{$color}-700 dark:text-{$color}-400'>{$description}</span>");
124
+            });
125
+
126
+            return $this;
127
+        });
128
+
117
         TextColumn::macro('hideOnTabs', function (array $tabs): static {
129
         TextColumn::macro('hideOnTabs', function (array $tabs): static {
118
             $this->toggleable(isToggledHiddenByDefault: function (HasTable $livewire) use ($tabs) {
130
             $this->toggleable(isToggledHiddenByDefault: function (HasTable $livewire) use ($tabs) {
119
                 return in_array($livewire->activeTab, $tabs);
131
                 return in_array($livewire->activeTab, $tabs);

+ 6
- 10
database/factories/Common/ContactFactory.php 查看文件

71
 
71
 
72
     public function primary(): self
72
     public function primary(): self
73
     {
73
     {
74
-        return $this->state(function (array $attributes) {
75
-            return [
76
-                'is_primary' => true,
77
-            ];
78
-        });
74
+        return $this->state([
75
+            'is_primary' => true,
76
+        ]);
79
     }
77
     }
80
 
78
 
81
     public function secondary(): self
79
     public function secondary(): self
82
     {
80
     {
83
-        return $this->state(function (array $attributes) {
84
-            return [
85
-                'is_primary' => false,
86
-            ];
87
-        });
81
+        return $this->state([
82
+            'is_primary' => false,
83
+        ]);
88
     }
84
     }
89
 }
85
 }

正在加载...
取消
保存