Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

PaymentsRelationManager.php 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <?php
  2. namespace App\Filament\Company\Resources\Sales\InvoiceResource\RelationManagers;
  3. use App\Enums\Accounting\InvoiceStatus;
  4. use App\Enums\Accounting\PaymentMethod;
  5. use App\Enums\Accounting\TransactionType;
  6. use App\Filament\Company\Resources\Sales\InvoiceResource\Pages\ViewInvoice;
  7. use App\Models\Accounting\Invoice;
  8. use App\Models\Accounting\Transaction;
  9. use App\Models\Banking\BankAccount;
  10. use App\Utilities\Currency\CurrencyAccessor;
  11. use App\Utilities\Currency\CurrencyConverter;
  12. use Closure;
  13. use Filament\Forms;
  14. use Filament\Forms\Form;
  15. use Filament\Resources\RelationManagers\RelationManager;
  16. use Filament\Support\Colors\Color;
  17. use Filament\Support\Enums\FontWeight;
  18. use Filament\Support\Enums\MaxWidth;
  19. use Filament\Tables;
  20. use Filament\Tables\Table;
  21. use Illuminate\Database\Eloquent\Model;
  22. class PaymentsRelationManager extends RelationManager
  23. {
  24. protected static string $relationship = 'payments';
  25. protected static ?string $modelLabel = 'Payment';
  26. protected static bool $isLazy = false;
  27. protected $listeners = [
  28. 'refresh' => '$refresh',
  29. ];
  30. public function isReadOnly(): bool
  31. {
  32. return false;
  33. }
  34. public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
  35. {
  36. return $ownerRecord->status !== InvoiceStatus::Draft && $pageClass === ViewInvoice::class;
  37. }
  38. public function form(Form $form): Form
  39. {
  40. return $form
  41. ->columns(1)
  42. ->schema([
  43. Forms\Components\DatePicker::make('posted_at')
  44. ->label('Date'),
  45. Forms\Components\TextInput::make('amount')
  46. ->label('Amount')
  47. ->required()
  48. ->money()
  49. ->live(onBlur: true)
  50. ->helperText(function (RelationManager $livewire, $state, ?Transaction $record) {
  51. if (! CurrencyConverter::isValidAmount($state)) {
  52. return null;
  53. }
  54. /** @var Invoice $ownerRecord */
  55. $ownerRecord = $livewire->getOwnerRecord();
  56. $amountDue = $ownerRecord->getRawOriginal('amount_due');
  57. $amount = CurrencyConverter::convertToCents($state);
  58. if ($amount <= 0) {
  59. return 'Please enter a valid positive amount';
  60. }
  61. $currentPaymentAmount = $record?->getRawOriginal('amount') ?? 0;
  62. if ($ownerRecord->status === InvoiceStatus::Overpaid) {
  63. $newAmountDue = $amountDue + $amount - $currentPaymentAmount;
  64. } else {
  65. $newAmountDue = $amountDue - $amount + $currentPaymentAmount;
  66. }
  67. return match (true) {
  68. $newAmountDue > 0 => 'Amount due after payment will be ' . CurrencyConverter::formatCentsToMoney($newAmountDue),
  69. $newAmountDue === 0 => 'Invoice will be fully paid',
  70. default => 'Invoice will be overpaid by ' . CurrencyConverter::formatCentsToMoney(abs($newAmountDue)),
  71. };
  72. })
  73. ->rules([
  74. static fn (): Closure => static function (string $attribute, $value, Closure $fail) {
  75. if (! CurrencyConverter::isValidAmount($value)) {
  76. $fail('Please enter a valid amount');
  77. }
  78. },
  79. ]),
  80. Forms\Components\Select::make('payment_method')
  81. ->label('Payment Method')
  82. ->required()
  83. ->options(PaymentMethod::class),
  84. Forms\Components\Select::make('bank_account_id')
  85. ->label('Account')
  86. ->required()
  87. ->options(BankAccount::query()
  88. ->get()
  89. ->pluck('account.name', 'id'))
  90. ->searchable(),
  91. Forms\Components\Textarea::make('notes')
  92. ->label('Notes'),
  93. ]);
  94. }
  95. public function table(Table $table): Table
  96. {
  97. return $table
  98. ->recordTitleAttribute('description')
  99. ->columns([
  100. Tables\Columns\TextColumn::make('posted_at')
  101. ->label('Date')
  102. ->sortable()
  103. ->defaultDateFormat(),
  104. Tables\Columns\TextColumn::make('type')
  105. ->label('Type')
  106. ->sortable()
  107. ->toggleable(isToggledHiddenByDefault: true),
  108. Tables\Columns\TextColumn::make('description')
  109. ->label('Description')
  110. ->limit(30)
  111. ->toggleable(),
  112. Tables\Columns\TextColumn::make('bankAccount.account.name')
  113. ->label('Account')
  114. ->toggleable(),
  115. Tables\Columns\TextColumn::make('amount')
  116. ->label('Amount')
  117. ->weight(static fn (Transaction $transaction) => $transaction->reviewed ? null : FontWeight::SemiBold)
  118. ->color(
  119. static fn (Transaction $transaction) => match ($transaction->type) {
  120. TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
  121. TransactionType::Journal => 'primary',
  122. default => null,
  123. }
  124. )
  125. ->sortable()
  126. ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(), true),
  127. ])
  128. ->filters([
  129. //
  130. ])
  131. ->headerActions([
  132. Tables\Actions\CreateAction::make()
  133. ->label(fn () => $this->getOwnerRecord()->status === InvoiceStatus::Overpaid ? 'Refund Overpayment' : 'Record Payment')
  134. ->modalHeading(fn (Tables\Actions\CreateAction $action) => $action->getLabel())
  135. ->modalWidth(MaxWidth::TwoExtraLarge)
  136. ->visible(function () {
  137. return $this->getOwnerRecord()->canRecordPayment();
  138. })
  139. ->mountUsing(function (Form $form) {
  140. $record = $this->getOwnerRecord();
  141. $form->fill([
  142. 'posted_at' => now(),
  143. 'amount' => $record->status === InvoiceStatus::Overpaid ? ltrim($record->amount_due, '-') : $record->amount_due,
  144. ]);
  145. })
  146. ->databaseTransaction()
  147. ->successNotificationTitle('Payment Recorded')
  148. ->action(function (Tables\Actions\CreateAction $action, array $data) {
  149. /** @var Invoice $record */
  150. $record = $this->getOwnerRecord();
  151. $record->recordPayment($data);
  152. $action->success();
  153. $this->dispatch('refresh');
  154. }),
  155. ])
  156. ->actions([
  157. Tables\Actions\EditAction::make()
  158. ->modalWidth(MaxWidth::TwoExtraLarge)
  159. ->after(fn () => $this->dispatch('refresh')),
  160. Tables\Actions\DeleteAction::make()
  161. ->after(fn () => $this->dispatch('refresh')),
  162. ])
  163. ->bulkActions([
  164. Tables\Actions\BulkActionGroup::make([
  165. Tables\Actions\DeleteBulkAction::make(),
  166. ]),
  167. ]);
  168. }
  169. }