Andrew Wallo 10 个月前
父节点
当前提交
452b0a06b2

+ 9
- 0
app/Collections/Accounting/InvoiceCollection.php 查看文件

24
 
24
 
25
         return CurrencyConverter::convertCentsToFormatSimple($totalCents, $currency);
25
         return CurrencyConverter::convertCentsToFormatSimple($totalCents, $currency);
26
     }
26
     }
27
+
28
+    public function sumMoneyFormatted(string $column, ?string $currency = null): string
29
+    {
30
+        $currency ??= CurrencyAccessor::getDefaultCurrency();
31
+
32
+        $totalCents = $this->sumMoneyInCents($column);
33
+
34
+        return CurrencyConverter::formatCentsToMoney($totalCents, $currency);
35
+    }
27
 }
36
 }

+ 5
- 0
app/Filament/Company/Pages/Accounting/Transactions.php 查看文件

88
         return static::getModel()::query();
88
         return static::getModel()::query();
89
     }
89
     }
90
 
90
 
91
+    public function getMaxContentWidth(): MaxWidth | string | null
92
+    {
93
+        return 'max-w-8xl';
94
+    }
95
+
91
     protected function getHeaderActions(): array
96
     protected function getHeaderActions(): array
92
     {
97
     {
93
         return [
98
         return [

+ 6
- 0
app/Filament/Company/Resources/Banking/AccountResource/Pages/ListAccounts.php 查看文件

5
 use App\Filament\Company\Resources\Banking\AccountResource;
5
 use App\Filament\Company\Resources\Banking\AccountResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
 use Filament\Resources\Pages\ListRecords;
7
 use Filament\Resources\Pages\ListRecords;
8
+use Filament\Support\Enums\MaxWidth;
8
 
9
 
9
 class ListAccounts extends ListRecords
10
 class ListAccounts extends ListRecords
10
 {
11
 {
16
             Actions\CreateAction::make(),
17
             Actions\CreateAction::make(),
17
         ];
18
         ];
18
     }
19
     }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return 'max-w-8xl';
24
+    }
19
 }
25
 }

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

5
 use App\Filament\Company\Resources\Common\OfferingResource;
5
 use App\Filament\Company\Resources\Common\OfferingResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
 use Filament\Resources\Pages\ListRecords;
7
 use Filament\Resources\Pages\ListRecords;
8
+use Filament\Support\Enums\MaxWidth;
8
 
9
 
9
 class ListOfferings extends ListRecords
10
 class ListOfferings extends ListRecords
10
 {
11
 {
16
             Actions\CreateAction::make(),
17
             Actions\CreateAction::make(),
17
         ];
18
         ];
18
     }
19
     }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return 'max-w-8xl';
24
+    }
19
 }
25
 }

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

5
 use App\Filament\Company\Resources\Purchases\BillResource;
5
 use App\Filament\Company\Resources\Purchases\BillResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
 use Filament\Resources\Pages\ListRecords;
7
 use Filament\Resources\Pages\ListRecords;
8
+use Filament\Support\Enums\MaxWidth;
8
 
9
 
9
 class ListBills extends ListRecords
10
 class ListBills extends ListRecords
10
 {
11
 {
16
             Actions\CreateAction::make(),
17
             Actions\CreateAction::make(),
17
         ];
18
         ];
18
     }
19
     }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return 'max-w-8xl';
24
+    }
19
 }
25
 }

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

5
 use App\Filament\Company\Resources\Purchases\VendorResource;
5
 use App\Filament\Company\Resources\Purchases\VendorResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
 use Filament\Resources\Pages\ListRecords;
7
 use Filament\Resources\Pages\ListRecords;
8
+use Filament\Support\Enums\MaxWidth;
8
 
9
 
9
 class ListVendors extends ListRecords
10
 class ListVendors extends ListRecords
10
 {
11
 {
16
             Actions\CreateAction::make(),
17
             Actions\CreateAction::make(),
17
         ];
18
         ];
