mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-24 15:49:39 +02:00
perf(transactions): add kind
to Transaction
model and remove expensive Transfer joins in aggregations (#2388)
* add kind to transaction model * Basic transfer creator * Fix method naming conflict * Creator form pattern * Remove stale methods * Tweak migration * Remove BaseQuery, write entire query in each class for clarity * Query optimizations * Remove unused exchange rate query lines * Remove temporary cache-warming strategy * Fix test * Update transaction search * Decouple transactions endpoint from IncomeStatement * Clean up transactions controller * Update cursor rules * Cleanup comments, logic in search * Fix totals logic on transactions view * Fix pagination * Optimize search totals query * Default to last 30 days on transactions page if no filters * Decouple transactions list from transfer details * Revert transfer route * Migration reset * Bundle update * Fix matching logic, tests * Remove unused code
This commit is contained in:
parent
7aca5a2277
commit
1aae00f586
49 changed files with 1749 additions and 705 deletions
|
@ -313,13 +313,13 @@ end
|
|||
accountable: Depository.new
|
||||
)
|
||||
|
||||
transfer = Transfer.from_accounts(
|
||||
from_account: from_account,
|
||||
to_account: to_account,
|
||||
transfer = Transfer::Creator.new(
|
||||
family: @family,
|
||||
source_account_id: from_account.id,
|
||||
destination_account_id: to_account.id,
|
||||
date: Date.current,
|
||||
amount: 100
|
||||
)
|
||||
transfer.save!
|
||||
).create
|
||||
|
||||
get api_v1_transaction_url(transfer.inflow_transaction), headers: api_headers(@api_key)
|
||||
assert_response :success
|
||||
|
|
|
@ -39,9 +39,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
assert_difference -> { Entry.count } => 2,
|
||||
-> { Transaction.count } => 2,
|
||||
-> { Transfer.count } => 1 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "deposit",
|
||||
date: Date.current,
|
||||
amount: 10,
|
||||
|
@ -60,9 +59,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
assert_difference -> { Entry.count } => 2,
|
||||
-> { Transaction.count } => 2,
|
||||
-> { Transfer.count } => 1 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "withdrawal",
|
||||
date: Date.current,
|
||||
amount: 10,
|
||||
|
@ -79,9 +77,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
assert_difference -> { Entry.count } => 1,
|
||||
-> { Transaction.count } => 1,
|
||||
-> { Transfer.count } => 0 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "withdrawal",
|
||||
date: Date.current,
|
||||
amount: 10,
|
||||
|
@ -98,9 +95,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
test "creates interest entry" do
|
||||
assert_difference [ "Entry.count", "Transaction.count" ], 1 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "interest",
|
||||
date: Date.current,
|
||||
amount: 10,
|
||||
|
@ -117,9 +113,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
test "creates trade buy entry" do
|
||||
assert_difference [ "Entry.count", "Trade.count", "Security.count" ], 1 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "buy",
|
||||
date: Date.current,
|
||||
ticker: "NVDA (NASDAQ)",
|
||||
|
@ -141,9 +136,8 @@ class TradesControllerTest < ActionDispatch::IntegrationTest
|
|||
|
||||
test "creates trade sell entry" do
|
||||
assert_difference [ "Entry.count", "Trade.count" ], 1 do
|
||||
post trades_url, params: {
|
||||
entry: {
|
||||
account_id: @entry.account_id,
|
||||
post trades_url(account_id: @entry.account_id), params: {
|
||||
model: {
|
||||
type: "sell",
|
||||
ticker: "AAPL (NYSE)",
|
||||
date: Date.current,
|
||||
|
|
|
@ -97,31 +97,98 @@ 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::Search totals method 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(
|
||||
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)
|
||||
expected_filters = { "start_date" => 30.days.ago.to_date }
|
||||
Transaction::Search.expects(:new).with(family, filters: expected_filters).returns(search)
|
||||
search.expects(:totals).once.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::Search totals method 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(
|
||||
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)
|
||||
search.expects(:totals).once.returns(totals)
|
||||
|
||||
get transactions_url(q: { categories: [ "Food" ], types: [ "expense" ] })
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue