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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace App\Models\Accounting;
  3. use App\Casts\TransactionAmountCast;
  4. use App\Concerns\Blamable;
  5. use App\Concerns\CompanyOwned;
  6. use App\Enums\Accounting\AccountCategory;
  7. use App\Enums\Accounting\PaymentMethod;
  8. use App\Enums\Accounting\TransactionType;
  9. use App\Models\Banking\BankAccount;
  10. use App\Models\Common\Contact;
  11. use App\Observers\TransactionObserver;
  12. use Database\Factories\Accounting\TransactionFactory;
  13. use Illuminate\Database\Eloquent\Attributes\ObservedBy;
  14. use Illuminate\Database\Eloquent\Builder;
  15. use Illuminate\Database\Eloquent\Factories\Factory;
  16. use Illuminate\Database\Eloquent\Factories\HasFactory;
  17. use Illuminate\Database\Eloquent\Model;
  18. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  19. use Illuminate\Database\Eloquent\Relations\HasMany;
  20. use Illuminate\Database\Eloquent\Relations\MorphTo;
  21. use Illuminate\Support\Collection;
  22. #[ObservedBy(TransactionObserver::class)]
  23. class Transaction extends Model
  24. {
  25. use Blamable;
  26. use CompanyOwned;
  27. use HasFactory;
  28. protected $fillable = [
  29. 'company_id',
  30. 'account_id', // Account from Chart of Accounts (Income/Expense accounts)
  31. 'bank_account_id', // Cash/Bank Account
  32. 'plaid_transaction_id',
  33. 'contact_id',
  34. 'type', // 'deposit', 'withdrawal', 'journal'
  35. 'payment_channel',
  36. 'payment_method',
  37. 'is_payment',
  38. 'description',
  39. 'notes',
  40. 'reference',
  41. 'amount',
  42. 'pending',
  43. 'reviewed',
  44. 'posted_at',
  45. 'created_by',
  46. 'updated_by',
  47. 'meta',
  48. ];
  49. protected $casts = [
  50. 'type' => TransactionType::class,
  51. 'payment_method' => PaymentMethod::class,
  52. 'amount' => TransactionAmountCast::class,
  53. 'pending' => 'boolean',
  54. 'reviewed' => 'boolean',
  55. 'posted_at' => 'date',
  56. 'meta' => 'array',
  57. ];
  58. public function account(): BelongsTo
  59. {
  60. return $this->belongsTo(Account::class, 'account_id');
  61. }
  62. public function bankAccount(): BelongsTo
  63. {
  64. return $this->belongsTo(BankAccount::class, 'bank_account_id');
  65. }
  66. public function contact(): BelongsTo
  67. {
  68. return $this->belongsTo(Contact::class, 'contact_id');
  69. }
  70. public function journalEntries(): HasMany
  71. {
  72. return $this->hasMany(JournalEntry::class, 'transaction_id');
  73. }
  74. public function transactionable(): MorphTo
  75. {
  76. return $this->morphTo();
  77. }
  78. public function isUncategorized(): bool
  79. {
  80. return $this->journalEntries->contains(fn (JournalEntry $entry) => $entry->account->isUncategorized());
  81. }
  82. public function updateAmountIfBalanced(): void
  83. {
  84. if ($this->journalEntries->areBalanced() && $this->journalEntries->sumDebits()->formatSimple() !== $this->getAttributeValue('amount')) {
  85. $this->setAttribute('amount', $this->journalEntries->sumDebits()->formatSimple());
  86. $this->save();
  87. }
  88. }
  89. public static function getBankAccountOptions(?int $excludedAccountId = null, ?int $currentBankAccountId = null): array
  90. {
  91. return BankAccount::query()
  92. ->whereHas('account', function (Builder $query) {
  93. $query->where('archived', false);
  94. })
  95. ->with(['account' => function ($query) {
  96. $query->where('archived', false);
  97. }, 'account.subtype' => function ($query) {
  98. $query->select(['id', 'name']);
  99. }])
  100. ->when($excludedAccountId, fn (Builder $query) => $query->where('account_id', '!=', $excludedAccountId))
  101. ->when($currentBankAccountId, fn (Builder $query) => $query->orWhere('id', $currentBankAccountId))
  102. ->get()
  103. ->groupBy('account.subtype.name')
  104. ->map(fn (Collection $bankAccounts, string $subtype) => $bankAccounts->pluck('account.name', 'id'))
  105. ->toArray();
  106. }
  107. public static function getBankAccountAccountOptions(?int $excludedBankAccountId = null, ?int $currentAccountId = null): array
  108. {
  109. return Account::query()
  110. ->whereHas('bankAccount', function (Builder $query) use ($excludedBankAccountId) {
  111. // Exclude the specific bank account if provided
  112. if ($excludedBankAccountId) {
  113. $query->whereNot('id', $excludedBankAccountId);
  114. }
  115. })
  116. ->where(function (Builder $query) use ($currentAccountId) {
  117. $query->where('archived', false)
  118. ->orWhere('id', $currentAccountId);
  119. })
  120. ->get()
  121. ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
  122. ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
  123. ->toArray();
  124. }
  125. public static function getChartAccountOptions(?TransactionType $type = null, ?bool $nominalAccountsOnly = null, ?int $currentAccountId = null): array
  126. {
  127. $nominalAccountsOnly ??= false;
  128. $excludedCategory = match ($type) {
  129. TransactionType::Deposit => AccountCategory::Expense,
  130. TransactionType::Withdrawal => AccountCategory::Revenue,
  131. default => null,
  132. };
  133. return Account::query()
  134. ->when($nominalAccountsOnly, fn (Builder $query) => $query->doesntHave('bankAccount'))
  135. ->when($excludedCategory, fn (Builder $query) => $query->whereNot('category', $excludedCategory))
  136. ->where(function (Builder $query) use ($currentAccountId) {
  137. $query->where('archived', false)
  138. ->orWhere('id', $currentAccountId);
  139. })
  140. ->get()
  141. ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
  142. ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
  143. ->toArray();
  144. }
  145. protected static function newFactory(): Factory
  146. {
  147. return TransactionFactory::new();
  148. }
  149. }