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.

BudgetResource.php 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace App\Filament\Company\Resources\Accounting;
  3. use App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
  4. use App\Filament\Forms\Components\CustomSection;
  5. use App\Models\Accounting\Account;
  6. use App\Models\Accounting\Budget;
  7. use App\Models\Accounting\BudgetItem;
  8. use Filament\Forms;
  9. use Filament\Forms\Form;
  10. use Filament\Resources\Resource;
  11. use Filament\Tables;
  12. use Filament\Tables\Table;
  13. use Illuminate\Support\Carbon;
  14. class BudgetResource extends Resource
  15. {
  16. protected static ?string $model = Budget::class;
  17. protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
  18. public static function form(Form $form): Form
  19. {
  20. return $form
  21. ->schema([
  22. Forms\Components\Section::make('Budget Details')
  23. ->columns()
  24. ->schema([
  25. Forms\Components\TextInput::make('name')
  26. ->required()
  27. ->maxLength(255),
  28. Forms\Components\Select::make('interval_type')
  29. ->label('Budget Interval')
  30. ->options([
  31. 'day' => 'Daily',
  32. 'week' => 'Weekly',
  33. 'month' => 'Monthly',
  34. 'quarter' => 'Quarterly',
  35. 'year' => 'Yearly',
  36. ])
  37. ->default('month')
  38. ->required()
  39. ->live(),
  40. Forms\Components\DatePicker::make('start_date')
  41. ->required()
  42. ->default(now()->startOfYear())
  43. ->live(),
  44. Forms\Components\DatePicker::make('end_date')
  45. ->required()
  46. ->default(now()->endOfYear())
  47. ->live(),
  48. Forms\Components\Textarea::make('notes')->columnSpanFull(),
  49. ]),
  50. Forms\Components\Section::make('Budget Items')
  51. ->schema([
  52. Forms\Components\Repeater::make('budgetItems')
  53. ->relationship()
  54. ->columns(4)
  55. ->hiddenLabel()
  56. ->schema([
  57. Forms\Components\Select::make('account_id')
  58. ->label('Account')
  59. ->options(Account::query()->pluck('name', 'id'))
  60. ->searchable()
  61. ->columnSpan(1)
  62. ->required(),
  63. CustomSection::make('Budget Allocations')
  64. ->contained(false)
  65. ->columns(4)
  66. ->schema(static fn (Forms\Get $get) => self::getAllocationFields($get('../../start_date'), $get('../../end_date'), $get('../../interval_type'))),
  67. ])
  68. ->defaultItems(1)
  69. ->addActionLabel('Add Budget Item'),
  70. ]),
  71. ]);
  72. }
  73. public static function table(Table $table): Table
  74. {
  75. return $table
  76. ->columns([
  77. //
  78. ])
  79. ->filters([
  80. //
  81. ])
  82. ->actions([
  83. Tables\Actions\ViewAction::make(),
  84. Tables\Actions\EditAction::make(),
  85. ])
  86. ->bulkActions([
  87. Tables\Actions\BulkActionGroup::make([
  88. Tables\Actions\DeleteBulkAction::make(),
  89. ]),
  90. ]);
  91. }
  92. private static function getAllocationFields(?string $startDate, ?string $endDate, ?string $intervalType): array
  93. {
  94. if (! $startDate || ! $endDate || ! $intervalType) {
  95. return [];
  96. }
  97. $start = Carbon::parse($startDate);
  98. $end = Carbon::parse($endDate);
  99. $fields = [];
  100. while ($start->lte($end)) {
  101. $label = match ($intervalType) {
  102. 'month' => $start->format('M'), // Example: Jan, Feb, Mar
  103. 'quarter' => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
  104. 'year' => (string) $start->year, // Example: 2024, 2025
  105. default => '',
  106. };
  107. $fields[] = Forms\Components\TextInput::make("amounts.{$label}")
  108. ->label($label)
  109. ->numeric()
  110. ->suffix('USD')
  111. ->required();
  112. // Move to the next period
  113. match ($intervalType) {
  114. 'month' => $start->addMonth(),
  115. 'quarter' => $start->addQuarter(),
  116. 'year' => $start->addYear(),
  117. default => null,
  118. };
  119. }
  120. return $fields;
  121. }
  122. /**
  123. * Generates an array of interval labels (e.g., Jan 2024, Q1 2024, etc.).
  124. */
  125. private static function generateIntervals(string $startDate, string $endDate, string $intervalType): array
  126. {
  127. $start = Carbon::parse($startDate);
  128. $end = Carbon::parse($endDate);
  129. $intervals = [];
  130. while ($start->lte($end)) {
  131. if ($intervalType === 'month') {
  132. $intervals[] = $start->format('M Y'); // Example: Jan 2024
  133. $start->addMonth();
  134. } elseif ($intervalType === 'quarter') {
  135. $intervals[] = 'Q' . $start->quarter . ' ' . $start->year; // Example: Q1 2024
  136. $start->addQuarter();
  137. } elseif ($intervalType === 'year') {
  138. $intervals[] = $start->year; // Example: 2024
  139. $start->addYear();
  140. }
  141. }
  142. return $intervals;
  143. }
  144. /**
  145. * Saves budget allocations correctly in `budget_allocations` table.
  146. */
  147. public static function saveBudgetAllocations(BudgetItem $record, array $data): void
  148. {
  149. $record->update($data);
  150. $intervals = self::generateIntervals($data['start_date'], $data['end_date'], $data['interval_type']);
  151. foreach ($intervals as $interval) {
  152. $record->allocations()->updateOrCreate(
  153. ['period' => $interval],
  154. [
  155. 'interval_type' => $data['interval_type'],
  156. 'start_date' => Carbon::parse($interval)->startOfMonth(),
  157. 'end_date' => Carbon::parse($interval)->endOfMonth(),
  158. 'amount' => $data['allocations'][$interval] ?? 0,
  159. ]
  160. );
  161. }
  162. }
  163. public static function getRelations(): array
  164. {
  165. return [
  166. //
  167. ];
  168. }
  169. public static function getPages(): array
  170. {
  171. return [
  172. 'index' => Pages\ListBudgets::route('/'),
  173. 'create' => Pages\CreateBudget::route('/create'),
  174. 'view' => Pages\ViewBudget::route('/{record}'),
  175. 'edit' => Pages\EditBudget::route('/{record}/edit'),
  176. ];
  177. }
  178. }