Andrew Wallo 8 月之前
父節點
當前提交
b862e438b6

+ 4
- 1
app/Filament/Company/Resources/Purchases/BillResource.php 查看文件

@@ -7,6 +7,7 @@ use App\Enums\Accounting\DocumentDiscountMethod;
7 7
 use App\Enums\Accounting\DocumentType;
8 8
 use App\Enums\Accounting\PaymentMethod;
9 9
 use App\Filament\Company\Resources\Purchases\BillResource\Pages;
10
+use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers\BillsRelationManager;
10 11
 use App\Filament\Forms\Components\CreateCurrencySelect;
11 12
 use App\Filament\Forms\Components\DocumentTotals;
12 13
 use App\Filament\Tables\Actions\ReplicateBulkAction;
@@ -251,7 +252,9 @@ class BillResource extends Resource
251 252
                     ->searchable()
252 253
                     ->sortable(),
253 254
                 Tables\Columns\TextColumn::make('vendor.name')
254
-                    ->sortable(),
255
+                    ->sortable()
256
+                    ->searchable()
257
+                    ->hiddenOn(BillsRelationManager::class),
255 258
                 Tables\Columns\TextColumn::make('total')
256 259
                     ->currencyWithConversion(static fn (Bill $record) => $record->currency_code)
257 260
                     ->sortable()

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

@@ -6,9 +6,11 @@ use App\Concerns\ManagesLineItems;
6 6
 use App\Concerns\RedirectToListPage;
7 7
 use App\Filament\Company\Resources\Purchases\BillResource;
8 8
 use App\Models\Accounting\Bill;
9
+use App\Models\Common\Vendor;
9 10
 use Filament\Resources\Pages\CreateRecord;
10 11
 use Filament\Support\Enums\MaxWidth;
11 12
 use Illuminate\Database\Eloquent\Model;
13
+use Livewire\Attributes\Url;
12 14
 
13 15
 class CreateBill extends CreateRecord
14 16
 {
@@ -17,6 +19,22 @@ class CreateBill extends CreateRecord
17 19
 
18 20
     protected static string $resource = BillResource::class;
19 21
 
22
+    #[Url(as: 'vendor')]
23
+    public ?int $vendorId = null;
24
+
25
+    public function mount(): void
26
+    {
27
+        parent::mount();
28
+
29
+        if ($this->vendorId) {
30
+            $this->data['vendor_id'] = $this->vendorId;
31
+
32
+            if ($currencyCode = Vendor::find($this->vendorId)?->currency_code) {
33
+                $this->data['currency_code'] = $currencyCode;
34
+            }
35
+        }
36
+    }
37
+
20 38
     public function getMaxContentWidth(): MaxWidth | string | null
21 39
     {
22 40
         return MaxWidth::Full;

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

@@ -211,7 +211,10 @@ class VendorResource extends Resource
211 211
                 //
212 212
             ])
213 213
             ->actions([
214
-                Tables\Actions\EditAction::make(),
214
+                Tables\Actions\ActionGroup::make([
215
+                    Tables\Actions\EditAction::make(),
216
+                    Tables\Actions\ViewAction::make(),
217
+                ]),
215 218
             ])
