@tallyui/pos 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +70 -1
- package/dist/index.js +73 -4
- package/package.json +6 -6
- package/src/index.ts +4 -4
- package/src/outbox/register-outbox.ts +9 -4
- package/src/pos-order/needs-attention.ts +2 -1
- package/src/receipt/build-receipt-data.ts +9 -0
- package/src/receipt/index.ts +2 -1
- package/src/receipt/types.ts +22 -0
- package/src/tender/index.ts +2 -0
- package/src/tender/tender-void.ts +66 -0
package/dist/index.d.ts
CHANGED
|
@@ -534,6 +534,28 @@ declare function useParkedSales(drafts: RxCollection | undefined): {
|
|
|
534
534
|
discard: (id: string) => Promise<void>;
|
|
535
535
|
};
|
|
536
536
|
|
|
537
|
+
/** WCPOS receipt data schema version these blocks follow (wiki: fiscal groundwork, receipt identity and QR §2). */
|
|
538
|
+
declare const RECEIPT_SCHEMA_VERSION = "1.4.0";
|
|
539
|
+
/** 1.4 `software`: which software produced the receipt. */
|
|
540
|
+
interface ReceiptSoftware {
|
|
541
|
+
name: string;
|
|
542
|
+
plugin_version: string;
|
|
543
|
+
app_version: string;
|
|
544
|
+
app_build: string;
|
|
545
|
+
platform: string;
|
|
546
|
+
}
|
|
547
|
+
/** 1.4 `register`: `{ id: '', name: '' }` when there is none. */
|
|
548
|
+
interface ReceiptRegister {
|
|
549
|
+
id: string;
|
|
550
|
+
name: string;
|
|
551
|
+
}
|
|
552
|
+
/** 1.4 `fiscal` identity fields a till can know. Sale time, zone and counter come with the per-register sale counter (#475). */
|
|
553
|
+
interface ReceiptFiscal {
|
|
554
|
+
document_type: 'sale' | 'refund' | 'void' | 'cancellation' | 'closure' | 'x_report';
|
|
555
|
+
is_reprint: boolean;
|
|
556
|
+
reprint_count: number;
|
|
557
|
+
qr_payload: string;
|
|
558
|
+
}
|
|
537
559
|
interface ReceiptLineItem {
|
|
538
560
|
name: string;
|
|
539
561
|
sku: string;
|
|
@@ -549,6 +571,10 @@ interface ReceiptLineItem {
|
|
|
549
571
|
}[];
|
|
550
572
|
}
|
|
551
573
|
interface ReceiptData {
|
|
574
|
+
schemaVersion: string;
|
|
575
|
+
software: ReceiptSoftware;
|
|
576
|
+
register: ReceiptRegister;
|
|
577
|
+
fiscal: ReceiptFiscal;
|
|
552
578
|
header: {
|
|
553
579
|
storeName: string;
|
|
554
580
|
storeAddress?: string;
|
|
@@ -602,6 +628,9 @@ interface ReceiptConfig {
|
|
|
602
628
|
storeAddress?: string;
|
|
603
629
|
cashier?: string;
|
|
604
630
|
register?: string;
|
|
631
|
+
registerName?: string;
|
|
632
|
+
software?: Partial<ReceiptSoftware>;
|
|
633
|
+
fiscal?: Partial<ReceiptFiscal>;
|
|
605
634
|
taxLabels?: Record<number, string>;
|
|
606
635
|
}
|
|
607
636
|
|
|
@@ -921,6 +950,7 @@ declare function getDeviceId(storage: {
|
|
|
921
950
|
* session closed (`lateSessionId`) or with local warnings (whatever the sync status), or pending with a `commandId` in
|
|
922
951
|
* `stuckCommandIds` (the outbox's `OutboxState.stuck`: the store keeps failing it), newest first.
|
|
923
952
|
* A warning code this till version doesn't know is not shown here (`knownWarnings`). Never changes `orders`.
|
|
953
|
+
* `register_session_unknown` is not shown, because the store counts the order once its session arrives (ADR-078).
|
|
924
954
|
*/
|
|
925
955
|
declare function needsAttention(orders: PosOrder[], options?: {
|
|
926
956
|
stuckCommandIds?: readonly string[];
|
|
@@ -2496,4 +2526,43 @@ declare function planLegs(plan: TenderPlan, rowsSinceFrom: {
|
|
|
2496
2526
|
};
|
|
2497
2527
|
};
|
|
2498
2528
|
|
|
2499
|
-
|
|
2529
|
+
/**
|
|
2530
|
+
* One record per voided tender leg: its v2 ledger row plus the provenance a v2 `void`
|
|
2531
|
+
* fiscal record carries (TallyUI#479). Write-once: insert only, never update or delete;
|
|
2532
|
+
* a replay of a known `paymentId` is a no-op for the caller to handle.
|
|
2533
|
+
* Local only, never replicated. No device-side number or checksum:
|
|
2534
|
+
* v2 numbers records on the server.
|
|
2535
|
+
*/
|
|
2536
|
+
|
|
2537
|
+
interface TenderVoid {
|
|
2538
|
+
/** The voided leg's client-minted id (the `clientPaymentId` order.create would send). Primary key, write-once. */
|
|
2539
|
+
paymentId: string;
|
|
2540
|
+
/** The sale's id (`PosOrder.saleId`). */
|
|
2541
|
+
saleId: string;
|
|
2542
|
+
type: 'void';
|
|
2543
|
+
method: PaymentMethodKind;
|
|
2544
|
+
amountMinor: number;
|
|
2545
|
+
/** Cash only: what the customer handed over. */
|
|
2546
|
+
tenderedMinor?: number;
|
|
2547
|
+
/** An external terminal's reference. */
|
|
2548
|
+
reference?: string;
|
|
2549
|
+
/** ISO 4217 code. */
|
|
2550
|
+
currency: string;
|
|
2551
|
+
/** `removed`: the cashier removed this one leg. `cancelled`: the cashier backed out of tendering. */
|
|
2552
|
+
reason: 'removed' | 'cancelled';
|
|
2553
|
+
/** Device clock at the void, ISO 8601. */
|
|
2554
|
+
deviceTime: string;
|
|
2555
|
+
/** The device's IANA time zone. */
|
|
2556
|
+
deviceTz: string;
|
|
2557
|
+
/** The bound register's id, `''` when no register is bound. */
|
|
2558
|
+
registerId: string;
|
|
2559
|
+
sessionId?: string;
|
|
2560
|
+
/** The cashier, as the backend's user id string. */
|
|
2561
|
+
cashierRef: string;
|
|
2562
|
+
}
|
|
2563
|
+
declare const tenderVoidSchema: RxJsonSchema<TenderVoid>;
|
|
2564
|
+
declare function tenderVoidCollection(): {
|
|
2565
|
+
readonly schema: RxJsonSchema<TenderVoid>;
|
|
2566
|
+
};
|
|
2567
|
+
|
|
2568
|
+
export { type Actor, type AddLineInput, type AppliedDiscount, type BackendNotFound, type BasketLine, CartError, type CashMovement, type CashMovementCollection, type CatalogueEntry, type CatalogueGridColumns, type CatalogueView, type CatalogueViewAction, type CatalogueViewState, type ChargeInput, type ChargeLine, type Closure, type ClosureCollection, type ClosureContext, type ClosureScope, type CommandTransport, type Correction, CurrencyProvider, type CurrencyProviderProps, type CustomerSummary, DEFAULT_CATALOGUE_VIEW_STATE, DISCOUNTS_UNSUPPORTED, type Discount, type DisplayLine, type DisplayTotals, type FeeLine, 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 OrderTransportContext, type OutboxState, PRODUCT_SORT_FIELDS, type ParkedOrderSummary, type Payment, type PaymentMethod, type PaymentTransport, type PlanLeg, type PosOrder, type PosOrderLine, type PosOrderLocalWarning, PosOrderOpenClosedError, type PosOrderPayment, type PosOrderServerFailures, type PosOrderSyncStatus, type ProductSort, type ProductSortValue, RECEIPT_SCHEMA_VERSION, type RateTaxLine, type ReceiptConfig, type ReceiptData, type ReceiptFiscal, type ReceiptLineItem, type ReceiptRegister, type ReceiptSoftware, 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 ShippingLine, 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 TenderVoid, type TransportOutcome, UnsupportedOrderVersionError, type UseOrderOutboxOptions, type UseOrderOutboxResult, type UseRegisterOutboxOptions, type UseRegisterOutboxResult, type UseRegisterSessionOptions, activePlan, addEntryToCart, addPosOrderCollection, advancePerpetual, allocateOrderDiscount, appliedMinor, backToSelling, bindRegister, buildClosureDocument, buildReceiptData, buildXReportDocument, callbackSink, cashMovementSchema, catalogueEntries, catalogueViewReducer, 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, inCategory, initTenderState, initialTenderState, isServerDecimal, labelKeys, listCategories, minorToDecimal, mintClosureNumber, mintUuid, movementCommand, movementFieldError, needsAttention, nextSaleCounter, normalizeAmount, normalizeCatalogueViewState, observeRegister$, openSession, openSessionSelector, orderDraftSchema, outboxLogger, overThreshold, parkedOrderSummaries$, parseMinor, planLegs, posOrderCollection, posOrderSchema, posOrdersLogger, productCategories, productSortValue, quickTenderedAmounts, ratePpmFromPercent, readBoundRegister, readRegister, reconcileRegisterCommands, recordMovement, recordRegisterFact, registerCommandCollection, registerCommandSchema, registerCommandsLogger, registerFactsLogger, registerSessionCollection, registerSessionSchema, requireOpenSession, resolveGridColumns, resolveStoreSettings, restoreOrderDraft, roundMicrosToMinor, saleLogger, sameSale, searchProducts, selectClosureRows, sessionOpenCommand, sessionTransitionCommand, sortProducts, stampSession, startCounting, stockOverlay$, stockOverlayAsOf$, taxFiguresForBasket, taxLinesByRate, taxLogger, taxMicros, taxProviderProps, tenderReducer, tenderVoidCollection, tenderVoidSchema, toOrderCreateEnvelope, unbindRegister, useCurrencyCode, useCurrencyFormatter, useOrderOutbox, useParkedSales, useRegisterOutbox, useRegisterSession, useSale, useStoreSettings, useTax, uuidv7, validAmount, varianceText, variantPriceLabel, voidMovement, withPricingContext, writeClosure, writeOrderDraft };
|
package/dist/index.js
CHANGED
|
@@ -1364,6 +1364,9 @@ function useParkedSales(drafts) {
|
|
|
1364
1364
|
return { parked, discard };
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
+
// src/receipt/types.ts
|
|
1368
|
+
var RECEIPT_SCHEMA_VERSION = "1.4.0";
|
|
1369
|
+
|
|
1367
1370
|
// src/receipt/build-receipt-data.ts
|
|
1368
1371
|
function buildReceiptData(order, config) {
|
|
1369
1372
|
const taxedLines = [...order.lineItems.map((line) => order.taxRounding?.granularity === "woocommerce" && line.netMicros !== void 0 ? { ...line, taxInclusive: order.pricesIncludeTax } : line), ...[...order.fees ?? [], ...order.shipping ?? []].map((charge) => ({
|
|
@@ -1387,6 +1390,23 @@ function buildReceiptData(order, config) {
|
|
|
1387
1390
|
const label = (d) => d.label ?? d.couponCode ?? `${d.type} discount`;
|
|
1388
1391
|
const customer = order.customer?.name || order.customer?.email;
|
|
1389
1392
|
return {
|
|
1393
|
+
schemaVersion: RECEIPT_SCHEMA_VERSION,
|
|
1394
|
+
software: {
|
|
1395
|
+
name: "WCPOS",
|
|
1396
|
+
plugin_version: "",
|
|
1397
|
+
app_version: "",
|
|
1398
|
+
app_build: "",
|
|
1399
|
+
platform: "",
|
|
1400
|
+
...config.software
|
|
1401
|
+
},
|
|
1402
|
+
register: { id: config.register ?? "", name: config.registerName ?? "" },
|
|
1403
|
+
fiscal: {
|
|
1404
|
+
document_type: "sale",
|
|
1405
|
+
is_reprint: false,
|
|
1406
|
+
reprint_count: 0,
|
|
1407
|
+
qr_payload: "",
|
|
1408
|
+
...config.fiscal
|
|
1409
|
+
},
|
|
1390
1410
|
header: {
|
|
1391
1411
|
storeName: config.storeName,
|
|
1392
1412
|
storeAddress: config.storeAddress,
|
|
@@ -2282,7 +2302,7 @@ function getDeviceId(storage, key) {
|
|
|
2282
2302
|
// src/pos-order/needs-attention.ts
|
|
2283
2303
|
import { knownWarnings } from "@tallyui/core";
|
|
2284
2304
|
function needsAttention(orders, options = {}) {
|
|
2285
|
-
return orders.filter((order) => order.syncStatus === "rejected" || (order.localWarnings?.length ?? 0) > 0 || order.syncStatus === "applied" && knownWarnings(order.warnings).
|
|
2305
|
+
return orders.filter((order) => order.syncStatus === "rejected" || (order.localWarnings?.length ?? 0) > 0 || order.syncStatus === "applied" && knownWarnings(order.warnings).some((warning) => warning.code !== "register_session_unknown") || order.lateSessionId !== void 0 || order.syncStatus === "pending" && options.stuckCommandIds?.includes(order.commandId) === true).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
2286
2306
|
}
|
|
2287
2307
|
|
|
2288
2308
|
// src/pos-order/same-sale.ts
|
|
@@ -5086,6 +5106,7 @@ function createOrderOutbox(options) {
|
|
|
5086
5106
|
|
|
5087
5107
|
// src/outbox/register-outbox.ts
|
|
5088
5108
|
import { BehaviorSubject as BehaviorSubject5 } from "rxjs";
|
|
5109
|
+
var NON_BLOCKING_REJECTIONS = /* @__PURE__ */ new Set(["register_session_superseded", "register_session_abandoned"]);
|
|
5089
5110
|
function plainCommand({
|
|
5090
5111
|
key,
|
|
5091
5112
|
registerId,
|
|
@@ -5165,8 +5186,9 @@ function createRegisterOutbox(options) {
|
|
|
5165
5186
|
selector: { syncStatus: { $in: ["pending", "rejected"] } },
|
|
5166
5187
|
sort: [{ seq: "asc" }, { key: "asc" }]
|
|
5167
5188
|
})) {
|
|
5168
|
-
if (command.syncStatus === "rejected")
|
|
5169
|
-
|
|
5189
|
+
if (command.syncStatus === "rejected") {
|
|
5190
|
+
if (!NON_BLOCKING_REJECTIONS.has(command.error?.code ?? "")) blocked.add(command.registerId);
|
|
5191
|
+
} else if (!blocked.has(command.registerId)) sendable2.add(command.commandId);
|
|
5170
5192
|
}
|
|
5171
5193
|
for (const id2 of clocks.keys()) if (!sendable2.has(id2)) clocks.delete(id2);
|
|
5172
5194
|
state$.next({ ...state$.value, ...patch, pending, stuck: stuckState() });
|
|
@@ -5202,9 +5224,10 @@ function createRegisterOutbox(options) {
|
|
|
5202
5224
|
});
|
|
5203
5225
|
const commands = [];
|
|
5204
5226
|
for (const command of ledger) {
|
|
5227
|
+
if (command.syncStatus === "rejected" && NON_BLOCKING_REJECTIONS.has(command.error?.code ?? "")) continue;
|
|
5205
5228
|
if (command.syncStatus === "rejected") break;
|
|
5206
5229
|
commands.push(command);
|
|
5207
|
-
if (commands.length >= batchSize) break;
|
|
5230
|
+
if (command.type === "register.session.open" || commands.length >= batchSize) break;
|
|
5208
5231
|
}
|
|
5209
5232
|
if (stopped) {
|
|
5210
5233
|
stoppedDuringRun = true;
|
|
@@ -5707,6 +5730,49 @@ function planLegs(plan, rowsSinceFrom, balanceMinor) {
|
|
|
5707
5730
|
const title = plan.kind === "fixed" && taken === 0 ? plan.title : null;
|
|
5708
5731
|
return { legs, thisPaymentMinor: each, label: { n: taken + 1, ways, rest, title } };
|
|
5709
5732
|
}
|
|
5733
|
+
|
|
5734
|
+
// src/tender/tender-void.ts
|
|
5735
|
+
var tenderVoidSchema = {
|
|
5736
|
+
title: "Tender voids",
|
|
5737
|
+
version: 0,
|
|
5738
|
+
primaryKey: "paymentId",
|
|
5739
|
+
type: "object",
|
|
5740
|
+
additionalProperties: false,
|
|
5741
|
+
properties: {
|
|
5742
|
+
paymentId: { type: "string", maxLength: 64 },
|
|
5743
|
+
saleId: { type: "string", maxLength: 64 },
|
|
5744
|
+
type: { type: "string", enum: ["void"], maxLength: 4 },
|
|
5745
|
+
// Not an enum: PaymentMethodKind grows, and a stored enum would need a schema migration per new kind.
|
|
5746
|
+
method: { type: "string", maxLength: 32 },
|
|
5747
|
+
amountMinor: { type: "integer" },
|
|
5748
|
+
tenderedMinor: { type: "integer" },
|
|
5749
|
+
reference: { type: "string" },
|
|
5750
|
+
currency: { type: "string", maxLength: 3 },
|
|
5751
|
+
reason: { type: "string", enum: ["removed", "cancelled"] },
|
|
5752
|
+
deviceTime: { type: "string", maxLength: 40 },
|
|
5753
|
+
deviceTz: { type: "string" },
|
|
5754
|
+
registerId: { type: "string" },
|
|
5755
|
+
sessionId: { type: "string" },
|
|
5756
|
+
cashierRef: { type: "string" }
|
|
5757
|
+
},
|
|
5758
|
+
required: [
|
|
5759
|
+
"paymentId",
|
|
5760
|
+
"saleId",
|
|
5761
|
+
"type",
|
|
5762
|
+
"method",
|
|
5763
|
+
"amountMinor",
|
|
5764
|
+
"currency",
|
|
5765
|
+
"reason",
|
|
5766
|
+
"deviceTime",
|
|
5767
|
+
"deviceTz",
|
|
5768
|
+
"registerId",
|
|
5769
|
+
"cashierRef"
|
|
5770
|
+
],
|
|
5771
|
+
indexes: [["saleId"], ["deviceTime"]]
|
|
5772
|
+
};
|
|
5773
|
+
function tenderVoidCollection() {
|
|
5774
|
+
return { schema: tenderVoidSchema };
|
|
5775
|
+
}
|
|
5710
5776
|
export {
|
|
5711
5777
|
CartError,
|
|
5712
5778
|
CurrencyProvider,
|
|
@@ -5717,6 +5783,7 @@ export {
|
|
|
5717
5783
|
OrderContentMismatchError,
|
|
5718
5784
|
PRODUCT_SORT_FIELDS,
|
|
5719
5785
|
PosOrderOpenClosedError,
|
|
5786
|
+
RECEIPT_SCHEMA_VERSION,
|
|
5720
5787
|
RegisterApprovalRequiredError,
|
|
5721
5788
|
RegisterCloseIncompleteError,
|
|
5722
5789
|
RegisterIdInvalidError,
|
|
@@ -5842,6 +5909,8 @@ export {
|
|
|
5842
5909
|
taxMicros,
|
|
5843
5910
|
taxProviderProps,
|
|
5844
5911
|
tenderReducer,
|
|
5912
|
+
tenderVoidCollection,
|
|
5913
|
+
tenderVoidSchema,
|
|
5845
5914
|
toOrderCreateEnvelope,
|
|
5846
5915
|
unbindRegister,
|
|
5847
5916
|
useCurrencyCode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/pos",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared POS business logic for Tally UI — tax, orders, payments, discounts, receipts",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
"directory": "packages/pos"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@tallyui/core": "^3.
|
|
30
|
+
"@tallyui/core": "^3.6.0",
|
|
31
31
|
"react": ">=18",
|
|
32
32
|
"rxdb": "~17.5.0",
|
|
33
33
|
"rxjs": ">=7"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@tallyui/core": "3.
|
|
36
|
+
"@tallyui/core": "3.6.0",
|
|
37
37
|
"@types/react": ">=18",
|
|
38
38
|
"react": "^19.2.4",
|
|
39
39
|
"rxdb": "17.5.0",
|
|
40
40
|
"rxjs": "7.8.2",
|
|
41
|
-
"@tallyui/connector-medusa": "3.
|
|
42
|
-
"@tallyui/connector-woocommerce": "3.
|
|
43
|
-
"@tallyui/database": "3.
|
|
41
|
+
"@tallyui/connector-medusa": "3.6.0",
|
|
42
|
+
"@tallyui/connector-woocommerce": "3.6.0",
|
|
43
|
+
"@tallyui/database": "3.6.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup",
|
package/src/index.ts
CHANGED
|
@@ -51,8 +51,8 @@ export type {
|
|
|
51
51
|
} from './order';
|
|
52
52
|
|
|
53
53
|
// Receipt
|
|
54
|
-
export { buildReceiptData } from './receipt';
|
|
55
|
-
export type { ReceiptData, ReceiptLineItem, ReceiptConfig } from './receipt';
|
|
54
|
+
export { buildReceiptData, RECEIPT_SCHEMA_VERSION } from './receipt';
|
|
55
|
+
export type { ReceiptData, ReceiptLineItem, ReceiptConfig, ReceiptSoftware, ReceiptRegister, ReceiptFiscal } from './receipt';
|
|
56
56
|
|
|
57
57
|
// Product
|
|
58
58
|
export { searchProducts, sortProducts, productSortValue, PRODUCT_SORT_FIELDS, type ProductSort, type ProductSortValue, catalogueViewReducer, normalizeCatalogueViewState, resolveGridColumns, DEFAULT_CATALOGUE_VIEW_STATE, type CatalogueView, type CatalogueGridColumns, type CatalogueViewState, type CatalogueViewAction, withStockOverlay, getProductStock, stockOverlay$, stockOverlayAsOf$, productCategories, listCategories, inCategory } from './product';
|
|
@@ -69,8 +69,8 @@ export type { RegisterOutboxOptions, RegisterOutbox, UseRegisterOutboxOptions, U
|
|
|
69
69
|
export { createBackendNotFound } from './outbox/backend-not-found';
|
|
70
70
|
export type { BackendNotFound } from './outbox/backend-not-found';
|
|
71
71
|
export type { HttpTransportOptions, OrderOutboxOptions, OrderOutbox, TransportOutcome, CommandTransport, OrderTransportContext, OutboxState, UseOrderOutboxOptions, UseOrderOutboxResult } from './outbox';
|
|
72
|
-
export { tenderReducer, initTenderState, initialTenderState, appliedMinor, changeMinor, quickTenderedAmounts, evenSplitShareMinor, activePlan, planLegs, MAX_TENDER_MINOR } from './tender';
|
|
73
|
-
export type { PaymentTransport, TenderView, TenderLineId, TenderPlan, SplitTab, TenderState, TenderKey, TenderAction, PlanLeg } from './tender';
|
|
72
|
+
export { tenderReducer, initTenderState, initialTenderState, appliedMinor, changeMinor, quickTenderedAmounts, evenSplitShareMinor, activePlan, planLegs, MAX_TENDER_MINOR, tenderVoidSchema, tenderVoidCollection } from './tender';
|
|
73
|
+
export type { PaymentTransport, TenderView, TenderLineId, TenderPlan, SplitTab, TenderState, TenderKey, TenderAction, PlanLeg, TenderVoid } from './tender';
|
|
74
74
|
|
|
75
75
|
// Register
|
|
76
76
|
export { normalizeAmount, isServerDecimal, movementFieldError, deriveExpected, parseMinor, validAmount, countVariance, overThreshold, closeNeedsApproval, denominationTotal, varianceText, denominations } from './register';
|
|
@@ -7,6 +7,9 @@ import { createBackendNotFound, type BackendNotFound } from './backend-not-found
|
|
|
7
7
|
import { STUCK_AFTER_MS } from './order-outbox';
|
|
8
8
|
import type { CommandTransport, OutboxState } from './types';
|
|
9
9
|
|
|
10
|
+
/** Final rejections that do not stop their register's ledger (ADR-078): the commands behind them are still sent. */
|
|
11
|
+
export const NON_BLOCKING_REJECTIONS: ReadonlySet<string> = new Set(['register_session_superseded', 'register_session_abandoned']);
|
|
12
|
+
|
|
10
13
|
export interface RegisterOutboxOptions {
|
|
11
14
|
collection: RxCollection<RegisterCommand>;
|
|
12
15
|
transport: CommandTransport<RegisterCommandEnvelope>;
|
|
@@ -68,7 +71,7 @@ export function createRegisterOutbox(options: RegisterOutboxOptions): RegisterOu
|
|
|
68
71
|
// like a 503, or `no_progress`); a running clock keeps its `since` and takes the latest reason. A `network` failure
|
|
69
72
|
// pauses every clock (`pausedAt`), and the next answer of any kind resumes them, moving `since` on by the offline
|
|
70
73
|
// gap, so `since` is now minus the answered time. A clock clears when its command is no longer pending under its
|
|
71
|
-
// commandId (applied or rejected), or when a rejected command comes ahead of it in its ledger (it is not being
|
|
74
|
+
// commandId (applied or rejected), or when a blocking rejected command comes ahead of it in its ledger (it is not being
|
|
72
75
|
// sent: Front desk, 2026-09-30); commands never sent have none. Nothing is isolated: a stuck command at the head
|
|
73
76
|
// of a register's ledger holds up the commands behind it, by design, since register facts apply in `seq` order.
|
|
74
77
|
// In memory only: a restart starts with no clocks, and the first answered failure after it starts them afresh
|
|
@@ -96,8 +99,9 @@ export function createRegisterOutbox(options: RegisterOutboxOptions): RegisterOu
|
|
|
96
99
|
const blocked = new Set<string>();
|
|
97
100
|
if (clocks.size) for (const command of await readFresh(collection, { selector: { syncStatus: { $in: ['pending', 'rejected'] } },
|
|
98
101
|
sort: [{ seq: 'asc' }, { key: 'asc' }] })) {
|
|
99
|
-
if (command.syncStatus === 'rejected')
|
|
100
|
-
|
|
102
|
+
if (command.syncStatus === 'rejected') {
|
|
103
|
+
if (!NON_BLOCKING_REJECTIONS.has(command.error?.code ?? '')) blocked.add(command.registerId);
|
|
104
|
+
} else if (!blocked.has(command.registerId)) sendable.add(command.commandId);
|
|
101
105
|
}
|
|
102
106
|
for (const id of clocks.keys()) if (!sendable.has(id)) clocks.delete(id);
|
|
103
107
|
state$.next({ ...state$.value, ...patch, pending, stuck: stuckState() });
|
|
@@ -127,9 +131,10 @@ export function createRegisterOutbox(options: RegisterOutboxOptions): RegisterOu
|
|
|
127
131
|
});
|
|
128
132
|
const commands: RegisterCommand[] = [];
|
|
129
133
|
for (const command of ledger) {
|
|
134
|
+
if (command.syncStatus === 'rejected' && NON_BLOCKING_REJECTIONS.has(command.error?.code ?? '')) continue;
|
|
130
135
|
if (command.syncStatus === 'rejected') break;
|
|
131
136
|
commands.push(command);
|
|
132
|
-
if (commands.length >= batchSize) break;
|
|
137
|
+
if (command.type === 'register.session.open' || commands.length >= batchSize) break;
|
|
133
138
|
}
|
|
134
139
|
if (stopped) { stoppedDuringRun = true; return; }
|
|
135
140
|
if (!commands.length) continue;
|
|
@@ -6,11 +6,12 @@ import type { PosOrder } from './types';
|
|
|
6
6
|
* session closed (`lateSessionId`) or with local warnings (whatever the sync status), or pending with a `commandId` in
|
|
7
7
|
* `stuckCommandIds` (the outbox's `OutboxState.stuck`: the store keeps failing it), newest first.
|
|
8
8
|
* A warning code this till version doesn't know is not shown here (`knownWarnings`). Never changes `orders`.
|
|
9
|
+
* `register_session_unknown` is not shown, because the store counts the order once its session arrives (ADR-078).
|
|
9
10
|
*/
|
|
10
11
|
export function needsAttention(orders: PosOrder[], options: { stuckCommandIds?: readonly string[] } = {}): PosOrder[] {
|
|
11
12
|
return orders.filter((order) => order.syncStatus === 'rejected'
|
|
12
13
|
|| (order.localWarnings?.length ?? 0) > 0
|
|
13
|
-
|| (order.syncStatus === 'applied' && knownWarnings(order.warnings).
|
|
14
|
+
|| (order.syncStatus === 'applied' && knownWarnings(order.warnings).some((warning) => warning.code !== 'register_session_unknown')) || order.lateSessionId !== undefined
|
|
14
15
|
|| (order.syncStatus === 'pending' && options.stuckCommandIds?.includes(order.commandId) === true))
|
|
15
16
|
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
16
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Discount, SentOrder } from '../order/types';
|
|
2
2
|
import { roundMicrosToMinor, taxLinesByRate } from '../tax/exact';
|
|
3
3
|
import type { ReceiptConfig, ReceiptData } from './types';
|
|
4
|
+
import { RECEIPT_SCHEMA_VERSION } from './types';
|
|
4
5
|
|
|
5
6
|
export function buildReceiptData(order: SentOrder, config: ReceiptConfig): ReceiptData {
|
|
6
7
|
// Drops the shared helper's `netMinor` (the Z report's own use): the receipt's tax summary
|
|
@@ -29,6 +30,14 @@ export function buildReceiptData(order: SentOrder, config: ReceiptConfig): Recei
|
|
|
29
30
|
const customer = order.customer?.name || order.customer?.email;
|
|
30
31
|
|
|
31
32
|
return {
|
|
33
|
+
schemaVersion: RECEIPT_SCHEMA_VERSION,
|
|
34
|
+
software: {
|
|
35
|
+
name: 'WCPOS', plugin_version: '', app_version: '', app_build: '', platform: '', ...config.software,
|
|
36
|
+
},
|
|
37
|
+
register: { id: config.register ?? '', name: config.registerName ?? '' },
|
|
38
|
+
fiscal: {
|
|
39
|
+
document_type: 'sale', is_reprint: false, reprint_count: 0, qr_payload: '', ...config.fiscal,
|
|
40
|
+
},
|
|
32
41
|
header: {
|
|
33
42
|
storeName: config.storeName,
|
|
34
43
|
storeAddress: config.storeAddress,
|
package/src/receipt/index.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { buildReceiptData } from './build-receipt-data';
|
|
2
|
-
export
|
|
2
|
+
export { RECEIPT_SCHEMA_VERSION } from './types';
|
|
3
|
+
export type { ReceiptData, ReceiptLineItem, ReceiptConfig, ReceiptSoftware, ReceiptRegister, ReceiptFiscal } from './types';
|
package/src/receipt/types.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/** WCPOS receipt data schema version these blocks follow (wiki: fiscal groundwork, receipt identity and QR §2). */
|
|
2
|
+
export const RECEIPT_SCHEMA_VERSION = '1.4.0';
|
|
3
|
+
|
|
4
|
+
/** 1.4 `software`: which software produced the receipt. */
|
|
5
|
+
export interface ReceiptSoftware { name: string; plugin_version: string; app_version: string; app_build: string; platform: string }
|
|
6
|
+
/** 1.4 `register`: `{ id: '', name: '' }` when there is none. */
|
|
7
|
+
export interface ReceiptRegister { id: string; name: string }
|
|
8
|
+
/** 1.4 `fiscal` identity fields a till can know. Sale time, zone and counter come with the per-register sale counter (#475). */
|
|
9
|
+
export interface ReceiptFiscal {
|
|
10
|
+
document_type: 'sale' | 'refund' | 'void' | 'cancellation' | 'closure' | 'x_report';
|
|
11
|
+
is_reprint: boolean;
|
|
12
|
+
reprint_count: number;
|
|
13
|
+
qr_payload: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
export interface ReceiptLineItem {
|
|
2
17
|
name: string;
|
|
3
18
|
sku: string;
|
|
@@ -11,6 +26,10 @@ export interface ReceiptLineItem {
|
|
|
11
26
|
}
|
|
12
27
|
|
|
13
28
|
export interface ReceiptData {
|
|
29
|
+
schemaVersion: string;
|
|
30
|
+
software: ReceiptSoftware;
|
|
31
|
+
register: ReceiptRegister;
|
|
32
|
+
fiscal: ReceiptFiscal;
|
|
14
33
|
header: {
|
|
15
34
|
storeName: string;
|
|
16
35
|
storeAddress?: string;
|
|
@@ -47,5 +66,8 @@ export interface ReceiptConfig {
|
|
|
47
66
|
storeAddress?: string;
|
|
48
67
|
cashier?: string;
|
|
49
68
|
register?: string;
|
|
69
|
+
registerName?: string;
|
|
70
|
+
software?: Partial<ReceiptSoftware>;
|
|
71
|
+
fiscal?: Partial<ReceiptFiscal>;
|
|
50
72
|
taxLabels?: Record<number, string>;
|
|
51
73
|
}
|
package/src/tender/index.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { tenderReducer, initTenderState, initialTenderState, appliedMinor, changeMinor, quickTenderedAmounts, evenSplitShareMinor, activePlan, planLegs, MAX_TENDER_MINOR } from './tender-state';
|
|
2
2
|
export type { PaymentTransport, TenderView, TenderLineId, TenderPlan, SplitTab, TenderState, TenderKey, TenderAction, PlanLeg } from './tender-state';
|
|
3
|
+
export { tenderVoidSchema, tenderVoidCollection } from './tender-void';
|
|
4
|
+
export type { TenderVoid } from './tender-void';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One record per voided tender leg: its v2 ledger row plus the provenance a v2 `void`
|
|
3
|
+
* fiscal record carries (TallyUI#479). Write-once: insert only, never update or delete;
|
|
4
|
+
* a replay of a known `paymentId` is a no-op for the caller to handle.
|
|
5
|
+
* Local only, never replicated. No device-side number or checksum:
|
|
6
|
+
* v2 numbers records on the server.
|
|
7
|
+
*/
|
|
8
|
+
import type { PaymentMethodKind } from '@tallyui/core';
|
|
9
|
+
import type { RxJsonSchema } from 'rxdb';
|
|
10
|
+
|
|
11
|
+
export interface TenderVoid {
|
|
12
|
+
/** The voided leg's client-minted id (the `clientPaymentId` order.create would send). Primary key, write-once. */
|
|
13
|
+
paymentId: string;
|
|
14
|
+
/** The sale's id (`PosOrder.saleId`). */
|
|
15
|
+
saleId: string;
|
|
16
|
+
type: 'void';
|
|
17
|
+
method: PaymentMethodKind;
|
|
18
|
+
amountMinor: number;
|
|
19
|
+
/** Cash only: what the customer handed over. */
|
|
20
|
+
tenderedMinor?: number;
|
|
21
|
+
/** An external terminal's reference. */
|
|
22
|
+
reference?: string;
|
|
23
|
+
/** ISO 4217 code. */
|
|
24
|
+
currency: string;
|
|
25
|
+
/** `removed`: the cashier removed this one leg. `cancelled`: the cashier backed out of tendering. */
|
|
26
|
+
reason: 'removed' | 'cancelled';
|
|
27
|
+
/** Device clock at the void, ISO 8601. */
|
|
28
|
+
deviceTime: string;
|
|
29
|
+
/** The device's IANA time zone. */
|
|
30
|
+
deviceTz: string;
|
|
31
|
+
/** The bound register's id, `''` when no register is bound. */
|
|
32
|
+
registerId: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
/** The cashier, as the backend's user id string. */
|
|
35
|
+
cashierRef: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tenderVoidSchema: RxJsonSchema<TenderVoid> = {
|
|
39
|
+
title: 'Tender voids', version: 0, primaryKey: 'paymentId', type: 'object', additionalProperties: false,
|
|
40
|
+
properties: {
|
|
41
|
+
paymentId: { type: 'string', maxLength: 64 },
|
|
42
|
+
saleId: { type: 'string', maxLength: 64 },
|
|
43
|
+
type: { type: 'string', enum: ['void'], maxLength: 4 },
|
|
44
|
+
// Not an enum: PaymentMethodKind grows, and a stored enum would need a schema migration per new kind.
|
|
45
|
+
method: { type: 'string', maxLength: 32 },
|
|
46
|
+
amountMinor: { type: 'integer' },
|
|
47
|
+
tenderedMinor: { type: 'integer' },
|
|
48
|
+
reference: { type: 'string' },
|
|
49
|
+
currency: { type: 'string', maxLength: 3 },
|
|
50
|
+
reason: { type: 'string', enum: ['removed', 'cancelled'] },
|
|
51
|
+
deviceTime: { type: 'string', maxLength: 40 },
|
|
52
|
+
deviceTz: { type: 'string' },
|
|
53
|
+
registerId: { type: 'string' },
|
|
54
|
+
sessionId: { type: 'string' },
|
|
55
|
+
cashierRef: { type: 'string' },
|
|
56
|
+
},
|
|
57
|
+
required: [
|
|
58
|
+
'paymentId', 'saleId', 'type', 'method', 'amountMinor', 'currency', 'reason',
|
|
59
|
+
'deviceTime', 'deviceTz', 'registerId', 'cashierRef',
|
|
60
|
+
],
|
|
61
|
+
indexes: [['saleId'], ['deviceTime']],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function tenderVoidCollection(): { readonly schema: RxJsonSchema<TenderVoid> } {
|
|
65
|
+
return { schema: tenderVoidSchema } as const;
|
|
66
|
+
}
|