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

AdjustmentResource.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. namespace App\Filament\Company\Clusters\Settings\Resources;
  3. use App\Enums\Accounting\AdjustmentCategory;
  4. use App\Enums\Accounting\AdjustmentComputation;
  5. use App\Enums\Accounting\AdjustmentScope;
  6. use App\Enums\Accounting\AdjustmentType;
  7. use App\Filament\Company\Clusters\Settings;
  8. use App\Filament\Company\Clusters\Settings\Resources\AdjustmentResource\Pages;
  9. use App\Models\Accounting\Adjustment;
  10. use Filament\Forms;
  11. use Filament\Forms\Form;
  12. use Filament\Notifications\Notification;
  13. use Filament\Resources\Resource;
  14. use Filament\Tables;
  15. use Filament\Tables\Table;
  16. use Illuminate\Database\Eloquent\Collection;
  17. class AdjustmentResource extends Resource
  18. {
  19. protected static ?string $model = Adjustment::class;
  20. protected static ?string $cluster = Settings::class;
  21. public static function form(Form $form): Form
  22. {
  23. return $form
  24. ->schema([
  25. Forms\Components\Section::make('General')
  26. ->schema([
  27. Forms\Components\TextInput::make('name')
  28. ->autofocus()
  29. ->required()
  30. ->maxLength(255),
  31. Forms\Components\Textarea::make('description')
  32. ->label('Description'),
  33. ]),
  34. Forms\Components\Section::make('Configuration')
  35. ->schema([
  36. Forms\Components\Select::make('category')
  37. ->localizeLabel()
  38. ->options(AdjustmentCategory::class)
  39. ->default(AdjustmentCategory::Tax)
  40. ->live()
  41. ->required(),
  42. Forms\Components\Select::make('type')
  43. ->localizeLabel()
  44. ->options(AdjustmentType::class)
  45. ->default(AdjustmentType::Sales)
  46. ->live()
  47. ->required(),
  48. Forms\Components\Checkbox::make('recoverable')
  49. ->label('Recoverable')
  50. ->default(false)
  51. ->helperText('When enabled, tax is tracked separately as claimable from the government. Non-recoverable taxes are treated as part of the expense.')
  52. ->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category'))->isTax() && AdjustmentType::parse($get('type'))->isPurchase()),
  53. ])
  54. ->columns()
  55. ->visibleOn('create'),
  56. Forms\Components\Section::make('Adjustment Details')
  57. ->schema([
  58. Forms\Components\Select::make('computation')
  59. ->localizeLabel()
  60. ->options(AdjustmentComputation::class)
  61. ->default(AdjustmentComputation::Percentage)
  62. ->live()
  63. ->required(),
  64. Forms\Components\TextInput::make('rate')
  65. ->localizeLabel()
  66. ->rate(static fn (Forms\Get $get) => $get('computation'))
  67. ->required(),
  68. Forms\Components\Select::make('scope')
  69. ->localizeLabel()
  70. ->options(AdjustmentScope::class),
  71. ])
  72. ->columns(),
  73. Forms\Components\Section::make('Dates')
  74. ->schema([
  75. Forms\Components\DateTimePicker::make('start_date'),
  76. Forms\Components\DateTimePicker::make('end_date')
  77. ->after('start_date'),
  78. ])
  79. ->columns()
  80. ->visible(fn (Forms\Get $get) => AdjustmentCategory::parse($get('category'))->isDiscount()),
  81. ]);
  82. }
  83. public static function table(Table $table): Table
  84. {
  85. return $table
  86. ->columns([
  87. Tables\Columns\TextColumn::make('name')
  88. ->label('Name')
  89. ->sortable(),
  90. Tables\Columns\TextColumn::make('category')
  91. ->searchable(),
  92. Tables\Columns\TextColumn::make('type')
  93. ->searchable(),
  94. Tables\Columns\TextColumn::make('status')
  95. ->badge(),
  96. Tables\Columns\TextColumn::make('rate')
  97. ->localizeLabel()
  98. ->rate(static fn (Adjustment $record) => $record->computation->value)
  99. ->searchable()
  100. ->sortable(),
  101. ])
  102. ->filters([
  103. //
  104. ])
  105. ->actions([
  106. Tables\Actions\ActionGroup::make([
  107. Tables\Actions\EditAction::make(),
  108. Tables\Actions\Action::make('pause')
  109. ->label('Pause')
  110. ->icon('heroicon-m-pause')
  111. ->form([
  112. Forms\Components\DateTimePicker::make('paused_until')
  113. ->label('Auto-resume Date')
  114. ->helperText('When should this adjustment automatically resume? Leave empty to keep paused indefinitely.')
  115. ->after('now'),
  116. Forms\Components\Textarea::make('status_reason')
  117. ->label('Reason for Pausing')
  118. ->maxLength(255),
  119. ])
  120. ->visible(fn (Adjustment $record) => $record->canBePaused())
  121. ->action(function (Adjustment $record, array $data) {
  122. $pausedUntil = $data['paused_until'] ?? null;
  123. $reason = $data['status_reason'] ?? null;
  124. $record->pause($reason, $pausedUntil);
  125. }),
  126. Tables\Actions\Action::make('resume')
  127. ->label('Resume')
  128. ->icon('heroicon-m-play')
  129. ->requiresConfirmation()
  130. ->visible(fn (Adjustment $record) => $record->canBeResumed())
  131. ->action(fn (Adjustment $record) => $record->resume()),
  132. Tables\Actions\Action::make('archive')
  133. ->label('Archive')
  134. ->icon('heroicon-m-archive-box')
  135. ->color('danger')
  136. ->form([
  137. Forms\Components\Textarea::make('status_reason')
  138. ->label('Reason for Archiving')
  139. ->maxLength(255),
  140. ])
  141. ->visible(fn (Adjustment $record) => $record->canBeArchived())
  142. ->action(function (Adjustment $record, array $data) {
  143. $reason = $data['status_reason'] ?? null;
  144. $record->archive($reason);
  145. }),
  146. ]),
  147. ])
  148. ->bulkActions([
  149. Tables\Actions\BulkActionGroup::make([
  150. Tables\Actions\BulkAction::make('pause')
  151. ->label('Pause')
  152. ->icon('heroicon-m-pause')
  153. ->color('warning')
  154. ->form([
  155. Forms\Components\DateTimePicker::make('paused_until')
  156. ->label('Auto-resume Date')
  157. ->helperText('When should these adjustments automatically resume? Leave empty to keep paused indefinitely.')
  158. ->after('now'),
  159. Forms\Components\Textarea::make('status_reason')
  160. ->label('Reason for Pausing')
  161. ->maxLength(255),
  162. ])
  163. ->databaseTransaction()
  164. ->successNotificationTitle('Adjustments paused')
  165. ->failureNotificationTitle('Failed to pause adjustments')
  166. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  167. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBePaused());
  168. if ($isInvalid) {
  169. Notification::make()
  170. ->title('Pause failed')
  171. ->body('Only adjustments that are currently active can be paused. Please adjust your selection and try again.')
  172. ->persistent()
  173. ->danger()
  174. ->send();
  175. $action->cancel(true);
  176. }
  177. })
  178. ->deselectRecordsAfterCompletion()
  179. ->action(function (Collection $records, array $data) {
  180. $pausedUntil = $data['paused_until'] ?? null;
  181. $reason = $data['status_reason'] ?? null;
  182. $records->each(function (Adjustment $record) use ($reason, $pausedUntil) {
  183. $record->pause($reason, $pausedUntil);
  184. });
  185. }),
  186. Tables\Actions\BulkAction::make('resume')
  187. ->label('Resume')
  188. ->icon('heroicon-m-play')
  189. ->color('success')
  190. ->databaseTransaction()
  191. ->successNotificationTitle('Adjustments resumed')
  192. ->failureNotificationTitle('Failed to resume adjustments')
  193. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  194. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBeResumed());
  195. if ($isInvalid) {
  196. Notification::make()
  197. ->title('Resume failed')
  198. ->body('Only adjustments that are currently paused can be resumed. Please adjust your selection and try again.')
  199. ->persistent()
  200. ->danger()
  201. ->send();
  202. $action->cancel(true);
  203. }
  204. })
  205. ->deselectRecordsAfterCompletion()
  206. ->action(function (Collection $records) {
  207. $records->each(function (Adjustment $record) {
  208. $record->resume();
  209. });
  210. }),
  211. Tables\Actions\BulkAction::make('archive')
  212. ->label('Archive')
  213. ->icon('heroicon-m-archive-box')
  214. ->color('danger')
  215. ->form([
  216. Forms\Components\Textarea::make('status_reason')
  217. ->label('Reason for Archiving')
  218. ->maxLength(255),
  219. ])
  220. ->databaseTransaction()
  221. ->successNotificationTitle('Adjustments archived')
  222. ->failureNotificationTitle('Failed to archive adjustments')
  223. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  224. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBeArchived());
  225. if ($isInvalid) {
  226. Notification::make()
  227. ->title('Archive failed')
  228. ->body('Only adjustments that are currently active or paused can be archived. Please adjust your selection and try again.')
  229. ->persistent()
  230. ->danger()
  231. ->send();
  232. $action->cancel(true);
  233. }
  234. })
  235. ->deselectRecordsAfterCompletion()
  236. ->action(function (Collection $records, array $data) {
  237. $reason = $data['status_reason'] ?? null;
  238. $records->each(function (Adjustment $record) use ($reason) {
  239. $record->archive($reason);
  240. });
  241. }),
  242. ]),
  243. ]);
  244. }
  245. public static function getRelations(): array
  246. {
  247. return [
  248. //
  249. ];
  250. }
  251. public static function getPages(): array
  252. {
  253. return [
  254. 'index' => Pages\ListAdjustments::route('/'),
  255. 'create' => Pages\CreateAdjustment::route('/create'),
  256. 'edit' => Pages\EditAdjustment::route('/{record}/edit'),
  257. ];
  258. }
  259. }