216 219
             ->bulkActions([
217 220
                 Tables\Actions\BulkActionGroup::make([
@@ -232,6 +235,7 @@ class VendorResource extends Resource
232 235
         return [
233 236
             'index' => Pages\ListVendors::route('/'),
234 237
             'create' => Pages\CreateVendor::route('/create'),
238
+            'view' => Pages\ViewVendor::route('/{record}'),
235 239
             'edit' => Pages\EditVendor::route('/{record}/edit'),
236 240
         ];
237 241
     }

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

@@ -0,0 +1,62 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Purchases\VendorResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Purchases\VendorResource;
6
+use App\Filament\Company\Resources\Purchases\VendorResource\RelationManagers;
7
+use Filament\Infolists\Components\Section;
8
+use Filament\Infolists\Components\TextEntry;
9
+use Filament\Infolists\Infolist;
10
+use Filament\Resources\Pages\ViewRecord;
11
+
12
+class ViewVendor extends ViewRecord
13
+{
14
+    protected static string $resource = VendorResource::class;
15
+
16
+    public function getRelationManagers(): array
17
+    {
18
+        return [
19
+            RelationManagers\BillsRelationManager::class,
20
+        ];
21
+    }
22
+
23
+    public function getTitle(): string
24
+    {
25
+        return $this->record->name;
26
+    }
27
+
28
+    protected function getHeaderWidgets(): array
29
+    {
30
+        return [
31
+            VendorResource\Widgets\BillOverview::class,
32
+        ];
33
+    }
34
+
35
+    public function infolist(Infolist $infolist): Infolist
36
+    {
37
+        return $infolist
38
+            ->schema([
39
+                Section::make('General')
40
+                    ->columns()
41
+                    ->schema([
42
+                        TextEntry::make('contact.full_name')
43
+                            ->label('Contact'),
44
+                        TextEntry::make('contact.email')
45
+                            ->label('Email'),
46
+                        TextEntry::make('contact.first_available_phone')
47
+                            ->label('Primary Phone'),
48
+                        TextEntry::make('website')
49
+                            ->label('Website')
50
+                            ->url(static fn ($state) => $state, true),
51
+                    ]),
52
+                Section::make('Additional Details')
53
+                    ->columns()
54
+                    ->schema([
55
+                        TextEntry::make('address.address_string')
56
+                            ->label('Billing Address')
57
+                            ->listWithLineBreaks(),
58
+                        TextEntry::make('notes'),
59
+                    ]),
60
+            ]);
61
+    }
62
+}

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

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

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

@@ -0,0 +1,66 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Purchases\VendorResource\Widgets;
4
+
5
+use App\Enums\Accounting\BillStatus;
6
+use App\Filament\Widgets\EnhancedStatsOverviewWidget;
7
+use App\Utilities\Currency\CurrencyAccessor;
8
+use App\Utilities\Currency\CurrencyConverter;
9
+use Illuminate\Database\Eloquent\Model;
10
+use Illuminate\Support\Number;
11
+
12
+class BillOverview extends EnhancedStatsOverviewWidget
13
+{
14
+    public ?Model $record = null;
15
+
16
+    protected function getStats(): array
17
+    {
18
+        $unpaidBills = $this->record->bills()
19
+            ->whereIn('status', [BillStatus::Unpaid, BillStatus::Partial, BillStatus::Overdue]);
20
+
21
+        $amountToPay = $unpaidBills->get()->sumMoneyInDefaultCurrency('amount_due');
22
+
23
+        $amountOverdue = $unpaidBills
24
+            ->clone()
25
+            ->where('status', BillStatus::Overdue)
26
+            ->get()
27
+            ->sumMoneyInDefaultCurrency('amount_due');
28
+
29
+        $amountDueWithin7Days = $unpaidBills
30
+            ->clone()
31
+            ->whereBetween('due_date', [today(), today()->addWeek()])
32
+            ->get()
33
+            ->sumMoneyInDefaultCurrency('amount_due');
34
+
35
+        $averagePaymentTime = $this->record->bills()
36
+            ->whereNotNull('paid_at')
37
+            ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, paid_at)) as avg_days')
38
+            ->value('avg_days');
39
+
40
+        $averagePaymentTimeFormatted = Number::format($averagePaymentTime ?? 0, maxPrecision: 1);
41
+
42
+        $lastMonthTotal = $this->record->bills()
43
+            ->where('status', BillStatus::Paid)
44
+            ->whereBetween('date', [
45
+                today()->subMonth()->startOfMonth(),
46
+                today()->subMonth()->endOfMonth(),
47
+            ])
48
+            ->get()
49
+            ->sumMoneyInDefaultCurrency('amount_paid');
50
+
51
+        return [
52
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Total To Pay', CurrencyConverter::formatCentsToMoney($amountToPay))
53
+                ->suffix(CurrencyAccessor::getDefaultCurrency())
54
+                ->description('Includes ' . CurrencyConverter::formatCentsToMoney($amountOverdue) . ' overdue'),
55
+
56
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Due Within 7 Days', CurrencyConverter::formatCentsToMoney($amountDueWithin7Days))
57
+                ->suffix(CurrencyAccessor::getDefaultCurrency()),
58
+
59
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Average Payment Time', $averagePaymentTimeFormatted)
60
+                ->suffix('days'),
61
+
62
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Paid Last Month', CurrencyConverter::formatCentsToMoney($lastMonthTotal))
63
+                ->suffix(CurrencyAccessor::getDefaultCurrency()),
64
+        ];
65
+    }
66
+}

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

@@ -4,12 +4,11 @@ namespace App\Filament\Company\Resources\Sales\ClientResource\Pages;
4 4
 
5 5
 use App\Filament\Company\Resources\Sales\ClientResource;
6 6
 use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers;
7
-use App\Models\Common\Client;
8 7
 use Filament\Infolists\Components\Section;
9 8
 use Filament\Infolists\Components\TextEntry;
10 9
 use Filament\Infolists\Infolist;
11 10
 use Filament\Resources\Pages\ViewRecord;
12
-use Illuminate\Support\Number;
11
+use Illuminate\Contracts\Support\Htmlable;
13 12
 
14 13
 class ViewClient extends ViewRecord
15 14
 {
@@ -19,6 +18,20 @@ class ViewClient extends ViewRecord
19 18
     {
20 19
         return [
21 20
             RelationManagers\InvoicesRelationManager::class,
21
+            RelationManagers\RecurringInvoicesRelationManager::class,
22
+            RelationManagers\EstimatesRelationManager::class,
23
+        ];
24
+    }
25
+
26
+    public function getTitle(): string | Htmlable
27
+    {
28
+        return $this->record->name;
29
+    }
30
+
31
+    protected function getHeaderWidgets(): array
32
+    {
33
+        return [
34
+            ClientResource\Widgets\InvoiceOverview::class,
22 35
         ];
23 36
     }
24 37
 
@@ -26,49 +39,30 @@ class ViewClient extends ViewRecord
26 39
     {
27 40
         return $infolist
28 41
             ->schema([
29
-                Section::make('Financial Overview')
30
-                    ->columns(4)
42
+                Section::make('General')
43
+                    ->columns()
44
+                    ->schema([
45
+                        TextEntry::make('primaryContact.full_name')
46
+                            ->label('Primary Contact'),
47
+                        TextEntry::make('primaryContact.email')
48
+                            ->label('Primary Email'),
49
+                        TextEntry::make('primaryContact.first_available_phone')
50
+                            ->label('Primary Phone'),
51
+                        TextEntry::make('website')
52
+                            ->label('Website')
53
+                            ->url(static fn ($state) => $state, true),
54
+                    ]),
55
+                Section::make('Additional Details')
56
+                    ->columns()
31 57
                     ->schema([
32
-                        TextEntry::make('last_12_months_paid')
33
-                            ->label('Last 12 Months Paid')
34
-                            ->getStateUsing(function (Client $record) {
35
-                                return $record->invoices()
36
-                                    ->whereNotNull('paid_at')
37
-                                    ->where('paid_at', '>=', now()->subMonths(12))
38
-                                    ->get()
39
-                                    ->sumMoneyInDefaultCurrency('total');
40
-                            })
41
-                            ->currency(convert: false),
42
-                        TextEntry::make('total_unpaid')
43
-                            ->label('Total Unpaid')
44
-                            ->getStateUsing(function (Client $record) {
45
-                                return $record->invoices()
46
-                                    ->unpaid()
47
-                                    ->get()
48
-                                    ->sumMoneyInDefaultCurrency('amount_due');
49
-                            })
50
-                            ->currency(convert: false),
51
-                        TextEntry::make('total_overdue')
52
-                            ->label('Total Overdue')
53
-                            ->getStateUsing(function (Client $record) {
54
-                                return $record->invoices()
55
-                                    ->overdue()
56
-                                    ->get()
57
-                                    ->sumMoneyInDefaultCurrency('amount_due');
58
-                            })
59
-                            ->currency(convert: false),
60
-                        TextEntry::make('average_payment_time')
61
-                            ->label('Average Payment Time')
62
-                            ->getStateUsing(function (Client $record) {
63
-                                return $record->invoices()
64
-                                    ->whereNotNull('paid_at')
65
-                                    ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, paid_at)) as avg_days')
66
-                                    ->value('avg_days');
67
-                            })
68
-                            ->suffix(' days')
69
-                            ->formatStateUsing(function ($state) {
70
-                                return Number::format($state ?? 0, maxPrecision: 1);
71
-                            }),
58
+                        TextEntry::make('billingAddress.address_string')
59
+                            ->label('Billing Address')
60
+                            ->listWithLineBreaks(),
61
+                        TextEntry::make('shippingAddress.address_string')
62
+                            ->label('Shipping Address')
63
+                            ->listWithLineBreaks(),
64
+                        TextEntry::make('notes')
65
+                            ->label('Delivery Instructions'),
72 66
                     ]),
73 67
             ]);
74 68
     }

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

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

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

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

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