18
     }
19
     }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return 'max-w-8xl';
24
+    }
19
 }
25
 }

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

5
 use App\Filament\Company\Resources\Sales\ClientResource;
5
 use App\Filament\Company\Resources\Sales\ClientResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
 use Filament\Resources\Pages\ListRecords;
7
 use Filament\Resources\Pages\ListRecords;
8
+use Filament\Support\Enums\MaxWidth;
8
 
9
 
9
 class ListClients extends ListRecords
10
 class ListClients extends ListRecords
10
 {
11
 {
16
             Actions\CreateAction::make(),
17
             Actions\CreateAction::make(),
17
         ];
18
         ];
18
     }
19
     }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return 'max-w-8xl';
24
+    }
19
 }
25
 }

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

7
 use App\Enums\Accounting\PaymentMethod;
7
 use App\Enums\Accounting\PaymentMethod;
8
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
8
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
9
 use App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
9
 use App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
10
+use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
10
 use App\Filament\Tables\Actions\ReplicateBulkAction;
11
 use App\Filament\Tables\Actions\ReplicateBulkAction;
11
 use App\Filament\Tables\Filters\DateRangeFilter;
12
 use App\Filament\Tables\Filters\DateRangeFilter;
12
 use App\Models\Accounting\Adjustment;
13
 use App\Models\Accounting\Adjustment;
420
     public static function table(Table $table): Table
421
     public static function table(Table $table): Table
421
     {
422
     {
422
         return $table
423
         return $table
424
+            ->defaultSort('due_date')
423
             ->columns([
425
             ->columns([
424
                 Tables\Columns\TextColumn::make('status')
426
                 Tables\Columns\TextColumn::make('status')
425
                     ->badge()
427
                     ->badge()
451
                     ->searchable()
453
                     ->searchable()
452
                     ->sortable(),
454
                     ->sortable(),
453
                 Tables\Columns\TextColumn::make('client.name')
455
                 Tables\Columns\TextColumn::make('client.name')
454
-                    ->sortable(),
456
+                    ->sortable()
457
+                    ->searchable(),
455
                 Tables\Columns\TextColumn::make('total')
458
                 Tables\Columns\TextColumn::make('total')
456
-                    ->currency(),
459
+                    ->currency()
460
+                    ->sortable(),
457
                 Tables\Columns\TextColumn::make('amount_paid')
461
                 Tables\Columns\TextColumn::make('amount_paid')
458
                     ->label('Amount Paid')
462
                     ->label('Amount Paid')
459
-                    ->currency(),
463
+                    ->currency()
464
+                    ->sortable(),
460
                 Tables\Columns\TextColumn::make('amount_due')
465
                 Tables\Columns\TextColumn::make('amount_due')
461
                     ->label('Amount Due')
466
                     ->label('Amount Due')
462
-                    ->currency(),
467
+                    ->currency()
468
+                    ->sortable(),
463
             ])
469
             ])
464
             ->filters([
470
             ->filters([
465
                 Tables\Filters\SelectFilter::make('client')
471
                 Tables\Filters\SelectFilter::make('client')
545
                         })
551
                         })
546
                         ->successNotificationTitle('Invoice Sent')
552
                         ->successNotificationTitle('Invoice Sent')
547
                         ->action(function (Invoice $record, Tables\Actions\Action $action) {
553
                         ->action(function (Invoice $record, Tables\Actions\Action $action) {
548
-                            $record->updateQuietly([
554
+                            $record->update([
549
                                 'status' => InvoiceStatus::Sent,
555
                                 'status' => InvoiceStatus::Sent,
550
                             ]);
556
                             ]);
551
 
557
 
845
             'edit' => Pages\EditInvoice::route('/{record}/edit'),
851
             'edit' => Pages\EditInvoice::route('/{record}/edit'),
846
         ];
852
         ];
847
     }
853
     }
854
+
855
+    public static function getWidgets(): array
856
+    {
857
+        return [
858
+            Widgets\InvoiceOverview::class,
859
+        ];
860
+    }
848
 }
861
 }

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

