mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 16:05:22 +02:00
Update transaction search
This commit is contained in:
parent
e4a22f81a0
commit
415fd670b6
2 changed files with 131 additions and 23 deletions
|
@ -16,7 +16,6 @@ class Transaction::Search
|
|||
|
||||
def build_query(scope)
|
||||
query = scope.joins(entry: :account)
|
||||
.joins(transfer_join)
|
||||
|
||||
query = apply_category_filter(query, categories)
|
||||
query = apply_type_filter(query, types)
|
||||
|
@ -31,27 +30,12 @@ class Transaction::Search
|
|||
end
|
||||
|
||||
private
|
||||
def transfer_join
|
||||
<<~SQL
|
||||
LEFT JOIN (
|
||||
SELECT t.*, t.id as transfer_id, a.accountable_type
|
||||
FROM transfers t
|
||||
JOIN entries ae ON ae.entryable_id = t.inflow_transaction_id
|
||||
AND ae.entryable_type = 'Transaction'
|
||||
JOIN accounts a ON a.id = ae.account_id
|
||||
) transfer_info ON (
|
||||
transfer_info.inflow_transaction_id = transactions.id OR
|
||||
transfer_info.outflow_transaction_id = transactions.id
|
||||
)
|
||||
SQL
|
||||
end
|
||||
|
||||
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.id IS NULL AND (transactions.kind NOT IN ('transfer', 'payment', 'one_time') OR transactions.kind = 'loan_payment')
|
||||
)",
|
||||
categories
|
||||
)
|
||||
|
@ -67,7 +51,7 @@ class Transaction::Search
|
|||
return query unless types.present?
|
||||
return query if types.sort == [ "expense", "income", "transfer" ]
|
||||
|
||||
transfer_condition = "transfer_info.transfer_id IS NOT NULL"
|
||||
transfer_condition = "transactions.kind IN ('transfer', 'payment', 'one_time')"
|
||||
expense_condition = "entries.amount >= 0"
|
||||
income_condition = "entries.amount <= 0"
|
||||
|
||||
|
@ -75,15 +59,15 @@ class Transaction::Search
|
|||
when [ "transfer" ]
|
||||
transfer_condition
|
||||
when [ "expense" ]
|
||||
Arel.sql("#{expense_condition} AND NOT (#{transfer_condition})")
|
||||
Arel.sql("(#{expense_condition} AND transactions.kind NOT IN ('transfer', 'payment', 'one_time')) OR transactions.kind = 'loan_payment'")
|
||||
when [ "income" ]
|
||||
Arel.sql("#{income_condition} AND NOT (#{transfer_condition})")
|
||||
Arel.sql("#{income_condition} AND transactions.kind NOT IN ('transfer', 'payment', 'one_time', 'loan_payment')")
|
||||
when [ "expense", "transfer" ]
|
||||
Arel.sql("#{expense_condition} OR #{transfer_condition}")
|
||||
Arel.sql("(#{expense_condition} AND transactions.kind NOT IN ('transfer', 'payment', 'one_time')) OR transactions.kind = 'loan_payment' OR #{transfer_condition}")
|
||||
when [ "income", "transfer" ]
|
||||
Arel.sql("#{income_condition} OR #{transfer_condition}")
|
||||
Arel.sql("(#{income_condition} AND transactions.kind NOT IN ('transfer', 'payment', 'one_time', 'loan_payment')) OR #{transfer_condition}")
|
||||
when [ "expense", "income" ]
|
||||
Arel.sql("NOT (#{transfer_condition})")
|
||||
Arel.sql("transactions.kind NOT IN ('transfer', 'payment', 'one_time') OR transactions.kind = 'loan_payment'")
|
||||
end
|
||||
|
||||
query.where(condition)
|
||||
|
|
124
test/models/transaction_test.rb
Normal file
124
test/models/transaction_test.rb
Normal file
|
@ -0,0 +1,124 @@
|
|||
require "test_helper"
|
||||
|
||||
class TransactionTest < ActiveSupport::TestCase
|
||||
include EntriesTestHelper
|
||||
|
||||
setup do
|
||||
@family = families(:dylan_family)
|
||||
@checking_account = accounts(:depository)
|
||||
@credit_card_account = accounts(:credit_card)
|
||||
@loan_account = accounts(:loan)
|
||||
end
|
||||
|
||||
test "search filters by transaction types using kind enum" do
|
||||
# Create different types of transactions using the helper method
|
||||
standard_entry = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: 100,
|
||||
category: categories(:food_and_drink)
|
||||
)
|
||||
standard_entry.entryable.update!(kind: "standard")
|
||||
|
||||
transfer_entry = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: 200
|
||||
)
|
||||
transfer_entry.entryable.update!(kind: "transfer")
|
||||
|
||||
payment_entry = create_transaction(
|
||||
account: @credit_card_account,
|
||||
amount: -300
|
||||
)
|
||||
payment_entry.entryable.update!(kind: "payment")
|
||||
|
||||
loan_payment_entry = create_transaction(
|
||||
account: @loan_account,
|
||||
amount: 400
|
||||
)
|
||||
loan_payment_entry.entryable.update!(kind: "loan_payment")
|
||||
|
||||
one_time_entry = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: 500
|
||||
)
|
||||
one_time_entry.entryable.update!(kind: "one_time")
|
||||
|
||||
# Test transfer type filter
|
||||
transfer_results = Transaction.search(types: [ "transfer" ])
|
||||
transfer_ids = transfer_results.pluck(:id)
|
||||
|
||||
assert_includes transfer_ids, transfer_entry.entryable.id
|
||||
assert_includes transfer_ids, payment_entry.entryable.id
|
||||
assert_includes transfer_ids, one_time_entry.entryable.id
|
||||
assert_not_includes transfer_ids, standard_entry.entryable.id
|
||||
assert_not_includes transfer_ids, loan_payment_entry.entryable.id
|
||||
|
||||
# Test expense type filter (should include loan_payment)
|
||||
expense_results = Transaction.search(types: [ "expense" ])
|
||||
expense_ids = expense_results.pluck(:id)
|
||||
|
||||
assert_includes expense_ids, standard_entry.entryable.id
|
||||
assert_includes expense_ids, loan_payment_entry.entryable.id
|
||||
assert_not_includes expense_ids, transfer_entry.entryable.id
|
||||
assert_not_includes expense_ids, payment_entry.entryable.id
|
||||
assert_not_includes expense_ids, one_time_entry.entryable.id
|
||||
|
||||
# Test income type filter
|
||||
income_entry = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: -600
|
||||
)
|
||||
income_entry.entryable.update!(kind: "standard")
|
||||
|
||||
income_results = Transaction.search(types: [ "income" ])
|
||||
income_ids = income_results.pluck(:id)
|
||||
|
||||
assert_includes income_ids, income_entry.entryable.id
|
||||
assert_not_includes income_ids, standard_entry.entryable.id
|
||||
assert_not_includes income_ids, loan_payment_entry.entryable.id
|
||||
assert_not_includes income_ids, transfer_entry.entryable.id
|
||||
|
||||
# Test combined expense and income filter (excludes transfers)
|
||||
non_transfer_results = Transaction.search(types: [ "expense", "income" ])
|
||||
non_transfer_ids = non_transfer_results.pluck(:id)
|
||||
|
||||
assert_includes non_transfer_ids, standard_entry.entryable.id
|
||||
assert_includes non_transfer_ids, income_entry.entryable.id
|
||||
assert_includes non_transfer_ids, loan_payment_entry.entryable.id
|
||||
assert_not_includes non_transfer_ids, transfer_entry.entryable.id
|
||||
assert_not_includes non_transfer_ids, payment_entry.entryable.id
|
||||
assert_not_includes non_transfer_ids, one_time_entry.entryable.id
|
||||
end
|
||||
|
||||
test "search category filter handles uncategorized transactions correctly with kind filtering" do
|
||||
# Create uncategorized transactions of different kinds
|
||||
uncategorized_standard = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: 100
|
||||
)
|
||||
uncategorized_standard.entryable.update!(kind: "standard")
|
||||
|
||||
uncategorized_transfer = create_transaction(
|
||||
account: @checking_account,
|
||||
amount: 200
|
||||
)
|
||||
uncategorized_transfer.entryable.update!(kind: "transfer")
|
||||
|
||||
uncategorized_loan_payment = create_transaction(
|
||||
account: @loan_account,
|
||||
amount: 300
|
||||
)
|
||||
uncategorized_loan_payment.entryable.update!(kind: "loan_payment")
|
||||
|
||||
# Search for uncategorized transactions
|
||||
uncategorized_results = Transaction.search(categories: [ "Uncategorized" ])
|
||||
uncategorized_ids = uncategorized_results.pluck(:id)
|
||||
|
||||
# Should include standard and loan_payment (budget-relevant) uncategorized transactions
|
||||
assert_includes uncategorized_ids, uncategorized_standard.entryable.id
|
||||
assert_includes uncategorized_ids, uncategorized_loan_payment.entryable.id
|
||||
|
||||
# Should exclude transfer transactions even if uncategorized
|
||||
assert_not_includes uncategorized_ids, uncategorized_transfer.entryable.id
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue