From e5fbdfb593dcb1c5ec5fbd8993b94a8f853d26f8 Mon Sep 17 00:00:00 2001 From: LPW Date: Mon, 12 Jan 2026 08:05:46 -0500 Subject: [PATCH] Add cost basis source tracking with manual override and lock protection (#623) * Add cost basis tracking and management to holdings - Added migration to introduce `cost_basis_source` and `cost_basis_locked` fields to `holdings`. - Implemented backfill for existing holdings to set `cost_basis_source` based on heuristics. - Introduced `Holding::CostBasisReconciler` to manage cost basis resolution logic. - Added user interface components for editing and locking cost basis in holdings. - Updated `materializer` to integrate reconciliation logic and respect locked holdings. - Extended tests for cost basis-related workflows to ensure accuracy and reliability. * Fix cost basis calculation in holdings controller - Ensure `cost_basis` is converted to decimal for accurate arithmetic. - Fix conditional check to properly validate positive `cost_basis`. * Improve cost basis validation and error handling in holdings controller - Allow zero as a valid cost basis for gifted/inherited shares. - Add error handling with user feedback for invalid cost basis values. --------- Co-authored-by: Josh Waldrep --- db/schema.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 04b9920ae..067d2c9f6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_01_10_180000) do +ActiveRecord::Schema[7.2].define(version: 2026_01_12_065106) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -489,6 +489,8 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_10_180000) do t.string "external_id" t.decimal "cost_basis", precision: 19, scale: 4 t.uuid "account_provider_id" + t.string "cost_basis_source" + t.boolean "cost_basis_locked", default: false, null: false t.index ["account_id", "external_id"], name: "idx_holdings_on_account_id_external_id_unique", unique: true, where: "(external_id IS NOT NULL)" t.index ["account_id", "security_id", "date", "currency"], name: "idx_on_account_id_security_id_date_currency_5323e39f8b", unique: true t.index ["account_id"], name: "index_holdings_on_account_id" @@ -1129,7 +1131,14 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_10_180000) do t.string "currency" t.jsonb "locked_attributes", default: {} t.uuid "category_id" + t.decimal "realized_gain", precision: 19, scale: 4 + t.decimal "cost_basis_amount", precision: 19, scale: 4 + t.string "cost_basis_currency" + t.integer "holding_period_days" + t.string "realized_gain_confidence" + t.string "realized_gain_currency" t.index ["category_id"], name: "index_trades_on_category_id" + t.index ["realized_gain"], name: "index_trades_on_realized_gain_not_null", where: "(realized_gain IS NOT NULL)" t.index ["security_id"], name: "index_trades_on_security_id" end