@aglyn/plugins-commerce 1.0.0-beta.289 → 1.0.0-beta.291

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.
Files changed (140) hide show
  1. package/README.md +4 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +2 -1
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
  6. package/src/lib/components/console/commerce-pdf-download.js +53 -0
  7. package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
  8. package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
  9. package/src/lib/components/console/host-orders-card.component.js +3 -2
  10. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  11. package/src/lib/components/console/host-quotes-card.component.js +20 -4
  12. package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
  13. package/src/lib/components/console/invoice-send.component.d.ts +44 -0
  14. package/src/lib/components/console/invoice-send.component.js +237 -0
  15. package/src/lib/components/console/invoice-send.component.js.map +1 -0
  16. package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
  17. package/src/lib/components/console/order-detail-dialog.component.js +9 -2
  18. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  19. package/src/lib/components/console/order-invoice.component.d.ts +15 -4
  20. package/src/lib/components/console/order-invoice.component.js +93 -12
  21. package/src/lib/components/console/order-invoice.component.js.map +1 -1
  22. package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
  23. package/src/lib/components/console/quote-editor-dialog.component.js +154 -5
  24. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
  25. package/src/lib/components/console/quote-proofs.component.d.ts +48 -0
  26. package/src/lib/components/console/quote-proofs.component.js +694 -0
  27. package/src/lib/components/console/quote-proofs.component.js.map +1 -0
  28. package/src/lib/components/console/quote-send.component.d.ts +42 -0
  29. package/src/lib/components/console/quote-send.component.js +307 -0
  30. package/src/lib/components/console/quote-send.component.js.map +1 -0
  31. package/src/lib/components/console/quote-start.component.d.ts +45 -0
  32. package/src/lib/components/console/quote-start.component.js +182 -0
  33. package/src/lib/components/console/quote-start.component.js.map +1 -0
  34. package/src/lib/components/product-options-field-body.d.ts +44 -0
  35. package/src/lib/components/product-options-field-body.js +260 -0
  36. package/src/lib/components/product-options-field-body.js.map +1 -0
  37. package/src/lib/components/product-options-field.d.ts +32 -0
  38. package/src/lib/components/product-options-field.js +128 -0
  39. package/src/lib/components/product-options-field.js.map +1 -0
  40. package/src/lib/components/quote-page-body.js +67 -20
  41. package/src/lib/components/quote-page-body.js.map +1 -1
  42. package/src/lib/components/quote-proof-panel.d.ts +42 -0
  43. package/src/lib/components/quote-proof-panel.js +436 -0
  44. package/src/lib/components/quote-proof-panel.js.map +1 -0
  45. package/src/lib/model/commerce-invoices.d.ts +132 -0
  46. package/src/lib/model/commerce-invoices.js +332 -0
  47. package/src/lib/model/commerce-invoices.js.map +1 -0
  48. package/src/lib/model/commerce-orders.d.ts +29 -2
  49. package/src/lib/model/commerce-orders.js.map +1 -1
  50. package/src/lib/model/commerce-product-field.d.ts +23 -0
  51. package/src/lib/model/commerce-product-field.js +26 -0
  52. package/src/lib/model/commerce-product-field.js.map +1 -0
  53. package/src/lib/model/commerce-quote-email.d.ts +67 -0
  54. package/src/lib/model/commerce-quote-email.js +154 -0
  55. package/src/lib/model/commerce-quote-email.js.map +1 -0
  56. package/src/lib/model/commerce-quote-pay.d.ts +76 -6
  57. package/src/lib/model/commerce-quote-pay.js +105 -39
  58. package/src/lib/model/commerce-quote-pay.js.map +1 -1
  59. package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
  60. package/src/lib/model/commerce-quote-proofs.js +348 -0
  61. package/src/lib/model/commerce-quote-proofs.js.map +1 -0
  62. package/src/lib/model/commerce-quote-request.d.ts +93 -0
  63. package/src/lib/model/commerce-quote-request.js +207 -0
  64. package/src/lib/model/commerce-quote-request.js.map +1 -0
  65. package/src/lib/model/commerce-quotes.d.ts +140 -3
  66. package/src/lib/model/commerce-quotes.js.map +1 -1
  67. package/src/lib/model/index.d.ts +4 -0
  68. package/src/lib/model/index.js +4 -0
  69. package/src/lib/model/index.js.map +1 -1
  70. package/src/lib/plugin.js +19 -0
  71. package/src/lib/plugin.js.map +1 -1
  72. package/src/lib/server/billing-webhook.js +115 -70
  73. package/src/lib/server/billing-webhook.js.map +1 -1
  74. package/src/lib/server/commerce-pdf.d.ts +104 -0
  75. package/src/lib/server/commerce-pdf.js +310 -0
  76. package/src/lib/server/commerce-pdf.js.map +1 -0
  77. package/src/lib/server/draft-order.js +23 -3
  78. package/src/lib/server/draft-order.js.map +1 -1
  79. package/src/lib/server/invoice-email.d.ts +76 -0
  80. package/src/lib/server/invoice-email.js +183 -0
  81. package/src/lib/server/invoice-email.js.map +1 -0
  82. package/src/lib/server/order-invoice-send.d.ts +43 -0
  83. package/src/lib/server/order-invoice-send.js +218 -0
  84. package/src/lib/server/order-invoice-send.js.map +1 -0
  85. package/src/lib/server/order-print.d.ts +6 -0
  86. package/src/lib/server/order-print.js +22 -5
  87. package/src/lib/server/order-print.js.map +1 -1
  88. package/src/lib/server/order-status.d.ts +11 -0
  89. package/src/lib/server/order-status.js +44 -8
  90. package/src/lib/server/order-status.js.map +1 -1
  91. package/src/lib/server/pdf-response.d.ts +31 -0
  92. package/src/lib/server/pdf-response.js +30 -0
  93. package/src/lib/server/pdf-response.js.map +1 -0
  94. package/src/lib/server/person-eraser.d.ts +3 -2
  95. package/src/lib/server/person-eraser.js +25 -6
  96. package/src/lib/server/person-eraser.js.map +1 -1
  97. package/src/lib/server/pos-reader-splash.d.ts +2 -18
  98. package/src/lib/server/pos-reader-splash.js +8 -93
  99. package/src/lib/server/pos-reader-splash.js.map +1 -1
  100. package/src/lib/server/quote-email.d.ts +85 -0
  101. package/src/lib/server/quote-email.js +211 -0
  102. package/src/lib/server/quote-email.js.map +1 -0
  103. package/src/lib/server/quote-link-token.d.ts +11 -0
  104. package/src/lib/server/quote-link-token.js +21 -0
  105. package/src/lib/server/quote-link-token.js.map +1 -1
  106. package/src/lib/server/quote-pay.d.ts +38 -6
  107. package/src/lib/server/quote-pay.js +115 -53
  108. package/src/lib/server/quote-pay.js.map +1 -1
  109. package/src/lib/server/quote-proofs.d.ts +51 -0
  110. package/src/lib/server/quote-proofs.js +500 -0
  111. package/src/lib/server/quote-proofs.js.map +1 -0
  112. package/src/lib/server/quote-uploads.d.ts +33 -0
  113. package/src/lib/server/quote-uploads.js +29 -0
  114. package/src/lib/server/quote-uploads.js.map +1 -0
  115. package/src/lib/server/quote.d.ts +25 -0
  116. package/src/lib/server/quote.js +638 -8
  117. package/src/lib/server/quote.js.map +1 -1
  118. package/src/lib/server/site-image-source.d.ts +38 -0
  119. package/src/lib/server/site-image-source.js +130 -0
  120. package/src/lib/server/site-image-source.js.map +1 -0
  121. package/src/lib/server/site-page-resolver.js +2 -1
  122. package/src/lib/server/site-page-resolver.js.map +1 -1
  123. package/src/lib/server.js +24 -1
  124. package/src/lib/server.js.map +1 -1
  125. package/src/lib/site.js +8 -0
  126. package/src/lib/site.js.map +1 -1
  127. package/src/lib/tenant-emails.js +243 -0
  128. package/src/lib/tenant-emails.js.map +1 -1
  129. package/src/lib/utils/commerce-document-pdf.d.ts +82 -0
  130. package/src/lib/utils/commerce-document-pdf.js +613 -0
  131. package/src/lib/utils/commerce-document-pdf.js.map +1 -0
  132. package/src/lib/utils/invoice-email-content.d.ts +45 -0
  133. package/src/lib/utils/invoice-email-content.js +126 -0
  134. package/src/lib/utils/invoice-email-content.js.map +1 -0
  135. package/src/lib/utils/order-invoice.d.ts +52 -3
  136. package/src/lib/utils/order-invoice.js +124 -64
  137. package/src/lib/utils/order-invoice.js.map +1 -1
  138. package/src/lib/utils/quote-email-content.d.ts +62 -0
  139. package/src/lib/utils/quote-email-content.js +154 -0
  140. package/src/lib/utils/quote-email-content.js.map +1 -0
