Quellcode durchsuchen

wip budgets

3.x
Andrew Wallo vor 7 Monaten
Ursprung
Commit
922cdbcd7a

+ 166
- 2
app/Filament/Company/Resources/Accounting/BudgetResource.php Datei anzeigen

@@ -59,13 +59,106 @@ class BudgetResource extends Resource
59 59
 
60 60
                 Forms\Components\Section::make('Budget Items')
61 61
                     ->headerActions([
62
+                        Forms\Components\Actions\Action::make('addAccounts')
63
+                            ->label('Add Accounts')
64
+                            ->icon('heroicon-m-plus')
65
+                            ->outlined()
66
+                            ->color('primary')
67
+                            ->form(fn (Forms\Get $get) => [
68
+                                Forms\Components\Select::make('selected_accounts')
69
+                                    ->label('Choose Accounts to Add')
70
+                                    ->options(function () use ($get) {
71
+                                        $existingAccounts = collect($get('budgetItems'))->pluck('account_id')->toArray();
72
+
73
+                                        return Account::query()
74
+                                            ->budgetable()
75
+                                            ->whereNotIn('id', $existingAccounts) // Prevent duplicate selections
76
+                                            ->pluck('name', 'id');
77
+                                    })
78
+                                    ->searchable()
79
+                                    ->multiple()
80
+                                    ->hint('Select the accounts you want to add to this budget'),
81
+                            ])
82
+                            ->action(static fn (Forms\Set $set, Forms\Get $get, array $data) => self::addSelectedAccounts($set, $get, $data)),
83
+
62 84
                         Forms\Components\Actions\Action::make('addAllAccounts')
63 85
                             ->label('Add All Accounts')
64
-                            ->icon('heroicon-m-plus')
86
+                            ->icon('heroicon-m-folder-plus')
65 87
                             ->outlined()
66 88
                             ->color('primary')
67 89
                             ->action(static fn (Forms\Set $set, Forms\Get $get) => self::addAllAccounts($set, $get))
68 90
                             ->hidden(static fn (Forms\Get $get) => filled($get('budgetItems'))),
91
+
92
+                        Forms\Components\Actions\Action::make('increaseAllocations')
93
+                            ->label('Increase Allocations')
94
+                            ->icon('heroicon-m-arrow-up')
95
+                            ->outlined()
96
+                            ->color('success')
97
+                            ->form(fn (Forms\Get $get) => [
98
+                                Forms\Components\Select::make('increase_type')
99
+                                    ->label('Increase Type')
100
+                                    ->options([
101
+                                        'percentage' => 'Percentage (%)',
102
+                                        'fixed' => 'Fixed Amount',
103
+                                    ])
104
+                                    ->default('percentage')
105
+                                    ->live()
106
+                                    ->required(),
107
+
108
+                                Forms\Components\TextInput::make('percentage')
109
+                                    ->label('Increase by %')
110
+                                    ->numeric()
111
+                                    ->suffix('%')
112
+                                    ->required()
113
+                                    ->hidden(fn (Forms\Get $get) => $get('increase_type') !== 'percentage'),
114
+
115
+                                Forms\Components\TextInput::make('fixed_amount')
116
+                                    ->label('Increase by Fixed Amount')
117
+                                    ->numeric()
118
+                                    ->suffix('USD')
119
+                                    ->required()
120
+                                    ->hidden(fn (Forms\Get $get) => $get('increase_type') !== 'fixed'),
121
+
122
+                                Forms\Components\Select::make('apply_to_accounts')
123
+                                    ->label('Apply to Accounts')
124
+                                    ->options(function () use ($get) {
125
+                                        $budgetItems = $get('budgetItems') ?? [];
126
+                                        $accountIds = collect($budgetItems)
127
+                                            ->pluck('account_id')
128
+                                            ->filter()
129
+                                            ->unique()
130
+                                            ->toArray();
131
+
132
+                                        return Account::query()
133
+                                            ->whereIn('id', $accountIds)
134
+                                            ->pluck('name', 'id')
135
+                                            ->toArray();
136
+                                    })
137
+                                    ->searchable()
138
+                                    ->multiple()
139
+                                    ->hint('Leave blank to apply to all accounts'),
140
+
141
+                                Forms\Components\Select::make('apply_to_periods')
142
+                                    ->label('Apply to Periods')
143
+                                    ->options(static function () use ($get) {
144
+                                        $startDate = $get('start_date');
145
+                                        $endDate = $get('end_date');
146
+                                        $intervalType = $get('interval_type');
147
+
148
+                                        if (blank($startDate) || blank($endDate) || blank($intervalType)) {
149
+                                            return [];
150
+                                        }
151
+
152
+                                        $labels = self::generateFormattedLabels($startDate, $endDate, $intervalType);
153
+
154
+                                        return array_combine($labels, $labels);
155
+                                    })
156
+                                    ->searchable()
157
+                                    ->multiple()
158
+                                    ->hint('Leave blank to apply to all periods'),
159
+                            ])
160
+                            ->action(static fn (Forms\Set $set, Forms\Get $get, array $data) => self::increaseAllocations($set, $get, $data))
161
+                            ->visible(static fn (Forms\Get $get) => filled($get('budgetItems'))),
69 162
                     ])
