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.

CurrencyResource.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. namespace App\Filament\Resources;
  3. use App\Filament\Resources\CurrencyResource\Pages;
  4. use App\Models\Banking\Account;
  5. use App\Models\Setting\Currency;
  6. use App\Services\CurrencyService;
  7. use Closure;
  8. use Exception;
  9. use Filament\Forms;
  10. use Filament\Notifications\Notification;
  11. use Filament\Resources\Form;
  12. use Filament\Resources\Resource;
  13. use Filament\Resources\Table;
  14. use Filament\Tables;
  15. use Illuminate\Support\Collection;
  16. use Illuminate\Support\Facades\Auth;
  17. use Wallo\FilamentSelectify\Components\ToggleButton;
  18. class CurrencyResource extends Resource
  19. {
  20. protected static ?string $model = Currency::class;
  21. protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
  22. protected static ?string $navigationGroup = 'Settings';
  23. public static function form(Form $form): Form
  24. {
  25. return $form
  26. ->schema([
  27. Forms\Components\Section::make('General')
  28. ->description('Upon selecting a currency code, the corresponding values based on real-world currencies will auto-populate. The default currency is used for all transactions and reports and cannot be deleted. Currency precision determines the number of decimal places to display when formatting currency amounts. The currency rate is set to 1 for the default currency and is utilized as the basis for setting exchange rates for all other currencies. Alterations to default values are allowed but manage such changes wisely as any confusion or discrepancies are your responsibility.')
  29. ->schema([
  30. Forms\Components\Select::make('code')
  31. ->label('Code')
  32. ->options(Currency::getCurrencyCodes())
  33. ->searchable()
  34. ->placeholder('Select a currency code...')
  35. ->reactive()
  36. ->hidden(static fn (Closure $get): bool => $get('enabled'))
  37. ->afterStateUpdated(static function (Closure $set, $state) {
  38. if ($state === null) {
  39. return;
  40. }
  41. $code = $state;
  42. $currencyConfig = config("money.{$code}", []);
  43. $currencyService = app(CurrencyService::class);
  44. $defaultCurrency = Currency::getDefaultCurrency();
  45. $rate = 1;
  46. if ($defaultCurrency !== null) {
  47. $rate = $currencyService->getCachedExchangeRate($defaultCurrency, $code);
  48. }
  49. $set('name', $currencyConfig['name'] ?? '');
  50. $set('rate', $rate);
  51. $set('precision', $currencyConfig['precision'] ?? '');
  52. $set('symbol', $currencyConfig['symbol'] ?? '');
  53. $set('symbol_first', $currencyConfig['symbol_first'] ?? '');
  54. $set('decimal_mark', $currencyConfig['decimal_mark'] ?? '');
  55. $set('thousands_separator', $currencyConfig['thousands_separator'] ?? '');
  56. })
  57. ->required(),
  58. Forms\Components\TextInput::make('code')
  59. ->label('Code')
  60. ->hidden(static fn (Closure $get): bool => !$get('enabled'))
  61. ->disabled(static fn (Closure $get): bool => $get('enabled'))
  62. ->required(),
  63. Forms\Components\TextInput::make('name')
  64. ->translateLabel()
  65. ->maxLength(100)
  66. ->required(),
  67. Forms\Components\TextInput::make('rate')
  68. ->label('Rate')
  69. ->dehydrateStateUsing(static fn (Closure $get, $state) => $get('enabled') ? '1' : $state)
  70. ->numeric()
  71. ->reactive()
  72. ->disabled(static fn (Closure $get): bool => $get('enabled'))
  73. ->required(),
  74. Forms\Components\Select::make('precision')
  75. ->label('Precision')
  76. ->searchable()
  77. ->placeholder('Select the currency precision...')
  78. ->options(['0', '1', '2', '3', '4'])
  79. ->required(),
  80. Forms\Components\TextInput::make('symbol')
  81. ->label('Symbol')
  82. ->maxLength(5)
  83. ->required(),
  84. Forms\Components\Select::make('symbol_first')
  85. ->label('Symbol Position')
  86. ->searchable()
  87. ->boolean('Before Amount', 'After Amount', 'Select the currency symbol position...')
  88. ->required(),
  89. Forms\Components\TextInput::make('decimal_mark')
  90. ->label('Decimal Separator')
  91. ->maxLength(1)
  92. ->required(),
  93. Forms\Components\TextInput::make('thousands_separator')
  94. ->label('Thousands Separator')
  95. ->maxLength(1)
  96. ->required(),
  97. ToggleButton::make('enabled')
  98. ->label('Default Currency')
  99. ->reactive()
  100. ->offColor('danger')
  101. ->onColor('primary')
  102. ->afterStateUpdated(static function (Closure $set, Closure $get, $state) {
  103. $enabled = $state;
  104. $code = $get('code');
  105. $currencyService = app(CurrencyService::class);
  106. if ($enabled) {
  107. $rate = 1;
  108. } else {
  109. $defaultCurrency = Currency::getDefaultCurrency();
  110. $rate = $defaultCurrency ? $currencyService->getCachedExchangeRate($defaultCurrency, $code) : 1;
  111. }
  112. $set('rate', $rate);
  113. }),
  114. ])->columns(),
  115. ]);
  116. }
  117. /**
  118. * @throws Exception
  119. */
  120. public static function table(Table $table): Table
  121. {
  122. return $table
  123. ->columns([
  124. Tables\Columns\TextColumn::make('name')
  125. ->label('Name')
  126. ->weight('semibold')
  127. ->icon(static fn (Currency $record) => $record->enabled ? 'heroicon-o-lock-closed' : null)
  128. ->tooltip(static fn (Currency $record) => $record->enabled ? 'Default Currency' : null)
  129. ->iconPosition('after')
  130. ->searchable()
  131. ->sortable(),
  132. Tables\Columns\TextColumn::make('code')
  133. ->label('Code')
  134. ->searchable()
  135. ->sortable(),
  136. Tables\Columns\TextColumn::make('symbol')
  137. ->label('Symbol')
  138. ->searchable()
  139. ->sortable(),
  140. Tables\Columns\TextColumn::make('rate')
  141. ->label('Rate')
  142. ->searchable()
  143. ->sortable(),
  144. ])
  145. ->filters([
  146. //
  147. ])
  148. ->actions([
  149. Tables\Actions\EditAction::make(),
  150. Tables\Actions\DeleteAction::make()
  151. ->before(static function (Tables\Actions\DeleteAction $action, Currency $record) {
  152. $defaultCurrency = $record->enabled;
  153. $accountUsesCurrency = Account::where('currency_code', $record->code)->exists();
  154. if ($defaultCurrency) {
  155. Notification::make()
  156. ->danger()
  157. ->title('Action Denied')
  158. ->body(__('The :name currency is currently set as the default currency and cannot be deleted. Please set a different currency as your default before attempting to delete this one.', ['name' => $record->name]))
  159. ->persistent()
  160. ->send();
  161. $action->cancel();
  162. } elseif ($accountUsesCurrency) {
  163. Notification::make()
  164. ->danger()
  165. ->title('Action Denied')
  166. ->body(__('The :name currency is currently in use by one or more accounts and cannot be deleted. Please remove this currency from all accounts before attempting to delete it.', ['name' => $record->name]))
  167. ->persistent()
  168. ->send();
  169. $action->cancel();
  170. }
  171. }),
  172. ])
  173. ->bulkActions([
  174. Tables\Actions\DeleteBulkAction::make()
  175. ->before(static function (Tables\Actions\DeleteBulkAction $action, Collection $records) {
  176. foreach ($records as $record) {
  177. $defaultCurrency = $record->enabled;
  178. $accountUsesCurrency = Account::where('currency_code', $record->code)->exists();
  179. if ($defaultCurrency) {
  180. Notification::make()
  181. ->danger()
  182. ->title('Action Denied')
  183. ->body(__('The :name currency is currently set as the default currency and cannot be deleted. Please set a different currency as your default before attempting to delete this one.', ['name' => $record->name]))
  184. ->persistent()
  185. ->send();
  186. $action->cancel();
  187. } elseif ($accountUsesCurrency) {
  188. Notification::make()
  189. ->danger()
  190. ->title('Action Denied')
  191. ->body(__('The :name currency is currently in use by one or more accounts and cannot be deleted. Please remove this currency from all accounts before attempting to delete it.', ['name' => $record->name]))
  192. ->persistent()
  193. ->send();
  194. $action->cancel();
  195. }
  196. }
  197. }),
  198. ]);
  199. }
  200. public static function getSlug(): string
  201. {
  202. return '{company}/settings/currencies';
  203. }
  204. public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
  205. {
  206. $routeBaseName = static::getRouteBaseName();
  207. return route("{$routeBaseName}.{$name}", [
  208. 'company' => Auth::user()->currentCompany,
  209. 'record' => $params['record'] ?? null,
  210. ], $isAbsolute);
  211. }
  212. public static function getRelations(): array
  213. {
  214. return [
  215. //
  216. ];
  217. }
  218. public static function getPages(): array
  219. {
  220. return [
  221. 'index' => Pages\ListCurrencies::route('/'),
  222. 'create' => Pages\CreateCurrency::route('/create'),
  223. 'edit' => Pages\EditCurrency::route('/{record}/edit'),
  224. ];
  225. }
  226. }