4
 
4
 
5
 use App\Enums\Accounting\InvoiceStatus;
5
 use App\Enums\Accounting\InvoiceStatus;
6
 use App\Filament\Company\Resources\Sales\InvoiceResource;
6
 use App\Filament\Company\Resources\Sales\InvoiceResource;
7
+use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
7
 use App\Models\Accounting\Invoice;
8
 use App\Models\Accounting\Invoice;
8
 use Filament\Actions;
9
 use Filament\Actions;
10
+use Filament\Pages\Concerns\ExposesTableToWidgets;
9
 use Filament\Resources\Components\Tab;
11
 use Filament\Resources\Components\Tab;
10
 use Filament\Resources\Pages\ListRecords;
12
 use Filament\Resources\Pages\ListRecords;
13
+use Filament\Support\Enums\MaxWidth;
11
 use Illuminate\Database\Eloquent\Builder;
14
 use Illuminate\Database\Eloquent\Builder;
12
 
15
 
13
 class ListInvoices extends ListRecords
16
 class ListInvoices extends ListRecords
14
 {
17
 {
18
+    use ExposesTableToWidgets;
19
+
15
     protected static string $resource = InvoiceResource::class;
20
     protected static string $resource = InvoiceResource::class;
16
 
21
 
17
     protected function getHeaderActions(): array
22
     protected function getHeaderActions(): array
21
         ];
26
         ];
22
     }
27
     }
23
 
28
 
29
+    protected function getHeaderWidgets(): array
30
+    {
31
+        return [
32
+            Widgets\InvoiceOverview::make(),
33
+        ];
34
+    }
35
+
36
+    public function getMaxContentWidth(): MaxWidth | string | null
37
+    {
38
+        return 'max-w-8xl';
39
+    }
40
+
24
     public function getTabs(): array
41
     public function getTabs(): array
25
     {
42
     {
26
         return [
43
         return [

+ 17
- 17
app/Filament/Company/Resources/Sales/InvoiceResource/Pages/ViewInvoice.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
3
 namespace App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
4
 
4
 
5
-use App\Enums\Accounting\InvoiceStatus;
6
 use App\Filament\Company\Resources\Sales\ClientResource;
5
 use App\Filament\Company\Resources\Sales\ClientResource;
7
 use App\Filament\Company\Resources\Sales\InvoiceResource;
6
 use App\Filament\Company\Resources\Sales\InvoiceResource;
8
 use App\Models\Accounting\Invoice;
7
 use App\Models\Accounting\Invoice;
27
         return $infolist
26
         return $infolist
28
             ->schema([
27
             ->schema([
29
                 Section::make('Invoice Details')
28
                 Section::make('Invoice Details')
30
-                    ->columns(3)
29
+                    ->columns(4)
31
                     ->schema([
30
                     ->schema([
32
                         TextEntry::make('invoice_number')
31
                         TextEntry::make('invoice_number')
33
                             ->label('Invoice #'),
32
                             ->label('Invoice #'),
44
                         TextEntry::make('amount_due')
43
                         TextEntry::make('amount_due')
45
                             ->label('Amount Due')
44
                             ->label('Amount Due')
46
                             ->money(),
45
                             ->money(),
46
+                        TextEntry::make('date')
47
+                            ->label('Date')
48
+                            ->date(),
47
                         TextEntry::make('due_date')
49
                         TextEntry::make('due_date')
48
-                            ->label('Due Date')
50
+                            ->label('Due')
49
                             ->formatStateUsing(function (TextEntry $entry, mixed $state) {
51
                             ->formatStateUsing(function (TextEntry $entry, mixed $state) {
50
                                 if (blank($state)) {
52
                                 if (blank($state)) {
51
                                     return null;
53
                                     return null;
62
                                     'options' => CarbonInterface::ONE_DAY_WORDS,
64
                                     'options' => CarbonInterface::ONE_DAY_WORDS,
63
                                 ]);
65
                                 ]);
64
                             }),
66
                             }),
67
+                        TextEntry::make('approved_at')
68
+                            ->label('Approved At')
69
+                            ->placeholder('Not Approved')
70
+                            ->date(),
71
+                        TextEntry::make('last_sent')
72
+                            ->label('Last Sent')
73
+                            ->placeholder('Never')
74
+                            ->date(),
75
+                        TextEntry::make('paid_at')
76
+                            ->label('Paid At')
77
+                            ->placeholder('Not Paid')
78
+                            ->date(),
65
                     ]),
79
                     ]),
66
             ]);
80
             ]);
67
     }
81
     }