70 163
                     ->schema([
71 164
                         Forms\Components\Repeater::make('budgetItems')
@@ -74,7 +167,9 @@ class BudgetResource extends Resource
74 167
                             ->schema([
75 168
                                 Forms\Components\Select::make('account_id')
76 169
                                     ->label('Account')
77
-                                    ->options(Account::query()->pluck('name', 'id'))
170
+                                    ->options(Account::query()
171
+                                        ->budgetable()
172
+                                        ->pluck('name', 'id'))
78 173
                                     ->searchable()
79 174
                                     ->disableOptionsWhenSelectedInSiblingRepeaterItems()
80 175
                                     ->columnSpan(1)
@@ -152,6 +247,7 @@ class BudgetResource extends Resource
152 247
     private static function addAllAccounts(Forms\Set $set, Forms\Get $get): void
153 248
     {
154 249
         $accounts = Account::query()
250
+            ->budgetable()
155 251
             ->pluck('id');
156 252
 
157 253
         $budgetItems = $accounts->map(static fn ($accountId) => [
@@ -163,6 +259,33 @@ class BudgetResource extends Resource
163 259
         $set('budgetItems', $budgetItems);
164 260
     }
165 261
 
262
+    private static function addSelectedAccounts(Forms\Set $set, Forms\Get $get, array $data): void
263
+    {
264
+        $selectedAccountIds = $data['selected_accounts'] ?? [];
265
+
266
+        if (empty($selectedAccountIds)) {
267
+            return; // No accounts selected, do nothing.
268
+        }
269
+
270
+        $existingAccountIds = collect($get('budgetItems'))
271
+            ->pluck('account_id')
272
+            ->unique()
273
+            ->filter()
274
+            ->toArray();
275
+
276
+        // Only add accounts that aren't already in the budget items
277
+        $newAccounts = array_diff($selectedAccountIds, $existingAccountIds);
278
+
279
+        $newBudgetItems = collect($newAccounts)->map(static fn ($accountId) => [
280
+            'account_id' => $accountId,
281
+            'total_amount' => 0,
282
+            'amounts' => self::generateDefaultAllocations($get('start_date'), $get('end_date'), $get('interval_type')),
283
+        ])->toArray();
284
+
285
+        // Merge new budget items with existing ones
286
+        $set('budgetItems', array_merge($get('budgetItems') ?? [], $newBudgetItems));
287
+    }
288
+
166 289
     private static function generateDefaultAllocations(?string $startDate, ?string $endDate, ?string $intervalType): array
167 290
     {
168 291
         if (! $startDate || ! $endDate || ! $intervalType) {
@@ -174,6 +297,47 @@ class BudgetResource extends Resource
174 297
         return collect($labels)->mapWithKeys(static fn ($label) => [$label => 0])->toArray();
175 298
     }
176 299
 
300
+    private static function increaseAllocations(Forms\Set $set, Forms\Get $get, array $data): void
301
+    {
302
+        $increaseType = $data['increase_type']; // 'percentage' or 'fixed'
303
+        $percentage = $data['percentage'] ?? 0;
304
+        $fixedAmount = $data['fixed_amount'] ?? 0;
305
+
306
+        $selectedAccounts = $data['apply_to_accounts'] ?? []; // Selected account IDs
307
+        $selectedPeriods = $data['apply_to_periods'] ?? []; // Selected period labels
308
+
309
+        $budgetItems = $get('budgetItems') ?? [];
310
+
311
+        foreach ($budgetItems as $index => $budgetItem) {
312
+            // Skip if this account isn't selected (unless all accounts are being updated)
313
+            if (! empty($selectedAccounts) && ! in_array($budgetItem['account_id'], $selectedAccounts)) {
314
+                continue;
315
+            }
316
+
317
+            if (empty($budgetItem['amounts'])) {
318
+                continue; // Skip if no allocations exist
319
+            }
320
+
321
+            $updatedAmounts = $budgetItem['amounts']; // Clone existing amounts
322
+            foreach ($updatedAmounts as $label => $amount) {
323
+                // Skip if this period isn't selected (unless all periods are being updated)
324
+                if (! empty($selectedPeriods) && ! in_array($label, $selectedPeriods)) {
325
+                    continue;
326
+                }
327
+
328
+                // Apply increase based on selected type
329
+                $updatedAmounts[$label] = match ($increaseType) {
330
+                    'percentage' => round($amount * (1 + $percentage / 100), 2),
331
+                    'fixed' => round($amount + $fixedAmount, 2),
332
+                    default => $amount,
333
+                };
334
+            }
335
+
336
+            $set("budgetItems.{$index}.amounts", $updatedAmounts);
337
+            $set("budgetItems.{$index}.total_amount", round(array_sum($updatedAmounts), 2));
338
+        }
339
+    }
340
+
177 341
     private static function disperseTotalAmount(Forms\Set $set, Forms\Get $get, float $totalAmount): void
178 342
     {
179 343
         $startDate = $get('../../start_date');

+ 28
- 0
app/Models/Accounting/Account.php Datei anzeigen

@@ -10,8 +10,10 @@ use App\Facades\Accounting;
10 10
 use App\Models\Banking\BankAccount;
11 11
 use App\Models\Setting\Currency;
12 12
 use App\Observers\AccountObserver;
13
+use App\Utilities\Currency\CurrencyAccessor;
13 14
 use Database\Factories\Accounting\AccountFactory;
14 15
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
16
+use Illuminate\Database\Eloquent\Builder;
15 17
 use Illuminate\Database\Eloquent\Casts\Attribute;
16 18
 use Illuminate\Database\Eloquent\Factories\Factory;
17 19
 use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -84,6 +86,32 @@ class Account extends Model
84 86
         return $this->hasOne(Adjustment::class, 'account_id');
85 87
     }
86 88
 
89
+    public function scopeBudgetable(Builder $query): Builder
90
+    {
91
+        return $query->whereNotIn('category', [
92
+            AccountCategory::Equity,
93
+            AccountCategory::Liability,
94
+        ])
95
+            ->whereNotIn('type', [
96
+                AccountType::ContraAsset,
97
+                AccountType::ContraRevenue,
98
+                AccountType::ContraExpense,
99
+                AccountType::UncategorizedRevenue,
100
+                AccountType::UncategorizedExpense,
101
+            ])
102
+            ->whereDoesntHave('subtype', function (Builder $query) {
103
+                $query->whereIn('name', [
104
+                    'Receivables',
105
+                    'Input Tax Recoverable',
106
+                ]);
107
+            })
108
+            ->whereNotIn('name', [
109
+                'Gain on Foreign Exchange',
110
+                'Loss on Foreign Exchange',
111
+            ])
112
+            ->where('currency_code', CurrencyAccessor::getDefaultCurrency());
113
+    }
114
+
87 115
     public function getLastTransactionDate(): ?string
88 116
     {
89 117
         $lastJournalEntryTransaction = $this->journalEntries()

Laden…
Abbrechen
Speichern