@teleporthq/teleport-plugin-next-workflows 0.43.57 → 0.43.58

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.
@@ -90,8 +90,13 @@ describe('/api/invoices/generate — orderId hydration', () => {
90
90
  it('prefers body.items over hydrated items when both exist', () => {
91
91
  // Admin "regenerate invoice with custom items" use cases need
92
92
  // body.items to win. The condition checks length > 0 so an empty
93
- // explicit array still falls back to DB-hydrated items.
94
- expect(route).toContain('var items = Array.isArray(body.items) && body.items.length > 0')
93
+ // explicit array still falls back to DB-hydrated items. The predicate is
94
+ // named because the settle-wait below keys off it too — a caller that
95
+ // brought its own lines never waits for the DB rows to land.
96
+ expect(route).toContain(
97
+ 'var callerSuppliedItems = Array.isArray(body.items) && body.items.length > 0;'
98
+ )
99
+ expect(route).toContain('var items = callerSuppliedItems')
95
100
  })
96
101
 
97
102
  it('falls back to order.billing_* / shipping_* fields for customer data', () => {
@@ -0,0 +1,308 @@
1
+ import { generateInvoiceGenerateRouteCode } from '../src/invoice/api-routes-code'
2
+ import type { UIDLInvoiceSettings } from '@teleporthq/teleport-types'
3
+
4
+ // `/api/invoices/generate` used to invoice a HALF-WRITTEN order.
5
+ //
6
+ // The runtime `data-create-item` handler fire-and-forgets
7
+ // `/api/ecommerce/order-notification` the moment the `teleport_orders` row
8
+ // lands, and that route generates the invoice for every non-webhook payment
9
+ // method. But the checkout workflow writes `teleport_order_items` AFTER the
10
+ // order row, one HTTP round-trip per cart line — so the invoice endpoint was
11
+ // reading the line items while they were still being inserted.
12
+ //
13
+ // Observed in a real store: a 3-line order (4x Linen Table Runner, 3x Ceramic
14
+ // Dinner Plate Set, 2x Wireless Bluetooth Speaker, charged 474.45) produced an
15
+ // invoice with ONE line item and a total of 249.00 — the two rows that had not
16
+ // been inserted yet were simply absent, and nothing anywhere said so.
17
+ //
18
+ // These tests run the EMITTED handler against a data-access stub whose rows
19
+ // arrive progressively, which is what the race actually looks like.
20
+
21
+ const FAKE_SETTINGS = {
22
+ invoicePrefix: 'INV-',
23
+ defaultTaxRate: 10,
24
+ showDiscount: false,
25
+ taxIncludedInPrice: false,
26
+ companyDetails: {},
27
+ template: { document: null as unknown },
28
+ tables: { invoicesTable: 'teleport_invoices', invoiceItemsTable: 'teleport_invoice_items' },
29
+ emailDelivery: { enabled: false },
30
+ } as unknown as UIDLInvoiceSettings
31
+
32
+ interface OrderItemRow {
33
+ product_id: string
34
+ product_name: string
35
+ quantity: number
36
+ unit_price: number
37
+ total_price: number
38
+ }
39
+
40
+ const ORDER_ROW_BASE = {
41
+ id: 'order-1',
42
+ currency: 'USD',
43
+ total_amount: 474.45,
44
+ shipping_amount: 150,
45
+ billing_name: 'Jane Buyer',
46
+ billing_email: 'jane@example.com',
47
+ }
48
+
49
+ const ITEM_ROWS: OrderItemRow[] = [
50
+ {
51
+ product_id: 'p1',
52
+ product_name: 'Linen Table Runner',
53
+ quantity: 4,
54
+ unit_price: 22.5,
55
+ total_price: 90,
56
+ },
57
+ {
58
+ product_id: 'p2',
59
+ product_name: 'Ceramic Dinner Plate Set',
60
+ quantity: 3,
61
+ unit_price: 34.99,
62
+ total_price: 104.97,
63
+ },
64
+ {
65
+ product_id: 'p3',
66
+ product_name: 'Wireless Bluetooth Speaker',
67
+ quantity: 2,
68
+ unit_price: 49.99,
69
+ total_price: 99.98,
70
+ },
71
+ ]
72
+
73
+ interface HandlerRun {
74
+ status: number
75
+ payload: Record<string, unknown>
76
+ insertedItems: unknown[]
77
+ reads: number
78
+ }
79
+
80
+ /**
81
+ * Loads the emitted `pages/api/invoices/generate.js` as a real module and
82
+ * invokes its handler.
83
+ *
84
+ * `orderNumberAfterRead` controls when checkout's order-number backfill
85
+ * becomes visible — that write happens after the item loop in both the COD
86
+ * and the online-payment branch, so it is one of the endpoint's "the order is
87
+ * whole now" signals. `Infinity` models a flow that never sets one.
88
+ */
89
+ async function runHandler(options: {
90
+ body: Record<string, unknown>
91
+ /** Number of item rows visible on the Nth read (1-indexed reads). */
92
+ visibleItemsPerRead: (read: number) => number
93
+ orderNumberAfterRead?: number
94
+ }): Promise<HandlerRun> {
95
+ const code = generateInvoiceGenerateRouteCode(FAKE_SETTINGS)
96
+
97
+ let reads = 0
98
+ const insertedItems: unknown[] = []
99
+ const orderNumberAfterRead = options.orderNumberAfterRead ?? 1
100
+
101
+ const dataAccessStub = {
102
+ getOrderWithItems: async (orderId: string) => {
103
+ reads += 1
104
+ if (orderId !== ORDER_ROW_BASE.id) {
105
+ return null
106
+ }
107
+ return {
108
+ order: {
109
+ ...ORDER_ROW_BASE,
110
+ order_number: reads >= orderNumberAfterRead ? 'ORD-1' : null,
111
+ },
112
+ items: ITEM_ROWS.slice(0, options.visibleItemsPerRead(reads)),
113
+ }
114
+ },
115
+ getNextInvoiceNumber: async () => 1,
116
+ insertInvoice: async (invoiceData: Record<string, unknown>) => invoiceData,
117
+ insertInvoiceItems: async (_invoiceId: string, items: unknown[]) => {
118
+ insertedItems.push(...items)
119
+ return items
120
+ },
121
+ updateInvoice: async () => ({}),
122
+ }
123
+
124
+ const pdfGeneratorStub = {
125
+ generateInvoicePdf: async () => Buffer.from('%PDF-1.4 fake'),
126
+ COMPANY_DETAILS: {},
127
+ }
128
+
129
+ const stubRequire = ((id: string) => {
130
+ if (id.indexOf('data-access') !== -1) return dataAccessStub
131
+ if (id.indexOf('pdf-generator') !== -1) return pdfGeneratorStub
132
+ if (id === 'pg') return { Client: class {} }
133
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
134
+ return require(id)
135
+ }) as unknown as NodeRequire
136
+
137
+ const factory = new Function(
138
+ 'require',
139
+ 'module',
140
+ 'exports',
141
+ `${code}; return module.exports;`
142
+ ) as (
143
+ req: NodeRequire,
144
+ mod: { exports: unknown },
145
+ exp: unknown
146
+ ) => (req: unknown, res: unknown) => Promise<void>
147
+
148
+ const moduleObject = { exports: {} as unknown }
149
+ const handler = factory(stubRequire, moduleObject, moduleObject.exports)
150
+
151
+ let status = 0
152
+ let payload: Record<string, unknown> = {}
153
+ const res = {
154
+ status(statusCode: number) {
155
+ status = statusCode
156
+ return this
157
+ },
158
+ json(value: Record<string, unknown>) {
159
+ payload = value
160
+ return this
161
+ },
162
+ setHeader() {
163
+ /* not used by this route */
164
+ },
165
+ end() {
166
+ /* not used by this route */
167
+ },
168
+ }
169
+
170
+ await handler({ method: 'POST', headers: { host: 'localhost:3000' }, body: options.body }, res)
171
+
172
+ return { status, payload, insertedItems, reads }
173
+ }
174
+
175
+ describe('/api/invoices/generate — waits for the order to be fully written', () => {
176
+ const originalFetch = globalThis.fetch
177
+ const originalConnString = process.env.TELEPORT_DB_CONNECTION_STRING
178
+ const originalDatabaseUrl = process.env.DATABASE_URL
179
+
180
+ beforeAll(() => {
181
+ // The route self-fetches the runtime-storage proxy and mirrors onto
182
+ // `teleport_orders`; neither is under test and neither may reach the
183
+ // network from a unit test.
184
+ globalThis.fetch = (async () => {
185
+ throw new Error('runtime storage not configured in tests')
186
+ }) as unknown as typeof fetch
187
+ delete process.env.TELEPORT_DB_CONNECTION_STRING
188
+ delete process.env.DATABASE_URL
189
+ })
190
+
191
+ afterAll(() => {
192
+ globalThis.fetch = originalFetch
193
+ if (originalConnString !== undefined) {
194
+ process.env.TELEPORT_DB_CONNECTION_STRING = originalConnString
195
+ }
196
+ if (originalDatabaseUrl !== undefined) {
197
+ process.env.DATABASE_URL = originalDatabaseUrl
198
+ }
199
+ })
200
+
201
+ it('invoices every line of an order whose items are still being inserted', async () => {
202
+ // The exact shape of the reported bug: one row visible on the first read,
203
+ // the rest landing while the endpoint runs. `expectedItemCount` is what
204
+ // the order-notification route forwards from the cart it was handed.
205
+ const run = await runHandler({
206
+ body: { orderId: 'order-1', expectedItemCount: 3 },
207
+ visibleItemsPerRead: (read) => Math.min(read, 3),
208
+ orderNumberAfterRead: Infinity,
209
+ })
210
+
211
+ expect(run.status).toBe(200)
212
+ expect(run.insertedItems).toHaveLength(3)
213
+ expect(run.insertedItems.map((item) => (item as { name: string }).name)).toEqual([
214
+ 'Linen Table Runner',
215
+ 'Ceramic Dinner Plate Set',
216
+ 'Wireless Bluetooth Speaker',
217
+ ])
218
+ // 90 + 104.97 + 99.98 = 294.95 net, +10% VAT = 324.45, +150 delivery.
219
+ expect(run.payload.total).toBe(474.45)
220
+ })
221
+
222
+ it('accepts the order as whole once checkout has backfilled the order number', async () => {
223
+ // No `expectedItemCount` — the webhook-driven callers send only an
224
+ // orderId. `teleport_orders.order_number` is written after the item loop,
225
+ // so its presence is the endpoint's own proof that no line is missing.
226
+ const run = await runHandler({
227
+ body: { orderId: 'order-1' },
228
+ visibleItemsPerRead: (read) => Math.min(read, 3),
229
+ orderNumberAfterRead: 3,
230
+ })
231
+
232
+ expect(run.status).toBe(200)
233
+ expect(run.insertedItems).toHaveLength(3)
234
+ expect(run.reads).toBe(3)
235
+ })
236
+
237
+ it('does not poll an order that is already complete on the first read', async () => {
238
+ // The common case — every payment-webhook invoice. It must cost exactly
239
+ // one query and no added latency.
240
+ const run = await runHandler({
241
+ body: { orderId: 'order-1' },
242
+ visibleItemsPerRead: () => 3,
243
+ orderNumberAfterRead: 1,
244
+ })
245
+
246
+ expect(run.status).toBe(200)
247
+ expect(run.insertedItems).toHaveLength(3)
248
+ expect(run.reads).toBe(1)
249
+ })
250
+
251
+ it('does not wait at all when the caller supplied its own line items', async () => {
252
+ const run = await runHandler({
253
+ body: {
254
+ orderId: 'order-1',
255
+ items: [{ name: 'Handpicked line', quantity: 1, unitPrice: 10, totalPrice: 10 }],
256
+ },
257
+ visibleItemsPerRead: () => 0,
258
+ orderNumberAfterRead: Infinity,
259
+ })
260
+
261
+ expect(run.status).toBe(200)
262
+ expect(run.reads).toBe(1)
263
+ expect(run.insertedItems).toHaveLength(1)
264
+ expect((run.insertedItems[0] as { name: string }).name).toBe('Handpicked line')
265
+ })
266
+
267
+ it('still rejects an order that never gets any line items', async () => {
268
+ const run = await runHandler({
269
+ body: { orderId: 'order-1', expectedItemCount: 3 },
270
+ visibleItemsPerRead: () => 0,
271
+ orderNumberAfterRead: 1,
272
+ })
273
+
274
+ expect(run.status).toBe(400)
275
+ expect(run.payload.error).toBe('At least one item is required')
276
+ }, 15000)
277
+ })
278
+
279
+ describe('/api/invoices/generate — settle contract (emitted source)', () => {
280
+ const route = generateInvoiceGenerateRouteCode(FAKE_SETTINGS)
281
+
282
+ it('reads the line items through the settling helper, never a bare query', () => {
283
+ expect(route).toContain('async function hydrateOrderWhenSettled(orderId, expectedItemCount)')
284
+ expect(route).toContain('await hydrateOrderWhenSettled(body.orderId, expectedItemCount)')
285
+ })
286
+
287
+ it('treats a populated order_number as proof the item loop finished', () => {
288
+ // Checkout writes it in "Mark Order As Cash On Delivery Confirmed" /
289
+ // "Set Order Number Before Payment Redirect", both downstream of the loop.
290
+ expect(route).toContain('var orderNumber = hydrated.order.order_number;')
291
+ expect(route).toContain(
292
+ 'if (count > 0 && orderNumber != null && String(orderNumber).length > 0) {'
293
+ )
294
+ })
295
+
296
+ it('bounds the wait so a stuck order still produces an invoice', () => {
297
+ expect(route).toContain('var SETTLE_TIMEOUT_MS = 6000;')
298
+ expect(route).toContain('did not settle within ')
299
+ })
300
+
301
+ it('warns when the invoice total disagrees with the amount charged', () => {
302
+ // The tripwire that would have caught this the first time: the invoice is
303
+ // summed from line items, `teleport_orders.total_amount` is what the buyer
304
+ // actually paid, and a missing line makes them disagree.
305
+ expect(route).toContain('var orderTotalAmount = Number(orderShippingSource.total_amount);')
306
+ expect(route).toContain('[invoice] Total mismatch for order ')
307
+ })
308
+ })
@@ -1 +1 @@
1
- {"version":3,"file":"api-routes-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/api-routes-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE,eAAO,MAAM,gCAAgC,aAAc,mBAAmB,KAAG,MAqehF,CAAA;AAED,eAAO,MAAM,2BAA2B,QAAO,MAsD9C,CAAA"}
1
+ {"version":3,"file":"api-routes-code.d.ts","sourceRoot":"","sources":["../../../src/invoice/api-routes-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE,eAAO,MAAM,gCAAgC,aAAc,mBAAmB,KAAG,MAsmBhF,CAAA;AAED,eAAO,MAAM,2BAA2B,QAAO,MAsD9C,CAAA"}
@@ -93,6 +93,100 @@ async function uploadInvoicePdfToRuntimeStorage(pdfBuffer, fileName, baseUrl) {
93
93
  }
94
94
  }
95
95
 
96
+ // ---------------------------------------------------------------------------
97
+ // Waiting for the order's line items to be fully written.
98
+ //
99
+ // This endpoint is reached BEFORE checkout has finished writing the order.
100
+ // \`/api/ecommerce/order-notification\` is fire-and-forget'd by the runtime
101
+ // \`data-create-item\` handler the moment the \`teleport_orders\` row lands —
102
+ // which is upstream of the checkout workflow's "Loop Over Each Cart Item",
103
+ // the loop that inserts \`teleport_order_items\` one HTTP round-trip at a
104
+ // time. Hydrating on arrival therefore snapshots a HALF-WRITTEN order: a
105
+ // real 3-line order was invoiced with a single line and a total 225.45
106
+ // short of what the buyer was charged, because the other two rows were
107
+ // still in flight when the hydration query ran.
108
+ //
109
+ // So don't race it — wait for the order to settle, then hydrate. Three exit
110
+ // conditions, cheapest first:
111
+ //
112
+ // 1. \`expectedItemCount\` — the caller's own cart-line count — is reached.
113
+ // The order-notification route passes it, so the racing path is exact.
114
+ // 2. \`teleport_orders.order_number\` is populated. Checkout backfills the
115
+ // order number only AFTER the item loop has run to completion (the COD
116
+ // branch does it in "Mark Order As Cash On Delivery Confirmed", the
117
+ // online-payment branch in "Set Order Number Before Payment Redirect"),
118
+ // so a non-empty order number proves every line has been written.
119
+ // 3. The row count is identical across \`SETTLE_STABLE_READS\` consecutive
120
+ // reads — the fallback for orders written by a flow that never sets an
121
+ // order number.
122
+ //
123
+ // An order that is already complete — every webhook-driven call, and any
124
+ // hand-built admin call — satisfies (1) or (2) on the FIRST read, so the
125
+ // only request that ever pays for a poll is the one that would otherwise
126
+ // have shipped a wrong invoice.
127
+ var SETTLE_TIMEOUT_MS = 6000;
128
+ var SETTLE_POLL_MS = 400;
129
+ var SETTLE_STABLE_READS = 3;
130
+
131
+ function sleepMs(ms) {
132
+ return new Promise(function (resolve) { setTimeout(resolve, ms); });
133
+ }
134
+
135
+ async function hydrateOrderWhenSettled(orderId, expectedItemCount) {
136
+ var deadline = Date.now() + SETTLE_TIMEOUT_MS;
137
+ var lastCount = -1;
138
+ var repeats = 0;
139
+ var attempts = 0;
140
+
141
+ while (true) {
142
+ var hydrated = await dataAccess.getOrderWithItems(orderId);
143
+ attempts += 1;
144
+ if (!hydrated || !hydrated.order) {
145
+ // Nothing to wait for — the order id doesn't resolve. The caller logs
146
+ // this as a miss and falls back to its own body.* fields.
147
+ return hydrated;
148
+ }
149
+
150
+ var rows = Array.isArray(hydrated.items) ? hydrated.items : [];
151
+ var count = rows.length;
152
+
153
+ if (expectedItemCount > 0 && count >= expectedItemCount) {
154
+ if (attempts > 1) {
155
+ console.info('[invoice] Order ' + orderId + ' settled at ' + count + ' line(s) after ' + attempts + ' read(s) (expected ' + expectedItemCount + ')');
156
+ }
157
+ return hydrated;
158
+ }
159
+
160
+ var orderNumber = hydrated.order.order_number;
161
+ if (count > 0 && orderNumber != null && String(orderNumber).length > 0) {
162
+ if (attempts > 1) {
163
+ console.info('[invoice] Order ' + orderId + ' settled at ' + count + ' line(s) after ' + attempts + ' read(s) (order_number=' + orderNumber + ')');
164
+ }
165
+ return hydrated;
166
+ }
167
+
168
+ if (count === lastCount) {
169
+ repeats += 1;
170
+ } else {
171
+ lastCount = count;
172
+ repeats = 1;
173
+ }
174
+ if (count > 0 && repeats >= SETTLE_STABLE_READS) {
175
+ console.info('[invoice] Order ' + orderId + ' settled at ' + count + ' line(s) — row count stable across ' + repeats + ' reads');
176
+ return hydrated;
177
+ }
178
+
179
+ if (Date.now() >= deadline) {
180
+ console.warn('[invoice] Order ' + orderId + ' did not settle within ' + SETTLE_TIMEOUT_MS +
181
+ 'ms — invoicing the ' + count + ' line(s) visible now' +
182
+ (expectedItemCount > 0 ? ' (expected ' + expectedItemCount + ')' : ''));
183
+ return hydrated;
184
+ }
185
+
186
+ await sleepMs(SETTLE_POLL_MS);
187
+ }
188
+ }
189
+
96
190
  var CURRENCY_SYMBOLS = {
97
191
  'USD': '$', 'EUR': '\\u20AC', 'GBP': '\\u00A3', 'JPY': '\\u00A5',
98
192
  'CAD': 'C$', 'AUD': 'A$', 'CHF': 'CHF ', 'CNY': '\\u00A5',
@@ -129,11 +223,23 @@ module.exports = async function handler(req, res) {
129
223
  return;
130
224
  }
131
225
 
226
+ var callerSuppliedItems = Array.isArray(body.items) && body.items.length > 0;
227
+
228
+ // How many line items the caller believes this order has. Used only to
229
+ // stop \`hydrateOrderWhenSettled\` polling the moment the order is whole —
230
+ // never to reject or pad the invoice, so a wrong count can at worst make
231
+ // us wait a little longer and fall through to the other exit conditions.
232
+ var expectedItemCount = Number(body.expectedItemCount);
233
+ if (!isFinite(expectedItemCount) || expectedItemCount < 0) {
234
+ expectedItemCount = 0;
235
+ }
236
+
132
237
  console.info('[invoice] === /api/invoices/generate === baseUrl=' + __baseUrl);
133
238
  console.info('[invoice] Request received:', {
134
239
  orderId: body.orderId || null,
135
- hasBodyItems: Array.isArray(body.items) && body.items.length > 0,
240
+ hasBodyItems: callerSuppliedItems,
136
241
  bodyItemCount: Array.isArray(body.items) ? body.items.length : 0,
242
+ expectedItemCount: expectedItemCount,
137
243
  hasCustomerEmail: !!body.customerEmail,
138
244
  requestedCurrency: body.currency || null,
139
245
  });
@@ -148,11 +254,19 @@ module.exports = async function handler(req, res) {
148
254
  // provide wins, so hand-built admin-side calls (or future clients)
149
255
  // can still override the customer/company/notes/etc. without being
150
256
  // forced to round-trip the DB themselves.
257
+ //
258
+ // The line items are read through \`hydrateOrderWhenSettled\`, which waits
259
+ // for checkout to finish writing them — see the comment on that helper
260
+ // for why an unguarded read snapshots a half-written order. A caller that
261
+ // brought its own \`items\` array doesn't depend on the DB rows at all, so
262
+ // that path takes the plain single read and skips the wait entirely.
151
263
  var hydratedOrder = null;
152
264
  var hydratedItems = [];
153
265
  if (body.orderId) {
154
266
  try {
155
- var hydrated = await dataAccess.getOrderWithItems(body.orderId);
267
+ var hydrated = callerSuppliedItems
268
+ ? await dataAccess.getOrderWithItems(body.orderId)
269
+ : await hydrateOrderWhenSettled(body.orderId, expectedItemCount);
156
270
  if (hydrated && hydrated.order) {
157
271
  hydratedOrder = hydrated.order;
158
272
  hydratedItems = Array.isArray(hydrated.items) ? hydrated.items : [];
@@ -167,7 +281,7 @@ module.exports = async function handler(req, res) {
167
281
  console.info('[invoice] No orderId in request — skipping DB hydration, using body.* fields verbatim');
168
282
  }
169
283
 
170
- var items = Array.isArray(body.items) && body.items.length > 0
284
+ var items = callerSuppliedItems
171
285
  ? body.items
172
286
  : hydratedItems.map(function (row) {
173
287
  return {
@@ -254,6 +368,21 @@ module.exports = async function handler(req, res) {
254
368
  }
255
369
  var total = goodsTotal + shippingAmount;
256
370
 
371
+ // Cross-check against the amount the order was actually placed for. The
372
+ // invoice is built from line items, the order carries the charged total,
373
+ // and the two must agree — when they don't, the invoice is being written
374
+ // from an incomplete set of lines (exactly what a lost race against
375
+ // checkout's item loop looks like). Log-only: a mismatch must never stop
376
+ // the merchant getting an invoice, but it has to be visible in the
377
+ // function logs instead of silently shipping a short bill.
378
+ var orderTotalAmount = Number(orderShippingSource.total_amount);
379
+ if (isFinite(orderTotalAmount) && orderTotalAmount > 0 && Math.abs(orderTotalAmount - total) > 0.02) {
380
+ console.warn('[invoice] Total mismatch for order ' + (body.orderId || '(none)') +
381
+ ' — invoice total ' + (Math.round(total * 100) / 100) +
382
+ ' vs teleport_orders.total_amount ' + orderTotalAmount +
383
+ ' across ' + items.length + ' line item(s). The invoice may be missing lines.');
384
+ }
385
+
257
386
  var nextNumber = await dataAccess.getNextInvoiceNumber(INVOICE_PREFIX);
258
387
  var invoiceNumber = INVOICE_PREFIX + String(nextNumber).padStart(4, '0');
259
388
 
@@ -1 +1 @@
1
- {"version":3,"file":"api-routes-code.js","sourceRoot":"","sources":["../../../src/invoice/api-routes-code.ts"],"names":[],"mappings":";;;AAEO,MAAM,gCAAgC,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,IAAI,MAAM,CAAA;IAC/C,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,KAAK,IAAI,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,YAAY,GAAG,QAAQ,CAAC,qBAAqB,KAAK,KAAK,CAAA;IAC7D,MAAM,YAAY,GAAG,CAAA,MAAA,QAAQ,CAAC,aAAa,0CAAE,OAAO,MAAK,IAAI,CAAA;IAC7D,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;;;;;;;;;EASP,YAAY,CAAC,CAAC,CAAC,oEAAoE,CAAC,CAAC,CAAC,EAAE;;uBAEnE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;yBACpB,cAAc;;sBAEjB,YAAY;8BACJ,kBAAkB;0BACtB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmZ5C,YAAY;QACV,CAAC,CAAC;;;;;;;;;;;;;;;CAeL;QACG,CAAC,CAAC;;CAGN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,CAAA;AACD,CAAC,CAAA;AAreY,QAAA,gCAAgC,oCAqe5C;AAEM,MAAM,2BAA2B,GAAG,GAAW,EAAE;IACtD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDR,CAAA;AACD,CAAC,CAAA;AAtDY,QAAA,2BAA2B,+BAsDvC"}
1
+ {"version":3,"file":"api-routes-code.js","sourceRoot":"","sources":["../../../src/invoice/api-routes-code.ts"],"names":[],"mappings":";;;AAEO,MAAM,gCAAgC,GAAG,CAAC,QAA6B,EAAU,EAAE;;IACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,IAAI,MAAM,CAAA;IAC/C,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAA;IACnD,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,KAAK,IAAI,CAAA;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAA;IAC/D,MAAM,YAAY,GAAG,QAAQ,CAAC,qBAAqB,KAAK,KAAK,CAAA;IAC7D,MAAM,YAAY,GAAG,CAAA,MAAA,QAAQ,CAAC,aAAa,0CAAE,OAAO,MAAK,IAAI,CAAA;IAC7D,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;;;;;;;;;EASP,YAAY,CAAC,CAAC,CAAC,oEAAoE,CAAC,CAAC,CAAC,EAAE;;uBAEnE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;yBACpB,cAAc;;sBAEjB,YAAY;8BACJ,kBAAkB;0BACtB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAohB5C,YAAY;QACV,CAAC,CAAC;;;;;;;;;;;;;;;CAeL;QACG,CAAC,CAAC;;CAGN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCC,CAAA;AACD,CAAC,CAAA;AAtmBY,QAAA,gCAAgC,oCAsmB5C;AAEM,MAAM,2BAA2B,GAAG,GAAW,EAAE;IACtD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDR,CAAA;AACD,CAAC,CAAA;AAtDY,QAAA,2BAA2B,+BAsDvC"}