瀏覽代碼

wip budgets

3.x
Andrew Wallo 7 月之前
父節點
當前提交
1eb23d2e77

+ 46
- 0
app/Enums/Accounting/BudgetIntervalType.php 查看文件

1
+<?php
2
+
3
+namespace App\Enums\Accounting;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum BudgetIntervalType: string implements HasLabel
9
+{
10
+    use ParsesEnum;
11
+
12
+    case Week = 'week';
13
+    case Month = 'month';
14
+    case Quarter = 'quarter';
15
+    case Year = 'year';
16
+
17
+    public function getLabel(): ?string
18
+    {
19
+        return match ($this) {
20
+            self::Week => 'Weekly',
21
+            self::Month => 'Monthly',
22
+            self::Quarter => 'Quarterly',
23
+            self::Year => 'Yearly',
24
+        };
25
+    }
26
+
27
+    public function isWeek(): bool
28
+    {
29
+        return $this === self::Week;
30
+    }
31
+
32
+    public function isMonth(): bool
33
+    {
34
+        return $this === self::Month;
35
+    }
36
+
37
+    public function isQuarter(): bool
38
+    {
39
+        return $this === self::Quarter;
40
+    }
41
+
42
+    public function isYear(): bool
43
+    {
44
+        return $this === self::Year;
45
+    }
46
+}

+ 28
- 28
app/Filament/Company/Resources/Accounting/BudgetResource.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Accounting;
3
 namespace App\Filament\Company\Resources\Accounting;
4
 
4
 
5
+use App\Enums\Accounting\BudgetIntervalType;
5
 use App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
6
 use App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
6
 use App\Filament\Forms\Components\CustomSection;
7
 use App\Filament\Forms\Components\CustomSection;
7
 use App\Models\Accounting\Account;
8
 use App\Models\Accounting\Account;
31
                             ->maxLength(255),
32
                             ->maxLength(255),
32
                         Forms\Components\Select::make('interval_type')
33
                         Forms\Components\Select::make('interval_type')
33
                             ->label('Budget Interval')
34
                             ->label('Budget Interval')
34
-                            ->options([
35
-                                'day' => 'Daily',
36
-                                'week' => 'Weekly',
37
-                                'month' => 'Monthly',
38
-                                'quarter' => 'Quarterly',
39
-                                'year' => 'Yearly',
40
-                            ])
41
-                            ->default('month')
35
+                            ->options(BudgetIntervalType::class)
36
+                            ->default(BudgetIntervalType::Month->value)
42
                             ->required()
37
                             ->required()
43
                             ->live(),
38
                             ->live(),
44
                         Forms\Components\DatePicker::make('start_date')
39
                         Forms\Components\DatePicker::make('start_date')
75
                                             ->label('Disperse')
70
                                             ->label('Disperse')
76
                                             ->icon('heroicon-m-bars-arrow-down')
71
                                             ->icon('heroicon-m-bars-arrow-down')
77
                                             ->color('primary')
72
                                             ->color('primary')
78
-                                            ->action(fn (Forms\Set $set, Forms\Get $get, $state) => self::disperseTotalAmount($set, $get, $state))
73
+                                            ->action(static fn (Forms\Set $set, Forms\Get $get, $state) => self::disperseTotalAmount($set, $get, $state))
79
                                     ),
74
                                     ),
80
 
75
 
81
                                 CustomSection::make('Budget Allocations')
76
                                 CustomSection::make('Budget Allocations')
100
                 Tables\Columns\TextColumn::make('interval_type')
95
                 Tables\Columns\TextColumn::make('interval_type')
101
                     ->label('Interval')
96
                     ->label('Interval')
102
                     ->sortable()
97
                     ->sortable()
103
-                    ->badge()
104
-                    ->formatStateUsing(fn (string $state) => ucfirst($state)),
98
+                    ->badge(),
105
 
99
 
106
                 Tables\Columns\TextColumn::make('start_date')
100
                 Tables\Columns\TextColumn::make('start_date')
107
                     ->label('Start Date')
101
                     ->label('Start Date')
118
                     ->money()
112
                     ->money()
119
                     ->sortable()
113
                     ->sortable()
120
                     ->alignEnd()
114
                     ->alignEnd()
121
-                    ->formatStateUsing(fn (Budget $record) => $record->budgetItems->sum(fn ($item) => $item->allocations->sum('amount'))),
115
+                    ->getStateUsing(fn (Budget $record) => $record->budgetItems->sum(fn ($item) => $item->allocations->sum('amount'))),
122
             ])
116
             ])
