diff --git a/app/controllers/budget_categories_controller.rb b/app/controllers/budget_categories_controller.rb index 0490a5f61..65880ea05 100644 --- a/app/controllers/budget_categories_controller.rb +++ b/app/controllers/budget_categories_controller.rb @@ -7,7 +7,15 @@ class BudgetCategoriesController < ApplicationController end def show + # The aggregate `Budget#actual_spending` already excludes transactions + # whose kind is in BUDGET_EXCLUDED_KINDS (funds_movement, one_time, + # cc_payment) via IncomeStatement. The drilldown list must apply the + # same filter, otherwise a matched transfer (post-#874 the matcher + # correctly tags inflow as funds_movement and outflow per destination + # account) shows under the Uncategorized card -- or any retained + # category -- even though the aggregate ignores it. See issue #1059. @recent_transactions = @budget.transactions + .where.not(transactions: { kind: Transaction::BUDGET_EXCLUDED_KINDS }) if params[:id] == BudgetCategory.uncategorized.id @budget_category = @budget.uncategorized_budget_category diff --git a/test/controllers/budget_categories_controller_test.rb b/test/controllers/budget_categories_controller_test.rb index c810baa83..5bea22d96 100644 --- a/test/controllers/budget_categories_controller_test.rb +++ b/test/controllers/budget_categories_controller_test.rb @@ -2,6 +2,7 @@ require "test_helper" class BudgetCategoriesControllerTest < ActionDispatch::IntegrationTest include ActionView::RecordIdentifier + include EntriesTestHelper setup do sign_in users(:family_admin) @@ -107,4 +108,58 @@ class BudgetCategoriesControllerTest < ActionDispatch::IntegrationTest assert_equal 0.0, @electric_budget_category.reload.budgeted_spending.to_f end + + test "show drilldown excludes BUDGET_EXCLUDED_KINDS transfers from recent transactions" do + # Issue #1059: a matched depository <-> CC pair becomes + # (cc_payment outflow + funds_movement inflow). Both kinds are in + # BUDGET_EXCLUDED_KINDS so the budget aggregate excludes them, but + # the per-category drilldown previously listed them anyway -- + # appearing under whatever category they retained (or under + # Uncategorized once the matcher cleared the category). Filter + # them out so the drilldown matches the aggregate. + create_transaction( + date: @budget.start_date, + account: accounts(:depository), + amount: 500, + name: "BUG_1059_REPRO_OUTFLOW" + ) + create_transaction( + date: @budget.start_date, + account: accounts(:credit_card), + amount: -500, + name: "BUG_1059_REPRO_INFLOW" + ) + @family.auto_match_transfers! + + get budget_budget_category_path(@budget, BudgetCategory.uncategorized.id) + assert_response :success + refute_includes @response.body, "BUG_1059_REPRO_OUTFLOW", + "matched cc_payment outflow must not appear in Uncategorized drilldown" + refute_includes @response.body, "BUG_1059_REPRO_INFLOW", + "matched funds_movement inflow must not appear in Uncategorized drilldown" + end + + test "show drilldown still lists loan_payment transfers (intentionally budget-tracked)" do + # loan_payment is NOT in BUDGET_EXCLUDED_KINDS. The drilldown should + # keep showing loan_payment transfers so the user can see what's + # under Uncategorized (or whichever category they manually set). + create_transaction( + date: @budget.start_date, + account: accounts(:depository), + amount: 500, + name: "MORTGAGE_REPRO_OUTFLOW" + ) + create_transaction( + date: @budget.start_date, + account: accounts(:loan), + amount: -500, + name: "MORTGAGE_REPRO_INFLOW" + ) + @family.auto_match_transfers! + + get budget_budget_category_path(@budget, BudgetCategory.uncategorized.id) + assert_response :success + assert_includes @response.body, "MORTGAGE_REPRO_OUTFLOW", + "loan_payment outflow remains visible (kind is not BUDGET_EXCLUDED)" + end end