@tallyui/pos 2.0.0 → 3.0.0-next.1
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 +323 -61
- package/dist/index.js +1423 -186
- package/package.json +8 -8
- package/src/index.ts +15 -7
- package/src/order/index.ts +3 -0
- package/src/order/order-builder.ts +23 -7
- package/src/order/order-manager.ts +3 -1
- package/src/order/tax-figures.ts +23 -0
- package/src/order/types.ts +9 -3
- package/src/outbox/backend-not-found.ts +27 -0
- package/src/outbox/http-transport.ts +5 -4
- package/src/outbox/index.ts +2 -0
- package/src/outbox/logger.ts +3 -0
- package/src/outbox/order-outbox.ts +336 -27
- package/src/outbox/register-outbox.ts +250 -0
- package/src/outbox/types.test-d.ts +22 -0
- package/src/outbox/types.ts +17 -3
- package/src/outbox/use-order-outbox.ts +20 -6
- package/src/pos-order/__fixtures__/order-create-v3.json +97 -0
- package/src/pos-order/command.ts +99 -11
- package/src/pos-order/finalize.ts +145 -7
- package/src/pos-order/index.ts +2 -2
- package/src/pos-order/needs-attention.ts +9 -4
- package/src/pos-order/open.ts +100 -37
- package/src/pos-order/schema.ts +35 -5
- package/src/pos-order/types.ts +31 -5
- package/src/product/search-products.ts +5 -2
- package/src/receipt/build-receipt-data.ts +5 -3
- package/src/receipt/types.ts +1 -0
- package/src/register/closure-document.ts +7 -0
- package/src/register/index.ts +5 -3
- package/src/register/movement-input.ts +1 -1
- package/src/register/register-commands.ts +147 -0
- package/src/register/register-document.ts +26 -1
- package/src/register/session-store.ts +41 -12
- package/src/register/use-register-session.ts +46 -2
- package/src/sale/cart.ts +1 -0
- package/src/sale/use-sale.ts +97 -40
- package/src/store-settings/map-store-settings.ts +7 -2
- package/src/store-settings/use-store-settings.ts +55 -9
- package/src/tax/exact.ts +74 -11
- package/src/tax/index.ts +1 -1
- package/src/tax/tax-provider.tsx +26 -4
- package/src/tax/types.ts +6 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { Money, TallyConnector, SyncContext, StoreSettingsChoice, StoreSettings, StoreSettingsChoices, ProductTraits, PaymentMethodKind, CommandServerRefs, CommandWarning, CommandError, ServerCapabilities,
|
|
3
|
+
import { Money, TaxRounding, TallyConnector, SyncContext, StoreSettingsChoice, StoreSettings, StoreSettingsChoices, ProductTraits, PaymentMethodKind, CommandServerRefs, CommandWarning, CommandError, ServerCapabilities, OrderCreateEnvelope, RegisterCommandType, RegisterCommandResult, VariantSummary, CommandResult, AnyCommandEnvelope, CommandEnvelope, OrderCreatePayload, RegisterCommandEnvelope } from '@tallyui/core';
|
|
4
4
|
export { getProductStock, withStockOverlay } from '@tallyui/core';
|
|
5
5
|
export { countFresh, readFresh, watchFresh } from '@tallyui/core/rxdb';
|
|
6
6
|
import * as rxdb from 'rxdb';
|
|
@@ -66,8 +66,10 @@ declare function ratePpmFromPercent(percent: number | string): number;
|
|
|
66
66
|
* Throws RangeError unless amount and rate are safe integers and rate is non-negative.
|
|
67
67
|
*/
|
|
68
68
|
declare function taxMicros(amountMinor: number, ratePpm: number, pricesIncludeTax: boolean): bigint;
|
|
69
|
-
/**
|
|
70
|
-
|
|
69
|
+
/** #287: `half_up` is Math.round's rule, Vendure's DefaultMoneyStrategy (default-money-strategy.js:19-21). */
|
|
70
|
+
type RoundingMode = 'half_away_from_zero' | 'half_up';
|
|
71
|
+
/** Rounds micro-minor-units to integer minor units, half away from zero unless `mode` says otherwise. */
|
|
72
|
+
declare function roundMicrosToMinor(micros: bigint, mode?: RoundingMode): number;
|
|
71
73
|
interface TaxLineInput {
|
|
72
74
|
unitPriceMinor: number;
|
|
73
75
|
quantity: number;
|
|
@@ -93,6 +95,16 @@ interface RateTaxLine {
|
|
|
93
95
|
netMinor: number;
|
|
94
96
|
amountMinor: number;
|
|
95
97
|
}
|
|
98
|
+
type TaxedLine = {
|
|
99
|
+
netMinor: number;
|
|
100
|
+
discountMinor?: number;
|
|
101
|
+
taxInclusive: boolean;
|
|
102
|
+
taxLines: readonly {
|
|
103
|
+
code?: string;
|
|
104
|
+
ratePpm: number;
|
|
105
|
+
taxMicros: string;
|
|
106
|
+
}[];
|
|
107
|
+
};
|
|
96
108
|
/**
|
|
97
109
|
* Groups each line's stacked tax rates (ADR-040: each independently taxes the line's full
|
|
98
110
|
* tax-free base) by `code`+`ratePpm`, floors each group's exact tax to minor units, then
|
|
@@ -100,17 +112,10 @@ interface RateTaxLine {
|
|
|
100
112
|
* so the rates sum to exactly `orderTaxMinor`. A line's `netMinor` is in its OWN tax mode
|
|
101
113
|
* (`taxInclusive`): an inclusive line's net already contains its tax, so its tax-free base is
|
|
102
114
|
* `netMinor − roundMicrosToMinor(Σ its taxMicros)`; an exclusive line's base is `netMinor` as is.
|
|
103
|
-
* Shared by a receipt's tax summary and a Z report's per-rate breakdown.
|
|
115
|
+
* Shared by a receipt's tax summary and a Z report's per-rate breakdown. That is `per_order`'s split; a `per_line_items` or
|
|
116
|
+
* `per_rate_group_items` order's rows are `roundedTaxByRate`'s (#287).
|
|
104
117
|
*/
|
|
105
|
-
declare function taxLinesByRate(lines: readonly
|
|
106
|
-
netMinor: number;
|
|
107
|
-
taxInclusive: boolean;
|
|
108
|
-
taxLines: readonly {
|
|
109
|
-
code?: string;
|
|
110
|
-
ratePpm: number;
|
|
111
|
-
taxMicros: string;
|
|
112
|
-
}[];
|
|
113
|
-
}[], orderTaxMinor: number, taxLabels?: Record<number, string>): RateTaxLine[];
|
|
118
|
+
declare function taxLinesByRate(lines: readonly TaxedLine[], orderTaxMinor: number, taxLabels?: Record<number, string>, rounding?: TaxRounding): RateTaxLine[];
|
|
114
119
|
|
|
115
120
|
/** Tax rates as integer parts per million (19% = 190000). */
|
|
116
121
|
type TaxRateMap = {
|
|
@@ -120,15 +125,24 @@ type TaxRateMap = {
|
|
|
120
125
|
interface TaxContext {
|
|
121
126
|
/** Tax rate for a tax class as integer parts per million (19% = 190000); the default class when omitted or unknown. */
|
|
122
127
|
getTaxRatePpm(taxClass?: string): number;
|
|
128
|
+
/** The backend's name for that class's rate, when mapped: a `per_rate_group_items` store groups by name and value (#287). */
|
|
129
|
+
getTaxRateCode?(taxClass?: string): string | undefined;
|
|
123
130
|
pricesIncludeTax: boolean;
|
|
131
|
+
/** The store's tax rounding strategy (#287); absent means `per_order`, half away from zero. */
|
|
132
|
+
rounding?: TaxRounding;
|
|
124
133
|
}
|
|
125
134
|
|
|
135
|
+
declare const taxLogger: Logger;
|
|
126
136
|
interface TaxProviderProps {
|
|
127
137
|
ratesPpm: Record<string, number>;
|
|
128
138
|
pricesIncludeTax: boolean;
|
|
139
|
+
/** `ServerCapabilities.taxRounding` (#287); a change restarts an idle sale under it. */
|
|
140
|
+
rounding?: TaxRounding;
|
|
141
|
+
/** Tax class → the backend's rate name, for `per_rate_group_items`'s grouping (#287). */
|
|
142
|
+
rateCodes?: Record<string, string>;
|
|
129
143
|
children: ReactNode;
|
|
130
144
|
}
|
|
131
|
-
declare function TaxProvider({ ratesPpm, pricesIncludeTax, children }: TaxProviderProps): react_jsx_runtime.JSX.Element;
|
|
145
|
+
declare function TaxProvider({ ratesPpm, pricesIncludeTax, rounding, rateCodes, children }: TaxProviderProps): react_jsx_runtime.JSX.Element;
|
|
132
146
|
declare function useTax(): TaxContext;
|
|
133
147
|
|
|
134
148
|
type StoreSettingsResolution = {
|
|
@@ -162,7 +176,11 @@ declare function resolveStoreSettings(options: ResolveStoreSettingsOptions, choi
|
|
|
162
176
|
|
|
163
177
|
/** The replication's context with the settings' opaque pricing context; the key is left out when the settings have none. */
|
|
164
178
|
declare function withPricingContext(context: SyncContext, settings: StoreSettings): SyncContext;
|
|
165
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* `<TaxProvider>`'s props from the same settings, so its tax inclusivity, rounding and rate codes match the store's. `custom`
|
|
181
|
+
* rounding passes none, the default (#287). An explicit `rounding` prop after the spread still wins, since JSX
|
|
182
|
+
* applies props in order; so `<TaxProvider {...taxProviderProps(s)} rounding={undefined}>` wipes the store's rounding.
|
|
183
|
+
*/
|
|
166
184
|
declare function taxProviderProps(settings: StoreSettings): Omit<TaxProviderProps, 'children'>;
|
|
167
185
|
|
|
168
186
|
type StoreSettingsState = {
|
|
@@ -180,6 +198,7 @@ type StoreSettingsState = {
|
|
|
180
198
|
state: 'error';
|
|
181
199
|
error: unknown;
|
|
182
200
|
retry: () => void;
|
|
201
|
+
nextRetryAt?: number;
|
|
183
202
|
} | {
|
|
184
203
|
state: 'unsupported';
|
|
185
204
|
};
|
|
@@ -187,6 +206,10 @@ type StoreSettingsState = {
|
|
|
187
206
|
* Resolves the store settings on mount, and again when `connector` or `context` changes by
|
|
188
207
|
* identity (a store switch), so memoise both; the last request wins. `loadChoice` and
|
|
189
208
|
* `saveChoice` are read when a request starts, so inline functions don't trigger a re-resolve.
|
|
209
|
+
* The ready settings carry the store's `taxRounding` from the capabilities, so `taxProviderProps` passes it on (#324).
|
|
210
|
+
* The capabilities are read beside the settings, before `ready`, so each resolve emits the settings once, with the
|
|
211
|
+
* rounding already known: no later change of `settings` holds a sale.
|
|
212
|
+
* Unknown rounding keeps settings unresolved and retries by itself; show "Can't reach the store's settings yet. Retrying…" while `nextRetryAt` is set.
|
|
190
213
|
*
|
|
191
214
|
* ```tsx
|
|
192
215
|
* const store = useStoreSettings({ connector, context, loadChoice, saveChoice });
|
|
@@ -225,6 +248,8 @@ interface Order {
|
|
|
225
248
|
note: string;
|
|
226
249
|
currency: string;
|
|
227
250
|
pricesIncludeTax: boolean;
|
|
251
|
+
/** The store's tax rounding strategy the figures were computed with (#287), from the sale's tax context; absent is per_order. */
|
|
252
|
+
taxRounding?: TaxRounding;
|
|
228
253
|
subtotalMinor: number;
|
|
229
254
|
discountMinor: number;
|
|
230
255
|
taxMinor: number;
|
|
@@ -236,6 +261,12 @@ interface Order {
|
|
|
236
261
|
createdAt: string;
|
|
237
262
|
updatedAt: string;
|
|
238
263
|
}
|
|
264
|
+
/** An Order as it was stored and sent, whose customer id may have been left out. */
|
|
265
|
+
type SentOrder = Omit<Order, 'customer'> & {
|
|
266
|
+
customer: (Omit<CustomerSummary, 'id'> & {
|
|
267
|
+
id?: string;
|
|
268
|
+
}) | null;
|
|
269
|
+
};
|
|
239
270
|
/**
|
|
240
271
|
* Figures for showing the cart or a receipt in the store's display mode (ADR-063); never sent to the server.
|
|
241
272
|
* A parked order's saved draft may carry them; they are recomputed from the lines on resume, so never authoritative.
|
|
@@ -326,6 +357,7 @@ interface AddLineInput {
|
|
|
326
357
|
code?: string;
|
|
327
358
|
ratePpm: number;
|
|
328
359
|
}>;
|
|
360
|
+
taxClass?: string;
|
|
329
361
|
}
|
|
330
362
|
|
|
331
363
|
interface OrderBuilderOptions {
|
|
@@ -354,6 +386,29 @@ interface OrderBuilder {
|
|
|
354
386
|
}
|
|
355
387
|
declare function createOrderBuilder(options: OrderBuilderOptions): OrderBuilder;
|
|
356
388
|
|
|
389
|
+
/** A sent line; `discountMinor` is its `lines[].discountMinor` (line discounts + its order share), in its own mode. */
|
|
390
|
+
type BasketLine = {
|
|
391
|
+
unitPriceMinor: number;
|
|
392
|
+
quantity: number;
|
|
393
|
+
discountMinor?: number;
|
|
394
|
+
taxInclusive: boolean;
|
|
395
|
+
taxLines: Array<{
|
|
396
|
+
code?: string;
|
|
397
|
+
ratePpm: number;
|
|
398
|
+
}>;
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* The till's settlement figures for a sent basket under a store's strategy (#287), computed exactly as a sale computes
|
|
402
|
+
* them, so a backend can score its own orders against the till (vendurepos/app#38). Each discountMinor is applied as a
|
|
403
|
+
* fixed line discount in the line's own mode.
|
|
404
|
+
*/
|
|
405
|
+
declare function taxFiguresForBasket(currency: string, pricesIncludeTax: boolean, lines: readonly BasketLine[], rounding?: TaxRounding): {
|
|
406
|
+
subtotalMinor: number;
|
|
407
|
+
taxMinor: number;
|
|
408
|
+
totalMinor: number;
|
|
409
|
+
taxByRate: RateTaxLine[];
|
|
410
|
+
};
|
|
411
|
+
|
|
357
412
|
/**
|
|
358
413
|
* Splits an order discount across lines in proportion to each line's pre-order-discount amount,
|
|
359
414
|
* in its own tax mode (ADR-062), with largest-remainder rounding to the minor unit.
|
|
@@ -410,6 +465,7 @@ interface ReceiptData {
|
|
|
410
465
|
orderNumber: string;
|
|
411
466
|
date: string;
|
|
412
467
|
cashier?: string;
|
|
468
|
+
customer?: string;
|
|
413
469
|
register?: string;
|
|
414
470
|
};
|
|
415
471
|
lineItems: ReceiptLineItem[];
|
|
@@ -451,7 +507,7 @@ interface ReceiptConfig {
|
|
|
451
507
|
taxLabels?: Record<number, string>;
|
|
452
508
|
}
|
|
453
509
|
|
|
454
|
-
declare function buildReceiptData(order:
|
|
510
|
+
declare function buildReceiptData(order: SentOrder, config: ReceiptConfig): ReceiptData;
|
|
455
511
|
|
|
456
512
|
/**
|
|
457
513
|
* Filters product documents by a cashier's search term, through traits, so
|
|
@@ -474,6 +530,22 @@ declare function stockOverlay$(collection: RxCollection): Observable<Map<string,
|
|
|
474
530
|
declare function stockOverlayAsOf$(collection: RxCollection): Observable<string | undefined>;
|
|
475
531
|
|
|
476
532
|
type PosOrderSyncStatus = 'pending' | 'applied' | 'rejected';
|
|
533
|
+
type PosOrderLocalWarning = {
|
|
534
|
+
code: 'customer_omitted';
|
|
535
|
+
field: 'email' | 'id';
|
|
536
|
+
} | {
|
|
537
|
+
code: 'payment_reference_dropped';
|
|
538
|
+
paymentId: string;
|
|
539
|
+
};
|
|
540
|
+
/** The outbox's server-failure state for a pending order, kept so a restart restores it. */
|
|
541
|
+
interface PosOrderServerFailures {
|
|
542
|
+
/** The stuck clock's start in ms: the outbox's virtual start, which leaves out offline gaps. */
|
|
543
|
+
since: number;
|
|
544
|
+
/** The latest server-answered failure reason. */
|
|
545
|
+
reason: string;
|
|
546
|
+
/** The order failed when sent alone (as a probe or while isolated), so it is retried alone. */
|
|
547
|
+
isolated: boolean;
|
|
548
|
+
}
|
|
477
549
|
interface PosOrderLine {
|
|
478
550
|
id: string;
|
|
479
551
|
productId: string;
|
|
@@ -521,21 +593,28 @@ interface PosOrder {
|
|
|
521
593
|
registerId?: string;
|
|
522
594
|
/**
|
|
523
595
|
* The register session the sale was taken in (ADR-032): its closure counts this order.
|
|
524
|
-
*
|
|
596
|
+
* Sent at `order.create` version 3 as the payload's `sessionId` (the stamped or late session, one field).
|
|
525
597
|
*/
|
|
526
598
|
sessionId?: string;
|
|
527
599
|
/**
|
|
528
600
|
* Set only by `useSale`'s late-sale path (ADR-032): the session the sale was taken for, which
|
|
529
601
|
* refused the stamp after the money was taken. Such an order has no `sessionId`, so no closure
|
|
530
|
-
* counts it.
|
|
602
|
+
* counts it. Sent at `order.create` version 3 as the payload's `sessionId` (the stamped or late session, one field).
|
|
531
603
|
*/
|
|
532
604
|
lateSessionId?: string;
|
|
533
|
-
/**
|
|
605
|
+
/**
|
|
606
|
+
* The order.create version every attempt under this `commandId` goes out at: the outbox records it before the first
|
|
607
|
+
* send, and lowers it only on a downgrade (ADR-065 amendment). Absent means not sent yet (or requeued).
|
|
608
|
+
*/
|
|
609
|
+
sentVersion?: 1 | 2 | 3 | 4;
|
|
610
|
+
/** The version first tried, before the downgrade (the order's audit). */
|
|
611
|
+
downgradedFrom?: 1 | 2 | 3 | 4;
|
|
612
|
+
/** ADR-065: the receipt's display figures, in integer minor units of `currency` at `exponent`. */
|
|
534
613
|
display?: DisplayTotals & {
|
|
535
614
|
currency: string;
|
|
536
615
|
exponent: number;
|
|
537
616
|
};
|
|
538
|
-
/** ADR-065: tax by rate, named as `taxLinesByRate` names them (`amountMinor` is the tax).
|
|
617
|
+
/** ADR-065: tax by rate, named as `taxLinesByRate` names them (`amountMinor` is the tax). */
|
|
539
618
|
taxByRate?: Array<{
|
|
540
619
|
ratePpm: number;
|
|
541
620
|
code?: string;
|
|
@@ -544,11 +623,18 @@ interface PosOrder {
|
|
|
544
623
|
amountMinor: number;
|
|
545
624
|
grossMinor: number;
|
|
546
625
|
}>;
|
|
626
|
+
/**
|
|
627
|
+
* The tax rounding the figures were computed with (#287), frozen at finalize; the till's own record, never sent.
|
|
628
|
+
* Version 6's migration sets the default on every older row.
|
|
629
|
+
*/
|
|
630
|
+
taxRounding: TaxRounding;
|
|
547
631
|
cashierRef?: string;
|
|
548
632
|
syncStatus: PosOrderSyncStatus;
|
|
549
633
|
commandId: string;
|
|
550
634
|
serverRefs?: CommandServerRefs;
|
|
551
635
|
warnings?: CommandWarning[];
|
|
636
|
+
localWarnings?: PosOrderLocalWarning[];
|
|
637
|
+
serverFailures?: PosOrderServerFailures;
|
|
552
638
|
error?: CommandError;
|
|
553
639
|
updatedAt: string;
|
|
554
640
|
}
|
|
@@ -557,21 +643,38 @@ interface PosOrder {
|
|
|
557
643
|
declare function uuidv7(now?: number, randomBytes?: (n: number) => Uint8Array): string;
|
|
558
644
|
|
|
559
645
|
interface FinalizeOptions {
|
|
646
|
+
localWarnings?: PosOrderLocalWarning[];
|
|
560
647
|
registerId?: string;
|
|
561
648
|
cashierRef?: string;
|
|
562
649
|
now?: Date;
|
|
650
|
+
/** Tests only: replaces uuidv7. Every id it returns must be at most 255 characters with no NUL (order.create's bound). */
|
|
563
651
|
newId?: () => string;
|
|
564
652
|
/** The store's `order.create` capability (ADR-062); `undefined` is treated as 1. */
|
|
565
653
|
capabilities?: ServerCapabilities;
|
|
566
654
|
}
|
|
567
|
-
/**
|
|
655
|
+
/**
|
|
656
|
+
* Turns a fully paid builder Order into a pending PosOrder without mutating it. The PosOrder holds the sent form,
|
|
657
|
+
* frozen: line names and v3 discount labels are cut to PAYLOAD_STRING_MAX with NUL stripped (`cutText`), and a
|
|
658
|
+
* customer email or id the shape check would refuse is left out, so `toOrderCreateEnvelope` sends it unchanged.
|
|
659
|
+
*/
|
|
568
660
|
declare function finalizeOrder(order: Order, options?: FinalizeOptions): PosOrder;
|
|
569
661
|
|
|
662
|
+
declare class UnsupportedOrderVersionError extends Error {
|
|
663
|
+
readonly needed: number;
|
|
664
|
+
readonly supported: number;
|
|
665
|
+
readonly code = "UNSUPPORTED_ORDER_VERSION";
|
|
666
|
+
constructor(needed: number, supported: number);
|
|
667
|
+
}
|
|
570
668
|
/**
|
|
571
|
-
* Builds the ADR-038 order.create envelope for a PosOrder.
|
|
572
|
-
*
|
|
669
|
+
* Builds the ADR-038 order.create envelope for a PosOrder. It sends the stored order unchanged, so every resend of
|
|
670
|
+
* an order is byte-identical, and an order stored by an older till goes out exactly as that till sent it:
|
|
671
|
+
* `finalizeOrder` freezes the sent form (names and labels cut, an unsendable customer email or id left out).
|
|
672
|
+
* ADR-065's figures make version 3; otherwise a discounted order is version 2 (ADR-062), else version 1, byte-identical.
|
|
673
|
+
* A version-3 order goes as version 4 (#286) only when `maxVersion` is at least 4 and its `sentVersion` doesn't cap it.
|
|
573
674
|
*/
|
|
574
|
-
declare function toOrderCreateEnvelope(order: PosOrder, deviceId: string, attempt?: number
|
|
675
|
+
declare function toOrderCreateEnvelope(order: PosOrder, deviceId: string, attempt?: number, options?: {
|
|
676
|
+
maxVersion?: number;
|
|
677
|
+
}): OrderCreateEnvelope;
|
|
575
678
|
|
|
576
679
|
/**
|
|
577
680
|
* Version 1 adds the optional `sessionId` (ADR-032); nothing else changed from version 0.
|
|
@@ -579,6 +682,14 @@ declare function toOrderCreateEnvelope(order: PosOrder, deviceId: string, attemp
|
|
|
579
682
|
* sale), and ADR-065's `display` and `taxByRate`, declared ahead of the job that writes them so a
|
|
580
683
|
* till migrates once. Create the collection with `posOrderCollection()`, never with this schema
|
|
581
684
|
* alone: RxDB refuses a version above 0 without its migration strategies.
|
|
685
|
+
* Version 3 adds an index on `sessionId` (with its `maxLength`), and the optional `sentVersion` and `downgradedFrom` (the outbox's version fallback), and changes nothing else.
|
|
686
|
+
* Version 4 adds the optional `localWarnings` and `serverFailures` (the outbox's stuck clock start, latest reason and
|
|
687
|
+
* isolation, restored when an outbox starts), and changes nothing else.
|
|
688
|
+
* Version 5 lets `sentVersion` and `downgradedFrom` be 4 (order.create version 4, #286), and changes nothing else. Its
|
|
689
|
+
* migration sets a row's missing `sentVersion` to its content version, the version any earlier attempt went out at.
|
|
690
|
+
* Like version 4 (ADR-069), it is one-way: an older build shows no orders.
|
|
691
|
+
* Version 6 adds `taxRounding`, the strategy the sale's figures were computed with (#287; never sent), and changes
|
|
692
|
+
* nothing else. Its migration sets it on every older row. It is one-way like version 5.
|
|
582
693
|
*/
|
|
583
694
|
declare const posOrderSchema: RxJsonSchema<PosOrder>;
|
|
584
695
|
/**
|
|
@@ -604,8 +715,9 @@ declare function posOrderCollection(): {
|
|
|
604
715
|
declare const posOrdersLogger: Logger;
|
|
605
716
|
/**
|
|
606
717
|
* `addPosOrderCollection` stopped, before any further write, because its database is closing: it
|
|
607
|
-
* was called once `close()` had begun,
|
|
608
|
-
* `POS_ORDER_MIGRATION_CLOSE_WAIT_MS`). No order is lost: reopen the
|
|
718
|
+
* was called once `close()` had begun, the close cancelled its migration (RxDB 17.5), or the close
|
|
719
|
+
* stopped waiting for it (after `POS_ORDER_MIGRATION_CLOSE_WAIT_MS`). No order is lost: reopen the
|
|
720
|
+
* database and call it again.
|
|
609
721
|
*/
|
|
610
722
|
declare class PosOrderOpenClosedError extends Error {
|
|
611
723
|
readonly databaseName: string;
|
|
@@ -618,16 +730,20 @@ declare class PosOrderOpenClosedError extends Error {
|
|
|
618
730
|
* stopped and closes the collection, so the app can surface the error and open again; it never
|
|
619
731
|
* deletes an order.
|
|
620
732
|
*
|
|
621
|
-
* RxDB
|
|
733
|
+
* RxDB 17.5.0 trusts the status its last migration stored: a leftover `ERROR` rejects
|
|
622
734
|
* `migratePromise` at once while the migration keeps running (a close then interrupts it), and a
|
|
623
735
|
* `DONE` left before a rollback resolves it before the older version's new orders have moved. So the
|
|
624
|
-
* collection is added without `autoMigrate
|
|
625
|
-
* (
|
|
736
|
+
* collection is added without `autoMigrate` and the migration is started and awaited directly: RxDB
|
|
737
|
+
* 17.5's `startMigration()` ignores a leftover status (only `migratePromise()` trusts a leftover
|
|
738
|
+
* `DONE`), which lets a rolled-back or interrupted run migrate again and recovers its orders. The
|
|
739
|
+
* status is reset first so its record describes the new run, and a failed run's checkpoint is
|
|
740
|
+
* removed (RxDB bug 1); never an order or its storage.
|
|
626
741
|
*
|
|
627
|
-
* A close waits for the
|
|
628
|
-
* database's close has begun,
|
|
629
|
-
*
|
|
630
|
-
*
|
|
742
|
+
* A close waits for the open, up to `POS_ORDER_MIGRATION_CLOSE_WAIT_MS`, and cancels a running
|
|
743
|
+
* migration (RxDB 17.5). Called once the database's close has begun, when the close cancels its
|
|
744
|
+
* migration, or when the close stops waiting, it rejects with `PosOrderOpenClosedError`
|
|
745
|
+
* (`code: 'POS_ORDER_OPEN_CLOSED'`) before any further write: reopen and call it again. Any other
|
|
746
|
+
* rejection (DM4, a storage error) is a real failure.
|
|
631
747
|
*
|
|
632
748
|
* `closeWaitMs` is for tests only; the first open on a database sets it for that database's close.
|
|
633
749
|
*/
|
|
@@ -644,10 +760,14 @@ declare function getDeviceId(storage: {
|
|
|
644
760
|
} | null, key: string): string;
|
|
645
761
|
|
|
646
762
|
/**
|
|
647
|
-
* The orders a cashier must look at: rejected, applied with
|
|
648
|
-
* closed (`lateSessionId
|
|
763
|
+
* The orders a cashier must look at: rejected, applied with a known warning, taken after their
|
|
764
|
+
* session closed (`lateSessionId`) or with local warnings (whatever the sync status), or pending with a `commandId` in
|
|
765
|
+
* `stuckCommandIds` (the outbox's `OutboxState.stuck`: the store keeps failing it), newest first.
|
|
766
|
+
* A warning code this till version doesn't know is not shown here (`knownWarnings`). Never changes `orders`.
|
|
649
767
|
*/
|
|
650
|
-
declare function needsAttention(orders: PosOrder[]
|
|
768
|
+
declare function needsAttention(orders: PosOrder[], options?: {
|
|
769
|
+
stuckCommandIds?: readonly string[];
|
|
770
|
+
}): PosOrder[];
|
|
651
771
|
|
|
652
772
|
type SaleContent = Pick<PosOrder, 'totalMinor' | 'currency' | 'lines' | 'payments'>;
|
|
653
773
|
/**
|
|
@@ -877,6 +997,7 @@ type RegisterCounters = {
|
|
|
877
997
|
* same atomic write as its number, and `applied` once its period is in the perpetual totals.
|
|
878
998
|
*/
|
|
879
999
|
type RegisterBucket = Partial<RegisterCounters> & {
|
|
1000
|
+
commands_since?: string;
|
|
880
1001
|
closure_reservation?: {
|
|
881
1002
|
row: Closure;
|
|
882
1003
|
applied: boolean;
|
|
@@ -911,6 +1032,9 @@ declare function readBoundRegister(host: RegisterHost, storeKey: string): Promis
|
|
|
911
1032
|
id: string;
|
|
912
1033
|
name: string;
|
|
913
1034
|
} | null>;
|
|
1035
|
+
declare class RegisterIdInvalidError extends Error {
|
|
1036
|
+
constructor();
|
|
1037
|
+
}
|
|
914
1038
|
declare function bindRegister(host: RegisterHost, storeKey: string, register: {
|
|
915
1039
|
id: string | null;
|
|
916
1040
|
name: string | null;
|
|
@@ -944,10 +1068,17 @@ declare class RegisterSessionRequiredError extends Error {
|
|
|
944
1068
|
declare class RegisterSessionClosedError extends Error {
|
|
945
1069
|
constructor();
|
|
946
1070
|
}
|
|
1071
|
+
/** ADR-068 6a: movement amounts must be safe integers, positive for paid_in/paid_out and zero for no_sale. */
|
|
1072
|
+
declare class RegisterMovementAmountError extends Error {
|
|
1073
|
+
constructor(type: 'paid_in' | 'paid_out' | 'no_sale', amountMinor: number);
|
|
1074
|
+
}
|
|
1075
|
+
declare class RegisterMovementReasonError extends Error {
|
|
1076
|
+
constructor();
|
|
1077
|
+
}
|
|
947
1078
|
/**
|
|
948
1079
|
* The movement is recorded, on a session that closed while it was saved, and no closure is known
|
|
949
|
-
* to count it, so it
|
|
950
|
-
*
|
|
1080
|
+
* to count it, so it stays local on the till. The caller must not record it again; the server
|
|
1081
|
+
* refuses it once the closure exists (ADR-068 4). The message can be shown to a cashier as it is.
|
|
951
1082
|
*/
|
|
952
1083
|
declare class RegisterMovementStrandedError extends Error {
|
|
953
1084
|
readonly id: string;
|
|
@@ -975,9 +1106,9 @@ declare function openSession(sessions: RegisterSessionCollection, input: {
|
|
|
975
1106
|
day: number;
|
|
976
1107
|
};
|
|
977
1108
|
storeKey?: string | null;
|
|
978
|
-
}): Promise<rxdb.RxDocument<RegisterSession, {}>>;
|
|
979
|
-
declare const startCounting: (sessions: RegisterSessionCollection, id: string) => Promise<rxdb.RxDocument<RegisterSession, {}>>;
|
|
980
|
-
declare const backToSelling: (sessions: RegisterSessionCollection, id: string) => Promise<rxdb.RxDocument<RegisterSession, {}>>;
|
|
1109
|
+
}): Promise<rxdb.RxDocument<RegisterSession, {}, unknown>>;
|
|
1110
|
+
declare const startCounting: (sessions: RegisterSessionCollection, id: string) => Promise<rxdb.RxDocument<RegisterSession, {}, unknown>>;
|
|
1111
|
+
declare const backToSelling: (sessions: RegisterSessionCollection, id: string) => Promise<rxdb.RxDocument<RegisterSession, {}, unknown>>;
|
|
981
1112
|
/**
|
|
982
1113
|
* Closes the session with its counted tenders (minor units); `timezone` dates a session opened
|
|
983
1114
|
* without a business day. `approvedBy`, the manager who approved the count, is written in the same
|
|
@@ -988,7 +1119,7 @@ declare function closeSession(sessions: RegisterSessionCollection, id: string, i
|
|
|
988
1119
|
closedBy?: string;
|
|
989
1120
|
approvedBy?: string;
|
|
990
1121
|
timezone?: string;
|
|
991
|
-
}): Promise<rxdb.RxDocument<RegisterSession, {}>>;
|
|
1122
|
+
}): Promise<rxdb.RxDocument<RegisterSession, {}, unknown>>;
|
|
992
1123
|
/**
|
|
993
1124
|
* Records a movement on a session that is `open` or `counting`; a closed or missing one is refused.
|
|
994
1125
|
* A close that races the insert ends in `RegisterSessionClosedError` (removed) or
|
|
@@ -1000,14 +1131,14 @@ declare function recordMovement(sessions: RegisterSessionCollection, movements:
|
|
|
1000
1131
|
amountMinor: number;
|
|
1001
1132
|
reason: string;
|
|
1002
1133
|
actor: string;
|
|
1003
|
-
}): Promise<rxdb.RxDocument<CashMovement, {}>>;
|
|
1134
|
+
}): Promise<rxdb.RxDocument<CashMovement, {}, unknown>>;
|
|
1004
1135
|
/**
|
|
1005
1136
|
* Reverses a movement with a `void` row while its session is live; repeated or concurrent calls
|
|
1006
1137
|
* share one reversal. A close that races the writes ends in `RegisterMovementStrandedError` (kept;
|
|
1007
1138
|
* don't void it again). Without `closures`, nothing can prove the reversal counted, so a re-read
|
|
1008
1139
|
* that finds the session closed always ends in `RegisterMovementStrandedError`.
|
|
1009
1140
|
*/
|
|
1010
|
-
declare function voidMovement(sessions: RegisterSessionCollection, movements: CashMovementCollection, movementId: string, actor: string, closures?: ClosureCollection): Promise<rxdb.RxDocument<CashMovement, {}>>;
|
|
1141
|
+
declare function voidMovement(sessions: RegisterSessionCollection, movements: CashMovementCollection, movementId: string, actor: string, closures?: ClosureCollection): Promise<rxdb.RxDocument<CashMovement, {}, unknown>>;
|
|
1011
1142
|
/**
|
|
1012
1143
|
* Freezes a closed session's figures into its closure, once. The draft is reserved on the register
|
|
1013
1144
|
* document with its number (`mintClosureNumber`), so a failed insert or a restarted till reuses the
|
|
@@ -1039,7 +1170,7 @@ declare function writeClosure({ closures, register, storeKey, session, counted,
|
|
|
1039
1170
|
/** The app's version, stamped on the closure. */
|
|
1040
1171
|
softwareVersion: string;
|
|
1041
1172
|
timezone?: string;
|
|
1042
|
-
}): Promise<rxdb.RxDocument<Closure, {}>>;
|
|
1173
|
+
}): Promise<rxdb.RxDocument<Closure, {}, unknown>>;
|
|
1043
1174
|
|
|
1044
1175
|
/**
|
|
1045
1176
|
* Corrections layered onto a closure's recorded figures (ADR-032): late sales, late cash
|
|
@@ -1212,6 +1343,11 @@ declare function selectClosureRows(rows: readonly Closure[], scope: ClosureScope
|
|
|
1212
1343
|
declare function minorToDecimal(minor: number, exponent: number): string;
|
|
1213
1344
|
|
|
1214
1345
|
type Values = Record<string, unknown>;
|
|
1346
|
+
/**
|
|
1347
|
+
* The Z report's line when a session's sales used more than one tax rounding (#287; wording approved by the Front
|
|
1348
|
+
* desk, 2026-09-30). The closure template lives in the apps, so the document carries it ready to print.
|
|
1349
|
+
*/
|
|
1350
|
+
declare const TAX_ROUNDING_MIXED_NOTE = "This register's sales used more than one tax rounding method. Each sale's tax is as its receipt showed.";
|
|
1215
1351
|
type ClosureContext = {
|
|
1216
1352
|
store: Values;
|
|
1217
1353
|
currency: string;
|
|
@@ -1234,6 +1370,7 @@ type ClosureContext = {
|
|
|
1234
1370
|
declare function formatClosureDate(value: string | null | undefined, context: Pick<ClosureContext, 'timezone' | 'locale'>): Record<string, string>;
|
|
1235
1371
|
declare function buildClosureDocument(row: Closure, context: ClosureContext): {
|
|
1236
1372
|
closure: {
|
|
1373
|
+
tax_rounding_note?: string | undefined;
|
|
1237
1374
|
has_sales: boolean;
|
|
1238
1375
|
has_perpetual: boolean;
|
|
1239
1376
|
} & {
|
|
@@ -1344,6 +1481,7 @@ declare function buildClosureDocument(row: Closure, context: ClosureContext): {
|
|
|
1344
1481
|
};
|
|
1345
1482
|
declare function buildXReportDocument(session: RegisterSession, context: ClosureContext): {
|
|
1346
1483
|
closure: {
|
|
1484
|
+
tax_rounding_note?: string | undefined;
|
|
1347
1485
|
has_sales: boolean;
|
|
1348
1486
|
has_perpetual: boolean;
|
|
1349
1487
|
} & {
|
|
@@ -1486,6 +1624,48 @@ type RegisterFact = (Human & {
|
|
|
1486
1624
|
});
|
|
1487
1625
|
declare function recordRegisterFact(fact: RegisterFact): void;
|
|
1488
1626
|
|
|
1627
|
+
declare const registerCommandsLogger: Logger;
|
|
1628
|
+
interface RegisterCommand {
|
|
1629
|
+
key: string;
|
|
1630
|
+
registerId: string;
|
|
1631
|
+
seq: number;
|
|
1632
|
+
commandId: string;
|
|
1633
|
+
type: RegisterCommandType;
|
|
1634
|
+
version: number;
|
|
1635
|
+
payload: Record<string, unknown>;
|
|
1636
|
+
createdAt: string;
|
|
1637
|
+
syncStatus: 'pending' | 'applied' | 'rejected';
|
|
1638
|
+
error?: CommandError;
|
|
1639
|
+
result?: RegisterCommandResult;
|
|
1640
|
+
updatedAt: string;
|
|
1641
|
+
}
|
|
1642
|
+
type RegisterCommandCollection = RxCollection<RegisterCommand>;
|
|
1643
|
+
declare const registerCommandSchema: RxJsonSchema<RegisterCommand>;
|
|
1644
|
+
declare const registerCommandCollection: () => {
|
|
1645
|
+
schema: RxJsonSchema<RegisterCommand>;
|
|
1646
|
+
};
|
|
1647
|
+
type BuiltCommand = Pick<RegisterCommand, 'key' | 'type' | 'payload'> & {
|
|
1648
|
+
version: 1;
|
|
1649
|
+
};
|
|
1650
|
+
declare function sessionOpenCommand(s: RegisterSession): BuiltCommand;
|
|
1651
|
+
declare function sessionTransitionCommand(s: RegisterSession & {
|
|
1652
|
+
status_at: string;
|
|
1653
|
+
}): BuiltCommand;
|
|
1654
|
+
declare function movementCommand(m: CashMovement): BuiltCommand;
|
|
1655
|
+
declare function closureCommand(c: Closure): BuiltCommand;
|
|
1656
|
+
/** Appends missing facts in dependency order; the bytes of an existing key never change. */
|
|
1657
|
+
declare function reconcileRegisterCommands({ commands, sessions, movements, closures, host, storeKey, registerId, now, observed }: {
|
|
1658
|
+
commands: RegisterCommandCollection;
|
|
1659
|
+
sessions: RegisterSessionCollection;
|
|
1660
|
+
movements: CashMovementCollection;
|
|
1661
|
+
closures: ClosureCollection;
|
|
1662
|
+
host: RegisterHost;
|
|
1663
|
+
storeKey: string;
|
|
1664
|
+
registerId: string;
|
|
1665
|
+
now?: string;
|
|
1666
|
+
observed?: RegisterSession[];
|
|
1667
|
+
}): Promise<string[]>;
|
|
1668
|
+
|
|
1489
1669
|
/** A sale is at tender, so the till can't count or close under it. Its message can be shown to a cashier as it is. */
|
|
1490
1670
|
declare class RegisterTenderInProgressError extends Error {
|
|
1491
1671
|
constructor();
|
|
@@ -1509,6 +1689,9 @@ interface UseRegisterSessionOptions {
|
|
|
1509
1689
|
movements: CashMovementCollection | null;
|
|
1510
1690
|
/** `closures`; `null` while it opens. */
|
|
1511
1691
|
closures: ClosureCollection | null;
|
|
1692
|
+
/** `register_commands`, created with `registerCommandCollection()`; `null` while it opens. */
|
|
1693
|
+
commands?: RegisterCommandCollection | null;
|
|
1694
|
+
capabilities?: ServerCapabilities;
|
|
1512
1695
|
/** `pos_orders`: a session's sales are those whose `sessionId` is its id. */
|
|
1513
1696
|
orders: RxCollection<PosOrder> | null;
|
|
1514
1697
|
/** The register document's host, for the closure number and perpetual totals (`writeClosure`); `null` while it opens. */
|
|
@@ -1573,9 +1756,9 @@ declare function useRegisterSession(options: UseRegisterSessionOptions): {
|
|
|
1573
1756
|
openSession: (input: {
|
|
1574
1757
|
expectedFloatMinor: number | null;
|
|
1575
1758
|
countedFloatMinor: number;
|
|
1576
|
-
}) => Promise<rxdb.RxDocument<RegisterSession, {}>>;
|
|
1577
|
-
startCounting: () => Promise<rxdb.
|
|
1578
|
-
backToSelling: () => Promise<rxdb.
|
|
1759
|
+
}) => Promise<rxdb.RxDocument<RegisterSession, {}, unknown>>;
|
|
1760
|
+
startCounting: () => Promise<rxdb.RxDocumentBase<RegisterSession, {}, unknown> & RegisterSession & rxdb.ExtendObservables<RegisterSession> & rxdb.ExtendReactivity<RegisterSession, unknown>>;
|
|
1761
|
+
backToSelling: () => Promise<rxdb.RxDocumentBase<RegisterSession, {}, unknown> & RegisterSession & rxdb.ExtendObservables<RegisterSession> & rxdb.ExtendReactivity<RegisterSession, unknown>>;
|
|
1579
1762
|
/**
|
|
1580
1763
|
* `counted` maps each tender to its counted minor units. An interrupted close resumes with
|
|
1581
1764
|
* the count persisted on the session, not the one passed to the retry (WCPOS uses the retry's).
|
|
@@ -1590,13 +1773,13 @@ declare function useRegisterSession(options: UseRegisterSessionOptions): {
|
|
|
1590
1773
|
counted: Record<string, number>;
|
|
1591
1774
|
approvedBy?: string;
|
|
1592
1775
|
approvedByName?: string;
|
|
1593
|
-
}) => Promise<rxdb.RxDocument<Closure, {}>>;
|
|
1776
|
+
}) => Promise<rxdb.RxDocument<Closure, {}, unknown>>;
|
|
1594
1777
|
recordMovement: (input: {
|
|
1595
1778
|
type: "paid_in" | "paid_out" | "no_sale";
|
|
1596
1779
|
amountMinor: number;
|
|
1597
1780
|
reason: string;
|
|
1598
|
-
}) => Promise<rxdb.RxDocument<CashMovement, {}>>;
|
|
1599
|
-
voidMovement: (id: string) => Promise<rxdb.RxDocument<CashMovement, {}>>;
|
|
1781
|
+
}) => Promise<rxdb.RxDocument<CashMovement, {}, unknown>>;
|
|
1782
|
+
voidMovement: (id: string) => Promise<rxdb.RxDocument<CashMovement, {}, unknown>>;
|
|
1600
1783
|
};
|
|
1601
1784
|
};
|
|
1602
1785
|
|
|
@@ -1620,7 +1803,7 @@ type SaleStage = {
|
|
|
1620
1803
|
method: 'cash' | 'external';
|
|
1621
1804
|
} | {
|
|
1622
1805
|
kind: 'receipt';
|
|
1623
|
-
order:
|
|
1806
|
+
order: SentOrder;
|
|
1624
1807
|
posOrder: PosOrder;
|
|
1625
1808
|
};
|
|
1626
1809
|
/** TallyUI finalizeOrder's refusal below order.create v2 (c19a203), shown when the discount is applied; finalize stays the backstop. */
|
|
@@ -1668,6 +1851,8 @@ declare function useSale(settings: Pick<StoreSettings, 'currency'>, opts: {
|
|
|
1668
1851
|
/** A line's discount, or the order's without a line; returns the refusal to show, or null once applied. */
|
|
1669
1852
|
applyDiscount(lineId: string | null, discount: Discount): string | null;
|
|
1670
1853
|
removeDiscount(id: string): void;
|
|
1854
|
+
/** the picked customer reaches the server as order.create v3's customer.customerId */
|
|
1855
|
+
setCustomer(customer: CustomerSummary | null): void;
|
|
1671
1856
|
/**
|
|
1672
1857
|
* Pins `options.session` for this tender when given, else the rendered `session` option. Pass the
|
|
1673
1858
|
* session `useRegisterSession`'s `requireSaleSession()` returned: the rendered one can lag a session
|
|
@@ -1696,7 +1881,9 @@ declare function useSale(settings: Pick<StoreSettings, 'currency'>, opts: {
|
|
|
1696
1881
|
* and `createdAt`, no new stamp and no second late-sale fact) and hands it to `onSaleCompleted`
|
|
1697
1882
|
* again, so that must accept an order it already stored (as `useOrderOutbox.record` does). The
|
|
1698
1883
|
* pending completion is cleared once `onSaleCompleted` resolves, or by `newSale()`. A call while
|
|
1699
|
-
* another is in flight returns that call's promise, and a call on the receipt does nothing.
|
|
1884
|
+
* another is in flight returns that call's promise, and a call on the receipt does nothing. While
|
|
1885
|
+
* `cashierRef` or `registerId` is out of bounds (shown as `error`), a call does nothing either,
|
|
1886
|
+
* unless it's the Retry of a pending completion, which was built with the options as they were.
|
|
1700
1887
|
*/
|
|
1701
1888
|
complete: () => Promise<void>;
|
|
1702
1889
|
/**
|
|
@@ -1734,16 +1921,22 @@ type TransportOutcome = {
|
|
|
1734
1921
|
kind: 'refused';
|
|
1735
1922
|
status: number;
|
|
1736
1923
|
reason: string;
|
|
1737
|
-
}
|
|
1924
|
+
}
|
|
1925
|
+
/** `reason` is `network` when the store could not be reached, `timeout` when a sent request got no answer in time,
|
|
1926
|
+
* else what the store answered (`status_503`, `bad_body`, ...). The order outbox counts every reason but `network`. */
|
|
1927
|
+
| {
|
|
1738
1928
|
kind: 'retry';
|
|
1739
1929
|
reason: string;
|
|
1740
1930
|
retryAfterMs?: number;
|
|
1741
1931
|
};
|
|
1742
|
-
interface CommandTransport {
|
|
1743
|
-
send(batch:
|
|
1932
|
+
interface CommandTransport<E extends AnyCommandEnvelope = CommandEnvelope<OrderCreatePayload>> {
|
|
1933
|
+
send(batch: E[]): Promise<TransportOutcome>;
|
|
1744
1934
|
}
|
|
1745
1935
|
interface OutboxState {
|
|
1746
1936
|
pending: number;
|
|
1937
|
+
/** The order outbox's count of `pos_orders` the store refused (`syncStatus: 'rejected'`), read with `pending`. Each
|
|
1938
|
+
* stays until requeue() sends it again. The register outbox leaves it unset. */
|
|
1939
|
+
rejected?: number;
|
|
1747
1940
|
sending: boolean;
|
|
1748
1941
|
lastRetryReason?: string;
|
|
1749
1942
|
nextAttemptAt?: number;
|
|
@@ -1754,6 +1947,26 @@ interface OutboxState {
|
|
|
1754
1947
|
status: number;
|
|
1755
1948
|
reason: string;
|
|
1756
1949
|
};
|
|
1950
|
+
/** Set while the store (not offline) has kept failing some orders for 15 minutes, each on its own clock. Those
|
|
1951
|
+
* orders stay pending and keep retrying. `orders` has each order's own entry: its `since` is when its clock
|
|
1952
|
+
* would have started had there been no offline gaps (now minus its answered time; while offline, as of the
|
|
1953
|
+
* moment the clock paused), and its latest `reason`. `since` is the earliest of theirs; `reason` the latest. */
|
|
1954
|
+
stuck?: {
|
|
1955
|
+
commandIds: string[];
|
|
1956
|
+
since: number;
|
|
1957
|
+
reason: string;
|
|
1958
|
+
orders: {
|
|
1959
|
+
commandId: string;
|
|
1960
|
+
since: number;
|
|
1961
|
+
reason: string;
|
|
1962
|
+
}[];
|
|
1963
|
+
};
|
|
1964
|
+
/** Set after 3 consecutive 404 answers (counted across the outboxes sharing one BackendNotFound): the store address
|
|
1965
|
+
* may be wrong, or the store's plugin isn't installed or is switched off. The outbox keeps retrying; the next answer
|
|
1966
|
+
* that isn't a 404 clears it (offline changes nothing). `since` is when the first of those 404s arrived. */
|
|
1967
|
+
backendMissing?: {
|
|
1968
|
+
since: number;
|
|
1969
|
+
};
|
|
1757
1970
|
}
|
|
1758
1971
|
|
|
1759
1972
|
interface HttpTransportOptions {
|
|
@@ -1762,17 +1975,33 @@ interface HttpTransportOptions {
|
|
|
1762
1975
|
fetch?: typeof fetch;
|
|
1763
1976
|
timeoutMs?: number;
|
|
1764
1977
|
}
|
|
1765
|
-
declare function createHttpCommandTransport(options: HttpTransportOptions): CommandTransport
|
|
1978
|
+
declare function createHttpCommandTransport(options: HttpTransportOptions): CommandTransport<AnyCommandEnvelope>;
|
|
1979
|
+
|
|
1980
|
+
/** Counts consecutive 404s across every outbox given it, so a missing plugin shows once, whichever outbox meets it. */
|
|
1981
|
+
interface BackendNotFound {
|
|
1982
|
+
/** A 404 counts; any other answer from the store clears; offline (`network`) changes nothing. */
|
|
1983
|
+
record(outcome: TransportOutcome, at: number): void;
|
|
1984
|
+
backendMissing$: Observable<OutboxState['backendMissing']>;
|
|
1985
|
+
}
|
|
1986
|
+
declare function createBackendNotFound(): BackendNotFound;
|
|
1766
1987
|
|
|
1767
1988
|
interface OrderOutboxOptions {
|
|
1768
1989
|
collection: RxCollection<PosOrder>;
|
|
1769
|
-
transport: CommandTransport
|
|
1990
|
+
transport: CommandTransport<OrderCreateEnvelope>;
|
|
1770
1991
|
deviceId: string;
|
|
1992
|
+
getMaxOrderCreateVersion?: () => number | undefined | Promise<number | undefined>;
|
|
1993
|
+
refreshCapabilities?: () => Promise<void>;
|
|
1771
1994
|
batchSize?: number;
|
|
1772
1995
|
initialBackoffMs?: number;
|
|
1773
1996
|
maxBackoffMs?: number;
|
|
1774
1997
|
random?: () => number;
|
|
1775
1998
|
now?: () => number;
|
|
1999
|
+
/** Tests only: overrides ISOLATE_AFTER_ATTEMPTS. */
|
|
2000
|
+
isolateAfterAttempts?: number;
|
|
2001
|
+
/** Tests only: overrides STUCK_AFTER_MS. */
|
|
2002
|
+
stuckAfterMs?: number;
|
|
2003
|
+
/** Counts 404s toward OutboxState.backendMissing; pass the register outbox's too, so either one's 404s show one notice. */
|
|
2004
|
+
backendNotFound?: BackendNotFound;
|
|
1776
2005
|
}
|
|
1777
2006
|
interface OrderOutbox {
|
|
1778
2007
|
/** Sends pending orders until empty or retrying. Concurrent calls share one run. */
|
|
@@ -1788,8 +2017,34 @@ interface OrderOutbox {
|
|
|
1788
2017
|
}
|
|
1789
2018
|
declare function createOrderOutbox(options: OrderOutboxOptions): OrderOutbox;
|
|
1790
2019
|
|
|
1791
|
-
|
|
2020
|
+
interface RegisterOutboxOptions {
|
|
2021
|
+
collection: RxCollection<RegisterCommand>;
|
|
2022
|
+
transport: CommandTransport<RegisterCommandEnvelope>;
|
|
2023
|
+
deviceId: string;
|
|
2024
|
+
/** Checked at the start of every run; false sends nothing. */
|
|
2025
|
+
isEnabled?: () => boolean;
|
|
2026
|
+
/** Runs before marking, so a crash resends; a throw is logged and marking continues. */
|
|
2027
|
+
onResult?: (command: RegisterCommand, result: CommandResult) => Promise<void> | void;
|
|
2028
|
+
batchSize?: number;
|
|
2029
|
+
initialBackoffMs?: number;
|
|
2030
|
+
maxBackoffMs?: number;
|
|
2031
|
+
random?: () => number;
|
|
2032
|
+
now?: () => number;
|
|
2033
|
+
/** Tests only: overrides STUCK_AFTER_MS. */
|
|
2034
|
+
stuckAfterMs?: number;
|
|
2035
|
+
/** Counts 404s toward OutboxState.backendMissing; pass the order outbox's too, so either one's 404s show one notice. */
|
|
2036
|
+
backendNotFound?: BackendNotFound;
|
|
2037
|
+
}
|
|
2038
|
+
interface RegisterOutbox {
|
|
2039
|
+
flush(): Promise<void>;
|
|
2040
|
+
start(): void;
|
|
2041
|
+
stop(): void;
|
|
2042
|
+
state$: Observable<OutboxState>;
|
|
2043
|
+
}
|
|
2044
|
+
declare function createRegisterOutbox(options: RegisterOutboxOptions): RegisterOutbox;
|
|
2045
|
+
|
|
1792
2046
|
declare const outboxLogger: Logger;
|
|
2047
|
+
|
|
1793
2048
|
interface UseOrderOutboxOptions {
|
|
1794
2049
|
/** Which order store to use (medusapos: the backend's base URL); `null` means no store. A change reopens. */
|
|
1795
2050
|
storeKey: string | null;
|
|
@@ -1799,9 +2054,13 @@ interface UseOrderOutboxOptions {
|
|
|
1799
2054
|
close(): Promise<void>;
|
|
1800
2055
|
}>;
|
|
1801
2056
|
/** Builds the command transport for `storeKey` (the app's HTTP transport and auth headers). Read once per open. */
|
|
1802
|
-
transport(storeKey: string): CommandTransport
|
|
2057
|
+
transport(storeKey: string): CommandTransport<OrderCreateEnvelope>;
|
|
1803
2058
|
/** The device id sent on every command (see `getDeviceId`). A change reopens. */
|
|
1804
2059
|
deviceId: string;
|
|
2060
|
+
/** Presence is fixed when the store opens; calls use the latest function. Reopen to add or remove. */
|
|
2061
|
+
getMaxOrderCreateVersion?: () => number | undefined | Promise<number | undefined>;
|
|
2062
|
+
/** Presence is fixed when the store opens; calls use the latest function. Reopen to add or remove. */
|
|
2063
|
+
refreshCapabilities?: () => Promise<void>;
|
|
1805
2064
|
/** Called with `state.sending`, and with `false` on cleanup (medusapos: live-tab's `markBusy('outbox', …)`). */
|
|
1806
2065
|
onBusy?(busy: boolean): void;
|
|
1807
2066
|
/** Called when `open` rejects, even if the key has changed since (medusapos: reports storage worker failures). */
|
|
@@ -1837,6 +2096,9 @@ interface UseOrderOutboxResult {
|
|
|
1837
2096
|
* storage can wait forever, and TallyUI deliberately doesn't bound that close (#155).
|
|
1838
2097
|
*/
|
|
1839
2098
|
savesInFlight: number;
|
|
2099
|
+
/** The command ids in `state.stuck`: pending orders the store keeps failing. Pass them to `needsAttention` and
|
|
2100
|
+
* `OrdersList`. Empty when none, and before the current store is ready. */
|
|
2101
|
+
stuckCommandIds: readonly string[];
|
|
1840
2102
|
}
|
|
1841
2103
|
/** Opens the order store for `storeKey`, runs its outbox and watches the recent orders (lifted from medusapos/app, ADR-052). */
|
|
1842
2104
|
declare function useOrderOutbox(options: UseOrderOutboxOptions): UseOrderOutboxResult;
|
|
@@ -1987,4 +2249,4 @@ declare function planLegs(plan: TenderPlan, rowsSinceFrom: {
|
|
|
1987
2249
|
};
|
|
1988
2250
|
};
|
|
1989
2251
|
|
|
1990
|
-
export { type Actor, type AddLineInput, type AppliedDiscount, CartError, type CashMovement, type CashMovementCollection, type CatalogueEntry, type Closure, type ClosureCollection, type ClosureContext, type ClosureScope, type CommandTransport, type Correction, CurrencyProvider, type CurrencyProviderProps, type CustomerSummary, DISCOUNTS_UNSUPPORTED, type Discount, type DisplayLine, type DisplayTotals, type FinalizeOptions, type HttpTransportOptions, type LedgerRow, type LineItem, type LineTaxLine, type LogEntry, type LogLevel, type LogSink, type Logger, MAX_TENDER_MINOR, MICROS_PER_MINOR, type Movement$1 as Movement, type MovementType, type Order, type OrderBuilder, type OrderBuilderOptions, OrderContentMismatchError, type OrderManager, type OrderManagerOptions, type OrderOutbox, type OrderOutboxOptions, type OrderTaxTotals, type OutboxState, type ParkedOrderSummary, type Payment, type PaymentMethod, type PaymentTransport, type PlanLeg, type PosOrder, type PosOrderLine, PosOrderOpenClosedError, type PosOrderPayment, type PosOrderSyncStatus, type RateTaxLine, type ReceiptConfig, type ReceiptData, type ReceiptLineItem, type RecordedFigures, RegisterApprovalRequiredError, type RegisterBucket, RegisterCloseIncompleteError, type RegisterCounters, type RegisterDocument, type RegisterFact, type RegisterHost, RegisterMovementStrandedError, type RegisterSession, RegisterSessionAlreadyOpenError, RegisterSessionClosedError, type RegisterSessionCollection, RegisterSessionRequiredError, type RegisterStore, RegisterTenderInProgressError, type Repository, type ResolveStoreSettingsOptions, SALE_SAVING, type SaleStage, type SplitTab, type StoreSettingsResolution, type StoreSettingsState, type TaxContext, type TaxLineInput, TaxProvider, type TaxProviderProps, type TaxRateMap, type TenderAction, type TenderKey, type TenderLineId, type TenderPlan, type TenderState, type TenderView, type TransportOutcome, type UseOrderOutboxOptions, type UseOrderOutboxResult, type UseRegisterSessionOptions, activePlan, addEntryToCart, addPosOrderCollection, advancePerpetual, allocateOrderDiscount, appliedMinor, backToSelling, bindRegister, buildClosureDocument, buildReceiptData, buildXReportDocument, callbackSink, cashMovementSchema, catalogueEntries, changeMinor, clampClosureScope, closeNeedsApproval, closeSession, closureSchema, computeOrderTax, consoleSink, countVariance, createHttpCommandTransport, createLogger, createOrderBuilder, createOrderManager, createOrderOutbox, createRepository, denominationTotal, denominations, deriveExpected, deriveSettled, ensureRegister, evenSplitShareMinor, exportCsv, finalizeOrder, findEntryByCode, formatClosureDate, getBoundRegisterId, getDeviceId, initTenderState, initialTenderState, isServerDecimal, labelKeys, minorToDecimal, mintClosureNumber, mintUuid, movementFieldError, needsAttention, nextSaleCounter, normalizeAmount, observeRegister$, openSession, openSessionSelector, outboxLogger, overThreshold, parseMinor, planLegs, posOrderCollection, posOrderSchema, posOrdersLogger, quickTenderedAmounts, ratePpmFromPercent, readBoundRegister, readRegister, recordMovement, recordRegisterFact, registerFactsLogger, registerSessionCollection, registerSessionSchema, requireOpenSession, resolveStoreSettings, roundMicrosToMinor, saleLogger, sameSale, searchProducts, selectClosureRows, stampSession, startCounting, stockOverlay$, stockOverlayAsOf$, taxLinesByRate, taxMicros, taxProviderProps, tenderReducer, toOrderCreateEnvelope, unbindRegister, useCurrencyCode, useCurrencyFormatter, useOrderOutbox, useRegisterSession, useSale, useStoreSettings, useTax, uuidv7, validAmount, varianceText, variantPriceLabel, voidMovement, withPricingContext, writeClosure };
|
|
2252
|
+
export { type Actor, type AddLineInput, type AppliedDiscount, type BackendNotFound, type BasketLine, CartError, type CashMovement, type CashMovementCollection, type CatalogueEntry, type Closure, type ClosureCollection, type ClosureContext, type ClosureScope, type CommandTransport, type Correction, CurrencyProvider, type CurrencyProviderProps, type CustomerSummary, DISCOUNTS_UNSUPPORTED, type Discount, type DisplayLine, type DisplayTotals, type FinalizeOptions, type HttpTransportOptions, type LedgerRow, type LineItem, type LineTaxLine, type LogEntry, type LogLevel, type LogSink, type Logger, MAX_TENDER_MINOR, MICROS_PER_MINOR, type Movement$1 as Movement, type MovementType, type Order, type OrderBuilder, type OrderBuilderOptions, OrderContentMismatchError, type OrderManager, type OrderManagerOptions, type OrderOutbox, type OrderOutboxOptions, type OrderTaxTotals, type OutboxState, type ParkedOrderSummary, type Payment, type PaymentMethod, type PaymentTransport, type PlanLeg, type PosOrder, type PosOrderLine, type PosOrderLocalWarning, PosOrderOpenClosedError, type PosOrderPayment, type PosOrderServerFailures, type PosOrderSyncStatus, type RateTaxLine, type ReceiptConfig, type ReceiptData, type ReceiptLineItem, type RecordedFigures, RegisterApprovalRequiredError, type RegisterBucket, RegisterCloseIncompleteError, type RegisterCommand, type RegisterCommandCollection, type RegisterCounters, type RegisterDocument, type RegisterFact, type RegisterHost, RegisterIdInvalidError, RegisterMovementAmountError, RegisterMovementReasonError, RegisterMovementStrandedError, type RegisterOutbox, type RegisterOutboxOptions, type RegisterSession, RegisterSessionAlreadyOpenError, RegisterSessionClosedError, type RegisterSessionCollection, RegisterSessionRequiredError, type RegisterStore, RegisterTenderInProgressError, type Repository, type ResolveStoreSettingsOptions, SALE_SAVING, type SaleStage, type SentOrder, type SplitTab, type StoreSettingsResolution, type StoreSettingsState, TAX_ROUNDING_MIXED_NOTE, type TaxContext, type TaxLineInput, TaxProvider, type TaxProviderProps, type TaxRateMap, type TenderAction, type TenderKey, type TenderLineId, type TenderPlan, type TenderState, type TenderView, type TransportOutcome, UnsupportedOrderVersionError, type UseOrderOutboxOptions, type UseOrderOutboxResult, type UseRegisterSessionOptions, activePlan, addEntryToCart, addPosOrderCollection, advancePerpetual, allocateOrderDiscount, appliedMinor, backToSelling, bindRegister, buildClosureDocument, buildReceiptData, buildXReportDocument, callbackSink, cashMovementSchema, catalogueEntries, changeMinor, clampClosureScope, closeNeedsApproval, closeSession, closureCommand, closureSchema, computeOrderTax, consoleSink, countVariance, createBackendNotFound, createHttpCommandTransport, createLogger, createOrderBuilder, createOrderManager, createOrderOutbox, createRegisterOutbox, createRepository, denominationTotal, denominations, deriveExpected, deriveSettled, ensureRegister, evenSplitShareMinor, exportCsv, finalizeOrder, findEntryByCode, formatClosureDate, getBoundRegisterId, getDeviceId, initTenderState, initialTenderState, isServerDecimal, labelKeys, minorToDecimal, mintClosureNumber, mintUuid, movementCommand, movementFieldError, needsAttention, nextSaleCounter, normalizeAmount, observeRegister$, openSession, openSessionSelector, outboxLogger, overThreshold, parseMinor, planLegs, posOrderCollection, posOrderSchema, posOrdersLogger, quickTenderedAmounts, ratePpmFromPercent, readBoundRegister, readRegister, reconcileRegisterCommands, recordMovement, recordRegisterFact, registerCommandCollection, registerCommandSchema, registerCommandsLogger, registerFactsLogger, registerSessionCollection, registerSessionSchema, requireOpenSession, resolveStoreSettings, roundMicrosToMinor, saleLogger, sameSale, searchProducts, selectClosureRows, sessionOpenCommand, sessionTransitionCommand, stampSession, startCounting, stockOverlay$, stockOverlayAsOf$, taxFiguresForBasket, taxLinesByRate, taxLogger, taxMicros, taxProviderProps, tenderReducer, toOrderCreateEnvelope, unbindRegister, useCurrencyCode, useCurrencyFormatter, useOrderOutbox, useRegisterSession, useSale, useStoreSettings, useTax, uuidv7, validAmount, varianceText, variantPriceLabel, voidMovement, withPricingContext, writeClosure };
|