@tallyui/pos 3.2.0 → 3.3.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/dist/index.d.ts +122 -7
- package/dist/index.js +483 -47
- package/package.json +6 -6
- package/src/index.ts +5 -1
- package/src/order/index.ts +4 -0
- package/src/order/order-builder.ts +116 -14
- package/src/order/order-drafts.ts +6 -0
- package/src/order/types.ts +33 -3
- package/src/order/woocommerce-tax.ts +68 -0
- package/src/outbox/index.ts +1 -1
- package/src/outbox/order-outbox.ts +5 -3
- package/src/outbox/types.ts +8 -1
- package/src/pos-order/command.ts +21 -6
- package/src/pos-order/finalize.ts +38 -4
- package/src/pos-order/schema.ts +41 -5
- package/src/pos-order/types.ts +9 -4
- package/src/receipt/build-receipt-data.ts +8 -1
- package/src/receipt/types.ts +2 -0
- package/src/register/closure-document.ts +1 -1
- package/src/register/session-store.ts +2 -2
- package/src/sale/cart.ts +1 -0
- package/src/sale/use-sale.ts +50 -6
- package/src/store-settings/map-store-settings.ts +5 -1
- package/src/tax/exact.ts +3 -1
- package/src/tax/index.ts +1 -1
- package/src/tax/tax-provider.tsx +7 -4
- package/src/tax/types.ts +4 -1
package/dist/index.js
CHANGED
|
@@ -109,6 +109,84 @@ function useCurrencyCode() {
|
|
|
109
109
|
return ctx.currencyCode;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// src/order/woocommerce-tax.ts
|
|
113
|
+
import { minorUnitDigits, woocommerceTax } from "@tallyui/core";
|
|
114
|
+
function woocommerceLine(amountMinor, rates, inclusive, currency, roundAtSubtotal, pricesIncludeTax = inclusive) {
|
|
115
|
+
const dp = minorUnitDigits(currency), factor = 10 ** dp;
|
|
116
|
+
const precision = woocommerceTax.getRoundingPrecision(dp);
|
|
117
|
+
const input = {
|
|
118
|
+
amount: amountMinor / factor,
|
|
119
|
+
rates: rates.map((rate) => ({ ...rate, order: rate.priority })),
|
|
120
|
+
amountIncludesTax: inclusive,
|
|
121
|
+
dp,
|
|
122
|
+
perRatePrecision: precision
|
|
123
|
+
};
|
|
124
|
+
let result = woocommerceTax.calculateTaxes(input);
|
|
125
|
+
const net = woocommerceTax.roundHalfUp(input.amount - (inclusive ? result.total : 0), precision);
|
|
126
|
+
if (inclusive && rates.length) result = woocommerceTax.calculateTaxes({ ...input, amount: net, amountIncludesTax: false });
|
|
127
|
+
const taxLines = result.taxes.map((tax2) => {
|
|
128
|
+
const rate = rates.find((rate2) => rate2.id === tax2.id);
|
|
129
|
+
return {
|
|
130
|
+
code: rate.code,
|
|
131
|
+
rateId: rate.id,
|
|
132
|
+
compound: rate.compound,
|
|
133
|
+
ratePpm: Math.round(Number(rate.rate) * 1e4),
|
|
134
|
+
taxMicros: String(Math.round(tax2.total * factor * 1e6))
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
const tax = result.taxes.reduce((sum, rate) => sum + (roundAtSubtotal ? rate.total : woocommerceTax.roundTaxTotal(rate.total, dp, pricesIncludeTax)), 0);
|
|
138
|
+
return {
|
|
139
|
+
netMinor: Math.round(woocommerceTax.roundHalfUp(net, dp) * factor),
|
|
140
|
+
netMicros: String(Math.round(net * factor * 1e6)),
|
|
141
|
+
totalMinor: Math.round(woocommerceTax.roundHalfUp(net + tax, dp) * factor),
|
|
142
|
+
taxLines,
|
|
143
|
+
taxMicros: taxLines.reduce((sum, rate) => sum + BigInt(rate.taxMicros), 0n).toString()
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function woocommerceTotals(lines, fees, shipping, currency, roundAtSubtotal, inclusive) {
|
|
147
|
+
const dp = minorUnitDigits(currency), factor = 10 ** dp;
|
|
148
|
+
const net = (line) => Number(line.netMicros) / (factor * 1e6);
|
|
149
|
+
const productNet = lines.reduce((sum, line) => sum + (roundAtSubtotal ? net(line) : woocommerceTax.roundHalfUp(net(line), dp)), 0);
|
|
150
|
+
const amount = productNet + fees.reduce((sum, line) => sum + net(line), 0) + shipping.reduce((sum, line) => sum + woocommerceTax.roundHalfUp(net(line), dp), 0);
|
|
151
|
+
const taxes = (items) => woocommerceTax.roundHalfUp(items.reduce((sum, line) => line.taxLines.reduce((sum2, tax2) => sum2 + (roundAtSubtotal ? Number(tax2.taxMicros) / (factor * 1e6) : woocommerceTax.roundTaxTotal(Number(tax2.taxMicros) / (factor * 1e6), dp, inclusive)), sum), 0), woocommerceTax.getRoundingPrecision(dp));
|
|
152
|
+
const tax = woocommerceTax.roundHalfUp(taxes([...lines, ...fees]) + taxes(shipping), 6);
|
|
153
|
+
return {
|
|
154
|
+
subtotalMinor: Math.round(woocommerceTax.roundHalfUp(productNet, dp) * factor),
|
|
155
|
+
taxMinor: Math.round((roundAtSubtotal ? woocommerceTax.roundHalfUp(tax, dp) : woocommerceTax.roundTaxTotal(tax, dp, inclusive)) * factor),
|
|
156
|
+
totalMinor: Math.round(woocommerceTax.roundHalfUp(woocommerceTax.roundHalfUp(amount + tax, 6), dp) * factor)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function woocommerceTaxByRate(lines, roundAtSubtotal, taxLabels, orderTaxMinor) {
|
|
160
|
+
const groups = /* @__PURE__ */ new Map();
|
|
161
|
+
for (const line of lines) for (const tax of line.taxLines) {
|
|
162
|
+
const key = JSON.stringify([tax.rateId, tax.code, tax.ratePpm]);
|
|
163
|
+
const row = groups.get(key) ?? {
|
|
164
|
+
code: tax.code,
|
|
165
|
+
ratePpm: tax.ratePpm,
|
|
166
|
+
label: taxLabels?.[tax.ratePpm] ?? `Tax ${tax.ratePpm / 1e4}%`,
|
|
167
|
+
netMinor: 0,
|
|
168
|
+
amountMinor: 0,
|
|
169
|
+
amount: 0,
|
|
170
|
+
inclusive: line.taxInclusive
|
|
171
|
+
};
|
|
172
|
+
const amount = Number(tax.taxMicros) / 1e6;
|
|
173
|
+
row.amount += roundAtSubtotal ? amount : woocommerceTax.roundTaxTotal(amount, 0, line.taxInclusive);
|
|
174
|
+
row.netMinor += line.netMinor;
|
|
175
|
+
groups.set(key, row);
|
|
176
|
+
}
|
|
177
|
+
const rows = [...groups.values()].map(({ amount, inclusive, ...row }) => ({
|
|
178
|
+
...row,
|
|
179
|
+
amountMinor: woocommerceTax.roundTaxTotal(woocommerceTax.roundHalfUp(amount, 6), 0, inclusive)
|
|
180
|
+
}));
|
|
181
|
+
if (orderTaxMinor !== void 0 && rows.length) {
|
|
182
|
+
const residue = orderTaxMinor - rows.reduce((sum, row) => sum + row.amountMinor, 0);
|
|
183
|
+
const bound = Math.floor(rows.length / 2) + 1;
|
|
184
|
+
if (Math.abs(residue) > bound) throw new Error(`woocommerce tax by rate: residue ${residue} exceeds its bound ${bound} (ADR-076)`);
|
|
185
|
+
rows[rows.length - 1].amountMinor += residue;
|
|
186
|
+
}
|
|
187
|
+
return rows;
|
|
188
|
+
}
|
|
189
|
+
|
|
112
190
|
// src/tax/exact.ts
|
|
113
191
|
var MICROS_PER_MINOR = 1000000n;
|
|
114
192
|
var DEFAULT_TAX_ROUNDING = { granularity: "per_order", mode: "half_away_from_zero" };
|
|
@@ -211,6 +289,7 @@ function roundedTaxByRate(lines, rounding) {
|
|
|
211
289
|
return { baseMinor: Number(baseMinor), taxMinor: rows.reduce((sum, row) => sum + row.amountMinor, 0), rates: rows };
|
|
212
290
|
}
|
|
213
291
|
function taxLinesByRate(lines, orderTaxMinor, taxLabels, rounding) {
|
|
292
|
+
if (rounding?.granularity === "woocommerce" && lines.every((line) => line.netMicros !== void 0)) return woocommerceTaxByRate(lines, rounding.roundAtSubtotal, taxLabels, orderTaxMinor);
|
|
214
293
|
const label = (ratePpm) => taxLabels?.[ratePpm] ?? `Tax ${ratePpm / 1e4}%`;
|
|
215
294
|
const rounded = roundedTaxByRate(lines, rounding);
|
|
216
295
|
if (rounded) return rounded.rates.map(({ code, ratePpm, netMinor, amountMinor }) => ({ label: label(ratePpm), code, ratePpm, netMinor, amountMinor }));
|
|
@@ -244,10 +323,11 @@ function taxLinesByRate(lines, orderTaxMinor, taxLabels, rounding) {
|
|
|
244
323
|
|
|
245
324
|
// src/tax/tax-provider.tsx
|
|
246
325
|
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
326
|
+
import { woocommerceTax as woocommerceTax2 } from "@tallyui/core";
|
|
247
327
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
248
328
|
var taxLogger = createLogger("tax");
|
|
249
329
|
var TaxCtx = createContext2(null);
|
|
250
|
-
function TaxProvider({ ratesPpm, pricesIncludeTax, rounding, rateCodes, children }) {
|
|
330
|
+
function TaxProvider({ ratesPpm, pricesIncludeTax, rounding, rateCodes, taxRates, children }) {
|
|
251
331
|
const value = useMemo2(
|
|
252
332
|
() => {
|
|
253
333
|
if (Object.values(ratesPpm).some((rate) => !Number.isSafeInteger(rate) || rate < 0)) {
|
|
@@ -255,6 +335,7 @@ function TaxProvider({ ratesPpm, pricesIncludeTax, rounding, rateCodes, children
|
|
|
255
335
|
}
|
|
256
336
|
const unknown = /* @__PURE__ */ new Set();
|
|
257
337
|
return {
|
|
338
|
+
...taxRates ? { getTaxRates: (taxClass, options) => (taxRates[woocommerceTax2.normalizeTaxClass(taxClass)] ?? []).filter((rate) => !options?.shipping || rate.shipping) } : {},
|
|
258
339
|
getTaxRatePpm: (taxClass) => {
|
|
259
340
|
if (taxClass !== void 0 && Object.hasOwn(ratesPpm, taxClass)) return ratesPpm[taxClass];
|
|
260
341
|
if (taxClass !== void 0 && !unknown.has(taxClass)) {
|
|
@@ -272,7 +353,7 @@ function TaxProvider({ ratesPpm, pricesIncludeTax, rounding, rateCodes, children
|
|
|
272
353
|
...rounding ? { rounding } : {}
|
|
273
354
|
};
|
|
274
355
|
},
|
|
275
|
-
[ratesPpm, pricesIncludeTax, rounding, rateCodes]
|
|
356
|
+
[ratesPpm, pricesIncludeTax, rounding, rateCodes, taxRates]
|
|
276
357
|
);
|
|
277
358
|
return /* @__PURE__ */ jsx2(TaxCtx.Provider, { value, children });
|
|
278
359
|
}
|
|
@@ -319,7 +400,16 @@ function withPricingContext(context, settings) {
|
|
|
319
400
|
}
|
|
320
401
|
function taxProviderProps(settings) {
|
|
321
402
|
const rounding = settings.taxRounding?.granularity === "custom" ? void 0 : settings.taxRounding;
|
|
322
|
-
return {
|
|
403
|
+
return {
|
|
404
|
+
ratesPpm: settings.taxRatesPpm,
|
|
405
|
+
pricesIncludeTax: settings.pricesIncludeTax,
|
|
406
|
+
...rounding && { rounding },
|
|
407
|
+
...settings.taxRateCodes && { rateCodes: settings.taxRateCodes },
|
|
408
|
+
...settings.taxRates && { taxRates: settings.taxRates },
|
|
409
|
+
...settings.taxRoundAtSubtotal !== void 0 && { taxRoundAtSubtotal: settings.taxRoundAtSubtotal },
|
|
410
|
+
...settings.shippingTaxClass !== void 0 && { shippingTaxClass: settings.shippingTaxClass },
|
|
411
|
+
...settings.taxClassSlugs && { taxClassSlugs: settings.taxClassSlugs }
|
|
412
|
+
};
|
|
323
413
|
}
|
|
324
414
|
|
|
325
415
|
// src/store-settings/use-store-settings.ts
|
|
@@ -522,7 +612,16 @@ function createOrderBuilder(options) {
|
|
|
522
612
|
const { taxContext } = options;
|
|
523
613
|
const currency = options.currency.toUpperCase();
|
|
524
614
|
const orderId = options.id ?? uid();
|
|
615
|
+
const woo = taxContext.rounding?.granularity === "woocommerce" && taxContext.getTaxRates ? taxContext.rounding : void 0;
|
|
616
|
+
const wooLine = (amount, taxClass, inclusive, untaxed = false, shipping2 = false) => woocommerceLine(amount, untaxed ? [] : taxContext.getTaxRates(taxClass, { shipping: shipping2 }), inclusive, currency, woo.roundAtSubtotal, taxContext.pricesIncludeTax);
|
|
617
|
+
const displayAmount = (line, amount) => {
|
|
618
|
+
if (!woo || line.taxInclusive === taxContext.pricesIncludeTax) return toDisplayMode(line, amount, taxContext.pricesIncludeTax);
|
|
619
|
+
const item = wooLine(amount, line.taxClass, line.taxInclusive, line.taxStatus === "none");
|
|
620
|
+
return taxContext.pricesIncludeTax ? item.totalMinor : item.netMinor;
|
|
621
|
+
};
|
|
525
622
|
let lineItems = [];
|
|
623
|
+
let fees = [];
|
|
624
|
+
let shipping = [];
|
|
526
625
|
let orderDiscounts = [];
|
|
527
626
|
let payments = [];
|
|
528
627
|
let customer = null;
|
|
@@ -540,6 +639,13 @@ function createOrderBuilder(options) {
|
|
|
540
639
|
const lineDiscountMinor = Math.min(grossMinor, recalcedDiscounts.reduce((sum, d) => sum + d.amountMinor, 0));
|
|
541
640
|
const discountMinor = lineDiscountMinor + orderDiscountMinor;
|
|
542
641
|
const netMinor = grossMinor - discountMinor;
|
|
642
|
+
if (woo) return {
|
|
643
|
+
...line,
|
|
644
|
+
discounts: recalcedDiscounts,
|
|
645
|
+
discountMinor,
|
|
646
|
+
orderDiscountMinor,
|
|
647
|
+
...wooLine(netMinor, line.taxClass, line.taxInclusive, line.taxStatus === "none")
|
|
648
|
+
};
|
|
543
649
|
const combinedRate = line.taxLines.reduce((sum, tax) => sum + tax.ratePpm, 0);
|
|
544
650
|
const inclusiveTax = line.taxInclusive ? taxMicros(netMinor, combinedRate, true) : 0n;
|
|
545
651
|
let allocatedTax = 0n;
|
|
@@ -566,7 +672,7 @@ function createOrderBuilder(options) {
|
|
|
566
672
|
return Math.max(0, Math.min(discount.value, base));
|
|
567
673
|
}
|
|
568
674
|
function buildOrder() {
|
|
569
|
-
const lineAmounts = lineItems.map((li) => li.netMinor);
|
|
675
|
+
const lineAmounts = lineItems.map((li) => woo ? li.unitPriceMinor * li.quantity - li.discountMinor : li.netMinor);
|
|
570
676
|
const base = lineAmounts.reduce((sum, amount) => sum + Math.max(0, amount), 0);
|
|
571
677
|
let remaining = base;
|
|
572
678
|
const recalcedOrderDiscounts = orderDiscounts.map((d) => {
|
|
@@ -576,8 +682,16 @@ function createOrderBuilder(options) {
|
|
|
576
682
|
});
|
|
577
683
|
const shares = allocateOrderDiscount(lineAmounts, recalcedOrderDiscounts.reduce((sum, d) => sum + d.amountMinor, 0));
|
|
578
684
|
const lines = lineItems.map((li, index) => recalculateLine(li, shares[index]));
|
|
579
|
-
const
|
|
580
|
-
|
|
685
|
+
const wooTotals = woo ? woocommerceTotals(lines, fees, shipping, currency, woo.roundAtSubtotal, taxContext.pricesIncludeTax) : void 0;
|
|
686
|
+
const { subtotalMinor } = wooTotals ?? sumLines(lines, taxContext.rounding);
|
|
687
|
+
const charges = [...fees, ...shipping];
|
|
688
|
+
const taxedLines = [...lines, ...charges.map((charge) => ({
|
|
689
|
+
...charge,
|
|
690
|
+
taxInclusive: taxContext.pricesIncludeTax,
|
|
691
|
+
taxMicros: charge.taxLines.reduce((sum, tax) => sum + BigInt(tax.taxMicros), 0n).toString()
|
|
692
|
+
}))];
|
|
693
|
+
const { taxMinor, totalMinor } = wooTotals ?? sumLines(taxedLines, taxContext.rounding);
|
|
694
|
+
if (taxContext.rounding?.granularity === "per_rate_group_items" && taxedLines.some((li) => li.taxInclusive) && !perRateGroupWarned.has(taxContext)) {
|
|
581
695
|
perRateGroupWarned.add(taxContext);
|
|
582
696
|
taxLogger.warn("per_rate_group_items with inclusive lines: using per_order figures until the display has a rounding row (#287, #310)");
|
|
583
697
|
}
|
|
@@ -588,11 +702,11 @@ function createOrderBuilder(options) {
|
|
|
588
702
|
const discounts = returned ? [] : li.discounts.map((d) => ({
|
|
589
703
|
discountId: d.id,
|
|
590
704
|
...d.label !== void 0 ? { label: d.label } : {},
|
|
591
|
-
amountMinor:
|
|
705
|
+
amountMinor: displayAmount(li, d.amountMinor)
|
|
592
706
|
}));
|
|
593
|
-
const shareMinor =
|
|
594
|
-
const remainingMinor = toDisplayMode(li, li.netMinor, taxInclusive);
|
|
595
|
-
const amountMinor = li.taxInclusive === taxInclusive && !returned ? li.unitPriceMinor * li.quantity : discounts.reduce((sum, d) => sum + d.amountMinor, remainingMinor + shareMinor);
|
|
707
|
+
const shareMinor = displayAmount(li, li.orderDiscountMinor);
|
|
708
|
+
const remainingMinor = woo ? taxInclusive ? li.totalMinor : li.netMinor : toDisplayMode(li, li.netMinor, taxInclusive);
|
|
709
|
+
const amountMinor = !woo && li.taxInclusive === taxInclusive && !returned ? li.unitPriceMinor * li.quantity : discounts.reduce((sum, d) => sum + d.amountMinor, remainingMinor + shareMinor);
|
|
596
710
|
return { line: { lineId: li.id, amountMinor, discounts }, shareMinor, remainingMinor, returned };
|
|
597
711
|
});
|
|
598
712
|
const displayLines = figures.map((f) => f.line);
|
|
@@ -601,8 +715,17 @@ function createOrderBuilder(options) {
|
|
|
601
715
|
(sum, line) => line.discounts.reduce((lineSum, d) => lineSum + d.amountMinor, sum),
|
|
602
716
|
orderDiscountDisplay
|
|
603
717
|
);
|
|
604
|
-
const
|
|
605
|
-
const
|
|
718
|
+
const chargeRows = (items) => items.map(({ id: id2, name, amountMinor, netMinor, totalMinor: totalMinor2 }) => ({ id: id2, name, amountMinor: woo ? taxInclusive ? totalMinor2 : netMinor : amountMinor }));
|
|
719
|
+
const displayFees = chargeRows(fees), displayShipping = chargeRows(shipping);
|
|
720
|
+
let displaySubtotal = (taxInclusive ? totalMinor : totalMinor - taxMinor) + displayDiscount - [...displayFees, ...displayShipping].reduce((sum, charge) => sum + charge.amountMinor, 0);
|
|
721
|
+
let residue = displaySubtotal - displayLines.reduce((sum, line) => sum + line.amountMinor, 0);
|
|
722
|
+
if (woo) {
|
|
723
|
+
const charge = displayShipping.at(-1) ?? displayFees.at(-1);
|
|
724
|
+
const last = charge ?? displayLines.at(-1);
|
|
725
|
+
if (last) last.amountMinor += residue;
|
|
726
|
+
if (charge) displaySubtotal -= residue;
|
|
727
|
+
residue = displaySubtotal - displayLines.reduce((sum, line) => sum + line.amountMinor, 0);
|
|
728
|
+
}
|
|
606
729
|
const converted = lines.flatMap((li, index) => li.taxInclusive === taxInclusive ? [] : [index]);
|
|
607
730
|
const conversions = converted.reduce((count, index) => count + [
|
|
608
731
|
lines[index].netMinor,
|
|
@@ -622,7 +745,7 @@ function createOrderBuilder(options) {
|
|
|
622
745
|
if (displayDiscount < 0) throw new Error(`Display discount ${displayDiscount} is negative (ADR-063)`);
|
|
623
746
|
for (const { line, shareMinor, returned } of figures) {
|
|
624
747
|
if (returned && line.discounts.length > 0) throw new Error(`Return line ${line.lineId} shows discount rows (ADR-063)`);
|
|
625
|
-
if (returned) continue;
|
|
748
|
+
if (returned || woo) continue;
|
|
626
749
|
const rows = line.discounts.reduce((sum, d) => sum + d.amountMinor, 0);
|
|
627
750
|
if (shareMinor < 0 || line.discounts.some((d) => d.amountMinor < 0) || line.amountMinor < rows + shareMinor) {
|
|
628
751
|
throw new Error(`Display line ${line.lineId} shows ${line.amountMinor}, less than its rows ${rows} and share ${shareMinor} (ADR-063)`);
|
|
@@ -635,7 +758,9 @@ function createOrderBuilder(options) {
|
|
|
635
758
|
taxMinor,
|
|
636
759
|
totalMinor,
|
|
637
760
|
lines: displayLines,
|
|
638
|
-
orderDiscountMinor: orderDiscountDisplay
|
|
761
|
+
orderDiscountMinor: orderDiscountDisplay,
|
|
762
|
+
...fees.length ? { fees: displayFees } : {},
|
|
763
|
+
...shipping.length ? { shipping: displayShipping } : {}
|
|
639
764
|
};
|
|
640
765
|
const paidMinor = payments.reduce((sum, p) => sum + p.amountMinor, 0);
|
|
641
766
|
const balanceDueMinor = Math.max(0, totalMinor - paidMinor);
|
|
@@ -644,6 +769,8 @@ function createOrderBuilder(options) {
|
|
|
644
769
|
id: orderId,
|
|
645
770
|
status: "draft",
|
|
646
771
|
lineItems: lines,
|
|
772
|
+
...fees.length ? { fees: [...fees] } : {},
|
|
773
|
+
...shipping.length ? { shipping: [...shipping] } : {},
|
|
647
774
|
discounts: recalcedOrderDiscounts,
|
|
648
775
|
payments: [...payments],
|
|
649
776
|
customer,
|
|
@@ -666,17 +793,64 @@ function createOrderBuilder(options) {
|
|
|
666
793
|
function emit() {
|
|
667
794
|
subject.next(buildOrder());
|
|
668
795
|
}
|
|
796
|
+
function recalculateCharge(id2, input, shipping2 = false) {
|
|
797
|
+
if (!Number.isInteger(input.amountMinor)) throw new RangeError("Amount must be integer minor units");
|
|
798
|
+
if (input.amountMinor < 0) throw new RangeError("Amount must be >= 0");
|
|
799
|
+
if (!input.name.trim()) throw new RangeError("Name must not be empty");
|
|
800
|
+
const taxStatus = input.taxStatus ?? "taxable";
|
|
801
|
+
if (woo) {
|
|
802
|
+
const line2 = wooLine(input.amountMinor, input.taxClass, taxContext.pricesIncludeTax, taxStatus === "none", shipping2);
|
|
803
|
+
return {
|
|
804
|
+
id: id2,
|
|
805
|
+
name: input.name,
|
|
806
|
+
amountMinor: input.amountMinor,
|
|
807
|
+
taxClass: input.taxClass,
|
|
808
|
+
taxStatus,
|
|
809
|
+
...line2
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
const code = taxContext.getTaxRateCode?.(input.taxClass);
|
|
813
|
+
const line = recalculateLine({
|
|
814
|
+
id: id2,
|
|
815
|
+
productId: id2,
|
|
816
|
+
name: input.name,
|
|
817
|
+
sku: "",
|
|
818
|
+
unitPriceMinor: input.amountMinor,
|
|
819
|
+
quantity: 1,
|
|
820
|
+
taxLines: [{
|
|
821
|
+
...code !== void 0 ? { code } : {},
|
|
822
|
+
ratePpm: taxStatus === "none" ? 0 : taxContext.getTaxRatePpm(input.taxClass),
|
|
823
|
+
taxMicros: "0"
|
|
824
|
+
}],
|
|
825
|
+
discounts: [],
|
|
826
|
+
discountMinor: 0,
|
|
827
|
+
orderDiscountMinor: 0,
|
|
828
|
+
netMinor: 0,
|
|
829
|
+
taxMicros: "0",
|
|
830
|
+
taxInclusive: taxContext.pricesIncludeTax
|
|
831
|
+
});
|
|
832
|
+
return {
|
|
833
|
+
id: id2,
|
|
834
|
+
name: input.name,
|
|
835
|
+
amountMinor: input.amountMinor,
|
|
836
|
+
taxClass: input.taxClass,
|
|
837
|
+
taxStatus,
|
|
838
|
+
taxLines: line.taxLines,
|
|
839
|
+
netMinor: line.netMinor,
|
|
840
|
+
taxMicros: line.taxMicros
|
|
841
|
+
};
|
|
842
|
+
}
|
|
669
843
|
function addLine(input) {
|
|
670
844
|
const { productId, variantId, unitPrice } = input;
|
|
671
845
|
const quantity = input.quantity ?? 1;
|
|
672
846
|
if (unitPrice.currency !== currency) throw new RangeError("Line currency must match " + currency);
|
|
673
847
|
if (!Number.isInteger(unitPrice.amount)) throw new RangeError("Price must be integer minor units");
|
|
674
848
|
if (!Number.isInteger(quantity) || quantity < 1) throw new RangeError("Quantity must be an integer >= 1");
|
|
675
|
-
const code = input.taxRates ? void 0 : taxContext.getTaxRateCode?.(input.taxClass);
|
|
676
|
-
const taxRates = input.taxRates ?? [{ ...code !== void 0 ? { code } : {}, ratePpm: taxContext.getTaxRatePpm(input.taxClass) }];
|
|
849
|
+
const code = woo || input.taxRates ? void 0 : taxContext.getTaxRateCode?.(input.taxClass);
|
|
850
|
+
const taxRates = woo ? wooLine(unitPrice.amount * quantity, input.taxClass, unitPrice.taxInclusive ?? taxContext.pricesIncludeTax, input.taxStatus === "none").taxLines : input.taxStatus === "none" ? [{ ...code !== void 0 ? { code } : {}, ratePpm: 0 }] : input.taxRates ?? [{ ...code !== void 0 ? { code } : {}, ratePpm: taxContext.getTaxRatePpm(input.taxClass) }];
|
|
677
851
|
const taxInclusive = unitPrice.taxInclusive ?? taxContext.pricesIncludeTax;
|
|
678
852
|
const existing = lineItems.find(
|
|
679
|
-
(li) => li.productId === productId && li.variantId === variantId && li.taxInclusive === taxInclusive && li.unitPriceMinor === unitPrice.amount && li.taxLines.length === taxRates.length && li.taxLines.every((tax, index) => tax.code === taxRates[index].code && tax.ratePpm === taxRates[index].ratePpm)
|
|
853
|
+
(li) => !input.custom && !li.custom && li.productId === productId && li.variantId === variantId && li.taxInclusive === taxInclusive && (!woo || li.taxClass === input.taxClass && li.taxStatus === "none" === (input.taxStatus === "none")) && li.unitPriceMinor === unitPrice.amount && li.taxLines.length === taxRates.length && li.taxLines.every((tax, index) => tax.code === taxRates[index].code && tax.ratePpm === taxRates[index].ratePpm)
|
|
680
854
|
);
|
|
681
855
|
if (existing) {
|
|
682
856
|
lineItems = lineItems.map(
|
|
@@ -688,7 +862,10 @@ function createOrderBuilder(options) {
|
|
|
688
862
|
const lineId = uid();
|
|
689
863
|
const line = {
|
|
690
864
|
id: lineId,
|
|
865
|
+
...(input.custom ? input.taxClass !== void 0 : woo) ? { taxClass: input.taxClass } : {},
|
|
691
866
|
productId,
|
|
867
|
+
...input.custom ? { custom: true } : {},
|
|
868
|
+
...input.taxStatus === "none" ? { taxStatus: "none" } : {},
|
|
692
869
|
variantId,
|
|
693
870
|
name: input.name,
|
|
694
871
|
sku: input.sku ?? "",
|
|
@@ -716,6 +893,43 @@ function createOrderBuilder(options) {
|
|
|
716
893
|
return {
|
|
717
894
|
order$: subject.asObservable(),
|
|
718
895
|
addLine,
|
|
896
|
+
addFee(input) {
|
|
897
|
+
const fee = recalculateCharge(uid(), input);
|
|
898
|
+
fees = [...fees, fee];
|
|
899
|
+
emit();
|
|
900
|
+
return fee.id;
|
|
901
|
+
},
|
|
902
|
+
updateFee(id2, patch) {
|
|
903
|
+
const fee = fees.find((entry) => entry.id === id2);
|
|
904
|
+
if (!fee) throw new Error(`Unknown fee ${id2}`);
|
|
905
|
+
const updated = recalculateCharge(id2, { ...fee, ...patch });
|
|
906
|
+
fees = fees.map((entry) => entry.id === id2 ? updated : entry);
|
|
907
|
+
emit();
|
|
908
|
+
},
|
|
909
|
+
removeFee(id2) {
|
|
910
|
+
if (!fees.some((entry) => entry.id === id2)) throw new Error(`Unknown fee ${id2}`);
|
|
911
|
+
fees = fees.filter((entry) => entry.id !== id2);
|
|
912
|
+
emit();
|
|
913
|
+
},
|
|
914
|
+
addShipping(input) {
|
|
915
|
+
const charge = { ...recalculateCharge(uid(), input, true), methodId: input.methodId };
|
|
916
|
+
shipping = [...shipping, charge];
|
|
917
|
+
emit();
|
|
918
|
+
return charge.id;
|
|
919
|
+
},
|
|
920
|
+
updateShipping(id2, patch) {
|
|
921
|
+
const charge = shipping.find((entry) => entry.id === id2);
|
|
922
|
+
if (!charge) throw new Error(`Unknown shipping ${id2}`);
|
|
923
|
+
const input = { ...charge, ...patch };
|
|
924
|
+
const updated = { ...recalculateCharge(id2, input, true), methodId: input.methodId };
|
|
925
|
+
shipping = shipping.map((entry) => entry.id === id2 ? updated : entry);
|
|
926
|
+
emit();
|
|
927
|
+
},
|
|
928
|
+
removeShipping(id2) {
|
|
929
|
+
if (!shipping.some((entry) => entry.id === id2)) throw new Error(`Unknown shipping ${id2}`);
|
|
930
|
+
shipping = shipping.filter((entry) => entry.id !== id2);
|
|
931
|
+
emit();
|
|
932
|
+
},
|
|
719
933
|
addProduct(doc, traits, opts) {
|
|
720
934
|
const productId = traits.getId(doc);
|
|
721
935
|
if (!traits.isSellable(doc)) throw new Error("addProduct: this product isn't sold in this store's channel");
|
|
@@ -732,7 +946,8 @@ function createOrderBuilder(options) {
|
|
|
732
946
|
imageUrl: traits.getImageUrl(doc),
|
|
733
947
|
unitPrice,
|
|
734
948
|
quantity: opts?.quantity,
|
|
735
|
-
taxClass: traits.getTaxClass?.(doc, opts?.variantId)
|
|
949
|
+
taxClass: traits.getTaxClass?.(doc, opts?.variantId),
|
|
950
|
+
...traits.getTaxStatus?.(doc, opts?.variantId) === "none" ? { taxStatus: "none" } : {}
|
|
736
951
|
});
|
|
737
952
|
},
|
|
738
953
|
updateQuantity(lineId, quantity) {
|
|
@@ -811,6 +1026,8 @@ function createOrderBuilder(options) {
|
|
|
811
1026
|
},
|
|
812
1027
|
clear() {
|
|
813
1028
|
lineItems = [];
|
|
1029
|
+
fees = [];
|
|
1030
|
+
shipping = [];
|
|
814
1031
|
orderDiscounts = [];
|
|
815
1032
|
payments = [];
|
|
816
1033
|
customer = null;
|
|
@@ -877,20 +1094,23 @@ function netDiscountMinor(line, pricesIncludeTax) {
|
|
|
877
1094
|
return roundMicrosToMinor(BigInt(line.discountMinor) * MICROS_PER_MINOR - taxBefore + taxAfter);
|
|
878
1095
|
}
|
|
879
1096
|
function contentVersion(order) {
|
|
1097
|
+
if (order.fees?.length || order.shipping?.length || order.lines.some((line) => line.custom)) return 5;
|
|
880
1098
|
return order.display && order.taxByRate ? 3 : order.lines.reduce((sum, line) => sum + line.discountMinor, 0) > 0 ? 2 : 1;
|
|
881
1099
|
}
|
|
882
1100
|
function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
883
1101
|
const grossMinor = order.lines.reduce((sum, line) => sum + line.discountMinor, 0);
|
|
884
1102
|
const content = contentVersion(order);
|
|
885
1103
|
const cap = options?.maxVersion === void 0 ? order.sentVersion : Math.min(options.maxVersion, order.sentVersion ?? options.maxVersion);
|
|
1104
|
+
if (content === 5 && cap !== void 0 && cap < 5) throw new UnsupportedOrderVersionError(5, cap);
|
|
886
1105
|
if (grossMinor > 0 && cap !== void 0 && cap < 2) throw new UnsupportedOrderVersionError(2, cap);
|
|
887
1106
|
const version = content === 3 && cap !== void 0 && cap >= 4 ? 4 : Math.min(content, cap ?? content);
|
|
888
|
-
const lineDiscounts = order.lines.map((line) => version
|
|
1107
|
+
const lineDiscounts = order.lines.map((line) => version >= 4 ? netDiscountMinor(line, order.pricesIncludeTax) : line.discountMinor);
|
|
889
1108
|
const discountMinor = lineDiscounts.reduce((sum, discount) => sum + discount, 0);
|
|
890
1109
|
const email = order.customer?.email;
|
|
891
1110
|
const id2 = order.customer?.id;
|
|
892
1111
|
const customerId = typeof id2 === "string" && id2.length > 0 && id2.length <= 64 ? id2 : void 0;
|
|
893
1112
|
const sessionId = order.sessionId ?? order.lateSessionId;
|
|
1113
|
+
const { fees: _fees, shipping: _shipping, ...storedDisplay } = { ...order.display };
|
|
894
1114
|
return {
|
|
895
1115
|
id: order.commandId,
|
|
896
1116
|
type: "order.create",
|
|
@@ -905,13 +1125,30 @@ function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
|
905
1125
|
pricesIncludeTax: order.pricesIncludeTax,
|
|
906
1126
|
lines: order.lines.map((line, i) => ({
|
|
907
1127
|
clientLineId: line.id,
|
|
908
|
-
variantId: line.variantId ?? line.productId,
|
|
1128
|
+
...line.custom ? { custom: { ...line.custom } } : { variantId: line.variantId ?? line.productId },
|
|
909
1129
|
title: line.name,
|
|
910
1130
|
quantity: line.quantity,
|
|
911
1131
|
unitPriceMinor: line.unitPriceMinor,
|
|
912
1132
|
...line.taxInclusive !== void 0 ? { taxInclusive: line.taxInclusive } : {},
|
|
913
1133
|
...lineDiscounts[i] > 0 ? { discountMinor: lineDiscounts[i] } : {}
|
|
914
1134
|
})),
|
|
1135
|
+
...version === 5 && order.fees?.length ? { fees: order.fees.map((fee) => ({
|
|
1136
|
+
clientFeeId: fee.id,
|
|
1137
|
+
name: fee.name,
|
|
1138
|
+
amountMinor: fee.amountMinor,
|
|
1139
|
+
taxStatus: fee.taxStatus,
|
|
1140
|
+
...fee.taxClass !== void 0 ? { taxClass: fee.taxClass } : {},
|
|
1141
|
+
taxMinor: roundMicrosToMinor(BigInt(fee.taxMicros))
|
|
1142
|
+
})) } : {},
|
|
1143
|
+
...version === 5 && order.shipping?.length ? { shipping: order.shipping.map((charge) => ({
|
|
1144
|
+
clientShippingId: charge.id,
|
|
1145
|
+
name: charge.name,
|
|
1146
|
+
...charge.methodId !== void 0 ? { methodId: charge.methodId } : {},
|
|
1147
|
+
amountMinor: charge.amountMinor,
|
|
1148
|
+
taxStatus: charge.taxStatus,
|
|
1149
|
+
...charge.taxClass !== void 0 ? { taxClass: charge.taxClass } : {},
|
|
1150
|
+
taxMinor: roundMicrosToMinor(BigInt(charge.taxMicros))
|
|
1151
|
+
})) } : {},
|
|
915
1152
|
payments: order.payments.map((payment) => ({
|
|
916
1153
|
clientPaymentId: payment.id,
|
|
917
1154
|
method: payment.method,
|
|
@@ -925,11 +1162,16 @@ function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
|
925
1162
|
taxMinor: order.taxMinor,
|
|
926
1163
|
totalMinor: order.totalMinor,
|
|
927
1164
|
...version >= 3 ? {
|
|
928
|
-
display: {
|
|
929
|
-
|
|
930
|
-
amountMinor,
|
|
931
|
-
|
|
932
|
-
|
|
1165
|
+
display: {
|
|
1166
|
+
...storedDisplay,
|
|
1167
|
+
lines: order.display.lines.map(({ lineId, amountMinor, discounts }) => ({
|
|
1168
|
+
clientLineId: lineId,
|
|
1169
|
+
amountMinor,
|
|
1170
|
+
discounts
|
|
1171
|
+
})),
|
|
1172
|
+
...version === 5 && order.display.fees ? { fees: order.display.fees.map(({ id: id3, amountMinor }) => ({ clientFeeId: id3, amountMinor })) } : {},
|
|
1173
|
+
...version === 5 && order.display.shipping ? { shipping: order.display.shipping.map(({ id: id3, amountMinor }) => ({ clientShippingId: id3, amountMinor })) } : {}
|
|
1174
|
+
},
|
|
933
1175
|
taxByRate: order.taxByRate.map(({ ratePpm, code, netMinor, amountMinor, grossMinor: grossMinor2 }) => ({
|
|
934
1176
|
ratePpm,
|
|
935
1177
|
...code !== void 0 ? { code } : {},
|
|
@@ -988,6 +1230,9 @@ function restoreOrderDraft(saved, options) {
|
|
|
988
1230
|
for (const line of saved.lineItems) {
|
|
989
1231
|
const lineId = builder.addLine({
|
|
990
1232
|
productId: line.productId,
|
|
1233
|
+
custom: line.custom,
|
|
1234
|
+
taxStatus: line.taxStatus,
|
|
1235
|
+
...taxContext.rounding?.granularity === "woocommerce" ? { taxClass: line.taxClass } : {},
|
|
991
1236
|
variantId: line.variantId,
|
|
992
1237
|
name: line.name,
|
|
993
1238
|
sku: line.sku,
|
|
@@ -1005,6 +1250,8 @@ function restoreOrderDraft(saved, options) {
|
|
|
1005
1250
|
});
|
|
1006
1251
|
}
|
|
1007
1252
|
}
|
|
1253
|
+
for (const fee of saved.fees ?? []) builder.addFee(fee);
|
|
1254
|
+
for (const charge of saved.shipping ?? []) builder.addShipping(charge);
|
|
1008
1255
|
for (const discount of saved.discounts) {
|
|
1009
1256
|
builder.applyOrderDiscount({
|
|
1010
1257
|
type: discount.type,
|
|
@@ -1105,7 +1352,11 @@ function useParkedSales(drafts) {
|
|
|
1105
1352
|
|
|
1106
1353
|
// src/receipt/build-receipt-data.ts
|
|
1107
1354
|
function buildReceiptData(order, config) {
|
|
1108
|
-
const
|
|
1355
|
+
const taxedLines = [...order.lineItems.map((line) => order.taxRounding?.granularity === "woocommerce" && line.netMicros !== void 0 ? { ...line, taxInclusive: order.pricesIncludeTax } : line), ...[...order.fees ?? [], ...order.shipping ?? []].map((charge) => ({
|
|
1356
|
+
...charge,
|
|
1357
|
+
taxInclusive: order.pricesIncludeTax
|
|
1358
|
+
}))];
|
|
1359
|
+
const taxLines = taxLinesByRate(taxedLines, order.taxMinor, config.taxLabels, order.taxRounding).map(
|
|
1109
1360
|
({ label: label2, code, ratePpm, amountMinor }) => ({ label: label2, code, ratePpm, amountMinor })
|
|
1110
1361
|
);
|
|
1111
1362
|
let taxSoFar = order.lineItems.reduce(
|
|
@@ -1113,6 +1364,7 @@ function buildReceiptData(order, config) {
|
|
|
1113
1364
|
0n
|
|
1114
1365
|
);
|
|
1115
1366
|
const lineTotals = order.lineItems.map((li) => {
|
|
1367
|
+
if (order.taxRounding?.granularity === "woocommerce" && li.netMicros !== void 0) return order.pricesIncludeTax ? li.totalMinor : li.netMinor;
|
|
1116
1368
|
if (!li.priceTaxModeConverted) return li.netMinor;
|
|
1117
1369
|
const share = roundMicrosToMinor(taxSoFar + BigInt(li.taxMicros)) - roundMicrosToMinor(taxSoFar);
|
|
1118
1370
|
taxSoFar += BigInt(li.taxMicros);
|
|
@@ -1145,6 +1397,8 @@ function buildReceiptData(order, config) {
|
|
|
1145
1397
|
label: label(d),
|
|
1146
1398
|
amountMinor: d.amountMinor
|
|
1147
1399
|
})),
|
|
1400
|
+
fees: (order.display.fees ?? []).map(({ name, amountMinor }) => ({ name, amountMinor })),
|
|
1401
|
+
shipping: (order.display.shipping ?? []).map(({ name, amountMinor }) => ({ name, amountMinor })),
|
|
1148
1402
|
orderDiscountMinor: order.display.orderDiscountMinor,
|
|
1149
1403
|
totals: {
|
|
1150
1404
|
taxInclusive: order.display.taxInclusive,
|
|
@@ -1254,7 +1508,7 @@ function uuidv7(now, randomBytes) {
|
|
|
1254
1508
|
}
|
|
1255
1509
|
|
|
1256
1510
|
// src/pos-order/finalize.ts
|
|
1257
|
-
import { minorUnitDigits } from "@tallyui/core";
|
|
1511
|
+
import { minorUnitDigits as minorUnitDigits2 } from "@tallyui/core";
|
|
1258
1512
|
|
|
1259
1513
|
// src/outbox/logger.ts
|
|
1260
1514
|
var outboxLogger = createLogger("outbox");
|
|
@@ -1269,8 +1523,12 @@ function withSentForm(order, posOrder) {
|
|
|
1269
1523
|
...order,
|
|
1270
1524
|
customer,
|
|
1271
1525
|
lineItems: order.lineItems.map((line, i) => ({ ...line, name: posOrder.lines[i].name })),
|
|
1526
|
+
...order.fees ? { fees: order.fees.map((fee, i) => ({ ...fee, name: posOrder.fees[i].name })) } : {},
|
|
1527
|
+
...order.shipping ? { shipping: order.shipping.map((charge, i) => ({ ...charge, name: posOrder.shipping[i].name })) } : {},
|
|
1272
1528
|
display: posOrder.display ? {
|
|
1273
1529
|
...order.display,
|
|
1530
|
+
...order.display.fees ? { fees: order.display.fees.map((row, i) => ({ ...row, name: posOrder.display.fees[i].name })) } : {},
|
|
1531
|
+
...order.display.shipping ? { shipping: order.display.shipping.map((row, i) => ({ ...row, name: posOrder.display.shipping[i].name })) } : {},
|
|
1274
1532
|
lines: order.display.lines.map((line, i) => ({
|
|
1275
1533
|
...line,
|
|
1276
1534
|
discounts: line.discounts.map((discount, j) => ({ ...discount, label: posOrder.display.lines[i].discounts[j].label }))
|
|
@@ -1283,7 +1541,15 @@ function freezeSentForm(order) {
|
|
|
1283
1541
|
const lines = order.lines.map((line) => {
|
|
1284
1542
|
const name = cutText(line.name, PAYLOAD_STRING_MAX);
|
|
1285
1543
|
changed ||= name !== line.name;
|
|
1286
|
-
|
|
1544
|
+
const custom = line.custom && { ...line.custom, name: cutText(line.custom.name, PAYLOAD_STRING_MAX) };
|
|
1545
|
+
changed ||= custom !== void 0 && custom.name !== line.custom.name;
|
|
1546
|
+
return { ...line, name, ...custom ? { custom } : {} };
|
|
1547
|
+
});
|
|
1548
|
+
const charges = { fees: order.fees, shipping: order.shipping };
|
|
1549
|
+
for (const field of ["fees", "shipping"]) if (charges[field]) charges[field] = charges[field].map((charge) => {
|
|
1550
|
+
const name = cutText(charge.name, PAYLOAD_STRING_MAX);
|
|
1551
|
+
changed ||= name !== charge.name;
|
|
1552
|
+
return { ...charge, name };
|
|
1287
1553
|
});
|
|
1288
1554
|
const display = order.display && { ...order.display, lines: order.display.lines.map((line) => ({
|
|
1289
1555
|
...line,
|
|
@@ -1294,6 +1560,11 @@ function freezeSentForm(order) {
|
|
|
1294
1560
|
return { ...discount, label };
|
|
1295
1561
|
})
|
|
1296
1562
|
})) };
|
|
1563
|
+
for (const field of ["fees", "shipping"]) if (display?.[field]) display[field] = display[field].map((row) => {
|
|
1564
|
+
const name = cutText(row.name, PAYLOAD_STRING_MAX);
|
|
1565
|
+
changed ||= name !== row.name;
|
|
1566
|
+
return { ...row, name };
|
|
1567
|
+
});
|
|
1297
1568
|
const payments = order.payments.map((payment) => {
|
|
1298
1569
|
if (payment.reference === void 0) return payment;
|
|
1299
1570
|
const reference = cutText(payment.reference, PAYLOAD_STRING_MAX);
|
|
@@ -1309,7 +1580,16 @@ function freezeSentForm(order) {
|
|
|
1309
1580
|
if (!localWarnings.some((warning) => warning.code === "customer_omitted" && warning.field === field)) localWarnings.push({ code: "customer_omitted", field });
|
|
1310
1581
|
}
|
|
1311
1582
|
}
|
|
1312
|
-
return changed ? {
|
|
1583
|
+
return changed ? {
|
|
1584
|
+
...order,
|
|
1585
|
+
lines,
|
|
1586
|
+
payments,
|
|
1587
|
+
customer,
|
|
1588
|
+
...charges.fees ? { fees: charges.fees } : {},
|
|
1589
|
+
...charges.shipping ? { shipping: charges.shipping } : {},
|
|
1590
|
+
...display ? { display } : {},
|
|
1591
|
+
...localWarnings.length ? { localWarnings } : {}
|
|
1592
|
+
} : order;
|
|
1313
1593
|
}
|
|
1314
1594
|
function referenceError(label, value) {
|
|
1315
1595
|
const reason = referenceReason(value);
|
|
@@ -1321,6 +1601,9 @@ function referenceReason(value) {
|
|
|
1321
1601
|
return value.includes("\0") ? "nul" : null;
|
|
1322
1602
|
}
|
|
1323
1603
|
function finalizeOrder(order, options = {}) {
|
|
1604
|
+
if ((order.fees?.length || order.shipping?.length || order.lineItems.some((line) => line.custom)) && (options.capabilities?.orderCreate ?? 1) < 5) {
|
|
1605
|
+
throw new Error("finalize: fees, shipping and custom lines need the store to accept order.create version 5; update the store's TallyUI plugin");
|
|
1606
|
+
}
|
|
1324
1607
|
if (!order.lineItems.length) throw new Error("finalize: no lines");
|
|
1325
1608
|
if (order.discountMinor < 0 || order.lineItems.some((line) => line.discountMinor < 0) || order.discounts.some((d) => d.amountMinor < 0)) {
|
|
1326
1609
|
throw new Error("finalize: negative discount");
|
|
@@ -1362,15 +1645,25 @@ function finalizeOrder(order, options = {}) {
|
|
|
1362
1645
|
id: newId(),
|
|
1363
1646
|
productId: line.productId,
|
|
1364
1647
|
...line.variantId !== void 0 ? { variantId: line.variantId } : {},
|
|
1648
|
+
...line.custom ? { custom: {
|
|
1649
|
+
name: line.name,
|
|
1650
|
+
...line.sku ? { sku: line.sku } : {},
|
|
1651
|
+
...line.taxClass !== void 0 ? { taxClass: line.taxClass } : {},
|
|
1652
|
+
taxStatus: line.taxStatus ?? "taxable"
|
|
1653
|
+
} } : {},
|
|
1654
|
+
...line.taxStatus !== void 0 ? { taxStatus: line.taxStatus } : {},
|
|
1365
1655
|
name: line.name,
|
|
1366
1656
|
sku: line.sku,
|
|
1367
1657
|
quantity: line.quantity,
|
|
1368
1658
|
unitPriceMinor: line.unitPriceMinor,
|
|
1369
1659
|
discountMinor: line.discountMinor,
|
|
1370
1660
|
netMinor: line.netMinor,
|
|
1661
|
+
...order.taxRounding?.granularity === "woocommerce" ? { netMicros: line.netMicros } : {},
|
|
1371
1662
|
taxLines: line.taxLines.map((tax) => ({ ...tax })),
|
|
1372
1663
|
...line.priceTaxModeConverted ? { taxInclusive: line.taxInclusive } : {}
|
|
1373
1664
|
}));
|
|
1665
|
+
const fees = order.fees?.map((charge) => ({ ...charge, id: newId(), taxLines: charge.taxLines.map((tax) => ({ ...tax })) }));
|
|
1666
|
+
const shipping = order.shipping?.map((charge) => ({ ...charge, id: newId(), taxLines: charge.taxLines.map((tax) => ({ ...tax })) }));
|
|
1374
1667
|
const payments = order.payments.map((payment) => ({
|
|
1375
1668
|
id: newId(),
|
|
1376
1669
|
method: payment.method,
|
|
@@ -1408,8 +1701,10 @@ function finalizeOrder(order, options = {}) {
|
|
|
1408
1701
|
}
|
|
1409
1702
|
display = {
|
|
1410
1703
|
currency: order.currency,
|
|
1411
|
-
exponent:
|
|
1704
|
+
exponent: minorUnitDigits2(order.currency),
|
|
1412
1705
|
...order.display,
|
|
1706
|
+
...fees?.length ? { fees: order.display.fees.map((row, i) => ({ ...row, id: fees[i].id })) } : {},
|
|
1707
|
+
...shipping?.length ? { shipping: order.display.shipping.map((row, i) => ({ ...row, id: shipping[i].id })) } : {},
|
|
1413
1708
|
lines: order.display.lines.map((line, i) => ({
|
|
1414
1709
|
lineId: lines[i].id,
|
|
1415
1710
|
amountMinor: line.amountMinor,
|
|
@@ -1420,7 +1715,8 @@ function finalizeOrder(order, options = {}) {
|
|
|
1420
1715
|
}))
|
|
1421
1716
|
}))
|
|
1422
1717
|
};
|
|
1423
|
-
|
|
1718
|
+
const taxedLines = [...order.lineItems, ...[...order.fees ?? [], ...order.shipping ?? []].map((charge) => ({ ...charge, taxInclusive: order.pricesIncludeTax }))];
|
|
1719
|
+
taxByRate = taxLinesByRate(taxedLines, order.taxMinor, void 0, order.taxRounding).map(({ ratePpm, code, netMinor, amountMinor }) => ({
|
|
1424
1720
|
ratePpm,
|
|
1425
1721
|
...code !== void 0 ? { code } : {},
|
|
1426
1722
|
netMinor,
|
|
@@ -1435,7 +1731,7 @@ function finalizeOrder(order, options = {}) {
|
|
|
1435
1731
|
}
|
|
1436
1732
|
}
|
|
1437
1733
|
const rounding = order.taxRounding ?? DEFAULT_TAX_ROUNDING;
|
|
1438
|
-
const taxRounding = rounding.granularity === "custom" ? { granularity: "custom" } : { granularity: rounding.granularity, mode: rounding.mode };
|
|
1734
|
+
const taxRounding = rounding.granularity === "custom" ? { granularity: "custom" } : rounding.granularity === "woocommerce" ? { granularity: "woocommerce", roundAtSubtotal: rounding.roundAtSubtotal } : { granularity: rounding.granularity, mode: rounding.mode };
|
|
1439
1735
|
const now = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1440
1736
|
return freezeSentForm({
|
|
1441
1737
|
id: id2,
|
|
@@ -1448,6 +1744,8 @@ function finalizeOrder(order, options = {}) {
|
|
|
1448
1744
|
pricesIncludeTax: order.pricesIncludeTax,
|
|
1449
1745
|
lines,
|
|
1450
1746
|
payments,
|
|
1747
|
+
...fees?.length ? { fees } : {},
|
|
1748
|
+
...shipping?.length ? { shipping } : {},
|
|
1451
1749
|
subtotalMinor: order.subtotalMinor,
|
|
1452
1750
|
discountMinor: order.discountMinor,
|
|
1453
1751
|
taxMinor: order.taxMinor,
|
|
@@ -1470,7 +1768,7 @@ function finalizeOrder(order, options = {}) {
|
|
|
1470
1768
|
import { addRxPlugin } from "rxdb";
|
|
1471
1769
|
import { RxDBMigrationSchemaPlugin } from "rxdb/plugins/migration-schema";
|
|
1472
1770
|
var posOrderSchema = {
|
|
1473
|
-
version:
|
|
1771
|
+
version: 8,
|
|
1474
1772
|
primaryKey: "id",
|
|
1475
1773
|
type: "object",
|
|
1476
1774
|
additionalProperties: false,
|
|
@@ -1500,9 +1798,17 @@ var posOrderSchema = {
|
|
|
1500
1798
|
name: { type: "string" },
|
|
1501
1799
|
sku: { type: "string" },
|
|
1502
1800
|
quantity: { type: "integer" },
|
|
1801
|
+
taxStatus: { type: "string", enum: ["taxable", "none"] },
|
|
1802
|
+
custom: { type: "object", additionalProperties: false, properties: {
|
|
1803
|
+
name: { type: "string", minLength: 1, maxLength: 255 },
|
|
1804
|
+
sku: { type: "string", maxLength: 64 },
|
|
1805
|
+
taxClass: { type: "string", maxLength: 64 },
|
|
1806
|
+
taxStatus: { type: "string", enum: ["taxable", "none"] }
|
|
1807
|
+
}, required: ["name", "taxStatus"] },
|
|
1503
1808
|
unitPriceMinor: { type: "integer" },
|
|
1504
1809
|
discountMinor: { type: "integer" },
|
|
1505
1810
|
netMinor: { type: "integer" },
|
|
1811
|
+
netMicros: { type: "string", pattern: "^-?[0-9]{1,24}$", maxLength: 25 },
|
|
1506
1812
|
taxLines: { type: "array", items: {
|
|
1507
1813
|
type: "object",
|
|
1508
1814
|
properties: { code: { type: "string" }, ratePpm: { type: "integer" }, taxMicros: { type: "string" } },
|
|
@@ -1511,6 +1817,43 @@ var posOrderSchema = {
|
|
|
1511
1817
|
},
|
|
1512
1818
|
required: ["id", "productId", "name", "sku", "quantity", "unitPriceMinor", "discountMinor", "netMinor", "taxLines"]
|
|
1513
1819
|
} },
|
|
1820
|
+
fees: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1821
|
+
id: { type: "string", maxLength: 36 },
|
|
1822
|
+
name: { type: "string", minLength: 1, maxLength: 255 },
|
|
1823
|
+
amountMinor: { type: "integer", minimum: 0 },
|
|
1824
|
+
taxClass: { type: "string", maxLength: 64 },
|
|
1825
|
+
taxStatus: { type: "string", enum: ["taxable", "none"] },
|
|
1826
|
+
netMinor: { type: "integer" },
|
|
1827
|
+
taxMicros: { type: "string" },
|
|
1828
|
+
netMicros: { type: "string", pattern: "^-?[0-9]{1,24}$", maxLength: 25 },
|
|
1829
|
+
totalMinor: { type: "integer" },
|
|
1830
|
+
taxLines: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1831
|
+
code: { type: "string", maxLength: 255 },
|
|
1832
|
+
ratePpm: { type: "integer" },
|
|
1833
|
+
taxMicros: { type: "string" },
|
|
1834
|
+
rateId: { type: "integer" },
|
|
1835
|
+
compound: { type: "boolean" }
|
|
1836
|
+
}, required: ["ratePpm", "taxMicros"] } }
|
|
1837
|
+
}, required: ["id", "name", "amountMinor", "taxStatus", "taxLines", "netMinor", "taxMicros"] } },
|
|
1838
|
+
shipping: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1839
|
+
id: { type: "string", maxLength: 36 },
|
|
1840
|
+
name: { type: "string", minLength: 1, maxLength: 255 },
|
|
1841
|
+
methodId: { type: "string", maxLength: 64 },
|
|
1842
|
+
amountMinor: { type: "integer", minimum: 0 },
|
|
1843
|
+
taxClass: { type: "string", maxLength: 64 },
|
|
1844
|
+
taxStatus: { type: "string", enum: ["taxable", "none"] },
|
|
1845
|
+
netMinor: { type: "integer" },
|
|
1846
|
+
taxMicros: { type: "string" },
|
|
1847
|
+
netMicros: { type: "string", pattern: "^-?[0-9]{1,24}$", maxLength: 25 },
|
|
1848
|
+
totalMinor: { type: "integer" },
|
|
1849
|
+
taxLines: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1850
|
+
code: { type: "string", maxLength: 255 },
|
|
1851
|
+
ratePpm: { type: "integer" },
|
|
1852
|
+
taxMicros: { type: "string" },
|
|
1853
|
+
rateId: { type: "integer" },
|
|
1854
|
+
compound: { type: "boolean" }
|
|
1855
|
+
}, required: ["ratePpm", "taxMicros"] } }
|
|
1856
|
+
}, required: ["id", "name", "amountMinor", "taxStatus", "taxLines", "netMinor", "taxMicros"] } },
|
|
1514
1857
|
payments: { type: "array", items: {
|
|
1515
1858
|
type: "object",
|
|
1516
1859
|
properties: {
|
|
@@ -1548,11 +1891,12 @@ var posOrderSchema = {
|
|
|
1548
1891
|
}, required: ["since", "reason", "isolated"] },
|
|
1549
1892
|
lateSessionId: { type: "string" },
|
|
1550
1893
|
taxRounding: { type: "object", additionalProperties: false, properties: {
|
|
1551
|
-
granularity: { type: "string", enum: ["per_order", "per_line_items", "per_rate_group_items", "custom"] },
|
|
1894
|
+
granularity: { type: "string", enum: ["per_order", "per_line_items", "per_rate_group_items", "custom", "woocommerce"] },
|
|
1895
|
+
roundAtSubtotal: { type: "boolean" },
|
|
1552
1896
|
mode: { type: "string", enum: ["half_away_from_zero", "half_up"] }
|
|
1553
1897
|
}, required: ["granularity"] },
|
|
1554
|
-
sentVersion: { type: "integer", minimum: 1, maximum:
|
|
1555
|
-
downgradedFrom: { type: "integer", minimum: 1, maximum:
|
|
1898
|
+
sentVersion: { type: "integer", minimum: 1, maximum: 5 },
|
|
1899
|
+
downgradedFrom: { type: "integer", minimum: 1, maximum: 5 },
|
|
1556
1900
|
// The nested objects are closed too: loosening a schema later is free, tightening one costs a migration.
|
|
1557
1901
|
display: { type: "object", additionalProperties: false, properties: {
|
|
1558
1902
|
currency: { type: "string" },
|
|
@@ -1563,6 +1907,16 @@ var posOrderSchema = {
|
|
|
1563
1907
|
taxMinor: { type: "integer" },
|
|
1564
1908
|
totalMinor: { type: "integer" },
|
|
1565
1909
|
orderDiscountMinor: { type: "integer" },
|
|
1910
|
+
fees: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1911
|
+
id: { type: "string", maxLength: 36 },
|
|
1912
|
+
name: { type: "string", minLength: 1, maxLength: 255 },
|
|
1913
|
+
amountMinor: { type: "integer" }
|
|
1914
|
+
}, required: ["id", "name", "amountMinor"] } },
|
|
1915
|
+
shipping: { type: "array", items: { type: "object", additionalProperties: false, properties: {
|
|
1916
|
+
id: { type: "string", maxLength: 36 },
|
|
1917
|
+
name: { type: "string", minLength: 1, maxLength: 255 },
|
|
1918
|
+
amountMinor: { type: "integer" }
|
|
1919
|
+
}, required: ["id", "name", "amountMinor"] } },
|
|
1566
1920
|
lines: { type: "array", items: {
|
|
1567
1921
|
type: "object",
|
|
1568
1922
|
additionalProperties: false,
|
|
@@ -1625,7 +1979,7 @@ function posOrderCollection() {
|
|
|
1625
1979
|
};
|
|
1626
1980
|
return {
|
|
1627
1981
|
schema: posOrderSchema,
|
|
1628
|
-
migrationStrategies: { 1: identity2, 2: identity2, 3: identity2, 4: identity2, 5: recordSent, 6: recordRounding, 7: identity2 }
|
|
1982
|
+
migrationStrategies: { 1: identity2, 2: identity2, 3: identity2, 4: identity2, 5: recordSent, 6: recordRounding, 7: identity2, 8: identity2 }
|
|
1629
1983
|
};
|
|
1630
1984
|
}
|
|
1631
1985
|
|
|
@@ -2525,8 +2879,8 @@ async function writeClosure({
|
|
|
2525
2879
|
const roundings = /* @__PURE__ */ new Set();
|
|
2526
2880
|
for (const order of bound) {
|
|
2527
2881
|
const recorded = order.taxRounding ?? DEFAULT_TAX_ROUNDING;
|
|
2528
|
-
const
|
|
2529
|
-
roundings.add(`${granularity} ${mode}`);
|
|
2882
|
+
const rounding = recorded.granularity === "custom" ? DEFAULT_TAX_ROUNDING : recorded;
|
|
2883
|
+
roundings.add(rounding.granularity === "woocommerce" ? `woocommerce ${rounding.roundAtSubtotal}` : `${rounding.granularity} ${rounding.mode}`);
|
|
2530
2884
|
const lines = order.lines.map((line) => ({ ...line, taxInclusive: line.taxInclusive ?? order.pricesIncludeTax }));
|
|
2531
2885
|
for (const { ratePpm, netMinor, amountMinor } of taxLinesByRate(lines, order.taxMinor, void 0, order.taxRounding)) {
|
|
2532
2886
|
const existing2 = taxRates.get(String(ratePpm));
|
|
@@ -2895,7 +3249,7 @@ function envelope(row, context, xreport = false) {
|
|
|
2895
3249
|
});
|
|
2896
3250
|
return {
|
|
2897
3251
|
closure: {
|
|
2898
|
-
has_sales:
|
|
3252
|
+
has_sales: Number(breakdowns.transaction_count ?? 0) > 0 || Number(row.period_sales_total || 0) !== 0 || Number(row.period_refunds_total || 0) !== 0,
|
|
2899
3253
|
has_perpetual: row.perpetual_sales_total != null || row.perpetual_refunds_total != null,
|
|
2900
3254
|
...Object.fromEntries(
|
|
2901
3255
|
["payment_methods", "tax_rates", "movements"].map((key) => [`has_${key}`, Object.keys(breakdowns[key] ?? {}).length > 0])
|
|
@@ -3556,7 +3910,8 @@ function addEntryToCart(builder, entry, traits, currency) {
|
|
|
3556
3910
|
sku: variant.sku ?? "",
|
|
3557
3911
|
imageUrl: traits.getImageUrl?.(product),
|
|
3558
3912
|
unitPrice,
|
|
3559
|
-
taxClass: traits.getTaxClass?.(product, variant.id)
|
|
3913
|
+
taxClass: traits.getTaxClass?.(product, variant.id),
|
|
3914
|
+
...traits.getTaxStatus?.(product, variant.id) === "none" ? { taxStatus: "none" } : {}
|
|
3560
3915
|
});
|
|
3561
3916
|
}
|
|
3562
3917
|
|
|
@@ -3628,10 +3983,10 @@ function useSale(settings, opts) {
|
|
|
3628
3983
|
const subscription = rendered.order$.subscribe(setOrder);
|
|
3629
3984
|
return () => subscription.unsubscribe();
|
|
3630
3985
|
}, [rendered]);
|
|
3631
|
-
const idle3 = stage.kind === "cart" && !order.lineItems.length;
|
|
3986
|
+
const idle3 = stage.kind === "cart" && !order.lineItems.length && !order.fees?.length && !order.shipping?.length;
|
|
3632
3987
|
useLayoutEffect(() => {
|
|
3633
3988
|
const live = builderNow.current.getSnapshot();
|
|
3634
|
-
const liveIdle = stageNow.current.kind === "cart" && !live.lineItems.length && !inFlight.current && !pending.current;
|
|
3989
|
+
const liveIdle = stageNow.current.kind === "cart" && !live.lineItems.length && !live.fees?.length && !live.shipping?.length && !inFlight.current && !pending.current;
|
|
3635
3990
|
if (liveIdle && (madeWith.current.taxContext !== taxContext || madeWith.current.currency !== settings.currency)) resetSale(live.customer ?? null);
|
|
3636
3991
|
});
|
|
3637
3992
|
useEffect4(() => () => {
|
|
@@ -3754,6 +4109,85 @@ function useSale(settings, opts) {
|
|
|
3754
4109
|
setQuantity(lineId, quantity) {
|
|
3755
4110
|
if (!locked()) builderNow.current.updateQuantity(lineId, quantity);
|
|
3756
4111
|
},
|
|
4112
|
+
/** ADR-075: adds a fee; returns its id or the builder's refusal. */
|
|
4113
|
+
addFee(input) {
|
|
4114
|
+
if (locked()) return SALE_SAVING;
|
|
4115
|
+
try {
|
|
4116
|
+
return { id: builderNow.current.addFee(input) };
|
|
4117
|
+
} catch (error2) {
|
|
4118
|
+
if (error2 instanceof Error) return error2.message;
|
|
4119
|
+
throw error2;
|
|
4120
|
+
}
|
|
4121
|
+
},
|
|
4122
|
+
/** ADR-075: updates a fee; returns a refusal or null. */
|
|
4123
|
+
updateFee(id2, patch) {
|
|
4124
|
+
if (locked()) return SALE_SAVING;
|
|
4125
|
+
try {
|
|
4126
|
+
builderNow.current.updateFee(id2, patch);
|
|
4127
|
+
return null;
|
|
4128
|
+
} catch (error2) {
|
|
4129
|
+
if (error2 instanceof Error) return error2.message;
|
|
4130
|
+
throw error2;
|
|
4131
|
+
}
|
|
4132
|
+
},
|
|
4133
|
+
/** ADR-075: removes a fee; returns a refusal or null. */
|
|
4134
|
+
removeFee(id2) {
|
|
4135
|
+
if (locked()) return SALE_SAVING;
|
|
4136
|
+
try {
|
|
4137
|
+
builderNow.current.removeFee(id2);
|
|
4138
|
+
return null;
|
|
4139
|
+
} catch (error2) {
|
|
4140
|
+
if (error2 instanceof Error) return error2.message;
|
|
4141
|
+
throw error2;
|
|
4142
|
+
}
|
|
4143
|
+
},
|
|
4144
|
+
/** ADR-075: adds shipping; returns its id or the builder's refusal. */
|
|
4145
|
+
addShipping(input) {
|
|
4146
|
+
if (locked()) return SALE_SAVING;
|
|
4147
|
+
try {
|
|
4148
|
+
return { id: builderNow.current.addShipping(input) };
|
|
4149
|
+
} catch (error2) {
|
|
4150
|
+
if (error2 instanceof Error) return error2.message;
|
|
4151
|
+
throw error2;
|
|
4152
|
+
}
|
|
4153
|
+
},
|
|
4154
|
+
/** ADR-075: updates shipping; returns a refusal or null. */
|
|
4155
|
+
updateShipping(id2, patch) {
|
|
4156
|
+
if (locked()) return SALE_SAVING;
|
|
4157
|
+
try {
|
|
4158
|
+
builderNow.current.updateShipping(id2, patch);
|
|
4159
|
+
return null;
|
|
4160
|
+
} catch (error2) {
|
|
4161
|
+
if (error2 instanceof Error) return error2.message;
|
|
4162
|
+
throw error2;
|
|
4163
|
+
}
|
|
4164
|
+
},
|
|
4165
|
+
/** ADR-075: removes shipping; returns a refusal or null. */
|
|
4166
|
+
removeShipping(id2) {
|
|
4167
|
+
if (locked()) return SALE_SAVING;
|
|
4168
|
+
try {
|
|
4169
|
+
builderNow.current.removeShipping(id2);
|
|
4170
|
+
return null;
|
|
4171
|
+
} catch (error2) {
|
|
4172
|
+
if (error2 instanceof Error) return error2.message;
|
|
4173
|
+
throw error2;
|
|
4174
|
+
}
|
|
4175
|
+
},
|
|
4176
|
+
/** ADR-075: adds a distinct custom product line; returns its id or the builder's refusal. */
|
|
4177
|
+
addCustomLine(input) {
|
|
4178
|
+
if (locked()) return SALE_SAVING;
|
|
4179
|
+
try {
|
|
4180
|
+
return { id: builderNow.current.addLine({
|
|
4181
|
+
...input,
|
|
4182
|
+
custom: true,
|
|
4183
|
+
productId: `custom:${uuidv7()}`,
|
|
4184
|
+
unitPrice: { amount: input.priceMinor, currency: madeWith.current.currency.toUpperCase() }
|
|
4185
|
+
}) };
|
|
4186
|
+
} catch (error2) {
|
|
4187
|
+
if (error2 instanceof Error) return error2.message;
|
|
4188
|
+
throw error2;
|
|
4189
|
+
}
|
|
4190
|
+
},
|
|
3757
4191
|
/** Sets a line's unit price in minor units of its own tax mode, recomputing discounts and tax; returns a refusal or null. */
|
|
3758
4192
|
setUnitPrice(lineId, amountMinor) {
|
|
3759
4193
|
if (locked()) return SALE_SAVING;
|
|
@@ -3921,7 +4355,7 @@ function useSale(settings, opts) {
|
|
|
3921
4355
|
if (stageNow.current.kind !== "cart") return "Finish or cancel the payment before parking the sale";
|
|
3922
4356
|
const builder = builderNow.current;
|
|
3923
4357
|
const snapshot = builder.getSnapshot();
|
|
3924
|
-
if (!snapshot.lineItems.length) return "There is nothing to park";
|
|
4358
|
+
if (!snapshot.lineItems.length && !snapshot.fees?.length && !snapshot.shipping?.length) return "There is nothing to park";
|
|
3925
4359
|
await writeOrderDraft(opts.drafts, snapshot);
|
|
3926
4360
|
if (builderNow.current !== builder || builder.getSnapshot() !== snapshot) return "The sale changed while it was being parked; park it again";
|
|
3927
4361
|
resetSale(null);
|
|
@@ -3933,7 +4367,7 @@ function useSale(settings, opts) {
|
|
|
3933
4367
|
if (!opts.drafts) throw new Error("useSale: resume() needs the drafts option");
|
|
3934
4368
|
const live = builderNow.current;
|
|
3935
4369
|
const liveSnapshot = live.getSnapshot();
|
|
3936
|
-
if (stageNow.current.kind !== "cart" || liveSnapshot.lineItems.length) return "Park or clear the current sale first";
|
|
4370
|
+
if (stageNow.current.kind !== "cart" || liveSnapshot.lineItems.length || liveSnapshot.fees?.length || liveSnapshot.shipping?.length) return "Park or clear the current sale first";
|
|
3937
4371
|
const doc = await opts.drafts.findOne(draftId).exec();
|
|
3938
4372
|
if (!doc) return "That parked sale is no longer there";
|
|
3939
4373
|
const saved = JSON.parse(doc.toJSON().data);
|
|
@@ -4267,6 +4701,7 @@ function createOrderOutbox(options) {
|
|
|
4267
4701
|
const serverMax = firstSend && validMax(await Promise.resolve().then(() => options.getMaxOrderCreateVersion?.()).catch((cause) => {
|
|
4268
4702
|
outboxLogger.warn("Failed to read capabilities", { cause });
|
|
4269
4703
|
})) || 3;
|
|
4704
|
+
const localOrders = /* @__PURE__ */ new Map();
|
|
4270
4705
|
const batch = await Promise.all(orders.map(async (order) => {
|
|
4271
4706
|
const frozen = freezeSentForm(order);
|
|
4272
4707
|
const attempt2 = (attempts.get(order.commandId) ?? 0) + 1;
|
|
@@ -4276,14 +4711,15 @@ function createOrderOutbox(options) {
|
|
|
4276
4711
|
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: order.sentVersion ?? serverMax });
|
|
4277
4712
|
} catch (cause) {
|
|
4278
4713
|
if (!(cause instanceof UnsupportedOrderVersionError)) throw cause;
|
|
4279
|
-
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: 3 });
|
|
4714
|
+
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: cause.needed === 5 ? 5 : 3 });
|
|
4280
4715
|
}
|
|
4281
4716
|
if (frozen !== order || order.sentVersion === void 0) {
|
|
4282
4717
|
await (await collection.findOne(order.id).exec())?.incrementalModify((data) => ({ ...freezeSentForm(data), sentVersion: data.sentVersion ?? envelope2.version }));
|
|
4283
4718
|
}
|
|
4719
|
+
localOrders.set(envelope2.id, frozen);
|
|
4284
4720
|
return envelope2;
|
|
4285
4721
|
}));
|
|
4286
|
-
let outcome = await transport.send(batch);
|
|
4722
|
+
let outcome = await transport.send(batch, { local: { orders: localOrders } });
|
|
4287
4723
|
backendNotFound.record(outcome, now());
|
|
4288
4724
|
if (outcome.kind !== "retry" || outcome.reason !== "network") {
|
|
4289
4725
|
const at2 = now();
|