| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 | 
							- <?php
 - 
 - namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
 - 
 - use App\Filament\Tables\Columns\DeferredTextInputColumn;
 - use App\Models\Accounting\Budget;
 - use App\Models\Accounting\BudgetAllocation;
 - use App\Models\Accounting\BudgetItem;
 - use App\Utilities\Currency\CurrencyConverter;
 - use Filament\Notifications\Notification;
 - use Filament\Resources\RelationManagers\RelationManager;
 - use Filament\Support\RawJs;
 - use Filament\Tables\Actions\Action;
 - use Filament\Tables\Actions\BulkAction;
 - use Filament\Tables\Columns\IconColumn;
 - use Filament\Tables\Columns\Summarizers\Summarizer;
 - use Filament\Tables\Columns\TextColumn;
 - use Filament\Tables\Grouping\Group;
 - use Filament\Tables\Table;
 - use Illuminate\Database\Eloquent\Builder;
 - use Illuminate\Database\Eloquent\Collection;
 - use Illuminate\Support\Carbon;
 - use Illuminate\Support\Facades\DB;
 - use stdClass;
 - 
 - class BudgetItemsRelationManager extends RelationManager
 - {
 -     protected static string $relationship = 'budgetItems';
 - 
 -     protected static bool $isLazy = false;
 - 
 -     protected const TOTAL_COLUMN = 'total';
 - 
 -     public array $batchChanges = [];
 - 
 -     /**
 -      * Generate a consistent key for the budget item and period
 -      */
 -     protected static function generatePeriodKey(int $recordId, string | Carbon $startDate): string
 -     {
 -         $formattedDate = $startDate instanceof Carbon
 -             ? $startDate->format('Y_m_d')
 -             : Carbon::parse($startDate)->format('Y_m_d');
 - 
 -         return "{$recordId}.{$formattedDate}";
 -     }
 - 
 -     /**
 -      * Generate a consistent key for the budget item's total
 -      */
 -     protected static function generateTotalKey(int $recordId): string
 -     {
 -         return "{$recordId}." . self::TOTAL_COLUMN;
 -     }
 - 
 -     public function handleBatchColumnChanged($data): void
 -     {
 -         $key = "{$data['recordKey']}.{$data['name']}";
 -         $this->batchChanges[$key] = $data['value'];
 -     }
 - 
 -     public function saveBatchChanges(): void
 -     {
 -         foreach ($this->batchChanges as $key => $value) {
 -             [$recordKey, $column] = explode('.', $key, 2);
 - 
 -             try {
 -                 $startDate = Carbon::createFromFormat('Y_m_d', $column);
 -             } catch (\Exception) {
 -                 continue;
 -             }
 - 
 -             $record = BudgetItem::find($recordKey);
 -             if (! $record) {
 -                 continue;
 -             }
 - 
 -             $allocation = $record->allocations()
 -                 ->whereDate('start_date', $startDate)
 -                 ->first();
 - 
 -             $allocation?->update(['amount' => $value]);
 -         }
 - 
 -         $this->batchChanges = [];
 - 
 -         Notification::make()
 -             ->title('Budget allocations updated')
 -             ->success()
 -             ->send();
 -     }
 - 
 -     protected function calculatePeriodSum(array $budgetItemIds, string | Carbon $startDate): int
 -     {
 -         $allocations = DB::table('budget_allocations')
 -             ->whereIn('budget_item_id', $budgetItemIds)
 -             ->whereDate('start_date', $startDate)
 -             ->pluck('amount', 'budget_item_id');
 - 
 -         $dbTotal = $allocations->sum();
 - 
 -         $batchTotal = 0;
 - 
 -         foreach ($budgetItemIds as $itemId) {
 -             $key = self::generatePeriodKey($itemId, $startDate);
 - 
 -             if (isset($this->batchChanges[$key])) {
 -                 $batchValue = CurrencyConverter::convertToCents($this->batchChanges[$key]);
 -                 $existingAmount = $allocations[$itemId] ?? 0;
 - 
 -                 $batchTotal += ($batchValue - $existingAmount);
 -             }
 -         }
 - 
 -         return $dbTotal + $batchTotal;
 -     }
 - 
 -     public function table(Table $table): Table
 -     {
 -         /** @var Budget $budget */
 -         $budget = $this->getOwnerRecord();
 -         $allocationPeriods = $budget->getPeriods();
 - 
 -         return $table
 -             ->recordTitleAttribute('account_id')
 -             ->paginated(false)
 -             ->heading(null)
 -             ->modifyQueryUsing(function (Builder $query) use ($allocationPeriods) {
 -                 $query->select('budget_items.*')
 -                     ->leftJoin('budget_allocations', 'budget_allocations.budget_item_id', '=', 'budget_items.id');
 - 
 -                 foreach ($allocationPeriods as $period) {
 -                     $alias = $period->start_date->format('Y_m_d');
 -                     $query->selectRaw(
 -                         "SUM(CASE WHEN budget_allocations.start_date = ? THEN budget_allocations.amount ELSE 0 END) as {$alias}",
 -                         [$period->start_date->toDateString()]
 -                     );
 -                 }
 - 
 -                 return $query->groupBy('budget_items.id');
 -             })
 -             ->groups([
 -                 Group::make('account.category')
 -                     ->titlePrefixedWithLabel(false)
 -                     ->collapsible(),
 -             ])
 -             ->recordClasses(['budget-items-relation-manager'])
 -             ->defaultGroup('account.category')
 -             ->headerActions([
 -                 Action::make('saveBatchChanges')
 -                     ->label('Save all changes')
 -                     ->action('saveBatchChanges')
 -                     ->color('primary'),
 -             ])
 -             ->columns([
 -                 TextColumn::make('account.name')
 -                     ->label('Account')
 -                     ->limit(30)
 -                     ->searchable(),
 -                 DeferredTextInputColumn::make(self::TOTAL_COLUMN)
 -                     ->label('Total')
 -                     ->alignRight()
 -                     ->mask(RawJs::make('$money($input)'))
 -                     ->getStateUsing(function (BudgetItem $record) {
 -                         $key = self::generateTotalKey($record->getKey());
 -                         if (isset($this->batchChanges[$key])) {
 -                             return $this->batchChanges[$key];
 -                         }
 - 
 -                         $total = $record->allocations->sum(
 -                             fn (BudgetAllocation $allocation) => $allocation->getRawOriginal('amount')
 -                         );
 - 
 -                         return CurrencyConverter::convertCentsToFormatSimple($total);
 -                     })
 -                     ->batchMode()
 -                     ->summarize(
 -                         Summarizer::make()
 -                             ->using(function (\Illuminate\Database\Query\Builder $query) {
 -                                 $allocations = $query
 -                                     ->leftJoin('budget_allocations', 'budget_allocations.budget_item_id', '=', 'budget_items.id')
 -                                     ->select('budget_allocations.budget_item_id', 'budget_allocations.start_date', 'budget_allocations.amount')
 -                                     ->get();
 - 
 -                                 $allocationsByDate = $allocations->groupBy('start_date');
 - 
 -                                 $total = 0;
 - 
 -                                 /** @var \Illuminate\Support\Collection<string, \Illuminate\Support\Collection<int, stdClass>> $allocationsByDate */
 -                                 foreach ($allocationsByDate as $startDate => $group) {
 -                                     $dbTotal = $group->sum('amount');
 -                                     $amounts = $group->pluck('amount', 'budget_item_id');
 -                                     $batchTotal = 0;
 - 
 -                                     foreach ($amounts as $itemId => $existingAmount) {
 -                                         $key = self::generatePeriodKey($itemId, $startDate);
 - 
 -                                         if (isset($this->batchChanges[$key])) {
 -                                             $batchValue = CurrencyConverter::convertToCents($this->batchChanges[$key]);
 -                                             $batchTotal += ($batchValue - $existingAmount);
 -                                         }
 -                                     }
 - 
 -                                     $total += $dbTotal + $batchTotal;
 -                                 }
 - 
 -                                 return CurrencyConverter::convertCentsToFormatSimple($total);
 -                             })
 -                     ),
 -                 IconColumn::make('disperseAction')
 -                     ->icon('heroicon-m-chevron-double-right')
 -                     ->color('primary')
 -                     ->label('')
 -                     ->default('')
 -                     ->tooltip('Disperse total across periods')
 -                     ->action(
 -                         Action::make('disperse')
 -                             ->label('Disperse')
 -                             ->action(function (BudgetItem $record) use ($allocationPeriods) {
 -                                 if (empty($allocationPeriods)) {
 -                                     return;
 -                                 }
 - 
 -                                 $totalKey = self::generateTotalKey($record->getKey());
 -                                 $totalAmount = $this->batchChanges[$totalKey] ?? null;
 - 
 -                                 if (isset($totalAmount)) {
 -                                     $totalCents = CurrencyConverter::convertToCents($totalAmount);
 -                                 } else {
 -                                     $totalCents = $record->allocations->sum(function (BudgetAllocation $budgetAllocation) {
 -                                         return $budgetAllocation->getRawOriginal('amount');
 -                                     });
 -                                 }
 - 
 -                                 if ($totalCents <= 0) {
 -                                     foreach ($allocationPeriods as $period) {
 -                                         $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
 -                                         $this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
 -                                     }
 - 
 -                                     return;
 -                                 }
 - 
 -                                 $numPeriods = count($allocationPeriods);
 - 
 -                                 $baseAmount = floor($totalCents / $numPeriods);
 -                                 $remainder = $totalCents - ($baseAmount * $numPeriods);
 - 
 -                                 foreach ($allocationPeriods as $index => $period) {
 -                                     $amount = $baseAmount + ($index === 0 ? $remainder : 0);
 -                                     $formattedAmount = CurrencyConverter::convertCentsToFormatSimple($amount);
 - 
 -                                     $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
 -                                     $this->batchChanges[$periodKey] = $formattedAmount;
 -                                 }
 -                             }),
 -                     ),
 -                 ...$allocationPeriods->map(function (BudgetAllocation $period) {
 -                     $alias = $period->start_date->format('Y_m_d');
 - 
 -                     return DeferredTextInputColumn::make($alias)
 -                         ->label($period->period)
 -                         ->alignRight()
 -                         ->batchMode()
 -                         ->mask(RawJs::make('$money($input)'))
 -                         ->getStateUsing(function ($record) use ($alias) {
 -                             $key = "{$record->getKey()}.{$alias}";
 - 
 -                             return $this->batchChanges[$key] ?? CurrencyConverter::convertCentsToFormatSimple($record->{$alias} ?? 0);
 -                         })
 -                         ->summarize(
 -                             Summarizer::make()
 -                                 ->using(function (\Illuminate\Database\Query\Builder $query) use ($period) {
 -                                     $budgetItemIds = $query->pluck('id')->toArray();
 -                                     $total = $this->calculatePeriodSum($budgetItemIds, $period->start_date);
 - 
 -                                     return CurrencyConverter::convertCentsToFormatSimple($total);
 -                                 })
 -                         );
 -                 })->toArray(),
 -             ])
 -             ->bulkActions([
 -                 BulkAction::make('clearAllocations')
 -                     ->label('Clear Allocations')
 -                     ->icon('heroicon-o-trash')
 -                     ->color('danger')
 -                     ->requiresConfirmation()
 -                     ->deselectRecordsAfterCompletion()
 -                     ->action(function (Collection $records) use ($allocationPeriods) {
 -                         foreach ($records as $record) {
 -                             foreach ($allocationPeriods as $period) {
 -                                 $periodKey = self::generatePeriodKey($record->getKey(), $period->start_date);
 -                                 $this->batchChanges[$periodKey] = CurrencyConverter::convertCentsToFormatSimple(0);
 -                             }
 -                         }
 -                     }),
 -             ]);
 -     }
 - }
 
 
  |