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.

AccountResource.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. namespace App\Filament\Resources;
  3. use App\Actions\Banking\CreateCurrencyFromAccount;
  4. use App\Filament\Resources\AccountResource\Pages;
  5. use App\Filament\Resources\AccountResource\RelationManagers;
  6. use App\Models\Banking\Account;
  7. use Closure;
  8. use Exception;
  9. use Filament\Forms;
  10. use Filament\Forms\Components\TextInput\Mask;
  11. use Filament\Notifications\Notification;
  12. use Filament\Resources\Form;
  13. use Filament\Resources\Resource;
  14. use Filament\Resources\Table;
  15. use Filament\Tables;
  16. use Illuminate\Database\Eloquent\Builder;
  17. use Illuminate\Database\Eloquent\Model;
  18. use Illuminate\Database\Eloquent\SoftDeletingScope;
  19. use Illuminate\Support\Collection;
  20. use Illuminate\Support\Facades\Auth;
  21. use Illuminate\Support\Facades\DB;
  22. use Illuminate\Validation\Rule;
  23. use Illuminate\Validation\Rules\Unique;
  24. class AccountResource extends Resource
  25. {
  26. protected static ?string $model = Account::class;
  27. protected static ?string $navigationIcon = 'heroicon-o-credit-card';
  28. protected static ?string $navigationGroup = 'Banking';
  29. public static function getEloquentQuery(): Builder
  30. {
  31. return parent::getEloquentQuery()->where('company_id', Auth::user()->currentCompany->id);
  32. }
  33. public static function form(Form $form): Form
  34. {
  35. return $form
  36. ->schema([
  37. Forms\Components\Section::make('General')
  38. ->schema([
  39. Forms\Components\Radio::make('type')
  40. ->label('Type')
  41. ->options(Account::getAccountTypes())
  42. ->inline()
  43. ->default('bank')
  44. ->reactive()
  45. ->afterStateUpdated(static fn (Closure $set, $state) => $state === 'card' ? $set('enabled', 'hidden'): $set('enabled', null))
  46. ->required()
  47. ->columnSpanFull(),
  48. Forms\Components\TextInput::make('name')
  49. ->label('Name')
  50. ->maxLength(100)
  51. ->required(),
  52. Forms\Components\TextInput::make('number')
  53. ->label('Account Number')
  54. ->unique(callback: static function (Unique $rule, $state) {
  55. $companyId = Auth::user()->currentCompany->id;
  56. return $rule->where('company_id', $companyId)->where('number', $state);
  57. }, ignoreRecord: true)
  58. ->maxLength(20)
  59. ->required(),
  60. Forms\Components\Select::make('currency_code')
  61. ->label('Currency')
  62. ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
  63. ->preload()
  64. ->default(Account::getDefaultCurrencyCode())
  65. ->searchable()
  66. ->reactive()
  67. ->required()
  68. ->createOptionForm([
  69. Forms\Components\Select::make('currency.code')
  70. ->label('Code')
  71. ->searchable()
  72. ->options(Account::getCurrencyCodes())
  73. ->reactive()
  74. ->afterStateUpdated(static function (callable $set, $state) {
  75. $code = $state;
  76. $name = config("money.{$code}.name");
  77. $set('currency.name', $name);
  78. })
  79. ->required(),
  80. Forms\Components\TextInput::make('currency.name')
  81. ->label('Name')
  82. ->maxLength(100)
  83. ->required(),
  84. Forms\Components\TextInput::make('currency.rate')
  85. ->label('Rate')
  86. ->numeric()
  87. ->mask(static fn (Forms\Components\TextInput\Mask $mask) => $mask
  88. ->numeric()
  89. ->decimalPlaces(4)
  90. ->signed(false)
  91. ->padFractionalZeros(false)
  92. ->normalizeZeros(false)
  93. ->minValue(0.0001)
  94. ->maxValue(999999.9999)
  95. ->lazyPlaceholder(false))
  96. ->required(),
  97. ])->createOptionAction(static function (Forms\Components\Actions\Action $action) {
  98. return $action
  99. ->label('Add Currency')
  100. ->modalHeading('Add Currency')
  101. ->modalButton('Add')
  102. ->action(static function (array $data) {
  103. return DB::transaction(static function () use ($data) {
  104. $code = $data['currency']['code'];
  105. $name = $data['currency']['name'];
  106. $rate = $data['currency']['rate'];
  107. return (new CreateCurrencyFromAccount())->create($code, $name, $rate);
  108. });
  109. });
  110. }),
  111. Forms\Components\TextInput::make('opening_balance')
  112. ->label('Opening Balance')
  113. ->required()
  114. ->default('0')
  115. ->numeric()
  116. ->mask(static fn (Forms\Components\TextInput\Mask $mask, Closure $get) => $mask
  117. ->patternBlocks([
  118. 'money' => static fn (Mask $mask) => $mask
  119. ->numeric()
  120. ->decimalPlaces(config('money.' . $get('currency_code') . '.precision'))
  121. ->decimalSeparator(config('money.' . $get('currency_code') . '.decimal_mark'))
  122. ->thousandsSeparator(config('money.' . $get('currency_code') . '.thousands_separator'))
  123. ->signed()
  124. ->padFractionalZeros()
  125. ->normalizeZeros(false),
  126. ])
  127. ->pattern(config('money.' . $get('currency_code') . '.symbol_first') ? config('money.' . $get('currency_code') . '.symbol') . 'money' : 'money' . config('money.' . $get('currency_code') . '.symbol'))
  128. ->lazyPlaceholder(false)),
  129. Forms\Components\Toggle::make('enabled')
  130. ->hidden(fn (Closure $get) => $get('type') === 'card')
  131. ->label('Default Account'),
  132. ])->columns(),
  133. Forms\Components\Section::make('Bank')
  134. ->schema([
  135. Forms\Components\TextInput::make('bank_name')
  136. ->label('Bank Name')
  137. ->maxLength(100),
  138. Forms\Components\TextInput::make('bank_phone')
  139. ->label('Bank Phone')
  140. ->mask(static fn (Forms\Components\TextInput\Mask $mask) => $mask->pattern('(000) 000-0000'))
  141. ->maxLength(20),
  142. Forms\Components\Textarea::make('bank_address')
  143. ->label('Bank Address')
  144. ->columnSpanFull(),
  145. ])->columns(),
  146. ]);
  147. }
  148. /**
  149. * @throws Exception
  150. */
  151. public static function table(Table $table): Table
  152. {
  153. return $table
  154. ->columns([
  155. Tables\Columns\TextColumn::make('name')
  156. ->searchable()
  157. ->weight('semibold')
  158. ->icon(static fn (Account $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
  159. ->tooltip(static fn (Account $record) => $record->enabled ? 'Default Account' : null)
  160. ->iconPosition('after')
  161. ->sortable(),
  162. Tables\Columns\TextColumn::make('number')
  163. ->label('Account Number')
  164. ->searchable()
  165. ->sortable(),
  166. Tables\Columns\TextColumn::make('bank_name')
  167. ->label('Bank Name')
  168. ->searchable()
  169. ->sortable(),
  170. Tables\Columns\TextColumn::make('bank_phone')
  171. ->label('Phone')
  172. ->formatStateUsing(static fn ($record) => ($record->bank_phone !== '') ? vsprintf('(%d%d%d) %d%d%d-%d%d%d%d', str_split($record->bank_phone)) : '-'),
  173. Tables\Columns\TextColumn::make('opening_balance')
  174. ->label('Current Balance')
  175. ->sortable()
  176. ->money(static fn ($record) => $record->currency_code, true),
  177. ])
  178. ->filters([
  179. //
  180. ])
  181. ->actions([
  182. Tables\Actions\EditAction::make(),
  183. Tables\Actions\DeleteAction::make()
  184. ->before(static function (Tables\Actions\DeleteAction $action, Account $record) {
  185. if ($record->enabled) {
  186. Notification::make()
  187. ->danger()
  188. ->title('Action Denied')
  189. ->body(__('The :name account is currently set as your default account and cannot be deleted. Please set a different account as your default before attempting to delete this one.', ['name' => $record->name]))
  190. ->persistent()
  191. ->send();
  192. $action->cancel();
  193. }
  194. }),
  195. ])
  196. ->bulkActions([
  197. Tables\Actions\DeleteBulkAction::make()
  198. ->before(static function (Tables\Actions\DeleteBulkAction $action, Collection $records) {
  199. foreach ($records as $record) {
  200. if ($record->enabled) {
  201. Notification::make()
  202. ->danger()
  203. ->title('Action Denied')
  204. ->body(__('The :name account is currently set as your default account and cannot be deleted. Please set a different account as your default before attempting to delete this one.', ['name' => $record->name]))
  205. ->persistent()
  206. ->send();
  207. $action->cancel();
  208. }
  209. }
  210. }),
  211. ]);
  212. }
  213. public static function getRelations(): array
  214. {
  215. return [
  216. //
  217. ];
  218. }
  219. public static function getPages(): array
  220. {
  221. return [
  222. 'index' => Pages\ListAccounts::route('/'),
  223. 'create' => Pages\CreateAccount::route('/create'),
  224. 'edit' => Pages\EditAccount::route('/{record}/edit'),
  225. ];
  226. }
  227. }