@@ -0,0 +1,65 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Sales\ClientResource\Widgets;
4
+
5
+use App\Enums\Accounting\InvoiceStatus;
6
+use App\Filament\Widgets\EnhancedStatsOverviewWidget;
7
+use App\Utilities\Currency\CurrencyAccessor;
8
+use App\Utilities\Currency\CurrencyConverter;
9
+use Illuminate\Database\Eloquent\Model;
10
+use Illuminate\Support\Number;
11
+
12
+class InvoiceOverview extends EnhancedStatsOverviewWidget
13
+{
14
+    public ?Model $record = null;
15
+
16
+    protected function getStats(): array
17
+    {
18
+        $unpaidInvoices = $this->record->invoices()->unpaid();
19
+
20
+        $amountUnpaid = $unpaidInvoices->get()->sumMoneyInDefaultCurrency('amount_due');
21
+
22
+        $amountOverdue = $unpaidInvoices->clone()
23
+            ->where('status', InvoiceStatus::Overdue)
24
+            ->get()
25
+            ->sumMoneyInDefaultCurrency('amount_due');
26
+
27
+        $amountDueWithin30Days = $unpaidInvoices->clone()
28
+            ->whereBetween('due_date', [today(), today()->addMonth()])
29
+            ->get()
30
+            ->sumMoneyInDefaultCurrency('amount_due');
31
+
32
+        $validInvoices = $this->record->invoices()
33
+            ->whereNotIn('status', [
34
+                InvoiceStatus::Void,
35
+                InvoiceStatus::Draft,
36
+            ]);
37
+
38
+        $totalValidInvoiceAmount = $validInvoices->get()->sumMoneyInDefaultCurrency('total');
39
+
40
+        $totalValidInvoiceCount = $validInvoices->count();
41
+
42
+        $averageInvoiceTotal = $totalValidInvoiceCount > 0
43
+            ? (int) round($totalValidInvoiceAmount / $totalValidInvoiceCount)
44
+            : 0;
45
+
46
+        $averagePaymentTime = $this->record->invoices()
47
+            ->whereNotNull('paid_at')
48
+            ->selectRaw('AVG(TIMESTAMPDIFF(DAY, date, paid_at)) as avg_days')
49
+            ->value('avg_days');
50
+
51
+        $averagePaymentTimeFormatted = Number::format($averagePaymentTime ?? 0, maxPrecision: 1);
52
+
53
+        return [
54
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Total Unpaid', CurrencyConverter::formatCentsToMoney($amountUnpaid))
55
+                ->suffix(CurrencyAccessor::getDefaultCurrency())
56
+                ->description('Includes ' . CurrencyConverter::formatCentsToMoney($amountOverdue) . ' overdue'),
57
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Due Within 30 Days', CurrencyConverter::formatCentsToMoney($amountDueWithin30Days))
58
+                ->suffix(CurrencyAccessor::getDefaultCurrency()),
59
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Average Payment Time', $averagePaymentTimeFormatted)
60
+                ->suffix('days'),
61
+            EnhancedStatsOverviewWidget\EnhancedStat::make('Average Invoice Total', CurrencyConverter::formatCentsToMoney($averageInvoiceTotal))
62
+                ->suffix(CurrencyAccessor::getDefaultCurrency()),
63
+        ];
64
+    }
65
+}

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

