@tallyui/pos 3.7.1 → 3.9.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 +167 -16
- package/dist/index.js +487 -49
- package/package.json +8 -7
- package/src/index.ts +5 -3
- package/src/order/index.ts +2 -1
- package/src/order/order-builder.ts +41 -9
- package/src/order/order-drafts.ts +3 -1
- package/src/order/types.ts +11 -1
- package/src/order/woocommerce-tax.ts +61 -4
- package/src/outbox/register-outbox.ts +26 -3
- package/src/outbox/types.test-d.ts +5 -0
- package/src/outbox/use-register-outbox.ts +6 -2
- package/src/refund/index.ts +3 -0
- package/src/refund/pos-refund.ts +35 -0
- package/src/refund/send-order-refund.ts +43 -0
- package/src/refund/submit-order-refund.ts +85 -0
- package/src/register/expected.ts +23 -6
- package/src/register/index.ts +2 -1
- package/src/register/register-adoption.ts +105 -0
- package/src/register/register-commands.ts +20 -7
- package/src/register/schemas.ts +3 -3
- package/src/register/session-store.ts +42 -6
- package/src/register/use-register-session.ts +64 -19
package/dist/index.js
CHANGED
|
@@ -110,7 +110,7 @@ function useCurrencyCode() {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// src/order/woocommerce-tax.ts
|
|
113
|
-
import { minorUnitDigits, woocommerceTax } from "@tallyui/core";
|
|
113
|
+
import { minorUnitDigits, woocommerceTax, woocommerceCoupons } from "@tallyui/core";
|
|
114
114
|
function woocommerceLine(amountMinor, rates, inclusive, currency, roundAtSubtotal, pricesIncludeTax = inclusive) {
|
|
115
115
|
const dp = minorUnitDigits(currency), factor = 10 ** dp;
|
|
116
116
|
const precision = woocommerceTax.getRoundingPrecision(dp);
|
|
@@ -124,17 +124,21 @@ function woocommerceLine(amountMinor, rates, inclusive, currency, roundAtSubtota
|
|
|
124
124
|
let result = woocommerceTax.calculateTaxes(input);
|
|
125
125
|
const net = woocommerceTax.roundHalfUp(input.amount - (inclusive ? result.total : 0), precision);
|
|
126
126
|
if (inclusive && rates.length) result = woocommerceTax.calculateTaxes({ ...input, amount: net, amountIncludesTax: false });
|
|
127
|
-
const taxLines = result.taxes.map((
|
|
128
|
-
const rate = rates.find((rate2) => rate2.id ===
|
|
127
|
+
const taxLines = result.taxes.map((tax) => {
|
|
128
|
+
const rate = rates.find((rate2) => rate2.id === tax.id);
|
|
129
129
|
return {
|
|
130
130
|
code: rate.code,
|
|
131
131
|
rateId: rate.id,
|
|
132
132
|
compound: rate.compound,
|
|
133
133
|
ratePpm: Math.round(Number(rate.rate) * 1e4),
|
|
134
|
-
taxMicros: String(Math.round(
|
|
134
|
+
taxMicros: String(Math.round(tax.total * factor * 1e6))
|
|
135
135
|
};
|
|
136
136
|
});
|
|
137
|
-
|
|
137
|
+
return lineAmounts(net, result.taxes, taxLines, dp, roundAtSubtotal, pricesIncludeTax);
|
|
138
|
+
}
|
|
139
|
+
function lineAmounts(net, taxes, taxLines, dp, roundAtSubtotal, pricesIncludeTax) {
|
|
140
|
+
const factor = 10 ** dp;
|
|
141
|
+
const tax = taxes.reduce((sum, rate) => sum + (roundAtSubtotal ? rate.total : woocommerceTax.roundTaxTotal(rate.total, dp, pricesIncludeTax)), 0);
|
|
138
142
|
return {
|
|
139
143
|
netMinor: Math.round(woocommerceTax.roundHalfUp(net, dp) * factor),
|
|
140
144
|
netMicros: String(Math.round(net * factor * 1e6)),
|
|
@@ -143,6 +147,69 @@ function woocommerceLine(amountMinor, rates, inclusive, currency, roundAtSubtota
|
|
|
143
147
|
taxMicros: taxLines.reduce((sum, rate) => sum + BigInt(rate.taxMicros), 0n).toString()
|
|
144
148
|
};
|
|
145
149
|
}
|
|
150
|
+
function woocommerceCouponReplay(lines, codes, context, getTaxRates, currency, roundAtSubtotal, pricesIncludeTax) {
|
|
151
|
+
const dp = minorUnitDigits(currency), factor = 10 ** dp;
|
|
152
|
+
const inputLines = lines.filter((line) => line.unitPriceMinor >= 0);
|
|
153
|
+
const lineItems = inputLines.map((line) => {
|
|
154
|
+
const productId = Number(line.productId), variationId = Number(line.variantId);
|
|
155
|
+
const net = (Number(line.netMicros) / (factor * 1e6)).toFixed(6);
|
|
156
|
+
const taxes = line.taxLines.map((tax2) => {
|
|
157
|
+
const amount = (Number(tax2.taxMicros) / (factor * 1e6)).toFixed(6);
|
|
158
|
+
return { id: tax2.rateId, subtotal: amount, total: amount };
|
|
159
|
+
});
|
|
160
|
+
const tax = taxes.reduce((sum, row) => sum + Number(row.total), 0).toFixed(6);
|
|
161
|
+
return {
|
|
162
|
+
product_id: !line.custom && Number.isSafeInteger(productId) && productId >= 0 ? productId : 0,
|
|
163
|
+
...Number.isSafeInteger(variationId) && variationId >= 0 ? { variation_id: variationId } : {},
|
|
164
|
+
quantity: line.quantity,
|
|
165
|
+
tax_class: line.taxClass ?? "",
|
|
166
|
+
...line.taxStatus === "none" ? { tax_status: "none" } : {},
|
|
167
|
+
subtotal: net,
|
|
168
|
+
total: net,
|
|
169
|
+
taxes,
|
|
170
|
+
subtotal_tax: tax,
|
|
171
|
+
total_tax: tax
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
const taxRates = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const line of inputLines) for (const rate of getTaxRates(line.taxClass)) {
|
|
176
|
+
const taxClass = woocommerceTax.normalizeTaxClass(line.taxClass);
|
|
177
|
+
taxRates.set(JSON.stringify([rate.id, taxClass]), {
|
|
178
|
+
id: rate.id,
|
|
179
|
+
rate: rate.rate,
|
|
180
|
+
compound: rate.compound,
|
|
181
|
+
order: rate.priority,
|
|
182
|
+
priority: rate.priority,
|
|
183
|
+
class: taxClass
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const result = woocommerceCoupons.recalculateCoupons({
|
|
187
|
+
lineItems,
|
|
188
|
+
couponLines: codes.map((code) => ({ code })),
|
|
189
|
+
couponConfigs: new Map(codes.map((code) => [code, context.configs.get(code)])),
|
|
190
|
+
pricesIncludeTax,
|
|
191
|
+
calcDiscountsSequentially: context.calcDiscountsSequentially,
|
|
192
|
+
productCategories: new Map(context.productCategories),
|
|
193
|
+
taxRates: [...taxRates.values()],
|
|
194
|
+
taxRoundAtSubtotal: roundAtSubtotal,
|
|
195
|
+
dp
|
|
196
|
+
});
|
|
197
|
+
let index = 0;
|
|
198
|
+
return {
|
|
199
|
+
lines: lines.map((line) => {
|
|
200
|
+
if (line.unitPriceMinor < 0) return line;
|
|
201
|
+
const item = result.lineItems[index++];
|
|
202
|
+
const taxes = line.taxLines.map((tax) => ({ total: Number(item.taxes?.find((row) => row.id === tax.rateId)?.total ?? "0") }));
|
|
203
|
+
const taxLines = line.taxLines.map((tax, index2) => ({ ...tax, taxMicros: String(Math.round(taxes[index2].total * factor * 1e6)) }));
|
|
204
|
+
return { ...line, ...lineAmounts(Number(item.total), taxes, taxLines, dp, roundAtSubtotal, pricesIncludeTax) };
|
|
205
|
+
}),
|
|
206
|
+
coupons: result.couponLines.map((coupon, index2) => ({
|
|
207
|
+
code: codes[index2],
|
|
208
|
+
discountMinor: Math.round(woocommerceTax.roundHalfUp(Number(coupon.discount), dp) * factor),
|
|
209
|
+
discountTaxMinor: Math.round(woocommerceTax.roundHalfUp(Number(coupon.discount_tax), dp) * factor)
|
|
210
|
+
}))
|
|
211
|
+
};
|
|
212
|
+
}
|
|
146
213
|
function woocommerceTotals(lines, fees, shipping, currency, roundAtSubtotal, inclusive) {
|
|
147
214
|
const dp = minorUnitDigits(currency), factor = 10 ** dp;
|
|
148
215
|
const net = (line) => Number(line.netMicros) / (factor * 1e6);
|
|
@@ -611,7 +678,7 @@ function assertDisplayResidue(residue, convertedLines, conversions) {
|
|
|
611
678
|
if (Math.abs(residue) > bound) throw new Error(`Display residue ${residue} exceeds its bound of ${bound} (ADR-063)`);
|
|
612
679
|
}
|
|
613
680
|
function createOrderBuilder(options) {
|
|
614
|
-
const { taxContext } = options;
|
|
681
|
+
const { taxContext, couponContext } = options;
|
|
615
682
|
const currency = options.currency.toUpperCase();
|
|
616
683
|
const orderId = options.id ?? uid();
|
|
617
684
|
const woo = taxContext.rounding?.granularity === "woocommerce" && taxContext.getTaxRates ? taxContext.rounding : void 0;
|
|
@@ -628,6 +695,7 @@ function createOrderBuilder(options) {
|
|
|
628
695
|
let payments = [];
|
|
629
696
|
let customer = null;
|
|
630
697
|
let note = "";
|
|
698
|
+
let couponCodes = [];
|
|
631
699
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
632
700
|
const subject = new BehaviorSubject(buildOrder());
|
|
633
701
|
function recalculateLine(line, orderDiscountMinor = 0) {
|
|
@@ -674,16 +742,26 @@ function createOrderBuilder(options) {
|
|
|
674
742
|
return Math.max(0, Math.min(discount.value, base));
|
|
675
743
|
}
|
|
676
744
|
function buildOrder() {
|
|
677
|
-
const
|
|
678
|
-
const base =
|
|
745
|
+
const lineAmounts2 = lineItems.map((li) => woo ? li.unitPriceMinor * li.quantity - li.discountMinor : li.netMinor);
|
|
746
|
+
const base = lineAmounts2.reduce((sum, amount) => sum + Math.max(0, amount), 0);
|
|
679
747
|
let remaining = base;
|
|
680
748
|
const recalcedOrderDiscounts = orderDiscounts.map((d) => {
|
|
681
749
|
const amountMinor = Math.min(remaining, computeDiscountAmount(d, d.type === "percentage" ? base : remaining));
|
|
682
750
|
remaining -= amountMinor;
|
|
683
751
|
return { ...d, amountMinor };
|
|
684
752
|
});
|
|
685
|
-
const shares = allocateOrderDiscount(
|
|
686
|
-
const
|
|
753
|
+
const shares = allocateOrderDiscount(lineAmounts2, recalcedOrderDiscounts.reduce((sum, d) => sum + d.amountMinor, 0));
|
|
754
|
+
const displaySourceLines = lineItems.map((li, index) => recalculateLine(li, shares[index]));
|
|
755
|
+
const replay = woo && couponCodes.length ? woocommerceCouponReplay(
|
|
756
|
+
displaySourceLines,
|
|
757
|
+
couponCodes,
|
|
758
|
+
couponContext,
|
|
759
|
+
taxContext.getTaxRates,
|
|
760
|
+
currency,
|
|
761
|
+
woo.roundAtSubtotal,
|
|
762
|
+
taxContext.pricesIncludeTax
|
|
763
|
+
) : void 0;
|
|
764
|
+
const lines = replay?.lines ?? displaySourceLines;
|
|
687
765
|
if (woo) shipping = shipping.map((charge) => ({ ...charge, ...recalculateCharge(charge.id, charge, true) }));
|
|
688
766
|
const wooTotals = woo ? woocommerceTotals(lines, fees, shipping, currency, woo.roundAtSubtotal, taxContext.pricesIncludeTax) : void 0;
|
|
689
767
|
const { subtotalMinor } = wooTotals ?? sumLines(lines, taxContext.rounding);
|
|
@@ -700,7 +778,7 @@ function createOrderBuilder(options) {
|
|
|
700
778
|
}
|
|
701
779
|
const discountMinor = lines.reduce((sum, li) => sum + li.discountMinor, 0);
|
|
702
780
|
const taxInclusive = taxContext.pricesIncludeTax;
|
|
703
|
-
const figures =
|
|
781
|
+
const figures = displaySourceLines.map((li) => {
|
|
704
782
|
const returned = li.unitPriceMinor < 0;
|
|
705
783
|
const discounts = returned ? [] : li.discounts.map((d) => ({
|
|
706
784
|
discountId: d.id,
|
|
@@ -714,10 +792,11 @@ function createOrderBuilder(options) {
|
|
|
714
792
|
});
|
|
715
793
|
const displayLines = figures.map((f) => f.line);
|
|
716
794
|
const orderDiscountDisplay = figures.reduce((sum, f) => sum + f.shareMinor, 0);
|
|
795
|
+
const displayCoupons = replay?.coupons.map(({ code, discountMinor: discountMinor2, discountTaxMinor }) => ({ code, amountMinor: discountMinor2 + (taxInclusive ? discountTaxMinor : 0) }));
|
|
717
796
|
const displayDiscount = displayLines.reduce(
|
|
718
797
|
(sum, line) => line.discounts.reduce((lineSum, d) => lineSum + d.amountMinor, sum),
|
|
719
798
|
orderDiscountDisplay
|
|
720
|
-
);
|
|
799
|
+
) + (displayCoupons?.reduce((sum, row) => sum + row.amountMinor, 0) ?? 0);
|
|
721
800
|
const chargeRows = (items) => items.map(({ id: id2, name, amountMinor, netMinor, totalMinor: totalMinor2 }) => ({ id: id2, name, amountMinor: woo ? taxInclusive ? totalMinor2 : netMinor : amountMinor }));
|
|
722
801
|
const displayFees = chargeRows(fees), displayShipping = chargeRows(shipping);
|
|
723
802
|
let displaySubtotal = (taxInclusive ? totalMinor : totalMinor - taxMinor) + displayDiscount - [...displayFees, ...displayShipping].reduce((sum, charge) => sum + charge.amountMinor, 0);
|
|
@@ -729,11 +808,11 @@ function createOrderBuilder(options) {
|
|
|
729
808
|
if (charge) displaySubtotal -= residue;
|
|
730
809
|
residue = displaySubtotal - displayLines.reduce((sum, line) => sum + line.amountMinor, 0);
|
|
731
810
|
}
|
|
732
|
-
const converted =
|
|
811
|
+
const converted = displaySourceLines.flatMap((li, index) => li.taxInclusive === taxInclusive ? [] : [index]);
|
|
733
812
|
const conversions = converted.reduce((count, index) => count + [
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
...
|
|
813
|
+
displaySourceLines[index].netMinor,
|
|
814
|
+
displaySourceLines[index].orderDiscountMinor,
|
|
815
|
+
...displaySourceLines[index].discounts.map((d) => d.amountMinor)
|
|
737
816
|
].filter((x) => x !== 0).length, 0);
|
|
738
817
|
assertDisplayResidue(residue, converted.length, conversions);
|
|
739
818
|
let left = residue;
|
|
@@ -762,6 +841,7 @@ function createOrderBuilder(options) {
|
|
|
762
841
|
totalMinor,
|
|
763
842
|
lines: displayLines,
|
|
764
843
|
orderDiscountMinor: orderDiscountDisplay,
|
|
844
|
+
...displayCoupons ? { coupons: displayCoupons } : {},
|
|
765
845
|
...fees.length ? { fees: displayFees } : {},
|
|
766
846
|
...shipping.length ? { shipping: displayShipping } : {}
|
|
767
847
|
};
|
|
@@ -772,6 +852,7 @@ function createOrderBuilder(options) {
|
|
|
772
852
|
id: orderId,
|
|
773
853
|
status: "draft",
|
|
774
854
|
lineItems: lines,
|
|
855
|
+
...replay ? { coupons: replay.coupons } : {},
|
|
775
856
|
...fees.length ? { fees: [...fees] } : {},
|
|
776
857
|
...shipping.length ? { shipping: [...shipping] } : {},
|
|
777
858
|
discounts: recalcedOrderDiscounts,
|
|
@@ -1038,6 +1119,17 @@ function createOrderBuilder(options) {
|
|
|
1038
1119
|
note = n;
|
|
1039
1120
|
emit();
|
|
1040
1121
|
},
|
|
1122
|
+
setCoupons(codes) {
|
|
1123
|
+
const normalized = [...new Set(codes.map((code) => code.trim().toLowerCase()))];
|
|
1124
|
+
if (normalized.length && !couponContext) throw new Error("Coupon context is required");
|
|
1125
|
+
for (const code of normalized) {
|
|
1126
|
+
const config = couponContext.configs.get(code);
|
|
1127
|
+
if (!config) throw new RangeError(`Unknown coupon ${code}`);
|
|
1128
|
+
if (config.exclude_sale_items) throw new RangeError(`Coupon ${code} excludes sale items`);
|
|
1129
|
+
}
|
|
1130
|
+
couponCodes = normalized;
|
|
1131
|
+
emit();
|
|
1132
|
+
},
|
|
1041
1133
|
clear() {
|
|
1042
1134
|
lineItems = [];
|
|
1043
1135
|
fees = [];
|
|
@@ -1046,6 +1138,7 @@ function createOrderBuilder(options) {
|
|
|
1046
1138
|
payments = [];
|
|
1047
1139
|
customer = null;
|
|
1048
1140
|
note = "";
|
|
1141
|
+
couponCodes = [];
|
|
1049
1142
|
emit();
|
|
1050
1143
|
},
|
|
1051
1144
|
getSnapshot() {
|
|
@@ -1218,9 +1311,10 @@ var orderDraftSchema = {
|
|
|
1218
1311
|
required: ["id", "data"]
|
|
1219
1312
|
};
|
|
1220
1313
|
async function writeOrderDraft(drafts, snapshot) {
|
|
1314
|
+
const { coupons: _coupons, ...draft } = snapshot;
|
|
1221
1315
|
await drafts.upsert({
|
|
1222
1316
|
id: snapshot.id,
|
|
1223
|
-
data: JSON.stringify(
|
|
1317
|
+
data: JSON.stringify(draft),
|
|
1224
1318
|
customerName: snapshot.customer?.name ?? "",
|
|
1225
1319
|
itemCount: snapshot.lineItems.length,
|
|
1226
1320
|
total: snapshot.totalMinor,
|
|
@@ -2361,10 +2455,18 @@ function movementFieldError(input) {
|
|
|
2361
2455
|
}
|
|
2362
2456
|
|
|
2363
2457
|
// src/register/expected.ts
|
|
2458
|
+
function refundAmounts(refund) {
|
|
2459
|
+
const result = refund.result;
|
|
2460
|
+
if (typeof result !== "object" || result === null || Object.getPrototypeOf(result) !== Object.prototype && Object.getPrototypeOf(result) !== null) return [];
|
|
2461
|
+
const byMethod = result.byMethod;
|
|
2462
|
+
if (typeof byMethod !== "object" || byMethod === null || Object.getPrototypeOf(byMethod) !== Object.prototype && Object.getPrototypeOf(byMethod) !== null) return [];
|
|
2463
|
+
return Object.entries(byMethod).filter(([method, amount]) => method.length > 0 && Number.isSafeInteger(amount) && amount >= 0);
|
|
2464
|
+
}
|
|
2364
2465
|
function deriveExpected({
|
|
2365
2466
|
session,
|
|
2366
2467
|
movements,
|
|
2367
|
-
ledgerRowsBySession
|
|
2468
|
+
ledgerRowsBySession,
|
|
2469
|
+
refunds
|
|
2368
2470
|
}) {
|
|
2369
2471
|
const totals = { cash: session.countedFloatMinor };
|
|
2370
2472
|
for (const row of ledgerRowsBySession) {
|
|
@@ -2380,6 +2482,10 @@ function deriveExpected({
|
|
|
2380
2482
|
if (row.type === "paid_in") totals.cash += row.amountMinor;
|
|
2381
2483
|
if (row.type === "paid_out") totals.cash -= row.amountMinor;
|
|
2382
2484
|
}
|
|
2485
|
+
for (const refund of refunds ?? []) {
|
|
2486
|
+
if (refund.status !== "applied" || refund.sessionId !== session.id) continue;
|
|
2487
|
+
for (const [method, amount] of refundAmounts(refund)) totals[method] = (totals[method] ?? 0) - amount;
|
|
2488
|
+
}
|
|
2383
2489
|
return totals;
|
|
2384
2490
|
}
|
|
2385
2491
|
|
|
@@ -2813,6 +2919,20 @@ var RegisterNeedsUpgradeError = class extends Error {
|
|
|
2813
2919
|
this.name = "RegisterNeedsUpgradeError";
|
|
2814
2920
|
}
|
|
2815
2921
|
};
|
|
2922
|
+
var RegisterSessionConflictError = class extends Error {
|
|
2923
|
+
code = "REGISTER_SESSION_CONFLICT";
|
|
2924
|
+
constructor() {
|
|
2925
|
+
super("This register is open on another till. Take it over or choose another register.");
|
|
2926
|
+
this.name = "RegisterSessionConflictError";
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
var RegisterTakeOverError = class extends Error {
|
|
2930
|
+
constructor(code) {
|
|
2931
|
+
super(code === "REGISTER_TAKEOVER_UNSUPPORTED" ? "This store cannot take over a register. Choose another register." : "Taking over this register is still waiting for the store.");
|
|
2932
|
+
this.code = code;
|
|
2933
|
+
this.name = "RegisterTakeOverError";
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2816
2936
|
var RegisterMovementAmountError = class extends Error {
|
|
2817
2937
|
constructor(type, amountMinor) {
|
|
2818
2938
|
super(`movement_amount_invalid:${type}:${amountMinor}`);
|
|
@@ -2840,7 +2960,8 @@ var TRANSITIONS = {
|
|
|
2840
2960
|
counting: ["open", "closed"],
|
|
2841
2961
|
closed: [],
|
|
2842
2962
|
conflict: [],
|
|
2843
|
-
superseded: []
|
|
2963
|
+
superseded: [],
|
|
2964
|
+
abandoned: []
|
|
2844
2965
|
};
|
|
2845
2966
|
function isKnownSessionStatus(status) {
|
|
2846
2967
|
return Object.hasOwn(TRANSITIONS, status);
|
|
@@ -2858,6 +2979,7 @@ async function requireLiveSession(sessions, id2) {
|
|
|
2858
2979
|
if (!session) throw new RegisterSessionRequiredError();
|
|
2859
2980
|
if (!isKnownSessionStatus(session.status)) throw new RegisterNeedsUpgradeError();
|
|
2860
2981
|
if (session.status === "closed") throw new RegisterSessionClosedError();
|
|
2982
|
+
if (session.status === "abandoned") throw new RegisterSessionClosedError();
|
|
2861
2983
|
return session;
|
|
2862
2984
|
}
|
|
2863
2985
|
var openSessionSelector = { status: "open" };
|
|
@@ -3034,6 +3156,7 @@ async function writeClosure({
|
|
|
3034
3156
|
otherTenders,
|
|
3035
3157
|
movements,
|
|
3036
3158
|
orders,
|
|
3159
|
+
refunds,
|
|
3037
3160
|
tillExpected,
|
|
3038
3161
|
labels,
|
|
3039
3162
|
resolveCashierName,
|
|
@@ -3051,6 +3174,7 @@ async function writeClosure({
|
|
|
3051
3174
|
return existing;
|
|
3052
3175
|
}
|
|
3053
3176
|
const bound = orders.filter((order) => order.sessionId === session.id);
|
|
3177
|
+
const countedRefunds = (refunds ?? []).filter((refund) => refund.status === "applied" && refund.sessionId === session.id);
|
|
3054
3178
|
const rows = bound.flatMap(
|
|
3055
3179
|
(order) => order.payments.map((payment) => ({
|
|
3056
3180
|
session_id: order.sessionId,
|
|
@@ -3064,7 +3188,8 @@ async function writeClosure({
|
|
|
3064
3188
|
const till_expected = tillExpected ?? deriveExpected({
|
|
3065
3189
|
session: { id: session.id, countedFloatMinor: session.counted_float_minor },
|
|
3066
3190
|
movements: entries,
|
|
3067
|
-
ledgerRowsBySession: rows
|
|
3191
|
+
ledgerRowsBySession: rows,
|
|
3192
|
+
refunds
|
|
3068
3193
|
});
|
|
3069
3194
|
const counts = { cash: counted, ...otherTenders };
|
|
3070
3195
|
const sum = (values) => values.reduce((total, value) => total + value, 0);
|
|
@@ -3073,6 +3198,11 @@ async function writeClosure({
|
|
|
3073
3198
|
const method = row2.kind === "cash" ? "cash" : row2.method_id;
|
|
3074
3199
|
payment_methods[method] = { sales_minor: (payment_methods[method]?.sales_minor ?? 0) + row2.amountMinor, refunds_minor: 0 };
|
|
3075
3200
|
}
|
|
3201
|
+
for (const refund of countedRefunds) {
|
|
3202
|
+
for (const [method, amount] of refundAmounts(refund)) {
|
|
3203
|
+
payment_methods[method] = { sales_minor: payment_methods[method]?.sales_minor ?? 0, refunds_minor: (payment_methods[method]?.refunds_minor ?? 0) + amount };
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3076
3206
|
const taxRates = /* @__PURE__ */ new Map();
|
|
3077
3207
|
const roundings = /* @__PURE__ */ new Set();
|
|
3078
3208
|
for (const order of bound) {
|
|
@@ -3108,7 +3238,7 @@ async function writeClosure({
|
|
|
3108
3238
|
Object.entries(counts).map(([method, value]) => [method, countVariance(value, till_expected[method] ?? 0)])
|
|
3109
3239
|
),
|
|
3110
3240
|
period_sales_total_minor: sum(rows.map((row2) => row2.amountMinor)),
|
|
3111
|
-
period_refunds_total_minor:
|
|
3241
|
+
period_refunds_total_minor: sum(countedRefunds.flatMap((refund) => refundAmounts(refund).map(([, amount]) => amount))),
|
|
3112
3242
|
perpetual_sales_total_minor: 0,
|
|
3113
3243
|
perpetual_refunds_total_minor: 0,
|
|
3114
3244
|
unsynced_count: unsynced.length,
|
|
@@ -3145,7 +3275,11 @@ async function writeClosure({
|
|
|
3145
3275
|
voided_by: voided_by ?? null
|
|
3146
3276
|
})),
|
|
3147
3277
|
transaction_count: bound.length,
|
|
3148
|
-
refund_count:
|
|
3278
|
+
refund_count: countedRefunds.length,
|
|
3279
|
+
...refunds !== void 0 ? {
|
|
3280
|
+
refund_ids: countedRefunds.map((refund) => refund.id),
|
|
3281
|
+
pending_refund_ids: refunds.filter((refund) => refund.status === "pending" && refund.sessionId === session.id).map((refund) => refund.id)
|
|
3282
|
+
} : {},
|
|
3149
3283
|
cashiers: [...new Set(bound.map((order) => order.cashierRef ?? "").filter(Boolean))].map((id2) => ({
|
|
3150
3284
|
id: id2,
|
|
3151
3285
|
name: resolveCashierName?.(id2) || id2
|
|
@@ -3603,8 +3737,17 @@ var registerCommandSchema = {
|
|
|
3603
3737
|
indexes: [["registerId", "seq"], ["syncStatus", "registerId", "seq"]]
|
|
3604
3738
|
};
|
|
3605
3739
|
var registerCommandCollection = () => ({ schema: registerCommandSchema });
|
|
3606
|
-
function
|
|
3607
|
-
|
|
3740
|
+
function deviceNameOf(name) {
|
|
3741
|
+
let trimmed = name?.trim();
|
|
3742
|
+
if (trimmed && trimmed.length > 64) {
|
|
3743
|
+
const last = trimmed.charCodeAt(63);
|
|
3744
|
+
trimmed = trimmed.slice(0, last >= 55296 && last <= 56319 ? 63 : 64).trimEnd();
|
|
3745
|
+
}
|
|
3746
|
+
return trimmed || void 0;
|
|
3747
|
+
}
|
|
3748
|
+
function sessionOpenCommand(s, v2) {
|
|
3749
|
+
const deviceName = deviceNameOf(v2?.deviceName);
|
|
3750
|
+
return { key: `session.open:${s.id}`, type: "register.session.open", version: v2 ? 2 : 1, payload: {
|
|
3608
3751
|
sessionId: s.id,
|
|
3609
3752
|
registerId: s.register_id,
|
|
3610
3753
|
openedAt: s.opened_at_gmt,
|
|
@@ -3613,7 +3756,8 @@ function sessionOpenCommand(s) {
|
|
|
3613
3756
|
...s.business_day == null ? {} : { businessDay: s.business_day },
|
|
3614
3757
|
...s.opened_by == null ? {} : { openedBy: s.opened_by },
|
|
3615
3758
|
...s.expected_float_minor == null ? {} : { expectedFloatMinor: s.expected_float_minor },
|
|
3616
|
-
...s.opening_variance_minor == null ? {} : { openingVarianceMinor: s.opening_variance_minor }
|
|
3759
|
+
...s.opening_variance_minor == null ? {} : { openingVarianceMinor: s.opening_variance_minor },
|
|
3760
|
+
...deviceName === void 0 ? {} : { deviceName }
|
|
3617
3761
|
} };
|
|
3618
3762
|
}
|
|
3619
3763
|
function sessionTransitionCommand(s) {
|
|
@@ -3670,7 +3814,7 @@ function closureCommand(c) {
|
|
|
3670
3814
|
} };
|
|
3671
3815
|
}
|
|
3672
3816
|
var chains = /* @__PURE__ */ new WeakMap();
|
|
3673
|
-
function reconcileRegisterCommands({ commands, sessions, movements, closures, host, storeKey, registerId, now, observed }) {
|
|
3817
|
+
function reconcileRegisterCommands({ commands, sessions, movements, closures, host, storeKey, registerId, now, observed, registerContract, deviceName }) {
|
|
3674
3818
|
const sentStatus = (s) => s.status === "open" || s.status === "counting" || s.status === "closed";
|
|
3675
3819
|
let registers = chains.get(commands);
|
|
3676
3820
|
if (!registers) chains.set(commands, registers = /* @__PURE__ */ new Map());
|
|
@@ -3681,7 +3825,7 @@ function reconcileRegisterCommands({ commands, sessions, movements, closures, ho
|
|
|
3681
3825
|
const transitioned = new Set((await readFresh(commands, { selector: { registerId, type: "register.session.transition" } })).map(({ payload }) => payload.sessionId));
|
|
3682
3826
|
const rows = await readFresh(sessions, { selector: { register_id: registerId } });
|
|
3683
3827
|
const complete = new Set((await commands.storageInstance.findDocumentsById(rows.flatMap((session) => [`session.open:${session.id}`, ...session.status === "closed" && session.closure_id ? [`closure.submit:${session.closure_id}`] : []]), false)).map(({ key }) => key));
|
|
3684
|
-
const facts = await Promise.all(rows.filter((session) => session.status !== "closed" || (session.status_at == null || session.status_at >= since || complete.has(`session.open:${session.id}`)) && !complete.has(`closure.submit:${session.closure_id}`)).map(async (session) => {
|
|
3828
|
+
const facts = await Promise.all(rows.filter((session) => session.status !== "abandoned" && (session.status !== "closed" || (session.status_at == null || session.status_at >= since || complete.has(`session.open:${session.id}`)) && !complete.has(`closure.submit:${session.closure_id}`))).map(async (session) => {
|
|
3685
3829
|
const [entries, closureRows] = await Promise.all([
|
|
3686
3830
|
readFresh(movements, { selector: { session_id: session.id } }),
|
|
3687
3831
|
session.closure_id ? closures.storageInstance.findDocumentsById([session.closure_id], false) : Promise.resolve([])
|
|
@@ -3693,7 +3837,7 @@ function reconcileRegisterCommands({ commands, sessions, movements, closures, ho
|
|
|
3693
3837
|
);
|
|
3694
3838
|
if (session.status_at != null && sentStatus(session)) events.push(sessionTransitionCommand({ ...session, status_at: session.status_at }));
|
|
3695
3839
|
return { session, number: closureRows[0]?.number, built: [
|
|
3696
|
-
sessionOpenCommand(session),
|
|
3840
|
+
(registerContract ?? 0) >= 2 ? sessionOpenCommand(session, { deviceName }) : sessionOpenCommand(session),
|
|
3697
3841
|
...events,
|
|
3698
3842
|
...closureRows.map((closure) => closureCommand(closure))
|
|
3699
3843
|
] };
|
|
@@ -3721,6 +3865,95 @@ function reconcileRegisterCommands({ commands, sessions, movements, closures, ho
|
|
|
3721
3865
|
return run;
|
|
3722
3866
|
}
|
|
3723
3867
|
|
|
3868
|
+
// src/register/register-adoption.ts
|
|
3869
|
+
var chains2 = /* @__PURE__ */ new WeakMap();
|
|
3870
|
+
async function adoptRegisterResults({ commands, sessions, registerId }) {
|
|
3871
|
+
const run = (chains2.get(commands) ?? Promise.resolve()).then(async () => {
|
|
3872
|
+
const [answers, transitions, rows] = await Promise.all([
|
|
3873
|
+
readFresh(commands, { selector: { registerId, syncStatus: { $in: ["applied", "rejected"] } }, sort: [{ seq: "asc" }, { key: "asc" }] }),
|
|
3874
|
+
readFresh(commands, { selector: { registerId, type: "register.session.transition" }, sort: [{ seq: "desc" }, { key: "desc" }] }),
|
|
3875
|
+
readFresh(sessions, { selector: { register_id: registerId } })
|
|
3876
|
+
]);
|
|
3877
|
+
for (const session of rows) {
|
|
3878
|
+
if (session.status === "conflict" && answers.some((answer) => answer.payload.sessionId === session.id && answer.syncStatus === "rejected" && answer.error?.code === "register_session_abandoned")) {
|
|
3879
|
+
await finishAbandon({ commands, sessions, sessionId: session.id });
|
|
3880
|
+
continue;
|
|
3881
|
+
}
|
|
3882
|
+
const latest = transitions.find(({ payload }) => payload.sessionId === session.id);
|
|
3883
|
+
const modify = (doc) => {
|
|
3884
|
+
let next = doc;
|
|
3885
|
+
for (const answer of answers) {
|
|
3886
|
+
if (!isKnownSessionStatus(next.status) || next.status === "closed" || next.status === "superseded" || next.status === "abandoned") break;
|
|
3887
|
+
if (answer.payload.sessionId !== doc.id) continue;
|
|
3888
|
+
if (answer.syncStatus === "rejected") {
|
|
3889
|
+
if (answer.error?.code === "register_session_superseded" && (next.status === "open" || next.status === "counting")) next = { ...next, status: "superseded" };
|
|
3890
|
+
else if (answer.type === "register.session.open" && answer.error?.code === "register_session_already_open" && (next.status === "open" || next.status === "counting")) next = { ...next, status: "conflict" };
|
|
3891
|
+
} else if (answer.type === "register.session.open") {
|
|
3892
|
+
const resumed = answer.result?.resumed?.fromSessionId;
|
|
3893
|
+
if (typeof resumed === "string" && next.server_session_id == null) next = { ...next, server_session_id: resumed };
|
|
3894
|
+
if (next.status === "conflict") next = { ...next, status: latest?.payload.status === "counting" ? "counting" : "open" };
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
return next;
|
|
3898
|
+
};
|
|
3899
|
+
if (modify(session) === session) continue;
|
|
3900
|
+
const row = await sessions.findOne(session.id).exec();
|
|
3901
|
+
if (row) await row.incrementalModify(modify);
|
|
3902
|
+
}
|
|
3903
|
+
}).catch((error) => {
|
|
3904
|
+
registerCommandsLogger.error("Register result adoption failed", { context: { registerId, error: String(error) } });
|
|
3905
|
+
});
|
|
3906
|
+
chains2.set(commands, run.then(() => void 0, () => void 0));
|
|
3907
|
+
return run;
|
|
3908
|
+
}
|
|
3909
|
+
async function takeOverSession({ commands, sessions, sessionId, now, registerContract }) {
|
|
3910
|
+
if ((registerContract ?? 0) < 2) throw new RegisterTakeOverError("REGISTER_TAKEOVER_UNSUPPORTED");
|
|
3911
|
+
const run = (chains2.get(commands) ?? Promise.resolve()).then(async () => {
|
|
3912
|
+
const [session] = await readFresh(sessions, { selector: { id: sessionId } });
|
|
3913
|
+
const row = await commands.findOne(`session.open:${sessionId}`).exec();
|
|
3914
|
+
if (session?.status !== "conflict" || !row) throw new RegisterSessionRequiredError();
|
|
3915
|
+
await row.incrementalModify((doc) => {
|
|
3916
|
+
if (doc.syncStatus !== "rejected" || doc.error?.code !== "register_session_already_open" || typeof doc.error.data?.sessionId !== "string") throw new RegisterSessionRequiredError();
|
|
3917
|
+
const { error, ...rest } = doc;
|
|
3918
|
+
return {
|
|
3919
|
+
...rest,
|
|
3920
|
+
commandId: uuidv7(),
|
|
3921
|
+
version: 2,
|
|
3922
|
+
syncStatus: "pending",
|
|
3923
|
+
payload: { ...doc.payload, supersedes: error.data.sessionId },
|
|
3924
|
+
updatedAt: now ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3925
|
+
};
|
|
3926
|
+
});
|
|
3927
|
+
});
|
|
3928
|
+
chains2.set(commands, run.then(() => void 0, () => void 0));
|
|
3929
|
+
return run;
|
|
3930
|
+
}
|
|
3931
|
+
async function finishAbandon({ commands, sessions, sessionId, now }) {
|
|
3932
|
+
const rows = await readFresh(commands, { selector: { "payload.sessionId": sessionId, syncStatus: "pending" } });
|
|
3933
|
+
for (const row of rows) {
|
|
3934
|
+
await (await commands.findOne(row.key).exec(true)).incrementalModify((doc) => doc.syncStatus !== "pending" ? doc : {
|
|
3935
|
+
...doc,
|
|
3936
|
+
syncStatus: "rejected",
|
|
3937
|
+
error: { code: "register_session_abandoned", message: "The cashier chose another register." },
|
|
3938
|
+
updatedAt: now ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
await (await sessions.findOne(sessionId).exec(true)).incrementalModify((doc) => doc.status === "conflict" ? { ...doc, status: "abandoned" } : doc);
|
|
3942
|
+
}
|
|
3943
|
+
async function abandonSession(input) {
|
|
3944
|
+
const { commands, sessions, sessionId } = input;
|
|
3945
|
+
const run = (chains2.get(commands) ?? Promise.resolve()).then(async () => {
|
|
3946
|
+
const [session] = await readFresh(sessions, { selector: { id: sessionId } });
|
|
3947
|
+
if (session?.status === "abandoned") return;
|
|
3948
|
+
if (session?.status !== "conflict") throw new RegisterSessionRequiredError();
|
|
3949
|
+
const [open] = await readFresh(commands, { selector: { key: `session.open:${sessionId}` } });
|
|
3950
|
+
if (open?.syncStatus === "pending") throw new RegisterTakeOverError("REGISTER_TAKEOVER_PENDING");
|
|
3951
|
+
await finishAbandon(input);
|
|
3952
|
+
});
|
|
3953
|
+
chains2.set(commands, run.then(() => void 0, () => void 0));
|
|
3954
|
+
return run;
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3724
3957
|
// src/register/use-register-session.ts
|
|
3725
3958
|
var RegisterTenderInProgressError = class extends Error {
|
|
3726
3959
|
constructor() {
|
|
@@ -3761,7 +3994,7 @@ var reservationOf = (register, storeKey, registerId) => register?.stores[storeKe
|
|
|
3761
3994
|
var unwritten = (row, closureRows) => row.status === "closed" && row.closure_id === row.id && !closureRows.some((closure) => closure.id === row.id);
|
|
3762
3995
|
function currentSession(rows, closureRows, reservation) {
|
|
3763
3996
|
if (rows.some((row) => !isKnownSessionStatus(row.status))) return null;
|
|
3764
|
-
return (reservation && !reservation.applied ? rows.find((row) => row.id === reservation.row.session_id) : void 0) ?? rows.find((row) => row.status
|
|
3997
|
+
return (reservation && !reservation.applied ? rows.find((row) => row.id === reservation.row.session_id) : void 0) ?? rows.find((row) => row.status === "open" || row.status === "counting") ?? rows.find((row) => row.status === "conflict") ?? rows.find((row) => unwritten(row, closureRows)) ?? null;
|
|
3765
3998
|
}
|
|
3766
3999
|
function ledgerRows(orders) {
|
|
3767
4000
|
return orders.flatMap((order) => order.payments.map((payment) => ({
|
|
@@ -3774,11 +4007,11 @@ function ledgerRows(orders) {
|
|
|
3774
4007
|
}
|
|
3775
4008
|
function useRegisterSession(options) {
|
|
3776
4009
|
const { sessions, movements, closures, orders, register, storeKey, registerId, enabled, actor, timezone, tenderInProgress, expectedCloseTime, labels } = options;
|
|
3777
|
-
const { commands } = options;
|
|
4010
|
+
const { commands, refunds } = options;
|
|
3778
4011
|
const commandEnabled = (options.capabilities?.register ?? 0) >= 1;
|
|
3779
4012
|
const commandTarget = useMemo3(
|
|
3780
|
-
() => enabled && commandEnabled && commands && sessions && movements && closures && register && registerId ? { commands, sessions, movements, closures, host: register, storeKey, registerId } : null,
|
|
3781
|
-
[enabled, commandEnabled, commands, sessions, movements, closures, register, storeKey, registerId]
|
|
4013
|
+
() => enabled && commandEnabled && commands && sessions && movements && closures && register && registerId ? { commands, sessions, movements, closures, host: register, storeKey, registerId, registerContract: options.capabilities?.register, deviceName: options.deviceName } : null,
|
|
4014
|
+
[enabled, commandEnabled, commands, sessions, movements, closures, register, storeKey, registerId, options.capabilities?.register, options.deviceName]
|
|
3782
4015
|
);
|
|
3783
4016
|
const commandTargetRef = useRef2(commandTarget);
|
|
3784
4017
|
commandTargetRef.current = commandTarget;
|
|
@@ -3793,21 +4026,30 @@ function useRegisterSession(options) {
|
|
|
3793
4026
|
});
|
|
3794
4027
|
};
|
|
3795
4028
|
useEffect3(reconcile, [commandTarget]);
|
|
4029
|
+
useEffect3(() => {
|
|
4030
|
+
if (!commandTarget) return;
|
|
4031
|
+
const subscription = commandTarget.commands.find({ selector: { registerId: commandTarget.registerId } }).$.subscribe(() => {
|
|
4032
|
+
void adoptRegisterResults(commandTarget);
|
|
4033
|
+
});
|
|
4034
|
+
return () => subscription.unsubscribe();
|
|
4035
|
+
}, [commandTarget]);
|
|
3796
4036
|
const source = useMemo3(() => {
|
|
3797
|
-
if (!enabled || !sessions || !movements || !closures || !orders || !registerId || !register) return null;
|
|
4037
|
+
if (!enabled || !sessions || !movements || !closures || !orders || !registerId || !register || refunds === null) return null;
|
|
3798
4038
|
return combineLatest([
|
|
3799
4039
|
watchFresh(sessions, { selector: { register_id: registerId } }),
|
|
3800
4040
|
watchFresh(closures, { selector: { register_id: registerId } }),
|
|
3801
4041
|
observeRegister$(register).pipe(map5((document) => reservationOf(document, storeKey, registerId)))
|
|
3802
4042
|
]).pipe(switchMap(([rows, closureRows, reservation]) => {
|
|
3803
4043
|
const current2 = currentSession(rows, closureRows, reservation);
|
|
3804
|
-
if (!current2) return of({ rows, closureRows, reservation, entries: [], sales: [] });
|
|
4044
|
+
if (!current2) return of({ rows, closureRows, reservation, entries: [], sales: [], refundRows: [] });
|
|
3805
4045
|
return combineLatest([
|
|
3806
4046
|
watchFresh(movements, { selector: { session_id: current2.id } }),
|
|
3807
|
-
watchFresh(orders, { selector: { sessionId: current2.id } })
|
|
3808
|
-
|
|
4047
|
+
watchFresh(orders, { selector: { sessionId: current2.id } }),
|
|
4048
|
+
commandTarget ? watchFresh(commandTarget.commands, { selector: { key: `session.open:${current2.id}` } }) : of([]),
|
|
4049
|
+
refunds ? watchFresh(refunds, { selector: { sessionId: current2.id } }) : of([])
|
|
4050
|
+
]).pipe(map5(([entries2, sales2, opens, refundRows]) => ({ rows, closureRows, reservation, entries: entries2, sales: sales2, open: opens[0], refundRows })));
|
|
3809
4051
|
}));
|
|
3810
|
-
}, [enabled, sessions, movements, closures, orders, register, storeKey, registerId]);
|
|
4052
|
+
}, [enabled, sessions, movements, closures, orders, register, storeKey, registerId, commandTarget, refunds]);
|
|
3811
4053
|
const tender = useRef2(tenderInProgress);
|
|
3812
4054
|
tender.current = tenderInProgress;
|
|
3813
4055
|
const closing = useSyncExternalStore(subscribeClosing, () => closingByRegister.has(registerId ?? ""));
|
|
@@ -3862,9 +4104,11 @@ function useRegisterSession(options) {
|
|
|
3862
4104
|
}
|
|
3863
4105
|
}, [data, source]);
|
|
3864
4106
|
const session = data ? currentSession(data.rows, data.closureRows, data.reservation) : null;
|
|
4107
|
+
const refusal = data?.open?.error?.data;
|
|
4108
|
+
const conflict = session?.status !== "conflict" || !commandTarget ? void 0 : data?.open?.syncStatus === "pending" && typeof data.open.payload.supersedes === "string" ? { sessionId: data.open.payload.supersedes, takingOver: true } : data?.open?.syncStatus === "rejected" && refusal ? { sessionId: refusal.sessionId, openedAt: refusal.openedAt, openedBy: refusal.openedBy, deviceName: refusal.deviceName, takingOver: false } : void 0;
|
|
3865
4109
|
const entries = data?.entries ?? [];
|
|
3866
4110
|
const sales = data?.sales ?? [];
|
|
3867
|
-
const expected = session ? deriveExpected({ session: { id: session.id, countedFloatMinor: session.counted_float_minor }, movements: entries, ledgerRowsBySession: ledgerRows(sales) }) : {};
|
|
4111
|
+
const expected = session ? deriveExpected({ session: { id: session.id, countedFloatMinor: session.counted_float_minor }, movements: entries, ledgerRowsBySession: ledgerRows(sales), refunds: data?.refundRows ?? [] }) : {};
|
|
3868
4112
|
const now = /* @__PURE__ */ new Date();
|
|
3869
4113
|
const [hour, minute] = String(expectedCloseTime ?? "").split(":").map(Number);
|
|
3870
4114
|
const overdue = session?.status === "open" && !!expectedCloseTime && now.getTime() > new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute).getTime();
|
|
@@ -3874,8 +4118,8 @@ function useRegisterSession(options) {
|
|
|
3874
4118
|
return id2 === null ? null : { id: id2, sessions };
|
|
3875
4119
|
};
|
|
3876
4120
|
const live = () => {
|
|
3877
|
-
if (!enabled || !sessions || !movements || !closures || !orders || !registerId || !register) throw new RegisterSessionRequiredError();
|
|
3878
|
-
return { sessions, movements, closures, orders, registerId, register };
|
|
4121
|
+
if (!enabled || !sessions || !movements || !closures || !orders || !registerId || !register || refunds === null) throw new RegisterSessionRequiredError();
|
|
4122
|
+
return { sessions, movements, closures, orders, registerId, register, refunds };
|
|
3879
4123
|
};
|
|
3880
4124
|
const current = () => {
|
|
3881
4125
|
if (!session) throw new RegisterSessionRequiredError();
|
|
@@ -3887,6 +4131,8 @@ function useRegisterSession(options) {
|
|
|
3887
4131
|
const nameOf = (id2) => !id2 ? "" : id2 === actor.id ? actor.name : labels?.resolveCashierName?.(id2) ?? "";
|
|
3888
4132
|
return {
|
|
3889
4133
|
session,
|
|
4134
|
+
/** The other till's live session, or the session this till is waiting to take over. */
|
|
4135
|
+
conflict,
|
|
3890
4136
|
/** Any unknown session status in the current snapshot means the register needs upgrade. */
|
|
3891
4137
|
needsUpgrade: data?.rows.some((row) => !isKnownSessionStatus(row.status)) ?? false,
|
|
3892
4138
|
movements: entries,
|
|
@@ -3901,17 +4147,38 @@ function useRegisterSession(options) {
|
|
|
3901
4147
|
enabled,
|
|
3902
4148
|
blind: options.blind ?? false,
|
|
3903
4149
|
/** Pass straight to useSale's `session` option: `complete()` then stamps through `stampSession`. */
|
|
3904
|
-
saleSession: session && sessions && session.status
|
|
4150
|
+
saleSession: session && sessions && (session.status === "open" || session.status === "counting") ? { id: session.id, sessions } : void 0,
|
|
3905
4151
|
/** The open session's id, `null` when sessions are off, else `RegisterSessionRequiredError`. Call it when tender starts and before a card terminal captures. */
|
|
3906
4152
|
requireOpen,
|
|
3907
4153
|
/** `requireOpen()`, returning `{ id, sessions }`: pass it to useSale's `startTender`, which pins it (the rendered `saleSession` can lag). */
|
|
3908
4154
|
requireSaleSession,
|
|
3909
4155
|
actions: {
|
|
4156
|
+
takeOver: async () => {
|
|
4157
|
+
if (!commandTarget) throw new RegisterSessionRequiredError();
|
|
4158
|
+
const row = currentSession(
|
|
4159
|
+
await readFresh(commandTarget.sessions, { selector: { register_id: commandTarget.registerId } }),
|
|
4160
|
+
await readFresh(commandTarget.closures, { selector: { register_id: commandTarget.registerId } }),
|
|
4161
|
+
reservationOf(await readRegister(commandTarget.host), commandTarget.storeKey, commandTarget.registerId)
|
|
4162
|
+
);
|
|
4163
|
+
if (row?.status !== "conflict") throw new RegisterSessionRequiredError();
|
|
4164
|
+
await takeOverSession({ ...commandTarget, sessionId: row.id, registerContract: options.capabilities?.register });
|
|
4165
|
+
},
|
|
4166
|
+
chooseAnotherRegister: async () => {
|
|
4167
|
+
if (!commandTarget) throw new RegisterSessionRequiredError();
|
|
4168
|
+
const row = currentSession(
|
|
4169
|
+
await readFresh(commandTarget.sessions, { selector: { register_id: commandTarget.registerId } }),
|
|
4170
|
+
await readFresh(commandTarget.closures, { selector: { register_id: commandTarget.registerId } }),
|
|
4171
|
+
reservationOf(await readRegister(commandTarget.host), commandTarget.storeKey, commandTarget.registerId)
|
|
4172
|
+
);
|
|
4173
|
+
if (row?.status !== "conflict") throw new RegisterSessionRequiredError();
|
|
4174
|
+
await abandonSession({ ...commandTarget, sessionId: row.id });
|
|
4175
|
+
},
|
|
3910
4176
|
/**
|
|
3911
4177
|
* Refuses with `RegisterSessionAlreadyOpenError` while the register has a live session or
|
|
3912
4178
|
* another open is running (in any hook instance), and with `RegisterCloseIncompleteError`
|
|
3913
4179
|
* while an earlier close hasn't finished: a new session would lock the till behind it.
|
|
3914
4180
|
* An unknown stored status refuses with `RegisterNeedsUpgradeError`.
|
|
4181
|
+
* A conflict session refuses with `RegisterSessionConflictError`; superseded and abandoned sessions do not block.
|
|
3915
4182
|
*/
|
|
3916
4183
|
openSession: async (input) => {
|
|
3917
4184
|
const key = registerId ?? "";
|
|
@@ -3920,7 +4187,8 @@ function useRegisterSession(options) {
|
|
|
3920
4187
|
const { sessions: sessions2, closures: closures2, registerId: registerId2, register: register2 } = live();
|
|
3921
4188
|
const rows = await readFresh(sessions2, { selector: { register_id: registerId2 } });
|
|
3922
4189
|
if (rows.some((row2) => !isKnownSessionStatus(row2.status))) throw new RegisterNeedsUpgradeError();
|
|
3923
|
-
if (rows.some((row2) => row2.status
|
|
4190
|
+
if (rows.some((row2) => row2.status === "conflict")) throw new RegisterSessionConflictError();
|
|
4191
|
+
if (rows.some((row2) => row2.status === "open" || row2.status === "counting")) throw new RegisterSessionAlreadyOpenError();
|
|
3924
4192
|
const closureRows = await readFresh(closures2, { selector: { register_id: registerId2 } });
|
|
3925
4193
|
const reservation = reservationOf(await readRegister(register2), storeKey, registerId2);
|
|
3926
4194
|
if (reservation && !reservation.applied || rows.some((row2) => unwritten(row2, closureRows))) {
|
|
@@ -3988,7 +4256,7 @@ function useRegisterSession(options) {
|
|
|
3988
4256
|
const key = registerId ?? "";
|
|
3989
4257
|
if (closingByRegister.has(key)) return closingByRegister.get(key);
|
|
3990
4258
|
const run = (async () => {
|
|
3991
|
-
const { sessions: sessions2, movements: movements2, closures: closures2, orders: orders2, register: register2 } = live();
|
|
4259
|
+
const { sessions: sessions2, movements: movements2, closures: closures2, orders: orders2, register: register2, refunds: refunds2 } = live();
|
|
3992
4260
|
const open = current();
|
|
3993
4261
|
const stored = await readSession(sessions2, open.id);
|
|
3994
4262
|
if (!stored) throw new RegisterSessionRequiredError();
|
|
@@ -3996,7 +4264,8 @@ function useRegisterSession(options) {
|
|
|
3996
4264
|
const fresh = deriveExpected({
|
|
3997
4265
|
session: { id: stored.id, countedFloatMinor: stored.counted_float_minor },
|
|
3998
4266
|
movements: await readFresh(movements2, { selector: { session_id: stored.id } }),
|
|
3999
|
-
ledgerRowsBySession: ledgerRows(await readFresh(orders2, { selector: { sessionId: stored.id } }))
|
|
4267
|
+
ledgerRowsBySession: ledgerRows(await readFresh(orders2, { selector: { sessionId: stored.id } })),
|
|
4268
|
+
refunds: refunds2 ? await readFresh(refunds2, { selector: { sessionId: stored.id } }) : void 0
|
|
4000
4269
|
});
|
|
4001
4270
|
if (closeNeedsApproval(input.counted.cash ?? 0, fresh.cash ?? 0, options.varianceThreshold)) throw new RegisterApprovalRequiredError();
|
|
4002
4271
|
}
|
|
@@ -4014,6 +4283,7 @@ function useRegisterSession(options) {
|
|
|
4014
4283
|
softwareVersion: options.softwareVersion,
|
|
4015
4284
|
movements: await readFresh(movements2, { selector: { session_id: closed.id } }),
|
|
4016
4285
|
orders: await readFresh(orders2, { selector: { sessionId: closed.id } }),
|
|
4286
|
+
refunds: refunds2 ? await readFresh(refunds2, { selector: { sessionId: closed.id } }) : void 0,
|
|
4017
4287
|
resolveCashierName: labels?.resolveCashierName,
|
|
4018
4288
|
labels: {
|
|
4019
4289
|
register_name: labels?.registerName ?? "",
|
|
@@ -5203,7 +5473,7 @@ function plainCommand({
|
|
|
5203
5473
|
});
|
|
5204
5474
|
}
|
|
5205
5475
|
function createRegisterOutbox(options) {
|
|
5206
|
-
const { collection, transport, deviceId } = options;
|
|
5476
|
+
const { collection, sessions, transport, deviceId } = options;
|
|
5207
5477
|
const size = options.batchSize;
|
|
5208
5478
|
const batchSize = Math.max(1, Math.min(typeof size === "number" && Number.isFinite(size) ? Math.floor(size) : 10, 10));
|
|
5209
5479
|
const initialBackoff = options.initialBackoffMs ?? 1e3;
|
|
@@ -5223,6 +5493,7 @@ function createRegisterOutbox(options) {
|
|
|
5223
5493
|
let running;
|
|
5224
5494
|
let timer;
|
|
5225
5495
|
let subscription;
|
|
5496
|
+
let sessionsSubscription;
|
|
5226
5497
|
let stopped = false;
|
|
5227
5498
|
let insertedDuringRun = false;
|
|
5228
5499
|
let stoppedDuringRun = false;
|
|
@@ -5244,7 +5515,11 @@ function createRegisterOutbox(options) {
|
|
|
5244
5515
|
const at2 = now();
|
|
5245
5516
|
for (const { commandId } of commands) clocks.set(commandId, { ...clocks.get(commandId) ?? { since: at2, firstFailedAt: at2 }, seq: ++failureSeq, reason });
|
|
5246
5517
|
}
|
|
5518
|
+
function isNonBlocking(command, abandoned) {
|
|
5519
|
+
return NON_BLOCKING_REJECTIONS.has(command.error?.code ?? "") || command.error?.code === "register_session_already_open" && typeof command.payload.sessionId === "string" && abandoned.has(command.payload.sessionId);
|
|
5520
|
+
}
|
|
5247
5521
|
async function updateState(patch = {}) {
|
|
5522
|
+
const abandoned = new Set(sessions && clocks.size ? (await readFresh(sessions, { selector: { status: "abandoned" } })).map((session) => session.id) : []);
|
|
5248
5523
|
const pending = await countFresh(collection, { syncStatus: "pending" });
|
|
5249
5524
|
const sendable2 = /* @__PURE__ */ new Set();
|
|
5250
5525
|
const blocked = /* @__PURE__ */ new Set();
|
|
@@ -5253,7 +5528,7 @@ function createRegisterOutbox(options) {
|
|
|
5253
5528
|
sort: [{ seq: "asc" }, { key: "asc" }]
|
|
5254
5529
|
})) {
|
|
5255
5530
|
if (command.syncStatus === "rejected") {
|
|
5256
|
-
if (!
|
|
5531
|
+
if (!isNonBlocking(command, abandoned)) blocked.add(command.registerId);
|
|
5257
5532
|
} else if (!blocked.has(command.registerId)) sendable2.add(command.commandId);
|
|
5258
5533
|
}
|
|
5259
5534
|
for (const id2 of clocks.keys()) if (!sendable2.has(id2)) clocks.delete(id2);
|
|
@@ -5282,6 +5557,7 @@ function createRegisterOutbox(options) {
|
|
|
5282
5557
|
if (options.isEnabled?.() === false) return;
|
|
5283
5558
|
const pending = await readFresh(collection, { selector: { syncStatus: "pending" } });
|
|
5284
5559
|
const registerIds = [...new Set(pending.map((command) => command.registerId))].sort();
|
|
5560
|
+
const abandoned = new Set(sessions ? (await readFresh(sessions, { selector: { status: "abandoned" } })).map((session) => session.id) : []);
|
|
5285
5561
|
let sent = false;
|
|
5286
5562
|
for (const registerId of registerIds) {
|
|
5287
5563
|
const ledger = await readFresh(collection, {
|
|
@@ -5290,7 +5566,7 @@ function createRegisterOutbox(options) {
|
|
|
5290
5566
|
});
|
|
5291
5567
|
const commands = [];
|
|
5292
5568
|
for (const command of ledger) {
|
|
5293
|
-
if (command.syncStatus === "rejected" &&
|
|
5569
|
+
if (command.syncStatus === "rejected" && isNonBlocking(command, abandoned)) continue;
|
|
5294
5570
|
if (command.syncStatus === "rejected") break;
|
|
5295
5571
|
commands.push(command);
|
|
5296
5572
|
if (command.type === "register.session.open" || commands.length >= batchSize) break;
|
|
@@ -5426,6 +5702,13 @@ function createRegisterOutbox(options) {
|
|
|
5426
5702
|
});
|
|
5427
5703
|
}
|
|
5428
5704
|
});
|
|
5705
|
+
if (sessions && !sessionsSubscription) sessionsSubscription = sessions.$.subscribe((event) => {
|
|
5706
|
+
if (event.documentData?.status === "abandoned") {
|
|
5707
|
+
insertedDuringRun = true;
|
|
5708
|
+
flush().catch(() => {
|
|
5709
|
+
});
|
|
5710
|
+
}
|
|
5711
|
+
});
|
|
5429
5712
|
if (!stopped) flush().catch(() => {
|
|
5430
5713
|
});
|
|
5431
5714
|
},
|
|
@@ -5435,6 +5718,8 @@ function createRegisterOutbox(options) {
|
|
|
5435
5718
|
timer = void 0;
|
|
5436
5719
|
subscription?.unsubscribe();
|
|
5437
5720
|
subscription = void 0;
|
|
5721
|
+
sessionsSubscription?.unsubscribe();
|
|
5722
|
+
sessionsSubscription = void 0;
|
|
5438
5723
|
missingSubscription?.unsubscribe();
|
|
5439
5724
|
missingSubscription = void 0;
|
|
5440
5725
|
state$.next({ ...state$.value, nextAttemptAt: void 0 });
|
|
@@ -5446,7 +5731,7 @@ function createRegisterOutbox(options) {
|
|
|
5446
5731
|
import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
|
|
5447
5732
|
var idle = { pending: 0, sending: false };
|
|
5448
5733
|
function useRegisterOutbox(options) {
|
|
5449
|
-
const { commands, deviceId } = options;
|
|
5734
|
+
const { commands, sessions, deviceId } = options;
|
|
5450
5735
|
const latest = useRef4(options);
|
|
5451
5736
|
latest.current = options;
|
|
5452
5737
|
const current = useRef4(null);
|
|
@@ -5458,6 +5743,7 @@ function useRegisterOutbox(options) {
|
|
|
5458
5743
|
collection: commands,
|
|
5459
5744
|
deviceId,
|
|
5460
5745
|
transport: latest.current.transport(),
|
|
5746
|
+
sessions: sessions ?? void 0,
|
|
5461
5747
|
isEnabled: latest.current.isEnabled ? () => latest.current.isEnabled?.() ?? true : void 0,
|
|
5462
5748
|
onResult: latest.current.onResult ? async (command, result) => {
|
|
5463
5749
|
await latest.current.onResult?.(command, result);
|
|
@@ -5473,7 +5759,7 @@ function useRegisterOutbox(options) {
|
|
|
5473
5759
|
current.current = null;
|
|
5474
5760
|
setState(idle);
|
|
5475
5761
|
};
|
|
5476
|
-
}, [commands, deviceId]);
|
|
5762
|
+
}, [commands, sessions, deviceId]);
|
|
5477
5763
|
const opened = current.current;
|
|
5478
5764
|
return {
|
|
5479
5765
|
state: opened?.commands === commands && opened.deviceId === deviceId ? state : idle,
|
|
@@ -5606,6 +5892,148 @@ function useOrderOutbox(options) {
|
|
|
5606
5892
|
};
|
|
5607
5893
|
}
|
|
5608
5894
|
|
|
5895
|
+
// src/refund/send-order-refund.ts
|
|
5896
|
+
import { parseCommandResult } from "@tallyui/core/server";
|
|
5897
|
+
async function sendOrderRefund(transport, envelope2) {
|
|
5898
|
+
let outcome;
|
|
5899
|
+
try {
|
|
5900
|
+
outcome = await transport.send([envelope2]);
|
|
5901
|
+
} catch {
|
|
5902
|
+
return { kind: "unknown", reason: "transport_error" };
|
|
5903
|
+
}
|
|
5904
|
+
if (outcome.kind === "retry") {
|
|
5905
|
+
return {
|
|
5906
|
+
kind: "unknown",
|
|
5907
|
+
reason: outcome.reason,
|
|
5908
|
+
...outcome.retryAfterMs !== void 0 ? { retryAfterMs: outcome.retryAfterMs } : {}
|
|
5909
|
+
};
|
|
5910
|
+
}
|
|
5911
|
+
if (outcome.kind === "refused" || outcome.kind === "unauthorized") return outcome;
|
|
5912
|
+
const matches = outcome.results.filter((result2) => result2.id === envelope2.id);
|
|
5913
|
+
if (matches.length !== 1) return { kind: "unknown", reason: "missing_result" };
|
|
5914
|
+
let result;
|
|
5915
|
+
try {
|
|
5916
|
+
result = parseCommandResult(matches[0]);
|
|
5917
|
+
} catch {
|
|
5918
|
+
return { kind: "unknown", reason: "bad_result" };
|
|
5919
|
+
}
|
|
5920
|
+
if (result.status === "applied" || result.status === "duplicate") {
|
|
5921
|
+
if (!result.refund) return { kind: "unknown", reason: "bad_result" };
|
|
5922
|
+
return { kind: "applied", refund: result.refund, duplicate: result.status === "duplicate" };
|
|
5923
|
+
}
|
|
5924
|
+
return { kind: "rejected", error: result.error };
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5927
|
+
// src/refund/pos-refund.ts
|
|
5928
|
+
var posRefundSchema = {
|
|
5929
|
+
title: "POS refunds",
|
|
5930
|
+
version: 0,
|
|
5931
|
+
primaryKey: "id",
|
|
5932
|
+
type: "object",
|
|
5933
|
+
additionalProperties: false,
|
|
5934
|
+
properties: {
|
|
5935
|
+
id: { type: "string", maxLength: 64 },
|
|
5936
|
+
commandId: { type: "string", maxLength: 64 },
|
|
5937
|
+
orderId: { type: "string", maxLength: 64 },
|
|
5938
|
+
sessionId: { type: "string", maxLength: 64 },
|
|
5939
|
+
registerId: { type: "string", maxLength: 64 },
|
|
5940
|
+
envelope: { type: "object", additionalProperties: true },
|
|
5941
|
+
status: { type: "string", enum: ["pending", "applied", "rejected", "unsent"], maxLength: 10 },
|
|
5942
|
+
result: { type: "object", additionalProperties: true },
|
|
5943
|
+
duplicate: { type: "boolean" },
|
|
5944
|
+
error: { type: "object", properties: {
|
|
5945
|
+
code: { type: "string" },
|
|
5946
|
+
message: { type: "string" },
|
|
5947
|
+
data: { type: "object", additionalProperties: true }
|
|
5948
|
+
}, required: ["code", "message"], additionalProperties: false },
|
|
5949
|
+
createdAt: { type: "string" },
|
|
5950
|
+
updatedAt: { type: "string" }
|
|
5951
|
+
},
|
|
5952
|
+
required: ["id", "commandId", "orderId", "sessionId", "registerId", "envelope", "status", "createdAt", "updatedAt"],
|
|
5953
|
+
indexes: [["sessionId"]]
|
|
5954
|
+
};
|
|
5955
|
+
var posRefundCollection = () => ({ schema: posRefundSchema });
|
|
5956
|
+
|
|
5957
|
+
// src/refund/submit-order-refund.ts
|
|
5958
|
+
import { RxError } from "rxdb";
|
|
5959
|
+
var RefundAnswerPendingError = class extends Error {
|
|
5960
|
+
constructor(record) {
|
|
5961
|
+
super("This refund's answer is still unknown. Send it again before trying a new refund attempt.");
|
|
5962
|
+
this.record = record;
|
|
5963
|
+
this.name = "RefundAnswerPendingError";
|
|
5964
|
+
}
|
|
5965
|
+
code = "REFUND_ANSWER_PENDING";
|
|
5966
|
+
};
|
|
5967
|
+
async function submitOrderRefund({ refunds, transport, envelope: envelope2, now = () => (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
5968
|
+
const id2 = envelope2.payload.clientRefundId;
|
|
5969
|
+
let doc = await refunds.findOne(id2).exec();
|
|
5970
|
+
if (!doc) {
|
|
5971
|
+
const at2 = now();
|
|
5972
|
+
const { orderId, sessionId, registerId } = envelope2.payload;
|
|
5973
|
+
try {
|
|
5974
|
+
doc = await refunds.insert({
|
|
5975
|
+
id: id2,
|
|
5976
|
+
commandId: envelope2.id,
|
|
5977
|
+
orderId,
|
|
5978
|
+
sessionId,
|
|
5979
|
+
registerId,
|
|
5980
|
+
envelope: envelope2,
|
|
5981
|
+
status: "pending",
|
|
5982
|
+
createdAt: at2,
|
|
5983
|
+
updatedAt: at2
|
|
5984
|
+
});
|
|
5985
|
+
} catch (error) {
|
|
5986
|
+
if (!(error instanceof RxError) || error.code !== "CONFLICT") throw error;
|
|
5987
|
+
doc = await refunds.findOne(id2).exec();
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
let record = doc.toJSON();
|
|
5991
|
+
for (let pass = 0; ; pass++) {
|
|
5992
|
+
if (pass === 2) throw new RefundAnswerPendingError(record);
|
|
5993
|
+
if (record.status === "applied") {
|
|
5994
|
+
return { outcome: { kind: "applied", refund: record.result, duplicate: true }, record };
|
|
5995
|
+
}
|
|
5996
|
+
if (record.status === "pending") {
|
|
5997
|
+
if (record.commandId !== envelope2.id) throw new RefundAnswerPendingError(record);
|
|
5998
|
+
break;
|
|
5999
|
+
}
|
|
6000
|
+
if (record.status === "rejected" && record.commandId === envelope2.id) {
|
|
6001
|
+
return { outcome: { kind: "rejected", error: record.error }, record };
|
|
6002
|
+
}
|
|
6003
|
+
if (record.status === "unsent" && record.commandId === envelope2.id) {
|
|
6004
|
+
doc = await doc.incrementalModify((stored) => {
|
|
6005
|
+
if (stored.status !== "unsent" || stored.commandId !== envelope2.id) return stored;
|
|
6006
|
+
const { error, ...rest } = stored;
|
|
6007
|
+
return { ...rest, status: "pending", updatedAt: now() };
|
|
6008
|
+
});
|
|
6009
|
+
record = doc.toJSON();
|
|
6010
|
+
continue;
|
|
6011
|
+
}
|
|
6012
|
+
doc = await doc.incrementalModify((stored) => {
|
|
6013
|
+
if (stored.status !== "rejected" && stored.status !== "unsent") return stored;
|
|
6014
|
+
const { result, duplicate, error, ...rest } = stored;
|
|
6015
|
+
return { ...rest, commandId: envelope2.id, envelope: envelope2, status: "pending", updatedAt: now() };
|
|
6016
|
+
});
|
|
6017
|
+
record = doc.toJSON();
|
|
6018
|
+
if (record.commandId === envelope2.id) break;
|
|
6019
|
+
}
|
|
6020
|
+
const outcome = await sendOrderRefund(transport, envelope2);
|
|
6021
|
+
if (outcome.kind !== "unknown") {
|
|
6022
|
+
doc = await doc.incrementalModify((stored) => {
|
|
6023
|
+
if (stored.status !== "pending" || stored.commandId !== envelope2.id) return stored;
|
|
6024
|
+
const updatedAt = now();
|
|
6025
|
+
if (outcome.kind === "applied") {
|
|
6026
|
+
const { error: error2, ...rest } = stored;
|
|
6027
|
+
return { ...rest, status: "applied", result: outcome.refund, duplicate: outcome.duplicate, updatedAt };
|
|
6028
|
+
}
|
|
6029
|
+
if (outcome.kind === "rejected") return { ...stored, status: "rejected", error: outcome.error, updatedAt };
|
|
6030
|
+
const error = outcome.kind === "refused" ? { code: "refused", message: outcome.reason, data: { status: outcome.status } } : { code: "unauthorized", message: "unauthorized" };
|
|
6031
|
+
return { ...stored, status: "unsent", error, updatedAt };
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
return { outcome, record: doc.getLatest().toJSON() };
|
|
6035
|
+
}
|
|
6036
|
+
|
|
5609
6037
|
// src/tender/tender-state.ts
|
|
5610
6038
|
var MAX_TENDER_MINOR = 999999999;
|
|
5611
6039
|
var initialTenderState = {
|
|
@@ -5851,6 +6279,7 @@ export {
|
|
|
5851
6279
|
PosOrderOpenClosedError,
|
|
5852
6280
|
RECEIPT_SCHEMA_VERSION,
|
|
5853
6281
|
REGISTER_SESSION_MIGRATION_CLOSE_WAIT_MS,
|
|
6282
|
+
RefundAnswerPendingError,
|
|
5854
6283
|
RegisterApprovalRequiredError,
|
|
5855
6284
|
RegisterCloseIncompleteError,
|
|
5856
6285
|
RegisterIdInvalidError,
|
|
@@ -5860,17 +6289,21 @@ export {
|
|
|
5860
6289
|
RegisterNeedsUpgradeError,
|
|
5861
6290
|
RegisterSessionAlreadyOpenError,
|
|
5862
6291
|
RegisterSessionClosedError,
|
|
6292
|
+
RegisterSessionConflictError,
|
|
5863
6293
|
RegisterSessionOpenClosedError,
|
|
5864
6294
|
RegisterSessionRequiredError,
|
|
6295
|
+
RegisterTakeOverError,
|
|
5865
6296
|
RegisterTenderInProgressError,
|
|
5866
6297
|
SALE_SAVING,
|
|
5867
6298
|
TAX_ROUNDING_MIXED_NOTE,
|
|
5868
6299
|
TaxProvider,
|
|
5869
6300
|
UnsupportedOrderVersionError,
|
|
6301
|
+
abandonSession,
|
|
5870
6302
|
activePlan,
|
|
5871
6303
|
addEntryToCart,
|
|
5872
6304
|
addPosOrderCollection,
|
|
5873
6305
|
addRegisterSessionCollection,
|
|
6306
|
+
adoptRegisterResults,
|
|
5874
6307
|
advancePerpetual,
|
|
5875
6308
|
allocateOrderDiscount,
|
|
5876
6309
|
appliedMinor,
|
|
@@ -5942,6 +6375,8 @@ export {
|
|
|
5942
6375
|
posOrderCollection,
|
|
5943
6376
|
posOrderSchema,
|
|
5944
6377
|
posOrdersLogger,
|
|
6378
|
+
posRefundCollection,
|
|
6379
|
+
posRefundSchema,
|
|
5945
6380
|
productCategories,
|
|
5946
6381
|
productSortValue,
|
|
5947
6382
|
quickTenderedAmounts,
|
|
@@ -5968,6 +6403,7 @@ export {
|
|
|
5968
6403
|
sameSale,
|
|
5969
6404
|
searchProducts,
|
|
5970
6405
|
selectClosureRows,
|
|
6406
|
+
sendOrderRefund,
|
|
5971
6407
|
sessionOpenCommand,
|
|
5972
6408
|
sessionTransitionCommand,
|
|
5973
6409
|
sortProducts,
|
|
@@ -5975,6 +6411,8 @@ export {
|
|
|
5975
6411
|
startCounting,
|
|
5976
6412
|
stockOverlay$,
|
|
5977
6413
|
stockOverlayAsOf$,
|
|
6414
|
+
submitOrderRefund,
|
|
6415
|
+
takeOverSession,
|
|
5978
6416
|
taxFiguresForBasket,
|
|
5979
6417
|
taxLinesByRate,
|
|
5980
6418
|
taxLogger,
|