@unisim/doc 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,6 +70,44 @@ it, and can take the readers and the model from here while keeping its own
70
70
  writer. The seam is that `write/pdf.ts` depends only on `pdfcore`'s small
71
71
  surface — `addPage` / `drawText` / `drawRect` / `drawImage` / `widthOfText`.
72
72
 
73
+
74
+ ## Business documents (0.9.0)
75
+
76
+ Quotes, estimates, pro formas, invoices and delivery notes, as fixed-layout A4
77
+ PDFs. Built for Universal Invoices; shaped so Universal Exports can move its
78
+ Estimate / Invoice / Packing List PDFs onto it.
79
+
80
+ ```ts
81
+ import { businessDocToPdf, computeTotals, formatMoney } from '@unisim/doc'
82
+
83
+ const totals = computeTotals(doc) // what the app shows on screen
84
+ const { blob, pages, dropped } = businessDocToPdf(doc) // the same numbers, printed
85
+ ```
86
+
87
+ - **Money is integer minor units** (cents/pence). A line total is
88
+ `Math.round(qty × unitPrice)`, rounded once; tax is rounded once **per rate**
89
+ on the summed base, not per line.
90
+ - **Language and number style are separate.** `language: 'en' | 'fr'` picks the
91
+ wording; `numberStyle: 'fr' | 'en'` picks `163 000 €` vs `€163,000`. The
92
+ golden fixture is English text with French numbers.
93
+ - **Discounts (0.10.0, replacing `listPrice`):** `discount: { percent } |
94
+ { amount }` on a line (per unit — the catalogue `unitPrice` prints struck
95
+ through above the discounted one, with "-10%" for a percentage) and on the
96
+ whole document (off the subtotal, before tax; the totals read Subtotal,
97
+ Discount, then net/tax/gross). A document discount comes off each VAT rate in
98
+ proportion, each base rounded once and summing to the net.
99
+ - **Tax modes:** `export-exempt` and `reverse-charge` print `taxNote` under the
100
+ totals; `standard` prints net, one row per rate, then the gross.
101
+ - **Two steps:** `layoutBusinessDoc` returns a display list in top-down points,
102
+ `businessDocToPdf` replays it onto `pdfcore`. The self-tests assert on the
103
+ display list, so they need no PDF parser.
104
+ - ⚠️ **The geometry is measured off a real Sellsy estimate** (DEV-20260727-00406)
105
+ and the self-tests pin its landmarks. Every text span of the re-render landed
106
+ within 0.5pt of the original. Moving a constant in `src/business/layout.ts`
107
+ moves the fixture — run `npm test`.
108
+ - Images are `PdfImage` (JPEG). Use `imageToJpeg` in the browser: it fills white
109
+ first, so a transparent PNG logo does not come out on black.
110
+
73
111
  ## Notes for anyone editing it
74
112
 
