@teleporthq/teleport-plugin-next-workflows 0.44.5 → 0.44.6

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.
@@ -0,0 +1,382 @@
1
+ /* tslint:disable:function-constructor */
2
+ import { generateInvoiceAssemblyCode } from '../src/invoice/invoice-assembly-code'
3
+ import { generatePdfGeneratorCode } from '../src/invoice/pdf-generator-code'
4
+ import type { UIDLInvoiceSettings } from '@teleporthq/teleport-types'
5
+ import { evaluateEmittedModule } from './_helpers/load-invoice-route'
6
+
7
+ /**
8
+ * What the invoice says about being paid, and how it writes money.
9
+ *
10
+ * A store invoices an order the moment its payment webhook marks it paid, so
11
+ * that invoice — its email and its PDF — must not ask for a payment already
12
+ * made: no due date, no amount due. A cash-on-delivery order is invoiced
13
+ * unpaid when it is placed and keeps both. A summary line whose amount is zero
14
+ * (no delivery, no discount, no tax, no gift card) is left out. And an empty
15
+ * field on the real invoice prints nothing — never the design canvas's sample.
16
+ *
17
+ * Every piece below is the EMITTED code, run: the assembly the generate route
18
+ * builds the invoice with, then the renderer the email and the PDF go through
19
+ * (`email-sender-code.ts` fills the body with `buildDataContext` +
20
+ * `replacePlaceholders`).
21
+ */
22
+
23
+ const SETTINGS = {
24
+ invoicePrefix: 'INV-',
25
+ defaultTaxRate: 0,
26
+ showDiscount: false,
27
+ taxIncludedInPrice: false,
28
+ companyDetails: {},
29
+ template: { document: null as unknown },
30
+ tables: { invoicesTable: 'teleport_invoices', invoiceItemsTable: 'teleport_invoice_items' },
31
+ emailDelivery: { enabled: false },
32
+ } as unknown as UIDLInvoiceSettings
33
+
34
+ type Row = Record<string, unknown>
35
+
36
+ interface AssemblyModule {
37
+ assembleInvoiceData: (body: Row, order: Row | null, items: Row[]) => Row
38
+ }
39
+
40
+ interface PdfGeneratorModule {
41
+ buildInvoiceDataScope: (invoiceData: Row) => {
42
+ invoiceData: { Invoice: Record<string, string>; Company: Record<string, string> }
43
+ }
44
+ buildInvoiceHtml: (invoiceData: Row) => string
45
+ replacePlaceholders: (template: string, data: Row) => string
46
+ buildDataContext: (invoiceData: Row) => Row
47
+ }
48
+
49
+ function loadAssembly(): AssemblyModule {
50
+ const requireFn = ((id: string) => {
51
+ if (id.indexOf('pdf-generator') !== -1) {
52
+ return { COMPANY_DETAILS: {} }
53
+ }
54
+ if (id.indexOf('data-access') !== -1) {
55
+ return {}
56
+ }
57
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
58
+ return require(id)
59
+ }) as unknown as NodeRequire
60
+ return evaluateEmittedModule<AssemblyModule>(generateInvoiceAssemblyCode(SETTINGS), requireFn)
61
+ }
62
+
63
+ function loadPdfGenerator(templateUidl: unknown = null): PdfGeneratorModule {
64
+ return evaluateEmittedModule<PdfGeneratorModule>(
65
+ generatePdfGeneratorCode(SETTINGS, templateUidl, templateUidl ? {} : null),
66
+ require
67
+ )
68
+ }
69
+
70
+ // The date the renderer prints for a stored date: it reads the calendar day in
71
+ // the server's own time zone, so the expectation does too.
72
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
73
+ const printedDate = (raw: unknown): string => {
74
+ const date = new Date(raw as string)
75
+ return MONTHS[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear()
76
+ }
77
+
78
+ // The editor's invoice email design (teleport-gui purposed-email-designs.ts) as
79
+ // it is published: each summary row is a <div> of <span>s, copied from a body a
80
+ // store sent on 2026-10-23 with its values put back to their tokens.
81
+ const SUMMARY_ROW_STYLE =
82
+ 'color: #374151; margin: 0; padding: 10px 0; font-size: 14px; line-height: 1.5'
83
+ const summaryRow = (label: string, value: string, last = false) =>
84
+ `<div style="${SUMMARY_ROW_STYLE}${last ? '' : '; border-bottom: 1px solid #f0f1f3'}">` +
85
+ `<span>${label}: </span>${value}</div>`
86
+ const INVOICE_EMAIL_BODY =
87
+ '<div style="color: #1f2937; margin: 0 auto; padding: 8px; max-width: 600px; font-family: \'Segoe UI\',Roboto,Helvetica,Arial,sans-serif">' +
88
+ '<div style="border: 1px solid #e5e7eb; overflow: hidden; background: #ffffff; border-radius: 14px">' +
89
+ '<div style="padding: 24px 32px; background: #1e293b"><small style="color: #94a3b8; margin: 0; font-size: 12px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase">Invoice</small>' +
90
+ '<p style="color: #ffffff; margin: 6px 0 0; font-size: 22px; font-weight: 700">Your invoice</p></div>' +
91
+ '<div style="padding: 28px 32px">' +
92
+ '<div style="color: #1f2937; margin: 0 0 16px; font-size: 15px; line-height: 1.6"><span>Dear </span><span>{{customerName}}</span><span>,</span></div>' +
93
+ '<p style="color: #4b5563; margin: 0 0 18px; font-size: 15px; line-height: 1.6">Thank you for your purchase. Your invoice is attached to this email. Here is a quick summary:</p>' +
94
+ '<div style="margin: 0 0 22px">' +
95
+ summaryRow('Invoice number', '<span>{{invoiceNumber}}</span>') +
96
+ summaryRow('Invoice date', '<span>{{invoiceDate}}</span>') +
97
+ summaryRow('Due date', '<span>{{dueDate}}</span>') +
98
+ summaryRow('Subtotal', '<span>{{subtotal}}</span>') +
99
+ summaryRow('Discount', '<span>−</span><span>{{discountAmount}}</span>') +
100
+ summaryRow('Tax', '<span>{{taxAmount}}</span>') +
101
+ summaryRow('Delivery', '<span>{{shippingAmount}}</span>') +
102
+ summaryRow('Total', '<span>{{totalAmount}}</span>') +
103
+ summaryRow('Paid by gift card', '<span>−</span><span>{{giftCardAmount}}</span>') +
104
+ summaryRow('Amount due', '<span>{{amountDue}}</span>', true) +
105
+ '</div>' +
106
+ '<div style="color: #4b5563; margin: 4px 0 0; font-size: 14px; line-height: 1.6"><span>If you have any questions about this invoice, contact us at </span><span>{{companyEmail}}</span><span>.</span></div>' +
107
+ '<p style="color: #4b5563; margin: 20px 0 2px; font-size: 14px; line-height: 1.6">Best regards,</p>' +
108
+ '<p style="color: #1f2937; margin: 0; font-size: 14px; font-weight: 600; line-height: 1.6">{{companyName}}</p>' +
109
+ '</div></div></div>'
110
+
111
+ // ORD-2 of the store this was found on: one 119.00 lei line paid by card, no
112
+ // delivery, discount, tax or gift card. The payment webhook marked it paid —
113
+ // `payment_status` and the `updated_at` that write stamps — then asked for the
114
+ // invoice with just the order id.
115
+ const PAID_CARD_ORDER: Row = {
116
+ id: 'order-2',
117
+ order_number: 'ORD-2',
118
+ currency: 'RON',
119
+ total_amount: 119,
120
+ shipping_amount: 0,
121
+ discount_amount: 0,
122
+ gift_card_amount: 0,
123
+ status: 'confirmed',
124
+ payment_status: 'paid',
125
+ payment_method: 'stripe',
126
+ payment_provider: 'stripe',
127
+ updated_at: '2026-10-23T09:15:00.000Z',
128
+ billing_name: 'Card Buyer',
129
+ }
130
+ const LINES: Row[] = [
131
+ { product_id: 'p1', product_name: 'Bouquet', quantity: 1, unit_price: 119, total_price: 119 },
132
+ ]
133
+ // The same basket, delivered and paid in cash at the door: invoiced when it is
134
+ // placed, before any money moved.
135
+ const CASH_ORDER: Row = {
136
+ ...PAID_CARD_ORDER,
137
+ id: 'order-3',
138
+ order_number: 'ORD-3',
139
+ total_amount: 134,
140
+ shipping_amount: 15,
141
+ status: 'pending',
142
+ payment_status: 'unpaid',
143
+ payment_method: 'cash_on_delivery',
144
+ payment_provider: '',
145
+ }
146
+
147
+ // The editor's default invoice template, reduced to the parts this covers: the
148
+ // <context> carrying the design canvas's sample (teleport-gui
149
+ // invoice-template-builder.ts), the dates under the number, the total, the
150
+ // notes and the seller's VAT number.
151
+ const SAMPLE_NOTES = 'Payment is due within 30 days. Thank you for your business.'
152
+ const boundText = (path: string) => ({
153
+ type: 'element',
154
+ content: {
155
+ elementType: 'text',
156
+ semanticType: 'p',
157
+ children: [{ type: 'expr', content: `invoiceData?.${path}` }],
158
+ },
159
+ })
160
+ const INVOICE_TEMPLATE = {
161
+ name: 'invoice-template',
162
+ node: {
163
+ type: 'element',
164
+ content: {
165
+ elementType: 'context',
166
+ attrs: {
167
+ Company: {
168
+ type: 'static',
169
+ content: { name: 'Acme Corporation', vat: 'US123456789', logoUrl: '' },
170
+ },
171
+ Invoice: {
172
+ type: 'static',
173
+ content: {
174
+ invoiceNumber: '#INV-001',
175
+ status: 'paid',
176
+ issueDate: 'Jan 15, 2025',
177
+ dueDate: 'Feb 14, 2025',
178
+ paidDate: 'Jan 20, 2025',
179
+ total: '371.49',
180
+ currency: 'USD',
181
+ notes: SAMPLE_NOTES,
182
+ },
183
+ },
184
+ },
185
+ children: [
186
+ boundText('Invoice?.invoiceNumber'),
187
+ boundText('Invoice?.issueDate'),
188
+ boundText('Invoice?.dueDate'),
189
+ boundText('Invoice?.total'),
190
+ boundText('Invoice?.notes'),
191
+ boundText('Invoice?.paidDate'),
192
+ boundText('Company?.vat'),
193
+ ],
194
+ },
195
+ },
196
+ }
197
+
198
+ const assembly = loadAssembly()
199
+ const pdfGenerator = loadPdfGenerator()
200
+
201
+ function invoiceFor(order: Row, body: Row = {}): Row {
202
+ const invoice = assembly.assembleInvoiceData(
203
+ { orderId: order.id, issueDate: '2026-10-23', ...body },
204
+ order,
205
+ LINES
206
+ )
207
+ return { ...invoice, invoiceNumber: 'INV-0002' }
208
+ }
209
+
210
+ function emailFor(invoice: Row, body = INVOICE_EMAIL_BODY): string {
211
+ return pdfGenerator.replacePlaceholders(body, pdfGenerator.buildDataContext(invoice))
212
+ }
213
+
214
+ describe('the invoice of a paid order', () => {
215
+ it('is issued paid, at the time the payment marked the order', () => {
216
+ const invoice = invoiceFor(PAID_CARD_ORDER)
217
+ expect(invoice.status).toBe('paid')
218
+ expect(invoice.paidAt).toBe('2026-10-23T09:15:00.000Z')
219
+ })
220
+
221
+ it('keeps the status and payment time a caller states', () => {
222
+ const invoice = invoiceFor(PAID_CARD_ORDER, { status: 'issued' })
223
+ expect(invoice.status).toBe('issued')
224
+ expect(invoice.paidAt).toBeNull()
225
+ const stated = invoiceFor(PAID_CARD_ORDER, { status: 'paid', paidAt: '2026-10-20T08:00:00Z' })
226
+ expect(stated.paidAt).toBe('2026-10-20T08:00:00Z')
227
+ })
228
+
229
+ it('emails no due date, no amount due and no zero lines', () => {
230
+ const email = emailFor(invoiceFor(PAID_CARD_ORDER))
231
+ expect(email).toContain('<span>Invoice number: </span><span>INV-0002</span>')
232
+ expect(email).toContain(
233
+ '<span>Invoice date: </span><span>' + printedDate('2026-10-23') + '</span>'
234
+ )
235
+ expect(email).toContain('<span>Subtotal: </span><span>lei 119.00</span>')
236
+ expect(email).toContain('<span>Total: </span><span>lei 119.00</span>')
237
+ for (const gone of ['Due date', 'Amount due', 'Discount', 'Tax', 'Delivery', 'gift card']) {
238
+ expect(email).not.toContain(gone)
239
+ }
240
+ // Only the rows went: the summary box and the rest of the message stay.
241
+ expect(email).toContain('<div style="margin: 0 0 22px">')
242
+ expect(email).toContain('Best regards,')
243
+ })
244
+
245
+ it('prints a paid line where the PDF printed the due date', () => {
246
+ const invoice = invoiceFor(PAID_CARD_ORDER)
247
+ const html = loadPdfGenerator(INVOICE_TEMPLATE).buildInvoiceHtml(invoice)
248
+ expect(html).toContain('<p>Paid on ' + printedDate('2026-10-23T09:15:00.000Z') + '</p>')
249
+ expect(html).not.toContain(printedDate(invoice.dueDate))
250
+ expect(html).toContain('<p>lei 119.00</p>')
251
+ // The built-in layout of a store without a template says so too.
252
+ expect(pdfGenerator.buildInvoiceHtml(invoice)).toContain(
253
+ 'Paid on ' + printedDate('2026-10-23T09:15:00.000Z')
254
+ )
255
+ })
256
+ })
257
+
258
+ describe('the invoice of a cash-on-delivery order', () => {
259
+ it('is issued unpaid and due in 30 days', () => {
260
+ const invoice = invoiceFor(CASH_ORDER)
261
+ expect(invoice.status).toBe('issued')
262
+ expect(invoice.paidAt).toBeNull()
263
+ expect(invoice.dueDate).toBe('2026-11-22')
264
+ })
265
+
266
+ it('emails its due date and the delivery, and no amount due without a gift card', () => {
267
+ const email = emailFor(invoiceFor(CASH_ORDER))
268
+ expect(email).toContain(
269
+ '<span>Due date: </span><span>' + printedDate('2026-11-22') + '</span></div>'
270
+ )
271
+ expect(email).toContain('<span>Delivery: </span><span>lei 15.00</span>')
272
+ expect(email).toContain('<span>Total: </span><span>lei 134.00</span>')
273
+ for (const gone of ['Amount due', 'Discount', 'Tax', 'gift card']) {
274
+ expect(email).not.toContain(gone)
275
+ }
276
+ })
277
+
278
+ it('keeps the due date on its PDF', () => {
279
+ const invoice = invoiceFor(CASH_ORDER)
280
+ const html = loadPdfGenerator(INVOICE_TEMPLATE).buildInvoiceHtml(invoice)
281
+ expect(html).toContain('<p>' + printedDate('2026-11-22') + '</p>')
282
+ expect(html).not.toContain('Paid on')
283
+ expect(pdfGenerator.buildInvoiceHtml(invoice)).not.toContain('Paid on')
284
+ })
285
+
286
+ it('emails what a gift card paid and what is left to pay at the door', () => {
287
+ const email = emailFor(invoiceFor({ ...CASH_ORDER, gift_card_amount: 50 }))
288
+ expect(email).toContain(
289
+ '<span>Paid by gift card: </span><span>−</span><span>lei 50.00</span></div>'
290
+ )
291
+ expect(email).toContain('<span>Amount due: </span><span>lei 84.00</span></div>')
292
+ expect(email).toContain('<span>Due date: </span>')
293
+ })
294
+
295
+ it('emails no amount due once the rest was paid by card', () => {
296
+ const email = emailFor(invoiceFor({ ...PAID_CARD_ORDER, gift_card_amount: 50 }))
297
+ expect(email).toContain('<span>Paid by gift card: </span><span>−</span><span>lei 50.00</span>')
298
+ expect(email).not.toContain('Amount due')
299
+ })
300
+ })
301
+
302
+ describe('the invoice email drops a zero line, never what holds it', () => {
303
+ const zeroDeliveryInvoice = invoiceFor(CASH_ORDER, { shippingAmount: 0 })
304
+
305
+ it("keeps a merchant's own block around the line it drops", () => {
306
+ const body =
307
+ '<div class="merchant-summary">' +
308
+ '<div class="row"><span>Delivery: </span><span>{{shippingAmount}}</span></div>' +
309
+ '<div class="row"><span>Total: </span><span>{{totalAmount}}</span></div>' +
310
+ '<p>Thank you for shopping with us.</p>' +
311
+ '</div>'
312
+ expect(emailFor(zeroDeliveryInvoice, body)).toBe(
313
+ '<div class="merchant-summary">' +
314
+ '<div class="row"><span>Total: </span><span>lei 119.00</span></div>' +
315
+ '<p>Thank you for shopping with us.</p>' +
316
+ '</div>'
317
+ )
318
+ })
319
+
320
+ it('keeps a block that holds more than the line', () => {
321
+ const withBlocks =
322
+ '<div class="note"><span>Delivery {{shippingAmount}}</span><p>See you soon.</p></div>'
323
+ expect(emailFor(zeroDeliveryInvoice, withBlocks)).toBe(
324
+ '<div class="note"><span>Delivery lei 0.00</span><p>See you soon.</p></div>'
325
+ )
326
+ const withFigures = '<div>Delivery {{shippingAmount}}, total {{totalAmount}}.</div>'
327
+ expect(emailFor(zeroDeliveryInvoice, withFigures)).toBe(
328
+ '<div>Delivery lei 0.00, total lei 119.00.</div>'
329
+ )
330
+ })
331
+ })
332
+
333
+ describe('money on the invoice', () => {
334
+ it.each([
335
+ ['RON', 'lei 119.00'],
336
+ ['CHF', 'CHF 119.00'],
337
+ ['USD', '$119.00'],
338
+ ['EUR', '€119.00'],
339
+ ])('writes %s as %s in the email and the PDF alike', (currency, printed) => {
340
+ const invoice = invoiceFor({ ...PAID_CARD_ORDER, currency })
341
+ expect(emailFor(invoice, '<span>{{totalAmount}}</span>')).toBe(`<span>${printed}</span>`)
342
+ expect(pdfGenerator.buildInvoiceDataScope(invoice).invoiceData.Invoice.total).toBe(printed)
343
+ })
344
+ })
345
+
346
+ describe("the invoice PDF never prints the template's sample", () => {
347
+ it('prints nothing for a field the real invoice leaves empty', () => {
348
+ const html = loadPdfGenerator(INVOICE_TEMPLATE).buildInvoiceHtml(
349
+ invoiceFor(CASH_ORDER, { notes: '' })
350
+ )
351
+ expect(html).not.toContain(SAMPLE_NOTES)
352
+ expect(html).not.toContain('Jan 20, 2025')
353
+ expect(html).not.toContain('US123456789')
354
+ expect(html).not.toContain('371.49')
355
+ expect(html).toContain('<p>#INV-0002</p>')
356
+ expect(html).toContain('<p>lei 134.00</p>')
357
+ })
358
+
359
+ it("prints the invoice's own notes", () => {
360
+ const html = loadPdfGenerator(INVOICE_TEMPLATE).buildInvoiceHtml(
361
+ invoiceFor(CASH_ORDER, { notes: 'Leave it with the neighbour.' })
362
+ )
363
+ expect(html).toContain('<p>Leave it with the neighbour.</p>')
364
+ })
365
+
366
+ it("keeps the company logo the editor wrote onto the template's context", () => {
367
+ const withLogo = {
368
+ ...INVOICE_TEMPLATE,
369
+ node: {
370
+ ...INVOICE_TEMPLATE.node,
371
+ content: {
372
+ ...INVOICE_TEMPLATE.node.content,
373
+ attrs: {
374
+ Company: { type: 'static', content: { name: '', logoUrl: 'https://cdn/logo.png' } },
375
+ },
376
+ },
377
+ },
378
+ }
379
+ const scope = loadPdfGenerator(withLogo).buildInvoiceDataScope(invoiceFor(CASH_ORDER))
380
+ expect(scope.invoiceData.Company.logoUrl).toBe('https://cdn/logo.png')
381
+ })
382
+ })
@@ -420,7 +420,9 @@ describe('invoice PDF renderer — vouchers and the gift-card tender', () => {
420
420
  expect(voucherEmail).not.toContain('Delivery')
421
421
  expect(voucherEmail).not.toContain('gift card')
422
422
  expect(voucherEmail).toContain('<tr><td>Discount</td><td>−$25.00</td></tr>')
423
- expect(voucherEmail).toContain('<tr><td>Amount due</td><td>$75.00</td></tr>')
423
+ // Without a gift card nothing is left beside the total, so, as on the PDF,
424
+ // there is no amount due.
425
+ expect(voucherEmail).not.toContain('Amount due')
424
426
 
425
427
  const giftCardEmail = pdfGenerator.replacePlaceholders(
426
428
  body,
@@ -434,10 +436,10 @@ describe('invoice PDF renderer — vouchers and the gift-card tender', () => {
434
436
  // A rich-text editor turns table rows into paragraphs; the same rule holds.
435
437
  expect(
436
438
  pdfGenerator.replacePlaceholders(
437
- '<p>Delivery {{shippingAmount}}</p><p>Due {{amountDue}}</p>',
439
+ '<p>Delivery {{shippingAmount}}</p><p>Total {{totalAmount}}</p>',
438
440
  pdfGenerator.buildDataContext(voucherInvoice)
439
441
  )
440
- ).toBe('<p>Due $75.00</p>')
442
+ ).toBe('<p>Total $75.00</p>')
441
443
  })
442
444
  })
443
445
 
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-assembly-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,aAAc,mBAAmB,KAAG,MAgiB3E,CAAA"}
1
+ {"version":3,"file":"invoice-assembly-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,aAAc,mBAAmB,KAAG,MAijB3E,CAAA"}
@@ -94,6 +94,16 @@ var CURRENCY_SYMBOLS = {
94
94
  'CLP': 'CLP$', 'PEN': 'S/.', 'VND': '\\u20AB', 'UAH': '\\u20B4',
95
95
  };
96
96
 
97
+ // \`teleport_orders.payment_status\` values that mean the order's money was
98
+ // collected — the editor's PAID_PAYMENT_STATUS_VALUES (teleport-gui
99
+ // e-commerce/constants/order-statuses.ts), provider spellings included.
100
+ var PAID_ORDER_PAYMENT_STATUSES = ['paid', 'partially_refunded', 'succeeded', 'success', 'completed', 'captured'];
101
+
102
+ function isPaidOrder(order) {
103
+ var paymentStatus = String((order && order.payment_status) || '').trim().toLowerCase();
104
+ return PAID_ORDER_PAYMENT_STATUSES.indexOf(paymentStatus) !== -1;
105
+ }
106
+
97
107
  function isLoopbackHost(host) {
98
108
  var name = String(host || '').replace(/:[0-9]+$/, '').toLowerCase();
99
109
  return name === 'localhost' || name === '127.0.0.1' || name === '[::1]';
@@ -474,15 +484,22 @@ function assembleInvoiceData(body, hydratedOrder, hydratedItems) {
474
484
  var fallbackPaymentIntentId = orderRow.payment_intent_id || '';
475
485
  var fallbackNotes = orderRow.notes || '';
476
486
 
487
+ // Paid or not, when the caller does not say: the order's money state. The
488
+ // payment webhook marks an order paid and then asks for its invoice with just
489
+ // the order id; a cash-on-delivery order is invoiced unpaid when it is placed.
490
+ // The orders table keeps no payment time of its own — the mark-paid write
491
+ // stamps \`updated_at\`, just before the webhook asks.
492
+ var paidByOrder = !body.status && isPaidOrder(orderRow);
493
+
477
494
  var invoiceData = {
478
495
  id: body.id || require('crypto').randomUUID(),
479
496
  // Numbered by the route: an invoice issued at payment takes the next
480
497
  // number, a regenerated one keeps its own.
481
498
  invoiceNumber: '',
482
- status: body.status || 'issued',
499
+ status: body.status || (paidByOrder ? 'paid' : 'issued'),
483
500
  issueDate: issueDate,
484
501
  dueDate: dueDate,
485
- paidAt: body.paidAt || null,
502
+ paidAt: body.paidAt || (paidByOrder ? orderRow.updated_at || new Date().toISOString() : null),
486
503
  customerName: body.customerName || fallbackCustomerName || '',
487
504
  customerEmail: body.customerEmail || fallbackCustomerEmail || '',
488
505
  customerAddress: body.customerAddress || fallbackCustomerAddress || '',
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-assembly-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":";;;AACA,iEAA4D;AAC5D,2EAAuE;AAEvE;;;;;;;;GAQG;AACI,MAAM,2BAA2B,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACnF,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,oBAAoB,GAAG,CAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,QAAQ;QACtD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAA;IAEV,OAAO;;;;;;;;yBAQgB,cAAc;;8BAET,kBAAkB;0BACtB,oBAAoB;EAC5C,qDAAyB;EACzB,gCAAc,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2gBlD,CAAA;AACD,CAAC,CAAA;AAhiBY,QAAA,2BAA2B,+BAgiBvC"}
1
+ {"version":3,"file":"invoice-assembly-code.js","sourceRoot":"","sources":["../../../src/invoice/invoice-assembly-code.ts"],"names":[],"mappings":";;;AACA,iEAA4D;AAC5D,2EAAuE;AAEvE;;;;;;;;GAQG;AACI,MAAM,2BAA2B,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACnF,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,oBAAoB,GAAG,CAAA,MAAA,QAAQ,CAAC,QAAQ,0CAAE,QAAQ;QACtD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAA;IAEV,OAAO;;;;;;;;yBAQgB,cAAc;;8BAET,kBAAkB;0BACtB,oBAAoB;EAC5C,qDAAyB;EACzB,gCAAc,CAAC,gCAAgC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4hBlD,CAAA;AACD,CAAC,CAAA;AAjjBY,QAAA,2BAA2B,+BAijBvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"invoice-html-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4HhE,eAAO,MAAM,uBAAuB,aACxB,mBAAmB,uBACR,OAAO,iBACb,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,KACxD,MA8gCF,CAAA"}
1
+ {"version":3,"file":"invoice-html-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/invoice-html-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4HhE,eAAO,MAAM,uBAAuB,aACxB,mBAAmB,uBACR,OAAO,iBACb,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,KACxD,MA2iCF,CAAA"}