浏览代码

Merge pull request #184 from andrewdwallo/feat/exports

Feat/exports
3.x
Andrew Wallo 3 个月前
父节点
当前提交
edccd0e911
没有帐户链接到提交者的电子邮件

+ 5
- 0
app/Filament/Company/Resources/Accounting/TransactionResource.php 查看文件

4
 
4
 
5
 use App\Enums\Accounting\TransactionType;
5
 use App\Enums\Accounting\TransactionType;
6
 use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
6
 use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
7
+use App\Filament\Exports\Accounting\TransactionExporter;
7
 use App\Filament\Forms\Components\DateRangeSelect;
8
 use App\Filament\Forms\Components\DateRangeSelect;
8
 use App\Filament\Tables\Actions\EditTransactionAction;
9
 use App\Filament\Tables\Actions\EditTransactionAction;
9
 use App\Filament\Tables\Actions\ReplicateBulkAction;
10
 use App\Filament\Tables\Actions\ReplicateBulkAction;
155
                 $filters['updated_at'],
156
                 $filters['updated_at'],
156
             ])
157
             ])
157
             ->filtersFormWidth(MaxWidth::ThreeExtraLarge)
158
             ->filtersFormWidth(MaxWidth::ThreeExtraLarge)
159
+            ->headerActions([
160
+                Tables\Actions\ExportAction::make()
161
+                    ->exporter(TransactionExporter::class),
162
+            ])
158
             ->actions([
163
             ->actions([
159
                 Tables\Actions\Action::make('markAsReviewed')
164
                 Tables\Actions\Action::make('markAsReviewed')
160
                     ->label('Mark as reviewed')
165
                     ->label('Mark as reviewed')

+ 5
- 0
app/Filament/Company/Resources/Purchases/BillResource.php 查看文件

12
 use App\Enums\Setting\PaymentTerms;
12
 use App\Enums\Setting\PaymentTerms;
13
 use App\Filament\Company\Resources\Purchases\BillResource\Pages;
13
 use App\Filament\Company\Resources\Purchases\BillResource\Pages;
14
 use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers\BillsRelationManager;
14
 use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers\BillsRelationManager;
15
+use App\Filament\Exports\Accounting\BillExporter;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
 use App\Filament\Forms\Components\CreateOfferingSelect;
18
 use App\Filament\Forms\Components\CreateOfferingSelect;
427
                     ->untilLabel('To due date')
428
                     ->untilLabel('To due date')
428
                     ->indicatorLabel('Due'),
429
                     ->indicatorLabel('Due'),
429
             ])
430
             ])
