mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 22:34:47 +00:00
* Add security remapping support to holdings - Introduced `provider_security` tracking for holdings with schema updates. - Implemented security remap/reset workflows in `Holding` model and UI. - Updated routes, controllers, and tests to support new functionality. - Enhanced client-side interaction with Stimulus controller for remapping. # Conflicts: # app/components/UI/account/activity_feed.html.erb # db/schema.rb * Refactor "New transaction" to "New activity" across UI and tests - Updated localized strings, button labels, and ARIA attributes. - Improved error handling in holdings' current price display. - Scoped fallback queries in `provider_import_adapter` to prevent overwrites. - Added safeguard for offline securities in price fetching logic. * Update security remapping to merge holdings on collision by deleting duplicates - Removed error handling for collisions in `remap_security!`. - Added logic to merge holdings by deleting duplicates on conflicting dates. - Modified associated test to validate merging behavior. * Update security remapping to merge holdings on collision by combining qty and amount - Modified `remap_security!` to merge holdings by summing `qty` and `amount` on conflicting dates. - Adjusted logic to calculate `price` for merged holdings. - Updated test to validate new merge behavior. * Improve DOM handling in Turbo redirect action & enhance holdings merge logic - Updated Turbo's custom `redirect` action to use the "replace" option for cleaner DOM updates without clearing the cache. - Enhanced holdings merge logic to calculate weighted average cost basis during security remapping, ensuring more accurate cost_basis updates. * Track provider_security_id during security updates to support reset workflows * Fix provider tracking: guard nil ticker lookups and preserve merge attrs - Guard fallback 1b lookup when security.ticker is blank to avoid matching NULL tickers - Preserve external_id, provider_security_id, account_provider_id during collision merge * Fix schema.rb version after merge (includes tax_treatment migration) * fix: Rename migration to run after schema version The migration 20260117000001 was skipped in CI because it had a timestamp earlier than the schema version (2026_01_17_200000). CI loads schema.rb directly and only runs migrations with versions after the schema version. Renamed to 20260119000001 so it runs correctly. * Update schema: remove Coinbase tables, add new fields and indexes * Update schema: add back `tax_treatment` field with default value "taxable" * Improve Turbo redirect action: use "replace" to avoid form submission in history * Lock merged holdings to prevent provider overwrites and fix activity feed template indentation * Refactor holdings transfer logic: enforce currency checks during collisions and enhance merge handling --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: luckyPipewrench <luckypipewrench@proton.me> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
101 lines
3.0 KiB
Ruby
101 lines
3.0 KiB
Ruby
class HoldingsController < ApplicationController
|
|
before_action :set_holding, only: %i[show update destroy unlock_cost_basis remap_security reset_security]
|
|
|
|
def index
|
|
@account = Current.family.accounts.find(params[:account_id])
|
|
end
|
|
|
|
def show
|
|
end
|
|
|
|
def update
|
|
total_cost_basis = holding_params[:cost_basis].to_d
|
|
|
|
if total_cost_basis >= 0 && @holding.qty.positive?
|
|
# Convert total cost basis to per-share cost (the cost_basis field stores per-share)
|
|
# Zero is valid for gifted/inherited shares
|
|
per_share_cost = total_cost_basis / @holding.qty
|
|
@holding.set_manual_cost_basis!(per_share_cost)
|
|
flash[:notice] = t(".success")
|
|
else
|
|
flash[:alert] = t(".error")
|
|
end
|
|
|
|
# Redirect to account page holdings tab to refresh list and close drawer
|
|
redirect_to account_path(@holding.account, tab: "holdings")
|
|
end
|
|
|
|
def unlock_cost_basis
|
|
@holding.unlock_cost_basis!
|
|
flash[:notice] = t(".success")
|
|
|
|
# Redirect to account page holdings tab to refresh list and close drawer
|
|
redirect_to account_path(@holding.account, tab: "holdings")
|
|
end
|
|
|
|
def destroy
|
|
if @holding.account.can_delete_holdings?
|
|
@holding.destroy_holding_and_entries!
|
|
flash[:notice] = t(".success")
|
|
else
|
|
flash[:alert] = "You cannot delete this holding"
|
|
end
|
|
|
|
respond_to do |format|
|
|
format.html { redirect_back_or_to account_path(@holding.account) }
|
|
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, account_path(@holding.account)) }
|
|
end
|
|
end
|
|
|
|
def remap_security
|
|
# Combobox returns "TICKER|EXCHANGE" format
|
|
ticker, exchange = params[:security_id].to_s.split("|")
|
|
|
|
# Validate ticker is present (form has required: true, but can be bypassed)
|
|
if ticker.blank?
|
|
flash[:alert] = t(".security_not_found")
|
|
redirect_to account_path(@holding.account, tab: "holdings")
|
|
return
|
|
end
|
|
|
|
new_security = Security::Resolver.new(
|
|
ticker,
|
|
exchange_operating_mic: exchange,
|
|
country_code: Current.family.country
|
|
).resolve
|
|
|
|
if new_security.nil?
|
|
flash[:alert] = t(".security_not_found")
|
|
redirect_to account_path(@holding.account, tab: "holdings")
|
|
return
|
|
end
|
|
|
|
@holding.remap_security!(new_security)
|
|
flash[:notice] = t(".success")
|
|
|
|
respond_to do |format|
|
|
format.html { redirect_to account_path(@holding.account, tab: "holdings") }
|
|
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, account_path(@holding.account, tab: "holdings")) }
|
|
end
|
|
end
|
|
|
|
def reset_security
|
|
@holding.reset_security_to_provider!
|
|
flash[:notice] = t(".success")
|
|
|
|
respond_to do |format|
|
|
format.html { redirect_to account_path(@holding.account, tab: "holdings") }
|
|
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, account_path(@holding.account, tab: "holdings")) }
|
|
end
|
|
end
|
|
|
|
private
|
|
def set_holding
|
|
@holding = Current.family.holdings.find(params[:id])
|
|
end
|
|
|
|
def holding_params
|
|
params.require(:holding).permit(:cost_basis)
|
|
end
|
|
end
|