68
-
69
-    public function approveDraft(): void
70
-    {
71
-        $this->record->update([
72
-            'status' => InvoiceStatus::Unsent,
73
-        ]);
74
-    }
75
-
76
-    public function markAsSent(): void
77
-    {
78
-        $this->record->update([
79
-            'status' => InvoiceStatus::Sent,
80
-        ]);
81
-    }
82
 }
82
 }

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

1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
4
+
5
+use App\Enums\Accounting\InvoiceStatus;
6
+use App\Filament\Company\Resources\Sales\InvoiceResource\Pages\ListInvoices;
7
+use App\Utilities\Currency\CurrencyConverter;
8
+use Filament\Widgets\Concerns\InteractsWithPageTable;
9
+use Filament\Widgets\StatsOverviewWidget as BaseWidget;
10
+use Filament\Widgets\StatsOverviewWidget\Stat;
11
+use Illuminate\Support\Number;
12
+
13
+class InvoiceOverview extends BaseWidget
14
+{
15
+    use InteractsWithPageTable;
16
+
17
+    protected static ?string $pollingInterval = null;
18
+
19
+    protected function getTablePage(): string
20
+    {
21
+        return ListInvoices::class;
22
+    }
23
+
24
+    protected function getStats(): array
25
+    {
26
+        $outstandingInvoices = $this->getPageTableQuery()
27
+            ->whereNotIn('status', [
28
+                InvoiceStatus::Paid,
29
+                InvoiceStatus::Void,
30
+                InvoiceStatus::Draft,
31
+                InvoiceStatus::Overpaid,
32
+            ]);
33
+
34
+        $amountOutstanding = $outstandingInvoices
35
+            ->clone()
36
+            ->sum('amount_due');
37
+
38
+        $amountOverdue = $outstandingInvoices
39
+            ->clone()
40
+            ->where('status', InvoiceStatus::Overdue)
41
+            ->sum('amount_due');
42
+
43
+        $amountDueWithin30Days = $outstandingInvoices
44
+            ->clone()
45
+            ->where('due_date', '>=', today())
46
+            ->where('due_date', '<=', today()->addDays(30))
47
+            ->sum('amount_due');
48
+
49
+        $validInvoices = $this->getPageTableQuery()
50
+            ->whereNotIn('status', [
51
+                InvoiceStatus::Void,
52
+                InvoiceStatus::Draft,
53
+            ]);
54
+
55
+        $totalValidInvoiceAmount = $validInvoices->clone()->sum('amount_due');
56
+
57
+        $totalValidInvoiceCount = $validInvoices->clone()->count();
58
+
59
+        $averageInvoiceTotal = $totalValidInvoiceCount > 0 ? $totalValidInvoiceAmount / $totalValidInvoiceCount : 0;
60
+
61
+        $averagePaymentTime = $this->getPageTableQuery()
62
+            ->withWhereHas('statusHistories', function ($query) {
63
+                $query->where('new_status', InvoiceStatus::Paid);
64
+            })
65
+            ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, (
66
+                SELECT changed_at
67
+                FROM invoice_status_histories
68
+                WHERE invoice_status_histories.invoice_id = invoices.id
69
+                AND status = ?
70
+                LIMIT 1
71
+            ))) as avg_days', [InvoiceStatus::Paid])
72
+            ->value('avg_days');
73
+
74
+        return [
75
+            Stat::make('Total Outstanding', CurrencyConverter::formatCentsToMoney($amountOutstanding))
76
+                ->description('Includes ' . CurrencyConverter::formatCentsToMoney($amountOverdue) . ' overdue'),
77
+            Stat::make('Due Within 30 Days', CurrencyConverter::formatCentsToMoney($amountDueWithin30Days)),
78
+            Stat::make('Average Payment Time', Number::format($averagePaymentTime ?? 0, maxPrecision: 1) . ' days')
79
+                ->description('From invoice date to payment received'),
80
+            Stat::make('Average Invoice Total', CurrencyConverter::formatCentsToMoney($averageInvoiceTotal))
81
+                ->description('Excludes void and draft invoices'),
82
+        ];
83
+    }
84
+}

