Fixed assets carry federal tax basis beside book value; tests sweep stale temp dirs
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
- core: TaxBasisRule (cost IRC 1012, carryover IRC 723, personal-use conversion Treas. Reg. 1.168(i)-4(b)), TaxBasis with the contributor's adjusted basis, Asset::tax_basis and ::built_in_gain (IRC 704(c)); the register gains tax rule, contributor basis, tax basis, built-in gain, total tax basis, missing count (rules.md B-25; sources vendored in docs/spec/sources/irs-partnership-basis.md) - store: asset.tax_basis_set journaled, replayed, projected (schema v10) - mcp: books.set_asset_tax_basis; register_asset takes tax_basis_rule and contributor_basis, checked before anything registers (found by the spec-blind suite); list_assets and the register export (json, csv) carry tax basis; instructions say to record it for every asset - import: the register's XLSX and PDF gain the federal tax basis table with citations - viewer: tax basis on the Assets page, the asset page (a Tax basis card), the report, the journal, and Needs attention for assets without one - just: check and the mutation recipes sweep this user's test book dirs older than an hour (killed runs had left 23 GB in RAM-backed /tmp) - docs: the parties, attachments, TIN vault, completeness engine, and updates designs (docs/parties-and-attachments.md) - tests: spec-blind tax basis suites in core, store, MCP; register PDF/XLSX and viewer tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
49c49aef33
commit
6e6f9d65b2
30 files changed
+3240
-93
No files matched your search
@@ -5,7 +5,7 @@
|
||||
//! is the current version.
|
||||
|
||||
/// Current schema version.
|
||||
pub const SCHEMA_VERSION: u32 = 9;
|
||||
pub const SCHEMA_VERSION: u32 = 10;
|
||||
|
||||
/// Migrations, in order. `MIGRATIONS[i]` migrates from version `i` to `i+1`.
|
||||
pub const MIGRATIONS: &[&str] = &[
|
||||
@@ -220,5 +220,12 @@ CREATE TABLE IF NOT EXISTS asset_tags_log(
|
||||
asset_id INTEGER NOT NULL,
|
||||
tag TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
r#"
|
||||
-- v10: each asset's federal tax basis: the rule (cost, carryover,
|
||||
-- personal_use_conversion) and the contributing member's adjusted basis
|
||||
-- (TEXT, exact decimal, never REAL).
|
||||
ALTER TABLE assets ADD COLUMN tax_rule TEXT;
|
||||
ALTER TABLE assets ADD COLUMN contributor_basis TEXT;
|
||||
"#,
|
||||
];
|
||||
@@ -725,6 +725,27 @@ impl Store {
|
||||
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(
|
||||
&mut self,
|
||||
id: AssetId,
|
||||
tax: ledger_core::TaxBasis,
|
||||
) -> Result<(), StoreError> {
|
||||
self.ledger.check_asset_tax_basis(id, tax)?;
|
||||
let line = self.append_journal(
|
||||
"asset.tax_basis_set",
|
||||
json!({
|
||||
"asset_id": id,
|
||||
"rule": tax.rule.key(),
|
||||
"contributor_basis": tax.contributor_basis.map(|b| b.to_string()),
|
||||
}),
|
||||
)?;
|
||||
self.ledger.set_asset_tax_basis(id, tax)?;
|
||||
apply_line_to_sqlite(&self.conn, &line)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change an asset's description (see `Ledger::set_asset_description`).
|
||||
/// Journaled as `asset.described`, with the description it had.
|
||||
pub fn set_asset_description(
|
||||
@@ -2897,6 +2918,21 @@ impl Store {
|
||||
let (id, serial) = row?;
|
||||
ledger.set_asset_serial(id as u64, Some(&serial))?;
|
||||
}
|
||||
let mut xstmt = self.conn.prepare(
|
||||
"SELECT id, tax_rule, contributor_basis FROM assets WHERE tax_rule IS NOT NULL ORDER BY id",
|
||||
)?;
|
||||
let taxes = xstmt.query_map([], |r| {
|
||||
Ok((
|
||||
r.get::<_, i64>(0)?,
|
||||
r.get::<_, String>(1)?,
|
||||
r.get::<_, Option<String>>(2)?,
|
||||
))
|
||||
})?;
|
||||
for row in taxes {
|
||||
let (id, rule, basis) = row?;
|
||||
let tax = tax_basis_from_journal(&json!({"rule": rule, "contributor_basis": basis}))?;
|
||||
ledger.set_asset_tax_basis(id as u64, tax)?;
|
||||
}
|
||||
let mut qstmt = self.conn.prepare(
|
||||
"SELECT id, acquisition_entry FROM assets WHERE acquisition_entry IS NOT NULL ORDER BY id",
|
||||
)?;
|
||||
@@ -3452,6 +3488,10 @@ pub(crate) fn replay_line_into_ledger(
|
||||
"asset.tagged" => {
|
||||
ledger.set_asset_tag(get_u64(p, "asset_id")?, get_str(p, "tag")?)?;
|
||||
}
|
||||
"asset.tax_basis_set" => {
|
||||
let tax = tax_basis_from_journal(p)?;
|
||||
ledger.set_asset_tax_basis(get_u64(p, "asset_id")?, tax)?;
|
||||
}
|
||||
"asset.described" => {
|
||||
ledger.set_asset_description(get_u64(p, "asset_id")?, get_str(p, "description")?)?;
|
||||
}
|
||||
@@ -3680,6 +3720,26 @@ fn apply_document_payment(
|
||||
/// The close target recorded in a `period.closed` line. Absent or
|
||||
/// `"retained_earnings"`: Retained Earnings with no sweeps (every journal
|
||||
/// written before targets existed).
|
||||
/// An `asset.tax_basis_set` payload's rule and contributor basis, strictly:
|
||||
/// an unknown rule or a basis that isn't a decimal string is corrupt.
|
||||
fn tax_basis_from_journal(p: &Value) -> Result<ledger_core::TaxBasis, StoreError> {
|
||||
let corrupt = |what: &str| StoreError::JournalCorrupt(format!("asset.tax_basis_set {what}"));
|
||||
let rule =
|
||||
ledger_core::TaxBasisRule::parse(get_str(p, "rule")?).ok_or_else(|| corrupt("rule"))?;
|
||||
let contributor_basis = match p.get("contributor_basis") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(v) => Some(
|
||||
v.as_str()
|
||||
.and_then(|s| s.parse::<Decimal>().ok())
|
||||
.ok_or_else(|| corrupt("contributor_basis"))?,
|
||||
),
|
||||
};
|
||||
Ok(ledger_core::TaxBasis {
|
||||
rule,
|
||||
contributor_basis,
|
||||
})
|
||||
}
|
||||
|
||||
fn close_to_from_journal(v: Option<&Value>) -> Result<CloseTo, StoreError> {
|
||||
let corrupt = |what: &str| StoreError::JournalCorrupt(format!("close_to: {what}"));
|
||||
let ids = |v: &Value| -> Result<Vec<AccountId>, StoreError> {
|
||||
@@ -3929,6 +3989,16 @@ fn apply_line_rows(conn: &Connection, line: &JournalLine) -> Result<(), StoreErr
|
||||
params![id, tag],
|
||||
)?;
|
||||
}
|
||||
"asset.tax_basis_set" => {
|
||||
conn.execute(
|
||||
"UPDATE assets SET tax_rule = ?, contributor_basis = ? WHERE id = ?",
|
||||
params![
|
||||
get_str(p, "rule")?,
|
||||
p.get("contributor_basis").and_then(|v| v.as_str()),
|
||||
get_u64(p, "asset_id")? as i64
|
||||
],
|
||||
)?;
|
||||
}
|
||||
"asset.described" => {
|
||||
conn.execute(
|
||||
"UPDATE assets SET description = ? WHERE id = ?",
|
||||
|
||||
Reference in new issue
Block a user