You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

EditBudget.php 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <?php
  2. namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
  3. use App\Enums\Accounting\BudgetIntervalType;
  4. use App\Filament\Company\Resources\Accounting\BudgetResource;
  5. use App\Filament\Forms\Components\CustomTableRepeater;
  6. use App\Models\Accounting\Budget;
  7. use App\Models\Accounting\BudgetAllocation;
  8. use App\Models\Accounting\BudgetItem;
  9. use Awcodes\TableRepeater\Header;
  10. use Filament\Actions;
  11. use Filament\Forms;
  12. use Filament\Forms\Form;
  13. use Filament\Resources\Pages\EditRecord;
  14. use Filament\Support\Enums\Alignment;
  15. use Filament\Support\Enums\MaxWidth;
  16. use Filament\Support\RawJs;
  17. use Illuminate\Database\Eloquent\Model;
  18. use Illuminate\Support\Carbon;
  19. class EditBudget extends EditRecord
  20. {
  21. protected static string $resource = BudgetResource::class;
  22. protected function getHeaderActions(): array
  23. {
  24. return [
  25. Actions\ViewAction::make(),
  26. Actions\DeleteAction::make(),
  27. ];
  28. }
  29. public function getMaxContentWidth(): MaxWidth | string | null
  30. {
  31. return 'max-w-8xl';
  32. }
  33. public function form(Form $form): Form
  34. {
  35. /** @var Budget $budget */
  36. $budget = $this->record;
  37. $periods = $budget->getPeriods();
  38. $headers = [
  39. Header::make('Account')
  40. ->label('Account')
  41. ->width('200px'),
  42. ];
  43. foreach ($periods as $period) {
  44. $headers[] = Header::make($period)
  45. ->label($period)
  46. ->width('120px')
  47. ->align(Alignment::Center);
  48. }
  49. return $form->schema([
  50. Forms\Components\Section::make('Budget Details')
  51. ->schema([
  52. Forms\Components\TextInput::make('name')
  53. ->required(),
  54. Forms\Components\Select::make('interval_type')
  55. ->disabled(), // Can't change interval type after creation
  56. Forms\Components\DatePicker::make('start_date')
  57. ->disabled(),
  58. Forms\Components\DatePicker::make('end_date')
  59. ->disabled(),
  60. Forms\Components\Textarea::make('notes'),
  61. ]),
  62. Forms\Components\Section::make('Budget Allocations')
  63. ->schema([
  64. CustomTableRepeater::make('budgetItems')
  65. ->relationship()
  66. ->headers($headers)
  67. ->schema([
  68. Forms\Components\Placeholder::make('account')
  69. ->hiddenLabel()
  70. ->content(fn ($record) => $record->account->name ?? ''),
  71. // Create a field for each period
  72. ...collect($periods)->map(function ($period) {
  73. return Forms\Components\TextInput::make("allocations.{$period}")
  74. ->mask(RawJs::make('$money($input)'))
  75. ->stripCharacters(',')
  76. ->numeric()
  77. ->afterStateHydrated(function ($component, $state, $record) use ($period) {
  78. // Find the allocation for this period
  79. $allocation = $record->allocations->firstWhere('period', $period);
  80. $component->state($allocation ? $allocation->amount : 0);
  81. })
  82. ->dehydrated(false); // We'll handle saving manually
  83. })->toArray(),
  84. ])
  85. ->spreadsheet()
  86. ->itemLabel(fn ($record) => $record->account->name ?? 'Budget Item')
  87. ->deletable(false)
  88. ->reorderable(false)
  89. ->addable(false) // Don't allow adding new budget items
  90. ->columnSpanFull(),
  91. ]),
  92. ]);
  93. }
  94. // protected function mutateFormDataBeforeFill(array $data): array
  95. // {
  96. // /** @var Budget $budget */
  97. // $budget = $this->record;
  98. //
  99. // $data['budgetItems'] = $budget->budgetItems->map(function (BudgetItem $budgetItem) {
  100. // return [
  101. // 'id' => $budgetItem->id,
  102. // 'account_id' => $budgetItem->account_id,
  103. // 'total_amount' => $budgetItem->allocations->sum('amount'), // Calculate total dynamically
  104. // 'amounts' => $budgetItem->allocations->mapWithKeys(static function (BudgetAllocation $allocation) {
  105. // return [$allocation->period => $allocation->amount]; // Use the correct period label
  106. // })->toArray(),
  107. // ];
  108. // })->toArray();
  109. //
  110. // return $data;
  111. // }
  112. protected function handleRecordUpdate(Model $record, array $data): Model
  113. {
  114. /** @var Budget $budget */
  115. $budget = $record;
  116. $budget->update([
  117. 'name' => $data['name'],
  118. 'interval_type' => $data['interval_type'],
  119. 'start_date' => $data['start_date'],
  120. 'end_date' => $data['end_date'],
  121. 'notes' => $data['notes'] ?? null,
  122. ]);
  123. $budgetItemIds = [];
  124. foreach ($data['budgetItems'] as $itemData) {
  125. /** @var BudgetItem $budgetItem */
  126. $budgetItem = $budget->budgetItems()->updateOrCreate(
  127. ['id' => $itemData['id'] ?? null],
  128. ['account_id' => $itemData['account_id']]
  129. );
  130. $budgetItemIds[] = $budgetItem->id;
  131. $budgetItem->allocations()->delete();
  132. $allocationStart = Carbon::parse($data['start_date']);
  133. foreach ($itemData['amounts'] as $periodLabel => $amount) {
  134. $allocationEnd = self::calculateEndDate($allocationStart, BudgetIntervalType::parse($data['interval_type']));
  135. // Recreate allocations
  136. $budgetItem->allocations()->create([
  137. 'period' => $periodLabel,
  138. 'interval_type' => $data['interval_type'],
  139. 'start_date' => $allocationStart->toDateString(),
  140. 'end_date' => $allocationEnd->toDateString(),
  141. 'amount' => $amount,
  142. ]);
  143. $allocationStart = $allocationEnd->addDay();
  144. }
  145. }
  146. $budget->budgetItems()->whereNotIn('id', $budgetItemIds)->delete();
  147. return $budget;
  148. }
  149. private static function calculateEndDate(Carbon $startDate, BudgetIntervalType $intervalType): Carbon
  150. {
  151. return match ($intervalType) {
  152. BudgetIntervalType::Month => $startDate->copy()->endOfMonth(),
  153. BudgetIntervalType::Quarter => $startDate->copy()->endOfQuarter(),
  154. BudgetIntervalType::Year => $startDate->copy()->endOfYear(),
  155. };
  156. }
  157. }