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.

PaymentsRelationManager.php 7.7KB

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