2024-12-20 11:37:26 -05:00
|
|
|
class Account::TransactionSearch
|
|
|
|
include ActiveModel::Model
|
|
|
|
include ActiveModel::Attributes
|
|
|
|
|
|
|
|
attribute :search, :string
|
|
|
|
attribute :amount, :string
|
|
|
|
attribute :amount_operator, :string
|
|
|
|
attribute :types, array: true
|
|
|
|
attribute :accounts, array: true
|
|
|
|
attribute :account_ids, array: true
|
|
|
|
attribute :start_date, :string
|
|
|
|
attribute :end_date, :string
|
|
|
|
attribute :categories, array: true
|
|
|
|
attribute :merchants, array: true
|
|
|
|
attribute :tags, array: true
|
|
|
|
|
|
|
|
def build_query(scope)
|
2025-02-21 11:57:59 -05:00
|
|
|
query = scope.joins(entry: :account)
|
2025-03-11 15:38:45 -04:00
|
|
|
.joins(transfer_join)
|
2024-12-20 11:37:26 -05:00
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
query = apply_category_filter(query, categories)
|
|
|
|
query = apply_type_filter(query, types)
|
|
|
|
query = apply_merchant_filter(query, merchants)
|
|
|
|
query = apply_tag_filter(query, tags)
|
|
|
|
query = Account::EntrySearch.apply_search_filter(query, search)
|
|
|
|
query = Account::EntrySearch.apply_date_filters(query, start_date, end_date)
|
|
|
|
query = Account::EntrySearch.apply_amount_filter(query, amount, amount_operator)
|
|
|
|
query = Account::EntrySearch.apply_accounts_filter(query, accounts, account_ids)
|
|
|
|
|
|
|
|
query
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def transfer_join
|
|
|
|
<<~SQL
|
|
|
|
LEFT JOIN (
|
|
|
|
SELECT t.*, t.id as transfer_id, a.accountable_type
|
|
|
|
FROM transfers t
|
|
|
|
JOIN account_entries ae ON ae.entryable_id = t.inflow_transaction_id
|
|
|
|
AND ae.entryable_type = 'Account::Transaction'
|
|
|
|
JOIN accounts a ON a.id = ae.account_id
|
|
|
|
) transfer_info ON (
|
|
|
|
transfer_info.inflow_transaction_id = account_transactions.id OR
|
|
|
|
transfer_info.outflow_transaction_id = account_transactions.id
|
|
|
|
)
|
|
|
|
SQL
|
2025-01-07 09:41:24 -05:00
|
|
|
end
|
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
def apply_category_filter(query, categories)
|
|
|
|
return query unless categories.present?
|
|
|
|
|
|
|
|
query = query.left_joins(:category).where(
|
|
|
|
"categories.name IN (?) OR (
|
|
|
|
categories.id IS NULL AND (transfer_info.transfer_id IS NULL OR transfer_info.accountable_type = 'Loan')
|
|
|
|
)",
|
|
|
|
categories
|
|
|
|
)
|
|
|
|
|
2024-12-20 11:37:26 -05:00
|
|
|
if categories.exclude?("Uncategorized")
|
2025-03-11 15:38:45 -04:00
|
|
|
query = query.where.not(category_id: nil)
|
2024-12-20 11:37:26 -05:00
|
|
|
end
|
2025-03-11 15:38:45 -04:00
|
|
|
|
|
|
|
query
|
2024-12-20 11:37:26 -05:00
|
|
|
end
|
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
def apply_type_filter(query, types)
|
|
|
|
return query unless types.present?
|
|
|
|
return query if types.sort == [ "expense", "income", "transfer" ]
|
2024-12-20 11:37:26 -05:00
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
transfer_condition = "transfer_info.transfer_id IS NOT NULL"
|
|
|
|
expense_condition = "account_entries.amount >= 0"
|
|
|
|
income_condition = "account_entries.amount <= 0"
|
2024-12-20 11:37:26 -05:00
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
condition = case types.sort
|
|
|
|
when [ "transfer" ]
|
|
|
|
transfer_condition
|
|
|
|
when [ "expense" ]
|
|
|
|
Arel.sql("#{expense_condition} AND NOT (#{transfer_condition})")
|
|
|
|
when [ "income" ]
|
|
|
|
Arel.sql("#{income_condition} AND NOT (#{transfer_condition})")
|
|
|
|
when [ "expense", "transfer" ]
|
|
|
|
Arel.sql("#{expense_condition} OR #{transfer_condition}")
|
|
|
|
when [ "income", "transfer" ]
|
|
|
|
Arel.sql("#{income_condition} OR #{transfer_condition}")
|
|
|
|
when [ "expense", "income" ]
|
|
|
|
Arel.sql("NOT (#{transfer_condition})")
|
|
|
|
end
|
2024-12-20 11:37:26 -05:00
|
|
|
|
2025-03-11 15:38:45 -04:00
|
|
|
query.where(condition)
|
|
|
|
end
|
|
|
|
|
|
|
|
def apply_merchant_filter(query, merchants)
|
|
|
|
return query unless merchants.present?
|
|
|
|
query.joins(:merchant).where(merchants: { name: merchants })
|
|
|
|
end
|
|
|
|
|
|
|
|
def apply_tag_filter(query, tags)
|
|
|
|
return query unless tags.present?
|
|
|
|
query.joins(:tags).where(tags: { name: tags })
|
|
|
|
end
|
2024-12-20 11:37:26 -05:00
|
|
|
end
|