mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
Auto-categorize investment contributions across all transfer paths (#924)
* Ensure investment contributions are auto-categorized with proper kind and category creation. * Retrigger CI
This commit is contained in:
@@ -39,4 +39,24 @@ class TransferMatchesControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to transactions_url
|
||||
assert_equal "Transfer created", flash[:notice]
|
||||
end
|
||||
|
||||
test "assigns investment_contribution kind and category for investment destination" do
|
||||
# Outflow from depository (positive amount), target is investment
|
||||
outflow_entry = create_transaction(amount: 100, account: accounts(:depository))
|
||||
|
||||
post transaction_transfer_match_path(outflow_entry), params: {
|
||||
transfer_match: {
|
||||
method: "new",
|
||||
target_account_id: accounts(:investment).id
|
||||
}
|
||||
}
|
||||
|
||||
outflow_entry.reload
|
||||
outflow_txn = outflow_entry.entryable
|
||||
|
||||
assert_equal "investment_contribution", outflow_txn.kind
|
||||
|
||||
category = @user.family.investment_contributions_category
|
||||
assert_equal category, outflow_txn.category
|
||||
end
|
||||
end
|
||||
|
||||
@@ -109,6 +109,19 @@ class Family::AutoTransferMatchableTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "auto-matched cash to investment assigns investment contribution category" do
|
||||
investment = accounts(:investment)
|
||||
outflow_entry = create_transaction(date: Date.current, account: @depository, amount: 500)
|
||||
inflow_entry = create_transaction(date: Date.current, account: investment, amount: -500)
|
||||
|
||||
@family.auto_match_transfers!
|
||||
|
||||
outflow_entry.reload
|
||||
|
||||
category = @family.investment_contributions_category
|
||||
assert_equal category, outflow_entry.entryable.category
|
||||
end
|
||||
|
||||
test "does not match multi-currency transfer with missing exchange rate" do
|
||||
create_transaction(date: Date.current, account: @depository, amount: 500)
|
||||
create_transaction(date: Date.current, account: @credit_card, amount: -700, currency: "GBP")
|
||||
|
||||
@@ -7,6 +7,35 @@ class FamilyTest < ActiveSupport::TestCase
|
||||
@syncable = families(:dylan_family)
|
||||
end
|
||||
|
||||
test "investment_contributions_category creates category when missing" do
|
||||
family = families(:dylan_family)
|
||||
family.categories.where(name: Category.investment_contributions_name).destroy_all
|
||||
|
||||
assert_nil family.categories.find_by(name: Category.investment_contributions_name)
|
||||
|
||||
category = family.investment_contributions_category
|
||||
|
||||
assert category.persisted?
|
||||
assert_equal Category.investment_contributions_name, category.name
|
||||
assert_equal "#0d9488", category.color
|
||||
assert_equal "expense", category.classification
|
||||
assert_equal "trending-up", category.lucide_icon
|
||||
end
|
||||
|
||||
test "investment_contributions_category returns existing category" do
|
||||
family = families(:dylan_family)
|
||||
existing = family.categories.find_or_create_by!(name: Category.investment_contributions_name) do |c|
|
||||
c.color = "#0d9488"
|
||||
c.classification = "expense"
|
||||
c.lucide_icon = "trending-up"
|
||||
end
|
||||
|
||||
assert_no_difference "Category.count" do
|
||||
result = family.investment_contributions_category
|
||||
assert_equal existing, result
|
||||
end
|
||||
end
|
||||
|
||||
test "available_merchants includes family merchants without transactions" do
|
||||
family = families(:dylan_family)
|
||||
|
||||
|
||||
@@ -149,6 +149,70 @@ class PlaidAccount::Investments::TransactionsProcessorTest < ActiveSupport::Test
|
||||
assert_equal -1, entry.trade.qty
|
||||
end
|
||||
|
||||
test "creates contribution transactions as cash transactions" do
|
||||
test_investments_payload = {
|
||||
transactions: [
|
||||
{
|
||||
"investment_transaction_id" => "contrib_123",
|
||||
"type" => "contribution",
|
||||
"amount" => -500.0,
|
||||
"iso_currency_code" => "USD",
|
||||
"date" => Date.current,
|
||||
"name" => "401k Contribution"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@plaid_account.update!(raw_holdings_payload: test_investments_payload)
|
||||
|
||||
@security_resolver.expects(:resolve).never
|
||||
|
||||
processor = PlaidAccount::Investments::TransactionsProcessor.new(@plaid_account, security_resolver: @security_resolver)
|
||||
|
||||
assert_difference [ "Entry.count", "Transaction.count" ], 1 do
|
||||
processor.process
|
||||
end
|
||||
|
||||
entry = Entry.order(created_at: :desc).first
|
||||
|
||||
assert_equal(-500.0, entry.amount)
|
||||
assert_equal "USD", entry.currency
|
||||
assert_equal "401k Contribution", entry.name
|
||||
assert_instance_of Transaction, entry.entryable
|
||||
end
|
||||
|
||||
test "creates withdrawal transactions as cash transactions" do
|
||||
test_investments_payload = {
|
||||
transactions: [
|
||||
{
|
||||
"investment_transaction_id" => "withdraw_123",
|
||||
"type" => "withdrawal",
|
||||
"amount" => 1000.0,
|
||||
"iso_currency_code" => "USD",
|
||||
"date" => Date.current,
|
||||
"name" => "IRA Withdrawal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@plaid_account.update!(raw_holdings_payload: test_investments_payload)
|
||||
|
||||
@security_resolver.expects(:resolve).never
|
||||
|
||||
processor = PlaidAccount::Investments::TransactionsProcessor.new(@plaid_account, security_resolver: @security_resolver)
|
||||
|
||||
assert_difference [ "Entry.count", "Transaction.count" ], 1 do
|
||||
processor.process
|
||||
end
|
||||
|
||||
entry = Entry.order(created_at: :desc).first
|
||||
|
||||
assert_equal 1000.0, entry.amount
|
||||
assert_equal "USD", entry.currency
|
||||
assert_equal "IRA Withdrawal", entry.name
|
||||
assert_instance_of Transaction, entry.entryable
|
||||
end
|
||||
|
||||
test "creates transfer transactions as cash transactions" do
|
||||
test_investments_payload = {
|
||||
transactions: [
|
||||
|
||||
@@ -165,6 +165,30 @@ class Rule::ActionTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "set_as_transfer_or_payment assigns investment_contribution kind and category for investment destination" do
|
||||
investment = accounts(:investment)
|
||||
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
action_type: "set_as_transfer_or_payment",
|
||||
value: investment.id
|
||||
)
|
||||
|
||||
# Only apply to txn1 (positive amount = outflow)
|
||||
action.apply(Transaction.where(id: @txn1.id))
|
||||
|
||||
@txn1.reload
|
||||
|
||||
transfer = Transfer.find_by(outflow_transaction_id: @txn1.id) || Transfer.find_by(inflow_transaction_id: @txn1.id)
|
||||
assert transfer.present?, "Transfer should be created"
|
||||
|
||||
assert_equal "investment_contribution", transfer.outflow_transaction.kind
|
||||
assert_equal "funds_movement", transfer.inflow_transaction.kind
|
||||
|
||||
category = @family.investment_contributions_category
|
||||
assert_equal category, transfer.outflow_transaction.category
|
||||
end
|
||||
|
||||
test "set_investment_activity_label ignores invalid values" do
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
|
||||
Reference in New Issue
Block a user