1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-18 20:59:39 +02:00
Maybe/app/models/trade/create_form.rb
Zach Gollwitzer 1aae00f586
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
2025-06-20 13:31:58 -04:00

113 lines
3 KiB
Ruby

class Trade::CreateForm
include ActiveModel::Model
attr_accessor :account, :date, :amount, :currency, :qty,
:price, :ticker, :manual_ticker, :type, :transfer_account_id
# Either creates a trade, transaction, or transfer based on type
# Returns the model, regardless of success or failure
def create
case type
when "buy", "sell"
create_trade
when "interest"
create_interest_income
when "deposit", "withdrawal"
create_transfer
end
end
private
# Users can either look up a ticker from our provider (Synth) or enter a manual, "offline" ticker (that we won't fetch prices for)
def security
ticker_symbol, exchange_operating_mic = ticker.present? ? ticker.split("|") : [ manual_ticker, nil ]
Security::Resolver.new(
ticker_symbol,
exchange_operating_mic: exchange_operating_mic
).resolve
end
def create_trade
prefix = type == "sell" ? "Sell " : "Buy "
trade_name = prefix + "#{qty.to_i.abs} shares of #{security.ticker}"
signed_qty = type == "sell" ? -qty.to_d : qty.to_d
signed_amount = signed_qty * price.to_d
trade_entry = account.entries.new(
name: trade_name,
date: date,
amount: signed_amount,
currency: currency,
entryable: Trade.new(
qty: signed_qty,
price: price,
currency: currency,
security: security
)
)
if trade_entry.save
trade_entry.lock_saved_attributes!
account.sync_later
end
trade_entry
end
def create_interest_income
signed_amount = amount.to_d * -1
entry = account.entries.build(
name: "Interest payment",
date: date,
amount: signed_amount,
currency: currency,
entryable: Transaction.new
)
if entry.save
entry.lock_saved_attributes!
account.sync_later
end
entry
end
def create_transfer
if transfer_account_id.present?
from_account_id = type == "withdrawal" ? account.id : transfer_account_id
to_account_id = type == "withdrawal" ? transfer_account_id : account.id
Transfer::Creator.new(
family: account.family,
source_account_id: from_account_id,
destination_account_id: to_account_id,
date: date,
amount: amount
).create
else
create_unlinked_transfer
end
end
# If user doesn't provide the reciprocal account, it's a regular transaction
def create_unlinked_transfer
signed_amount = type == "deposit" ? amount.to_d * -1 : amount.to_d
entry = account.entries.build(
name: signed_amount < 0 ? "Deposit to #{account.name}" : "Withdrawal from #{account.name}",
date: date,
amount: signed_amount,
currency: currency,
entryable: Transaction.new
)
if entry.save
entry.lock_saved_attributes!
account.sync_later
end
entry
end
end