@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.
Files changed (44) hide show
  1. package/dist/index.d.ts +323 -61
  2. package/dist/index.js +1423 -186
  3. package/package.json +8 -8
  4. package/src/index.ts +15 -7
  5. package/src/order/index.ts +3 -0
  6. package/src/order/order-builder.ts +23 -7
  7. package/src/order/order-manager.ts +3 -1
  8. package/src/order/tax-figures.ts +23 -0
  9. package/src/order/types.ts +9 -3
  10. package/src/outbox/backend-not-found.ts +27 -0
  11. package/src/outbox/http-transport.ts +5 -4
  12. package/src/outbox/index.ts +2 -0
  13. package/src/outbox/logger.ts +3 -0
  14. package/src/outbox/order-outbox.ts +336 -27
  15. package/src/outbox/register-outbox.ts +250 -0
  16. package/src/outbox/types.test-d.ts +22 -0
  17. package/src/outbox/types.ts +17 -3
  18. package/src/outbox/use-order-outbox.ts +20 -6
  19. package/src/pos-order/__fixtures__/order-create-v3.json +97 -0
  20. package/src/pos-order/command.ts +99 -11
  21. package/src/pos-order/finalize.ts +145 -7
  22. package/src/pos-order/index.ts +2 -2
  23. package/src/pos-order/needs-attention.ts +9 -4
  24. package/src/pos-order/open.ts +100 -37
  25. package/src/pos-order/schema.ts +35 -5
  26. package/src/pos-order/types.ts +31 -5
  27. package/src/product/search-products.ts +5 -2
  28. package/src/receipt/build-receipt-data.ts +5 -3
  29. package/src/receipt/types.ts +1 -0
  30. package/src/register/closure-document.ts +7 -0
  31. package/src/register/index.ts +5 -3
  32. package/src/register/movement-input.ts +1 -1
  33. package/src/register/register-commands.ts +147 -0
  34. package/src/register/register-document.ts +26 -1
  35. package/src/register/session-store.ts +41 -12
  36. package/src/register/use-register-session.ts +46 -2
  37. package/src/sale/cart.ts +1 -0
  38. package/src/sale/use-sale.ts +97 -40
  39. package/src/store-settings/map-store-settings.ts +7 -2
  40. package/src/store-settings/use-store-settings.ts +55 -9
  41. package/src/tax/exact.ts +74 -11
  42. package/src/tax/index.ts +1 -1
  43. package/src/tax/tax-provider.tsx +26 -4
  44. 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, CommandEnvelope, OrderCreatePayload, VariantSummary, CommandResult } from '@tallyui/core';
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
- /** Rounds micro-minor-units to integer minor units, half away from zero. */
70
- declare function roundMicrosToMinor(micros: bigint): number;
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
- /** `<TaxProvider>`'s props from the same settings, so its tax inclusivity matches the connector's. */
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: Order, config: ReceiptConfig): ReceiptData;
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
- * Local only: `toOrderCreateEnvelope` never sends it.
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. Local only: `toOrderCreateEnvelope` never sends 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
- /** ADR-065: the receipt's display figures, in integer minor units of `currency` at `exponent`. Not written yet. */
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). Not written yet. */
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
- /** Turns a fully paid builder Order into a pending PosOrder without mutating it. */
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
- * A discounted order is version 2 (ADR-062); a discount-free one stays version 1, byte-identical.
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): CommandEnvelope<OrderCreatePayload>;
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, or the close stopped waiting for it (after
608
- * `POS_ORDER_MIGRATION_CLOSE_WAIT_MS`). No order is lost: reopen the database and call it again.
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 16.21 trusts the status its last migration stored: a leftover `ERROR` rejects
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`, the status and a failed run's checkpoint are reset
625
- * (never an order or its storage), and the migration itself is awaited.
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 whole open, up to `POS_ORDER_MIGRATION_CLOSE_WAIT_MS`. Called once the
628
- * database's close has begun, or when the close stops waiting, it rejects with
629
- * `PosOrderOpenClosedError` (`code: 'POS_ORDER_OPEN_CLOSED'`) before any further write: reopen and
630
- * call it again. Any other rejection (DM4, a storage error) is a real failure.
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 warnings, or taken after their session
648
- * closed (`lateSessionId`, whatever the sync status), newest first. Never changes `orders`.
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[]): 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 was kept rather than deleted. The caller must not record it again; job c's
950
- * server resolves stranded movements. The message can be shown to a cashier as it is.
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.RxDocument<RegisterSession, {}>>;
1578
- backToSelling: () => Promise<rxdb.RxDocument<RegisterSession, {}>>;
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: 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: CommandEnvelope<OrderCreatePayload>[]): Promise<TransportOutcome>;
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
- /** Logs a retried order found stored under another `commandId` (warn), and a content mismatch (error). */
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 };