+ 36
- 0
app/Models/Accounting/Invoice.php 查看文件

17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
18
 use Illuminate\Database\Eloquent\Model;
18
 use Illuminate\Database\Eloquent\Model;
19
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
19
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
20
+use Illuminate\Database\Eloquent\Relations\HasMany;
20
 use Illuminate\Database\Eloquent\Relations\MorphMany;
21
 use Illuminate\Database\Eloquent\Relations\MorphMany;
21
 use Illuminate\Database\Eloquent\Relations\MorphOne;
22
 use Illuminate\Database\Eloquent\Relations\MorphOne;
23
+use Illuminate\Support\Carbon;
22
 
24
 
23
 #[ObservedBy(InvoiceObserver::class)]
25
 #[ObservedBy(InvoiceObserver::class)]
24
 #[CollectedBy(InvoiceCollection::class)]
26
 #[CollectedBy(InvoiceCollection::class)]
95
         return $this->transactions()->where('type', TransactionType::Withdrawal)->where('is_payment', true);
97
         return $this->transactions()->where('type', TransactionType::Withdrawal)->where('is_payment', true);
96
     }
98
     }
97
 
99
 
100
+    protected function lastSent(): Attribute
101
+    {
102
+        return Attribute::get(function () {
103
+            return $this->getStatusChangedAt(InvoiceStatus::Sent);
104
+        });
105
+    }
106
+
107
+    protected function approvedAt(): Attribute
108
+    {
109
+        return Attribute::get(function () {
110
+            return $this->getStatusChangedAt(InvoiceStatus::Unsent);
111
+        });
112
+    }
113
+
114
+    protected function paidAt(): Attribute
115
+    {
116
+        return Attribute::get(function () {
117
+            return $this->getStatusChangedAt(InvoiceStatus::Paid);
118
+        });
119
+    }
120
+
121
+    protected function getStatusChangedAt(InvoiceStatus $status): ?Carbon
122
+    {
123
+        return $this->statusHistories
124
+            ->where('new_status', $status)
125
+            ->sortByDesc('changed_at')
126
+            ->first()?->changed_at;
127
+    }
128
+
98
     public function approvalTransaction(): MorphOne
129
     public function approvalTransaction(): MorphOne
99
     {
130
     {
100
         return $this->morphOne(Transaction::class, 'transactionable')
131
         return $this->morphOne(Transaction::class, 'transactionable')
101
             ->where('type', TransactionType::Journal);
132
             ->where('type', TransactionType::Journal);
102
     }
133
     }
103
 
134
 
135
+    public function statusHistories(): HasMany
136
+    {
137
+        return $this->hasMany(InvoiceStatusHistory::class);
138
+    }
139
+
104
     protected function isCurrentlyOverdue(): Attribute
140
     protected function isCurrentlyOverdue(): Attribute
