// The fixed asset register: every asset, its cost, depreciation, and book // value, then the totals and the reconciliation to the general ledger. // // Everything arrives as data in sys.inputs.data (JSON), laid out by // ledger-import::asset_register, with amounts already split into exact // parts. The template only typesets: no arithmetic, and no text is ever // parsed as markup. // // Shape: { company, title, period, footer, columns: [10 headings], empty, // lines: [{ tag, description, serial, in_service, cost, salvage, // life, accumulated, book_value, status, disposed }], // totals: [cost, accumulated, book_value], // reconciliation: [{ label, amount, difference }] } // where every amount is { digits: "1,234.56", negative }. #let d = json(bytes(sys.inputs.data)) #let ink = luma(25) #let quiet = luma(110) #let hair = 0.5pt + luma(160) #let firm = 0.6pt + ink #set document(title: d.company + " — " + d.title, author: d.company) #set text(font: "Inter", size: 8pt, fill: ink, number-width: "tabular", hyphenate: false) #set page( paper: "us-letter", flipped: true, margin: (x: 0.6in, top: 0.7in, bottom: 0.7in), header: context { if counter(page).get().first() > 1 { set text(7.5pt, fill: quiet) [#d.company · #d.title #h(1fr) #d.period] } }, footer: context { set text(7.5pt, fill: quiet) grid( columns: (1fr, auto, 1fr), [], d.footer, align(right)[Page #counter(page).display() of #counter(page).final().first()], ) }, ) #align(center)[ #text(15pt, fill: luma(45), d.company) \ #v(4pt) #text(10.5pt, weight: "bold", d.title) \ #v(2pt) #d.period ] #v(12pt) // An amount: negatives in parentheses, the closing one's width reserved on // every row so decimal points line up; "$" on totals. #let amount(a, currency: false) = { let open = if a.negative { [(] } else { [] } let close = if a.negative { [)] } else { hide[)] } let sign = if currency [\$] else [] [#open#sign#a.digits#close] } // Columns: 4-8 are numbers (right-aligned). #let numeric = (4, 5, 6, 7, 8) #let ncols = d.columns.len() #let full(body, ..args) = table.cell(colspan: ncols, ..args, body) #table( // Number columns are fixed widths (headings wrap); text takes the rest. columns: (auto, 1.4fr, auto, auto, 6.4em, 5.6em, 6em, 7.4em, 6.4em, 1fr), stroke: none, inset: (x: 3.5pt, y: 3.4pt), align: (col, row) => (if col in numeric { right } else { left }) + (if row == 0 { bottom } else { horizon }), table.header( ..d.columns.map(c => table.cell( stroke: (top: firm, bottom: hair), text(6.5pt, weight: "bold", tracking: 0.04em, upper(c)), )), ), ..if d.lines.len() == 0 { (full(emph(d.empty)),) } else { d .lines .map(l => { let t(s) = if l.disposed { text(fill: quiet, s) } else { s } let a(x) = t(amount(x)) ( t(l.tag), t(l.description), t(l.serial), t(l.in_service), a(l.cost), a(l.salvage), t(l.life), a(l.accumulated), a(l.book_value), t(l.status), ) }) .flatten() }, // Totals of the assets held: rule above, double rule beneath. ..{ let s = (top: firm) let tot(x) = table.cell(stroke: s, text(weight: "bold", amount(x, currency: true))) let blank = table.cell(stroke: s, []) ( table.cell(stroke: s, colspan: 4, text(weight: "bold", "Total, assets held")), tot(d.totals.at(0)), blank, blank, tot(d.totals.at(1)), tot(d.totals.at(2)), blank, ) }, full( inset: (x: 0pt, y: 0pt), stack(spacing: 1.6pt, line(length: 100%, stroke: firm), line(length: 100%, stroke: firm)), ), ) #v(14pt) #block(breakable: false, { text(weight: "semibold", "Reconciliation to the books") v(2pt) table( columns: (auto, 9em), stroke: none, inset: (x: 4pt, y: 3.4pt), align: (left + horizon, right + horizon), ..d .reconciliation .map(x => { if x.difference { let s = (top: hair) ( table.cell(stroke: s, text(weight: "bold", x.label)), table.cell(stroke: s, text(weight: "bold", amount(x.amount, currency: true))), table.cell(colspan: 2, inset: 2pt, []), ) } else { (pad(left: 1.4em, x.label), amount(x.amount)) } }) .flatten(), ) })