|
@@ -19,7 +19,9 @@ use Filament\Tables\Grouping\Group;
|
19
|
19
|
use Filament\Tables\Table;
|
20
|
20
|
use Illuminate\Database\Eloquent\Builder;
|
21
|
21
|
use Illuminate\Database\Eloquent\Collection;
|
22
|
|
-use Illuminate\Support\Str;
|
|
22
|
+use Illuminate\Support\Carbon;
|
|
23
|
+use Illuminate\Support\Facades\DB;
|
|
24
|
+use stdClass;
|
23
|
25
|
|
24
|
26
|
class BudgetItemsRelationManager extends RelationManager
|
25
|
27
|
{
|
|
@@ -27,8 +29,6 @@ class BudgetItemsRelationManager extends RelationManager
|
27
|
29
|
|
28
|
30
|
protected static bool $isLazy = false;
|
29
|
31
|
|
30
|
|
- protected const AMOUNT_PREFIX = 'amount_';
|
31
|
|
-
|
32
|
32
|
protected const TOTAL_COLUMN = 'total';
|
33
|
33
|
|
34
|
34
|
public array $batchChanges = [];
|
|
@@ -36,9 +36,13 @@ class BudgetItemsRelationManager extends RelationManager
|
36
|
36
|
/**
|
37
|
37
|
* Generate a consistent key for the budget item and period
|
38
|
38
|
*/
|
39
|
|
- protected static function generatePeriodKey(int $recordId, string $period): string
|
|
39
|
+ protected static function generatePeriodKey(int $recordId, string | Carbon $startDate): string
|
40
|
40
|
{
|
41
|
|
- return "{$recordId}." . self::AMOUNT_PREFIX . Str::snake($period);
|
|
41
|
+ $formattedDate = $startDate instanceof Carbon
|
|
42
|
+ ? $startDate->format('Y_m_d')
|
|
43
|
+ : Carbon::parse($startDate)->format('Y_m_d');
|
|
44
|
+
|
|
45
|
+ return "{$recordId}.{$formattedDate}";
|
42
|
46
|
}
|
43
|
47
|
|
44
|
48
|
/**
|
|
@@ -49,18 +53,6 @@ class BudgetItemsRelationManager extends RelationManager
|
49
|
53
|
return "{$recordId}." . self::TOTAL_COLUMN;
|
50
|
54
|
}
|
51
|
55
|
|
52
|
|
- /**
|
53
|
|
- * Extract the period from a column name
|
54
|
|
- */
|
55
|
|
- protected static function extractPeriodFromColumn(string $columnName): ?string
|
56
|
|
- {
|
57
|
|
- if (preg_match('/' . self::AMOUNT_PREFIX . '(.+)/', $columnName, $matches)) {
|
58
|
|
- return str_replace('_', ' ', $matches[1] ?? '');
|
59
|
|
- }
|
60
|
|
-
|
61
|
|
- return null;
|
62
|
|
- }
|
63
|
|
-
|
64
|
56
|
public function handleBatchColumnChanged($data): void
|
65
|
57
|
{
|
66
|
58
|
$key = "{$data['recordKey']}.{$data['name']}";
|
|
@@ -72,8 +64,9 @@ class BudgetItemsRelationManager extends RelationManager
|
72
|
64
|
foreach ($this->batchChanges as $key => $value) {
|
73
|
65
|
[$recordKey, $column] = explode('.', $key, 2);
|
74
|
66
|
|
75
|
|
- $period = self::extractPeriodFromColumn($column);
|
76
|
|
- if (! $period) {
|
|
67
|
+ try {
|
|
68
|
+ $startDate = Carbon::createFromFormat('Y_m_d', $column);
|
|
69
|
+ } catch (\Exception) {
|
77
|
70
|
continue;
|
78
|
71
|
}
|
79
|
72
|
|
|
@@ -82,15 +75,11 @@ class BudgetItemsRelationManager extends RelationManager
|
82
|
75
|
continue;
|
83
|
76
|
}
|
84
|
77
|
|
85
|
|
- $allocation = $record->allocations->firstWhere('period', $period);
|
86
|
|
- if ($allocation) {
|
87
|
|
- $allocation->update(['amount' => $value]);
|
88
|
|
- } else {
|
89
|
|
- $record->allocations()->create([
|
90
|
|
- 'period' => $period,
|
91
|
|
- 'amount' => $value,
|
92
|
|
- ]);
|
93
|
|
- }
|
|
78
|
+ $allocation = $record->allocations()
|
|
79
|
+ ->whereDate('start_date', $startDate)
|
|
80
|
+ ->first();
|
|
81
|
+
|
|
82
|
+ $allocation?->update(['amount' => $value]);
|
94
|
83
|
}
|
95
|
84
|
|
96
|
85
|
$this->batchChanges = [];
|
|
@@ -101,37 +90,23 @@ class BudgetItemsRelationManager extends RelationManager
|
101
|
90
|
->send();
|
102
|
91
|
}
|
103
|
92
|
|
104
|
|
- protected function calculateTotalSum(array $budgetItemIds): int
|
|
93
|
+ protected function calculatePeriodSum(array $budgetItemIds, string | Carbon $startDate): int
|
105
|
94
|
{
|
106
|
|
- $periods = BudgetAllocation::whereIn('budget_item_id', $budgetItemIds)
|
107
|
|
- ->pluck('period')
|
108
|
|
- ->unique()
|
109
|
|
- ->values()
|
110
|
|
- ->toArray();
|
111
|
|
-
|
112
|
|
- $total = 0;
|
113
|
|
- foreach ($periods as $period) {
|
114
|
|
- $total += $this->calculatePeriodSum($budgetItemIds, $period);
|
115
|
|
- }
|
|
95
|
+ $allocations = DB::table('budget_allocations')
|
|
96
|
+ ->whereIn('budget_item_id', $budgetItemIds)
|
|
97
|
+ ->whereDate('start_date', $startDate)
|
|
98
|
+ ->pluck('amount', 'budget_item_id');
|
116
|
99
|
|
117
|
|
- return $total;
|
118
|
|
- }
|
119
|
|
-
|
120
|
|
- protected function calculatePeriodSum(array $budgetItemIds, string $period): int
|
121
|
|
- {
|
122
|
|
- $dbTotal = BudgetAllocation::whereIn('budget_item_id', $budgetItemIds)
|
123
|
|
- ->where('period', $period)
|
124
|
|
- ->sum('amount');
|
|
100
|
+ $dbTotal = $allocations->sum();
|
125
|
101
|
|
126
|
102
|
$batchTotal = 0;
|
|
103
|
+
|
127
|
104
|
foreach ($budgetItemIds as $itemId) {
|
128
|
|
- $key = self::generatePeriodKey($itemId, $period);
|
|
105
|
+ $key = self::generatePeriodKey($itemId, $startDate);
|
|
106
|
+
|
129
|
107
|
if (isset($this->batchChanges[$key])) {
|
130
|
108
|
$batchValue = CurrencyConverter::convertToCents($this->batchChanges[$key]);
|
131
|
|
- $existingAmount = BudgetAllocation::where('budget_item_id', $itemId)
|
132
|
|
- ->where('period', $period)
|
133
|
|
- ->first()
|
134
|
|
- ?->getRawOriginal('amount') ?? 0;
|
|
109
|
+ $existingAmount = $allocations[$itemId] ?? 0;
|
135
|
110
|
|
136
|
111
|
$batchTotal += ($batchValue - $existingAmount);
|
137
|
112
|
}
|
|
@@ -144,21 +119,21 @@ class BudgetItemsRelationManager extends RelationManager
|
144
|
119
|
{
|
145
|
120
|
/** @var Budget $budget */
|
146
|
121
|
$budget = $this->getOwnerRecord();
|
147
|
|
- $periods = $budget->getPeriods();
|
|
122
|
+ $allocationPeriods = $budget->getPeriods();
|
148
|
123
|
|
149
|
124
|
return $table
|
150
|
125
|
->recordTitleAttribute('account_id')
|
151
|
126
|
->paginated(false)
|
152
|
127
|
->heading(null)
|
153
|
|
- ->modifyQueryUsing(function (Builder $query) use ($periods) {
|
|
128
|
+ ->modifyQueryUsing(function (Builder $query) use ($allocationPeriods) {
|
154
|
129
|
$query->select('budget_items.*')
|
155
|
130
|
->leftJoin('budget_allocations', 'budget_allocations.budget_item_id', '=', 'budget_items.id');
|
156
|
131
|
|
157
|
|
- foreach ($periods as $period) {
|
158
|
|
- $alias = self::AMOUNT_PREFIX . Str::snake($period);
|
|
132
|
+ foreach ($allocationPeriods as $period) {
|
|
133
|
+ $alias = $period->start_date->format('Y_m_d');
|
159
|
134
|
$query->selectRaw(
|
160
|
|
- "SUM(CASE WHEN budget_allocations.period = ? THEN budget_allocations.amount ELSE 0 END) as {$alias}",
|
161
|
|
- [$period]
|
|
135
|
+ "SUM(CASE WHEN budget_allocations.start_date = ? THEN budget_allocations.amount ELSE 0 END) as {$alias}",
|
|
136
|
+ [$period->start_date->toDateString()]
|
162
|
137
|
);
|
163
|
138
|
}
|
164
|
139
|
|
|
@@ -202,8 +177,32 @@ class BudgetItemsRelationManager extends RelationManager
|
202
|
177
|
->summarize(
|
203
|
178
|
Summarizer::make()
|
204
|
179
|
->using(function (\Illuminate\Database\Query\Builder $query) {
|
205
|
|
- $budgetItemIds = $query->pluck('id')->toArray();
|
206
|
|
- $total = $this->calculateTotalSum($budgetItemIds);
|
|
180
|
+ $allocations = $query
|
|
181
|
+ ->leftJoin('budget_allocations', 'budget_allocations.budget_item_id', '=', 'budget_items.id')
|
|
182
|
+ ->select('budget_allocations.budget_item_id', 'budget_allocations.start_date', 'budget_allocations.amount')
|
|
183
|
+ ->get();
|
|
184
|
+
|
|
185
|
+ $allocationsByDate = $allocations->groupBy('start_date');
|
|
186
|
+
|
|
187
|
+ $total = 0;
|
|
188
|
+
|
|
189
|
+ /** @var \Illuminate\Support\Collection<string, \Illuminate\Support\Collection<int, stdClass>> $allocationsByDate */
|
|
190
|
+ foreach ($allocationsByDate as $startDate => $group) {
|
|
191
|
+ $dbTotal = $group->sum('amount');
|
|
192
|
+ $amounts = $group->pluck('amount', 'budget_item_id');
|
|
193
|
+ $batchTotal = 0;
|
|
194
|
+
|
|
195
|
+ foreach ($amounts as $itemId => $existingAmount) {
|
|
196
|
+ $key = self::generatePeriodKey($itemId, $startDate);
|
|
197
|
+
|
|
198
|
+ if (isset($this->batchChanges[$key])) {
|
|
199
|
+ $batchValue = CurrencyConverter::convertToCents($this->batchChanges[$key]);
|
|
200
|
+ $batchTotal += ($batchValue - $existingAmount);
|
|
201
|
+ }
|
|
202
|
+ }
|
|
203
|
+
|
|
204
|
+ $total += $dbTotal + $batchTotal;
|
|
205
|
+ }
|
207
|
206
|
|
208
|
207
|
return CurrencyConverter::convertCentsToFormatSimple($total);
|
209
|
208
|
})
|
|
@@ -217,8 +216,8 @@ class BudgetItemsRelationManager extends RelationManager
|
217
|
216
|
->action(
|
218
|
217
|
Action::make('disperse')
|
219
|
218
|
->label('Disperse')
|
220
|
|
- ->action(function (BudgetItem $record) use ($periods) {
|
221
|
|
- if (empty($periods)) {
|
|
219
|
+ ->action(function (BudgetItem $record) use ($allocationPeriods) {
|
|
220
|
+ if (empty($allocationPeriods)) {
|
222
|
221
|
return;
|
223
|
222
|
}
|
224
|
223
|
|
|
@@ -233,38 +232,34 @@ class BudgetItemsRelationManager extends RelationManager
|
233
|
232
|
});
|
234
|
233
|
}
|
235
|
234
|
|
236
|
|
- $numPeriods = count($periods);
|
237
|
|
-
|
238
|
|
- if ($numPeriods === 0) {
|
239
|
|
- return;
|
240
|
|
- }
|
241
|
|
-
|
242
|
235
|
if ($totalCents <= 0) {
|
243
|
|
- foreach ($periods as $period) {
|
244
|
|
- $periodKey = self::generatePeriodKey($record->getKey(), $period);
|
|
236
|
+ foreach ($allocationPeriods as $period) {
|
|
237
|
+ $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
|
245
|
238
|
$this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
|
246
|
239
|
}
|
247
|
240
|
|
248
|
241
|
return;
|
249
|
242
|
}
|
250
|
243
|
|
|
244
|
+ $numPeriods = count($allocationPeriods);
|
|
245
|
+
|
251
|
246
|
$baseAmount = floor($totalCents / $numPeriods);
|
252
|
247
|
$remainder = $totalCents - ($baseAmount * $numPeriods);
|
253
|
248
|
|
254
|
|
- foreach ($periods as $index => $period) {
|
|
249
|
+ foreach ($allocationPeriods as $index => $period) {
|
255
|
250
|
$amount = $baseAmount + ($index === 0 ? $remainder : 0);
|
256
|
251
|
$formattedAmount = CurrencyConverter::convertCentsToFormatSimple($amount);
|
257
|
252
|
|
258
|
|
- $periodKey = self::generatePeriodKey($record->getKey(), $period);
|
|
253
|
+ $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
|
259
|
254
|
$this->batchChanges[$periodKey] = $formattedAmount;
|
260
|
255
|
}
|
261
|
256
|
}),
|
262
|
257
|
),
|
263
|
|
- ...array_map(function (string $period) {
|
264
|
|
- $alias = self::AMOUNT_PREFIX . Str::snake($period);
|
|
258
|
+ ...$allocationPeriods->map(function (BudgetAllocation $period) {
|
|
259
|
+ $alias = $period->start_date->format('Y_m_d');
|
265
|
260
|
|
266
|
261
|
return DeferredTextInputColumn::make($alias)
|
267
|
|
- ->label($period)
|
|
262
|
+ ->label($period->period)
|
268
|
263
|
->alignRight()
|
269
|
264
|
->batchMode()
|
270
|
265
|
->mask(RawJs::make('$money($input)'))
|
|
@@ -277,12 +272,12 @@ class BudgetItemsRelationManager extends RelationManager
|
277
|
272
|
Summarizer::make()
|
278
|
273
|
->using(function (\Illuminate\Database\Query\Builder $query) use ($period) {
|
279
|
274
|
$budgetItemIds = $query->pluck('id')->toArray();
|
280
|
|
- $total = $this->calculatePeriodSum($budgetItemIds, $period);
|
|
275
|
+ $total = $this->calculatePeriodSum($budgetItemIds, $period->start_date);
|
281
|
276
|
|
282
|
277
|
return CurrencyConverter::convertCentsToFormatSimple($total);
|
283
|
278
|
})
|
284
|
279
|
);
|
285
|
|
- }, $periods),
|
|
280
|
+ })->toArray(),
|
286
|
281
|
])
|
287
|
282
|
->bulkActions([
|
288
|
283
|
BulkAction::make('clearAllocations')
|
|
@@ -291,10 +286,10 @@ class BudgetItemsRelationManager extends RelationManager
|
291
|
286
|
->color('danger')
|
292
|
287
|
->requiresConfirmation()
|
293
|
288
|
->deselectRecordsAfterCompletion()
|
294
|
|
- ->action(function (Collection $records) use ($periods) {
|
|
289
|
+ ->action(function (Collection $records) use ($allocationPeriods) {
|
295
|
290
|
foreach ($records as $record) {
|
296
|
|
- foreach ($periods as $period) {
|
297
|
|
- $periodKey = self::generatePeriodKey($record->getKey(), $period);
|
|
291
|
+ foreach ($allocationPeriods as $period) {
|
|
292
|
+ $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
|
298
|
293
|
$this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
|
299
|
294
|
}
|
300
|
295
|
}
|