| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 | 
							- <?php
 - 
 - namespace App\Observers;
 - 
 - use App\Enums\Accounting\BillStatus;
 - use App\Enums\Accounting\InvoiceStatus;
 - use App\Models\Accounting\Bill;
 - use App\Models\Accounting\Invoice;
 - use App\Models\Accounting\Transaction;
 - use App\Models\Common\Client;
 - use App\Models\Common\Vendor;
 - use App\Services\TransactionService;
 - use App\Utilities\Currency\CurrencyConverter;
 - use Illuminate\Database\Eloquent\Builder;
 - use Illuminate\Support\Facades\DB;
 - 
 - class TransactionObserver
 - {
 -     public function __construct(
 -         protected TransactionService $transactionService,
 -     ) {}
 - 
 -     /**
 -      * Handle the Transaction "saving" event.
 -      */
 -     public function saving(Transaction $transaction): void
 -     {
 -         if ($transaction->type->isTransfer() && $transaction->description === null) {
 -             $transaction->description = 'Account Transfer';
 -         }
 - 
 -         if ($transaction->transactionable && ! $transaction->payeeable_id) {
 -             $document = $transaction->transactionable;
 - 
 -             if ($document instanceof Invoice) {
 -                 $transaction->payeeable_id = $document->client_id;
 -                 $transaction->payeeable_type = Client::class;
 -             } elseif ($document instanceof Bill) {
 -                 $transaction->payeeable_id = $document->vendor_id;
 -                 $transaction->payeeable_type = Vendor::class;
 -             }
 -         }
 -     }
 - 
 -     /**
 -      * Handle the Transaction "created" event.
 -      */
 -     public function created(Transaction $transaction): void
 -     {
 -         $this->transactionService->createJournalEntries($transaction);
 - 
 -         if (! $transaction->transactionable) {
 -             return;
 -         }
 - 
 -         $document = $transaction->transactionable;
 - 
 -         if ($document instanceof Invoice) {
 -             $this->updateInvoiceTotals($document);
 -         } elseif ($document instanceof Bill) {
 -             $this->updateBillTotals($document);
 -         }
 -     }
 - 
 -     /**
 -      * Handle the Transaction "updated" event.
 -      */
 -     public function updated(Transaction $transaction): void
 -     {
 -         $transaction->refresh(); // DO NOT REMOVE
 - 
 -         $this->transactionService->updateJournalEntries($transaction);
 - 
 -         if (! $transaction->transactionable) {
 -             return;
 -         }
 - 
 -         $document = $transaction->transactionable;
 - 
 -         if ($document instanceof Invoice) {
 -             $this->updateInvoiceTotals($document);
 -         } elseif ($document instanceof Bill) {
 -             $this->updateBillTotals($document);
 -         }
 -     }
 - 
 -     /**
 -      * Handle the Transaction "deleting" event.
 -      */
 -     public function deleting(Transaction $transaction): void
 -     {
 -         DB::transaction(function () use ($transaction) {
 -             $this->transactionService->deleteJournalEntries($transaction);
 - 
 -             if (! $transaction->transactionable) {
 -                 return;
 -             }
 - 
 -             $document = $transaction->transactionable;
 - 
 -             if (($document instanceof Invoice || $document instanceof Bill) && ! $document->exists) {
 -                 return;
 -             }
 - 
 -             if ($document instanceof Invoice) {
 -                 $this->updateInvoiceTotals($document, $transaction);
 -             } elseif ($document instanceof Bill) {
 -                 $this->updateBillTotals($document, $transaction);
 -             }
 -         });
 -     }
 - 
 -     public function deleted(Transaction $transaction): void
 -     {
 -         //
 -     }
 - 
 -     protected function updateInvoiceTotals(Invoice $invoice, ?Transaction $excludedTransaction = null): void
 -     {
 -         if (! $invoice->hasPayments()) {
 -             return;
 -         }
 - 
 -         $invoiceCurrency = $invoice->currency_code;
 - 
 -         $depositTotalInInvoiceCurrencyCents = (int) $invoice->deposits()
 -             ->when($excludedTransaction, fn (Builder $query) => $query->whereKeyNot($excludedTransaction->getKey()))
 -             ->get()
 -             ->sum(function (Transaction $transaction) use ($invoiceCurrency) {
 -                 // If the transaction has stored the original invoice amount in metadata, use that
 -                 if (! empty($transaction->meta) &&
 -                     isset($transaction->meta['original_document_currency']) &&
 -                     $transaction->meta['original_document_currency'] === $invoiceCurrency &&
 -                     isset($transaction->meta['amount_in_document_currency_cents'])) {
 - 
 -                     return (int) $transaction->meta['amount_in_document_currency_cents'];
 -                 }
 - 
 -                 // Fall back to conversion if metadata is not available
 -                 $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
 -                 $amountCents = (int) $transaction->amount;
 - 
 -                 return CurrencyConverter::convertBalance($amountCents, $bankAccountCurrency, $invoiceCurrency);
 -             });
 - 
 -         $withdrawalTotalInInvoiceCurrencyCents = (int) $invoice->withdrawals()
 -             ->when($excludedTransaction, fn (Builder $query) => $query->whereKeyNot($excludedTransaction->getKey()))
 -             ->get()
 -             ->sum(function (Transaction $transaction) use ($invoiceCurrency) {
 -                 // If the transaction has stored the original invoice amount in metadata, use that
 -                 if (! empty($transaction->meta) &&
 -                     isset($transaction->meta['original_document_currency']) &&
 -                     $transaction->meta['original_document_currency'] === $invoiceCurrency &&
 -                     isset($transaction->meta['amount_in_document_currency_cents'])) {
 - 
 -                     return (int) $transaction->meta['amount_in_document_currency_cents'];
 -                 }
 - 
 -                 // Fall back to conversion if metadata is not available
 -                 $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
 -                 $amountCents = (int) $transaction->amount;
 - 
 -                 return CurrencyConverter::convertBalance($amountCents, $bankAccountCurrency, $invoiceCurrency);
 -             });
 - 
 -         $totalPaidInInvoiceCurrencyCents = $depositTotalInInvoiceCurrencyCents - $withdrawalTotalInInvoiceCurrencyCents;
 - 
 -         $invoiceTotalInInvoiceCurrencyCents = (int) $invoice->total;
 - 
 -         $newStatus = match (true) {
 -             $totalPaidInInvoiceCurrencyCents > $invoiceTotalInInvoiceCurrencyCents => InvoiceStatus::Overpaid,
 -             $totalPaidInInvoiceCurrencyCents === $invoiceTotalInInvoiceCurrencyCents => InvoiceStatus::Paid,
 -             $totalPaidInInvoiceCurrencyCents === 0 => $invoice->last_sent_at ? InvoiceStatus::Sent : InvoiceStatus::Unsent,
 -             default => InvoiceStatus::Partial,
 -         };
 - 
 -         $paidAt = $invoice->paid_at;
 - 
 -         if (in_array($newStatus, [InvoiceStatus::Paid, InvoiceStatus::Overpaid]) && ! $paidAt) {
 -             $paidAt = $invoice->deposits()
 -                 ->latest('posted_at')
 -                 ->value('posted_at');
 -         }
 - 
 -         $invoice->update([
 -             'amount_paid' => $totalPaidInInvoiceCurrencyCents,
 -             'status' => $newStatus,
 -             'paid_at' => $paidAt,
 -         ]);
 -     }
 - 
 -     protected function updateBillTotals(Bill $bill, ?Transaction $excludedTransaction = null): void
 -     {
 -         if (! $bill->hasPayments()) {
 -             return;
 -         }
 - 
 -         $billCurrency = $bill->currency_code;
 - 
 -         $withdrawalTotalInBillCurrencyCents = (int) $bill->withdrawals()
 -             ->when($excludedTransaction, fn (Builder $query) => $query->whereKeyNot($excludedTransaction->getKey()))
 -             ->get()
 -             ->sum(function (Transaction $transaction) use ($billCurrency) {
 -                 // If the transaction has stored the original bill amount in metadata, use that
 -                 if (! empty($transaction->meta) &&
 -                     isset($transaction->meta['original_document_currency']) &&
 -                     $transaction->meta['original_document_currency'] === $billCurrency &&
 -                     isset($transaction->meta['amount_in_document_currency_cents'])) {
 - 
 -                     return (int) $transaction->meta['amount_in_document_currency_cents'];
 -                 }
 - 
 -                 // Fall back to conversion if metadata is not available
 -                 $bankAccountCurrency = $transaction->bankAccount->account->currency_code;
 -                 $amountCents = (int) $transaction->amount;
 - 
 -                 return CurrencyConverter::convertBalance($amountCents, $bankAccountCurrency, $billCurrency);
 -             });
 - 
 -         $totalPaidInBillCurrencyCents = $withdrawalTotalInBillCurrencyCents;
 - 
 -         $billTotalInBillCurrencyCents = (int) $bill->total;
 - 
 -         $newStatus = match (true) {
 -             $totalPaidInBillCurrencyCents >= $billTotalInBillCurrencyCents => BillStatus::Paid,
 -             $totalPaidInBillCurrencyCents === 0 => BillStatus::Open,
 -             default => BillStatus::Partial,
 -         };
 - 
 -         $paidAt = $bill->paid_at;
 - 
 -         if ($newStatus === BillStatus::Paid && ! $paidAt) {
 -             $paidAt = $bill->withdrawals()
 -                 ->latest('posted_at')
 -                 ->value('posted_at');
 -         }
 - 
 -         $bill->update([
 -             'amount_paid' => $totalPaidInBillCurrencyCents,
 -             'status' => $newStatus,
 -             'paid_at' => $paidAt,
 -         ]);
 -     }
 - }
 
 
  |