@@ -6,9 +6,11 @@ use App\Concerns\ManagesLineItems;
6 6
 use App\Concerns\RedirectToListPage;
7 7
 use App\Filament\Company\Resources\Sales\EstimateResource;
8 8
 use App\Models\Accounting\Estimate;
9
+use App\Models\Common\Client;
9 10
 use Filament\Resources\Pages\CreateRecord;
10 11
 use Filament\Support\Enums\MaxWidth;
11 12
 use Illuminate\Database\Eloquent\Model;
13
+use Livewire\Attributes\Url;
12 14
 
13 15
 class CreateEstimate extends CreateRecord
14 16
 {
@@ -17,6 +19,22 @@ class CreateEstimate extends CreateRecord
17 19
 
18 20
     protected static string $resource = EstimateResource::class;
19 21
 
22
+    #[Url(as: 'client')]
23
+    public ?int $clientId = null;
24
+
25
+    public function mount(): void
26
+    {
27
+        parent::mount();
28
+
29
+        if ($this->clientId) {
30
+            $this->data['client_id'] = $this->clientId;
31
+
32
+            if ($currencyCode = Client::find($this->clientId)?->currency_code) {
33
+                $this->data['currency_code'] = $currencyCode;
34
+            }
35
+        }
36
+    }
37
+
20 38
     public function getMaxContentWidth(): MaxWidth | string | null
21 39
     {
22 40
         return MaxWidth::Full;

+ 3
- 1
app/Filament/Company/Resources/Sales/InvoiceResource.php 查看文件

@@ -7,6 +7,7 @@ use App\Enums\Accounting\DocumentDiscountMethod;
7 7
 use App\Enums\Accounting\DocumentType;
8 8
 use App\Enums\Accounting\InvoiceStatus;
9 9
 use App\Enums\Accounting\PaymentMethod;
10
+use App\Filament\Company\Resources\Sales\ClientResource\RelationManagers\InvoicesRelationManager;
10 11
 use App\Filament\Company\Resources\Sales\InvoiceResource\Pages;
11 12
 use App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
12 13
 use App\Filament\Company\Resources\Sales\InvoiceResource\Widgets;
@@ -333,7 +334,8 @@ class InvoiceResource extends Resource
333 334
                     ->sortable(),
334 335
                 Tables\Columns\TextColumn::make('client.name')
335 336
                     ->sortable()
336
-                    ->searchable(),
337
+                    ->searchable()
338
+                    ->hiddenOn(InvoicesRelationManager::class),
337 339
                 Tables\Columns\TextColumn::make('total')
338 340
                     ->currencyWithConversion(static fn (Invoice $record) => $record->currency_code)
339 341
                     ->sortable()

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

@@ -5,9 +5,11 @@ namespace App\Filament\Company\Resources\Sales\RecurringInvoiceResource\Pages;
5 5
 use App\Concerns\ManagesLineItems;
6 6
 use App\Filament\Company\Resources\Sales\RecurringInvoiceResource;
7 7
 use App\Models\Accounting\RecurringInvoice;
8
+use App\Models\Common\Client;
8 9
 use Filament\Resources\Pages\CreateRecord;
9 10
 use Filament\Support\Enums\MaxWidth;
10 11
 use Illuminate\Database\Eloquent\Model;
12
+use Livewire\Attributes\Url;
11 13
 
12 14
 class CreateRecurringInvoice extends CreateRecord
13 15
 {
@@ -15,6 +17,22 @@ class CreateRecurringInvoice extends CreateRecord
15 17
 
16 18
     protected static string $resource = RecurringInvoiceResource::class;
17 19
 
20
+    #[Url(as: 'client')]
21
+    public ?int $clientId = null;
22
+
23
+    public function mount(): void
24
+    {
25
+        parent::mount();
26
+
27
+        if ($this->clientId) {
28
+            $this->data['client_id'] = $this->clientId;
29
+
30
+            if ($currencyCode = Client::find($this->clientId)?->currency_code) {
31
+                $this->data['currency_code'] = $currencyCode;
32
+            }
33
+        }
34
+    }
35
+
18 36
     public function getMaxContentWidth(): MaxWidth | string | null
19 37
     {
20 38
         return MaxWidth::Full;

+ 12
- 0
app/Models/Common/Client.php 查看文件

@@ -5,7 +5,9 @@ namespace App\Models\Common;
5 5
 use App\Concerns\Blamable;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Common\AddressType;
8
+use App\Models\Accounting\Estimate;
8 9
 use App\Models\Accounting\Invoice;
10
+use App\Models\Accounting\RecurringInvoice;
9 11
 use App\Models\Setting\Currency;
10 12
 use Illuminate\Database\Eloquent\Factories\HasFactory;
11 13
 use Illuminate\Database\Eloquent\Model;
@@ -72,8 +74,18 @@ class Client extends Model
72 74
             ->where('type', AddressType::Shipping);
73 75
     }
74 76
 
77
+    public function estimates(): HasMany
78
+    {
79
+        return $this->hasMany(Estimate::class);
80
+    }
81
+
75 82
     public function invoices(): HasMany
76 83
     {
77 84
         return $this->hasMany(Invoice::class);
78 85
     }
86
+
87
+    public function recurringInvoices(): HasMany
88
+    {
89
+        return $this->hasMany(RecurringInvoice::class);
90
+    }
79 91
 }

+ 16
- 16
composer.lock 查看文件

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.337.2",
500
+            "version": "3.337.3",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "f885dd803a257da9d54e72a4750bba73e1196aee"
504
+                "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f885dd803a257da9d54e72a4750bba73e1196aee",
509
-                "reference": "f885dd803a257da9d54e72a4750bba73e1196aee",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06dfc8f76423b49aaa181debd25bbdc724c346d6",
509
+                "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.337.2"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.337.3"
593 593
             },
594
-            "time": "2025-01-17T19:10:04+00:00"
594
+            "time": "2025-01-21T19:10:05+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",
@@ -3051,16 +3051,16 @@
3051 3051
         },
