| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 | 
							- <?php
 - 
 - namespace App\Filament\Company\Resources\Accounting;
 - 
 - use App\Enums\Accounting\TransactionType;
 - use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
 - use App\Filament\Forms\Components\DateRangeSelect;
 - use App\Filament\Tables\Actions\EditTransactionAction;
 - use App\Filament\Tables\Actions\ReplicateBulkAction;
 - use App\Models\Accounting\JournalEntry;
 - use App\Models\Accounting\Transaction;
 - use App\Models\Common\Client;
 - use App\Models\Common\Vendor;
 - use Exception;
 - use Filament\Forms\Components\DatePicker;
 - use Filament\Forms\Components\Grid;
 - use Filament\Forms\Form;
 - use Filament\Forms\Set;
 - use Filament\Notifications\Notification;
 - use Filament\Resources\Resource;
 - use Filament\Support\Colors\Color;
 - use Filament\Support\Enums\FontWeight;
 - use Filament\Support\Enums\MaxWidth;
 - use Filament\Tables;
 - use Filament\Tables\Table;
 - use Illuminate\Database\Eloquent\Builder;
 - use Illuminate\Database\Eloquent\Collection;
 - use Illuminate\Support\Carbon;
 - 
 - class TransactionResource extends Resource
 - {
 -     protected static ?string $model = Transaction::class;
 - 
 -     protected static ?string $recordTitleAttribute = 'description';
 - 
 -     public static function form(Form $form): Form
 -     {
 -         return $form
 -             ->schema([]);
 -     }
 - 
 -     public static function table(Table $table): Table
 -     {
 -         return $table
 -             ->modifyQueryUsing(function (Builder $query) {
 -                 $query->with([
 -                     'account',
 -                     'bankAccount.account',
 -                     'journalEntries.account',
 -                     'payeeable',
 -                 ])
 -                     ->where(function (Builder $query) {
 -                         $query->whereNull('transactionable_id')
 -                             ->orWhere('is_payment', true);
 -                     });
 -             })
 -             ->columns([
 -                 Tables\Columns\TextColumn::make('posted_at')
 -                     ->label('Date')
 -                     ->sortable()
 -                     ->defaultDateFormat(),
 -                 Tables\Columns\TextColumn::make('type')
 -                     ->label('Type')
 -                     ->sortable()
 -                     ->toggleable(isToggledHiddenByDefault: true),
 -                 Tables\Columns\TextColumn::make('description')
 -                     ->label('Description')
 -                     ->limit(50)
 -                     ->searchable()
 -                     ->toggleable(),
 -                 Tables\Columns\TextColumn::make('payeeable.name')
 -                     ->label('Payee')
 -                     ->searchable()
 -                     ->toggleable(isToggledHiddenByDefault: true),
 -                 Tables\Columns\TextColumn::make('bankAccount.account.name')
 -                     ->label('Account')
 -                     ->searchable()
 -                     ->toggleable(),
 -                 Tables\Columns\TextColumn::make('account.name')
 -                     ->label('Category')
 -                     ->prefix(static fn (Transaction $transaction) => $transaction->type->isTransfer() ? 'Transfer to ' : null)
 -                     ->searchable()
 -                     ->toggleable()
 -                     ->state(static fn (Transaction $transaction) => $transaction->account->name ?? 'Journal Entry'),
 -                 Tables\Columns\TextColumn::make('amount')
 -                     ->label('Amount')
 -                     ->weight(static fn (Transaction $transaction) => $transaction->reviewed ? null : FontWeight::SemiBold)
 -                     ->color(
 -                         static fn (Transaction $transaction) => match ($transaction->type) {
 -                             TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
 -                             TransactionType::Journal => 'primary',
 -                             default => null,
 -                         }
 -                     )
 -                     ->sortable()
 -                     ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code),
 -             ])
 -             ->defaultSort('posted_at', 'desc')
 -             ->filters([
 -                 Tables\Filters\SelectFilter::make('bank_account_id')
 -                     ->label('Account')
 -                     ->searchable()
 -                     ->options(static fn () => Transaction::getBankAccountOptions(false)),
 -                 Tables\Filters\SelectFilter::make('account_id')
 -                     ->label('Category')
 -                     ->multiple()
 -                     ->options(static fn () => Transaction::getChartAccountOptions()),
 -                 Tables\Filters\TernaryFilter::make('reviewed')
 -                     ->label('Status')
 -                     ->trueLabel('Reviewed')
 -                     ->falseLabel('Not Reviewed'),
 -                 Tables\Filters\SelectFilter::make('type')
 -                     ->label('Type')
 -                     ->options(TransactionType::class),
 -                 Tables\Filters\TernaryFilter::make('is_payment')
 -                     ->label('Payment')
 -                     ->default(false),
 -                 Tables\Filters\SelectFilter::make('payee')
 -                     ->label('Payee')
 -                     ->options(static fn () => Transaction::getPayeeOptions())
 -                     ->searchable()
 -                     ->query(function (Builder $query, array $data): Builder {
 -                         if (empty($data['value'])) {
 -                             return $query;
 -                         }
 - 
 -                         $id = (int) $data['value'];
 - 
 -                         if ($id < 0) {
 -                             return $query->where('payeeable_type', Vendor::class)
 -                                 ->where('payeeable_id', abs($id));
 -                         } else {
 -                             return $query->where('payeeable_type', Client::class)
 -                                 ->where('payeeable_id', $id);
 -                         }
 -                     }),
 -                 static::buildDateRangeFilter('posted_at', 'Posted', true),
 -                 static::buildDateRangeFilter('updated_at', 'Last modified'),
 -             ])
 -             ->filtersFormSchema(fn (array $filters): array => [
 -                 Grid::make()
 -                     ->schema([
 -                         $filters['bank_account_id'],
 -                         $filters['account_id'],
 -                         $filters['reviewed'],
 -                         $filters['type'],
 -                         $filters['is_payment'],
 -                         $filters['payee'],
 -                     ])
 -                     ->columnSpanFull()
 -                     ->extraAttributes(['class' => 'border-b border-gray-200 dark:border-white/10 pb-8']),
 -                 $filters['posted_at'],
 -                 $filters['updated_at'],
 -             ])
 -             ->filtersFormWidth(MaxWidth::ThreeExtraLarge)
 -             ->actions([
 -                 Tables\Actions\Action::make('markAsReviewed')
 -                     ->label('Mark as reviewed')
 -                     ->view('filament.company.components.tables.actions.mark-as-reviewed')
 -                     ->icon(static fn (Transaction $transaction) => $transaction->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
 -                     ->color(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
 -                         'reviewed' => 'primary',
 -                         'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
 -                         'uncategorized' => 'gray',
 -                     })
 -                     ->tooltip(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
 -                         'reviewed' => 'Reviewed',
 -                         'unreviewed' => 'Mark as reviewed',
 -                         'uncategorized' => 'Categorize first to mark as reviewed',
 -                     })
 -                     ->disabled(fn (Transaction $transaction): bool => $transaction->isUncategorized())
 -                     ->action(fn (Transaction $transaction) => $transaction->update(['reviewed' => ! $transaction->reviewed])),
 -                 Tables\Actions\ActionGroup::make([
 -                     Tables\Actions\ActionGroup::make([
 -                         EditTransactionAction::make(),
 -                         Tables\Actions\ReplicateAction::make()
 -                             ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
 -                             ->modal(false)
 -                             ->beforeReplicaSaved(static function (Transaction $replica) {
 -                                 $replica->description = '(Copy of) ' . $replica->description;
 -                             })
 -                             ->hidden(static fn (Transaction $transaction) => $transaction->transactionable_id)
 -                             ->after(static function (Transaction $original, Transaction $replica) {
 -                                 $original->journalEntries->each(function (JournalEntry $entry) use ($replica) {
 -                                     $entry->replicate([
 -                                         'transaction_id',
 -                                     ])->fill([
 -                                         'transaction_id' => $replica->id,
 -                                     ])->save();
 -                                 });
 -                             }),
 -                     ])->dropdown(false),
 -                     Tables\Actions\DeleteAction::make(),
 -                 ]),
 -             ])
 -             ->bulkActions([
 -                 Tables\Actions\BulkActionGroup::make([
 -                     Tables\Actions\DeleteBulkAction::make(),
 -                     ReplicateBulkAction::make()
 -                         ->label('Replicate')
 -                         ->modalWidth(MaxWidth::Large)
 -                         ->modalDescription('Replicating transactions will also replicate their journal entries. Are you sure you want to proceed?')
 -                         ->successNotificationTitle('Transactions replicated successfully')
 -                         ->failureNotificationTitle('Failed to replicate transactions')
 -                         ->deselectRecordsAfterCompletion()
 -                         ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
 -                         ->beforeReplicaSaved(static function (Transaction $replica) {
 -                             $replica->description = '(Copy of) ' . $replica->description;
 -                         })
 -                         ->before(function (Collection $records, ReplicateBulkAction $action) {
 -                             $isInvalid = $records->contains(fn (Transaction $record) => $record->transactionable_id);
 - 
 -                             if ($isInvalid) {
 -                                 Notification::make()
 -                                     ->title('Cannot replicate transactions')
 -                                     ->body('You cannot replicate transactions associated with bills or invoices')
 -                                     ->persistent()
 -                                     ->danger()
 -                                     ->send();
 - 
 -                                 $action->cancel(true);
 -                             }
 -                         })
 -                         ->withReplicatedRelationships(['journalEntries']),
 -                 ]),
 -             ]);
 -     }
 - 
 -     public static function getRelations(): array
 -     {
 -         return [
 -             //
 -         ];
 -     }
 - 
 -     public static function getPages(): array
 -     {
 -         return [
 -             'index' => Pages\ListTransactions::route('/'),
 -             'view' => Pages\ViewTransaction::route('/{record}'),
 -         ];
 -     }
 - 
 -     /**
 -      * @throws Exception
 -      */
 -     public static function buildDateRangeFilter(string $fieldPrefix, string $label, bool $hasBottomBorder = false): Tables\Filters\Filter
 -     {
 -         return Tables\Filters\Filter::make($fieldPrefix)
 -             ->columnSpanFull()
 -             ->form([
 -                 Grid::make()
 -                     ->live()
 -                     ->schema([
 -                         DateRangeSelect::make("{$fieldPrefix}_date_range")
 -                             ->label($label)
 -                             ->selectablePlaceholder(false)
 -                             ->placeholder('Select a date range')
 -                             ->startDateField("{$fieldPrefix}_start_date")
 -                             ->endDateField("{$fieldPrefix}_end_date"),
 -                         DatePicker::make("{$fieldPrefix}_start_date")
 -                             ->label("{$label} from")
 -                             ->columnStart(1)
 -                             ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
 -                                 $set("{$fieldPrefix}_date_range", 'Custom');
 -                             }),
 -                         DatePicker::make("{$fieldPrefix}_end_date")
 -                             ->label("{$label} to")
 -                             ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
 -                                 $set("{$fieldPrefix}_date_range", 'Custom');
 -                             }),
 -                     ])
 -                     ->extraAttributes($hasBottomBorder ? ['class' => 'border-b border-gray-200 dark:border-white/10 pb-8'] : []),
 -             ])
 -             ->query(function (Builder $query, array $data) use ($fieldPrefix): Builder {
 -                 $query
 -                     ->when($data["{$fieldPrefix}_start_date"], fn (Builder $query, $startDate) => $query->whereDate($fieldPrefix, '>=', $startDate))
 -                     ->when($data["{$fieldPrefix}_end_date"], fn (Builder $query, $endDate) => $query->whereDate($fieldPrefix, '<=', $endDate));
 - 
 -                 return $query;
 -             })
 -             ->indicateUsing(function (array $data) use ($fieldPrefix, $label): array {
 -                 $indicators = [];
 - 
 -                 static::addIndicatorForDateRange($data, "{$fieldPrefix}_start_date", "{$fieldPrefix}_end_date", $label, $indicators);
 - 
 -                 return $indicators;
 -             });
 - 
 -     }
 - 
 -     public static function addIndicatorForDateRange($data, $startKey, $endKey, $labelPrefix, &$indicators): void
 -     {
 -         $formattedStartDate = filled($data[$startKey]) ? Carbon::parse($data[$startKey])->toFormattedDateString() : null;
 -         $formattedEndDate = filled($data[$endKey]) ? Carbon::parse($data[$endKey])->toFormattedDateString() : null;
 -         if ($formattedStartDate && $formattedEndDate) {
 -             // If both start and end dates are set, show the combined date range as the indicator, no specific field needs to be removed since the entire filter will be removed
 -             $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix}: {$formattedStartDate} - {$formattedEndDate}");
 -         } else {
 -             if ($formattedStartDate) {
 -                 $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} After: {$formattedStartDate}")
 -                     ->removeField($startKey);
 -             }
 - 
 -             if ($formattedEndDate) {
 -                 $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} Before: {$formattedEndDate}")
 -                     ->removeField($endKey);
 -             }
 -         }
 -     }
 - 
 -     protected static function determineTransactionState(Transaction $transaction, Tables\Actions\Action $action): string
 -     {
 -         if ($transaction->reviewed) {
 -             return 'reviewed';
 -         }
 - 
 -         if ($transaction->reviewed === false && $action->isEnabled()) {
 -             return 'unreviewed';
 -         }
 - 
 -         return 'uncategorized';
 -     }
 - }
 
 
  |