Billpad 瀏覽器發票工具的五個計算與 PDF 陷阱
Five calculation and PDF pitfalls in a browser invoice tool
Billpad 開發者梳理瀏覽器發票工具在金額計算與 PDF 輸出上的五類陷阱。文章建議讓編輯器、列印畫面及 PDF 共用同一計算結果,並明確處理貨幣精度、零税率與空白税率,以及折扣分配。計算檢查應與 PDF 排版檢查分開;部分字元可能以點陣圖形式輸出,未必可選取文字。
I make Billpad, a browser-based tool for creating invoices, estimates, receipts and proforma invoices. It does not collect payments or send invoice emails. Working on a document tool brings up a smaller engineering problem that is easy to underestimate: the number in the editor must match the number in the downloaded document.
Here is a practical way to reason about that problem. The examples below are synthetic and describe calculation behavior, rather than tax rules.
1. Pick the rounding boundary before formatting the output
Suppose a document has three lines:
| Line | Quantity | Unit price | Tax rate |
|---|---|---|---|
| Design work | 1 | 1200.00 USD | 8% |
| Template set | 6 | 75.00 USD | 8% |
| Workshop | 4 | 95.00 USD | 0% |
The subtotal is 2030.00 USD. The taxable base in this example is 1650.00 USD, so the tax is 132.00 USD and the total is 2162.00 USD.
A tempting implementation is to format each visible number independently, then recalculate the PDF total from the original floating-point inputs. That creates two separate paths through rounding. A better boundary is to create a calculation result once and make the editor, print view and PDF renderer consume it.
Formatting should turn a result into text. It should not decide the financial result.
2. Minor units depend on the selected currency
“Store money as cents” is incomplete advice. A document generator can support currencies with zero, two or three fractional digits. The integer 1234 means different major-unit amounts depending on the currency metadata.
Keep the currency code and its precision with the calculation. Treat a currency change explicitly: relabeling an amount is not an exchange-rate conversion. Billpad offers currency selection, but it does not perform foreign-exchange conversion.
Integer minor units simplify downstream additions and comparisons, but converting a decimal input into those units still needs a deliberate rounding rule. Binary floating-point input, unusually precise unit prices and large values all deserve attention. For a larger billing system, an explicit decimal arithmetic library or a carefully bounded decimal parser may be preferable.
3. A blank tax override is different from zero
In the table above, the workshop's zero rate is intentional. It must not fall back to the document's default 8% rate.
This condition loses that distinction:
const appliedRate = line.rate || defaultRate;
If the data model uses null for “inherit the default,” the decision can be explicit:
const appliedRate = line.rate === null
? defaultRate
: line.rate;
The parser needs the same distinction. An empty input, an explicit zero and an invalid string are three different states. Converting all three into zero makes a broken or incomplete document look valid.
4. Discounts introduce an allocation problem
With multiple tax rates, a document-level discount cannot simply disappear from the taxable base. It needs an allocation policy.
For example, a 10% discount on this synthetic subtotal is 203.00 USD. Proportional allocation reduces the 8% group's base from 1650.00 to 1485.00 USD, and the zero-rate group's base from 380.00 to 342.00 USD. The example's tax becomes 118.80 USD and the total becomes 1945.80 USD.
A fixed discount can produce fractional minor-unit allocations. Decide where any remainder goes, keep the allocation deterministic, and check that allocated discounts add back to the original discount. The correct policy for a regulated invoicing system may also depend on its jurisdiction and accounting requirements.
5. Keep calculation checks separate from PDF appearance checks
Useful calculation checks include:
- A line's explicit zero rate overrides a nonzero default.
- Allocated discounts sum to the document discount.
- Adding a payment received changes the balance, not the original line prices.
- Zero- and three-decimal currencies use their selected precision.
- Empty and invalid inputs remain distinguishable.
PDF inspection answers different questions: does a long description wrap, do page breaks retain the totals, and can the font represent the entered text? A correct calculation does not prove that the PDF displays it correctly. Conversely, a polished screenshot does not prove the arithmetic.
Billpad uses a text-based PDF path for supported characters and can fall back to a raster representation for others. That is a useful limitation to state explicitly; “PDF export” does not imply every document has selectable text.
If you have built similar browser tools, I would be interested in the rounding or layout edge case that caused the most trouble. This article was generated by an AI assistant using Billpad's calculation code and documentation. The worked examples were checked against the calculation code.
來源:DEV Community · dev.to