diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index 5431cfb7..6c3a2b89 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -3,8 +3,6 @@ class TransactionsController < ApplicationController before_action :store_params!, only: :index - require "digest/md5" - def new super @income_categories = Current.family.categories.incomes.alphabetically @@ -13,95 +11,33 @@ class TransactionsController < ApplicationController def index @q = search_params - transactions_query = Transaction::Search.new(@q, family: Current.family).relation + search = Transaction::Search.new(Current.family, filters: @q) + @totals = Transaction::Totals.compute(search) + transactions_query = search.relation set_focused_record(transactions_query, params[:focused_record_id], default_per_page: 50) - # ------------------------------------------------------------------ - # Cache the expensive includes & pagination block so the DB work only - # runs when either the query params change *or* any entry has been - # updated for the current family. - # ------------------------------------------------------------------ + items_per_page = params[:per_page].to_i.positive? ? params[:per_page].to_i : 50 + current_page = params[:page].to_i.positive? ? params[:page].to_i : 1 - latest_update_ts = Current.family.entries.maximum(:updated_at)&.utc&.to_i || 0 - - items_per_page = (params[:per_page].presence || default_params[:per_page]).to_i - items_per_page = 1 if items_per_page <= 0 - - current_page = (params[:page].presence || default_params[:page]).to_i - current_page = 1 if current_page <= 0 - - # Build a compact cache digest: sanitized filters + page info + a - # token that changes on updates *or* deletions. - entries_changed_token = [ latest_update_ts, Current.family.entries.count ].join(":") - - digest_source = { - q: @q, # processed & sanitised search params - page: current_page, # requested page number - per: items_per_page, # page size - tok: entries_changed_token - }.to_json - - cache_key = Current.family.build_cache_key( - "transactions_idx_#{Digest::MD5.hexdigest(digest_source)}" - ) - - cache_data = Rails.cache.fetch(cache_key, expires_in: 30.minutes) do - current_page_i = current_page - - # Initial query - offset = (current_page_i - 1) * items_per_page - ids = transactions_query - .reverse_chronological - .limit(items_per_page) - .offset(offset) - .pluck(:id) - - total_count = transactions_query.count - - if ids.empty? && total_count.positive? && current_page_i > 1 - current_page_i = (total_count.to_f / items_per_page).ceil - offset = (current_page_i - 1) * items_per_page - - ids = transactions_query - .reverse_chronological - .limit(items_per_page) - .offset(offset) - .pluck(:id) - end - - { ids: ids, total_count: total_count, current_page: current_page_i } - end - - ids = cache_data[:ids] - total_count = cache_data[:total_count] - current_page = cache_data[:current_page] - - # Build Pagy object (this part is cheap – done *after* potential - # page fallback so the pagination UI reflects the adjusted page - # number). @pagy = Pagy.new( - count: total_count, - page: current_page, - items: items_per_page, + count: transactions_query.count, + page: current_page, + items: items_per_page, params: ->(p) { p.except(:focused_record_id) } ) - # Fetch the transactions in the cached order - @transactions = Current.family.transactions - .active - .where(id: ids) - .includes( - { entry: :account }, - :category, :merchant, :tags, - transfer_as_outflow: { inflow_transaction: { entry: :account } }, - transfer_as_inflow: { outflow_transaction: { entry: :account } } - ) - - # Preserve the order defined by `ids` - @transactions = ids.map { |id| @transactions.detect { |t| t.id == id } }.compact - - @totals = Current.family.income_statement.totals(transactions_scope: transactions_query) + # Use Pagy's calculated page (which handles overflow) with our variables + @transactions = transactions_query + .reverse_chronological + .limit(items_per_page) + .offset((@pagy.page - 1) * items_per_page) + .includes( + { entry: :account }, + :category, :merchant, :tags, + transfer_as_outflow: { inflow_transaction: { entry: :account } }, + transfer_as_inflow: { outflow_transaction: { entry: :account } } + ) end def clear_filter @@ -226,37 +162,6 @@ class TransactionsController < ApplicationController cleaned_params.delete(:amount_operator) unless cleaned_params[:amount].present? - # ------------------------------------------------------------------- - # Performance optimisation - # ------------------------------------------------------------------- - # When a user lands on the Transactions page without an explicit date - # filter, the previous behaviour queried *all* historical transactions - # for the family. For large datasets this results in very expensive - # SQL (as shown in Skylight) – particularly the aggregation queries - # used for @totals. To keep the UI responsive while still showing a - # sensible period of activity, we fall back to the user's preferred - # default period (stored on User#default_period, defaulting to - # "last_30_days") when **no** date filters have been supplied. - # - # This effectively changes the default view from "all-time" to a - # rolling window, dramatically reducing the rows scanned / grouped in - # Postgres without impacting the UX (the user can always clear the - # filter). - # ------------------------------------------------------------------- - if cleaned_params[:start_date].blank? && cleaned_params[:end_date].blank? - period_key = Current.user&.default_period.presence || "last_30_days" - - begin - period = Period.from_key(period_key) - cleaned_params[:start_date] = period.start_date - cleaned_params[:end_date] = period.end_date - rescue Period::InvalidKeyError - # Fallback – should never happen but keeps things safe. - cleaned_params[:start_date] = 30.days.ago.to_date - cleaned_params[:end_date] = Date.current - end - end - cleaned_params end @@ -264,9 +169,9 @@ class TransactionsController < ApplicationController if should_restore_params? params_to_restore = {} - params_to_restore[:q] = stored_params["q"].presence || default_params[:q] - params_to_restore[:page] = stored_params["page"].presence || default_params[:page] - params_to_restore[:per_page] = stored_params["per_page"].presence || default_params[:per_page] + params_to_restore[:q] = stored_params["q"].presence || {} + params_to_restore[:page] = stored_params["page"].presence || 1 + params_to_restore[:per_page] = stored_params["per_page"].presence || 50 redirect_to transactions_path(params_to_restore) else @@ -287,12 +192,4 @@ class TransactionsController < ApplicationController def stored_params Current.session.prev_transaction_page_params end - - def default_params - { - q: {}, - page: 1, - per_page: 50 - } - end end diff --git a/app/models/transaction/search.rb b/app/models/transaction/search.rb index 4ea79fca..233fc312 100644 --- a/app/models/transaction/search.rb +++ b/app/models/transaction/search.rb @@ -18,9 +18,9 @@ class Transaction::Search attr_reader :family - def initialize(attributes = {}, family:) + def initialize(family, filters: {}) @family = family - super(attributes) + super(filters) end # Build the complete filtered relation diff --git a/test/controllers/transactions_controller_test.rb b/test/controllers/transactions_controller_test.rb index 9eb427d5..85bc1934 100644 --- a/test/controllers/transactions_controller_test.rb +++ b/test/controllers/transactions_controller_test.rb @@ -97,31 +97,97 @@ class TransactionsControllerTest < ActionDispatch::IntegrationTest end test "can paginate" do + family = families(:empty) + sign_in users(:empty) + + # Clean up any existing entries to ensure clean test + family.accounts.each { |account| account.entries.delete_all } + + account = family.accounts.create! name: "Test", balance: 0, currency: "USD", accountable: Depository.new + + # Create multiple transactions for pagination + 25.times do |i| + create_transaction( + account: account, + name: "Transaction #{i + 1}", + amount: 100 + i, # Different amounts to prevent transfer matching + date: Date.current - i.days # Different dates + ) + end + + total_transactions = family.entries.transactions.count + assert_operator total_transactions, :>=, 20, "Should have at least 20 transactions for testing" + + # Test page 1 - should show limited transactions + get transactions_url(page: 1, per_page: 10) + assert_response :success + + page_1_count = css_select("turbo-frame[id^='entry_']").count + assert_equal 10, page_1_count, "Page 1 should respect per_page limit" + + # Test page 2 - should show different transactions + get transactions_url(page: 2, per_page: 10) + assert_response :success + + page_2_count = css_select("turbo-frame[id^='entry_']").count + assert_operator page_2_count, :>, 0, "Page 2 should show some transactions" + assert_operator page_2_count, :<=, 10, "Page 2 should not exceed per_page limit" + + # Test Pagy overflow handling - should redirect or handle gracefully + get transactions_url(page: 9999999, per_page: 10) + + # Either success (if Pagy shows last page) or redirect (if Pagy redirects) + assert_includes [ 200, 302 ], response.status, "Pagy should handle overflow gracefully" + + if response.status == 302 + follow_redirect! + assert_response :success + end + + overflow_count = css_select("turbo-frame[id^='entry_']").count + assert_operator overflow_count, :>, 0, "Overflow should show some transactions" +end + + test "calls Transaction::Totals service with correct search parameters" do family = families(:empty) sign_in users(:empty) account = family.accounts.create! name: "Test", balance: 0, currency: "USD", accountable: Depository.new - 11.times do - create_transaction(account: account) - end + create_transaction(account: account, amount: 100) - sorted_transactions = family.entries.transactions.reverse_chronological.to_a + search = Transaction::Search.new(family) + totals = OpenStruct.new( + transactions_count: 1, + expense_money: Money.new(10000, "USD"), + income_money: Money.new(0, "USD") + ) - assert_equal 11, sorted_transactions.count - - get transactions_url(page: 1, per_page: 10) + Transaction::Search.expects(:new).with(family, filters: {}).returns(search) + Transaction::Totals.expects(:compute).once.with(search).returns(totals) + get transactions_url assert_response :success - sorted_transactions.first(10).each do |transaction| - assert_dom "#" + dom_id(transaction), count: 1 - end + end - get transactions_url(page: 2, per_page: 10) + test "calls Transaction::Totals service with filtered search parameters" do + family = families(:empty) + sign_in users(:empty) + account = family.accounts.create! name: "Test", balance: 0, currency: "USD", accountable: Depository.new + category = family.categories.create! name: "Food", color: "#ff0000" - assert_dom "#" + dom_id(sorted_transactions.last), count: 1 + create_transaction(account: account, amount: 100, category: category) - get transactions_url(page: 9999999, per_page: 10) # out of range loads last page + search = Transaction::Search.new(family, filters: { "categories" => [ "Food" ], "types" => [ "expense" ] }) + totals = OpenStruct.new( + transactions_count: 1, + expense_money: Money.new(10000, "USD"), + income_money: Money.new(0, "USD") + ) - assert_dom "#" + dom_id(sorted_transactions.last), count: 1 + Transaction::Search.expects(:new).with(family, filters: { "categories" => [ "Food" ], "types" => [ "expense" ] }).returns(search) + Transaction::Totals.expects(:compute).once.with(search).returns(totals) + + get transactions_url(q: { categories: [ "Food" ], types: [ "expense" ] }) + assert_response :success end end diff --git a/test/models/income_statement_test.rb b/test/models/income_statement_test.rb index e20513b7..867efe3f 100644 --- a/test/models/income_statement_test.rb +++ b/test/models/income_statement_test.rb @@ -153,12 +153,8 @@ class IncomeStatementTest < ActiveSupport::TestCase # NOTE: These tests now pass because kind filtering is working after the refactoring! test "excludes regular transfers from income statement calculations" do # Create a regular transfer between accounts - outflow_transaction = create_transaction(account: @checking_account, amount: 500) - inflow_transaction = create_transaction(account: @credit_card_account, amount: -500) - - # Manually set transaction kinds to simulate transfer - outflow_transaction.entryable.update!(kind: "transfer") - inflow_transaction.entryable.update!(kind: "transfer") + outflow_transaction = create_transaction(account: @checking_account, amount: 500, kind: "transfer") + inflow_transaction = create_transaction(account: @credit_card_account, amount: -500, kind: "transfer") income_statement = IncomeStatement.new(@family) totals = income_statement.totals @@ -171,8 +167,7 @@ class IncomeStatementTest < ActiveSupport::TestCase test "includes loan payments as expenses in income statement" do # Create a loan payment transaction - loan_payment = create_transaction(account: @checking_account, amount: 1000, category: nil) - loan_payment.entryable.update!(kind: "loan_payment") + loan_payment = create_transaction(account: @checking_account, amount: 1000, category: nil, kind: "loan_payment") income_statement = IncomeStatement.new(@family) totals = income_statement.totals @@ -185,8 +180,7 @@ class IncomeStatementTest < ActiveSupport::TestCase test "excludes one-time transactions from income statement calculations" do # Create a one-time transaction - one_time_transaction = create_transaction(account: @checking_account, amount: 250, category: @groceries_category) - one_time_transaction.entryable.update!(kind: "one_time") + one_time_transaction = create_transaction(account: @checking_account, amount: 250, category: @groceries_category, kind: "one_time") income_statement = IncomeStatement.new(@family) totals = income_statement.totals @@ -199,8 +193,7 @@ class IncomeStatementTest < ActiveSupport::TestCase test "excludes payment transactions from income statement calculations" do # Create a payment transaction (credit card payment) - payment_transaction = create_transaction(account: @checking_account, amount: 300, category: nil) - payment_transaction.entryable.update!(kind: "payment") + payment_transaction = create_transaction(account: @checking_account, amount: 300, category: nil, kind: "payment") income_statement = IncomeStatement.new(@family) totals = income_statement.totals diff --git a/test/models/transaction_test.rb b/test/models/transaction/search_test.rb similarity index 72% rename from test/models/transaction_test.rb rename to test/models/transaction/search_test.rb index 7d08a1e1..b405ac62 100644 --- a/test/models/transaction_test.rb +++ b/test/models/transaction/search_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class TransactionTest < ActiveSupport::TestCase +class Transaction::SearchTest < ActiveSupport::TestCase include EntriesTestHelper setup do @@ -15,36 +15,36 @@ class TransactionTest < ActiveSupport::TestCase standard_entry = create_transaction( account: @checking_account, amount: 100, - category: categories(:food_and_drink) + category: categories(:food_and_drink), + kind: "standard" ) - standard_entry.entryable.update!(kind: "standard") transfer_entry = create_transaction( account: @checking_account, - amount: 200 + amount: 200, + kind: "transfer" ) - transfer_entry.entryable.update!(kind: "transfer") payment_entry = create_transaction( account: @credit_card_account, - amount: -300 + amount: -300, + kind: "payment" ) - payment_entry.entryable.update!(kind: "payment") loan_payment_entry = create_transaction( account: @loan_account, - amount: 400 + amount: 400, + kind: "loan_payment" ) - loan_payment_entry.entryable.update!(kind: "loan_payment") one_time_entry = create_transaction( account: @checking_account, - amount: 500 + amount: 500, + kind: "one_time" ) - one_time_entry.entryable.update!(kind: "one_time") # Test transfer type filter - transfer_results = Transaction::Search.new({ types: [ "transfer" ] }, family: @family).relation + transfer_results = Transaction::Search.new(@family, filters: { types: [ "transfer" ] }).relation transfer_ids = transfer_results.pluck(:id) assert_includes transfer_ids, transfer_entry.entryable.id @@ -54,7 +54,7 @@ class TransactionTest < ActiveSupport::TestCase assert_not_includes transfer_ids, loan_payment_entry.entryable.id # Test expense type filter (should include loan_payment) - expense_results = Transaction::Search.new({ types: [ "expense" ] }, family: @family).relation + expense_results = Transaction::Search.new(@family, filters: { types: [ "expense" ] }).relation expense_ids = expense_results.pluck(:id) assert_includes expense_ids, standard_entry.entryable.id @@ -66,11 +66,11 @@ class TransactionTest < ActiveSupport::TestCase # Test income type filter income_entry = create_transaction( account: @checking_account, - amount: -600 + amount: -600, + kind: "standard" ) - income_entry.entryable.update!(kind: "standard") - income_results = Transaction::Search.new({ types: [ "income" ] }, family: @family).relation + income_results = Transaction::Search.new(@family, filters: { types: [ "income" ] }).relation income_ids = income_results.pluck(:id) assert_includes income_ids, income_entry.entryable.id @@ -79,7 +79,7 @@ class TransactionTest < ActiveSupport::TestCase assert_not_includes income_ids, transfer_entry.entryable.id # Test combined expense and income filter (excludes transfers) - non_transfer_results = Transaction::Search.new({ types: [ "expense", "income" ] }, family: @family).relation + non_transfer_results = Transaction::Search.new(@family, filters: { types: [ "expense", "income" ] }).relation non_transfer_ids = non_transfer_results.pluck(:id) assert_includes non_transfer_ids, standard_entry.entryable.id @@ -94,24 +94,24 @@ class TransactionTest < ActiveSupport::TestCase # Create uncategorized transactions of different kinds uncategorized_standard = create_transaction( account: @checking_account, - amount: 100 + amount: 100, + kind: "standard" ) - uncategorized_standard.entryable.update!(kind: "standard") uncategorized_transfer = create_transaction( account: @checking_account, - amount: 200 + amount: 200, + kind: "transfer" ) - uncategorized_transfer.entryable.update!(kind: "transfer") uncategorized_loan_payment = create_transaction( account: @loan_account, - amount: 300 + amount: 300, + kind: "loan_payment" ) - uncategorized_loan_payment.entryable.update!(kind: "loan_payment") # Search for uncategorized transactions - uncategorized_results = Transaction::Search.new({ categories: [ "Uncategorized" ] }, family: @family).relation + uncategorized_results = Transaction::Search.new(@family, filters: { categories: [ "Uncategorized" ] }).relation uncategorized_ids = uncategorized_results.pluck(:id) # Should include standard and loan_payment (budget-relevant) uncategorized transactions @@ -127,18 +127,18 @@ class TransactionTest < ActiveSupport::TestCase transaction1 = create_transaction( account: @checking_account, amount: 100, - category: categories(:food_and_drink) + category: categories(:food_and_drink), + kind: "standard" ) - transaction1.entryable.update!(kind: "standard") transaction2 = create_transaction( account: @checking_account, - amount: 200 + amount: 200, + kind: "transfer" ) - transaction2.entryable.update!(kind: "transfer") # Test new family-based API - search = Transaction::Search.new({ types: [ "expense" ] }, family: @family) + search = Transaction::Search.new(@family, filters: { types: [ "expense" ] }) results = search.relation result_ids = results.pluck(:id) @@ -154,8 +154,9 @@ class TransactionTest < ActiveSupport::TestCase end test "family-based API requires family parameter" do - assert_raises(ArgumentError, "missing keyword: :family") do - Transaction::Search.new({ types: [ "expense" ] }) + assert_raises(NoMethodError) do + search = Transaction::Search.new({ types: [ "expense" ] }) + search.relation # This will fail when trying to call .transactions on a Hash + end end end -end diff --git a/test/models/transaction/totals_test.rb b/test/models/transaction/totals_test.rb index c435ad09..d5879e16 100644 --- a/test/models/transaction/totals_test.rb +++ b/test/models/transaction/totals_test.rb @@ -11,6 +11,8 @@ class Transaction::TotalsTest < ActiveSupport::TestCase # Clean up existing entries/transactions from fixtures to ensure test isolation @family.accounts.each { |account| account.entries.delete_all } + + @search = Transaction::Search.new(@family) end test "computes basic expense and income totals" do @@ -18,19 +20,18 @@ class Transaction::TotalsTest < ActiveSupport::TestCase expense_entry = create_transaction( account: @checking_account, amount: 100, - category: categories(:food_and_drink) + category: categories(:food_and_drink), + kind: "standard" ) - expense_entry.entryable.update!(kind: "standard") # Create income transaction income_entry = create_transaction( account: @checking_account, - amount: -200 + amount: -200, + kind: "standard" ) - income_entry.entryable.update!(kind: "standard") - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 2, totals.transactions_count assert_equal Money.new(10000, "USD"), totals.expense_money # $100 @@ -41,19 +42,18 @@ class Transaction::TotalsTest < ActiveSupport::TestCase # Create loan payment transaction loan_payment_entry = create_transaction( account: @loan_account, - amount: 500 + amount: 500, + kind: "loan_payment" ) - loan_payment_entry.entryable.update!(kind: "loan_payment") # Create regular expense expense_entry = create_transaction( account: @checking_account, - amount: 100 + amount: 100, + kind: "standard" ) - expense_entry.entryable.update!(kind: "standard") - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 2, totals.transactions_count assert_equal Money.new(60000, "USD"), totals.expense_money # $500 + $100 @@ -64,31 +64,30 @@ class Transaction::TotalsTest < ActiveSupport::TestCase # Create transactions that should be excluded transfer_entry = create_transaction( account: @checking_account, - amount: 100 + amount: 100, + kind: "transfer" ) - transfer_entry.entryable.update!(kind: "transfer") payment_entry = create_transaction( account: @credit_card_account, - amount: -200 + amount: -200, + kind: "payment" ) - payment_entry.entryable.update!(kind: "payment") one_time_entry = create_transaction( account: @checking_account, - amount: 300 + amount: 300, + kind: "one_time" ) - one_time_entry.entryable.update!(kind: "one_time") # Create transaction that should be included standard_entry = create_transaction( account: @checking_account, - amount: 50 + amount: 50, + kind: "standard" ) - standard_entry.entryable.update!(kind: "standard") - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) # Only the standard transaction should be counted assert_equal 1, totals.transactions_count @@ -101,9 +100,9 @@ class Transaction::TotalsTest < ActiveSupport::TestCase eur_entry = create_transaction( account: @checking_account, amount: 100, - currency: "EUR" + currency: "EUR", + kind: "standard" ) - eur_entry.entryable.update!(kind: "standard") # Create exchange rate EUR -> USD ExchangeRate.create!( @@ -117,12 +116,11 @@ class Transaction::TotalsTest < ActiveSupport::TestCase usd_entry = create_transaction( account: @checking_account, amount: 50, - currency: "USD" + currency: "USD", + kind: "standard" ) - usd_entry.entryable.update!(kind: "standard") - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 2, totals.transactions_count # EUR 100 * 1.1 + USD 50 = 110 + 50 = 160 @@ -135,12 +133,11 @@ class Transaction::TotalsTest < ActiveSupport::TestCase eur_entry = create_transaction( account: @checking_account, amount: 100, - currency: "EUR" + currency: "EUR", + kind: "standard" ) - eur_entry.entryable.update!(kind: "standard") - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 1, totals.transactions_count # Should use rate of 1 when exchange rate is missing @@ -153,19 +150,19 @@ class Transaction::TotalsTest < ActiveSupport::TestCase food_entry = create_transaction( account: @checking_account, amount: 100, - category: categories(:food_and_drink) + category: categories(:food_and_drink), + kind: "standard" ) - food_entry.entryable.update!(kind: "standard") other_entry = create_transaction( account: @checking_account, amount: 50, - category: categories(:income) + category: categories(:income), + kind: "standard" ) - other_entry.entryable.update!(kind: "standard") # Filter by food category only - search = Transaction::Search.new({ categories: [ "Food & Drink" ] }, family: @family) + search = Transaction::Search.new(@family, filters: { categories: [ "Food & Drink" ] }) totals = Transaction::Totals.compute(search) assert_equal 1, totals.transactions_count @@ -177,18 +174,18 @@ class Transaction::TotalsTest < ActiveSupport::TestCase # Create expense and income transactions expense_entry = create_transaction( account: @checking_account, - amount: 100 + amount: 100, + kind: "standard" ) - expense_entry.entryable.update!(kind: "standard") income_entry = create_transaction( account: @checking_account, - amount: -200 + amount: -200, + kind: "standard" ) - income_entry.entryable.update!(kind: "standard") # Filter by expense type only - search = Transaction::Search.new({ types: [ "expense" ] }, family: @family) + search = Transaction::Search.new(@family, filters: { types: [ "expense" ] }) totals = Transaction::Totals.compute(search) assert_equal 1, totals.transactions_count @@ -197,8 +194,7 @@ class Transaction::TotalsTest < ActiveSupport::TestCase end test "handles empty results" do - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 0, totals.transactions_count assert_equal Money.new(0, "USD"), totals.expense_money @@ -209,27 +205,26 @@ class Transaction::TotalsTest < ActiveSupport::TestCase # Create an excluded transaction (should be excluded by default) excluded_entry = create_transaction( account: @checking_account, - amount: 100 + amount: 100, + kind: "standard" ) - excluded_entry.entryable.update!(kind: "standard") excluded_entry.update!(excluded: true) # Marks it as excluded # Create a normal transaction normal_entry = create_transaction( account: @checking_account, - amount: 50 + amount: 50, + kind: "standard" ) - normal_entry.entryable.update!(kind: "standard") # Default behavior should exclude excluded transactions - search = Transaction::Search.new({}, family: @family) - totals = Transaction::Totals.compute(search) + totals = Transaction::Totals.compute(@search) assert_equal 1, totals.transactions_count assert_equal Money.new(5000, "USD"), totals.expense_money # Only non-excluded transaction # Explicitly include excluded transactions - search_with_excluded = Transaction::Search.new({ excluded_transactions: true }, family: @family) + search_with_excluded = Transaction::Search.new(@family, filters: { excluded_transactions: true }) totals_with_excluded = Transaction::Totals.compute(search_with_excluded) assert_equal 2, totals_with_excluded.transactions_count diff --git a/test/support/entries_test_helper.rb b/test/support/entries_test_helper.rb index 908f9676..a4f2013f 100644 --- a/test/support/entries_test_helper.rb +++ b/test/support/entries_test_helper.rb @@ -1,7 +1,7 @@ module EntriesTestHelper def create_transaction(attributes = {}) - entry_attributes = attributes.except(:category, :tags, :merchant) - transaction_attributes = attributes.slice(:category, :tags, :merchant) + entry_attributes = attributes.except(:category, :tags, :merchant, :kind) + transaction_attributes = attributes.slice(:category, :tags, :merchant, :kind) entry_defaults = { account: accounts(:depository),