您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

VendorResource.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. namespace App\Filament\Company\Resources\Purchases;
  3. use App\Enums\Accounting\BillStatus;
  4. use App\Enums\Common\ContractorType;
  5. use App\Enums\Common\VendorType;
  6. use App\Filament\Company\Resources\Purchases\VendorResource\Pages;
  7. use App\Filament\Exports\Common\VendorExporter;
  8. use App\Filament\Forms\Components\AddressFields;
  9. use App\Filament\Forms\Components\CreateCurrencySelect;
  10. use App\Filament\Forms\Components\CustomSection;
  11. use App\Filament\Forms\Components\PhoneBuilder;
  12. use App\Filament\Tables\Columns;
  13. use App\Models\Common\Vendor;
  14. use App\Utilities\Currency\CurrencyConverter;
  15. use Filament\Forms;
  16. use Filament\Forms\Form;
  17. use Filament\Resources\Resource;
  18. use Filament\Tables;
  19. use Filament\Tables\Table;
  20. use Illuminate\Database\Eloquent\Builder;
  21. class VendorResource extends Resource
  22. {
  23. protected static ?string $model = Vendor::class;
  24. public static function form(Form $form): Form
  25. {
  26. return $form
  27. ->schema([
  28. Forms\Components\Section::make('General Information')
  29. ->schema([
  30. Forms\Components\Group::make()
  31. ->columns(2)
  32. ->schema([
  33. Forms\Components\TextInput::make('name')
  34. ->label('Vendor name')
  35. ->required()
  36. ->maxLength(255),
  37. Forms\Components\Radio::make('type')
  38. ->label('Vendor type')
  39. ->required()
  40. ->live()
  41. ->options(VendorType::class)
  42. ->default(VendorType::Regular)
  43. ->columnSpanFull(),
  44. CreateCurrencySelect::make('currency_code')
  45. ->softRequired()
  46. ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
  47. Forms\Components\Select::make('contractor_type')
  48. ->label('Contractor type')
  49. ->required()
  50. ->live()
  51. ->visible(static fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Contractor)
  52. ->options(ContractorType::class),
  53. Forms\Components\TextInput::make('ssn')
  54. ->label('Social security number')
  55. ->required()
  56. ->live()
  57. ->mask('999-99-9999')
  58. ->stripCharacters('-')
  59. ->maxLength(11)
  60. ->visible(static fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Individual)
  61. ->maxLength(255),
  62. Forms\Components\TextInput::make('ein')
  63. ->label('Employer identification number')
  64. ->required()
  65. ->live()
  66. ->mask('99-9999999')
  67. ->stripCharacters('-')
  68. ->maxLength(10)
  69. ->visible(static fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Business)
  70. ->maxLength(255),
  71. Forms\Components\TextInput::make('account_number')
  72. ->maxLength(255),
  73. Forms\Components\TextInput::make('website')
  74. ->maxLength(255),
  75. Forms\Components\Textarea::make('notes')
  76. ->columnSpanFull(),
  77. ]),
  78. CustomSection::make('Primary Contact')
  79. ->relationship('contact')
  80. ->saveRelationshipsUsing(null)
  81. ->saveRelationshipsBeforeChildrenUsing(null)
  82. ->dehydrated(true)
  83. ->contained(false)
  84. ->schema([
  85. Forms\Components\Hidden::make('is_primary')
  86. ->default(true),
  87. Forms\Components\TextInput::make('first_name')
  88. ->label('First name')
  89. ->maxLength(255),
  90. Forms\Components\TextInput::make('last_name')
  91. ->label('Last name')
  92. ->maxLength(255),
  93. Forms\Components\TextInput::make('email')
  94. ->label('Email')
  95. ->email()
  96. ->columnSpanFull()
  97. ->maxLength(255),
  98. PhoneBuilder::make('phones')
  99. ->hiddenLabel()
  100. ->blockLabels(false)
  101. ->default([
  102. ['type' => 'primary'],
  103. ])
  104. ->columnSpanFull()
  105. ->blocks([
  106. Forms\Components\Builder\Block::make('primary')
  107. ->schema([
  108. Forms\Components\TextInput::make('number')
  109. ->label('Phone')
  110. ->maxLength(15),
  111. ])->maxItems(1),
  112. Forms\Components\Builder\Block::make('mobile')
  113. ->schema([
  114. Forms\Components\TextInput::make('number')
  115. ->label('Mobile')
  116. ->maxLength(15),
  117. ])->maxItems(1),
  118. Forms\Components\Builder\Block::make('toll_free')
  119. ->schema([
  120. Forms\Components\TextInput::make('number')
  121. ->label('Toll free')
  122. ->maxLength(15),
  123. ])->maxItems(1),
  124. Forms\Components\Builder\Block::make('fax')
  125. ->schema([
  126. Forms\Components\TextInput::make('number')
  127. ->label('Fax')
  128. ->live()
  129. ->maxLength(15),
  130. ])->maxItems(1),
  131. ])
  132. ->deletable(fn (PhoneBuilder $builder) => $builder->getItemsCount() > 1)
  133. ->reorderable(false)
  134. ->blockNumbers(false)
  135. ->addActionLabel('Add Phone'),
  136. ])->columns(),
  137. ])->columns(1),
  138. Forms\Components\Section::make('Address Information')
  139. ->relationship('address')
  140. ->saveRelationshipsUsing(null)
  141. ->saveRelationshipsBeforeChildrenUsing(null)
  142. ->dehydrated(true)
  143. ->schema([
  144. Forms\Components\Hidden::make('type')
  145. ->default('general'),
  146. AddressFields::make(),
  147. ])
  148. ->columns(2),
  149. ]);
  150. }
  151. public static function table(Table $table): Table
  152. {
  153. return $table
  154. ->columns([
  155. Columns::id(),
  156. Tables\Columns\TextColumn::make('type')
  157. ->badge()
  158. ->searchable()
  159. ->sortable(),
  160. Tables\Columns\TextColumn::make('name')
  161. ->searchable()
  162. ->sortable()
  163. ->description(static fn (Vendor $vendor) => $vendor->contact?->full_name),
  164. Tables\Columns\TextColumn::make('contact.email')
  165. ->label('Email')
  166. ->searchable(),
  167. Tables\Columns\TextColumn::make('contact.first_available_phone')
  168. ->label('Phone')
  169. ->state(static fn (Vendor $vendor) => $vendor->contact?->first_available_phone),
  170. Tables\Columns\TextColumn::make('address.address_string')
  171. ->label('Address')
  172. ->searchable()
  173. ->toggleable(isToggledHiddenByDefault: true)
  174. ->listWithLineBreaks(),
  175. Tables\Columns\TextColumn::make('payable_balance')
  176. ->label('Payable balance')
  177. ->getStateUsing(function (Vendor $vendor) {
  178. return $vendor->bills()
  179. ->unpaid()
  180. ->get()
  181. ->sumMoneyInDefaultCurrency('amount_due');
  182. })
  183. ->coloredDescription(function (Vendor $vendor) {
  184. $overdue = $vendor->bills()
  185. ->where('status', BillStatus::Overdue)
  186. ->get()
  187. ->sumMoneyInDefaultCurrency('amount_due');
  188. if ($overdue <= 0) {
  189. return null;
  190. }
  191. $formattedOverdue = CurrencyConverter::formatCentsToMoney($overdue);
  192. return "Overdue: {$formattedOverdue}";
  193. })
  194. ->sortable(query: function (Builder $query, string $direction) {
  195. return $query
  196. ->withSum(['bills' => fn (Builder $query) => $query->unpaid()], 'amount_due')
  197. ->orderBy('bills_sum_amount_due', $direction);
  198. })
  199. ->currency(convert: false)
  200. ->alignEnd(),
  201. ])
  202. ->filters([
  203. //
  204. ])
  205. ->headerActions([
  206. Tables\Actions\ExportAction::make()
  207. ->exporter(VendorExporter::class),
  208. ])
  209. ->actions([
  210. Tables\Actions\ActionGroup::make([
  211. Tables\Actions\ActionGroup::make([
  212. Tables\Actions\EditAction::make(),
  213. Tables\Actions\ViewAction::make(),
  214. ])->dropdown(false),
  215. Tables\Actions\DeleteAction::make(),
  216. ]),
  217. ])
  218. ->bulkActions([
  219. //
  220. ]);
  221. }
  222. public static function getRelations(): array
  223. {
  224. return [
  225. //
  226. ];
  227. }
  228. public static function getPages(): array
  229. {
  230. return [
  231. 'index' => Pages\ListVendors::route('/'),
  232. 'create' => Pages\CreateVendor::route('/create'),
  233. 'view' => Pages\ViewVendor::route('/{record}'),
  234. 'edit' => Pages\EditVendor::route('/{record}/edit'),
  235. ];
  236. }
  237. }