3052 3052
         {
3053 3053
             "name": "laravel/framework",
3054
-            "version": "v11.38.2",
3054
+            "version": "v11.39.0",
3055 3055
             "source": {
3056 3056
                 "type": "git",
3057 3057
                 "url": "https://github.com/laravel/framework.git",
3058
-                "reference": "9d290aa90fcad44048bedca5219d2b872e98772a"
3058
+                "reference": "996c96955f78e8a2b26a24c490a1721cfb14574f"
3059 3059
             },
3060 3060
             "dist": {
3061 3061
                 "type": "zip",
3062
-                "url": "https://api.github.com/repos/laravel/framework/zipball/9d290aa90fcad44048bedca5219d2b872e98772a",
3063
-                "reference": "9d290aa90fcad44048bedca5219d2b872e98772a",
3062
+                "url": "https://api.github.com/repos/laravel/framework/zipball/996c96955f78e8a2b26a24c490a1721cfb14574f",
3063
+                "reference": "996c96955f78e8a2b26a24c490a1721cfb14574f",
3064 3064
                 "shasum": ""
3065 3065
             },
3066 3066
             "require": {
@@ -3261,7 +3261,7 @@
3261 3261
                 "issues": "https://github.com/laravel/framework/issues",
3262 3262
                 "source": "https://github.com/laravel/framework"
3263 3263
             },
3264
-            "time": "2025-01-15T00:06:46+00:00"
3264
+            "time": "2025-01-21T15:02:43+00:00"
3265 3265
         },