75
113
  - **Imports carry a `.js` extension.** Not `.ts` (which needs
@@ -0,0 +1,8 @@
1
+ export type { BusinessClient, BusinessDiscount, BusinessDoc, BusinessDocKind, BusinessDocLanguage, BusinessIssuer, BusinessLine, BusinessParty, CurrencyCode, NumberStyle, TaxMode, } from './model.js';
2
+ export { BUSINESS_LABELS } from './labels.js';
3
+ export type { BusinessLabels } from './labels.js';
4
+ export { computeTotals, discountOff, formatAmount, formatDate, formatMoney, formatQuantity, formatRate, lineTotal, unitNet, wantsDecimals, } from './totals.js';
5
+ export type { BusinessTotals, PricedDoc, PricedLine, TaxBand } from './totals.js';
6
+ export { businessDocToPdf, layoutBusinessDoc, wrapText } from './layout.js';
7
+ export type { BusinessPdfResult, LayoutOp, LayoutPage } from './layout.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,OAAO,GACR,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,OAAO,EACP,aAAa,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3E,YAAY,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { BUSINESS_LABELS } from './labels.js';
2
+ export { computeTotals, discountOff, formatAmount, formatDate, formatMoney, formatQuantity, formatRate, lineTotal, unitNet, wantsDecimals, } from './totals.js';
3
+ export { businessDocToPdf, layoutBusinessDoc, wrapText } from './layout.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,OAAO,EACP,aAAa,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,32 @@
1
+ import type { BusinessDocKind, BusinessDocLanguage } from './model.js';
2
+ export interface BusinessLabels {
3
+ kind: Record<BusinessDocKind, string>;
4
+ date: string;
5
+ clientRef: string;
6
+ contact: string;
7
+ phone: string;
8
+ email: string;
9
+ subject: string;
10
+ image: string;
11
+ code: string;
12
+ description: string;
13
+ qty: string;
14
+ unitPrice: string;
15
+ lineTotal: string;
16
+ notes: string;
17
+ subtotal: string;
18
+ /** "Discount 10%", or "Discount" for an amount. */
19
+ discount: (rate: string) => string;
20
+ totalNet: string;
21
+ tax: (rate: string) => string;
22
+ total: string;
23
+ totalDue: string;
24
+ signature: Record<BusinessDocKind, string>;
25
+ validUntil: string;
26
+ dueDate: string;
27
+ paymentTerms: string;
28
+ bank: string;
29
+ page: (n: number, of: number) => string;
30
+ }
31
+ export declare const BUSINESS_LABELS: Record<BusinessDocLanguage, BusinessLabels>;
32
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/business/labels.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAA;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAA;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;CACxC;AAoFD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAAsB,CAAA"}
@@ -0,0 +1,86 @@
1
+ // The printed wording, per document language. French keeps its space before
2
+ // the colon (typographic rule, and what the Sellsy fixture does); English does
3
+ // not. Kept as whole strings rather than "label + colon" so neither language
4
+ // has to know the other's rule.
5
+ const EN = {
6
+ kind: {
7
+ quote: 'Quote',
8
+ estimate: 'Estimate',
9
+ proforma: 'Pro forma invoice',
10
+ invoice: 'Invoice',
11
+ 'delivery-note': 'Delivery note',
12
+ },
13
+ date: 'Date:',
14
+ clientRef: 'Client no.:',
15
+ contact: 'Your contact:',
16
+ phone: 'Phone:',
17
+ email: 'Email:',
18
+ subject: 'Subject:',
19
+ image: 'Image',
20
+ code: 'Name / Code',
21
+ description: 'Description',
22
+ qty: 'Qty',
23
+ unitPrice: 'Unit price',
24
+ lineTotal: 'Line total',
25
+ notes: 'Notes:',
26
+ subtotal: 'Subtotal',
27
+ discount: (rate) => (rate ? `Discount ${rate}%` : 'Discount'),
28
+ totalNet: 'Total excl. tax',
29
+ tax: (rate) => `VAT ${rate}%`,
30
+ total: 'Total',
31
+ totalDue: 'Total due',
32
+ signature: {
33
+ quote: 'Acceptance signature:',
34
+ estimate: 'Acceptance signature:',
35
+ proforma: 'Acceptance signature:',
36
+ invoice: 'Signature:',
37
+ 'delivery-note': 'Received by (name, date, signature):',
38
+ },
39
+ validUntil: 'Valid until:',
40
+ dueDate: 'Due date:',
41
+ paymentTerms: 'Terms of payment:',
42
+ bank: 'Bank:',
43
+ page: (n, of) => `Page ${n} / ${of}`,
44
+ };
45
+ const FR = {
46
+ kind: {
47
+ quote: 'Devis',
48
+ estimate: 'Devis estimatif',
49
+ proforma: 'Facture pro forma',
50
+ invoice: 'Facture',
51
+ 'delivery-note': 'Bon de livraison',
52
+ },
53
+ date: 'Date :',
54
+ clientRef: 'N° client :',
55
+ contact: 'Votre contact :',
56
+ phone: 'Tél. :',
57
+ email: 'E-mail :',
58
+ subject: 'Objet :',
59
+ image: 'Image',
60
+ code: 'Nom / Code',
61
+ description: 'Description',
62
+ qty: 'Qté',
63
+ unitPrice: 'Prix unitaire',
64
+ lineTotal: 'Total ligne',
65
+ notes: 'Notes :',
66
+ subtotal: 'Sous-total',
67
+ discount: (rate) => (rate ? `Remise ${rate} %` : 'Remise'),
68
+ totalNet: 'Total HT',
69
+ tax: (rate) => `TVA ${rate} %`,
70
+ total: 'Total',
71
+ totalDue: 'Net à payer',
72
+ signature: {
73
+ quote: 'Bon pour accord, signature :',
74
+ estimate: 'Bon pour accord, signature :',
75
+ proforma: 'Bon pour accord, signature :',
76
+ invoice: 'Signature :',
77
+ 'delivery-note': 'Reçu par (nom, date, signature) :',
78
+ },
79
+ validUntil: 'Valable jusqu’au :',
80
+ dueDate: 'Échéance :',
81
+ paymentTerms: 'Conditions de paiement :',
82
+ bank: 'Banque :',
83
+ page: (n, of) => `Page ${n} / ${of}`,
84
+ };
85
+ export const BUSINESS_LABELS = { en: EN, fr: FR };
86
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../../src/business/labels.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,6EAA6E;AAC7E,gCAAgC;AAkChC,MAAM,EAAE,GAAmB;IACzB,IAAI,EAAE;QACJ,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,SAAS;QAClB,eAAe,EAAE,eAAe;KACjC;IACD,IAAI,EAAE,OAAO;IACb,SAAS,EAAE,aAAa;IACxB,OAAO,EAAE,eAAe;IACxB,KAAK,EAAE,QAAQ;IACf,KAAK,EAAE,QAAQ;IACf,OAAO,EAAE,UAAU;IACnB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,aAAa;IAC1B,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE,YAAY;IACvB,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;IAC7D,QAAQ,EAAE,iBAAiB;IAC3B,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,GAAG;IAC7B,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,WAAW;IACrB,SAAS,EAAE;QACT,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,uBAAuB;QACjC,QAAQ,EAAE,uBAAuB;QACjC,OAAO,EAAE,YAAY;QACrB,eAAe,EAAE,sCAAsC;KACxD;IACD,UAAU,EAAE,cAAc;IAC1B,OAAO,EAAE,WAAW;IACpB,YAAY,EAAE,mBAAmB;IACjC,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE;CACrC,CAAA;AAED,MAAM,EAAE,GAAmB;IACzB,IAAI,EAAE;QACJ,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,iBAAiB;QAC3B,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,SAAS;QAClB,eAAe,EAAE,kBAAkB;KACpC;IACD,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,aAAa;IACxB,OAAO,EAAE,iBAAiB;IAC1B,KAAK,EAAE,QAAQ;IACf,KAAK,EAAE,UAAU;IACjB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,aAAa;IAC1B,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,eAAe;IAC1B,SAAS,EAAE,aAAa;IACxB,KAAK,EAAE,SAAS;IAChB,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,QAAQ,EAAE,UAAU;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,IAAI;IAC9B,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,aAAa;IACvB,SAAS,EAAE;QACT,KAAK,EAAE,8BAA8B;QACrC,QAAQ,EAAE,8BAA8B;QACxC,QAAQ,EAAE,8BAA8B;QACxC,OAAO,EAAE,aAAa;QACtB,eAAe,EAAE,mCAAmC;KACrD;IACD,UAAU,EAAE,oBAAoB;IAChC,OAAO,EAAE,YAAY;IACrB,YAAY,EAAE,0BAA0B;IACxC,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE;CACrC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAgD,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAA"}
@@ -0,0 +1,45 @@
1
+ import { type FontId, type PdfImage, type Rgb } from '../pdfcore.js';
2
+ import type { BusinessDoc } from './model.js';
3
+ export type LayoutOp =
4
+ /** `top` is the top of the line box; the baseline is derived from it. */
5
+ {
6
+ op: 'text';
7
+ x: number;
8
+ top: number;
9
+ size: number;
10
+ font: FontId;
11
+ text: string;
12
+ color: Rgb;
13
+ } | {
14
+ op: 'rect';
15
+ x: number;
16
+ top: number;
17
+ width: number;
18
+ height: number;
19
+ fill?: Rgb;
20
+ stroke?: Rgb;
21
+ strokeWidth?: number;
22
+ } | {
23
+ op: 'image';
24
+ x: number;
25
+ top: number;
26
+ width: number;
27
+ height: number;
28
+ image: PdfImage;
29
+ };
30
+ export interface LayoutPage {
31
+ ops: LayoutOp[];
32
+ }
33
+ /** Greedy word wrap, honouring `\n`. A word wider than the box is broken by character. */
34
+ export declare function wrapText(text: string, font: FontId, size: number, width: number): string[];
35
+ export declare function layoutBusinessDoc(doc: BusinessDoc): LayoutPage[];
36
+ export interface BusinessPdfResult {
37
+ blob: Blob;
38
+ pages: number;
39
+ /** Characters base-14 Helvetica could not spell, for an honest warning. */
40
+ dropped: string[];
41
+ }
42
+ export declare function businessDocToPdf(doc: BusinessDoc, meta?: {
43
+ producer?: string;
44
+ }): BusinessPdfResult;
45
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/business/layout.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAiC,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,KAAK,GAAG,EAAE,MAAM,eAAe,CAAA;AAEnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAY7C,MAAM,MAAM,QAAQ;AAClB,yEAAyE;AACvE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,GAC5F;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACrH;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3F,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,QAAQ,EAAE,CAAA;CAChB;AA8DD,0FAA0F;AAC1F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4B1F;AAmED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CA6MhE;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,iBAAiB,CA6BtG"}
@@ -0,0 +1,403 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Business document → pages of drawing operations → PDF.
3
+ //
4
+ // TWO STEPS ON PURPOSE
5
+ // --------------------
6
+ // `layoutBusinessDoc` returns a display list (text, rules, images, in
7
+ // top-down points) and `businessDocToPdf` replays it onto `pdfcore`. The split
8
+ // is what makes the layout testable in plain Node — the self-tests assert
9
+ // where a column edge or a total landed without parsing a PDF back — and it is
10
+ // how "Page 1 / 3" gets printed: the page count is only known once the last
11
+ // row has been placed, and by then page one is already laid out.
12
+ //
13
+ // THE GEOMETRY IS THE SELLSY ESTIMATE'S
14
+ // -------------------------------------
15
+ // The constants below were measured off the golden fixture, DEV-20260727-00406
16
+ // (HRV → Karachi), with pymupdf: A4, Helvetica throughout, 9pt for the address
17
+ // blocks, 10pt for the table and totals, 8pt for the footer. Change them and
18
+ // the fixture stops matching — `selftest.mjs` checks the landmarks.
19
+ // ---------------------------------------------------------------------------
20
+ import { PdfDocument, rgb, widthOfText } from '../pdfcore.js';
21
+ import { BUSINESS_LABELS } from './labels.js';
22
+ import { computeTotals, formatAmount, formatDate, formatMoney, formatQuantity, formatRate, unitNet, wantsDecimals, } from './totals.js';
23
+ const PAGE_W = 595.28;
24
+ const PAGE_H = 841.89;
25
+ const LEFT = 25.5;
26
+ const RIGHT = 564.1;
27
+ const TABLE_RIGHT = 561.6;
28
+ const COL_RIGHT = 314.6;
29
+ const PAD = 2.8;
30
+ const RULE = 0.57;
31
+ const HAIRLINE = 0.28;
32
+ const SMALL = 9;
33
+ const SMALL_STEP = 11.25;
34
+ const BODY = 10;
35
+ const BODY_STEP = 12.5;
36
+ const FOOT = 8;
37
+ const FOOT_STEP = 12.75;
38
+ const BLOCK_STEP = 18.2;
39
+ const MIN_ROW = 45.4;
40
+ /**
41
+ * Line-box top → baseline, as a fraction of the font size. `top` is the y0 of
42
+ * the span box pymupdf reports, so the fixture's measurements and our own
43
+ * placement share one reference. Calibrated by round-trip (render → measure).
44
+ */
45
+ const ASCENT = 1.066;
46
+ const INK = rgb(0, 0, 0);
47
+ /** Column edges from the fixture: 25.5 | 70.9 | 145.6 | 354.7 | 411.4 | 485.1 | 561.6. */
48
+ function columnsFor(doc) {
49
+ const hasImages = doc.lines.some((l) => l.image);
50
+ const priced = doc.kind !== 'delivery-note';
51
+ const edges = [];
52
+ if (hasImages)
53
+ edges.push(['image', 70.9]);
54
+ edges.push(['code', hasImages ? 145.6 : 100.2]);
55
+ if (priced) {
56
+ edges.push(['description', 354.7], ['qty', 411.4], ['unitPrice', 485.1], ['lineTotal', TABLE_RIGHT]);
57
+ }
58
+ else {
59
+ edges.push(['description', 485.1], ['qty', TABLE_RIGHT]);
60
+ }
61
+ let left = LEFT;
62
+ return edges.map(([key, right]) => {
63
+ const col = {
64
+ key,
65
+ left,
66
+ right,
67
+ align: key === 'qty' || key === 'unitPrice' || key === 'lineTotal' ? 'right' : 'left',
68
+ };
69
+ left = right;
70
+ return col;
71
+ });
72
+ }
73
+ /** Greedy word wrap, honouring `\n`. A word wider than the box is broken by character. */
74
+ export function wrapText(text, font, size, width) {
75
+ const out = [];
76
+ for (const para of text.split(/\r?\n/)) {
77
+ const words = para.split(/ +/).filter(Boolean);
78
+ if (!words.length) {
79
+ out.push('');
80
+ continue;
81
+ }
82
+ let line = '';
83
+ for (let word of words) {
84
+ const candidate = line ? `${line} ${word}` : word;
85
+ if (widthOfText(font, candidate, size) <= width) {
86
+ line = candidate;
87
+ continue;
88
+ }
89
+ if (line)
90
+ out.push(line);
91
+ line = '';
92
+ while (widthOfText(font, word, size) > width && word.length > 1) {
93
+ let cut = word.length - 1;
94
+ while (cut > 1 && widthOfText(font, word.slice(0, cut), size) > width)
95
+ cut--;
96
+ out.push(word.slice(0, cut));
97
+ word = word.slice(cut);
98
+ }
99
+ line = word;
100
+ }
101
+ out.push(line);
102
+ }
103
+ return out;
104
+ }
105
+ class Pages {
106
+ doc;
107
+ labels;
108
+ pages = [];
109
+ current;
110
+ /** Where the content area ends — above the footer rule. */
111
+ bottom;
112
+ constructor(doc, labels) {
113
+ this.doc = doc;
114
+ this.labels = labels;
115
+ const footerLines = doc.issuer.footer?.length ?? 0;
116
+ this.bottom = this.footerRule(footerLines) - 8;
117
+ this.newPage();
118
+ }
119
+ footerRule(lines) {
120
+ return 785.2 - Math.max(0, lines - 4) * FOOT_STEP;
121
+ }
122
+ newPage() {
123
+ this.current = { ops: [] };
124
+ this.pages.push(this.current);
125
+ const { doc, labels } = this;
126
+ // The running header — "HAPTIQUE ET REALITE VIRTUELLE - Estimate DEV-…".
127
+ this.text(LEFT, 27.5, 7, 'helv', `${doc.issuer.name} - ${labels.kind[doc.kind]} ${doc.number}`);
128
+ this.hline(LEFT, RIGHT, 39.7, HAIRLINE);
129
+ // The legal footer.
130
+ const footer = doc.issuer.footer ?? [];
131
+ if (footer.length) {
132
+ const rule = this.footerRule(footer.length);
133
+ this.hline(LEFT, RIGHT, rule, HAIRLINE);
134
+ footer.forEach((line, i) => {
135
+ const font = i === 0 ? 'helvB' : 'helv';
136
+ const w = widthOfText(font, line, FOOT);
137
+ this.text((LEFT + RIGHT - w) / 2, rule + 2.2 + i * FOOT_STEP, FOOT, font, line);
138
+ });
139
+ }
140
+ }
141
+ text(x, top, size, font, text) {
142
+ if (text)
143
+ this.current.ops.push({ op: 'text', x, top, size, font, text, color: INK });
144
+ }
145
+ textRight(right, top, size, font, text) {
146
+ this.text(right - widthOfText(font, text, size), top, size, font, text);
147
+ }
148
+ hline(x1, x2, y, width = RULE) {
149
+ this.current.ops.push({ op: 'rect', x: x1, top: y - width / 2, width: x2 - x1, height: width, fill: INK });
150
+ }
151
+ vline(x, y1, y2, width = RULE) {
152
+ this.current.ops.push({ op: 'rect', x: x - width / 2, top: y1, width, height: y2 - y1, fill: INK });
153
+ }
154
+ box(x, top, width, height) {
155
+ this.current.ops.push({ op: 'rect', x, top, width, height, stroke: INK, strokeWidth: RULE });
156
+ }
157
+ image(image, x, top, maxW, maxH) {
158
+ const scale = Math.min(maxW / image.width, maxH / image.height);
159
+ this.current.ops.push({ op: 'image', x, top, width: image.width * scale, height: image.height * scale, image });
160
+ }
161
+ }
162
+ export function layoutBusinessDoc(doc) {
163
+ const labels = BUSINESS_LABELS[doc.language];
164
+ const p = new Pages(doc, labels);
165
+ const totals = computeTotals(doc);
166
+ const decimals = wantsDecimals(doc);
167
+ const money = (v) => formatMoney(v, doc.currency, doc.numberStyle, decimals);
168
+ const amount = (v) => formatAmount(v, doc.numberStyle, decimals);
169
+ const colon = (label, value) => `${label} ${value}`;
170
+ // ── Head: logo + issuer (left), document + client (right) ────────────────
171
+ const { issuer, client } = doc;
172
+ if (issuer.logo)
173
+ p.image(issuer.logo, 26, 60, 143, 80);
174
+ let rightTop = 58.8;
175
+ p.text(COL_RIGHT, rightTop, SMALL, 'helvB', `${labels.kind[doc.kind]} ${doc.number}`);
176
+ p.text(COL_RIGHT, (rightTop += SMALL_STEP), SMALL, 'helv', colon(labels.date, formatDate(doc.date)));
177
+ if (client.reference)
178
+ p.text(COL_RIGHT, (rightTop += SMALL_STEP), SMALL, 'helv', colon(labels.clientRef, client.reference));
179
+ let leftTop = issuer.logo ? 140.9 : 58.8;
180
+ const leftLine = (text, font = 'helv') => {
181
+ p.text(LEFT, leftTop, SMALL, font, text);
182
+ leftTop += SMALL_STEP;
183
+ };
184
+ if (!issuer.logo)
185
+ leftLine(issuer.name, 'helvB');
186
+ issuer.address.forEach((l) => leftLine(l));
187
+ if (issuer.contact?.name)
188
+ leftLine(colon(labels.contact, issuer.contact.name), 'helvB');
189
+ if (issuer.contact?.phone)
190
+ leftLine(colon(labels.phone, issuer.contact.phone));
191
+ if (issuer.contact?.email)
192
+ leftLine(colon(labels.email, issuer.contact.email));
193
+ let clientTop = Math.max(138.2, rightTop + 2 * SMALL_STEP);
194
+ p.text(COL_RIGHT, clientTop, SMALL, 'helvB', client.name);
195
+ for (const l of client.address)
196
+ p.text(COL_RIGHT, (clientTop += SMALL_STEP), SMALL, 'helv', l);
197
+ clientTop += SMALL_STEP;
198
+ let y = Math.max(leftTop, clientTop) + 11.3;
199
+ if (doc.subject) {
200
+ p.text(28.4, y, SMALL, 'helv', colon(labels.subject, doc.subject));
201
+ y += 34.2;
202
+ }
203
+ else {
204
+ y += 12;
205
+ }
206
+ // ── The line table ────────────────────────────────────────────────────────
207
+ const columns = columnsFor(doc);
208
+ const hasImages = columns[0].key === 'image';
209
+ const header = (top) => {
210
+ const names = {
211
+ image: labels.image,
212
+ code: labels.code,
213
+ description: labels.description,
214
+ qty: labels.qty,
215
+ unitPrice: labels.unitPrice,
216
+ lineTotal: labels.lineTotal,
217
+ };
218
+ // Headers are left-aligned even over right-aligned numbers — the fixture does it.
219
+ for (const col of columns)
220
+ p.text(col.left + PAD, top, SMALL, 'helvB', names[col.key]);
221
+ return top + 14.8;
222
+ };
223
+ let rowTop = header(y);
224
+ const closeTable = (bottom) => p.hline(LEFT, TABLE_RIGHT, bottom);
225
+ doc.lines.forEach((line, i) => {
226
+ const cells = new Map();
227
+ const inner = (key) => {
228
+ const col = columns.find((c) => c.key === key);
229
+ return col.right - col.left - 2 * PAD;
230
+ };
231
+ if (columns.some((c) => c.key === 'code'))
232
+ cells.set('code', wrapText(line.code ?? '', 'helv', BODY, inner('code')));
233
+ cells.set('description', wrapText(line.description ?? '', 'helv', BODY, inner('description')));
234
+ // A discounted line: the catalogue price struck through above what is charged.
235
+ const net = unitNet(line);
236
+ const struck = net !== line.unitPrice;
237
+ const priceLines = (struck ? 2 : 1) + (line.unit ? 0.87 : 0);
238
+ const textLines = Math.max(cells.get('code')?.length ?? 0, cells.get('description').length, doc.kind === 'delivery-note' ? 1 : priceLines);
239
+ // The fixture's 45.4pt rows are the product picture's height plus padding,
240
+ // so the floor applies only to a table that has a picture column.
241
+ const height = Math.max(hasImages ? MIN_ROW : 0, 1.9 + textLines * BODY_STEP + 4);
242
+ if (rowTop + height > p.bottom && i > 0) {
243
+ closeTable(rowTop);
244
+ p.newPage();
245
+ rowTop = header(50);
246
+ }
247
+ p.hline(LEFT, TABLE_RIGHT, rowTop);
248
+ for (const col of columns.slice(1))
249
+ p.vline(col.left, rowTop, rowTop + height);
250
+ const top = rowTop + 1.9;
251
+ for (const col of columns) {
252
+ const x = col.left + PAD;
253
+ const right = col.right - PAD;
254
+ switch (col.key) {
255
+ case 'image':
256
+ if (line.image)
257
+ p.image(line.image, 26, rowTop + 2.8, 39, 40);
258
+ break;
259
+ case 'code':
260
+ case 'description':
261
+ cells.get(col.key).forEach((t, n) => p.text(x, top + n * BODY_STEP, BODY, 'helv', t));
262
+ break;
263
+ case 'qty':
264
+ p.textRight(right, top, BODY, 'helv', formatQuantity(line.quantity, doc.numberStyle));
265
+ break;
266
+ case 'unitPrice': {
267
+ let t = top;
268
+ if (struck) {
269
+ const list = amount(line.unitPrice);
270
+ const w = widthOfText('helv', list, BODY);
271
+ p.textRight(right, t, BODY, 'helv', list);
272
+ p.current.ops.push({ op: 'rect', x: right - w, top: t + 7.9, width: w, height: 0.5, fill: INK });
273
+ // "-10 %" beside it, for a percentage.
274
+ if (line.discount && 'percent' in line.discount) {
275
+ p.textRight(right - w - 4, t + 1, 8, 'helvI', `-${formatRate(line.discount.percent, doc.numberStyle)}${doc.language === 'fr' ? '\u00a0%' : '%'}`);
276
+ }
277
+ t += BODY_STEP;
278
+ }
279
+ p.textRight(right, t, BODY, 'helv', amount(net));
280
+ if (line.unit)
281
+ p.textRight(right, t + 12.7, 8, 'helvI', line.unit);
282
+ break;
283
+ }
284
+ case 'lineTotal':
285
+ p.textRight(right, top, BODY, 'helv', amount(totals.lines[i]));
286
+ break;
287
+ }
288
+ }
289
+ rowTop += height;
290
+ });
291
+ closeTable(rowTop);
292
+ // ── Notes (left) and totals (right) ──────────────────────────────────────
293
+ const totalRows = [];
294
+ if (doc.kind !== 'delivery-note') {
295
+ if (totals.discount) {
296
+ const d = doc.discount;
297
+ totalRows.push([labels.subtotal, money(totals.subtotal), 'helv']);
298
+ totalRows.push([labels.discount(d && 'percent' in d ? formatRate(d.percent, doc.numberStyle) : ''), money(-totals.discount), 'helv']);
299
+ }
300
+ if (doc.taxMode === 'standard' && totals.taxes.length) {
301
+ totalRows.push([labels.totalNet, money(totals.net), 'helv']);
302
+ for (const band of totals.taxes)
303
+ totalRows.push([labels.tax(formatRate(band.rate, doc.numberStyle)), money(band.tax), 'helv']);
304
+ }
305
+ totalRows.push([labels.total, money(totals.gross), 'helvB']);
306
+ totalRows.push([labels.totalDue, money(totals.gross), 'helvB']);
307
+ }
308
+ const noteLines = doc.notes ? [labels.notes, ...wrapText(doc.notes, 'helv', BODY, 294.8 - 28.4 - PAD)] : [];
309
+ const taxNoteLines = doc.taxMode !== 'standard' && doc.taxNote && totalRows.length
310
+ ? wrapText(doc.taxNote, 'helvI', 8, RIGHT - 330)
311
+ : [];
312
+ const notesHeight = noteLines.length ? Math.max(68.1, noteLines.length * BODY_STEP + 5.6) : 0;
313
+ const totalsHeight = totalRows.length * BLOCK_STEP + (taxNoteLines.length ? 4 + taxNoteLines.length * 10 : 0);
314
+ let blockTop = rowTop + 18.2;
315
+ if (blockTop + Math.max(notesHeight, totalsHeight) > p.bottom) {
316
+ p.newPage();
317
+ blockTop = 58.8;
318
+ }
319
+ if (noteLines.length) {
320
+ p.box(LEFT, blockTop, 294.8 - LEFT, notesHeight);
321
+ noteLines.forEach((t, n) => p.text(28.4, blockTop + 1.8 + n * BODY_STEP, BODY, 'helv', t));
322
+ }
323
+ totalRows.forEach(([label, value, font], n) => {
324
+ const t = blockTop + 2 + n * BLOCK_STEP;
325
+ p.textRight(464.9, t, BODY, font, label);
326
+ p.textRight(RIGHT, t, BODY, font, value);
327
+ });
328
+ taxNoteLines.forEach((t, n) => p.textRight(RIGHT, blockTop + 2 + totalRows.length * BLOCK_STEP + 4 + n * 10, 8, 'helvI', t));
329
+ y = blockTop + Math.max(notesHeight, totalsHeight);
330
+ // ── Signature ─────────────────────────────────────────────────────────────
331
+ const signature = doc.signature ?? (doc.kind === 'quote' || doc.kind === 'estimate' || doc.kind === 'delivery-note');
332
+ if (signature) {
333
+ if (y + 16.2 + 70.7 > p.bottom) {
334
+ p.newPage();
335
+ y = 42.6;
336
+ }
337
+ p.text(297.6, y + 16.2, BODY, 'helvB', labels.signature[doc.kind]);
338
+ y += 16.2 + 70.7;
339
+ }
340
+ else {
341
+ y += 20;
342
+ }
343
+ // ── Terms: validity / due date, payment, bank ────────────────────────────
344
+ const terms = [];
345
+ const dated = doc.kind === 'invoice' ? doc.dueDate : doc.validUntil;
346
+ if (dated && doc.kind !== 'delivery-note')
347
+ terms.push([doc.kind === 'invoice' ? labels.dueDate : labels.validUntil, [formatDate(dated)]]);
348
+ if (doc.paymentTerms && doc.kind !== 'delivery-note')
349
+ terms.push([labels.paymentTerms, wrapText(doc.paymentTerms, 'helv', BODY, RIGHT - 187.1)]);
350
+ if (issuer.bank?.length && doc.kind !== 'delivery-note')
351
+ terms.push([labels.bank, issuer.bank]);
352
+ const termsHeight = terms.reduce((h, [, v]) => h + BLOCK_STEP + (v.length - 1) * BODY_STEP, 0);
353
+ if (terms.length && y + termsHeight > p.bottom) {
354
+ p.newPage();
355
+ y = 58.8;
356
+ }
357
+ for (const [label, values] of terms) {
358
+ p.text(28.4, y, BODY, 'helv', label);
359
+ values.forEach((v, n) => p.text(187.1, y + n * BODY_STEP, BODY, 'helv', v));
360
+ y += BLOCK_STEP + (values.length - 1) * BODY_STEP;
361
+ }
362
+ // ── Page numbers, now that the count is known ────────────────────────────
363
+ if (p.pages.length > 1) {
364
+ p.pages.forEach((page, i) => {
365
+ const text = labels.page(i + 1, p.pages.length);
366
+ page.ops.push({ op: 'text', x: RIGHT - widthOfText('helv', text, 7), top: 27.5, size: 7, font: 'helv', text, color: INK });
367
+ });
368
+ }
369
+ return p.pages;
370
+ }
371
+ export function businessDocToPdf(doc, meta = {}) {
372
+ const layout = layoutBusinessDoc(doc);
373
+ const pdf = new PdfDocument();
374
+ for (const { ops } of layout) {
375
+ const page = pdf.addPage(PAGE_W, PAGE_H);
376
+ for (const o of ops) {
377
+ if (o.op === 'text') {
378
+ page.drawText(o.text, { x: o.x, y: PAGE_H - o.top - ASCENT * o.size, size: o.size, font: o.font, color: o.color });
379
+ }
380
+ else if (o.op === 'rect') {
381
+ page.drawRect({
382
+ x: o.x,
383
+ y: PAGE_H - o.top - o.height,
384
+ width: o.width,
385
+ height: o.height,
386
+ color: o.fill,
387
+ borderColor: o.stroke,
388
+ borderWidth: o.strokeWidth,
389
+ });
390
+ }
391
+ else {
392
+ page.drawImage(o.image, { x: o.x, y: PAGE_H - o.top - o.height, width: o.width, height: o.height });
393
+ }
394
+ }
395
+ }
396
+ const blob = pdf.save({
397
+ title: `${BUSINESS_LABELS[doc.language].kind[doc.kind]} ${doc.number}`,
398
+ author: doc.issuer.name,
399
+ producer: meta.producer ?? 'Universal Invoices',
400
+ });
401
+ return { blob, pages: layout.length, dropped: [...pdf.report.dropped] };
402
+ }
403
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/business/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import type { PdfImage } from '../pdfcore.js';
2
+ export type BusinessDocKind = 'quote' | 'estimate' | 'proforma' | 'invoice' | 'delivery-note';
3
+ /** The language of the printed document — separate from the app's own UI language. */
4
+ export type BusinessDocLanguage = 'en' | 'fr';
5
+ /**
6
+ * How numbers are written. Separate from the language on purpose: the golden
7
+ * fixture (a Sellsy estimate) is English text with French numbers, and a
8
+ * French company writing to an English client may want exactly that.
9
+ * fr → 163 000,00 € en → €163,000.00
10
+ */
11
+ export type NumberStyle = 'fr' | 'en';
12
+ export type CurrencyCode = 'EUR' | 'GBP' | 'USD';
13
+ /**
14
+ * `export-exempt` — no tax lines; prints `taxNote` (the CGI exemption mention).
15
+ * `standard` — each line carries `taxRate`; totals show net, tax per rate, gross.
16
+ * `reverse-charge` — no tax charged; prints `taxNote` (the autoliquidation mention).
17
+ */
18
+ export type TaxMode = 'export-exempt' | 'standard' | 'reverse-charge';
19
+ export interface BusinessParty {
20
+ /** First line, printed bold for the client. */
21
+ name: string;
22
+ /** Address, one line per entry. */
23
+ address: string[];
24
+ }
25
+ export interface BusinessIssuer extends BusinessParty {
26
+ logo?: PdfImage | null;
27
+ contact?: {
28
+ name?: string;
29
+ phone?: string;
30
+ email?: string;
31
+ };
32
+ /** Bank details, one line per entry ("BIC : …", "IBAN : …"). */
33
+ bank?: string[];
34
+ /** The legal footer on every page. The first line is printed bold. */
35
+ footer?: string[];
36
+ }
37
+ export interface BusinessClient extends BusinessParty {
38
+ /** The client's number in the issuer's books ("No. client"). */
39
+ reference?: string;
40
+ }
41
+ export interface BusinessLine {
42
+ image?: PdfImage | null;
43
+ code?: string;
44
+ /** May contain `\n` for a hard line break. */
45
+ description?: string;
46
+ quantity: number;
47
+ /** "unité", "day", "kg" — printed in italics under the unit price. */
48
+ unit?: string;
49
+ /** The catalogue price, before any discount. Minor units. */
50
+ unitPrice: number;
51
+ /**
52
+ * Off this line's unit price (0.10.0, replacing `listPrice`): the
53
+ * catalogue price is printed struck through above the discounted one.
54
+ */
55
+ discount?: BusinessDiscount;
56
+ /** Percent, for `taxMode: 'standard'`. Ignored otherwise. */
57
+ taxRate?: number;
58
+ }
59
+ /**
60
+ * A discount: a percentage, or an amount in minor units — per unit on a
61
+ * line, off the subtotal on a whole document.
62
+ */
63
+ export type BusinessDiscount = {
64
+ percent: number;
65
+ } | {
66
+ amount: number;
67
+ };
68
+ export interface BusinessDoc {
69
+ kind: BusinessDocKind;
70
+ language: BusinessDocLanguage;
71
+ numberStyle: NumberStyle;
72
+ currency: CurrencyCode;
73
+ /** `auto` drops the decimals when every amount on the document is whole. */
74
+ decimals?: 'auto' | 2;
75
+ taxMode: TaxMode;
76
+ /** Printed under the totals: the exemption or reverse-charge mention. */
77
+ taxNote?: string;
78
+ number: string;
79
+ /** ISO dates, `YYYY-MM-DD`. */
80
+ date: string;
81
+ /** Quotes, estimates and pro formas. */
82
+ validUntil?: string;
83
+ /** Invoices. */
84
+ dueDate?: string;
85
+ subject?: string;
86
+ issuer: BusinessIssuer;
87
+ client: BusinessClient;
88
+ lines: BusinessLine[];
89
+ /** Off the whole document, after the lines' own discounts and before tax. */
90
+ discount?: BusinessDiscount;
91
+ notes?: string;
92
+ paymentTerms?: string;
93
+ /** Defaults on for quotes, estimates and delivery notes. */
94
+ signature?: boolean;
95
+ }
96
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/business/model.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,eAAe,CAAA;AAE7F,sFAAsF;AACtF,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE7C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAA;AAErC,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAA;AAEhD;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,UAAU,GAAG,gBAAgB,CAAA;AAErE,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAA;IACrB,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,QAAQ,EAAE,YAAY,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,MAAM,EAAE,MAAM,CAAA;IACd,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAE3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB"}
@@ -0,0 +1,21 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Business documents — quotes, estimates, pro formas, invoices, delivery notes.
3
+ //
4
+ // WHY THIS LIVES IN @unisim/doc
5
+ // -----------------------------
6
+ // Two apps need it: Universal Invoices (built for it) and Universal Exports
7
+ // (whose Estimate / Invoice / Packing List PDFs are label/value rows today).
8
+ // The suite has already paid twice for a model copied between apps — this
9
+ // package's own header, and Universal PDF's drifted copy of Universal QR's
10
+ // model — so it is written once, here, with no runtime dependency, on the same
11
+ // `pdfcore` the rest of the package uses.
12
+ //
13
+ // MONEY IS INTEGER MINOR UNITS
14
+ // ----------------------------
15
+ // Every price and total is a whole number of cents/pence. A quantity may be
16
+ // fractional (2.5 days), so a line total is `Math.round(qty × unitPrice)` —
17
+ // rounded once, per line, and never re-derived from a float. `0.1 + 0.2` is not
18
+ // a bug anybody wants to explain to a client on an invoice.
19
+ // ---------------------------------------------------------------------------
20
+ export {};
21
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/business/model.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,EAAE;AACF,gCAAgC;AAChC,gCAAgC;AAChC,4EAA4E;AAC5E,6EAA6E;AAC7E,0EAA0E;AAC1E,2EAA2E;AAC3E,+EAA+E;AAC/E,0CAA0C;AAC1C,EAAE;AACF,+BAA+B;AAC/B,+BAA+B;AAC/B,4EAA4E;AAC5E,4EAA4E;AAC5E,gFAAgF;AAChF,4DAA4D;AAC5D,8EAA8E"}
@@ -0,0 +1,60 @@
1
+ import type { BusinessDiscount, BusinessDoc, BusinessLine, CurrencyCode, NumberStyle } from './model.js';
2
+ export interface TaxBand {
3
+ /** Percent. */
4
+ rate: number;
5
+ /** Minor units. */
6
+ base: number;
7
+ tax: number;
8
+ }
9
+ export interface BusinessTotals {
10
+ /** Per line, minor units, same order as `doc.lines`. */
11
+ lines: number[];
12
+ /** The lines added up. */
13
+ subtotal: number;
14
+ /** The whole document's discount, as a positive amount (0 for none). */
15
+ discount: number;
16
+ /** `subtotal - discount`: what tax is charged on. */
17
+ net: number;
18
+ /** Empty unless `taxMode` is `standard`. Sorted by rate. */
19
+ taxes: TaxBand[];
20
+ gross: number;
21
+ }
22
+ /**
23
+ * Only what the arithmetic reads. An app's own line records (with a stored
24
+ * picture rather than a `PdfImage`, an id, a catalogue link) fit as they are.
25
+ */
26
+ export type PricedLine = Pick<BusinessLine, 'quantity' | 'unitPrice' | 'discount' | 'taxRate'>;
27
+ /** What the totals need from a document. */
28
+ export interface PricedDoc {
29
+ lines: readonly PricedLine[];
30
+ taxMode: BusinessDoc['taxMode'];
31
+ decimals?: BusinessDoc['decimals'];
32
+ discount?: BusinessDiscount;
33
+ }
34
+ /** How much a discount takes off `of`: never more than all of it, never less than nothing. */
35
+ export declare function discountOff(of: number, discount: BusinessDiscount | undefined): number;
36
+ /** A line's unit price after its own discount. */
37
+ export declare function unitNet(line: Pick<PricedLine, 'unitPrice' | 'discount'>): number;
38
+ export declare function lineTotal(line: PricedLine): number;
39
+ /**
40
+ * Tax is computed on each rate's summed base, then rounded — once per rate,
41
+ * not once per line. That is how French and UK VAT returns expect it, and
42
+ * per-line rounding drifts by a cent per line on a long invoice.
43
+ */
44
+ export declare function computeTotals(doc: PricedDoc): BusinessTotals;
45
+ /**
46
+ * Whether a document prints its amounts with decimals. `auto` keeps the
47
+ * Sellsy look ("150 000") until any amount on the page has cents.
48
+ */
49
+ export declare function wantsDecimals(doc: PricedDoc): boolean;
50
+ /** A plain number, no currency: "60 000", "1 234,50", "1,234.50". */
51
+ export declare function formatAmount(minor: number, style: NumberStyle, decimals: boolean): string;
52
+ /** With the currency: fr → "163 000 €", en → "€163,000". */
53
+ export declare function formatMoney(minor: number, currency: CurrencyCode, style: NumberStyle, decimals: boolean): string;
54
+ /** A quantity: whole numbers bare, fractions with up to three places. */
55
+ export declare function formatQuantity(qty: number, style: NumberStyle): string;
56
+ /** A tax rate for a label: 20 → "20", 5.5 → "5,5" (fr) / "5.5" (en). */
57
+ export declare function formatRate(rate: number, style: NumberStyle): string;
58
+ /** ISO `YYYY-MM-DD` → `DD/MM/YYYY`, which both languages use here. */
59
+ export declare function formatDate(iso: string): string;
60
+ //# sourceMappingURL=totals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totals.d.ts","sourceRoot":"","sources":["../../src/business/totals.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExG,MAAM,WAAW,OAAO;IACtB,eAAe;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAA;IACX,4DAA4D;IAC5D,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC,CAAA;AAE9F,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IAC5B,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IAC/B,QAAQ,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,CAAA;IAClC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAC5B;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,CAItF;AAED,kDAAkD;AAClD,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,MAAM,CAEhF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,cAAc,CA+B5D;AAOD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAUrD;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAUzF;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAKhH;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAGtE;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAEnE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C"}
@@ -0,0 +1,109 @@
1
+ // Totals and number formatting. Pure, so the app's on-screen totals and the
2
+ // PDF's printed totals are the same function — not two that agree today.
3
+ /** How much a discount takes off `of`: never more than all of it, never less than nothing. */
4
+ export function discountOff(of, discount) {
5
+ if (!discount)
6
+ return 0;
7
+ const off = 'percent' in discount ? Math.round((of * discount.percent) / 100) : Math.round(discount.amount);
8
+ return Math.max(0, Math.min(of, off));
9
+ }
10
+ /** A line's unit price after its own discount. */
11
+ export function unitNet(line) {
12
+ return line.unitPrice - discountOff(line.unitPrice, line.discount);
13
+ }
14
+ export function lineTotal(line) {
15
+ return Math.round(line.quantity * unitNet(line));
16
+ }
17
+ /**
18
+ * Tax is computed on each rate's summed base, then rounded — once per rate,
19
+ * not once per line. That is how French and UK VAT returns expect it, and
20
+ * per-line rounding drifts by a cent per line on a long invoice.
21
+ */
22
+ export function computeTotals(doc) {
23
+ const lines = doc.lines.map(lineTotal);
24
+ const subtotal = lines.reduce((sum, v) => sum + v, 0);
25
+ // A negative subtotal (credit lines) takes no discount.
26
+ const discount = subtotal > 0 ? discountOff(subtotal, doc.discount) : 0;
27
+ const net = subtotal - discount;
28
+ const taxes = [];
29
+ if (doc.taxMode === 'standard') {
30
+ const bases = new Map();
31
+ doc.lines.forEach((line, i) => {
32
+ const rate = line.taxRate ?? 0;
33
+ bases.set(rate, (bases.get(rate) ?? 0) + lines[i]);
34
+ });
35
+ // The whole-document discount comes off every rate in proportion, each
36
+ // base rounded once; the largest takes the rounding so they add up to `net`.
37
+ const sorted = [...bases].sort((a, b) => a[0] - b[0]);
38
+ if (discount) {
39
+ let left = net;
40
+ const largest = sorted.reduce((k, b, i) => (Math.abs(b[1]) > Math.abs(sorted[k][1]) ? i : k), 0);
41
+ sorted.forEach((b, i) => {
42
+ if (i !== largest)
43
+ left -= b[1] = Math.round((b[1] * net) / subtotal);
44
+ });
45
+ sorted[largest][1] = left;
46
+ }
47
+ for (const [rate, base] of sorted) {
48
+ if (rate === 0)
49
+ continue;
50
+ taxes.push({ rate, base, tax: Math.round((base * rate) / 100) });
51
+ }
52
+ }
53
+ const gross = net + taxes.reduce((sum, b) => sum + b.tax, 0);
54
+ return { lines, subtotal, discount, net, taxes, gross };
55
+ }
56
+ const SYMBOL = { EUR: '€', GBP: '£', USD: '$' };
57
+ /** U+00A0 — WinAnsi can spell it, and it stops "163 000" wrapping between digits. */
58
+ const NBSP = '\u00a0';
59
+ /**
60
+ * Whether a document prints its amounts with decimals. `auto` keeps the
61
+ * Sellsy look ("150 000") until any amount on the page has cents.
62
+ */
63
+ export function wantsDecimals(doc) {
64
+ if (doc.decimals === 2)
65
+ return true;
66
+ const totals = computeTotals(doc);
67
+ const amounts = [
68
+ ...doc.lines.flatMap((l) => [l.unitPrice, unitNet(l)]),
69
+ ...totals.lines,
70
+ totals.discount,
71
+ ...totals.taxes.map((t) => t.tax),
72
+ ];
73
+ return amounts.some((v) => v % 100 !== 0);
74
+ }
75
+ /** A plain number, no currency: "60 000", "1 234,50", "1,234.50". */
76
+ export function formatAmount(minor, style, decimals) {
77
+ const negative = minor < 0;
78
+ const abs = Math.abs(minor);
79
+ const whole = Math.floor(abs / 100);
80
+ const cents = abs % 100;
81
+ const group = style === 'fr' ? NBSP : ',';
82
+ const point = style === 'fr' ? ',' : '.';
83
+ const digits = String(whole).replace(/\B(?=(\d{3})+(?!\d))/g, group);
84
+ const out = decimals ? `${digits}${point}${String(cents).padStart(2, '0')}` : digits;
85
+ return negative ? `-${out}` : out;
86
+ }
87
+ /** With the currency: fr → "163 000 €", en → "€163,000". */
88
+ export function formatMoney(minor, currency, style, decimals) {
89
+ const amount = formatAmount(minor, style, decimals);
90
+ const symbol = SYMBOL[currency];
91
+ if (style === 'fr')
92
+ return `${amount}${NBSP}${symbol}`;
93
+ return amount.startsWith('-') ? `-${symbol}${amount.slice(1)}` : `${symbol}${amount}`;
94
+ }
95
+ /** A quantity: whole numbers bare, fractions with up to three places. */
96
+ export function formatQuantity(qty, style) {
97
+ const text = String(Math.round(qty * 1000) / 1000);
98
+ return style === 'fr' ? text.replace('.', ',') : text;
99
+ }
100
+ /** A tax rate for a label: 20 → "20", 5.5 → "5,5" (fr) / "5.5" (en). */
101
+ export function formatRate(rate, style) {
102
+ return formatQuantity(rate, style);
103
+ }
104
+ /** ISO `YYYY-MM-DD` → `DD/MM/YYYY`, which both languages use here. */
105
+ export function formatDate(iso) {
106
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
107
+ return m ? `${m[3]}/${m[2]}/${m[1]}` : iso;
108
+ }
109
+ //# sourceMappingURL=totals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totals.js","sourceRoot":"","sources":["../../src/business/totals.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export type { EmbeddableFont } from './truetype.js';
17
17
  export type { FontId, PdfImage, Rgb } from './pdfcore.js';
18
18
  export { imageToJpeg, imagesToPdf } from './image.js';
19
19
  export type { PdfPageSource } from './image.js';
20
+ export * from './business/index.js';
20
21
  export { visualOrder, resolveLevels, paragraphLevel } from './bidi.js';
21
22
  export type { BidiParagraph } from './bidi.js';
22
23
  export { emuToPoints, odfLengthToPoints, vmlStyleSize } from './length.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2CA,YAAY,EACV,KAAK,EACL,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,GAAG,GACJ,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG9C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,GACZ,MAAM,cAAc,CAAA;AAOrB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,YAAY,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGvD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAK3C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG7F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACrD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAM/C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACtE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAM9C,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2CA,YAAY,EACV,KAAK,EACL,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,GAAG,GACJ,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG9C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,GACZ,MAAM,cAAc,CAAA;AAOrB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,YAAY,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGvD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAK3C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAG7F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACrD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAK/C,cAAc,qBAAqB,CAAA;AAMnC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACtE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAM9C,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -64,6 +64,10 @@ export { PdfDocument, PdfPage, rgb, toWinAnsi, unspellable, widthOfText } from '
64
64
  // ---- Embedding a font, for the alphabets base-14 cannot spell --------------
65
65
  export { readTrueType, FontFormatError } from './truetype.js';
66
66
  export { imageToJpeg, imagesToPdf } from './image.js';
67
+ // ---- Business documents: quotes, pro formas, invoices, delivery notes ------
68
+ // Fixed-layout A4 on `pdfcore`, with totals in integer minor units. Built for
69
+ // Universal Invoices; Universal Exports is the second consumer it is shaped for.
70
+ export * from './business/index.js';
67
71
  // ---- Right-to-left text ----------------------------------------------------
68
72
  // The bidi reordering `docToPdf` applies to every line, for a caller that
69
73
  // draws its own text straight onto a `PdfPage` (which draws left to right, as
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4CAA4C;AAC5C,EAAE;AACF,0BAA0B;AAC1B,0BAA0B;AAC1B,8EAA8E;AAC9E,4EAA4E;AAC5E,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,+EAA+E;AAC/E,4EAA4E;AAC5E,YAAY;AACZ,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,sEAAsE;AACtE,+EAA+E;AAC/E,2EAA2E;AAC3E,gFAAgF;AAChF,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,uDAAuD;AACvD,uDAAuD;AACvD,6EAA6E;AAC7E,gFAAgF;AAChF,+DAA+D;AAC/D,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,4DAA4D;AAC5D,EAAE;AACF,gFAAgF;AAChF,wCAAwC;AACxC,8EAA8E;AAW9E,8EAA8E;AAC9E,kEAAkE;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,+EAA+E;AAC/E,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAA;AAYrB,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,2DAA2D;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEvD,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGrD,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE7F,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGrD,+EAA+E;AAC/E,0EAA0E;AAC1E,8EAA8E;AAC9E,mCAAmC;AACnC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAGtE,8EAA8E;AAC9E,0EAA0E;AAC1E,4EAA4E;AAC5E,sDAAsD;AACtD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4CAA4C;AAC5C,EAAE;AACF,0BAA0B;AAC1B,0BAA0B;AAC1B,8EAA8E;AAC9E,4EAA4E;AAC5E,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,+EAA+E;AAC/E,4EAA4E;AAC5E,YAAY;AACZ,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,sEAAsE;AACtE,+EAA+E;AAC/E,2EAA2E;AAC3E,gFAAgF;AAChF,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,uDAAuD;AACvD,uDAAuD;AACvD,6EAA6E;AAC7E,gFAAgF;AAChF,+DAA+D;AAC/D,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,4DAA4D;AAC5D,EAAE;AACF,gFAAgF;AAChF,wCAAwC;AACxC,8EAA8E;AAW9E,8EAA8E;AAC9E,kEAAkE;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE9C,+EAA+E;AAC/E,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAA;AAYrB,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,2DAA2D;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEvD,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGrD,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE7F,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAG7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGrD,+EAA+E;AAC/E,8EAA8E;AAC9E,iFAAiF;AACjF,cAAc,qBAAqB,CAAA;AAEnC,+EAA+E;AAC/E,0EAA0E;AAC1E,8EAA8E;AAC9E,mCAAmC;AACnC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAGtE,8EAA8E;AAC9E,0EAA0E;AAC1E,4EAA4E;AAC5E,sDAAsD;AACtD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unisim/doc",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "The suite's document stack: read Word, OpenDocument, RTF, HTML, Markdown, CSV, JSON and text into one model, and write it back out as PDF, text, HTML, Markdown, CSV or JSON. No runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "build": "tsc -p tsconfig.build.json",
22
22
  "dev": "tsc -p tsconfig.build.json --watch",
23
23
  "typecheck": "tsc --noEmit",
24
- "test": "npm run build && node scripts/selftest.mjs",
24
+ "test": "npm run build && node scripts/selftest.mjs && node scripts/business-selftest.mjs",
25
25
  "prepublishOnly": "npm run typecheck && npm run build"
26
26
  },
27
27
  "license": "MIT",