431
+            ->headerActions([
432
+                Tables\Actions\ExportAction::make()
433
+                    ->exporter(BillExporter::class),
434
+            ])
430
             ->actions([
435
             ->actions([
431
                 Tables\Actions\ActionGroup::make([
436
                 Tables\Actions\ActionGroup::make([
432
                     Tables\Actions\ActionGroup::make([
437
                     Tables\Actions\ActionGroup::make([

+ 5
- 0
app/Filament/Company/Resources/Purchases/VendorResource.php 查看文件

6
 use App\Enums\Common\ContractorType;
6
 use App\Enums\Common\ContractorType;
7
 use App\Enums\Common\VendorType;
7
 use App\Enums\Common\VendorType;
8
 use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
8
 use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
9
+use App\Filament\Exports\Common\VendorExporter;
9
 use App\Filament\Forms\Components\AddressFields;
10
 use App\Filament\Forms\Components\AddressFields;
10
 use App\Filament\Forms\Components\CreateCurrencySelect;
11
 use App\Filament\Forms\Components\CreateCurrencySelect;
11
 use App\Filament\Forms\Components\CustomSection;
12
 use App\Filament\Forms\Components\CustomSection;
210
             ->filters([
211
             ->filters([
211
                 //
212
                 //
212
             ])
213
             ])
214
+            ->headerActions([
215
+                Tables\Actions\ExportAction::make()
216
+                    ->exporter(VendorExporter::class),
217
+            ])
213
             ->actions([
218
             ->actions([
214
                 Tables\Actions\ActionGroup::make([
219
                 Tables\Actions\ActionGroup::make([
215
                     Tables\Actions\ActionGroup::make([
220
                     Tables\Actions\ActionGroup::make([

+ 5
- 0
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\Exports\Common\ClientExporter;
6
 use App\Filament\Forms\Components\AddressFields;
7
 use App\Filament\Forms\Components\AddressFields;
7
 use App\Filament\Forms\Components\CreateCurrencySelect;
8
 use App\Filament\Forms\Components\CreateCurrencySelect;
8
 use App\Filament\Forms\Components\CustomSection;
9
 use App\Filament\Forms\Components\CustomSection;
299
             ->filters([
300
             ->filters([
300
                 //
301
                 //
301
             ])
302
             ])
303
+            ->headerActions([
304
+                Tables\Actions\ExportAction::make()
305
+                    ->exporter(ClientExporter::class),
306
+            ])
302
             ->actions([
307
             ->actions([
303
                 Tables\Actions\ActionGroup::make([
308
                 Tables\Actions\ActionGroup::make([
304
                     Tables\Actions\ActionGroup::make([
309
                     Tables\Actions\ActionGroup::make([

+ 5
- 0
app/Filament/Company/Resources/Sales/EstimateResource.php 查看文件

12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\EstimatesRelationManager;
12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\EstimatesRelationManager;
13
 use App\Filament\Company\Resources\Sales\EstimateResource\Pages;
13
 use App\Filament\Company\Resources\Sales\EstimateResource\Pages;
14
 use App\Filament\Company\Resources\Sales\EstimateResource\Widgets;
14
 use App\Filament\Company\Resources\Sales\EstimateResource\Widgets;
15
+use App\Filament\Exports\Accounting\EstimateExporter;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateClientSelect;
17
 use App\Filament\Forms\Components\CreateClientSelect;
17
 use App\Filament\Forms\Components\CreateCurrencySelect;
18
 use App\Filament\Forms\Components\CreateCurrencySelect;
418
                     ->untilLabel('To expiration date')
419
                     ->untilLabel('To expiration date')
419
                     ->indicatorLabel('Due'),
420
                     ->indicatorLabel('Due'),
420
             ])
421
             ])
422
+            ->headerActions([
423
+                Tables\Actions\ExportAction::make()
424
+                    ->exporter(EstimateExporter::class),
425
+            ])
421
             ->actions([
426
             ->actions([
422
                 Tables\Actions\ActionGroup::make([
427
                 Tables\Actions\ActionGroup::make([
423
                     Tables\Actions\ActionGroup::make([
428
                     Tables\Actions\ActionGroup::make([

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

12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\InvoicesRelationManager;
12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\InvoicesRelationManager;
13
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
13
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
14
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
14
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
15
+use App\Filament\Exports\Accounting\InvoiceExporter;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
16
 use App\Filament\Forms\Components\CreateClientSelect;
17
 use App\Filament\Forms\Components\CreateClientSelect;
17
 use App\Filament\Forms\Components\CreateCurrencySelect;
18
 use App\Filament\Forms\Components\CreateCurrencySelect;
476
                     ->untilLabel('To due date')
477
                     ->untilLabel('To due date')
477
                     ->indicatorLabel('Due'),
478
                     ->indicatorLabel('Due'),
478
             ])
479
             ])
480
+            ->headerActions([
481
+                Tables\Actions\ExportAction::make()
482
+                    ->exporter(InvoiceExporter::class),
483
+            ])
479
             ->actions([
484
             ->actions([
480
                 Tables\Actions\ActionGroup::make([
485
                 Tables\Actions\ActionGroup::make([
481
                     Tables\Actions\ActionGroup::make([
486
                     Tables\Actions\ActionGroup::make([

+ 5
- 0
app/Filament/Company/Resources/Sales/RecurringInvoiceResource.php 查看文件

11
 use App\Enums\Setting\PaymentTerms;
11
 use App\Enums\Setting\PaymentTerms;
12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\RecurringInvoicesRelationManager;
12
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\RecurringInvoicesRelationManager;
13
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
13
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
14
+use App\Filament\Exports\Accounting\RecurringInvoiceExporter;
14
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
15
 use App\Filament\Forms\Components\CreateAdjustmentSelect;
15
 use App\Filament\Forms\Components\CreateClientSelect;
16
 use App\Filament\Forms\Components\CreateClientSelect;
16
 use App\Filament\Forms\Components\CreateCurrencySelect;
17
 use App\Filament\Forms\Components\CreateCurrencySelect;
354
                     ->options(RecurringInvoiceStatus::class)
355
                     ->options(RecurringInvoiceStatus::class)
355
                     ->native(false),
356
                     ->native(false),
356
             ])
357
             ])
358
+            ->headerActions([
359
+                Tables\Actions\ExportAction::make()
360
+                    ->exporter(RecurringInvoiceExporter::class),
361
+            ])
357
             ->actions([
362
             ->actions([
358
                 Tables\Actions\ActionGroup::make([
363
                 Tables\Actions\ActionGroup::make([
359
                     Tables\Actions\ActionGroup::make([
364
                     Tables\Actions\ActionGroup::make([

+ 63
- 0
app/Filament/Exports/Accounting/BillExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Accounting;
4
+
5
+use App\Models\Accounting\Bill;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+
10
+class BillExporter extends Exporter
11
+{
12
+    protected static ?string $model = Bill::class;
13
+
14
+    public static function getColumns(): array
15
+    {
16
+        return [
17
+            ExportColumn::make('bill_number'),
18
+            ExportColumn::make('date')
19
+                ->date(),
20
+            ExportColumn::make('due_date')
21
+                ->date(),
22
+            ExportColumn::make('vendor.name'),
23
+            ExportColumn::make('status')
24
+                ->enum(),
25
+            ExportColumn::make('total')
26
+                ->money(),
27
+            ExportColumn::make('amount_paid')
28
+                ->money(),
29
+            ExportColumn::make('amount_due')
30
+                ->money(),
31
+            ExportColumn::make('subtotal')
32
+                ->money(),
33
+            ExportColumn::make('tax_total')
34
+                ->money(),
35
+            ExportColumn::make('discount_total')
36
+                ->money(),
37
+            ExportColumn::make('discount_rate'),
38
+            ExportColumn::make('currency_code'),
39
+            ExportColumn::make('order_number'),
40
+            ExportColumn::make('paid_at')
41
+                ->dateTime(),
42
+            ExportColumn::make('notes')
43
+                ->enabledByDefault(false),
44
+            ExportColumn::make('discount_method')
45
+                ->enabledByDefault(false)
46
+                ->enum(),
47
+            ExportColumn::make('discount_computation')
48
+                ->enabledByDefault(false)
49
+                ->enum(),
50
+        ];
51
+    }
52
+
53
+    public static function getCompletedNotificationBody(Export $export): string
54
+    {
55
+        $body = 'Your bill export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
56
+
57
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
58
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
59
+        }
60
+
61
+        return $body;
62
+    }
63
+}

+ 65
- 0
app/Filament/Exports/Accounting/EstimateExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Accounting;
4
+
5
+use App\Models\Accounting\Estimate;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+
10
+class EstimateExporter extends Exporter
11
+{
12
+    protected static ?string $model = Estimate::class;
13
+
14
+    public static function getColumns(): array
15
+    {
16
+        return [
17
+            ExportColumn::make('estimate_number'),
18
+            ExportColumn::make('date')
19
+                ->date(),
20
+            ExportColumn::make('expiration_date')
21
+                ->date(),
22
+            ExportColumn::make('client.name'),
23
+            ExportColumn::make('status')
24
+                ->enum(),
25
+            ExportColumn::make('total')
26
+                ->money(),
27
+            ExportColumn::make('subtotal')
28
+                ->money(),
29
+            ExportColumn::make('tax_total')
30
+                ->money(),
31
+            ExportColumn::make('discount_total')
32
+                ->money(),
33
+            ExportColumn::make('discount_rate'),
34
+            ExportColumn::make('currency_code'),
35
+            ExportColumn::make('reference_number'),
36
+            ExportColumn::make('approved_at')
37
+                ->dateTime(),
38
+            ExportColumn::make('accepted_at')
39
+                ->dateTime(),
40
+            ExportColumn::make('declined_at')
41
+                ->dateTime(),
42
+            ExportColumn::make('converted_at')
43
+                ->dateTime(),
44
+            ExportColumn::make('last_sent_at')
45
+                ->dateTime(),
46
+            ExportColumn::make('discount_method')
47
+                ->enabledByDefault(false)
48
+                ->enum(),
49
+            ExportColumn::make('discount_computation')
50
+                ->enabledByDefault(false)
51
+                ->enum(),
52
+        ];
53
+    }
54
+
55
+    public static function getCompletedNotificationBody(Export $export): string
56
+    {
57
+        $body = 'Your estimate export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
58
+
59
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
60
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
61
+        }
62
+
63
+        return $body;
64
+    }
65
+}

+ 71
- 0
app/Filament/Exports/Accounting/InvoiceExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Accounting;
4
+
5
+use App\Models\Accounting\Invoice;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+
10
+class InvoiceExporter extends Exporter
11
+{
12
+    protected static ?string $model = Invoice::class;
13
+
14
+    public static function getColumns(): array
15
+    {
16
+        return [
17
+            ExportColumn::make('invoice_number'),
18
+            ExportColumn::make('date')
19
+                ->date(),
20
+            ExportColumn::make('due_date')
21
+                ->date(),
22
+            ExportColumn::make('client.name'),
23
+            ExportColumn::make('status')
24
+                ->enum(),
25
+            ExportColumn::make('total')
26
+                ->money(),
27
+            ExportColumn::make('amount_paid')
28
+                ->money(),
29
+            ExportColumn::make('amount_due')
30
+                ->money(),
31
+            ExportColumn::make('subtotal')
32
+                ->money(),
33
+            ExportColumn::make('tax_total')
34
+                ->money(),
35
+            ExportColumn::make('discount_total')
36
+                ->money(),
37
+            ExportColumn::make('discount_rate'),
38
+            ExportColumn::make('currency_code'),
39
+            ExportColumn::make('order_number'),
40
+            ExportColumn::make('approved_at')
41
+                ->dateTime(),
42
+            ExportColumn::make('paid_at')
43
+                ->dateTime(),
44
+            ExportColumn::make('last_sent_at')
45
+                ->dateTime(),
46
+            ExportColumn::make('estimate.estimate_number')
47
+                ->label('Estimate number')
48
+                ->enabledByDefault(false),
49
+            ExportColumn::make('recurringInvoice.order_number')
50
+                ->label('Recurring invoice number')
51
+                ->enabledByDefault(false),
52
+            ExportColumn::make('discount_method')
53
+                ->enabledByDefault(false)
54
+                ->enum(),
55
+            ExportColumn::make('discount_computation')
56
+                ->enabledByDefault(false)
57
+                ->enum(),
58
+        ];
59
+    }
60
+
61
+    public static function getCompletedNotificationBody(Export $export): string
62
+    {
63
+        $body = 'Your invoice export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
64
+
65
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
66
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
67
+        }
68
+
69
+        return $body;
70
+    }
71
+}

+ 99
- 0
app/Filament/Exports/Accounting/RecurringInvoiceExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Accounting;
4
+
5
+use App\Models\Accounting\RecurringInvoice;
6
+use Carbon\Carbon;
7
+use Filament\Actions\Exports\ExportColumn;
8
+use Filament\Actions\Exports\Exporter;
9
+use Filament\Actions\Exports\Models\Export;
10
+
11
+class RecurringInvoiceExporter extends Exporter
12
+{
13
+    protected static ?string $model = RecurringInvoice::class;
14
+
15
+    public static function getColumns(): array
16
+    {
17
+        return [
18
+            ExportColumn::make('order_number'),
19
+            ExportColumn::make('client.name'),
20
+            ExportColumn::make('status')
21
+                ->enum(),
22
+            ExportColumn::make('schedule')
23
+                ->formatStateUsing(function ($state, RecurringInvoice $record) {
24
+                    return $record->getScheduleDescription();
25
+                }),
26
+            ExportColumn::make('timeline')
27
+                ->formatStateUsing(function ($state, RecurringInvoice $record) {
28
+                    return $record->getTimelineDescription();
29
+                }),
30
+            ExportColumn::make('total')
31
+                ->money(),
32
+            ExportColumn::make('subtotal')
33
+                ->money(),
34
+            ExportColumn::make('tax_total')
35
+                ->money(),
36
+            ExportColumn::make('discount_total')
37
+                ->money(),
38
+            ExportColumn::make('discount_rate'),
39
+            ExportColumn::make('currency_code'),
40
+            ExportColumn::make('payment_terms')
41
+                ->enum(),
42
+            ExportColumn::make('start_date')
43
+                ->date(),
44
+            ExportColumn::make('end_date')
45
+                ->date(),
46
+            ExportColumn::make('next_date')
47
+                ->date(),
48
+            ExportColumn::make('last_date')
49
+                ->date(),
50
+            ExportColumn::make('approved_at')
51
+                ->dateTime(),
52
+            ExportColumn::make('ended_at')
53
+                ->dateTime(),
54
+            ExportColumn::make('occurrences_count'),
55
+            ExportColumn::make('max_occurrences'),
56
+            ExportColumn::make('send_time')
57
+                ->formatStateUsing(function (?Carbon $state) {
58
+                    return $state?->format('H:i');
59
+                }),
60
+            ExportColumn::make('frequency')
61
+                ->enabledByDefault(false)
62
+                ->enum(),
63
+            ExportColumn::make('interval_type')
64
+                ->enabledByDefault(false)
65
+                ->enum(),
66
+            ExportColumn::make('interval_value')
67
+                ->enabledByDefault(false),
68
+            ExportColumn::make('month')
69
+                ->enabledByDefault(false)
70
+                ->enum(),
71
+            ExportColumn::make('day_of_month')
72
+                ->enabledByDefault(false)
73
+                ->enum(),
74
+            ExportColumn::make('day_of_week')
75
+                ->enabledByDefault(false)
76
+                ->enum(),
77
+            ExportColumn::make('end_type')
78
+                ->enabledByDefault(false)
79
+                ->enum(),
80
+            ExportColumn::make('discount_method')
81
+                ->enabledByDefault(false)
82
+                ->enum(),
83
+            ExportColumn::make('discount_computation')
84
+                ->enabledByDefault(false)
85
+                ->enum(),
86
+        ];
87
+    }
88
+
89
+    public static function getCompletedNotificationBody(Export $export): string
90
+    {
91
+        $body = 'Your recurring invoice export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
92
+
93
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
94
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
95
+        }
96
+
97
+        return $body;
98
+    }
99
+}

+ 63
- 0
app/Filament/Exports/Accounting/TransactionExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Accounting;
4
+
5
+use App\Models\Accounting\Transaction;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+
10
+class TransactionExporter extends Exporter
11
+{
12
+    protected static ?string $model = Transaction::class;
13
+
14
+    public static function getColumns(): array
15
+    {
16
+        return [
17
+            ExportColumn::make('posted_at')
18
+                ->date(),
19
+            ExportColumn::make('description'),
20
+            ExportColumn::make('amount')
21
+                ->money(),
22
+            ExportColumn::make('account.name')
23
+                ->label('Category'),
24
+            ExportColumn::make('bankAccount.account.name')
25
+                ->label('Account'),
26
+            ExportColumn::make('type')
27
+                ->enum(),
28
+            ExportColumn::make('payeeable.name')
29
+                ->label('Payee'),
30
+            ExportColumn::make('payment_method')
31
+                ->enum(),
32
+            ExportColumn::make('notes')
33
+                ->enabledByDefault(false),
34
+            ExportColumn::make('transactionable_type')
35
+                ->label('Source type')
36
+                ->formatStateUsing(static function ($state) {
37
+                    return class_basename($state);
38
+                })
39
+                ->enabledByDefault(false),
40
+            ExportColumn::make('payeeable_type')
41
+                ->label('Payee type')
42
+                ->formatStateUsing(static function ($state) {
43
+                    return class_basename($state);
44
+                })
45
+                ->enabledByDefault(false),
46
+            ExportColumn::make('is_payment')
47
+                ->enabledByDefault(false),
48
+            ExportColumn::make('reviewed')
49
+                ->enabledByDefault(false),
50
+        ];
51
+    }
52
+
53
+    public static function getCompletedNotificationBody(Export $export): string
54
+    {
55
+        $body = 'Your transaction export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
56
+
57
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
58
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
59
+        }
60
+
61
+        return $body;
62
+    }
63
+}

+ 104
- 0
app/Filament/Exports/Common/ClientExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Common;
4
+
5
+use App\Models\Common\Client;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+
10
+class ClientExporter extends Exporter
11
+{
12
+    protected static ?string $model = Client::class;
13
+
14
+    public static function getColumns(): array
15
+    {
16
+        return [
17
+            ExportColumn::make('name'),
18
+            ExportColumn::make('account_number'),
19
+            ExportColumn::make('primaryContact.full_name')
20
+                ->label('Primary contact'),
21
+            ExportColumn::make('primaryContact.email')
22
+                ->label('Email'),
23
+            ExportColumn::make('primaryContact.first_available_phone')
24
+                ->label('Phone'),
25
+            ExportColumn::make('currency_code'),
26
+            ExportColumn::make('balance') // TODO: Potentially find an easier way to calculate this
27
+                ->state(function (Client $record) {
28
+                    return $record->invoices()
29
+                        ->unpaid()
30
+                        ->get()
31
+                        ->sumMoneyInDefaultCurrency('amount_due');
32
+                })
33
+                ->money(),
34
+            ExportColumn::make('overdue_amount')
35
+                ->state(function (Client $record) {
36
+                    return $record->invoices()
37
+                        ->overdue()
38
+                        ->get()
39
+                        ->sumMoneyInDefaultCurrency('amount_due');
40
+                })
41
+                ->money(),
42
+            ExportColumn::make('billingAddress.address_string')
43
+                ->label('Billing address')
44
+                ->enabledByDefault(false),
45
+            ExportColumn::make('billingAddress.address_line_1')
46
+                ->label('Billing address line 1'),
47
+            ExportColumn::make('billingAddress.address_line_2')
48
+                ->label('Billing address line 2'),
49
+            ExportColumn::make('billingAddress.city')
50
+                ->label('Billing city'),
51
+            ExportColumn::make('billingAddress.state.name')
52
+                ->label('Billing state'),
53
+            ExportColumn::make('billingAddress.postal_code')
54
+                ->label('Billing postal code'),
55
+            ExportColumn::make('billingAddress.country.name')
56
+                ->label('Billing country'),
57
+            ExportColumn::make('shippingAddress.recipient')
58
+                ->label('Shipping recipient')
59
+                ->enabledByDefault(false),
60
+            ExportColumn::make('shippingAddress.phone')
61
+                ->label('Shipping phone')
62
+                ->enabledByDefault(false),
63
+            ExportColumn::make('shippingAddress.address_string')
64
+                ->label('Shipping address')
65
+                ->enabledByDefault(false),
66
+            ExportColumn::make('shippingAddress.address_line_1')
67
+                ->label('Shipping address line 1')
68
+                ->enabledByDefault(false),
69
+            ExportColumn::make('shippingAddress.address_line_2')
70
+                ->label('Shipping address line 2')
71
+                ->enabledByDefault(false),
72
+            ExportColumn::make('shippingAddress.city')
73
+                ->label('Shipping city')
74
+                ->enabledByDefault(false),
75
+            ExportColumn::make('shippingAddress.state.name')
76
+                ->label('Shipping state')
77
+                ->enabledByDefault(false),
78
+            ExportColumn::make('shippingAddress.postal_code')
79
+                ->label('Shipping postal code')
80
+                ->enabledByDefault(false),
81
+            ExportColumn::make('shippingAddress.country.name')
82
+                ->label('Shipping country')
83
+                ->enabledByDefault(false),
84
+            ExportColumn::make('shippingAddress.notes')
85
+                ->label('Delivery instructions')
86
+                ->enabledByDefault(false),
87
+            ExportColumn::make('website')
88
+                ->enabledByDefault(false),
89
+            ExportColumn::make('notes')
90
+                ->enabledByDefault(false),
91
+        ];
92
+    }
93
+
94
+    public static function getCompletedNotificationBody(Export $export): string
95
+    {
96
+        $body = 'Your client export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
97
+
98
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
99
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
100
+        }
101
+
102
+        return $body;
103
+    }
104
+}

+ 85
- 0
app/Filament/Exports/Common/VendorExporter.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Exports\Common;
4
+
5
+use App\Enums\Accounting\BillStatus;
6
+use App\Models\Common\Vendor;
7
+use Filament\Actions\Exports\ExportColumn;
8
+use Filament\Actions\Exports\Exporter;
9
+use Filament\Actions\Exports\Models\Export;
10
+
11
+class VendorExporter extends Exporter
12
+{
13
+    protected static ?string $model = Vendor::class;
14
+
15
+    public static function getColumns(): array
16
+    {
17
+        return [
18
+            ExportColumn::make('name'),
19
+            ExportColumn::make('type')
20
+                ->enum(),
21
+            ExportColumn::make('contractor_type')
22
+                ->enum(),
23
+            ExportColumn::make('account_number'),
24
+            ExportColumn::make('contact.full_name')
25
+                ->label('Primary contact'),
26
+            ExportColumn::make('contact.email')
27
+                ->label('Email'),
28
+            ExportColumn::make('contact.first_available_phone')
29
+                ->label('Phone'),
30
+            ExportColumn::make('currency_code'),
31
+            ExportColumn::make('balance')
32
+                ->state(function (Vendor $record) {
33
+                    return $record->bills()
34
+                        ->unpaid()
35
+                        ->get()
36
+                        ->sumMoneyInDefaultCurrency('amount_due');
37
+                })
38
+                ->money(),
39
+            ExportColumn::make('overdue_amount')
40
+                ->state(function (Vendor $record) {
41
+                    return $record->bills()
42
+                        ->where('status', BillStatus::Overdue)
43
+                        ->get()
44
+                        ->sumMoneyInDefaultCurrency('amount_due');
45
+                })
46
+                ->money(),
47
+            ExportColumn::make('address.address_string')
48
+                ->label('Address')
49
+                ->enabledByDefault(false),
50
+            ExportColumn::make('address.address_line_1')
51
+                ->label('Address line 1'),
52
+            ExportColumn::make('address.address_line_2')
53
+                ->label('Address line 2'),
54
+            ExportColumn::make('address.city')
55
+                ->label('City'),
56
+            ExportColumn::make('address.state.name')
57
+                ->label('State'),
58
+            ExportColumn::make('address.postal_code')
59
+                ->label('Postal code'),
60
+            ExportColumn::make('address.country.name')
61
+                ->label('Country'),
62
+            ExportColumn::make('ssn')
63
+                ->label('SSN')
64
+                ->enabledByDefault(false),
65
+            ExportColumn::make('ein')
66
+                ->label('EIN')
67
+                ->enabledByDefault(false),
68
+            ExportColumn::make('website')
69
+                ->enabledByDefault(false),
70
+            ExportColumn::make('notes')
71
+                ->enabledByDefault(false),
72
+        ];
73
+    }
74
+
75
+    public static function getCompletedNotificationBody(Export $export): string
76
+    {
77
+        $body = 'Your vendor export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
78
+
79
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
80
+            $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
81
+        }
82
+
83
+        return $body;
84
+    }
85
+}

+ 7
- 0
app/Providers/Filament/CompanyPanelProvider.php 查看文件

174
             })
174
             })
175
             ->globalSearch(false)
175
             ->globalSearch(false)
176
             ->sidebarCollapsibleOnDesktop()
176
             ->sidebarCollapsibleOnDesktop()
177
+            ->databaseNotifications()
177
             ->viteTheme('resources/css/filament/company/theme.css')
178
             ->viteTheme('resources/css/filament/company/theme.css')
178
             ->brandLogo(static fn () => view('components.icons.logo'))
179
             ->brandLogo(static fn () => view('components.icons.logo'))
179
             ->tenant(Company::class)
180
             ->tenant(Company::class)
299
         TextEntry::configureUsing(function (TextEntry $component): void {
300
         TextEntry::configureUsing(function (TextEntry $component): void {
300
             $component->placeholder('–');
301
             $component->placeholder('–');
301
         });
302
         });
303
+
304
+        Tables\Actions\ExportAction::configureUsing(function (Tables\Actions\ExportAction $action) {
305
+            $action
306
+                ->color('primary')
307
+                ->slideOver();
308
+        });
302
     }
309
     }
303
 
310
 
304
     /**
311
     /**

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

15
 use BackedEnum;
15
 use BackedEnum;
16
 use Carbon\CarbonInterface;
16
 use Carbon\CarbonInterface;
17
 use Closure;
17
 use Closure;
18
+use Filament\Actions\Exports\ExportColumn;
18
 use Filament\Forms\Components\DatePicker;
19
 use Filament\Forms\Components\DatePicker;
19
 use Filament\Forms\Components\Field;
20
 use Filament\Forms\Components\Field;
20
 use Filament\Forms\Components\TextInput;
21
 use Filament\Forms\Components\TextInput;
21
 use Filament\Infolists\Components\TextEntry;
22
 use Filament\Infolists\Components\TextEntry;
23
+use Filament\Support\Contracts\HasLabel;
22
 use Filament\Support\Enums\IconPosition;
24
 use Filament\Support\Enums\IconPosition;
23
 use Filament\Support\RawJs;
25
 use Filament\Support\RawJs;
24
 use Filament\Tables\Columns\TextColumn;
26
 use Filament\Tables\Columns\TextColumn;
475
 
477
 
476
             return $this->setTimezone($timezone)->format($dateFormat);
478
             return $this->setTimezone($timezone)->format($dateFormat);
477
         });
479
         });
480
+
481
+        ExportColumn::macro('money', function () {
482
+            $this->formatStateUsing(static function ($state) {
483
+                if (blank($state) || ! is_int($state)) {
484
+                    return 0.00;
485
+                }
486
+
487
+                return CurrencyConverter::convertCentsToFloat($state);
488
+            });
489
+
490
+            return $this;
491
+        });
492
+
493
+        ExportColumn::macro('date', function () {
494
+            $this->formatStateUsing(static function ($state) {
495
+                if (blank($state)) {
496
+                    return null;
497
+                }
498
+
499
+                try {
500
+                    return Carbon::parse($state)->toDateString();
501
+                } catch (\Exception) {
502
+                    return null;
503
+                }
504
+            });
505
+
506
+            return $this;
507
+        });
508
+
509
+        ExportColumn::macro('dateTime', function () {
510
+            $this->formatStateUsing(static function ($state) {
511
+                if (blank($state)) {
512
+                    return null;
513
+                }
514
+
515
+                try {
516
+                    return Carbon::parse($state)->toDateTimeString();
517
+                } catch (\Exception) {
518
+                    return null;
519
+                }
520
+            });
521
+
522
+            return $this;
523
+        });
524
+
525
+        ExportColumn::macro('enum', function () {
526
+            $this->formatStateUsing(static function ($state) {
527
+                if (blank($state)) {
528
+                    return null;
529
+                }
530
+
531
+                if (! ($state instanceof BackedEnum)) {
532
+                    return $state;
533
+                }
534
+
535
+                if ($state instanceof HasLabel) {
536
+                    return $state->getLabel();
537
+                }
538
+
539
+                return $state->value;
540
+            });
541
+
542
+            return $this;
543
+        });
478
     }
544
     }
479
 }
545
 }

+ 1
- 1
app/Utilities/Currency/CurrencyConverter.php 查看文件

83
 
83
 
84
     public static function convertCentsToFloat(int $amount, ?string $currency = null): float
84
     public static function convertCentsToFloat(int $amount, ?string $currency = null): float
85
     {
85
     {
86
-        $currency ??= CurrencyAccessor::getDefaultCurrency();
86
+        $currency ??= 'USD';
87
 
87
 
88
         return money($amount, $currency)->getValue();
88
         return money($amount, $currency)->getValue();
89
     }
89
     }

+ 90
- 91
composer.lock 查看文件

497
         },
497
         },
498
         {
498
         {
499
             "name": "aws/aws-sdk-php",
499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.344.0",
500
+            "version": "3.344.6",
501
             "source": {
501
             "source": {
502
                 "type": "git",
502
                 "type": "git",
503
                 "url": "https://github.com/aws/aws-sdk-php.git",
503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "787a8ec6301657d9cbdb389db4fa92243c68666a"
504
+                "reference": "eb0bc621472592545539329499961a15a3f9f6dc"
505
             },
505
             },
506
             "dist": {
506
             "dist": {
507
                 "type": "zip",
507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/787a8ec6301657d9cbdb389db4fa92243c68666a",
509
-                "reference": "787a8ec6301657d9cbdb389db4fa92243c68666a",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb0bc621472592545539329499961a15a3f9f6dc",
509
+                "reference": "eb0bc621472592545539329499961a15a3f9f6dc",
510
                 "shasum": ""
510
                 "shasum": ""
511
             },
511
             },
512
             "require": {
512
             "require": {
588
             "support": {
588
             "support": {
589
                 "forum": "https://github.com/aws/aws-sdk-php/discussions",
589
                 "forum": "https://github.com/aws/aws-sdk-php/discussions",
590
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
590
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
591
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.344.0"
591
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.344.6"
592
             },
592
             },
593
-            "time": "2025-06-04T18:36:41+00:00"
593
+            "time": "2025-06-12T18:03:59+00:00"
594
         },
594
         },
595
         {
595
         {
596
             "name": "aws/aws-sdk-php-laravel",
596
             "name": "aws/aws-sdk-php-laravel",
1029
         },
1029
         },
1030
         {
1030
         {
1031
             "name": "codewithdennis/filament-simple-alert",
1031
             "name": "codewithdennis/filament-simple-alert",
1032
-            "version": "v3.0.18",
1032
+            "version": "v3.0.19",
1033
             "source": {
1033
             "source": {
1034
                 "type": "git",
1034
                 "type": "git",
1035
                 "url": "https://github.com/CodeWithDennis/filament-simple-alert.git",
1035
                 "url": "https://github.com/CodeWithDennis/filament-simple-alert.git",
1036
-                "reference": "50bb18626b8b6c3b9780a778e9781f726574e1e7"
1036
+                "reference": "88214c9ce5f0a7855c016c7a6bd57acb5fb78a46"
1037
             },
1037
             },
1038
             "dist": {
1038
             "dist": {
1039
                 "type": "zip",
1039
                 "type": "zip",
1040
-                "url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/50bb18626b8b6c3b9780a778e9781f726574e1e7",
1041
-                "reference": "50bb18626b8b6c3b9780a778e9781f726574e1e7",
1040
+                "url": "https://api.github.com/repos/CodeWithDennis/filament-simple-alert/zipball/88214c9ce5f0a7855c016c7a6bd57acb5fb78a46",
1041
+                "reference": "88214c9ce5f0a7855c016c7a6bd57acb5fb78a46",
1042
                 "shasum": ""
1042
                 "shasum": ""
1043
             },
1043
             },
1044
             "require": {
1044
             "require": {
1098
                     "type": "github"
1098
                     "type": "github"
1099
                 }
1099
                 }
1100
             ],
1100
             ],
1101
-            "time": "2025-02-10T09:29:26+00:00"
1101
+            "time": "2025-06-09T11:29:49+00:00"
1102
         },
1102
         },
1103
         {
1103
         {
1104
             "name": "danharrin/date-format-converter",
1104
             "name": "danharrin/date-format-converter",
1799
         },
1799
         },
1800
         {
1800
         {
1801
             "name": "filament/actions",
1801
             "name": "filament/actions",
1802
-            "version": "v3.3.20",
1802
+            "version": "v3.3.26",
1803
             "source": {
1803
             "source": {
1804
                 "type": "git",
1804
                 "type": "git",
1805
                 "url": "https://github.com/filamentphp/actions.git",
1805
                 "url": "https://github.com/filamentphp/actions.git",
1806
-                "reference": "151f776552ee10d70591c2649708bc4b0a7cba91"
1806
+                "reference": "67dd0da772f19e2d74e60eb53f99330faf183892"
1807
             },
1807
             },
1808
             "dist": {
1808
             "dist": {
1809
                 "type": "zip",
1809
                 "type": "zip",
1810
-                "url": "https://api.github.com/repos/filamentphp/actions/zipball/151f776552ee10d70591c2649708bc4b0a7cba91",
1811
-                "reference": "151f776552ee10d70591c2649708bc4b0a7cba91",
1810
+                "url": "https://api.github.com/repos/filamentphp/actions/zipball/67dd0da772f19e2d74e60eb53f99330faf183892",
1811
+                "reference": "67dd0da772f19e2d74e60eb53f99330faf183892",
1812
                 "shasum": ""
1812
                 "shasum": ""
1813
             },
1813
             },
1814
             "require": {
1814
             "require": {
1848
                 "issues": "https://github.com/filamentphp/filament/issues",
1848
                 "issues": "https://github.com/filamentphp/filament/issues",
1849
                 "source": "https://github.com/filamentphp/filament"
1849
                 "source": "https://github.com/filamentphp/filament"
1850
             },
1850
             },
1851
-            "time": "2025-06-03T06:15:27+00:00"
1851
+            "time": "2025-06-13T14:47:50+00:00"
1852
         },
1852
         },
1853
         {
1853
         {
1854
             "name": "filament/filament",
1854
             "name": "filament/filament",
1855
-            "version": "v3.3.20",
1855
+            "version": "v3.3.26",
1856
             "source": {
1856
             "source": {
1857
                 "type": "git",
1857
                 "type": "git",
1858
                 "url": "https://github.com/filamentphp/panels.git",
1858
                 "url": "https://github.com/filamentphp/panels.git",
1859
-                "reference": "94ee92244d2a64666fb8c1ea50cb7315ebceb63b"
1859
+                "reference": "b060d2d01a969e3b6541ab4f1e24c745352f51c1"
1860
             },
1860
             },
1861
             "dist": {
1861
             "dist": {
1862
                 "type": "zip",
1862
                 "type": "zip",
1863
-                "url": "https://api.github.com/repos/filamentphp/panels/zipball/94ee92244d2a64666fb8c1ea50cb7315ebceb63b",
1864
-                "reference": "94ee92244d2a64666fb8c1ea50cb7315ebceb63b",
1863
+                "url": "https://api.github.com/repos/filamentphp/panels/zipball/b060d2d01a969e3b6541ab4f1e24c745352f51c1",
1864
+                "reference": "b060d2d01a969e3b6541ab4f1e24c745352f51c1",
1865
                 "shasum": ""
1865
                 "shasum": ""
1866
             },
1866
             },
1867
             "require": {
1867
             "require": {
1913
                 "issues": "https://github.com/filamentphp/filament/issues",
1913
                 "issues": "https://github.com/filamentphp/filament/issues",
1914
                 "source": "https://github.com/filamentphp/filament"
1914
                 "source": "https://github.com/filamentphp/filament"
1915
             },
1915
             },
1916
-            "time": "2025-05-27T18:46:23+00:00"
1916
+            "time": "2025-06-12T15:10:00+00:00"
1917
         },
1917
         },
1918
         {
1918
         {
1919
             "name": "filament/forms",
1919
             "name": "filament/forms",
1920
-            "version": "v3.3.20",
1920
+            "version": "v3.3.26",
1921
             "source": {
1921
             "source": {
1922
                 "type": "git",
1922
                 "type": "git",
1923
                 "url": "https://github.com/filamentphp/forms.git",
1923
                 "url": "https://github.com/filamentphp/forms.git",
1924
-                "reference": "d73cdda057a4f5bd409eab9573101e73edb404cc"
1924
+                "reference": "586a13f9d2a6f395ffdc8557730c85a5858b4c4f"
1925
             },
1925
             },
1926
             "dist": {
1926
             "dist": {
1927
                 "type": "zip",
1927
                 "type": "zip",
1928
-                "url": "https://api.github.com/repos/filamentphp/forms/zipball/d73cdda057a4f5bd409eab9573101e73edb404cc",
1929
-                "reference": "d73cdda057a4f5bd409eab9573101e73edb404cc",
1928
+                "url": "https://api.github.com/repos/filamentphp/forms/zipball/586a13f9d2a6f395ffdc8557730c85a5858b4c4f",
1929
+                "reference": "586a13f9d2a6f395ffdc8557730c85a5858b4c4f",
1930
                 "shasum": ""
1930
                 "shasum": ""
1931
             },
1931
             },
1932
             "require": {
1932
             "require": {
1969
                 "issues": "https://github.com/filamentphp/filament/issues",
1969
                 "issues": "https://github.com/filamentphp/filament/issues",
1970
                 "source": "https://github.com/filamentphp/filament"
1970
                 "source": "https://github.com/filamentphp/filament"
1971
             },
1971
             },
1972
-            "time": "2025-06-03T13:40:37+00:00"
1972
+            "time": "2025-06-12T15:10:19+00:00"
1973
         },
1973
         },
1974
         {
1974
         {
1975
             "name": "filament/infolists",
1975
             "name": "filament/infolists",
1976
-            "version": "v3.3.20",
1976
+            "version": "v3.3.26",
1977
             "source": {
1977
             "source": {
1978
                 "type": "git",
1978
                 "type": "git",
1979
                 "url": "https://github.com/filamentphp/infolists.git",
1979
                 "url": "https://github.com/filamentphp/infolists.git",
2024
         },
2024
         },
2025
         {
2025
         {
2026
             "name": "filament/notifications",
2026
             "name": "filament/notifications",
2027
-            "version": "v3.3.20",
2027
+            "version": "v3.3.26",
2028
             "source": {
2028
             "source": {
2029
                 "type": "git",
2029
                 "type": "git",
2030
                 "url": "https://github.com/filamentphp/notifications.git",
2030
                 "url": "https://github.com/filamentphp/notifications.git",
2076
         },
2076
         },
2077
         {
2077
         {
2078
             "name": "filament/support",
2078
             "name": "filament/support",
2079
-            "version": "v3.3.20",
2079
+            "version": "v3.3.26",
2080
             "source": {
2080
             "source": {
2081
                 "type": "git",
2081
                 "type": "git",
2082
                 "url": "https://github.com/filamentphp/support.git",
2082
                 "url": "https://github.com/filamentphp/support.git",
2083
-                "reference": "4f9793ad3339301ca53ea6f2c984734f7ac38ce7"
2083
+                "reference": "7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe"
2084
             },
2084
             },
2085
             "dist": {
2085
             "dist": {
2086
                 "type": "zip",
2086
                 "type": "zip",
2087
-                "url": "https://api.github.com/repos/filamentphp/support/zipball/4f9793ad3339301ca53ea6f2c984734f7ac38ce7",
2088
-                "reference": "4f9793ad3339301ca53ea6f2c984734f7ac38ce7",
2087
+                "url": "https://api.github.com/repos/filamentphp/support/zipball/7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe",
2088
+                "reference": "7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe",
2089
                 "shasum": ""
2089
                 "shasum": ""
2090
             },
2090
             },
2091
             "require": {
2091
             "require": {
2131
                 "issues": "https://github.com/filamentphp/filament/issues",
2131
                 "issues": "https://github.com/filamentphp/filament/issues",
2132
                 "source": "https://github.com/filamentphp/filament"
2132
                 "source": "https://github.com/filamentphp/filament"
2133
             },
2133
             },
2134
-            "time": "2025-06-03T06:16:13+00:00"
2134
+            "time": "2025-06-12T15:02:34+00:00"
2135
         },
2135
         },
2136
         {
2136
         {
2137
             "name": "filament/tables",
2137
             "name": "filament/tables",
2138
-            "version": "v3.3.20",
2138
+            "version": "v3.3.26",
2139
             "source": {
2139
             "source": {
2140
                 "type": "git",
2140
                 "type": "git",
2141
                 "url": "https://github.com/filamentphp/tables.git",
2141
                 "url": "https://github.com/filamentphp/tables.git",
2142
-                "reference": "1a107a8411549297b97d1142b1f7a5fa7a65e32b"
2142
+                "reference": "920204cd5ec1550209cf398fea8dba3dece979de"
2143
             },
2143
             },
2144
             "dist": {
2144
             "dist": {
2145
                 "type": "zip",
2145
                 "type": "zip",
2146
-                "url": "https://api.github.com/repos/filamentphp/tables/zipball/1a107a8411549297b97d1142b1f7a5fa7a65e32b",
2147
-                "reference": "1a107a8411549297b97d1142b1f7a5fa7a65e32b",
2146
+                "url": "https://api.github.com/repos/filamentphp/tables/zipball/920204cd5ec1550209cf398fea8dba3dece979de",
2147
+                "reference": "920204cd5ec1550209cf398fea8dba3dece979de",
2148
                 "shasum": ""
2148
                 "shasum": ""
2149
             },
2149
             },
2150
             "require": {
2150
             "require": {
2183
                 "issues": "https://github.com/filamentphp/filament/issues",
2183
                 "issues": "https://github.com/filamentphp/filament/issues",
2184
                 "source": "https://github.com/filamentphp/filament"
2184
                 "source": "https://github.com/filamentphp/filament"
2185
             },
2185
             },
2186
-            "time": "2025-06-02T09:43:47+00:00"
2186
+            "time": "2025-06-12T15:01:25+00:00"
2187
         },
2187
         },
2188
         {
2188
         {
2189
             "name": "filament/widgets",
2189
             "name": "filament/widgets",
2190
-            "version": "v3.3.20",
2190
+            "version": "v3.3.26",
2191
             "source": {
2191
             "source": {
2192
                 "type": "git",
2192
                 "type": "git",
2193
                 "url": "https://github.com/filamentphp/widgets.git",
2193
                 "url": "https://github.com/filamentphp/widgets.git",
2194
-                "reference": "048c5a4bf0477efbe2910c54a1aeb55c64cf1348"
2194
+                "reference": "5b956f884aaef479f6091463cb829e7c9f2afc2c"
2195
             },
2195
             },
2196
             "dist": {
2196
             "dist": {
2197
                 "type": "zip",
2197
                 "type": "zip",
2198
-                "url": "https://api.github.com/repos/filamentphp/widgets/zipball/048c5a4bf0477efbe2910c54a1aeb55c64cf1348",
2199
-                "reference": "048c5a4bf0477efbe2910c54a1aeb55c64cf1348",
2198
+                "url": "https://api.github.com/repos/filamentphp/widgets/zipball/5b956f884aaef479f6091463cb829e7c9f2afc2c",
2199
+                "reference": "5b956f884aaef479f6091463cb829e7c9f2afc2c",
2200
                 "shasum": ""
2200
                 "shasum": ""
2201
             },
2201
             },
2202
             "require": {
2202
             "require": {
2227
                 "issues": "https://github.com/filamentphp/filament/issues",
2227
                 "issues": "https://github.com/filamentphp/filament/issues",
2228
                 "source": "https://github.com/filamentphp/filament"
2228
                 "source": "https://github.com/filamentphp/filament"
2229
             },
2229
             },
2230
-            "time": "2025-04-23T06:39:59+00:00"
2230
+            "time": "2025-06-12T15:11:14+00:00"
2231
         },
2231
         },
2232
         {
2232
         {
2233
             "name": "firebase/php-jwt",
2233
             "name": "firebase/php-jwt",
4515
         },
4515
         },
4516
         {
4516
         {
4517
             "name": "matomo/device-detector",
4517
             "name": "matomo/device-detector",
4518
-            "version": "6.4.5",
4518
+            "version": "6.4.6",
4519
             "source": {
4519
             "source": {
4520
                 "type": "git",
4520
                 "type": "git",
4521
                 "url": "https://github.com/matomo-org/device-detector.git",
4521
                 "url": "https://github.com/matomo-org/device-detector.git",
4522
-                "reference": "270bbc41f80994e80805ac377b67324eba53c412"
4522
+                "reference": "6f07f615199851548db47a900815d2ea2cdcde08"
4523
             },
4523
             },
4524
             "dist": {
4524
             "dist": {
4525
                 "type": "zip",
4525
                 "type": "zip",
4526
-                "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/270bbc41f80994e80805ac377b67324eba53c412",
4527
-                "reference": "270bbc41f80994e80805ac377b67324eba53c412",
4526
+                "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/6f07f615199851548db47a900815d2ea2cdcde08",
4527
+                "reference": "6f07f615199851548db47a900815d2ea2cdcde08",
4528
                 "shasum": ""
4528
                 "shasum": ""
4529
             },
4529
             },
4530
             "require": {
4530
             "require": {
4581
                 "source": "https://github.com/matomo-org/matomo",
4581
                 "source": "https://github.com/matomo-org/matomo",
4582
                 "wiki": "https://dev.matomo.org/"
4582
                 "wiki": "https://dev.matomo.org/"
4583
             },
4583
             },
4584
-            "time": "2025-02-26T17:37:32+00:00"
4584
+            "time": "2025-06-10T10:00:59+00:00"
4585
         },
4585
         },
4586
         {
4586
         {
4587
             "name": "monolog/monolog",
4587
             "name": "monolog/monolog",
4808
         },
4808
         },
4809
         {
4809
         {
4810
             "name": "nesbot/carbon",
4810
             "name": "nesbot/carbon",
4811
-            "version": "3.9.1",
4811
+            "version": "3.10.0",
4812
             "source": {
4812
             "source": {
4813
                 "type": "git",
4813
                 "type": "git",
4814
                 "url": "https://github.com/CarbonPHP/carbon.git",
4814
                 "url": "https://github.com/CarbonPHP/carbon.git",
4815
-                "reference": "ced71f79398ece168e24f7f7710462f462310d4d"
4815
+                "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9"
4816
             },
4816
             },
4817
             "dist": {
4817
             "dist": {
4818
                 "type": "zip",
4818
                 "type": "zip",
4819
-                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d",
4820
-                "reference": "ced71f79398ece168e24f7f7710462f462310d4d",
4819
+                "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
4820
+                "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
4821
                 "shasum": ""
4821
                 "shasum": ""
4822
             },
4822
             },
4823
             "require": {
4823
             "require": {
4825
                 "ext-json": "*",
4825
                 "ext-json": "*",
4826
                 "php": "^8.1",
4826
                 "php": "^8.1",
4827
                 "psr/clock": "^1.0",
4827
                 "psr/clock": "^1.0",
4828
-                "symfony/clock": "^6.3 || ^7.0",
4828
+                "symfony/clock": "^6.3.12 || ^7.0",
4829
                 "symfony/polyfill-mbstring": "^1.0",
4829
                 "symfony/polyfill-mbstring": "^1.0",
4830
-                "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0"
4830
+                "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0"
4831
             },
4831
             },
4832
             "provide": {
4832
             "provide": {
4833
                 "psr/clock-implementation": "1.0"
4833
                 "psr/clock-implementation": "1.0"
4835
             "require-dev": {
4835
             "require-dev": {
4836
                 "doctrine/dbal": "^3.6.3 || ^4.0",
4836
                 "doctrine/dbal": "^3.6.3 || ^4.0",
4837
                 "doctrine/orm": "^2.15.2 || ^3.0",
4837
                 "doctrine/orm": "^2.15.2 || ^3.0",
4838
-                "friendsofphp/php-cs-fixer": "^3.57.2",
4838
+                "friendsofphp/php-cs-fixer": "^3.75.0",
4839
                 "kylekatarnls/multi-tester": "^2.5.3",
4839
                 "kylekatarnls/multi-tester": "^2.5.3",
4840
-                "ondrejmirtes/better-reflection": "^6.25.0.4",
4841
                 "phpmd/phpmd": "^2.15.0",
4840
                 "phpmd/phpmd": "^2.15.0",
4842
-                "phpstan/extension-installer": "^1.3.1",
4843
-                "phpstan/phpstan": "^1.11.2",
4844
-                "phpunit/phpunit": "^10.5.20",
4845
-                "squizlabs/php_codesniffer": "^3.9.0"
4841
+                "phpstan/extension-installer": "^1.4.3",
4842
+                "phpstan/phpstan": "^2.1.17",
4843
+                "phpunit/phpunit": "^10.5.46",
4844
+                "squizlabs/php_codesniffer": "^3.13.0"
4846
             },
4845
             },
4847
             "bin": [
4846
             "bin": [
4848
                 "bin/carbon"
4847
                 "bin/carbon"
4910
                     "type": "tidelift"
4909
                     "type": "tidelift"
4911
                 }
4910
                 }
4912
             ],
4911
             ],
4913
-            "time": "2025-05-01T19:51:51+00:00"
4912
+            "time": "2025-06-12T10:24:28+00:00"
4914
         },
4913
         },
4915
         {
4914
         {
4916
             "name": "nette/schema",
4915
             "name": "nette/schema",
5492
         },
5491
         },
5493
         {
5492
         {
5494
             "name": "phpseclib/phpseclib",
5493
             "name": "phpseclib/phpseclib",
5495
-            "version": "3.0.43",
5494
+            "version": "3.0.44",
5496
             "source": {
5495
             "source": {
5497
                 "type": "git",
5496
                 "type": "git",
5498
                 "url": "https://github.com/phpseclib/phpseclib.git",
5497
                 "url": "https://github.com/phpseclib/phpseclib.git",
5499
-                "reference": "709ec107af3cb2f385b9617be72af8cf62441d02"
5498
+                "reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9"
5500
             },
5499
             },
5501
             "dist": {
5500
             "dist": {
5502
                 "type": "zip",
5501
                 "type": "zip",
5503
-                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02",
5504
-                "reference": "709ec107af3cb2f385b9617be72af8cf62441d02",
5502
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/1d0b5e7e1434678411787c5a0535e68907cf82d9",
5503
+                "reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9",
5505
                 "shasum": ""
5504
                 "shasum": ""
5506
             },
5505
             },
5507
             "require": {
5506
             "require": {
5582
             ],
5581
             ],
5583
             "support": {
5582
             "support": {
5584
                 "issues": "https://github.com/phpseclib/phpseclib/issues",
5583
                 "issues": "https://github.com/phpseclib/phpseclib/issues",
5585
-                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.43"
5584
+                "source": "https://github.com/phpseclib/phpseclib/tree/3.0.44"
5586
             },
5585
             },
5587
             "funding": [
5586
             "funding": [
5588
                 {
5587
                 {
5598
                     "type": "tidelift"
5597
                     "type": "tidelift"
5599
                 }
5598
                 }
5600
             ],
5599
             ],
5601
-            "time": "2024-12-14T21:12:59+00:00"
5600
+            "time": "2025-06-15T09:59:26+00:00"
5602
         },
5601
         },
5603
         {
5602
         {
5604
             "name": "psr/cache",
5603
             "name": "psr/cache",
6598
         },
6597
         },
6599
         {
6598
         {
6600
             "name": "squirephp/model",
6599
             "name": "squirephp/model",
6601
-            "version": "v3.9.0",
6600
+            "version": "v3.10.0",
6602
             "source": {
6601
             "source": {
6603
                 "type": "git",
6602
                 "type": "git",
6604
                 "url": "https://github.com/squirephp/model.git",
6603
                 "url": "https://github.com/squirephp/model.git",
6652
         },
6651
         },
6653
         {
6652
         {
6654
             "name": "squirephp/repository",
6653
             "name": "squirephp/repository",
6655
-            "version": "v3.9.0",
6654
+            "version": "v3.10.0",
6656
             "source": {
6655
             "source": {
6657
                 "type": "git",
6656
                 "type": "git",
6658
                 "url": "https://github.com/squirephp/repository.git",
6657
                 "url": "https://github.com/squirephp/repository.git",
9606
         },
9605
         },
9607
         {
9606
         {
9608
             "name": "filp/whoops",
9607
             "name": "filp/whoops",
9609
-            "version": "2.18.1",
9608
+            "version": "2.18.2",
9610
             "source": {
9609
             "source": {
9611
                 "type": "git",
9610
                 "type": "git",
9612
                 "url": "https://github.com/filp/whoops.git",
9611
                 "url": "https://github.com/filp/whoops.git",
9613
-                "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26"
9612
+                "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3"
9614
             },
9613
             },
9615
             "dist": {
9614
             "dist": {
9616
                 "type": "zip",
9615
                 "type": "zip",
9617
-                "url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
9618
-                "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
9616
+                "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3",
9617
+                "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3",
9619
                 "shasum": ""
9618
                 "shasum": ""
9620
             },
9619
             },
9621
             "require": {
9620
             "require": {
9665
             ],
9664
             ],
9666
             "support": {
9665
             "support": {
9667
                 "issues": "https://github.com/filp/whoops/issues",
9666
                 "issues": "https://github.com/filp/whoops/issues",
9668
-                "source": "https://github.com/filp/whoops/tree/2.18.1"
9667
+                "source": "https://github.com/filp/whoops/tree/2.18.2"
9669
             },
9668
             },
9670
             "funding": [
9669
             "funding": [
9671
                 {
9670
                 {
9673
                     "type": "github"
9672
                     "type": "github"
9674
                 }
9673
                 }
9675
             ],
9674
             ],
9676
-            "time": "2025-06-03T18:56:14+00:00"
9675
+            "time": "2025-06-11T20:42:19+00:00"
9677
         },
9676
         },
9678
         {
9677
         {
9679
             "name": "hamcrest/hamcrest-php",
9678
             "name": "hamcrest/hamcrest-php",
10060
         },
10059
         },
10061
         {
10060
         {
10062
             "name": "nunomaduro/collision",
10061
             "name": "nunomaduro/collision",
10063
-            "version": "v8.8.0",
10062
+            "version": "v8.8.1",
10064
             "source": {
10063
             "source": {
10065
                 "type": "git",
10064
                 "type": "git",
10066
                 "url": "https://github.com/nunomaduro/collision.git",
10065
                 "url": "https://github.com/nunomaduro/collision.git",
10067
-                "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8"
10066
+                "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5"
10068
             },
10067
             },
10069
             "dist": {
10068
             "dist": {
10070
                 "type": "zip",
10069
                 "type": "zip",
10071
-                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8",
10072
-                "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8",
10070
+                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
10071
+                "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
10073
                 "shasum": ""
10072
                 "shasum": ""
10074
             },
10073
             },
10075
             "require": {
10074
             "require": {
10076
-                "filp/whoops": "^2.18.0",
10077
-                "nunomaduro/termwind": "^2.3.0",
10075
+                "filp/whoops": "^2.18.1",
10076
+                "nunomaduro/termwind": "^2.3.1",
10078
                 "php": "^8.2.0",
10077
                 "php": "^8.2.0",
10079
-                "symfony/console": "^7.2.5"
10078
+                "symfony/console": "^7.3.0"
10080
             },
10079
             },
10081
             "conflict": {
10080
             "conflict": {
10082
                 "laravel/framework": "<11.44.2 || >=13.0.0",
10081
                 "laravel/framework": "<11.44.2 || >=13.0.0",
10084
             },
10083
             },
10085
             "require-dev": {
10084
             "require-dev": {
10086
                 "brianium/paratest": "^7.8.3",
10085
                 "brianium/paratest": "^7.8.3",
10087
-                "larastan/larastan": "^3.2",
10088
-                "laravel/framework": "^11.44.2 || ^12.6",
10089
-                "laravel/pint": "^1.21.2",
10090
-                "laravel/sail": "^1.41.0",
10091
-                "laravel/sanctum": "^4.0.8",
10086
+                "larastan/larastan": "^3.4.2",
10087
+                "laravel/framework": "^11.44.2 || ^12.18",
10088
+                "laravel/pint": "^1.22.1",
10089
+                "laravel/sail": "^1.43.1",
10090
+                "laravel/sanctum": "^4.1.1",
10092
                 "laravel/tinker": "^2.10.1",
10091
                 "laravel/tinker": "^2.10.1",
10093
-                "orchestra/testbench-core": "^9.12.0 || ^10.1",
10094
-                "pestphp/pest": "^3.8.0",
10095
-                "sebastian/environment": "^7.2.0 || ^8.0"
10092
+                "orchestra/testbench-core": "^9.12.0 || ^10.4",
10093
+                "pestphp/pest": "^3.8.2",
10094
+                "sebastian/environment": "^7.2.1 || ^8.0"
10096
             },
10095
             },
10097
             "type": "library",
10096
             "type": "library",
10098
             "extra": {
10097
             "extra": {
10155
                     "type": "patreon"
10154
                     "type": "patreon"
10156
                 }
10155
                 }
10157
             ],
10156
             ],
10158
-            "time": "2025-04-03T14:33:09+00:00"
10157
+            "time": "2025-06-11T01:04:21+00:00"
10159
         },
10158
         },
10160
         {
10159
         {
10161
             "name": "pestphp/pest",
10160
             "name": "pestphp/pest",

+ 31
- 0
database/migrations/2025_06_15_182955_create_notifications_table.php 查看文件

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('notifications', function (Blueprint $table) {
15
+            $table->uuid('id')->primary();
16
+            $table->string('type');
17
+            $table->morphs('notifiable');
18
+            $table->text('data');
19
+            $table->timestamp('read_at')->nullable();
20
+            $table->timestamps();
21
+        });
22
+    }
23
+
24
+    /**
25
+     * Reverse the migrations.
26
+     */
27
+    public function down(): void
28
+    {
29
+        Schema::dropIfExists('notifications');
30
+    }
31
+};

+ 35
- 0
database/migrations/2025_06_15_183012_create_imports_table.php 查看文件

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('imports', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->timestamp('completed_at')->nullable();
17
+            $table->string('file_name');
18
+            $table->string('file_path');
19
+            $table->string('importer');
20
+            $table->unsignedInteger('processed_rows')->default(0);
21
+            $table->unsignedInteger('total_rows');
22
+            $table->unsignedInteger('successful_rows')->default(0);
23
+            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
24
+            $table->timestamps();
25
+        });
26
+    }
27
+
28
+    /**
29
+     * Reverse the migrations.
30
+     */
31
+    public function down(): void
32
+    {
33
+        Schema::dropIfExists('imports');
34
+    }
35
+};

+ 35
- 0
database/migrations/2025_06_15_183013_create_exports_table.php 查看文件

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('exports', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->timestamp('completed_at')->nullable();
17
+            $table->string('file_disk');
18
+            $table->string('file_name')->nullable();
19
+            $table->string('exporter');
20
+            $table->unsignedInteger('processed_rows')->default(0);
21
+            $table->unsignedInteger('total_rows');
22
+            $table->unsignedInteger('successful_rows')->default(0);
23
+            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
24
+            $table->timestamps();
25
+        });
26
+    }
27
+
28
+    /**
29
+     * Reverse the migrations.
30
+     */
31
+    public function down(): void
32
+    {
33
+        Schema::dropIfExists('exports');
34
+    }
35
+};

+ 30
- 0
database/migrations/2025_06_15_183014_create_failed_import_rows_table.php 查看文件

1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('failed_import_rows', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->json('data');
17
+            $table->foreignId('import_id')->constrained()->cascadeOnDelete();
18
+            $table->text('validation_error')->nullable();
19
+            $table->timestamps();
20
+        });
21
+    }
22
+
23
+    /**
24
+     * Reverse the migrations.
25
+     */
26
+    public function down(): void
27
+    {
28
+        Schema::dropIfExists('failed_import_rows');
29
+    }
30
+};

正在加载...
取消
保存