@@ -0,0 +1,613 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { buildEmailPalette, emailAccent, resolveEmailColor } from "@aglyn/shared-util-email";
18
+ import { relativeLuminanceOfRgb, rgbChannelsOfHex } from "@aglyn/shared-util-tools/contrast";
19
+ import { PdfWriter, pdfRgbFromHex } from "@aglyn/shared-util-tools/pdf/pdf-writer";
20
+ import { buildOrderInvoiceData, invoiceAddressLines, invoiceMoney } from "./order-invoice.js";
21
+ const NEUTRAL_PALETTE = buildEmailPalette({
22
+ base: 'tenant'
23
+ });
24
+ const neutral = (path)=>{
25
+ var _resolveEmailColor;
26
+ return (_resolveEmailColor = resolveEmailColor(path, NEUTRAL_PALETTE)) != null ? _resolveEmailColor : '';
27
+ };
28
+ /** The colors a document wears where its site's theme gives none: the default site theme's greys. */ export const COMMERCE_PDF_NEUTRAL_COLORS = Object.freeze({
29
+ accent: neutral('grey.800'),
30
+ onAccent: neutral('background.paper'),
31
+ accentText: neutral('grey.800'),
32
+ tint: neutral('grey.100'),
33
+ ink: neutral('text.primary'),
34
+ muted: neutral('text.secondary'),
35
+ rule: neutral('divider')
36
+ });
37
+ /** WCAG contrast between two `#rrggbb` colors; 1 when either cannot be read. */ function contrast(one, other) {
38
+ const a = rgbChannelsOfHex(one);
39
+ const b = rgbChannelsOfHex(other);
40
+ if (!a || !b) return 1;
41
+ const la = relativeLuminanceOfRgb(...a);
42
+ const lb = relativeLuminanceOfRgb(...b);
43
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
44
+ }
45
+ /**
46
+ * A document's colors from a site's resolved palette: the primary color for
47
+ * the accent, the theme's own text and divider colors, and text on the
48
+ * accent that reads at 4.5:1 (the theme's contrast text when it does, else
49
+ * the theme's dark ink or white, whichever reads better).
50
+ */ export function commercePdfColorsFromPalette(palette) {
51
+ const accent = emailAccent(palette);
52
+ const read = (path, fallback)=>{
53
+ var _resolveEmailColor;
54
+ return (_resolveEmailColor = resolveEmailColor(path, palette)) != null ? _resolveEmailColor : fallback;
55
+ };
56
+ const ink = read('text.primary', COMMERCE_PDF_NEUTRAL_COLORS.ink);
57
+ const paper = read('background.paper', COMMERCE_PDF_NEUTRAL_COLORS.onAccent);
58
+ const onAccent = contrast(accent.label, accent.fill) >= 4.5 ? accent.label : contrast(ink, accent.fill) >= contrast(paper, accent.fill) ? ink : paper;
59
+ return {
60
+ accent: accent.fill,
61
+ onAccent,
62
+ accentText: contrast(accent.link, paper) >= 4.5 ? accent.link : ink,
63
+ tint: read('tint.primary', COMMERCE_PDF_NEUTRAL_COLORS.tint),
64
+ ink,
65
+ muted: read('text.secondary', COMMERCE_PDF_NEUTRAL_COLORS.muted),
66
+ rule: read('divider', COMMERCE_PDF_NEUTRAL_COLORS.rule)
67
+ };
68
+ }
69
+ /** A date the American way, in the site's time zone: `October 28, 2026`. */ export function formatPdfDate(ms, timeZone) {
70
+ if (!ms || !Number.isFinite(ms)) return '';
71
+ const options = {
72
+ year: 'numeric',
73
+ month: 'long',
74
+ day: 'numeric'
75
+ };
76
+ try {
77
+ return new Intl.DateTimeFormat('en-US', _extends({}, options, timeZone ? {
78
+ timeZone
79
+ } : {})).format(new Date(ms));
80
+ } catch (unused) {
81
+ return new Intl.DateTimeFormat('en-US', _extends({}, options, {
82
+ timeZone: 'UTC'
83
+ })).format(new Date(ms));
84
+ }
85
+ }
86
+ const minus = (text)=>`-${text}`;
87
+ function quoteAddressLines(address) {
88
+ return invoiceAddressLines(address, {
89
+ phone: false
90
+ });
91
+ }
92
+ function quoteDocument(input) {
93
+ var _view_customerName, _view_company;
94
+ const { view, brand } = input;
95
+ const money = (cents)=>invoiceMoney(cents, view.currency);
96
+ const date = (ms)=>formatPdfDate(ms, brand.timeZone);
97
+ const customer = [
98
+ (_view_customerName = view.customerName) != null ? _view_customerName : '',
99
+ (_view_company = view.company) != null ? _view_company : ''
100
+ ].filter(Boolean);
101
+ const billTo = quoteAddressLines(view.billTo);
102
+ const parties = [];
103
+ if (billTo.length) {
104
+ // The bill-to's own name leads; the customer's name and company are added when it does not carry them.
105
+ const named = customer.filter((part)=>!billTo.includes(part));
106
+ parties.push({
107
+ label: 'Bill to',
108
+ lines: [
109
+ ...named,
110
+ ...billTo
111
+ ]
112
+ });
113
+ } else if (customer.length) {
114
+ parties.push({
115
+ label: 'Prepared for',
116
+ lines: customer
117
+ });
118
+ }
119
+ const shipTo = quoteAddressLines(view.shipTo);
120
+ if (shipTo.length) parties.push({
121
+ label: 'Ship to',
122
+ lines: shipTo
123
+ });
124
+ const summary = [
125
+ {
126
+ label: 'Subtotal',
127
+ value: money(view.totals.itemsCents)
128
+ }
129
+ ];
130
+ if (view.totals.discountCents > 0) {
131
+ summary.push({
132
+ label: view.discountLabel ? `Discount (${view.discountLabel})` : 'Discount',
133
+ value: minus(money(view.totals.discountCents))
134
+ });
135
+ }
136
+ if (view.totals.shippingCents > 0) summary.push({
137
+ label: 'Shipping',
138
+ value: money(view.totals.shippingCents)
139
+ });
140
+ if (view.taxAtPayment) summary.push({
141
+ label: 'Tax is added at payment',
142
+ value: '',
143
+ note: true
144
+ });
145
+ else if (view.totals.taxCents > 0) summary.push({
146
+ label: view.taxLabel || 'Tax',
147
+ value: money(view.totals.taxCents)
148
+ });
149
+ else if (view.taxLabel && view.taxLabel !== 'Tax') summary.push({
150
+ label: view.taxLabel,
151
+ value: '',
152
+ note: true
153
+ });
154
+ summary.push({
155
+ label: view.taxAtPayment ? 'Total before tax' : 'Total',
156
+ value: money(view.totals.totalCents),
157
+ emphasis: true
158
+ });
159
+ const stamp = view.status === 'paid' ? `Paid${view.paidAtMs ? ` ${date(view.paidAtMs)}` : ''}` : view.status === 'canceled' ? 'Canceled' : view.status === 'refunded' ? 'Refunded' : null;
160
+ const facts = [];
161
+ if (view.dateMs) facts.push([
162
+ 'Date',
163
+ date(view.dateMs)
164
+ ]);
165
+ if (input.revision && input.revision > 0) facts.push([
166
+ 'Revision',
167
+ String(input.revision)
168
+ ]);
169
+ const sections = [];
170
+ if (view.notes) sections.push({
171
+ heading: 'Notes',
172
+ text: view.notes
173
+ });
174
+ if (view.terms) sections.push({
175
+ heading: 'Terms',
176
+ text: view.terms
177
+ });
178
+ const payUrl = view.status === 'unpaid' && input.payUrl ? input.payUrl : null;
179
+ return {
180
+ kind: 'QUOTE',
181
+ title: view.number,
182
+ facts,
183
+ stamp,
184
+ parties,
185
+ lines: view.lines.map((line)=>{
186
+ var _line_variantLabel, _line_description;
187
+ return {
188
+ name: line.name,
189
+ details: [
190
+ (_line_variantLabel = line.variantLabel) != null ? _line_variantLabel : '',
191
+ (_line_description = line.description) != null ? _line_description : '',
192
+ line.sku ? `SKU ${line.sku}` : ''
193
+ ].filter(Boolean),
194
+ quantity: line.quantity,
195
+ unit: line.included ? '' : money(line.unitAmountCents),
196
+ amount: line.included ? 'Included' : money(line.amountCents)
197
+ };
198
+ }),
199
+ summary,
200
+ sections,
201
+ pay: payUrl ? {
202
+ url: payUrl,
203
+ label: 'Pay this quote',
204
+ intro: view.approvalRequired ? `${view.storeName || 'We'} will ask you to approve the proof first, at this link:` : 'Open the quote on our site to pay it securely by card:'
205
+ } : null
206
+ };
207
+ }
208
+ /** The quote as a PDF. */ export function renderQuotePdf(input) {
209
+ return renderCommerceDocument(input.brand, quoteDocument(input));
210
+ }
211
+ function invoiceDocument(input) {
212
+ const data = buildOrderInvoiceData(input);
213
+ const money = (cents)=>invoiceMoney(cents, data.currency);
214
+ const parties = [];
215
+ if (data.billTo.length) parties.push({
216
+ label: 'Bill to',
217
+ lines: data.billTo
218
+ });
219
+ if (data.shipTo.length) parties.push({
220
+ label: 'Ship to',
221
+ lines: data.shipTo
222
+ });
223
+ const facts = [];
224
+ if (data.dateMs) facts.push([
225
+ 'Order date',
226
+ formatPdfDate(data.dateMs, input.brand.timeZone)
227
+ ]);
228
+ const sections = [];
229
+ if (data.footer) sections.push({
230
+ heading: '',
231
+ text: data.footer
232
+ });
233
+ if (data.termsUrl) sections.push({
234
+ heading: 'Terms',
235
+ text: data.termsUrl,
236
+ link: data.termsUrl
237
+ });
238
+ const payUrl = input.order.status === 'pending' && input.payUrl ? input.payUrl : null;
239
+ return {
240
+ kind: 'INVOICE',
241
+ title: `Invoice ${data.number}`,
242
+ facts,
243
+ stamp: data.paymentStatus,
244
+ parties,
245
+ lines: data.lines.map((line)=>({
246
+ name: line.name,
247
+ details: [
248
+ line.detail,
249
+ line.description
250
+ ].filter(Boolean),
251
+ quantity: line.quantity,
252
+ unit: line.included ? '' : money(line.unitCents),
253
+ amount: line.included ? 'Included' : money(line.amountCents)
254
+ })),
255
+ summary: data.summary.map((row)=>({
256
+ label: row.label,
257
+ value: row.minus ? minus(money(row.cents)) : money(row.cents),
258
+ emphasis: row.emphasis
259
+ })),
260
+ sections,
261
+ pay: payUrl ? {
262
+ url: payUrl,
263
+ label: 'Pay this invoice',
264
+ intro: 'Open the invoice on our site to pay it securely by card:'
265
+ } : null
266
+ };
267
+ }
268
+ /** Any order's invoice as a PDF, a paid quote's included. */ export function renderOrderInvoicePdf(input) {
269
+ return renderCommerceDocument(input.brand, invoiceDocument(input));
270
+ }
271
+ /*==========================================
272
+ * THE LAYOUT
273
+ *=========================================*/ const PAGE_MARGIN = {
274
+ top: 48,
275
+ right: 48,
276
+ bottom: 60,
277
+ left: 48
278
+ };
279
+ const BAND_HEIGHT = 10;
280
+ const LOGO_BOX = {
281
+ width: 180,
282
+ height: 50
283
+ };
284
+ function renderCommerceDocument(brand, document) {
285
+ var _brand_colors;
286
+ const palette = _extends({}, COMMERCE_PDF_NEUTRAL_COLORS, (_brand_colors = brand.colors) != null ? _brand_colors : {});
287
+ const color = (hex, fallback)=>{
288
+ var _ref, _pdfRgbFromHex;
289
+ return (_ref = (_pdfRgbFromHex = pdfRgbFromHex(hex)) != null ? _pdfRgbFromHex : pdfRgbFromHex(fallback)) != null ? _ref : [
290
+ 0,
291
+ 0,
292
+ 0
293
+ ];
294
+ };
295
+ const colors = {
296
+ accent: color(palette.accent, COMMERCE_PDF_NEUTRAL_COLORS.accent),
297
+ onAccent: color(palette.onAccent, COMMERCE_PDF_NEUTRAL_COLORS.onAccent),
298
+ accentText: color(palette.accentText, COMMERCE_PDF_NEUTRAL_COLORS.accentText),
299
+ tint: color(palette.tint, COMMERCE_PDF_NEUTRAL_COLORS.tint),
300
+ ink: color(palette.ink, COMMERCE_PDF_NEUTRAL_COLORS.ink),
301
+ muted: color(palette.muted, COMMERCE_PDF_NEUTRAL_COLORS.muted),
302
+ rule: color(palette.rule, COMMERCE_PDF_NEUTRAL_COLORS.rule)
303
+ };
304
+ const businessName = brand.businessName.trim();
305
+ const documentTitle = businessName ? `${document.title} - ${businessName}` : document.title;
306
+ const writer = new PdfWriter({
307
+ title: documentTitle,
308
+ margin: PAGE_MARGIN,
309
+ onNewPage: (page)=>{
310
+ page.fillRect(0, page.pageHeight - BAND_HEIGHT / 2, page.pageWidth, BAND_HEIGHT / 2, colors.accent);
311
+ const baseline = page.top + 14;
312
+ if (businessName) page.textIn(page.left, page.contentWidth, baseline, businessName, {
313
+ size: 8.5,
314
+ font: 'bold',
315
+ color: colors.muted
316
+ });
317
+ page.textIn(page.left, page.contentWidth, baseline, `${document.title} (continued)`, {
318
+ size: 8.5,
319
+ color: colors.muted
320
+ }, 'right');
321
+ }
322
+ });
323
+ const { left, right } = writer;
324
+ // The band across the top of the first page.
325
+ writer.fillRect(0, writer.pageHeight - BAND_HEIGHT, writer.pageWidth, BAND_HEIGHT, colors.accent);
326
+ // Left: logo, business name, contact.
327
+ const leftWidth = writer.contentWidth * 0.55;
328
+ let leftY = writer.top;
329
+ if (brand.logo) {
330
+ const drawn = writer.image(brand.logo, {
331
+ x: left,
332
+ y: leftY - LOGO_BOX.height,
333
+ width: LOGO_BOX.width,
334
+ height: LOGO_BOX.height
335
+ });
336
+ leftY = drawn.y - 12;
337
+ }
338
+ writer.y = leftY;
339
+ if (businessName) writer.paragraph(businessName, {
340
+ width: leftWidth,
341
+ style: {
342
+ font: 'bold',
343
+ size: 14,
344
+ color: colors.ink
345
+ },
346
+ leading: 1.25,
347
+ after: 2
348
+ });
349
+ for (const line of brand.contactLines){
350
+ writer.paragraph(line, {
351
+ width: leftWidth,
352
+ style: {
353
+ size: 9,
354
+ color: colors.muted
355
+ },
356
+ leading: 1.3
357
+ });
358
+ }
359
+ leftY = writer.y;
360
+ // Right: the document's name, number, facts and standing.
361
+ const factsWidth = 210;
362
+ const factsX = right - factsWidth;
363
+ let rightY = writer.top - 22;
364
+ writer.textIn(factsX, factsWidth, rightY, document.kind, {
365
+ font: 'bold',
366
+ size: 24,
367
+ color: colors.accentText
368
+ }, 'right');
369
+ rightY -= 20;
370
+ writer.textIn(factsX, factsWidth, rightY, document.title, {
371
+ font: 'bold',
372
+ size: 11,
373
+ color: colors.ink
374
+ }, 'right');
375
+ for (const [label, value] of document.facts){
376
+ rightY -= 14;
377
+ const valueWidth = writer.measure(value, {
378
+ size: 9.5
379
+ });
380
+ writer.textIn(factsX, factsWidth, rightY, value, {
381
+ size: 9.5,
382
+ color: colors.ink
383
+ }, 'right');
384
+ writer.textIn(factsX, factsWidth - valueWidth - 8, rightY, label, {
385
+ size: 9.5,
386
+ color: colors.muted
387
+ }, 'right');
388
+ }
389
+ if (document.stamp) {
390
+ rightY -= 24;
391
+ const style = {
392
+ font: 'bold',
393
+ size: 8.5,
394
+ color: colors.onAccent
395
+ };
396
+ const width = writer.measure(document.stamp, style) + 16;
397
+ writer.fillRect(right - width, rightY - 5, width, 17, colors.accent);
398
+ writer.textIn(right - width, width, rightY, document.stamp, style, 'center');
399
+ rightY -= 5;
400
+ }
401
+ writer.y = Math.min(leftY, rightY) - 22;
402
+ // Who it is for.
403
+ if (document.parties.length) {
404
+ const columnWidth = Math.min(220, writer.contentWidth / document.parties.length);
405
+ const top = writer.y;
406
+ let lowest = top;
407
+ document.parties.forEach((party, index)=>{
408
+ const x = left + index * (columnWidth + 16);
409
+ writer.y = top;
410
+ writer.paragraph(party.label.toUpperCase(), {
411
+ x,
412
+ width: columnWidth,
413
+ style: {
414
+ font: 'bold',
415
+ size: 8,
416
+ color: colors.muted
417
+ },
418
+ after: 2
419
+ });
420
+ party.lines.forEach((line, lineIndex)=>{
421
+ writer.paragraph(line, {
422
+ x,
423
+ width: columnWidth,
424
+ style: {
425
+ size: 9.5,
426
+ color: colors.ink,
427
+ font: lineIndex === 0 ? 'bold' : 'regular'
428
+ }
429
+ });
430
+ });
431
+ lowest = Math.min(lowest, writer.y);
432
+ });
433
+ writer.y = lowest - 18;
434
+ }
435
+ // The lines.
436
+ const rows = document.lines.map((line)=>[
437
+ [
438
+ {
439
+ text: line.name,
440
+ style: {
441
+ font: 'bold',
442
+ size: 9.5,
443
+ color: colors.ink
444
+ }
445
+ },
446
+ ...line.details.map((detail)=>({
447
+ text: detail,
448
+ style: {
449
+ size: 8.5,
450
+ color: colors.muted
451
+ }
452
+ }))
453
+ ],
454
+ String(line.quantity),
455
+ line.unit,
456
+ line.amount
457
+ ]);
458
+ writer.table({
459
+ columns: [
460
+ {
461
+ header: 'Item',
462
+ width: 0
463
+ },
464
+ {
465
+ header: 'Qty',
466
+ width: 48,
467
+ align: 'right'
468
+ },
469
+ {
470
+ header: 'Unit price',
471
+ width: 86,
472
+ align: 'right'
473
+ },
474
+ {
475
+ header: 'Amount',
476
+ width: 90,
477
+ align: 'right'
478
+ }
479
+ ],
480
+ rows,
481
+ headerFill: colors.accent,
482
+ headerStyle: {
483
+ font: 'bold',
484
+ size: 8.5,
485
+ color: colors.onAccent
486
+ },
487
+ bodyStyle: {
488
+ size: 9.5,
489
+ color: colors.ink
490
+ },
491
+ padding: {
492
+ x: 8,
493
+ y: 6
494
+ },
495
+ ruleColor: colors.rule
496
+ });
497
+ // The totals on the right; beside them on the left, paying it.
498
+ const summaryWidth = 250;
499
+ const summaryX = right - summaryWidth;
500
+ const payWidth = writer.contentWidth - summaryWidth - 28;
501
+ const rowHeight = 17;
502
+ const summaryHeight = document.summary.reduce((sum, row)=>sum + (row.emphasis ? rowHeight + 8 : rowHeight), 0);
503
+ const pay = document.pay;
504
+ const buttonStyle = {
505
+ font: 'bold',
506
+ size: 10.5,
507
+ color: colors.onAccent
508
+ };
509
+ const buttonHeight = 26;
510
+ const payParts = pay ? {
511
+ intro: writer.wrap(pay.intro, payWidth, {
512
+ size: 9
513
+ }),
514
+ url: writer.wrap(pay.url, payWidth, {
515
+ size: 8
516
+ })
517
+ } : null;
518
+ const payHeight = payParts ? 18 + payParts.intro.length * 12 + 6 + buttonHeight + 8 + payParts.url.length * 11 : 0;
519
+ writer.down(12);
520
+ writer.ensure(Math.max(summaryHeight, payHeight));
521
+ const blockTop = writer.y;
522
+ for (const row of document.summary){
523
+ const height = row.emphasis ? rowHeight + 8 : rowHeight;
524
+ if (row.emphasis) writer.fillRect(summaryX, writer.y - height, summaryWidth, height, colors.tint);
525
+ const size = row.emphasis ? 11.5 : 9.5;
526
+ const baseline = writer.y - height / 2 - size * 0.35;
527
+ const style = row.emphasis ? {
528
+ font: 'bold',
529
+ size,
530
+ color: colors.ink
531
+ } : _extends({
532
+ size,
533
+ color: row.note ? colors.muted : colors.ink
534
+ }, row.note ? {
535
+ font: 'italic'
536
+ } : {});
537
+ writer.textIn(summaryX + 8, summaryWidth - 16, baseline, row.label, style, 'left');
538
+ if (row.value) writer.textIn(summaryX + 8, summaryWidth - 16, baseline, row.value, style, 'right');
539
+ writer.down(height);
540
+ }
541
+ const summaryBottom = writer.y;
542
+ if (pay && payParts) {
543
+ let y = blockTop - 12;
544
+ writer.text(left, y, 'Pay online', {
545
+ font: 'bold',
546
+ size: 11,
547
+ color: colors.ink
548
+ });
549
+ y -= 6;
550
+ for (const line of payParts.intro){
551
+ y -= 12;
552
+ writer.text(left, y + 2, line, {
553
+ size: 9,
554
+ color: colors.muted
555
+ });
556
+ }
557
+ y -= 6;
558
+ const buttonWidth = writer.measure(pay.label, buttonStyle) + 36;
559
+ writer.fillRect(left, y - buttonHeight, buttonWidth, buttonHeight, colors.accent);
560
+ writer.textIn(left, buttonWidth, y - buttonHeight / 2 - 10.5 * 0.35, pay.label, buttonStyle, 'center');
561
+ writer.link(left, y - buttonHeight, buttonWidth, buttonHeight, pay.url);
562
+ y -= buttonHeight + 8;
563
+ for (const line of payParts.url){
564
+ y -= 11;
565
+ writer.text(left, y + 2, line, {
566
+ size: 8,
567
+ color: colors.accentText
568
+ });
569
+ writer.link(left, y, writer.measure(line, {
570
+ size: 8
571
+ }), 11, pay.url);
572
+ }
573
+ writer.y = Math.min(summaryBottom, y);
574
+ }
575
+ writer.down(22);
576
+ // Notes, terms, the store's footer.
577
+ for (const section of document.sections){
578
+ if (section.heading) {
579
+ writer.ensure(36);
580
+ writer.paragraph(section.heading, {
581
+ style: {
582
+ font: 'bold',
583
+ size: 10,
584
+ color: colors.ink
585
+ },
586
+ after: 1
587
+ });
588
+ }
589
+ writer.paragraph(section.text, _extends({
590
+ style: {
591
+ size: 9,
592
+ color: section.link ? colors.accentText : colors.muted
593
+ }
594
+ }, section.link ? {
595
+ link: section.link
596
+ } : {}, {
597
+ after: 12
598
+ }));
599
+ }
600
+ return writer.finish({
601
+ footer: (page, pages)=>({
602
+ left: businessName ? `${businessName} · ${document.title}` : document.title,
603
+ right: `Page ${page} of ${pages}`
604
+ }),
605
+ footerStyle: {
606
+ size: 8,
607
+ color: colors.muted
608
+ },
609
+ footerRule: colors.rule
610
+ });
611
+ }
612
+
613
+ //# sourceMappingURL=commerce-document-pdf.js.map