105
     {
141
     {
106
         return Attribute::get(function () {
142
         return Attribute::get(function () {

+ 41
- 0
app/Models/Accounting/InvoiceStatusHistory.php 查看文件

1
+<?php
2
+
3
+namespace App\Models\Accounting;
4
+
5
+use App\Concerns\CompanyOwned;
6
+use App\Enums\Accounting\InvoiceStatus;
7
+use App\Models\User;
8
+use Illuminate\Database\Eloquent\Factories\HasFactory;
9
+use Illuminate\Database\Eloquent\Model;
10
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
+
12
+class InvoiceStatusHistory extends Model
13
+{
14
+    use CompanyOwned;
15
+    use HasFactory;
16
+
17
+    protected $fillable = [
18
+        'company_id',
19
+        'invoice_id',
20
+        'old_status',
21
+        'new_status',
22
+        'changed_by',
23
+        'changed_at',
24
+    ];
25
+
26
+    protected $casts = [
27
+        'changed_at' => 'datetime',
28
+        'old_status' => InvoiceStatus::class,
29
+        'new_status' => InvoiceStatus::class,
30
+    ];
31
+
32
+    public function invoice(): BelongsTo
33
+    {
34
+        return $this->belongsTo(Invoice::class);
35
+    }
36
+
37
+    public function changedBy(): BelongsTo
38
+    {
39
+        return $this->belongsTo(User::class, 'changed_by');
40
+    }
41
+}

+ 5
- 0
app/Models/Company.php 查看文件

171
     {
171
     {
172
         return $this->hasMany(Common\Vendor::class, 'company_id');
172
         return $this->hasMany(Common\Vendor::class, 'company_id');
173
     }
173
     }
174
+
175
+    public function invoiceStatusHistories(): HasMany
176
+    {
177
+        return $this->hasMany(Accounting\InvoiceStatusHistory::class, 'company_id');
178
+    }
174
 }
179
 }

+ 8
- 1
app/Observers/InvoiceObserver.php 查看文件

23
      */
23
      */
24
     public function updated(Invoice $invoice): void
24
     public function updated(Invoice $invoice): void
25
     {
25
     {
26
-        //
26
+        if ($invoice->wasChanged('status')) {
27
+            $invoice->statusHistories()->create([
28
+                'company_id' => $invoice->company_id,
29
+                'old_status' => $invoice->getOriginal('status'),
30
+                'new_status' => $invoice->status,
31
+                'changed_by' => $invoice->updated_by,
32
+            ]);
33
+        }
27
     }
34
     }
28
 
35
 
29
     public function deleting(Invoice $invoice): void
36
     public function deleting(Invoice $invoice): void

+ 6
- 6
composer.lock 查看文件

497
         },
497
         },
498
         {
498
         {
499
             "name": "aws/aws-sdk-php",
499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.333.0",
500
+            "version": "3.334.0",
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": "11bb2709885c9954004620d3648e9355feb92a41"
504
+                "reference": "8afe50cb2a93051dafef21eb616e297a449764aa"
505
             },
505
             },
506
             "dist": {
506
             "dist": {
507
                 "type": "zip",
507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/11bb2709885c9954004620d3648e9355feb92a41",
509
-                "reference": "11bb2709885c9954004620d3648e9355feb92a41",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8afe50cb2a93051dafef21eb616e297a449764aa",
509
+                "reference": "8afe50cb2a93051dafef21eb616e297a449764aa",
510
                 "shasum": ""
510
                 "shasum": ""
511
             },
511
             },
512
             "require": {
512
             "require": {
589
             "support": {
589
             "support": {
590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.333.0"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.334.0"
593
             },
593
             },
594
-            "time": "2024-12-03T19:06:47+00:00"
594
+            "time": "2024-12-04T19:09:04+00:00"
595
         },
595
         },
596
         {
596
         {
597
             "name": "aws/aws-sdk-php-laravel",
597
             "name": "aws/aws-sdk-php-laravel",

+ 23
- 0
database/factories/Accounting/InvoiceStatusHistoryFactory.php 查看文件

1
+<?php
2
+
3
+namespace Database\Factories\Accounting;
4
+
5
+use Illuminate\Database\Eloquent\Factories\Factory;
6
+
7
+/**
8
+ * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Accounting\InvoiceStatusHistory>
9
+ */
10
+class InvoiceStatusHistoryFactory extends Factory
11
+{
12
+    /**
13
+     * Define the model's default state.
14
+     *
15
+     * @return array<string, mixed>
16
+     */
17
+    public function definition(): array
18
+    {
19
+        return [
20
+            //
21
+        ];
22
+    }
23
+}

+ 3
- 0
database/factories/CompanyFactory.php 查看文件

129
             Invoice::factory()
129
             Invoice::factory()
130
                 ->count($paidCount)
130
                 ->count($paidCount)
131
                 ->withLineItems()
131
                 ->withLineItems()
132
+                ->approved()
132
                 ->withPayments(max: 4)
133
                 ->withPayments(max: 4)
133
                 ->create([
134
                 ->create([
134
                     'company_id' => $company->id,
135
                     'company_id' => $company->id,
139
             Invoice::factory()
140
             Invoice::factory()
140
                 ->count($partialCount)
141
                 ->count($partialCount)
141
                 ->withLineItems()
142
                 ->withLineItems()
143
+                ->approved()
142
                 ->withPayments(max: 4, invoiceStatus: InvoiceStatus::Partial)
144
                 ->withPayments(max: 4, invoiceStatus: InvoiceStatus::Partial)
143
                 ->create([
145
                 ->create([
144
                     'company_id' => $company->id,
146
                     'company_id' => $company->id,
149
             Invoice::factory()
151
             Invoice::factory()
150
                 ->count($overpaidCount)
152
                 ->count($overpaidCount)
151
                 ->withLineItems()
153
                 ->withLineItems()
154
+                ->approved()
152
                 ->withPayments(max: 4, invoiceStatus: InvoiceStatus::Overpaid)
155
                 ->withPayments(max: 4, invoiceStatus: InvoiceStatus::Overpaid)
153
                 ->create([
156
                 ->create([
154
                     'company_id' => $company->id,
157
                     'company_id' => $company->id,

+ 33
- 0
database/migrations/2024_12_05_003805_create_invoice_status_histories_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('invoice_status_histories', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
+            $table->foreignId('invoice_id')->constrained()->cascadeOnDelete();
18
+            $table->string('old_status');
19
+            $table->string('new_status');
20
+            $table->foreignId('changed_by')->nullable()->constrained('users')->nullOnDelete();
21
+            $table->timestamp('changed_at')->useCurrent();
22
+            $table->timestamps();
23
+        });
24
+    }
25
+
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+    public function down(): void
30
+    {
31
+        Schema::dropIfExists('invoice_status_histories');
32
+    }
33
+};

+ 3
- 3
package-lock.json 查看文件

945
             }
945
             }
946
         },
946
         },
947
         "node_modules/axios": {
947
         "node_modules/axios": {
948
-            "version": "1.7.8",
949
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
950
-            "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
948
+            "version": "1.7.9",
949
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
950
+            "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
951
             "dev": true,
951
             "dev": true,
952
             "license": "MIT",
952
             "license": "MIT",
953
             "dependencies": {
953
             "dependencies": {

+ 3
- 0
resources/css/filament/company/tailwind.config.js 查看文件

20
                 white: '#F3F4F6',
20
                 white: '#F3F4F6',
21
                 platinum: '#E8E9EB',
21
                 platinum: '#E8E9EB',
22
             },
22
             },
23
+            maxWidth: {
24
+                '8xl': '88rem',
25
+            },
23
             transitionTimingFunction: {
26
             transitionTimingFunction: {
24
                 'ease-smooth': 'cubic-bezier(0.08, 0.52, 0.52, 1)',
27
                 'ease-smooth': 'cubic-bezier(0.08, 0.52, 0.52, 1)',
25
             }
28
             }

正在加载...
取消
保存