3266 3266
         {
3267 3267
             "name": "laravel/prompts",
@@ -3449,16 +3449,16 @@
3449 3449
         },
3450 3450
         {
3451 3451
             "name": "laravel/socialite",
3452
-            "version": "v5.16.1",
3452
+            "version": "v5.17.0",
3453 3453
             "source": {
3454 3454
                 "type": "git",
3455 3455
                 "url": "https://github.com/laravel/socialite.git",
3456
-                "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71"
3456
+                "reference": "77be8be7ee5099aed8ca7cfddc1bf6f9ab3fc159"
3457 3457
             },
3458 3458
             "dist": {
3459 3459
                 "type": "zip",
3460
-                "url": "https://api.github.com/repos/laravel/socialite/zipball/4e5be83c0b3ecf81b2ffa47092e917d1f79dce71",
3461
-                "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71",
3460
+                "url": "https://api.github.com/repos/laravel/socialite/zipball/77be8be7ee5099aed8ca7cfddc1bf6f9ab3fc159",
3461
+                "reference": "77be8be7ee5099aed8ca7cfddc1bf6f9ab3fc159",
3462 3462
                 "shasum": ""
3463 3463
             },
3464 3464
             "require": {
@@ -3517,7 +3517,7 @@
3517 3517
                 "issues": "https://github.com/laravel/socialite/issues",
3518 3518
                 "source": "https://github.com/laravel/socialite"
3519 3519
             },
3520
-            "time": "2024-12-11T16:43:51+00:00"
3520
+            "time": "2025-01-17T15:17:00+00:00"
3521 3521
         },
3522 3522
         {
3523 3523
             "name": "laravel/tinker",

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

@@ -1597,9 +1597,9 @@
1597 1597
             }
1598 1598
         },
1599 1599
         "node_modules/laravel-vite-plugin": {
1600
-            "version": "1.1.1",
1601
-            "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz",
1602
-            "integrity": "sha512-HMZXpoSs1OR+7Lw1+g4Iy/s3HF3Ldl8KxxYT2Ot8pEB4XB/QRuZeWgDYJdu552UN03YRSRNK84CLC9NzYRtncA==",
1600
+            "version": "1.2.0",
1601
+            "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz",
1602
+            "integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==",
1603 1603
             "dev": true,
1604 1604
             "license": "MIT",
1605 1605
             "dependencies": {
@@ -2624,9 +2624,9 @@
2624 2624
             "license": "MIT"
2625 2625
         },
2626 2626
         "node_modules/vite": {
2627
-            "version": "6.0.10",
2628
-            "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.10.tgz",
2629
-            "integrity": "sha512-MEszunEcMo6pFsfXN1GhCFQqnE25tWRH0MA4f0Q7uanACi4y1Us+ZGpTMnITwCTnYzB2b9cpmnelTlxgTBmaBA==",
2627
+            "version": "6.0.11",
2628
+            "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
2629
+            "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
2630 2630
             "dev": true,
2631 2631
             "license": "MIT",
2632 2632
             "dependencies": {

Loading…
取消
儲存