@tallyui/pos 3.2.1 → 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 +37 -8
- package/dist/index.js +304 -50
- package/package.json +6 -6
- package/src/index.ts +1 -1
- package/src/order/order-builder.ts +51 -19
- package/src/order/order-drafts.ts +1 -0
- package/src/order/types.ts +11 -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 +37 -6
- package/src/pos-order/schema.ts +41 -5
- package/src/pos-order/types.ts +9 -4
- package/src/receipt/build-receipt-data.ts +3 -1
- 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/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,6 +612,13 @@ 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 = [];
|
|
526
623
|
let fees = [];
|
|
527
624
|
let shipping = [];
|
|
@@ -542,6 +639,13 @@ function createOrderBuilder(options) {
|
|
|
542
639
|
const lineDiscountMinor = Math.min(grossMinor, recalcedDiscounts.reduce((sum, d) => sum + d.amountMinor, 0));
|
|
543
640
|
const discountMinor = lineDiscountMinor + orderDiscountMinor;
|
|
544
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
|
+
};
|
|
545
649
|
const combinedRate = line.taxLines.reduce((sum, tax) => sum + tax.ratePpm, 0);
|
|
546
650
|
const inclusiveTax = line.taxInclusive ? taxMicros(netMinor, combinedRate, true) : 0n;
|
|
547
651
|
let allocatedTax = 0n;
|
|
@@ -568,7 +672,7 @@ function createOrderBuilder(options) {
|
|
|
568
672
|
return Math.max(0, Math.min(discount.value, base));
|
|
569
673
|
}
|
|
570
674
|
function buildOrder() {
|
|
571
|
-
const lineAmounts = lineItems.map((li) => li.netMinor);
|
|
675
|
+
const lineAmounts = lineItems.map((li) => woo ? li.unitPriceMinor * li.quantity - li.discountMinor : li.netMinor);
|
|
572
676
|
const base = lineAmounts.reduce((sum, amount) => sum + Math.max(0, amount), 0);
|
|
573
677
|
let remaining = base;
|
|
574
678
|
const recalcedOrderDiscounts = orderDiscounts.map((d) => {
|
|
@@ -578,14 +682,15 @@ function createOrderBuilder(options) {
|
|
|
578
682
|
});
|
|
579
683
|
const shares = allocateOrderDiscount(lineAmounts, recalcedOrderDiscounts.reduce((sum, d) => sum + d.amountMinor, 0));
|
|
580
684
|
const lines = lineItems.map((li, index) => recalculateLine(li, shares[index]));
|
|
581
|
-
const
|
|
685
|
+
const wooTotals = woo ? woocommerceTotals(lines, fees, shipping, currency, woo.roundAtSubtotal, taxContext.pricesIncludeTax) : void 0;
|
|
686
|
+
const { subtotalMinor } = wooTotals ?? sumLines(lines, taxContext.rounding);
|
|
582
687
|
const charges = [...fees, ...shipping];
|
|
583
688
|
const taxedLines = [...lines, ...charges.map((charge) => ({
|
|
584
689
|
...charge,
|
|
585
690
|
taxInclusive: taxContext.pricesIncludeTax,
|
|
586
691
|
taxMicros: charge.taxLines.reduce((sum, tax) => sum + BigInt(tax.taxMicros), 0n).toString()
|
|
587
692
|
}))];
|
|
588
|
-
const { taxMinor, totalMinor } = sumLines(taxedLines, taxContext.rounding);
|
|
693
|
+
const { taxMinor, totalMinor } = wooTotals ?? sumLines(taxedLines, taxContext.rounding);
|
|
589
694
|
if (taxContext.rounding?.granularity === "per_rate_group_items" && taxedLines.some((li) => li.taxInclusive) && !perRateGroupWarned.has(taxContext)) {
|
|
590
695
|
perRateGroupWarned.add(taxContext);
|
|
591
696
|
taxLogger.warn("per_rate_group_items with inclusive lines: using per_order figures until the display has a rounding row (#287, #310)");
|
|
@@ -597,11 +702,11 @@ function createOrderBuilder(options) {
|
|
|
597
702
|
const discounts = returned ? [] : li.discounts.map((d) => ({
|
|
598
703
|
discountId: d.id,
|
|
599
704
|
...d.label !== void 0 ? { label: d.label } : {},
|
|
600
|
-
amountMinor:
|
|
705
|
+
amountMinor: displayAmount(li, d.amountMinor)
|
|
601
706
|
}));
|
|
602
|
-
const shareMinor =
|
|
603
|
-
const remainingMinor = toDisplayMode(li, li.netMinor, taxInclusive);
|
|
604
|
-
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);
|
|
605
710
|
return { line: { lineId: li.id, amountMinor, discounts }, shareMinor, remainingMinor, returned };
|
|
606
711
|
});
|
|
607
712
|
const displayLines = figures.map((f) => f.line);
|
|
@@ -610,8 +715,17 @@ function createOrderBuilder(options) {
|
|
|
610
715
|
(sum, line) => line.discounts.reduce((lineSum, d) => lineSum + d.amountMinor, sum),
|
|
611
716
|
orderDiscountDisplay
|
|
612
717
|
);
|
|
613
|
-
const
|
|
614
|
-
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
|
+
}
|
|
615
729
|
const converted = lines.flatMap((li, index) => li.taxInclusive === taxInclusive ? [] : [index]);
|
|
616
730
|
const conversions = converted.reduce((count, index) => count + [
|
|
617
731
|
lines[index].netMinor,
|
|
@@ -631,7 +745,7 @@ function createOrderBuilder(options) {
|
|
|
631
745
|
if (displayDiscount < 0) throw new Error(`Display discount ${displayDiscount} is negative (ADR-063)`);
|
|
632
746
|
for (const { line, shareMinor, returned } of figures) {
|
|
633
747
|
if (returned && line.discounts.length > 0) throw new Error(`Return line ${line.lineId} shows discount rows (ADR-063)`);
|
|
634
|
-
if (returned) continue;
|
|
748
|
+
if (returned || woo) continue;
|
|
635
749
|
const rows = line.discounts.reduce((sum, d) => sum + d.amountMinor, 0);
|
|
636
750
|
if (shareMinor < 0 || line.discounts.some((d) => d.amountMinor < 0) || line.amountMinor < rows + shareMinor) {
|
|
637
751
|
throw new Error(`Display line ${line.lineId} shows ${line.amountMinor}, less than its rows ${rows} and share ${shareMinor} (ADR-063)`);
|
|
@@ -645,8 +759,8 @@ function createOrderBuilder(options) {
|
|
|
645
759
|
totalMinor,
|
|
646
760
|
lines: displayLines,
|
|
647
761
|
orderDiscountMinor: orderDiscountDisplay,
|
|
648
|
-
...fees.length ? { fees:
|
|
649
|
-
...shipping.length ? { shipping:
|
|
762
|
+
...fees.length ? { fees: displayFees } : {},
|
|
763
|
+
...shipping.length ? { shipping: displayShipping } : {}
|
|
650
764
|
};
|
|
651
765
|
const paidMinor = payments.reduce((sum, p) => sum + p.amountMinor, 0);
|
|
652
766
|
const balanceDueMinor = Math.max(0, totalMinor - paidMinor);
|
|
@@ -679,11 +793,22 @@ function createOrderBuilder(options) {
|
|
|
679
793
|
function emit() {
|
|
680
794
|
subject.next(buildOrder());
|
|
681
795
|
}
|
|
682
|
-
function recalculateCharge(id2, input) {
|
|
796
|
+
function recalculateCharge(id2, input, shipping2 = false) {
|
|
683
797
|
if (!Number.isInteger(input.amountMinor)) throw new RangeError("Amount must be integer minor units");
|
|
684
798
|
if (input.amountMinor < 0) throw new RangeError("Amount must be >= 0");
|
|
685
799
|
if (!input.name.trim()) throw new RangeError("Name must not be empty");
|
|
686
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
|
+
}
|
|
687
812
|
const code = taxContext.getTaxRateCode?.(input.taxClass);
|
|
688
813
|
const line = recalculateLine({
|
|
689
814
|
id: id2,
|
|
@@ -712,7 +837,7 @@ function createOrderBuilder(options) {
|
|
|
712
837
|
taxStatus,
|
|
713
838
|
taxLines: line.taxLines,
|
|
714
839
|
netMinor: line.netMinor,
|
|
715
|
-
taxMicros:
|
|
840
|
+
taxMicros: line.taxMicros
|
|
716
841
|
};
|
|
717
842
|
}
|
|
718
843
|
function addLine(input) {
|
|
@@ -721,11 +846,11 @@ function createOrderBuilder(options) {
|
|
|
721
846
|
if (unitPrice.currency !== currency) throw new RangeError("Line currency must match " + currency);
|
|
722
847
|
if (!Number.isInteger(unitPrice.amount)) throw new RangeError("Price must be integer minor units");
|
|
723
848
|
if (!Number.isInteger(quantity) || quantity < 1) throw new RangeError("Quantity must be an integer >= 1");
|
|
724
|
-
const code = input.taxRates ? void 0 : taxContext.getTaxRateCode?.(input.taxClass);
|
|
725
|
-
const taxRates = input.taxStatus === "none" ? [{ ...code !== void 0 ? { code } : {}, ratePpm: 0 }] : 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) }];
|
|
726
851
|
const taxInclusive = unitPrice.taxInclusive ?? taxContext.pricesIncludeTax;
|
|
727
852
|
const existing = lineItems.find(
|
|
728
|
-
(li) => !input.custom && !li.custom && 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)
|
|
729
854
|
);
|
|
730
855
|
if (existing) {
|
|
731
856
|
lineItems = lineItems.map(
|
|
@@ -737,6 +862,7 @@ function createOrderBuilder(options) {
|
|
|
737
862
|
const lineId = uid();
|
|
738
863
|
const line = {
|
|
739
864
|
id: lineId,
|
|
865
|
+
...(input.custom ? input.taxClass !== void 0 : woo) ? { taxClass: input.taxClass } : {},
|
|
740
866
|
productId,
|
|
741
867
|
...input.custom ? { custom: true } : {},
|
|
742
868
|
...input.taxStatus === "none" ? { taxStatus: "none" } : {},
|
|
@@ -786,7 +912,7 @@ function createOrderBuilder(options) {
|
|
|
786
912
|
emit();
|
|
787
913
|
},
|
|
788
914
|
addShipping(input) {
|
|
789
|
-
const charge = { ...recalculateCharge(uid(), input), methodId: input.methodId };
|
|
915
|
+
const charge = { ...recalculateCharge(uid(), input, true), methodId: input.methodId };
|
|
790
916
|
shipping = [...shipping, charge];
|
|
791
917
|
emit();
|
|
792
918
|
return charge.id;
|
|
@@ -795,7 +921,7 @@ function createOrderBuilder(options) {
|
|
|
795
921
|
const charge = shipping.find((entry) => entry.id === id2);
|
|
796
922
|
if (!charge) throw new Error(`Unknown shipping ${id2}`);
|
|
797
923
|
const input = { ...charge, ...patch };
|
|
798
|
-
const updated = { ...recalculateCharge(id2, input), methodId: input.methodId };
|
|
924
|
+
const updated = { ...recalculateCharge(id2, input, true), methodId: input.methodId };
|
|
799
925
|
shipping = shipping.map((entry) => entry.id === id2 ? updated : entry);
|
|
800
926
|
emit();
|
|
801
927
|
},
|
|
@@ -820,7 +946,8 @@ function createOrderBuilder(options) {
|
|
|
820
946
|
imageUrl: traits.getImageUrl(doc),
|
|
821
947
|
unitPrice,
|
|
822
948
|
quantity: opts?.quantity,
|
|
823
|
-
taxClass: traits.getTaxClass?.(doc, opts?.variantId)
|
|
949
|
+
taxClass: traits.getTaxClass?.(doc, opts?.variantId),
|
|
950
|
+
...traits.getTaxStatus?.(doc, opts?.variantId) === "none" ? { taxStatus: "none" } : {}
|
|
824
951
|
});
|
|
825
952
|
},
|
|
826
953
|
updateQuantity(lineId, quantity) {
|
|
@@ -967,20 +1094,23 @@ function netDiscountMinor(line, pricesIncludeTax) {
|
|
|
967
1094
|
return roundMicrosToMinor(BigInt(line.discountMinor) * MICROS_PER_MINOR - taxBefore + taxAfter);
|
|
968
1095
|
}
|
|
969
1096
|
function contentVersion(order) {
|
|
1097
|
+
if (order.fees?.length || order.shipping?.length || order.lines.some((line) => line.custom)) return 5;
|
|
970
1098
|
return order.display && order.taxByRate ? 3 : order.lines.reduce((sum, line) => sum + line.discountMinor, 0) > 0 ? 2 : 1;
|
|
971
1099
|
}
|
|
972
1100
|
function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
973
1101
|
const grossMinor = order.lines.reduce((sum, line) => sum + line.discountMinor, 0);
|
|
974
1102
|
const content = contentVersion(order);
|
|
975
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);
|
|
976
1105
|
if (grossMinor > 0 && cap !== void 0 && cap < 2) throw new UnsupportedOrderVersionError(2, cap);
|
|
977
1106
|
const version = content === 3 && cap !== void 0 && cap >= 4 ? 4 : Math.min(content, cap ?? content);
|
|
978
|
-
const lineDiscounts = order.lines.map((line) => version
|
|
1107
|
+
const lineDiscounts = order.lines.map((line) => version >= 4 ? netDiscountMinor(line, order.pricesIncludeTax) : line.discountMinor);
|
|
979
1108
|
const discountMinor = lineDiscounts.reduce((sum, discount) => sum + discount, 0);
|
|
980
1109
|
const email = order.customer?.email;
|
|
981
1110
|
const id2 = order.customer?.id;
|
|
982
1111
|
const customerId = typeof id2 === "string" && id2.length > 0 && id2.length <= 64 ? id2 : void 0;
|
|
983
1112
|
const sessionId = order.sessionId ?? order.lateSessionId;
|
|
1113
|
+
const { fees: _fees, shipping: _shipping, ...storedDisplay } = { ...order.display };
|
|
984
1114
|
return {
|
|
985
1115
|
id: order.commandId,
|
|
986
1116
|
type: "order.create",
|
|
@@ -995,13 +1125,30 @@ function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
|
995
1125
|
pricesIncludeTax: order.pricesIncludeTax,
|
|
996
1126
|
lines: order.lines.map((line, i) => ({
|
|
997
1127
|
clientLineId: line.id,
|
|
998
|
-
variantId: line.variantId ?? line.productId,
|
|
1128
|
+
...line.custom ? { custom: { ...line.custom } } : { variantId: line.variantId ?? line.productId },
|
|
999
1129
|
title: line.name,
|
|
1000
1130
|
quantity: line.quantity,
|
|
1001
1131
|
unitPriceMinor: line.unitPriceMinor,
|
|
1002
1132
|
...line.taxInclusive !== void 0 ? { taxInclusive: line.taxInclusive } : {},
|
|
1003
1133
|
...lineDiscounts[i] > 0 ? { discountMinor: lineDiscounts[i] } : {}
|
|
1004
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
|
+
})) } : {},
|
|
1005
1152
|
payments: order.payments.map((payment) => ({
|
|
1006
1153
|
clientPaymentId: payment.id,
|
|
1007
1154
|
method: payment.method,
|
|
@@ -1015,11 +1162,16 @@ function toOrderCreateEnvelope(order, deviceId, attempt2 = 1, options) {
|
|
|
1015
1162
|
taxMinor: order.taxMinor,
|
|
1016
1163
|
totalMinor: order.totalMinor,
|
|
1017
1164
|
...version >= 3 ? {
|
|
1018
|
-
display: {
|
|
1019
|
-
|
|
1020
|
-
amountMinor,
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
+
},
|
|
1023
1175
|
taxByRate: order.taxByRate.map(({ ratePpm, code, netMinor, amountMinor, grossMinor: grossMinor2 }) => ({
|
|
1024
1176
|
ratePpm,
|
|
1025
1177
|
...code !== void 0 ? { code } : {},
|
|
@@ -1080,6 +1232,7 @@ function restoreOrderDraft(saved, options) {
|
|
|
1080
1232
|
productId: line.productId,
|
|
1081
1233
|
custom: line.custom,
|
|
1082
1234
|
taxStatus: line.taxStatus,
|
|
1235
|
+
...taxContext.rounding?.granularity === "woocommerce" ? { taxClass: line.taxClass } : {},
|
|
1083
1236
|
variantId: line.variantId,
|
|
1084
1237
|
name: line.name,
|
|
1085
1238
|
sku: line.sku,
|
|
@@ -1199,7 +1352,7 @@ function useParkedSales(drafts) {
|
|
|
1199
1352
|
|
|
1200
1353
|
// src/receipt/build-receipt-data.ts
|
|
1201
1354
|
function buildReceiptData(order, config) {
|
|
1202
|
-
const taxedLines = [...order.lineItems, ...[...order.fees ?? [], ...order.shipping ?? []].map((charge) => ({
|
|
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) => ({
|
|
1203
1356
|
...charge,
|
|
1204
1357
|
taxInclusive: order.pricesIncludeTax
|
|
1205
1358
|
}))];
|
|
@@ -1211,6 +1364,7 @@ function buildReceiptData(order, config) {
|
|
|
1211
1364
|
0n
|
|
1212
1365
|
);
|
|
1213
1366
|
const lineTotals = order.lineItems.map((li) => {
|
|
1367
|
+
if (order.taxRounding?.granularity === "woocommerce" && li.netMicros !== void 0) return order.pricesIncludeTax ? li.totalMinor : li.netMinor;
|
|
1214
1368
|
if (!li.priceTaxModeConverted) return li.netMinor;
|
|
1215
1369
|
const share = roundMicrosToMinor(taxSoFar + BigInt(li.taxMicros)) - roundMicrosToMinor(taxSoFar);
|
|
1216
1370
|
taxSoFar += BigInt(li.taxMicros);
|
|
@@ -1354,7 +1508,7 @@ function uuidv7(now, randomBytes) {
|
|
|
1354
1508
|
}
|
|
1355
1509
|
|
|
1356
1510
|
// src/pos-order/finalize.ts
|
|
1357
|
-
import { minorUnitDigits } from "@tallyui/core";
|
|
1511
|
+
import { minorUnitDigits as minorUnitDigits2 } from "@tallyui/core";
|
|
1358
1512
|
|
|
1359
1513
|
// src/outbox/logger.ts
|
|
1360
1514
|
var outboxLogger = createLogger("outbox");
|
|
@@ -1369,8 +1523,12 @@ function withSentForm(order, posOrder) {
|
|
|
1369
1523
|
...order,
|
|
1370
1524
|
customer,
|
|
1371
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 })) } : {},
|
|
1372
1528
|
display: posOrder.display ? {
|
|
1373
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 })) } : {},
|
|
1374
1532
|
lines: order.display.lines.map((line, i) => ({
|
|
1375
1533
|
...line,
|
|
1376
1534
|
discounts: line.discounts.map((discount, j) => ({ ...discount, label: posOrder.display.lines[i].discounts[j].label }))
|
|
@@ -1383,7 +1541,15 @@ function freezeSentForm(order) {
|
|
|
1383
1541
|
const lines = order.lines.map((line) => {
|
|
1384
1542
|
const name = cutText(line.name, PAYLOAD_STRING_MAX);
|
|
1385
1543
|
changed ||= name !== line.name;
|
|
1386
|
-
|
|
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 };
|
|
1387
1553
|
});
|
|
1388
1554
|
const display = order.display && { ...order.display, lines: order.display.lines.map((line) => ({
|
|
1389
1555
|
...line,
|
|
@@ -1394,6 +1560,11 @@ function freezeSentForm(order) {
|
|
|
1394
1560
|
return { ...discount, label };
|
|
1395
1561
|
})
|
|
1396
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
|
+
});
|
|
1397
1568
|
const payments = order.payments.map((payment) => {
|
|
1398
1569
|
if (payment.reference === void 0) return payment;
|
|
1399
1570
|
const reference = cutText(payment.reference, PAYLOAD_STRING_MAX);
|
|
@@ -1409,7 +1580,16 @@ function freezeSentForm(order) {
|
|
|
1409
1580
|
if (!localWarnings.some((warning) => warning.code === "customer_omitted" && warning.field === field)) localWarnings.push({ code: "customer_omitted", field });
|
|
1410
1581
|
}
|
|
1411
1582
|
}
|
|
1412
|
-
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;
|
|
1413
1593
|
}
|
|
1414
1594
|
function referenceError(label, value) {
|
|
1415
1595
|
const reason = referenceReason(value);
|
|
@@ -1421,8 +1601,8 @@ function referenceReason(value) {
|
|
|
1421
1601
|
return value.includes("\0") ? "nul" : null;
|
|
1422
1602
|
}
|
|
1423
1603
|
function finalizeOrder(order, options = {}) {
|
|
1424
|
-
if (order.fees?.length || order.shipping?.length || order.lineItems.some((line) => line.custom)) {
|
|
1425
|
-
throw new Error("finalize: fees, shipping and custom lines need order.create version 5
|
|
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");
|
|
1426
1606
|
}
|
|
1427
1607
|
if (!order.lineItems.length) throw new Error("finalize: no lines");
|
|
1428
1608
|
if (order.discountMinor < 0 || order.lineItems.some((line) => line.discountMinor < 0) || order.discounts.some((d) => d.amountMinor < 0)) {
|
|
@@ -1465,15 +1645,25 @@ function finalizeOrder(order, options = {}) {
|
|
|
1465
1645
|
id: newId(),
|
|
1466
1646
|
productId: line.productId,
|
|
1467
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 } : {},
|
|
1468
1655
|
name: line.name,
|
|
1469
1656
|
sku: line.sku,
|
|
1470
1657
|
quantity: line.quantity,
|
|
1471
1658
|
unitPriceMinor: line.unitPriceMinor,
|
|
1472
1659
|
discountMinor: line.discountMinor,
|
|
1473
1660
|
netMinor: line.netMinor,
|
|
1661
|
+
...order.taxRounding?.granularity === "woocommerce" ? { netMicros: line.netMicros } : {},
|
|
1474
1662
|
taxLines: line.taxLines.map((tax) => ({ ...tax })),
|
|
1475
1663
|
...line.priceTaxModeConverted ? { taxInclusive: line.taxInclusive } : {}
|
|
1476
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 })) }));
|
|
1477
1667
|
const payments = order.payments.map((payment) => ({
|
|
1478
1668
|
id: newId(),
|
|
1479
1669
|
method: payment.method,
|
|
@@ -1511,8 +1701,10 @@ function finalizeOrder(order, options = {}) {
|
|
|
1511
1701
|
}
|
|
1512
1702
|
display = {
|
|
1513
1703
|
currency: order.currency,
|
|
1514
|
-
exponent:
|
|
1704
|
+
exponent: minorUnitDigits2(order.currency),
|
|
1515
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 })) } : {},
|
|
1516
1708
|
lines: order.display.lines.map((line, i) => ({
|
|
1517
1709
|
lineId: lines[i].id,
|
|
1518
1710
|
amountMinor: line.amountMinor,
|
|
@@ -1523,7 +1715,8 @@ function finalizeOrder(order, options = {}) {
|
|
|
1523
1715
|
}))
|
|
1524
1716
|
}))
|
|
1525
1717
|
};
|
|
1526
|
-
|
|
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 }) => ({
|
|
1527
1720
|
ratePpm,
|
|
1528
1721
|
...code !== void 0 ? { code } : {},
|
|
1529
1722
|
netMinor,
|
|
@@ -1538,7 +1731,7 @@ function finalizeOrder(order, options = {}) {
|
|
|
1538
1731
|
}
|
|
1539
1732
|
}
|
|
1540
1733
|
const rounding = order.taxRounding ?? DEFAULT_TAX_ROUNDING;
|
|
1541
|
-
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 };
|
|
1542
1735
|
const now = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1543
1736
|
return freezeSentForm({
|
|
1544
1737
|
id: id2,
|
|
@@ -1551,6 +1744,8 @@ function finalizeOrder(order, options = {}) {
|
|
|
1551
1744
|
pricesIncludeTax: order.pricesIncludeTax,
|
|
1552
1745
|
lines,
|
|
1553
1746
|
payments,
|
|
1747
|
+
...fees?.length ? { fees } : {},
|
|
1748
|
+
...shipping?.length ? { shipping } : {},
|
|
1554
1749
|
subtotalMinor: order.subtotalMinor,
|
|
1555
1750
|
discountMinor: order.discountMinor,
|
|
1556
1751
|
taxMinor: order.taxMinor,
|
|
@@ -1573,7 +1768,7 @@ function finalizeOrder(order, options = {}) {
|
|
|
1573
1768
|
import { addRxPlugin } from "rxdb";
|
|
1574
1769
|
import { RxDBMigrationSchemaPlugin } from "rxdb/plugins/migration-schema";
|
|
1575
1770
|
var posOrderSchema = {
|
|
1576
|
-
version:
|
|
1771
|
+
version: 8,
|
|
1577
1772
|
primaryKey: "id",
|
|
1578
1773
|
type: "object",
|
|
1579
1774
|
additionalProperties: false,
|
|
@@ -1603,9 +1798,17 @@ var posOrderSchema = {
|
|
|
1603
1798
|
name: { type: "string" },
|
|
1604
1799
|
sku: { type: "string" },
|
|
1605
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"] },
|
|
1606
1808
|
unitPriceMinor: { type: "integer" },
|
|
1607
1809
|
discountMinor: { type: "integer" },
|
|
1608
1810
|
netMinor: { type: "integer" },
|
|
1811
|
+
netMicros: { type: "string", pattern: "^-?[0-9]{1,24}$", maxLength: 25 },
|
|
1609
1812
|
taxLines: { type: "array", items: {
|
|
1610
1813
|
type: "object",
|
|
1611
1814
|
properties: { code: { type: "string" }, ratePpm: { type: "integer" }, taxMicros: { type: "string" } },
|
|
@@ -1614,6 +1817,43 @@ var posOrderSchema = {
|
|
|
1614
1817
|
},
|
|
1615
1818
|
required: ["id", "productId", "name", "sku", "quantity", "unitPriceMinor", "discountMinor", "netMinor", "taxLines"]
|
|
1616
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"] } },
|
|
1617
1857
|
payments: { type: "array", items: {
|
|
1618
1858
|
type: "object",
|
|
1619
1859
|
properties: {
|
|
@@ -1651,11 +1891,12 @@ var posOrderSchema = {
|
|
|
1651
1891
|
}, required: ["since", "reason", "isolated"] },
|
|
1652
1892
|
lateSessionId: { type: "string" },
|
|
1653
1893
|
taxRounding: { type: "object", additionalProperties: false, properties: {
|
|
1654
|
-
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" },
|
|
1655
1896
|
mode: { type: "string", enum: ["half_away_from_zero", "half_up"] }
|
|
1656
1897
|
}, required: ["granularity"] },
|
|
1657
|
-
sentVersion: { type: "integer", minimum: 1, maximum:
|
|
1658
|
-
downgradedFrom: { type: "integer", minimum: 1, maximum:
|
|
1898
|
+
sentVersion: { type: "integer", minimum: 1, maximum: 5 },
|
|
1899
|
+
downgradedFrom: { type: "integer", minimum: 1, maximum: 5 },
|
|
1659
1900
|
// The nested objects are closed too: loosening a schema later is free, tightening one costs a migration.
|
|
1660
1901
|
display: { type: "object", additionalProperties: false, properties: {
|
|
1661
1902
|
currency: { type: "string" },
|
|
@@ -1666,6 +1907,16 @@ var posOrderSchema = {
|
|
|
1666
1907
|
taxMinor: { type: "integer" },
|
|
1667
1908
|
totalMinor: { type: "integer" },
|
|
1668
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"] } },
|
|
1669
1920
|
lines: { type: "array", items: {
|
|
1670
1921
|
type: "object",
|
|
1671
1922
|
additionalProperties: false,
|
|
@@ -1728,7 +1979,7 @@ function posOrderCollection() {
|
|
|
1728
1979
|
};
|
|
1729
1980
|
return {
|
|
1730
1981
|
schema: posOrderSchema,
|
|
1731
|
-
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 }
|
|
1732
1983
|
};
|
|
1733
1984
|
}
|
|
1734
1985
|
|
|
@@ -2628,8 +2879,8 @@ async function writeClosure({
|
|
|
2628
2879
|
const roundings = /* @__PURE__ */ new Set();
|
|
2629
2880
|
for (const order of bound) {
|
|
2630
2881
|
const recorded = order.taxRounding ?? DEFAULT_TAX_ROUNDING;
|
|
2631
|
-
const
|
|
2632
|
-
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}`);
|
|
2633
2884
|
const lines = order.lines.map((line) => ({ ...line, taxInclusive: line.taxInclusive ?? order.pricesIncludeTax }));
|
|
2634
2885
|
for (const { ratePpm, netMinor, amountMinor } of taxLinesByRate(lines, order.taxMinor, void 0, order.taxRounding)) {
|
|
2635
2886
|
const existing2 = taxRates.get(String(ratePpm));
|
|
@@ -2998,7 +3249,7 @@ function envelope(row, context, xreport = false) {
|
|
|
2998
3249
|
});
|
|
2999
3250
|
return {
|
|
3000
3251
|
closure: {
|
|
3001
|
-
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,
|
|
3002
3253
|
has_perpetual: row.perpetual_sales_total != null || row.perpetual_refunds_total != null,
|
|
3003
3254
|
...Object.fromEntries(
|
|
3004
3255
|
["payment_methods", "tax_rates", "movements"].map((key) => [`has_${key}`, Object.keys(breakdowns[key] ?? {}).length > 0])
|
|
@@ -3659,7 +3910,8 @@ function addEntryToCart(builder, entry, traits, currency) {
|
|
|
3659
3910
|
sku: variant.sku ?? "",
|
|
3660
3911
|
imageUrl: traits.getImageUrl?.(product),
|
|
3661
3912
|
unitPrice,
|
|
3662
|
-
taxClass: traits.getTaxClass?.(product, variant.id)
|
|
3913
|
+
taxClass: traits.getTaxClass?.(product, variant.id),
|
|
3914
|
+
...traits.getTaxStatus?.(product, variant.id) === "none" ? { taxStatus: "none" } : {}
|
|
3663
3915
|
});
|
|
3664
3916
|
}
|
|
3665
3917
|
|
|
@@ -4449,6 +4701,7 @@ function createOrderOutbox(options) {
|
|
|
4449
4701
|
const serverMax = firstSend && validMax(await Promise.resolve().then(() => options.getMaxOrderCreateVersion?.()).catch((cause) => {
|
|
4450
4702
|
outboxLogger.warn("Failed to read capabilities", { cause });
|
|
4451
4703
|
})) || 3;
|
|
4704
|
+
const localOrders = /* @__PURE__ */ new Map();
|
|
4452
4705
|
const batch = await Promise.all(orders.map(async (order) => {
|
|
4453
4706
|
const frozen = freezeSentForm(order);
|
|
4454
4707
|
const attempt2 = (attempts.get(order.commandId) ?? 0) + 1;
|
|
@@ -4458,14 +4711,15 @@ function createOrderOutbox(options) {
|
|
|
4458
4711
|
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: order.sentVersion ?? serverMax });
|
|
4459
4712
|
} catch (cause) {
|
|
4460
4713
|
if (!(cause instanceof UnsupportedOrderVersionError)) throw cause;
|
|
4461
|
-
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: 3 });
|
|
4714
|
+
envelope2 = toOrderCreateEnvelope(frozen, deviceId, attempt2, { maxVersion: cause.needed === 5 ? 5 : 3 });
|
|
4462
4715
|
}
|
|
4463
4716
|
if (frozen !== order || order.sentVersion === void 0) {
|
|
4464
4717
|
await (await collection.findOne(order.id).exec())?.incrementalModify((data) => ({ ...freezeSentForm(data), sentVersion: data.sentVersion ?? envelope2.version }));
|
|
4465
4718
|
}
|
|
4719
|
+
localOrders.set(envelope2.id, frozen);
|
|
4466
4720
|
return envelope2;
|
|
4467
4721
|
}));
|
|
4468
|
-
let outcome = await transport.send(batch);
|
|
4722
|
+
let outcome = await transport.send(batch, { local: { orders: localOrders } });
|
|
4469
4723
|
backendNotFound.record(outcome, now());
|
|
4470
4724
|
if (outcome.kind !== "retry" || outcome.reason !== "network") {
|
|
4471
4725
|
const at2 = now();
|