Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

AdjustmentResource.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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. ->color('warning')
  112. ->form([
  113. Forms\Components\DateTimePicker::make('paused_until')
  114. ->label('Auto-resume Date')
  115. ->helperText('When should this adjustment automatically resume? Leave empty to keep paused indefinitely.')
  116. ->after('now'),
  117. Forms\Components\Textarea::make('status_reason')
  118. ->label('Reason for Pausing')
  119. ->maxLength(255),
  120. ])
  121. ->visible(fn (Adjustment $record) => $record->canBePaused())
  122. ->action(function (Adjustment $record, array $data) {
  123. $pausedUntil = $data['paused_until'] ?? null;
  124. $reason = $data['status_reason'] ?? null;
  125. $record->pause($reason, $pausedUntil);
  126. }),
  127. Tables\Actions\Action::make('resume')
  128. ->label('Resume')
  129. ->icon('heroicon-m-play')
  130. ->color('success')
  131. ->requiresConfirmation()
  132. ->visible(fn (Adjustment $record) => $record->canBeResumed())
  133. ->action(fn (Adjustment $record) => $record->resume()),
  134. Tables\Actions\Action::make('archive')
  135. ->label('Archive')
  136. ->icon('heroicon-m-archive-box')
  137. ->color('danger')
  138. ->form([
  139. Forms\Components\Textarea::make('status_reason')
  140. ->label('Reason for Archiving')
  141. ->maxLength(255),
  142. ])
  143. ->visible(fn (Adjustment $record) => $record->canBeArchived())
  144. ->action(function (Adjustment $record, array $data) {
  145. $reason = $data['status_reason'] ?? null;
  146. $record->archive($reason);
  147. }),
  148. ]),
  149. ])
  150. ->bulkActions([
  151. Tables\Actions\BulkActionGroup::make([
  152. Tables\Actions\BulkAction::make('pause')
  153. ->label('Pause')
  154. ->icon('heroicon-m-pause')
  155. ->color('warning')
  156. ->form([
  157. Forms\Components\DateTimePicker::make('paused_until')
  158. ->label('Auto-resume Date')
  159. ->helperText('When should these adjustments automatically resume? Leave empty to keep paused indefinitely.')
  160. ->after('now'),
  161. Forms\Components\Textarea::make('status_reason')
  162. ->label('Reason for Pausing')
  163. ->maxLength(255),
  164. ])
  165. ->databaseTransaction()
  166. ->successNotificationTitle('Adjustments paused')
  167. ->failureNotificationTitle('Failed to pause adjustments')
  168. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  169. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBePaused());
  170. if ($isInvalid) {
  171. Notification::make()
  172. ->title('Pause failed')
  173. ->body('Only adjustments that are currently active can be paused. Please adjust your selection and try again.')
  174. ->persistent()
  175. ->danger()
  176. ->send();
  177. $action->cancel(true);
  178. }
  179. })
  180. ->deselectRecordsAfterCompletion()
  181. ->action(function (Collection $records, array $data) {
  182. $pausedUntil = $data['paused_until'] ?? null;
  183. $reason = $data['status_reason'] ?? null;
  184. $records->each(function (Adjustment $record) use ($reason, $pausedUntil) {
  185. $record->pause($reason, $pausedUntil);
  186. });
  187. }),
  188. Tables\Actions\BulkAction::make('resume')
  189. ->label('Resume')
  190. ->icon('heroicon-m-play')
  191. ->color('success')
  192. ->databaseTransaction()
  193. ->successNotificationTitle('Adjustments resumed')
  194. ->failureNotificationTitle('Failed to resume adjustments')
  195. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  196. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBeResumed());
  197. if ($isInvalid) {
  198. Notification::make()
  199. ->title('Resume failed')
  200. ->body('Only adjustments that are currently paused can be resumed. Please adjust your selection and try again.')
  201. ->persistent()
  202. ->danger()
  203. ->send();
  204. $action->cancel(true);
  205. }
  206. })
  207. ->deselectRecordsAfterCompletion()
  208. ->action(function (Collection $records) {
  209. $records->each(function (Adjustment $record) {
  210. $record->resume();
  211. });
  212. }),
  213. Tables\Actions\BulkAction::make('archive')
  214. ->label('Archive')
  215. ->icon('heroicon-m-archive-box')
  216. ->color('danger')
  217. ->form([
  218. Forms\Components\Textarea::make('status_reason')
  219. ->label('Reason for Archiving')
  220. ->maxLength(255),
  221. ])
  222. ->databaseTransaction()
  223. ->successNotificationTitle('Adjustments archived')
  224. ->failureNotificationTitle('Failed to archive adjustments')
  225. ->before(function (Collection $records, Tables\Actions\BulkAction $action) {
  226. $isInvalid = $records->contains(fn (Adjustment $record) => ! $record->canBeArchived());
  227. if ($isInvalid) {
  228. Notification::make()
  229. ->title('Archive failed')
  230. ->body('Only adjustments that are currently active or paused can be archived. Please adjust your selection and try again.')
  231. ->persistent()
  232. ->danger()
  233. ->send();
  234. $action->cancel(true);
  235. }
  236. })
  237. ->deselectRecordsAfterCompletion()
  238. ->action(function (Collection $records, array $data) {
  239. $reason = $data['status_reason'] ?? null;
  240. $records->each(function (Adjustment $record) use ($reason) {
  241. $record->archive($reason);
  242. });
  243. }),
  244. ]),
  245. ]);
  246. }
  247. public static function getRelations(): array
  248. {
  249. return [
  250. //
  251. ];
  252. }
  253. public static function getPages(): array
  254. {
  255. return [
  256. 'index' => Pages\ListAdjustments::route('/'),
  257. 'create' => Pages\CreateAdjustment::route('/create'),
  258. 'edit' => Pages\EditAdjustment::route('/{record}/edit'),
  259. ];
  260. }
  261. }