Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

PaymentsRelationManager.php 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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(function () {
  88. return BankAccount::query()
  89. ->join('accounts', 'bank_accounts.account_id', '=', 'accounts.id')
  90. ->select(['bank_accounts.id', 'accounts.name'])
  91. ->pluck('accounts.name', 'bank_accounts.id')
  92. ->toArray();
  93. })
  94. ->searchable(),
  95. Forms\Components\Textarea::make('notes')
  96. ->label('Notes'),
  97. ]);
  98. }
  99. public function table(Table $table): Table
  100. {
  101. return $table
  102. ->recordTitleAttribute('description')
  103. ->columns([
  104. Tables\Columns\TextColumn::make('posted_at')
  105. ->label('Date')
  106. ->sortable()
  107. ->defaultDateFormat(),
  108. Tables\Columns\TextColumn::make('type')
  109. ->label('Type')
  110. ->sortable()
  111. ->toggleable(isToggledHiddenByDefault: true),
  112. Tables\Columns\TextColumn::make('description')
  113. ->label('Description')
  114. ->limit(30)
  115. ->toggleable(),
  116. Tables\Columns\TextColumn::make('bankAccount.account.name')
  117. ->label('Account')
  118. ->toggleable(),
  119. Tables\Columns\TextColumn::make('amount')
  120. ->label('Amount')
  121. ->weight(static fn (Transaction $transaction) => $transaction->reviewed ? null : FontWeight::SemiBold)
  122. ->color(
  123. static fn (Transaction $transaction) => match ($transaction->type) {
  124. TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
  125. TransactionType::Journal => 'primary',
  126. default => null,
  127. }
  128. )
  129. ->sortable()
  130. ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(), true),
  131. ])
  132. ->filters([
  133. //
  134. ])
  135. ->headerActions([
  136. Tables\Actions\CreateAction::make()
  137. ->label(fn () => $this->getOwnerRecord()->status === InvoiceStatus::Overpaid ? 'Refund Overpayment' : 'Record Payment')
  138. ->modalHeading(fn (Tables\Actions\CreateAction $action) => $action->getLabel())
  139. ->modalWidth(MaxWidth::TwoExtraLarge)
  140. ->visible(function () {
  141. return $this->getOwnerRecord()->canRecordPayment();
  142. })
  143. ->mountUsing(function (Form $form) {
  144. $record = $this->getOwnerRecord();
  145. $form->fill([
  146. 'posted_at' => now(),
  147. 'amount' => $record->status === InvoiceStatus::Overpaid ? ltrim($record->amount_due, '-') : $record->amount_due,
  148. ]);
  149. })
  150. ->databaseTransaction()
  151. ->successNotificationTitle('Payment recorded')
  152. ->action(function (Tables\Actions\CreateAction $action, array $data) {
  153. /** @var Invoice $record */
  154. $record = $this->getOwnerRecord();
  155. $record->recordPayment($data);
  156. $action->success();
  157. $this->dispatch('refresh');
  158. }),
  159. ])
  160. ->actions([
  161. Tables\Actions\EditAction::make()
  162. ->modalWidth(MaxWidth::TwoExtraLarge)
  163. ->after(fn () => $this->dispatch('refresh')),
  164. Tables\Actions\DeleteAction::make()
  165. ->after(fn () => $this->dispatch('refresh')),
  166. ])
  167. ->bulkActions([
  168. Tables\Actions\BulkActionGroup::make([
  169. Tables\Actions\DeleteBulkAction::make(),
  170. ]),
  171. ]);
  172. }
  173. }