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
check / check (push) Canceled after 0s

- 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:
swaitsandClaude Opus 5.5 committed 2026-10-15 12:18:00 -06:00
1 parent 767fd469cd
commit ee7e205f5c
53 files changed
+7872 -104

No files matched your search

+24 -4
View File
@@ -156,20 +156,40 @@ CPA. Partial first/final months prorated by days; the final month trues up
so the lifetime total is exactly (cost − salvage).
- `books.register_asset { description, purchase_date, cost, salvage_value?,
useful_life_months, cost_account, accum_depr_account, expense_account }`
→ `{ asset_id }`. Accounts resolved by id or code.
- `books.asset_schedule { asset_id }`
useful_life_months, cost_account, accum_depr_account, expense_account,
tag?, serial? }` → `{ asset_id, tag }`. Accounts resolved by id or code.
Without `tag`, the next free `PREFIX-0001` (`[books] asset_tag_prefix`,
else the company name's initials without LLC/Inc; "FA" when none).
- `books.label_asset { asset, tag?, serial?, clear_serial? }` → `{ asset_id,
tag, serial }`: set or change the tag (unique ignoring case, never reused
by another asset even after a retag or disposal) or the manufacturer's
serial. `asset` is the id or current tag. Journaled as `asset.tagged`,
`asset.serial_set`.
- `books.asset_labels { assets?, skip? }` → `{ file_name, content_base64,
tags }`: an Avery 5160/8160 label sheet (PDF): company, tag, QR code of
the tag, description, serial. Default: every active tagged asset; `skip`
reuses a partly used sheet.
- `books.asset_schedule { asset_id | tag }`
→ `{ status, schedule: [{ period, amount, accumulated, book_value }] }`.
- `books.list_assets {}` → assets with status and current book values.
- `books.run_depreciation { period: "YYYY-MM", asset_id? }`
→ per-asset `{ entry_id, amount, already_posted }`, plus `skipped` for
assets outside their useful life. Idempotent per asset and period.
- `books.dispose_asset { asset_id, date, proceeds, proceeds_account,
- `books.dispose_asset { asset_id | tag, date, proceeds, proceeds_account,
gain_loss_account, confirmed?, receipt_attachment_ids? }` → without
confirmation: `{ status: "preview", ... }` and posts nothing; with
`confirmed: true`: auto-posts missing final-period depreciation, then
posts the balanced disposal entry. Subject to receipt policy (bill of
sale); monthly depreciation is exempt as an allocation.
- `books.export { report: "fixed_asset_register", format: json | csv,
as_of? }`: every asset registered by the date (tag, serial, cost,
depreciation by schedule, book value, disposal), totals, and the
general ledger's balances of the assets' cost and accumulated
depreciation accounts, with `unposted_depreciation` and
`unregistered_cost` (zero when register and books agree;
`ledger_core::fixed_asset_register`).
- `books.show { asset }` opens an asset in the viewer; `page: "assets"`
and `report: "fixed_asset_register"` open the register.
## Tools — schedules and recurring entries
+1 -1
View File
@@ -14,7 +14,7 @@ layers differ in what they cover and in whether we may copy them:
|---|---|---|---|
| **Pacioli (1494), Geijsbeek translation (1914)** | The mechanics of double entry: debits equal credits, the trial balance, one money, writing only forward, correcting by opposite entry, closing into capital. No modern standard restates these; they predate every regulator. | Public domain | Vendored verbatim: `sources/pacioli-1494.md` |
| **IRS: Internal Revenue Code, Treasury Regulations, publications, form instructions** | Records and supporting documents, retention, accounting periods and methods, information returns (1099), capitalization (de minimis, 12-month rule), partners' capital, payroll taxes, rounding on returns, return line items for tax categories (`sources/irs-tax-lines.md`) | Public domain (17 U.S.C. §105; IRS.gov's content policy) | Vendored verbatim: `sources/irs-*.md`, `sources/us-public-domain-status.md` |
| **File formats: Intuit (QuickBooks Online bank CSV, QuickBooks Desktop IIF), ECMA-376 Office Open XML, Microsoft Excel limits** | What `ledger-import` reads and writes: bank/card CSV layouts and signs, IIF journal entries, how XLSX stores numbers and Excel's 15-digit precision | © Intuit Inc.; © Ecma International; © Microsoft | Cited with short quotes, URLs, and hashes: `sources/intuit-bank-csv.md`, `sources/intuit-iif.md`, `sources/xlsx-numbers.md` |
| **File formats: Intuit (QuickBooks Online bank CSV, QuickBooks Desktop IIF), ECMA-376 Office Open XML, Microsoft Excel limits, Avery label sheets** | What `ledger-import` reads and writes: bank/card CSV layouts and signs, IIF journal entries, how XLSX stores numbers and Excel's 15-digit precision, asset-label sheet geometry (Avery 5160) | © Intuit Inc.; © Ecma International; © Microsoft; © Avery Products Corporation | Cited with short quotes, URLs, and hashes: `sources/intuit-bank-csv.md`, `sources/intuit-iif.md`, `sources/xlsx-numbers.md`, `sources/avery-5160.md` |
| **FASB Accounting Standards Codification (ASC)** | Authoritative US GAAP: cash flows (ASC 230), revenue and principal vs agent (ASC 606), foreign currency (ASC 830), credit losses and aging (ASC 326), property and depreciation (ASC 360) | © Financial Accounting Foundation | Cited by paragraph with short quotes only (in `rules.md`) |
| **FASB Concepts Statement No. 8, Chapter 4 (2021)** | Definitions of the five elements (it replaced Concepts Statement 6) | © Financial Accounting Foundation; non-authoritative | Cited with short quotes only |
| **Textbooks (OpenStax *Principles of Accounting, Vol. 1*)** | Conventions no standard states: normal balances, closing-entry mechanics, straight-line depreciation arithmetic | CC BY-NC-SA 4.0 (incompatible with this repo's license) | Cited with short quotes and attribution only |
+18
View File
@@ -446,3 +446,21 @@ postings stay where they are.
`check_assign_role`, `Ledger::check_renumber_account` (an assigned role
frees its code). **Checked:** `ledger-core` `tests/spec_roles.rs`;
`ledger-store` replay of `role.assigned`.
## B-24 Asset tags: one label per asset, never reused
**Status:** DECISION (Stephen, 2026-10-15)
Each fixed asset may carry a tag, the label stuck on the thing itself
(generated `PREFIX-0001` or the human's own: 1–32 ASCII letters, digits,
`- _ . /`), and the manufacturer's serial number. Tags are unique ignoring
case and are never reused by another asset, even after a retag or a
disposal, so an old label can never point at the wrong thing. Both are
journaled. The fixed asset register lists every asset as of a date and
reconciles to the general ledger's cost and accumulated depreciation
accounts.
**Enforced:** `Ledger::set_asset_tag` / `check_asset_tag_for`,
`assets::check_asset_tag`, `assets::check_serial`. **Checked:**
`ledger-core` `tests/spec_asset_tags.rs`; `ledger-store`
`tests/asset_tags.rs`; `ledger-mcp` `tests/spec_assets.rs`.
+49
View File
@@ -0,0 +1,49 @@
# Avery 5160 label sheet geometry
Governs `ledger_import::labels` (`LabelSheet::Avery5160`, printable asset-tag sheets). Avery's pages and templates are © Avery Products Corporation, not public domain, so per the vendoring rules (docs/spec/README.md) they are cited, with short quotes only; everything not inside a `>` blockquote is our description, not Avery's words. Avery's pages are dynamic (Next.js), so the hashes are of the files as fetched at retrieval and a later fetch will differ.
## Avery Template 5160 (product template page)
Source: <https://www.avery.com/templates/5160>
Retrieved: 2026-10-15
SHA-256 of the retrieved file (`templates_5160.html`, 90,873 bytes): `a274842ad34b3cb83e3129306df8b4f4ff51eb520c96cc263d96eff7b15c0a95`
> 1" x 2-5/8"
> 30 per sheet
The page links Avery's blank downloadable template for 5160 as `U-0087-01` (PDF, Word, Illustrator, InDesign).
## Blank labels, 1" x 2-5/8" (Presta 94200; lists 5160 as compatible)
Source: <https://www.avery.com/blank/labels/94200>
Retrieved: 2026-10-15
SHA-256 of the retrieved file (`blank_labels_94200.html`, 274,099 bytes): `c4e7c7eb3428654b1244ed04da5f52ef7668c14d6703f971ea7d0be7daa95b17`
Avery's specification table on this page (key and value, transcribed as given):
| Key | Value |
|---|---|
| Labels per Sheet | 30 labels per sheet |
| Size | 1" x 2-5/8" |
| Print to the edge | No |
| Sheet Size | 8-1/2" x 11" |
| Margin (Top and Bottom) | 0.5" |
| Margin (Left and Right) | 0.1875" |
| Corner Shape | Rounded |
The page's `compatibleSkus` field lists `5160` first.
## Avery 5160 blank PDF template (`U-0087-01.pdf`)
Source: the "PDF" download on <https://www.avery.com/templates/5160> (a signed S3 link: `https://s3.amazonaws.com/avery.dpp.projects.s3uspdownloadables/US_en/Downloadables/pdf/U-0087-01.pdf`, served as `Avery5160AddressLabels.pdf`)
Retrieved: 2026-10-15
SHA-256 of the retrieved file (`U-0087-01.pdf`, 675,971 bytes): `486b464c0380d41225f752dc834c5d7e6362364c1454d7ed92a77fc4164d0466`
One US Letter page (612 x 792 pt). We read the cell outlines from its content stream (PDF points, origin bottom-left): 30 rounded rectangles, left edges at x = 13.5, 211.5, 409.5 pt; bottom edges at y = 36, 108, ..., 684 pt (step 72); each 72 pt tall with 7.2 pt (0.1") corner radii. The outline is drawn 189.36 pt wide (2.63"); the stated label width is 2-5/8" (189 pt), and we use the stated size.
## What wanderbooks uses
Derived from the figures above (1 in = 72 pt):
- Page: 8.5" x 11" (US Letter), portrait.
- Label: 2.625" wide x 1" tall; 3 columns x 10 rows = 30 per sheet.
- Top margin 0.5" (the template's top row spans y = 756..684 pt, i.e. 36 pt below the top edge); bottom margin 0.5" (11 - 0.5 - 10 x 1 = 0.5).
- Side margin 0.1875" (13.5 pt).
- Column pitch 2.75" (211.5 - 13.5 = 198 pt), so the horizontal gap is 0.125" (8.5 - 2 x 0.1875 - 3 x 2.625 = 0.25, over two gaps).
- Row pitch 1" (72 pt): no vertical gap.