mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
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 <joshua.waldrep5+github@gmail.com>
This commit is contained in:
30
app/javascript/controllers/cost_basis_form_controller.js
Normal file
30
app/javascript/controllers/cost_basis_form_controller.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Handles bidirectional conversion between total cost basis and per-share cost
|
||||
// in the manual cost basis entry form.
|
||||
export default class extends Controller {
|
||||
static targets = ["total", "perShare", "perShareValue"]
|
||||
static values = { qty: Number }
|
||||
|
||||
// Called when user types in the total cost basis field
|
||||
// Updates the per-share display and input to show the calculated value
|
||||
updatePerShare() {
|
||||
const total = Number.parseFloat(this.totalTarget.value) || 0
|
||||
const qty = this.qtyValue || 1
|
||||
const perShare = qty > 0 ? (total / qty).toFixed(2) : "0.00"
|
||||
this.perShareValueTarget.textContent = perShare
|
||||
if (this.hasPerShareTarget) {
|
||||
this.perShareTarget.value = perShare
|
||||
}
|
||||
}
|
||||
|
||||
// Called when user types in the per-share field
|
||||
// Updates the total cost basis field with the calculated value
|
||||
updateTotal() {
|
||||
const perShare = Number.parseFloat(this.perShareTarget.value) || 0
|
||||
const qty = this.qtyValue || 1
|
||||
const total = (perShare * qty).toFixed(2)
|
||||
this.totalTarget.value = total
|
||||
this.perShareValueTarget.textContent = perShare.toFixed(2)
|
||||
}
|
||||
}
|
||||
33
app/javascript/controllers/drawer_cost_basis_controller.js
Normal file
33
app/javascript/controllers/drawer_cost_basis_controller.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Handles the inline cost basis editor in the holding drawer.
|
||||
// Shows/hides the form and handles bidirectional total <-> per-share conversion.
|
||||
export default class extends Controller {
|
||||
static targets = ["form", "total", "perShare", "perShareValue"]
|
||||
static values = { qty: Number }
|
||||
|
||||
toggle(event) {
|
||||
event.preventDefault()
|
||||
this.formTarget.classList.toggle("hidden")
|
||||
}
|
||||
|
||||
// Called when user types in total cost basis field
|
||||
updatePerShare() {
|
||||
const total = Number.parseFloat(this.totalTarget.value) || 0
|
||||
const qty = this.qtyValue || 1
|
||||
const perShare = qty > 0 ? (total / qty).toFixed(2) : "0.00"
|
||||
this.perShareValueTarget.textContent = perShare
|
||||
if (this.hasPerShareTarget) {
|
||||
this.perShareTarget.value = perShare
|
||||
}
|
||||
}
|
||||
|
||||
// Called when user types in per-share field
|
||||
updateTotal() {
|
||||
const perShare = Number.parseFloat(this.perShareTarget.value) || 0
|
||||
const qty = this.qtyValue || 1
|
||||
const total = (perShare * qty).toFixed(2)
|
||||
this.totalTarget.value = total
|
||||
this.perShareValueTarget.textContent = perShare.toFixed(2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user