diff --git a/app/models/family/auto_transfer_matchable.rb b/app/models/family/auto_transfer_matchable.rb index 62e2295fa..60f583360 100644 --- a/app/models/family/auto_transfer_matchable.rb +++ b/app/models/family/auto_transfer_matchable.rb @@ -69,8 +69,12 @@ module Family::AutoTransferMatchable # Another concurrent job created the transfer; safe to ignore end - Transaction.find(match.inflow_transaction_id).update!(kind: "funds_movement") - Transaction.find(match.outflow_transaction_id).update!(kind: Transfer.kind_for_account(Transaction.find(match.outflow_transaction_id).entry.account)) + inflow_transaction = Transaction.find(match.inflow_transaction_id) + outflow_transaction = Transaction.find(match.outflow_transaction_id) + + # The kind is determined by the DESTINATION account (inflow), matching Transfer::Creator logic + inflow_transaction.update!(kind: "funds_movement") + outflow_transaction.update!(kind: Transfer.kind_for_account(inflow_transaction.entry.account)) used_transaction_ids << match.inflow_transaction_id used_transaction_ids << match.outflow_transaction_id diff --git a/test/models/family/auto_transfer_matchable_test.rb b/test/models/family/auto_transfer_matchable_test.rb index 893da1992..41955f7eb 100644 --- a/test/models/family/auto_transfer_matchable_test.rb +++ b/test/models/family/auto_transfer_matchable_test.rb @@ -7,6 +7,7 @@ class Family::AutoTransferMatchableTest < ActiveSupport::TestCase @family = families(:dylan_family) @depository = accounts(:depository) @credit_card = accounts(:credit_card) + @loan = accounts(:loan) end test "auto-matches transfers" do @@ -117,6 +118,54 @@ class Family::AutoTransferMatchableTest < ActiveSupport::TestCase end end + # Regression tests for loan transfer kind assignment bug + # The kind should be determined by the DESTINATION account (inflow), not the source (outflow) + test "loan payment (cash to loan) assigns loan_payment kind to outflow" do + # Cash → Loan: outflow from depository, inflow to loan + outflow_entry = create_transaction(date: Date.current, account: @depository, amount: 500) + inflow_entry = create_transaction(date: Date.current, account: @loan, amount: -500) + + @family.auto_match_transfers! + + outflow_entry.reload + inflow_entry.reload + + # Destination is loan account, so outflow should be loan_payment + assert_equal "loan_payment", outflow_entry.entryable.kind + assert_equal "funds_movement", inflow_entry.entryable.kind + end + + test "loan disbursement (loan to cash) assigns funds_movement kind to outflow" do + # Loan → Cash: outflow from loan, inflow to depository + outflow_entry = create_transaction(date: Date.current, account: @loan, amount: 500) + inflow_entry = create_transaction(date: Date.current, account: @depository, amount: -500) + + @family.auto_match_transfers! + + outflow_entry.reload + inflow_entry.reload + + # Destination is depository (not loan), so outflow should be funds_movement + # This ensures loan disbursements don't incorrectly appear in cashflow + assert_equal "funds_movement", outflow_entry.entryable.kind + assert_equal "funds_movement", inflow_entry.entryable.kind + end + + test "credit card payment (cash to credit card) assigns cc_payment kind to outflow" do + # Cash → Credit Card: outflow from depository, inflow to credit card + outflow_entry = create_transaction(date: Date.current, account: @depository, amount: 500) + inflow_entry = create_transaction(date: Date.current, account: @credit_card, amount: -500) + + @family.auto_match_transfers! + + outflow_entry.reload + inflow_entry.reload + + # Destination is credit card, so outflow should be cc_payment + assert_equal "cc_payment", outflow_entry.entryable.kind + assert_equal "funds_movement", inflow_entry.entryable.kind + end + private def load_exchange_prices rates = { diff --git a/test/system/transactions_test.rb b/test/system/transactions_test.rb index efc71b0a5..056bf8ad2 100644 --- a/test/system/transactions_test.rb +++ b/test/system/transactions_test.rb @@ -201,10 +201,11 @@ class TransactionsTest < ApplicationSystemTestCase end test "transfers should always sum to zero" do + # Use two accounts that result in funds_movement kind (not investment/crypto which become investment_contribution) asset_account = accounts(:other_asset) - investment_account = accounts(:investment) + depository_account = accounts(:depository) outflow_entry = create_transaction("outflow", Date.current, 500, account: asset_account) - inflow_entry = create_transaction("inflow", 1.day.ago.to_date, -500, account: investment_account) + inflow_entry = create_transaction("inflow", 1.day.ago.to_date, -500, account: depository_account) @user.family.auto_match_transfers! visit transactions_url