Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

TransactionResource.php 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. namespace App\Filament\Company\Resources\Accounting;
  3. use App\Enums\Accounting\AccountCategory;
  4. use App\Enums\Accounting\AccountType;
  5. use App\Enums\DateFormat;
  6. use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
  7. use App\Models\Accounting\Account;
  8. use App\Models\Accounting\Transaction;
  9. use App\Models\Banking\BankAccount;
  10. use App\Models\Setting\Localization;
  11. use Filament\Forms;
  12. use Filament\Forms\Form;
  13. use Filament\Resources\Resource;
  14. use Filament\Support\Colors\Color;
  15. use Filament\Support\Enums\FontWeight;
  16. use Filament\Support\Enums\MaxWidth;
  17. use Filament\Tables;
  18. use Filament\Tables\Table;
  19. use Illuminate\Support\Carbon;
  20. use Illuminate\Support\Collection;
  21. class TransactionResource extends Resource
  22. {
  23. protected static ?string $model = Transaction::class;
  24. protected static ?string $modelLabel = 'Transaction';
  25. public static function form(Form $form): Form
  26. {
  27. return $form
  28. ->schema([
  29. Forms\Components\DatePicker::make('posted_at')
  30. ->label('Date')
  31. ->required()
  32. ->displayFormat('Y-m-d'),
  33. Forms\Components\TextInput::make('description')
  34. ->label('Description'),
  35. Forms\Components\Select::make('bank_account_id')
  36. ->label('Account')
  37. ->options(static fn () => static::getBankAccountOptions())
  38. ->live()
  39. ->searchable()
  40. ->required(),
  41. Forms\Components\Select::make('type')
  42. ->label('Type')
  43. ->live()
  44. ->options([
  45. 'deposit' => 'Deposit',
  46. 'withdrawal' => 'Withdrawal',
  47. ])
  48. ->required()
  49. ->afterStateUpdated(static fn (Forms\Set $set, $state) => $set('account_id', Pages\ManageTransaction::getUncategorizedAccountByType($state)?->id)),
  50. Forms\Components\TextInput::make('amount')
  51. ->label('Amount')
  52. ->money(static fn (Forms\Get $get) => BankAccount::find($get('bank_account_id'))?->account?->currency_code ?? 'USD')
  53. ->required(),
  54. Forms\Components\Select::make('account_id')
  55. ->label('Category')
  56. ->options(static fn (Forms\Get $get) => static::getAccountOptions($get('type')))
  57. ->searchable()
  58. ->preload()
  59. ->required(),
  60. Forms\Components\Textarea::make('notes')
  61. ->label('Notes')
  62. ->autosize()
  63. ->rows(10)
  64. ->columnSpanFull(),
  65. ]);
  66. }
  67. public static function table(Table $table): Table
  68. {
  69. return $table
  70. ->columns([
  71. Tables\Columns\TextColumn::make('posted_at')
  72. ->label('Date')
  73. ->sortable()
  74. ->formatStateUsing(static function ($state) {
  75. $dateFormat = Localization::firstOrFail()->date_format->value ?? DateFormat::DEFAULT;
  76. return Carbon::parse($state)->translatedFormat($dateFormat);
  77. }),
  78. Tables\Columns\TextColumn::make('description')
  79. ->limit(30)
  80. ->label('Description'),
  81. Tables\Columns\TextColumn::make('bankAccount.account.name')
  82. ->label('Account')
  83. ->sortable(),
  84. Tables\Columns\TextColumn::make('account.name')
  85. ->label('Category'),
  86. Tables\Columns\TextColumn::make('amount')
  87. ->label('Amount')
  88. ->sortable()
  89. ->weight(static fn (Transaction $record) => $record->reviewed ? null : FontWeight::SemiBold)
  90. ->color(static fn (Transaction $record) => $record->type === 'deposit' ? Color::rgb('rgb(' . Color::Green[700] . ')') : null)
  91. ->currency(static fn (Transaction $record) => $record->bankAccount->account->currency_code ?? 'USD', true),
  92. ])
  93. ->recordClasses(static fn (Transaction $record) => $record->reviewed ? 'bg-primary-300/10' : null)
  94. ->defaultSort('posted_at', 'desc')
  95. ->filters([
  96. //
  97. ])
  98. ->actions([
  99. Tables\Actions\Action::make('markAsReviewed')
  100. ->label('Mark as Reviewed')
  101. ->view('filament.company.components.tables.actions.mark-as-reviewed')
  102. ->icon(static fn (Transaction $record) => $record->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
  103. ->color(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
  104. 'reviewed' => 'primary',
  105. 'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
  106. 'uncategorized' => 'gray',
  107. })
  108. ->tooltip(static fn (Transaction $record, Tables\Actions\Action $action) => match (static::determineTransactionState($record, $action)) {
  109. 'reviewed' => 'Reviewed',
  110. 'unreviewed' => 'Mark as Reviewed',
  111. 'uncategorized' => 'Categorize first to mark as reviewed',
  112. })
  113. ->disabled(static fn (Transaction $record) => in_array($record->account->type, [AccountType::UncategorizedRevenue, AccountType::UncategorizedExpense], true))
  114. ->action(fn (Transaction $record) => $record->update(['reviewed' => ! $record->reviewed])),
  115. Tables\Actions\ActionGroup::make([
  116. Tables\Actions\EditAction::make()
  117. ->modalWidth(MaxWidth::ThreeExtraLarge)
  118. ->stickyModalHeader()
  119. ->stickyModalFooter(),
  120. Tables\Actions\DeleteAction::make(),
  121. Tables\Actions\ReplicateAction::make()
  122. ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
  123. ->modal(false)
  124. ->beforeReplicaSaved(static function (Transaction $replica) {
  125. $replica->description = '(Copy of) ' . $replica->description;
  126. }),
  127. ])
  128. ->dropdownPlacement('bottom-start')
  129. ->dropdownWidth('max-w-fit'),
  130. ])
  131. ->bulkActions([
  132. Tables\Actions\BulkActionGroup::make([
  133. Tables\Actions\DeleteBulkAction::make(),
  134. ]),
  135. ]);
  136. }
  137. protected static function determineTransactionState(Transaction $record, Tables\Actions\Action $action): string
  138. {
  139. if ($record->reviewed) {
  140. return 'reviewed';
  141. }
  142. if ($record->reviewed === false && $action->isEnabled()) {
  143. return 'unreviewed';
  144. }
  145. return 'uncategorized';
  146. }
  147. public static function getRelations(): array
  148. {
  149. return [
  150. //
  151. ];
  152. }
  153. public static function getPages(): array
  154. {
  155. return [
  156. 'index' => Pages\ManageTransaction::route('/'),
  157. ];
  158. }
  159. public static function getBankAccountOptions(): array
  160. {
  161. $bankAccounts = BankAccount::with('account.subtype')->get();
  162. return $bankAccounts->groupBy('account.subtype.name')
  163. ->map(fn (Collection $bankAccounts) => $bankAccounts->pluck('account.name', 'id'))
  164. ->toArray();
  165. }
  166. public static function getAccountOptions(string $type)
  167. {
  168. $excludedCategory = match ($type) {
  169. 'deposit' => AccountCategory::Expense,
  170. 'withdrawal' => AccountCategory::Revenue,
  171. };
  172. $accounts = Account::whereNot('category', $excludedCategory)->get();
  173. return $accounts->groupBy(fn (Account $account) => $account->category->getLabel())
  174. ->map(fn (Collection $accounts, string $category) => $accounts->mapWithKeys(static fn (Account $account) => [$account->id => $account->name]))
  175. ->toArray();
  176. }
  177. }