Andrew Wallo 6 månader sedan
förälder
incheckning
6052033f98

+ 2
- 2
app/Filament/Company/Resources/Accounting/BudgetResource.php Visa fil

@@ -292,9 +292,9 @@ class BudgetResource extends Resource
292 292
                                                 // Calculate the total for this budget item across all periods
293 293
                                                 foreach ($periods as $period) {
294 294
                                                     $allocation = $record->allocations->firstWhere('period', $period);
295
-                                                    $total += $allocation ? $allocation->amount : 0;
295
+                                                    $total += $allocation ? $allocation->getRawOriginal('amount') : 0;
296 296
                                                 }
297
-                                                $component->state($total);
297
+                                                $component->state(CurrencyConverter::convertCentsToFormatSimple($total));
298 298
                                             })
299 299
                                             ->dehydrated(false),
300 300
 

+ 6
- 0
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/ViewBudget.php Visa fil

@@ -7,11 +7,17 @@ use Filament\Actions;
7 7
 use Filament\Forms\Form;
8 8
 use Filament\Infolists\Infolist;
9 9
 use Filament\Resources\Pages\ViewRecord;
10
+use Filament\Support\Enums\MaxWidth;
10 11
 
11 12
 class ViewBudget extends ViewRecord
12 13
 {
13 14
     protected static string $resource = BudgetResource::class;
14 15
 
16
+    public function getMaxContentWidth(): MaxWidth | string | null
17
+    {
18
+        return '8xl';
19
+    }
20
+
15 21
     protected function getHeaderActions(): array
16 22
     {
17 23
         return [

+ 174
- 8
app/Filament/Company/Resources/Accounting/BudgetResource/RelationManagers/BudgetItemsRelationManager.php Visa fil

@@ -5,11 +5,19 @@ namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManag
5 5
 use App\Filament\Tables\Columns\DeferredTextInputColumn;
6 6
 use App\Models\Accounting\BudgetAllocation;
7 7
 use App\Models\Accounting\BudgetItem;
8
+use App\Utilities\Currency\CurrencyConverter;
8 9
 use Filament\Notifications\Notification;
9 10
 use Filament\Resources\RelationManagers\RelationManager;
11
+use Filament\Support\RawJs;
10 12
 use Filament\Tables\Actions\Action;
13
+use Filament\Tables\Actions\BulkAction;
14
+use Filament\Tables\Columns\IconColumn;
15
+use Filament\Tables\Columns\Summarizers\Summarizer;
11 16
 use Filament\Tables\Columns\TextColumn;
17
+use Filament\Tables\Grouping\Group;
12 18
 use Filament\Tables\Table;
19
+use Illuminate\Database\Eloquent\Builder;
20
+use Illuminate\Database\Eloquent\Collection;
13 21
 use Livewire\Attributes\On;
14 22
 
15 23
 class BudgetItemsRelationManager extends RelationManager
@@ -21,12 +29,9 @@ class BudgetItemsRelationManager extends RelationManager
21 29
     // Store changes that are pending
22 30
     public array $batchChanges = [];
23 31
 
24
-    // Listen for events from the custom column
25
-
26 32
     #[On('batch-column-changed')]
27 33
     public function handleBatchColumnChanged($data): void
28 34
     {
29
-        ray($data);
30 35
         // Store the changed value
31 36
         $key = "{$data['recordKey']}.{$data['name']}";
32 37
         $this->batchChanges[$key] = $data['value'];
@@ -35,7 +40,6 @@ class BudgetItemsRelationManager extends RelationManager
35 40
     #[On('save-batch-changes')]
36 41
     public function saveBatchChanges(): void
37 42
     {
38
-        ray('Saving batch changes');
39 43
         foreach ($this->batchChanges as $key => $value) {
40 44
             // Parse the composite key
41 45
             [$recordKey, $column] = explode('.', $key, 2);
@@ -77,6 +81,54 @@ class BudgetItemsRelationManager extends RelationManager
77 81
             ->send();
78 82
     }
79 83
 
84
+    protected function calculateTotalSum(array $budgetItemIds): int
85
+    {
86
+        // Get all applicable periods
87
+        $periods = BudgetAllocation::whereIn('budget_item_id', $budgetItemIds)
88
+            ->pluck('period')
89
+            ->unique()
90
+            ->values()
91
+            ->toArray();
92
+
93
+        $total = 0;
94
+
95
+        // Sum up each period
96
+        foreach ($periods as $period) {
97
+            $total += $this->calculatePeriodSum($budgetItemIds, $period);
98
+        }
99
+
100
+        return $total;
101
+    }
102
+
103
+    protected function calculatePeriodSum(array $budgetItemIds, string $period): int
104
+    {
105
+        // First get database values
106
+        $dbTotal = BudgetAllocation::whereIn('budget_item_id', $budgetItemIds)
107
+            ->where('period', $period)
108
+            ->sum('amount');
109
+
110
+        // Now add any batch changes
111
+        $batchTotal = 0;
112
+        foreach ($budgetItemIds as $itemId) {
113
+            $key = "{$itemId}.allocations_by_period.{$period}";
114
+            if (isset($this->batchChanges[$key])) {
115
+                // Get the current value from batch changes
116
+                $batchValue = CurrencyConverter::convertToCents($this->batchChanges[$key]);
117
+
118
+                // Find if there's a current allocation in DB
119
+                $existingAmount = BudgetAllocation::where('budget_item_id', $itemId)
120
+                    ->where('period', $period)
121
+                    ->first()
122
+                    ->getRawOriginal('amount');
123
+
124
+                // Add the difference to our running total
125
+                $batchTotal += ($batchValue - $existingAmount);
126
+            }
127
+        }
128
+
129
+        return $dbTotal + $batchTotal;
130
+    }
131
+
80 132
     public function table(Table $table): Table
81 133
     {
82 134
         $budget = $this->getOwnerRecord();
@@ -91,13 +143,11 @@ class BudgetItemsRelationManager extends RelationManager
91 143
             ->values()
92 144
             ->toArray();
93 145
 
94
-        ray($this->batchChanges);
95
-
96 146
         return $table
97 147
             ->recordTitleAttribute('account_id')
98 148
             ->paginated(false)
99 149
             ->modifyQueryUsing(
100
-                fn ($query) => $query->with(['account', 'allocations'])
150
+                fn (Builder $query) => $query->with(['account', 'allocations'])
101 151
             )
102 152
             ->headerActions([
103 153
                 Action::make('saveBatchChanges')
@@ -106,14 +156,130 @@ class BudgetItemsRelationManager extends RelationManager
106 156
                     ->color('primary')
107 157
                     ->icon('heroicon-o-check-circle'),
108 158
             ])
159
+            ->groups([
160
+                Group::make('account.category')
161
+                    ->titlePrefixedWithLabel(false)
162
+                    ->collapsible(),
163
+            ])
164
+            ->defaultGroup('account.category')
165
+            ->bulkActions([
166
+                BulkAction::make('clearAllocations')
167
+                    ->label('Clear Allocations')
168
+                    ->icon('heroicon-o-trash')
169
+                    ->color('danger')
170
+                    ->requiresConfirmation()
171
+                    ->deselectRecordsAfterCompletion()
172
+                    ->action(function (Collection $records) use ($periods) {
173
+                        foreach ($records as $record) {
174
+                            foreach ($periods as $period) {
175
+                                $periodKey = "{$record->getKey()}.allocations_by_period.{$period}";
176
+                                $this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
177
+                            }
178
+
179
+                            $totalKey = "{$record->getKey()}.total";
180
+                            $this->batchChanges[$totalKey] = CurrencyConverter::convertCentsToFormatSimple(0);
181
+                        }
182
+                    }),
183
+            ])
109 184
             ->columns(array_merge([
110 185
                 TextColumn::make('account.name')
111 186
                     ->label('Accounts')
112
-                    ->sortable()
187
+                    ->limit(30)
113 188
                     ->searchable(),
189
+                DeferredTextInputColumn::make('total')
190
+                    ->label('Total')
191
+                    ->alignRight()
192
+                    ->mask(RawJs::make('$money($input)'))
193
+                    ->getStateUsing(function (BudgetItem $record, DeferredTextInputColumn $column) {
194
+                        if (isset($this->batchChanges["{$record->getKey()}.{$column->getName()}"])) {
195
+                            return $this->batchChanges["{$record->getKey()}.{$column->getName()}"];
196
+                        }
197
+
198
+                        $total = $record->allocations->sum(function (BudgetAllocation $budgetAllocation) {
199
+                            return $budgetAllocation->getRawOriginal('amount');
200
+                        });
201
+
202
+                        return CurrencyConverter::convertCentsToFormatSimple($total);
203
+                    })
204
+                    ->batchMode()
205
+                    ->summarize(
206
+                        Summarizer::make()
207
+                            ->using(function (\Illuminate\Database\Query\Builder $query) {
208
+                                $budgetItemIds = $query->pluck('id')->toArray();
209
+                                $total = $this->calculateTotalSum($budgetItemIds);
210
+
211
+                                return CurrencyConverter::convertCentsToFormatSimple($total);
212
+                            })
213
+                    ),
214
+                IconColumn::make('disperseAction')
215
+                    ->icon('heroicon-m-chevron-double-right')
216
+                    ->color('primary')
217
+                    ->label('')
218
+                    ->default('')
219
+                    ->action(
220
+                        Action::make('disperse')
221
+                            ->label('Disperse')
222
+                            ->icon('heroicon-m-chevron-double-right')
223
+                            ->color('primary')
224
+                            ->iconButton()
225
+                            ->action(function (BudgetItem $record) use ($periods) {
226
+                                if (empty($periods)) {
227
+                                    return;
228
+                                }
229
+
230
+                                $totalKey = "{$record->getKey()}.total";
231
+                                $totalAmount = $this->batchChanges[$totalKey] ?? null;
232
+
233
+                                if (isset($totalAmount)) {
234
+                                    $totalCents = CurrencyConverter::convertToCents($totalAmount);
235
+                                } else {
236
+                                    $totalCents = $record->allocations->sum(function (BudgetAllocation $budgetAllocation) {
237
+                                        return $budgetAllocation->getRawOriginal('amount');
238
+                                    });
239
+                                }
240
+
241
+                                $numPeriods = count($periods);
242
+
243
+                                if ($numPeriods === 0) {
244
+                                    return;
245
+                                }
246
+
247
+                                if ($totalCents <= 0) {
248
+                                    foreach ($periods as $period) {
249
+                                        $periodKey = "{$record->getKey()}.allocations_by_period.{$period}";
250
+                                        $this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
251
+                                    }
252
+
253
+                                    return;
254
+                                }
255
+
256
+                                $baseAmount = floor($totalCents / $numPeriods);
257
+                                $remainder = $totalCents - ($baseAmount * $numPeriods);
258
+
259
+                                foreach ($periods as $index => $period) {
260
+                                    $amount = $baseAmount + ($index === 0 ? $remainder : 0);
261
+                                    $formattedAmount = CurrencyConverter::convertCentsToFormatSimple($amount);
262
+
263
+                                    $periodKey = "{$record->getKey()}.allocations_by_period.{$period}";
264
+                                    $this->batchChanges[$periodKey] = $formattedAmount;
265
+                                }
266
+                            }),
267
+                    ),
114 268
             ], collect($periods)->map(
115 269
                 fn ($period) => DeferredTextInputColumn::make("allocations_by_period.{$period}")
116 270
                     ->label($period)
271
+                    ->alignRight()
272
+                    ->mask(RawJs::make('$money($input)'))
273
+                    ->summarize(
274
+                        Summarizer::make()
275
+                            ->label($period)
276
+                            ->using(function (\Illuminate\Database\Query\Builder $query) use ($period) {
277
+                                $budgetItemIds = $query->pluck('id')->toArray();
278
+                                $total = $this->calculatePeriodSum($budgetItemIds, $period);
279
+
280
+                                return CurrencyConverter::convertCentsToFormatSimple($total);
281
+                            })
282
+                    )
117 283
                     ->getStateUsing(function ($record, DeferredTextInputColumn $column) use ($period) {
118 284
                         $key = "{$record->getKey()}.{$column->getName()}";
119 285
 

+ 2
- 2
app/Models/Accounting/BudgetAllocation.php Visa fil

@@ -2,7 +2,7 @@
2 2
 
3 3
 namespace App\Models\Accounting;
4 4
 
5
-use App\Casts\DocumentMoneyCast;
5
+use App\Casts\MoneyCast;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Accounting\BudgetIntervalType;
8 8
 use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -28,7 +28,7 @@ class BudgetAllocation extends Model
28 28
         'start_date' => 'date',
29 29
         'end_date' => 'date',
30 30
         'interval_type' => BudgetIntervalType::class,
31
-        'amount' => DocumentMoneyCast::class,
31
+        'amount' => MoneyCast::class,
32 32
     ];
33 33
 
34 34
     public function budgetItem(): BelongsTo

+ 12
- 12
composer.lock Visa fil

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.342.11",
500
+            "version": "3.342.12",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "e0afed3d0e2c89362f6c9b6bf8f278b04a4858b6"
504
+                "reference": "29c2c34ddc7ff02ec865355fc235c78acd095fcf"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e0afed3d0e2c89362f6c9b6bf8f278b04a4858b6",
509
-                "reference": "e0afed3d0e2c89362f6c9b6bf8f278b04a4858b6",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/29c2c34ddc7ff02ec865355fc235c78acd095fcf",
509
+                "reference": "29c2c34ddc7ff02ec865355fc235c78acd095fcf",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -588,9 +588,9 @@
588 588
             "support": {
589 589
                 "forum": "https://github.com/aws/aws-sdk-php/discussions",
590 590
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
591
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.342.11"
591
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.342.12"
592 592
             },
593
-            "time": "2025-03-21T18:11:10+00:00"
593
+            "time": "2025-03-24T18:06:44+00:00"
594 594
         },
595 595
         {
596 596
             "name": "aws/aws-sdk-php-laravel",
@@ -4519,16 +4519,16 @@
4519 4519
         },
4520 4520
         {
4521 4521
             "name": "monolog/monolog",
4522
-            "version": "3.8.1",
4522
+            "version": "3.9.0",
4523 4523
             "source": {
4524 4524
                 "type": "git",
4525 4525
                 "url": "https://github.com/Seldaek/monolog.git",
4526
-                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4"
4526
+                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
4527 4527
             },
4528 4528
             "dist": {
4529 4529
                 "type": "zip",
4530
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
4531
-                "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4",
4530
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
4531
+                "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
4532 4532
                 "shasum": ""
4533 4533
             },
4534 4534
             "require": {
@@ -4606,7 +4606,7 @@
4606 4606
             ],
4607 4607
             "support": {
4608 4608
                 "issues": "https://github.com/Seldaek/monolog/issues",
4609
-                "source": "https://github.com/Seldaek/monolog/tree/3.8.1"
4609
+                "source": "https://github.com/Seldaek/monolog/tree/3.9.0"
4610 4610
             },
4611 4611
             "funding": [
4612 4612
                 {
@@ -4618,7 +4618,7 @@
4618 4618
                     "type": "tidelift"
4619 4619
                 }
4620 4620
             ],
4621
-            "time": "2024-12-05T17:15:07+00:00"
4621
+            "time": "2025-03-24T10:02:05+00:00"
4622 4622
         },
4623 4623
         {
4624 4624
             "name": "mtdowling/jmespath.php",

+ 1
- 5
resources/css/filament/company/theme.css Visa fil

@@ -28,11 +28,7 @@
28 28
 
29 29
 /* End Alignment sortable column enhancement */
30 30
 .fi-ta-header-cell:has(button.justify-end > svg.fi-ta-header-cell-sort-icon) {
31
-    padding-right: 0.25rem;
32
-}
33
-
34
-.fi-ta-cell:has(.fi-ta-col-wrp > .justify-end) {
35
-    padding-right: 1rem;
31
+    padding-right: 0;
36 32
 }
37 33
 
38 34
 :not(.dark) .fi-body {

+ 1
- 1
resources/views/filament/tables/columns/deferred-text-input-column.blade.php Visa fil

@@ -66,7 +66,7 @@
66 66
         $attributes
67 67
             ->merge($getExtraAttributes(), escape: false)
68 68
             ->class([
69
-                'fi-ta-text-input w-full min-w-48',
69
+                'fi-ta-text-input w-full min-w-36',
70 70
                 'px-3 py-4' => ! $isInline(),
71 71
             ])
72 72
     }}

Laddar…
Avbryt
Spara