Fixed assets, fully featured: tags, serials, acquisition links, label sheets, the fixed asset register, and an Assets section in the viewer; fix a flaky verify
- core: asset tags (generated PREFIX-0001 or the human's own; unique ignoring case; never reused by another asset) and serial numbers (rules.md B-24); a journaled link from an asset to the entry that acquired it (posted, unreversed, debiting the cost account); fixed_asset_register(as_of), reconciled to the GL (unposted depreciation, unregistered cost) - store: asset.tagged, asset.serial_set, asset.acquisition_linked journaled, replayed, projected (schema v9); differs_from now also compares control roles and tag history - config: [books] asset_tag_prefix, default the company's initials without legal-form words - import: Avery 5160/8160 label sheets with QR codes (geometry vendored from Avery, docs/spec/sources/avery-5160.md); the register as XLSX and PDF - mcp: register_asset tags (and links) automatically; books.label_asset, books.asset_labels; asset tools take a tag; books.show asset / assets page / register report; export fixed_asset_register (json, csv, xlsx, pdf) and in the CPA package - viewer: Assets page (register, reconciliation), asset page (identity, schedule posted vs not, entries, documents), Fixed asset register report, global search by tag/serial/description, journal text, needs-attention items - fix: verify compared snapshot amounts as strings; a zero total summed in hash-map order could print "0" or "0.00", so agreeing books failed verify at random. Now by value (regression test) - tests: spec-blind suites for tags, register, prefix, store, MCP; label and register PDF/XLSX tests; 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
4e96b5f54d
commit
cd085108c1
53 files changed
+7872
-104
No files matched your search
@@ -1,7 +1,8 @@
|
||||
//! What the books need: drafts left unposted, missing receipts, schedules
|
||||
//! and recurring entries due, open past periods, uncategorized tax lines,
|
||||
//! 1099 vendors without a W-9, overdue bills and invoices, money owed to
|
||||
//! owners, and legal rules (like the 1099 threshold) due for an update. Each item says what's wrong, where to look, and the prompt that
|
||||
//! owners, fixed asset depreciation not posted and assets without a tag,
|
||||
//! and legal rules (like the 1099 threshold) due for an update. Each item says what's wrong, where to look, and the prompt that
|
||||
//! asks the agent to fix it (the GUI never changes the books itself).
|
||||
|
||||
use crate::books::{Attention, Books, Severity};
|
||||
@@ -31,7 +32,11 @@ use rust_decimal::Decimal;
|
||||
/// invoices (Problem, one each, with the total); money owed to
|
||||
/// owners (Info, per account with a balance); the database differing
|
||||
/// from the journal or unreadable, and damaged or missing stored
|
||||
/// files (Problem).
|
||||
/// files (Problem); depreciation due through the last finished month
|
||||
/// and not posted (Books::depreciation_due amount > 0: Todo, one
|
||||
/// item); the asset register and the books' asset cost accounts
|
||||
/// disagreeing today (unregistered_cost != 0: Problem); an active
|
||||
/// asset in service with no tag (Todo, one per asset).
|
||||
/// 3. Nothing else: books with none of those yield no items.
|
||||
/// 4. Every item's prompt starts "In wanderbooks," or names
|
||||
/// wanderbooks, and its amounts are the books' exact amounts.
|
||||
@@ -306,6 +311,79 @@ pub fn find(b: &Books) -> Vec<Attention> {
|
||||
}
|
||||
}
|
||||
|
||||
let due = b.depreciation_due(today);
|
||||
if due.amount > Decimal::ZERO {
|
||||
let months = if due.periods.is_empty() {
|
||||
"every month not yet posted".to_string()
|
||||
} else {
|
||||
due.periods.join(", ")
|
||||
};
|
||||
out.push(Attention {
|
||||
severity: Severity::Todo,
|
||||
title: format!("Depreciation not posted through {}", fmt::date(due.through)),
|
||||
detail: format!(
|
||||
"{} scheduled and not yet in the books ({}).",
|
||||
fmt::dollars(due.amount),
|
||||
months
|
||||
),
|
||||
target: Some(Route::Assets),
|
||||
prompt: format!(
|
||||
"In wanderbooks, run books.run_depreciation for {months} (depreciation due through {} and not posted: {}), then show me the fixed asset register.",
|
||||
due.through,
|
||||
fmt::dollars(due.amount)
|
||||
),
|
||||
});
|
||||
}
|
||||
let register = b.asset_register(today);
|
||||
let unregistered = register.unregistered_cost();
|
||||
if !unregistered.is_zero() {
|
||||
let (title, why) = if unregistered > Decimal::ZERO {
|
||||
(
|
||||
format!(
|
||||
"{} of equipment not in the asset register",
|
||||
fmt::dollars(unregistered)
|
||||
),
|
||||
"The asset cost accounts hold more than the registered assets cost: something bought was never registered.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!(
|
||||
"{} of registered assets not in the books",
|
||||
fmt::dollars(-unregistered)
|
||||
),
|
||||
"Registered assets cost more than their cost accounts hold: a purchase entry may be missing.",
|
||||
)
|
||||
};
|
||||
out.push(Attention {
|
||||
severity: Severity::Problem,
|
||||
title,
|
||||
detail: why.into(),
|
||||
target: Some(Route::Assets),
|
||||
prompt: format!(
|
||||
"In wanderbooks, the fixed asset register and the asset cost accounts differ by {} today. Find what's behind it (books.list_assets and the cost accounts' registers) and tell me how to fix it.",
|
||||
fmt::dollars(unregistered)
|
||||
),
|
||||
});
|
||||
}
|
||||
for a in b.ledger.registered_assets().filter(|a| {
|
||||
a.tag.is_none() && a.status == ledger_core::AssetStatus::Active && a.purchase_date <= today
|
||||
}) {
|
||||
out.push(Attention {
|
||||
severity: Severity::Todo,
|
||||
title: format!("No tag: {}", a.description),
|
||||
detail: format!(
|
||||
"Asset {}, in service {}. A tag is the label on the thing itself.",
|
||||
a.id,
|
||||
fmt::date(a.purchase_date)
|
||||
),
|
||||
target: Some(Route::Asset(a.id)),
|
||||
prompt: format!(
|
||||
"In wanderbooks, label asset {} (\"{}\") with books.label_asset: give it the next generated tag unless I have my own, and its serial number if I can find it. Then print its label with books.asset_labels.",
|
||||
a.id, a.description
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
use ledger_store::DiskCheck;
|
||||
let verify_prompt = "In wanderbooks, run books.verify and tell me what it found.".to_string();
|
||||
match &b.integrity.disk {
|
||||
|
||||
Reference in new issue
Block a user