|  | @@ -3,15 +3,16 @@
 | 
		
	
		
			
			| 3 | 3 |  namespace App\Filament\Company\Resources\Accounting;
 | 
		
	
		
			
			| 4 | 4 |  
 | 
		
	
		
			
			| 5 | 5 |  use App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
 | 
		
	
		
			
			|  | 6 | +use App\Filament\Forms\Components\CustomSection;
 | 
		
	
		
			
			| 6 | 7 |  use App\Models\Accounting\Account;
 | 
		
	
		
			
			| 7 | 8 |  use App\Models\Accounting\Budget;
 | 
		
	
		
			
			| 8 |  | -use Awcodes\TableRepeater\Components\TableRepeater;
 | 
		
	
		
			
			| 9 |  | -use Awcodes\TableRepeater\Header;
 | 
		
	
		
			
			|  | 9 | +use App\Models\Accounting\BudgetItem;
 | 
		
	
		
			
			| 10 | 10 |  use Filament\Forms;
 | 
		
	
		
			
			| 11 | 11 |  use Filament\Forms\Form;
 | 
		
	
		
			
			| 12 | 12 |  use Filament\Resources\Resource;
 | 
		
	
		
			
			| 13 | 13 |  use Filament\Tables;
 | 
		
	
		
			
			| 14 | 14 |  use Filament\Tables\Table;
 | 
		
	
		
			
			|  | 15 | +use Illuminate\Support\Carbon;
 | 
		
	
		
			
			| 15 | 16 |  
 | 
		
	
		
			
			| 16 | 17 |  class BudgetResource extends Resource
 | 
		
	
		
			
			| 17 | 18 |  {
 | 
		
	
	
		
			
			|  | @@ -24,16 +25,11 @@ class BudgetResource extends Resource
 | 
		
	
		
			
			| 24 | 25 |          return $form
 | 
		
	
		
			
			| 25 | 26 |              ->schema([
 | 
		
	
		
			
			| 26 | 27 |                  Forms\Components\Section::make('Budget Details')
 | 
		
	
		
			
			|  | 28 | +                    ->columns()
 | 
		
	
		
			
			| 27 | 29 |                      ->schema([
 | 
		
	
		
			
			| 28 | 30 |                          Forms\Components\TextInput::make('name')
 | 
		
	
		
			
			| 29 | 31 |                              ->required()
 | 
		
	
		
			
			| 30 | 32 |                              ->maxLength(255),
 | 
		
	
		
			
			| 31 |  | -
 | 
		
	
		
			
			| 32 |  | -                        Forms\Components\Grid::make(2)->schema([
 | 
		
	
		
			
			| 33 |  | -                            Forms\Components\DatePicker::make('start_date')->required(),
 | 
		
	
		
			
			| 34 |  | -                            Forms\Components\DatePicker::make('end_date')->required(),
 | 
		
	
		
			
			| 35 |  | -                        ]),
 | 
		
	
		
			
			| 36 |  | -
 | 
		
	
		
			
			| 37 | 33 |                          Forms\Components\Select::make('interval_type')
 | 
		
	
		
			
			| 38 | 34 |                              ->label('Budget Interval')
 | 
		
	
		
			
			| 39 | 35 |                              ->options([
 | 
		
	
	
		
			
			|  | @@ -46,33 +42,35 @@ class BudgetResource extends Resource
 | 
		
	
		
			
			| 46 | 42 |                              ->default('month')
 | 
		
	
		
			
			| 47 | 43 |                              ->required()
 | 
		
	
		
			
			| 48 | 44 |                              ->live(),
 | 
		
	
		
			
			| 49 |  | -
 | 
		
	
		
			
			|  | 45 | +                        Forms\Components\DatePicker::make('start_date')
 | 
		
	
		
			
			|  | 46 | +                            ->required()
 | 
		
	
		
			
			|  | 47 | +                            ->default(now()->startOfYear())
 | 
		
	
		
			
			|  | 48 | +                            ->live(),
 | 
		
	
		
			
			|  | 49 | +                        Forms\Components\DatePicker::make('end_date')
 | 
		
	
		
			
			|  | 50 | +                            ->required()
 | 
		
	
		
			
			|  | 51 | +                            ->default(now()->endOfYear())
 | 
		
	
		
			
			|  | 52 | +                            ->live(),
 | 
		
	
		
			
			| 50 | 53 |                          Forms\Components\Textarea::make('notes')->columnSpanFull(),
 | 
		
	
		
			
			| 51 | 54 |                      ]),
 | 
		
	
		
			
			| 52 | 55 |  
 | 
		
	
		
			
			| 53 | 56 |                  Forms\Components\Section::make('Budget Items')
 | 
		
	
		
			
			| 54 | 57 |                      ->schema([
 | 
		
	
		
			
			| 55 |  | -                        TableRepeater::make('budgetItems')
 | 
		
	
		
			
			|  | 58 | +                        Forms\Components\Repeater::make('budgetItems')
 | 
		
	
		
			
			| 56 | 59 |                              ->relationship()
 | 
		
	
		
			
			| 57 |  | -                            ->saveRelationshipsUsing(null)
 | 
		
	
		
			
			| 58 |  | -                            ->dehydrated(true)
 | 
		
	
		
			
			| 59 |  | -                            ->headers(fn (Forms\Get $get) => self::getHeaders($get('interval_type')))
 | 
		
	
		
			
			|  | 60 | +                            ->columns(4)
 | 
		
	
		
			
			|  | 61 | +                            ->hiddenLabel()
 | 
		
	
		
			
			| 60 | 62 |                              ->schema([
 | 
		
	
		
			
			| 61 | 63 |                                  Forms\Components\Select::make('account_id')
 | 
		
	
		
			
			| 62 | 64 |                                      ->label('Account')
 | 
		
	
		
			
			| 63 | 65 |                                      ->options(Account::query()->pluck('name', 'id'))
 | 
		
	
		
			
			| 64 | 66 |                                      ->searchable()
 | 
		
	
		
			
			|  | 67 | +                                    ->columnSpan(1)
 | 
		
	
		
			
			| 65 | 68 |                                      ->required(),
 | 
		
	
		
			
			| 66 | 69 |  
 | 
		
	
		
			
			| 67 |  | -                                Forms\Components\Grid::make(2)->schema([
 | 
		
	
		
			
			| 68 |  | -                                    Forms\Components\DatePicker::make('start_date')->required(),
 | 
		
	
		
			
			| 69 |  | -                                    Forms\Components\DatePicker::make('end_date')->required(),
 | 
		
	
		
			
			| 70 |  | -                                ]),
 | 
		
	
		
			
			| 71 |  | -
 | 
		
	
		
			
			| 72 |  | -                                Forms\Components\TextInput::make('amount')
 | 
		
	
		
			
			| 73 |  | -                                    ->numeric()
 | 
		
	
		
			
			| 74 |  | -                                    ->suffix('USD')
 | 
		
	
		
			
			| 75 |  | -                                    ->required(),
 | 
		
	
		
			
			|  | 70 | +                                CustomSection::make('Budget Allocations')
 | 
		
	
		
			
			|  | 71 | +                                    ->contained(false)
 | 
		
	
		
			
			|  | 72 | +                                    ->columns(4)
 | 
		
	
		
			
			|  | 73 | +                                    ->schema(static fn (Forms\Get $get) => self::getAllocationFields($get('../../start_date'), $get('../../end_date'), $get('../../interval_type'))),
 | 
		
	
		
			
			| 76 | 74 |                              ])
 | 
		
	
		
			
			| 77 | 75 |                              ->defaultItems(1)
 | 
		
	
		
			
			| 78 | 76 |                              ->addActionLabel('Add Budget Item'),
 | 
		
	
	
		
			
			|  | @@ -100,39 +98,87 @@ class BudgetResource extends Resource
 | 
		
	
		
			
			| 100 | 98 |              ]);
 | 
		
	
		
			
			| 101 | 99 |      }
 | 
		
	
		
			
			| 102 | 100 |  
 | 
		
	
		
			
			| 103 |  | -    private static function getHeaders(?string $intervalType): array
 | 
		
	
		
			
			|  | 101 | +    private static function getAllocationFields(?string $startDate, ?string $endDate, ?string $intervalType): array
 | 
		
	
		
			
			| 104 | 102 |      {
 | 
		
	
		
			
			| 105 |  | -        $headers = [
 | 
		
	
		
			
			| 106 |  | -            Header::make('Account')->width('20%'),
 | 
		
	
		
			
			| 107 |  | -            Header::make('Start Date')->width('15%'),
 | 
		
	
		
			
			| 108 |  | -            Header::make('End Date')->width('15%'),
 | 
		
	
		
			
			| 109 |  | -        ];
 | 
		
	
		
			
			| 110 |  | -
 | 
		
	
		
			
			| 111 |  | -        // Adjust the number of columns dynamically based on interval type
 | 
		
	
		
			
			| 112 |  | -        switch ($intervalType) {
 | 
		
	
		
			
			| 113 |  | -            case 'day':
 | 
		
	
		
			
			| 114 |  | -                $headers[] = Header::make('Daily Budget')->width('20%')->align('right');
 | 
		
	
		
			
			|  | 103 | +        if (! $startDate || ! $endDate || ! $intervalType) {
 | 
		
	
		
			
			|  | 104 | +            return [];
 | 
		
	
		
			
			|  | 105 | +        }
 | 
		
	
		
			
			| 115 | 106 |  
 | 
		
	
		
			
			| 116 |  | -                break;
 | 
		
	
		
			
			| 117 |  | -            case 'week':
 | 
		
	
		
			
			| 118 |  | -                $headers[] = Header::make('Weekly Budget')->width('20%')->align('right');
 | 
		
	
		
			
			|  | 107 | +        $start = Carbon::parse($startDate);
 | 
		
	
		
			
			|  | 108 | +        $end = Carbon::parse($endDate);
 | 
		
	
		
			
			|  | 109 | +        $fields = [];
 | 
		
	
		
			
			|  | 110 | +
 | 
		
	
		
			
			|  | 111 | +        while ($start->lte($end)) {
 | 
		
	
		
			
			|  | 112 | +            $label = match ($intervalType) {
 | 
		
	
		
			
			|  | 113 | +                'month' => $start->format('M'), // Example: Jan, Feb, Mar
 | 
		
	
		
			
			|  | 114 | +                'quarter' => 'Q' . $start->quarter, // Example: Q1, Q2, Q3
 | 
		
	
		
			
			|  | 115 | +                'year' => (string) $start->year, // Example: 2024, 2025
 | 
		
	
		
			
			|  | 116 | +                default => '',
 | 
		
	
		
			
			|  | 117 | +            };
 | 
		
	
		
			
			|  | 118 | +
 | 
		
	
		
			
			|  | 119 | +            $fields[] = Forms\Components\TextInput::make("amounts.{$label}")
 | 
		
	
		
			
			|  | 120 | +                ->label($label)
 | 
		
	
		
			
			|  | 121 | +                ->numeric()
 | 
		
	
		
			
			|  | 122 | +                ->suffix('USD')
 | 
		
	
		
			
			|  | 123 | +                ->required();
 | 
		
	
		
			
			|  | 124 | +
 | 
		
	
		
			
			|  | 125 | +            // Move to the next period
 | 
		
	
		
			
			|  | 126 | +            match ($intervalType) {
 | 
		
	
		
			
			|  | 127 | +                'month' => $start->addMonth(),
 | 
		
	
		
			
			|  | 128 | +                'quarter' => $start->addQuarter(),
 | 
		
	
		
			
			|  | 129 | +                'year' => $start->addYear(),
 | 
		
	
		
			
			|  | 130 | +                default => null,
 | 
		
	
		
			
			|  | 131 | +            };
 | 
		
	
		
			
			|  | 132 | +        }
 | 
		
	
		
			
			| 119 | 133 |  
 | 
		
	
		
			
			| 120 |  | -                break;
 | 
		
	
		
			
			| 121 |  | -            case 'month':
 | 
		
	
		
			
			| 122 |  | -                $headers[] = Header::make('Monthly Budget')->width('20%')->align('right');
 | 
		
	
		
			
			|  | 134 | +        return $fields;
 | 
		
	
		
			
			|  | 135 | +    }
 | 
		
	
		
			
			| 123 | 136 |  
 | 
		
	
		
			
			| 124 |  | -                break;
 | 
		
	
		
			
			| 125 |  | -            case 'quarter':
 | 
		
	
		
			
			| 126 |  | -                $headers[] = Header::make('Quarterly Budget')->width('20%')->align('right');
 | 
		
	
		
			
			|  | 137 | +    /**
 | 
		
	
		
			
			|  | 138 | +     * Generates an array of interval labels (e.g., Jan 2024, Q1 2024, etc.).
 | 
		
	
		
			
			|  | 139 | +     */
 | 
		
	
		
			
			|  | 140 | +    private static function generateIntervals(string $startDate, string $endDate, string $intervalType): array
 | 
		
	
		
			
			|  | 141 | +    {
 | 
		
	
		
			
			|  | 142 | +        $start = Carbon::parse($startDate);
 | 
		
	
		
			
			|  | 143 | +        $end = Carbon::parse($endDate);
 | 
		
	
		
			
			|  | 144 | +        $intervals = [];
 | 
		
	
		
			
			|  | 145 | +
 | 
		
	
		
			
			|  | 146 | +        while ($start->lte($end)) {
 | 
		
	
		
			
			|  | 147 | +            if ($intervalType === 'month') {
 | 
		
	
		
			
			|  | 148 | +                $intervals[] = $start->format('M Y'); // Example: Jan 2024
 | 
		
	
		
			
			|  | 149 | +                $start->addMonth();
 | 
		
	
		
			
			|  | 150 | +            } elseif ($intervalType === 'quarter') {
 | 
		
	
		
			
			|  | 151 | +                $intervals[] = 'Q' . $start->quarter . ' ' . $start->year; // Example: Q1 2024
 | 
		
	
		
			
			|  | 152 | +                $start->addQuarter();
 | 
		
	
		
			
			|  | 153 | +            } elseif ($intervalType === 'year') {
 | 
		
	
		
			
			|  | 154 | +                $intervals[] = $start->year; // Example: 2024
 | 
		
	
		
			
			|  | 155 | +                $start->addYear();
 | 
		
	
		
			
			|  | 156 | +            }
 | 
		
	
		
			
			|  | 157 | +        }
 | 
		
	
		
			
			| 127 | 158 |  
 | 
		
	
		
			
			| 128 |  | -                break;
 | 
		
	
		
			
			| 129 |  | -            case 'year':
 | 
		
	
		
			
			| 130 |  | -                $headers[] = Header::make('Yearly Budget')->width('20%')->align('right');
 | 
		
	
		
			
			|  | 159 | +        return $intervals;
 | 
		
	
		
			
			|  | 160 | +    }
 | 
		
	
		
			
			| 131 | 161 |  
 | 
		
	
		
			
			| 132 |  | -                break;
 | 
		
	
		
			
			|  | 162 | +    /**
 | 
		
	
		
			
			|  | 163 | +     * Saves budget allocations correctly in `budget_allocations` table.
 | 
		
	
		
			
			|  | 164 | +     */
 | 
		
	
		
			
			|  | 165 | +    public static function saveBudgetAllocations(BudgetItem $record, array $data): void
 | 
		
	
		
			
			|  | 166 | +    {
 | 
		
	
		
			
			|  | 167 | +        $record->update($data);
 | 
		
	
		
			
			|  | 168 | +
 | 
		
	
		
			
			|  | 169 | +        $intervals = self::generateIntervals($data['start_date'], $data['end_date'], $data['interval_type']);
 | 
		
	
		
			
			|  | 170 | +
 | 
		
	
		
			
			|  | 171 | +        foreach ($intervals as $interval) {
 | 
		
	
		
			
			|  | 172 | +            $record->allocations()->updateOrCreate(
 | 
		
	
		
			
			|  | 173 | +                ['period' => $interval],
 | 
		
	
		
			
			|  | 174 | +                [
 | 
		
	
		
			
			|  | 175 | +                    'interval_type' => $data['interval_type'],
 | 
		
	
		
			
			|  | 176 | +                    'start_date' => Carbon::parse($interval)->startOfMonth(),
 | 
		
	
		
			
			|  | 177 | +                    'end_date' => Carbon::parse($interval)->endOfMonth(),
 | 
		
	
		
			
			|  | 178 | +                    'amount' => $data['allocations'][$interval] ?? 0,
 | 
		
	
		
			
			|  | 179 | +                ]
 | 
		
	
		
			
			|  | 180 | +            );
 | 
		
	
		
			
			| 133 | 181 |          }
 | 
		
	
		
			
			| 134 |  | -
 | 
		
	
		
			
			| 135 |  | -        return $headers;
 | 
		
	
		
			
			| 136 | 182 |      }
 | 
		
	
		
			
			| 137 | 183 |  
 | 
		
	
		
			
			| 138 | 184 |      public static function getRelations(): array
 |