123
             ->filters([
117
             ->filters([
124
                 //
118
                 //
175
     {
169
     {
176
         $start = Carbon::parse($startDate);
170
         $start = Carbon::parse($startDate);
177
         $end = Carbon::parse($endDate);
171
         $end = Carbon::parse($endDate);
172
+        $intervalTypeEnum = BudgetIntervalType::parse($intervalType);
178
         $labels = [];
173
         $labels = [];
179
 
174
 
180
         while ($start->lte($end)) {
175
         while ($start->lte($end)) {
181
-            $labels[] = match ($intervalType) {
182
-                'month' => $start->format('M'), // Example: Jan, Feb, Mar
183
-                'quarter' => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
184
-                'year' => (string) $start->year, // Example: 2024, 2025
176
+            $labels[] = match ($intervalTypeEnum) {
177
+                BudgetIntervalType::Week => 'W' . $start->weekOfYear . ' ' . $start->year, // Example: W10 2024
178
+                BudgetIntervalType::Month => $start->format('M'), // Example: Jan, Feb, Mar
179
+                BudgetIntervalType::Quarter => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
180
+                BudgetIntervalType::Year => (string) $start->year, // Example: 2024, 2025
185
                 default => '',
181
                 default => '',
186
             };
182
             };
187
 
183
 
188
-            match ($intervalType) {
189
-                'month' => $start->addMonth(),
190
-                'quarter' => $start->addQuarter(),
191
-                'year' => $start->addYear(),
184
+            match ($intervalTypeEnum) {
185
+                BudgetIntervalType::Week => $start->addWeek(),
186
+                BudgetIntervalType::Month => $start->addMonth(),
187
+                BudgetIntervalType::Quarter => $start->addQuarter(),
188
+                BudgetIntervalType::Year => $start->addYear(),
192
                 default => null,
189
                 default => null,
193
             };
190
             };
194
         }
191
         }
204
 
201
 
205
         $start = Carbon::parse($startDate);
202
         $start = Carbon::parse($startDate);
206
         $end = Carbon::parse($endDate);
203
         $end = Carbon::parse($endDate);
204
+        $intervalTypeEnum = BudgetIntervalType::parse($intervalType);
207
         $fields = [];
205
         $fields = [];
208
 
206
 
209
         while ($start->lte($end)) {
207
         while ($start->lte($end)) {
210
-            $label = match ($intervalType) {
211
-                'month' => $start->format('M'), // Example: Jan, Feb, Mar
212
-                'quarter' => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
213
-                'year' => (string) $start->year, // Example: 2024, 2025
208
+            $label = match ($intervalTypeEnum) {
209
+                BudgetIntervalType::Week => 'W' . $start->weekOfYear . ' ' . $start->year, // Example: W10 2024
210
+                BudgetIntervalType::Month => $start->format('M'), // Example: Jan, Feb, Mar
211
+                BudgetIntervalType::Quarter => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
212
+                BudgetIntervalType::Year => (string) $start->year, // Example: 2024, 2025
214
                 default => '',
213
                 default => '',
215
             };
214
             };
216
 
215
 
220
                 ->required();
219
                 ->required();
221
 
220
 
222
             // Move to the next period
221
             // Move to the next period
223
-            match ($intervalType) {
224
-                'month' => $start->addMonth(),
225
-                'quarter' => $start->addQuarter(),
226
-                'year' => $start->addYear(),
222
+            match ($intervalTypeEnum) {
223
+                BudgetIntervalType::Week => $start->addWeek(),
224
+                BudgetIntervalType::Month => $start->addMonth(),
225
+                BudgetIntervalType::Quarter => $start->addQuarter(),
226
+                BudgetIntervalType::Year => $start->addYear(),
227
                 default => null,
227
                 default => null,
228
             };
228
             };
229
         }
229
         }

+ 7
- 5
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/CreateBudget.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
4
 
4
 
5
+use App\Enums\Accounting\BudgetIntervalType;
5
 use App\Filament\Company\Resources\Accounting\BudgetResource;
6
 use App\Filament\Company\Resources\Accounting\BudgetResource;
6
 use App\Models\Accounting\Budget;
7
 use App\Models\Accounting\Budget;
7
 use App\Models\Accounting\BudgetItem;
8
 use App\Models\Accounting\BudgetItem;
33
             $allocationStart = Carbon::parse($data['start_date']);
34
             $allocationStart = Carbon::parse($data['start_date']);
34
 
35
 
35
             foreach ($itemData['amounts'] as $periodLabel => $amount) {
36
             foreach ($itemData['amounts'] as $periodLabel => $amount) {
36
-                $allocationEnd = self::calculateEndDate($allocationStart, $data['interval_type']);
37
+                $allocationEnd = self::calculateEndDate($allocationStart, BudgetIntervalType::parse($data['interval_type']));
37
 
38
 
38
                 $budgetItem->allocations()->create([
39
                 $budgetItem->allocations()->create([
39
                     'period' => $periodLabel,
40
                     'period' => $periodLabel,
50
         return $budget;
51
         return $budget;
51
     }
52
     }
52
 
53
 
53
-    private static function calculateEndDate(Carbon $startDate, string $intervalType): Carbon
54
+    private static function calculateEndDate(Carbon $startDate, BudgetIntervalType $intervalType): Carbon
54
     {
55
     {
55
         return match ($intervalType) {
56
         return match ($intervalType) {
56
-            'quarter' => $startDate->copy()->addMonths(2)->endOfMonth(),
57
-            'year' => $startDate->copy()->endOfYear(),
58
-            default => $startDate->copy()->endOfMonth(),
57
+            BudgetIntervalType::Week => $startDate->copy()->endOfWeek(),
58
+            BudgetIntervalType::Month => $startDate->copy()->endOfMonth(),
59
+            BudgetIntervalType::Quarter => $startDate->copy()->endOfQuarter(),
60
+            BudgetIntervalType::Year => $startDate->copy()->endOfYear(),
59
         };
61
         };
60
     }
62
     }
61
 }
63
 }

+ 10
- 7
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/EditBudget.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
4
 
4
 
5
+use App\Enums\Accounting\BudgetIntervalType;
5
 use App\Filament\Company\Resources\Accounting\BudgetResource;
6
 use App\Filament\Company\Resources\Accounting\BudgetResource;
6
 use App\Models\Accounting\Budget;
7
 use App\Models\Accounting\Budget;
8
+use App\Models\Accounting\BudgetAllocation;
7
 use App\Models\Accounting\BudgetItem;
9
 use App\Models\Accounting\BudgetItem;
8
 use Filament\Actions;
10
 use Filament\Actions;
9
 use Filament\Resources\Pages\EditRecord;
11
 use Filament\Resources\Pages\EditRecord;
27
         /** @var Budget $budget */
29
         /** @var Budget $budget */
28
         $budget = $this->record;
30
         $budget = $this->record;
29
 
31
 
30
-        $data['budgetItems'] = $budget->budgetItems->map(function ($budgetItem) {
32
+        $data['budgetItems'] = $budget->budgetItems->map(function (BudgetItem $budgetItem) {
31
             return [
33
             return [
32
                 'id' => $budgetItem->id,
34
                 'id' => $budgetItem->id,
33
                 'account_id' => $budgetItem->account_id,
35
                 'account_id' => $budgetItem->account_id,
34
                 'total_amount' => $budgetItem->allocations->sum('amount'), // Calculate total dynamically
36
                 'total_amount' => $budgetItem->allocations->sum('amount'), // Calculate total dynamically
35
-                'amounts' => $budgetItem->allocations->mapWithKeys(function ($allocation) {
37
+                'amounts' => $budgetItem->allocations->mapWithKeys(static function (BudgetAllocation $allocation) {
36
                     return [$allocation->period => $allocation->amount]; // Use the correct period label
38
                     return [$allocation->period => $allocation->amount]; // Use the correct period label
37
                 })->toArray(),
39
                 })->toArray(),
38
             ];
40
             ];
70
             $allocationStart = Carbon::parse($data['start_date']);
72
             $allocationStart = Carbon::parse($data['start_date']);
71
 
73
 
72
             foreach ($itemData['amounts'] as $periodLabel => $amount) {
74
             foreach ($itemData['amounts'] as $periodLabel => $amount) {
73
-                $allocationEnd = self::calculateEndDate($allocationStart, $data['interval_type']);
75
+                $allocationEnd = self::calculateEndDate($allocationStart, BudgetIntervalType::parse($data['interval_type']));
74
 
76
 
75
                 // Recreate allocations
77
                 // Recreate allocations
76
                 $budgetItem->allocations()->create([
78
                 $budgetItem->allocations()->create([
90
         return $budget;
92
         return $budget;
91
     }
93
     }
92
 
94
 
93
-    private static function calculateEndDate(Carbon $startDate, string $intervalType): Carbon
95
+    private static function calculateEndDate(Carbon $startDate, BudgetIntervalType $intervalType): Carbon
94
     {
96
     {
95
         return match ($intervalType) {
97
         return match ($intervalType) {
96
-            'quarter' => $startDate->copy()->addMonths(2)->endOfMonth(),
97
-            'year' => $startDate->copy()->endOfYear(),
98
-            default => $startDate->copy()->endOfMonth(),
98
+            BudgetIntervalType::Week => $startDate->copy()->endOfWeek(),
99
+            BudgetIntervalType::Month => $startDate->copy()->endOfMonth(),
100
+            BudgetIntervalType::Quarter => $startDate->copy()->endOfQuarter(),
101
+            BudgetIntervalType::Year => $startDate->copy()->endOfYear(),
99
         };
102
         };
100
     }
103
     }
101
 }
104
 }

+ 19
- 0
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/ViewBudget.php 查看文件

4
 
4
 
5
 use App\Filament\Company\Resources\Accounting\BudgetResource;
5
 use App\Filament\Company\Resources\Accounting\BudgetResource;
6
 use Filament\Actions;
6
 use Filament\Actions;
7
+use Filament\Forms\Form;
8
+use Filament\Infolists\Infolist;
7
 use Filament\Resources\Pages\ViewRecord;
9
 use Filament\Resources\Pages\ViewRecord;
8
 
10
 
9
 class ViewBudget extends ViewRecord
11
 class ViewBudget extends ViewRecord
16
             Actions\EditAction::make(),
18
             Actions\EditAction::make(),
17
         ];
19
         ];
18
     }
20
     }
21
+
22
+    public function getRelationManagers(): array
23
+    {
24
+        return [
25
+            BudgetResource\RelationManagers\BudgetItemsRelationManager::class,
26
+        ];
27
+    }
28
+
29
+    public function form(Form $form): Form
30
+    {
31
+        return $form->schema([]);
32
+    }
33
+
34
+    public function infolist(Infolist $infolist): Infolist
35
+    {
36
+        return $infolist->schema([]);
37
+    }
19
 }
38
 }

+ 48
- 0
app/Filament/Company/Resources/Accounting/BudgetResource/RelationManagers/BudgetItemsRelationManager.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
4
+
5
+use Filament\Resources\RelationManagers\RelationManager;
6
+use Filament\Tables;
7
+use Filament\Tables\Table;
8
+
9
+class BudgetItemsRelationManager extends RelationManager
10
+{
11
+    protected static string $relationship = 'budgetItems';
12
+
13
+    protected static bool $isLazy = false;
14
+
15
+    public function table(Table $table): Table
16
+    {
17
+        return $table
18
+            ->recordTitleAttribute('account_id')
19
+            ->columns([
20
+                Tables\Columns\TextColumn::make('account.name')
21
+                    ->label('Account')
22
+                    ->sortable()
23
+                    ->searchable(),
24
+
25
+                Tables\Columns\TextColumn::make('allocations_sum_amount')
26
+                    ->label('Total Allocations')
27
+                    ->sortable()
28
+                    ->alignEnd()
29
+                    ->sum('allocations', 'amount')
30
+                    ->money(divideBy: 100),
31
+            ])
32
+            ->filters([
33
+                //
34
+            ])
35
+            ->headerActions([
36
+                // Tables\Actions\CreateAction::make(),
37
+            ])
38
+            ->actions([
39
+                // Tables\Actions\EditAction::make(),
40
+                // Tables\Actions\DeleteAction::make(),
41
+            ])
42
+            ->bulkActions([
43
+                //                Tables\Actions\BulkActionGroup::make([
44
+                //                    Tables\Actions\DeleteBulkAction::make(),
45
+                //                ]),
46
+            ]);
47
+    }
48
+}

+ 2
- 0
app/Models/Accounting/Budget.php 查看文件

4
 
4
 
5
 use App\Concerns\Blamable;
5
 use App\Concerns\Blamable;
6
 use App\Concerns\CompanyOwned;
6
 use App\Concerns\CompanyOwned;
7
+use App\Enums\Accounting\BudgetIntervalType;
7
 use App\Enums\Accounting\BudgetStatus;
8
 use App\Enums\Accounting\BudgetStatus;
8
 use App\Filament\Company\Resources\Accounting\BudgetResource;
9
 use App\Filament\Company\Resources\Accounting\BudgetResource;
9
 use Filament\Actions\Action;
10
 use Filament\Actions\Action;
41
         'start_date' => 'date',
42
         'start_date' => 'date',
42
         'end_date' => 'date',
43
         'end_date' => 'date',
43
         'status' => BudgetStatus::class,
44
         'status' => BudgetStatus::class,
45
+        'interval_type' => BudgetIntervalType::class,
44
         'approved_at' => 'datetime',
46
         'approved_at' => 'datetime',
45
         'closed_at' => 'datetime',
47
         'closed_at' => 'datetime',
46
     ];
48
     ];

Loading…
取消
儲存