Assets can be corrected before depreciation: books.correct_asset (cost, salvage, life, in-service date, with a reason), journaled as asset.corrected with the old figures; refused once depreciation is posted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-15 13:41:15 -06:00
1 parent ba7373bdb8
commit 1500d18cea
11 files changed
+289 -4

No files matched your search

+5
View File
@@ -5,6 +5,11 @@ use thiserror::Error;
/// Every way a store operation can fail.
#[derive(Debug, Error)]
pub enum StoreError {
/// An asset can't be corrected once depreciation for it is posted: the
/// posted entries would disagree with the corrected schedule.
#[error("asset {0} already has depreciation posted; correct it with entries instead")]
AssetDepreciationPosted(u64),
/// Filesystem error.
#[error("io error: {0}")]
Io(#[from] std::io::Error),
+68
View File
@@ -725,6 +725,48 @@ impl Store {
Ok(())
}
/// Correct a registered asset's cost, salvage, life, or in-service date
/// (see `Ledger::correct_asset`), with the reason. Refused once any
/// depreciation for it is posted. Journaled as `asset.corrected`, with
/// the figures it had.
pub fn correct_asset(
&mut self,
id: AssetId,
fix: ledger_core::AssetCorrection,
reason: &str,
) -> Result<(), StoreError> {
if reason.trim().is_empty() {
return Err(StoreError::ReasonRequired);
}
self.ledger.check_correct_asset(id, &fix)?;
if !self.asset_depreciation_total(id)?.is_zero() {
return Err(StoreError::AssetDepreciationPosted(id));
}
let a = self.ledger.get_asset(id).expect("checked").clone();
let money =
|m: &Money| json!({"amount": m.amount.to_string(), "currency": m.currency.code()});
let line = self.append_journal(
"asset.corrected",
json!({
"asset_id": id,
"cost": money(&fix.cost),
"salvage_value": money(&fix.salvage_value),
"useful_life_months": fix.useful_life_months,
"purchase_date": fix.purchase_date.to_string(),
"reason": reason,
"previous": {
"cost": money(&a.cost),
"salvage_value": money(&a.salvage_value),
"useful_life_months": a.useful_life_months,
"purchase_date": a.purchase_date.to_string(),
},
}),
)?;
self.ledger.correct_asset(id, fix)?;
apply_line_to_sqlite(&self.conn, &line)?;
Ok(())
}
/// Record an asset's federal tax basis (see
/// `Ledger::set_asset_tax_basis`). Journaled as `asset.tax_basis_set`.
pub fn set_asset_tax_basis(
@@ -3488,6 +3530,17 @@ pub(crate) fn replay_line_into_ledger(
"asset.tagged" => {
ledger.set_asset_tag(get_u64(p, "asset_id")?, get_str(p, "tag")?)?;
}
"asset.corrected" => {
ledger.correct_asset(
get_u64(p, "asset_id")?,
ledger_core::AssetCorrection {
cost: parse_money(p, "cost")?,
salvage_value: parse_money(p, "salvage_value")?,
useful_life_months: get_u64(p, "useful_life_months")? as u32,
purchase_date: parse_date(get_str(p, "purchase_date")?)?,
},
)?;
}
"asset.tax_basis_set" => {
let tax = tax_basis_from_journal(p)?;
ledger.set_asset_tax_basis(get_u64(p, "asset_id")?, tax)?;
@@ -3989,6 +4042,21 @@ fn apply_line_rows(conn: &Connection, line: &JournalLine) -> Result<(), StoreErr
params![id, tag],
)?;
}
"asset.corrected" => {
let amt = |k: &str| -> Result<String, StoreError> {
Ok(get_str(&p[k], "amount")?.to_string())
};
conn.execute(
"UPDATE assets SET cost_amount = ?, salvage_amount = ?, useful_life_months = ?, purchase_date = ? WHERE id = ?",
params![
amt("cost")?,
amt("salvage_value")?,
get_u64(p, "useful_life_months")? as i64,
get_str(p, "purchase_date")?,
get_u64(p, "asset_id")? as i64
],
)?;
}
"asset.tax_basis_set" => {
conn.execute(
"UPDATE assets SET tax_rule = ?, contributor_basis = ? WHERE id = ?",