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.

CustomerResource.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. namespace App\Filament\Resources;
  3. use App\Actions\Banking\CreateCurrencyFromAccount;
  4. use App\Filament\Resources\CustomerResource\Pages;
  5. use App\Filament\Resources\CustomerResource\RelationManagers;
  6. use Wallo\FilamentSelectify\Components\ButtonGroup;
  7. use App\Models\Banking\Account;
  8. use App\Models\Contact;
  9. use Filament\Forms;
  10. use Filament\Resources\Form;
  11. use Filament\Resources\Resource;
  12. use Filament\Resources\Table;
  13. use Filament\Tables;
  14. use Illuminate\Database\Eloquent\Builder;
  15. use Illuminate\Database\Eloquent\SoftDeletingScope;
  16. use Illuminate\Support\Facades\Auth;
  17. use Illuminate\Support\Facades\DB;
  18. use Squire\Models\Country;
  19. use Squire\Models\Region;
  20. class CustomerResource extends Resource
  21. {
  22. protected static ?string $model = Contact::class;
  23. protected static ?string $navigationIcon = 'heroicon-o-user-group';
  24. protected static ?string $navigationGroup = 'Sales';
  25. protected static ?string $navigationLabel = 'Customers';
  26. protected static ?string $modelLabel = 'customer';
  27. public static function getEloquentQuery(): Builder
  28. {
  29. return parent::getEloquentQuery()
  30. ->customer()
  31. ->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\Grid::make(3)
  40. ->schema([
  41. ButtonGroup::make('entity')
  42. ->label('Entity')
  43. ->options([
  44. 'individual' => 'Individual',
  45. 'company' => 'Company',
  46. ])
  47. ->gridDirection('column')
  48. ->default('individual')
  49. ->columnSpan(1)
  50. ->required(),
  51. Forms\Components\Grid::make()
  52. ->schema([
  53. Forms\Components\TextInput::make('name')
  54. ->label('Name')
  55. ->maxLength(100)
  56. ->required(),
  57. Forms\Components\TextInput::make('email')
  58. ->label('Email')
  59. ->email()
  60. ->nullable(),
  61. Forms\Components\TextInput::make('phone')
  62. ->label('Phone')
  63. ->tel()
  64. ->maxLength(20),
  65. Forms\Components\TextInput::make('website')
  66. ->label('Website')
  67. ->maxLength(100)
  68. ->url()
  69. ->nullable(),
  70. Forms\Components\TextInput::make('reference')
  71. ->label('Reference')
  72. ->maxLength(100)
  73. ->columnSpan(2)
  74. ->nullable(),
  75. ])->columnSpan(2),
  76. ]),
  77. ])->columns(),
  78. Forms\Components\Section::make('Billing')
  79. ->schema([
  80. Forms\Components\TextInput::make('tax_number')
  81. ->label('Tax Number')
  82. ->maxLength(100)
  83. ->nullable(),
  84. Forms\Components\Select::make('currency_code')
  85. ->label('Currency')
  86. ->relationship('currency', 'name', static fn (Builder $query) => $query->where('company_id', Auth::user()->currentCompany->id))
  87. ->preload()
  88. ->default(Account::getDefaultCurrencyCode())
  89. ->searchable()
  90. ->reactive()
  91. ->required()
  92. ->createOptionForm([
  93. Forms\Components\Select::make('currency.code')
  94. ->label('Code')
  95. ->searchable()
  96. ->options(Account::getCurrencyCodes())
  97. ->reactive()
  98. ->afterStateUpdated(static function (callable $set, $state) {
  99. $code = $state;
  100. $name = config("money.{$code}.name");
  101. $set('currency.name', $name);
  102. })
  103. ->required(),
  104. Forms\Components\TextInput::make('currency.name')
  105. ->label('Name')
  106. ->maxLength(100)
  107. ->required(),
  108. Forms\Components\TextInput::make('currency.rate')
  109. ->label('Rate')
  110. ->numeric()
  111. ->mask(static fn (Forms\Components\TextInput\Mask $mask) => $mask
  112. ->numeric()
  113. ->decimalPlaces(4)
  114. ->signed(false)
  115. ->padFractionalZeros(false)
  116. ->normalizeZeros(false)
  117. ->minValue(0.0001)
  118. ->maxValue(999999.9999)
  119. ->lazyPlaceholder(false))
  120. ->required(),
  121. ])->createOptionAction(static function (Forms\Components\Actions\Action $action) {
  122. return $action
  123. ->label('Add Currency')
  124. ->modalHeading('Add Currency')
  125. ->modalButton('Add')
  126. ->action(static function (array $data) {
  127. return DB::transaction(static function () use ($data) {
  128. $code = $data['currency']['code'];
  129. $name = $data['currency']['name'];
  130. $rate = $data['currency']['rate'];
  131. return (new CreateCurrencyFromAccount())->create($code, $name, $rate);
  132. });
  133. });
  134. }),
  135. ])->columns(2),
  136. Forms\Components\Section::make('Address')
  137. ->schema([
  138. Forms\Components\TextInput::make('address')
  139. ->label('Address')
  140. ->maxLength(100)
  141. ->columnSpanFull()
  142. ->nullable(),
  143. Forms\Components\Select::make('country')
  144. ->label('Country')
  145. ->searchable()
  146. ->reactive()
  147. ->options(Contact::getCountryOptions())
  148. ->nullable(),
  149. Forms\Components\Select::make('doesnt_exist') // TODO: Remove this when we have a better way to handle the searchable select when disabled
  150. ->label('Province/State')
  151. ->disabled()
  152. ->hidden(static fn (callable $get) => $get('country') !== null),
  153. Forms\Components\Select::make('state')
  154. ->label('Province/State')
  155. ->hidden(static fn (callable $get) => $get('country') === null)
  156. ->options(static function (callable $get) {
  157. $country = $get('country');
  158. if (! $country) {
  159. return [];
  160. }
  161. return Contact::getRegionOptions($country);
  162. })
  163. ->searchable()
  164. ->nullable(),
  165. Forms\Components\TextInput::make('city')
  166. ->label('Town/City')
  167. ->maxLength(100)
  168. ->nullable(),
  169. Forms\Components\TextInput::make('zip_code')
  170. ->label('Postal/Zip Code')
  171. ->maxLength(100)
  172. ->nullable(),
  173. ])->columns(),
  174. ]);
  175. }
  176. public static function table(Table $table): Table
  177. {
  178. return $table
  179. ->columns([
  180. Tables\Columns\TextColumn::make('name')
  181. ->label('Name')
  182. ->weight('semibold')
  183. ->description(static fn (Contact $record) => $record->tax_number ?: 'N/A')
  184. ->searchable()
  185. ->sortable(),
  186. Tables\Columns\TextColumn::make('email')
  187. ->label('Email')
  188. ->formatStateUsing(static fn (Contact $record) => $record->email ?: 'N/A')
  189. ->description(static fn (Contact $record) => $record->phone ?: 'N/A')
  190. ->searchable()
  191. ->sortable(),
  192. Tables\Columns\TextColumn::make('country')
  193. ->label('Country')
  194. ->searchable()
  195. ->formatStateUsing(static fn (Contact $record) => $record->country ?: 'N/A')
  196. ->description(static fn (Contact $record) => $record->currency->name ?: 'N/A')
  197. ->sortable(),
  198. ])
  199. ->filters([
  200. //
  201. ])
  202. ->actions([
  203. Tables\Actions\EditAction::make(),
  204. ])
  205. ->bulkActions([
  206. Tables\Actions\DeleteBulkAction::make(),
  207. ]);
  208. }
  209. public static function getRelations(): array
  210. {
  211. return [
  212. //
  213. ];
  214. }
  215. public static function getPages(): array
  216. {
  217. return [
  218. 'index' => Pages\ListCustomers::route('/'),
  219. 'create' => Pages\CreateCustomer::route('/create'),
  220. 'edit' => Pages\EditCustomer::route('/{record}/edit'),
  221. ];
  222. }
  223. }