sinfactura-types 1.10.615 → 1.10.617

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/basket.d.ts CHANGED
@@ -62,4 +62,18 @@ declare global {
62
62
  mergedCount: number;
63
63
  }
64
64
  }
65
+ /**
66
+ * Domain refusals from POST /baskets: legacy replacement, named cart actions,
67
+ * and park/resume. Check GATING_ERROR_CODES separately for middleware and role
68
+ * refusals. GET /baskets and storefront-only refusals are outside this tuple.
69
+ *
70
+ * Coupon and promotion reasons are forwarded from evaluateCoupon and
71
+ * evaluatePromotion (stacks/services/coupons.ts and promotions.ts). Checkout's
72
+ * COUPON_EXHAUSTED_AT_CHECKOUT never reaches this write surface. Pricing
73
+ * forwards FX_CONVERSION_UNAVAILABLE from displayCurrencyRateRefusal through
74
+ * priceHandler.resolveLineStamp. The legacy loose body also reaches the shared
75
+ * foreign-index-key guard; its owned terminalId is validated before that guard.
76
+ */
77
+ export declare const BASKET_ERROR_CODES: readonly ["VALIDATION_FAILED", "FOREIGN_INDEX_KEY_ATTRIBUTE", "CART_TARGET_REQUIRED", "CART_NOT_FOUND", "CART_NOT_PARKED", "CART_ALREADY_PARKED", "TERMINAL_INDEX_UNAVAILABLE", "PRODUCT_NOT_FOUND", "PRODUCT_COST_UNKNOWN", "UNIT_QUANTITY_NOT_WHOLE", "BASKET_VERSION_CONFLICT", "BASKET_LINE_LIMIT", "BASKET_SIZE_LIMIT", "UNKNOWN_LINE", "INVALID_PRICE", "LINE_DISCOUNT_NOT_ALLOWED", "LINE_DISCOUNT_EXCEEDS_MAX", "FX_CONVERSION_UNAVAILABLE", "COUPON_NOT_FOUND", "COUPON_NOT_ACTIVE", "COUPON_EXPIRED", "COUPON_EXHAUSTED", "COUPON_MIN_SUBTOTAL", "COUPON_REQUIRES_CUSTOMER", "COUPON_BUDGET_EXHAUSTED", "COUPON_RATE_LIMITED", "COUPON_CURRENCY_MISMATCH", "PROMOTION_NOT_FOUND", "PROMOTION_NOT_ACTIVE", "PROMOTION_NOT_STARTED", "PROMOTION_EXPIRED", "PROMOTION_NOT_CART_APPLICABLE"];
78
+ export type BasketErrorCode = (typeof BASKET_ERROR_CODES)[number];
65
79
  export {};
package/dist/basket.js CHANGED
@@ -1 +1,46 @@
1
- export {};
1
+ /**
2
+ * Domain refusals from POST /baskets: legacy replacement, named cart actions,
3
+ * and park/resume. Check GATING_ERROR_CODES separately for middleware and role
4
+ * refusals. GET /baskets and storefront-only refusals are outside this tuple.
5
+ *
6
+ * Coupon and promotion reasons are forwarded from evaluateCoupon and
7
+ * evaluatePromotion (stacks/services/coupons.ts and promotions.ts). Checkout's
8
+ * COUPON_EXHAUSTED_AT_CHECKOUT never reaches this write surface. Pricing
9
+ * forwards FX_CONVERSION_UNAVAILABLE from displayCurrencyRateRefusal through
10
+ * priceHandler.resolveLineStamp. The legacy loose body also reaches the shared
11
+ * foreign-index-key guard; its owned terminalId is validated before that guard.
12
+ */
13
+ export const BASKET_ERROR_CODES = [
14
+ 'VALIDATION_FAILED',
15
+ 'FOREIGN_INDEX_KEY_ATTRIBUTE',
16
+ 'CART_TARGET_REQUIRED',
17
+ 'CART_NOT_FOUND',
18
+ 'CART_NOT_PARKED',
19
+ 'CART_ALREADY_PARKED',
20
+ 'TERMINAL_INDEX_UNAVAILABLE',
21
+ 'PRODUCT_NOT_FOUND',
22
+ 'PRODUCT_COST_UNKNOWN',
23
+ 'UNIT_QUANTITY_NOT_WHOLE',
24
+ 'BASKET_VERSION_CONFLICT',
25
+ 'BASKET_LINE_LIMIT',
26
+ 'BASKET_SIZE_LIMIT',
27
+ 'UNKNOWN_LINE',
28
+ 'INVALID_PRICE',
29
+ 'LINE_DISCOUNT_NOT_ALLOWED',
30
+ 'LINE_DISCOUNT_EXCEEDS_MAX',
31
+ 'FX_CONVERSION_UNAVAILABLE',
32
+ 'COUPON_NOT_FOUND',
33
+ 'COUPON_NOT_ACTIVE',
34
+ 'COUPON_EXPIRED',
35
+ 'COUPON_EXHAUSTED',
36
+ 'COUPON_MIN_SUBTOTAL',
37
+ 'COUPON_REQUIRES_CUSTOMER',
38
+ 'COUPON_BUDGET_EXHAUSTED',
39
+ 'COUPON_RATE_LIMITED',
40
+ 'COUPON_CURRENCY_MISMATCH',
41
+ 'PROMOTION_NOT_FOUND',
42
+ 'PROMOTION_NOT_ACTIVE',
43
+ 'PROMOTION_NOT_STARTED',
44
+ 'PROMOTION_EXPIRED',
45
+ 'PROMOTION_NOT_CART_APPLICABLE',
46
+ ];
package/dist/cart.d.ts CHANGED
@@ -750,10 +750,11 @@ declare global {
750
750
  * Constraints the type system cannot express, enforced in zod at the handler
751
751
  * and stated here because a `.d.ts` reader has no other way to learn them:
752
752
  * - `productId` matches `/^PROD\d{6,10}$/`; `lineId` matches `/^L\d{4,}$/`.
753
- * - `quantity` is `min(0).max(1_000_000)` and is deliberately NOT an integer:
754
- * weight-priced products carry fractional quantities. The maximum is overflow
755
- * hygiene — an absurd quantity takes the running total to `Infinity` and dies
756
- * at the DynamoDB marshaller instead of at validation.
753
+ * - `quantity` is non-negative, at most `MAX_BASE_QUANTITY`, and exactly
754
+ * representable with at most `BASE_QUANTITY_DECIMAL_PLACES` decimal places
755
+ * (six). Weight-priced products may carry fractional base quantities;
756
+ * products declared as `each` require whole quantities. Nothing is rounded:
757
+ * `0.1 + 0.2` is refused as `VALIDATION_FAILED`; send the exact value `0.3`.
757
758
  * - `merge.items` is `min(1).max(50)`.
758
759
  */
759
760
  type CartActionRequest = CartActionAddLine | CartActionChangeQuantity | CartActionRemoveLine | CartActionClear | CartActionMerge | CartActionSaveLine | CartActionRestoreLine | CartActionRemoveSavedLine | CartActionApplyCoupon | CartActionRemoveCoupon | CartActionApplyPromotion | CartActionRemovePromotion | CartActionSetLineDiscount | CartActionResume;
@@ -1,2 +1,49 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASKET_ERROR_CODES = void 0;
4
+ /**
5
+ * Domain refusals from POST /baskets: legacy replacement, named cart actions,
6
+ * and park/resume. Check GATING_ERROR_CODES separately for middleware and role
7
+ * refusals. GET /baskets and storefront-only refusals are outside this tuple.
8
+ *
9
+ * Coupon and promotion reasons are forwarded from evaluateCoupon and
10
+ * evaluatePromotion (stacks/services/coupons.ts and promotions.ts). Checkout's
11
+ * COUPON_EXHAUSTED_AT_CHECKOUT never reaches this write surface. Pricing
12
+ * forwards FX_CONVERSION_UNAVAILABLE from displayCurrencyRateRefusal through
13
+ * priceHandler.resolveLineStamp. The legacy loose body also reaches the shared
14
+ * foreign-index-key guard; its owned terminalId is validated before that guard.
15
+ */
16
+ exports.BASKET_ERROR_CODES = [
17
+ 'VALIDATION_FAILED',
18
+ 'FOREIGN_INDEX_KEY_ATTRIBUTE',
19
+ 'CART_TARGET_REQUIRED',
20
+ 'CART_NOT_FOUND',
21
+ 'CART_NOT_PARKED',
22
+ 'CART_ALREADY_PARKED',
23
+ 'TERMINAL_INDEX_UNAVAILABLE',
24
+ 'PRODUCT_NOT_FOUND',
25
+ 'PRODUCT_COST_UNKNOWN',
26
+ 'UNIT_QUANTITY_NOT_WHOLE',
27
+ 'BASKET_VERSION_CONFLICT',
28
+ 'BASKET_LINE_LIMIT',
29
+ 'BASKET_SIZE_LIMIT',
30
+ 'UNKNOWN_LINE',
31
+ 'INVALID_PRICE',
32
+ 'LINE_DISCOUNT_NOT_ALLOWED',
33
+ 'LINE_DISCOUNT_EXCEEDS_MAX',
34
+ 'FX_CONVERSION_UNAVAILABLE',
35
+ 'COUPON_NOT_FOUND',
36
+ 'COUPON_NOT_ACTIVE',
37
+ 'COUPON_EXPIRED',
38
+ 'COUPON_EXHAUSTED',
39
+ 'COUPON_MIN_SUBTOTAL',
40
+ 'COUPON_REQUIRES_CUSTOMER',
41
+ 'COUPON_BUDGET_EXHAUSTED',
42
+ 'COUPON_RATE_LIMITED',
43
+ 'COUPON_CURRENCY_MISMATCH',
44
+ 'PROMOTION_NOT_FOUND',
45
+ 'PROMOTION_NOT_ACTIVE',
46
+ 'PROMOTION_NOT_STARTED',
47
+ 'PROMOTION_EXPIRED',
48
+ 'PROMOTION_NOT_CART_APPLICABLE',
49
+ ];
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KIT_COMPONENT_MAX_QUANTITY = exports.KIT_RECIPE_MAX_COMPONENTS = exports.KIT_SALE_CHANNELS = void 0;
4
+ const product_ts_1 = require("./product.js");
2
5
  /**
3
6
  * Kit recipes — the components one ordinary product is assembled from.
4
7
  *
@@ -20,8 +23,6 @@
20
23
  * A shopper never sees the recipe: the storefront reads `Product.isKit` and the
21
24
  * kit's `stock`, which the shopper projection derives from its components.
22
25
  */
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.KIT_COMPONENT_MAX_QUANTITY = exports.KIT_RECIPE_MAX_COMPONENTS = exports.KIT_SALE_CHANNELS = void 0;
25
26
  /**
26
27
  * The channels that sell a kit by consuming its components:
27
28
  *
@@ -38,4 +39,4 @@ exports.KIT_SALE_CHANNELS = ['counter', 'storefront'];
38
39
  */
39
40
  exports.KIT_RECIPE_MAX_COMPONENTS = 30;
40
41
  /** Largest quantity of one component per kit unit — the same ceiling an order line has. */
41
- exports.KIT_COMPONENT_MAX_QUANTITY = 1_000_000;
42
+ exports.KIT_COMPONENT_MAX_QUANTITY = product_ts_1.MAX_BASE_QUANTITY;
package/dist/cjs/order.js CHANGED
@@ -260,6 +260,13 @@ exports.orderFulfilmentProgress = orderFulfilmentProgress;
260
260
  * - `ORDER_HAS_KIT_LINES` · 409 — `mode: 'edit'` on an order holding a kit line
261
261
  * (`CartLine.kit`). Cancel or return the order instead. Nothing was written.
262
262
  *
263
+ * Unit refusals: `UNIT_QUANTITY_NOT_WHOLE` · 400 on checkout, a quantity edit
264
+ * or a return of an `each` product (including frozen kit components);
265
+ * `UNIT_QUANTITY_UNREPRESENTABLE` · 400 when kit components cannot be
266
+ * represented exactly on checkout or return; `UNIT_REVISION_STALE` · 409 when
267
+ * the product's units changed between the read and the sale commit. Re-read
268
+ * current product units before retrying. Nothing was written.
269
+ *
263
270
  * Shipment refusals (`mode: 'shipment' | 'shipment-void'`), nothing written on
264
271
  * any of them:
265
272
  * - `SHIPMENT_QUANTITY_EXCEEDED` · 409 — a line would exceed its ordered
@@ -367,6 +374,10 @@ exports.ORDER_ERROR_CODES = [
367
374
  // `POST /orders { mode: 'deposit-refund' }` — see `OrderDepositRefundRequest`.
368
375
  'DEPOSIT_NOT_FOUND',
369
376
  'DEPOSIT_ALREADY_REFUNDED',
377
+ // Checkout/quantity edits and commit-time product-unit revision guards.
378
+ 'UNIT_QUANTITY_NOT_WHOLE',
379
+ 'UNIT_REVISION_STALE',
380
+ 'UNIT_QUANTITY_UNREPRESENTABLE',
370
381
  // A kit's recipe changed or was retired between reading it and the sale's commit.
371
382
  'KIT_RECIPE_CHANGED',
372
383
  // `mode: 'edit'` on an order holding a kit line; cancel or return it instead.
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PRODUCT_MEDIA_INVALID_REASONS = exports.MAX_PRODUCT_PICTURE_ALT_CHARS = exports.PRODUCT_UPSERT_ERROR_CODES = exports.PRODUCT_UPSERT_ROW_ERROR_CODES = exports.PRODUCT_UPSERT_ROW_CAP = exports.PUBLIC_PRODUCT_BY_SLUG_ERROR_CODES = exports.PRODUCT_PUBLISH_OVERDUE_AFTER_MS = exports.PRODUCT_PUBLISH_AT_MAX_AHEAD_MS = exports.PRODUCT_POST_ERROR_CODES = exports.PRODUCT_IDENTITY_ERROR_CODES = exports.PRODUCT_EXPORT_ERROR_CODES = exports.PRODUCT_CATALOG_EXPORT_COLUMNS = void 0;
3
+ exports.PRODUCT_MEDIA_INVALID_REASONS = exports.MAX_PRODUCT_PICTURE_ALT_CHARS = exports.PRODUCT_UPSERT_ERROR_CODES = exports.PRODUCT_UPSERT_ROW_ERROR_CODES = exports.PRODUCT_UPSERT_ROW_CAP = exports.PUBLIC_PRODUCT_BY_SLUG_ERROR_CODES = exports.PRODUCT_PUBLISH_OVERDUE_AFTER_MS = exports.PRODUCT_PUBLISH_AT_MAX_AHEAD_MS = exports.PRODUCT_POST_ERROR_CODES = exports.PRODUCT_IDENTITY_ERROR_CODES = exports.PRODUCT_EXPORT_ERROR_CODES = exports.PRODUCT_CATALOG_EXPORT_COLUMNS = exports.MAX_BASE_QUANTITY = exports.BASE_QUANTITY_DECIMAL_PLACES = void 0;
4
+ /** Maximum decimal places in a canonical base quantity; inputs are exact, never rounded. */
5
+ exports.BASE_QUANTITY_DECIMAL_PLACES = 6;
6
+ /** Absolute base-quantity input bound; signed adjustments admit the negative of this value. */
7
+ exports.MAX_BASE_QUANTITY = 1_000_000;
4
8
  /**
5
9
  * The CSV header of `GET /products?mode=export`, in file order.
6
10
  *
@@ -69,10 +69,25 @@ exports.PURCHASE_ORDER_STATUSES = [
69
69
  * (`stacks/services/ingressLevels.ts`), which the handler forwards verbatim, so
70
70
  * they never appear as literals in `_postStock.ts`.
71
71
  *
72
+ * The optional `Idempotency-Key` wrapper also refuses inline:
73
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
74
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
75
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
76
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
77
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
78
+ * inspect what committed before retrying with a new key.
79
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
80
+ * omitted from this refusal tuple.
81
+ *
72
82
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
73
83
  * as well.
74
84
  */
75
85
  exports.PURCHASE_ORDER_RECEIPT_ERROR_CODES = [
86
+ 'INVALID_IDEMPOTENCY_KEY',
87
+ 'IDEMPOTENCY_KEY_REUSE',
88
+ 'IDEMPOTENCY_IN_PROGRESS',
89
+ 'IDEMPOTENCY_LOCK_CONTENTION',
90
+ 'IDEMPOTENCY_KEY_FAILED',
76
91
  'PURCHASE_ORDER_NOT_FOUND',
77
92
  'INVALID_CURSOR',
78
93
  'ADMIN_REQUIRED',
package/dist/cjs/stock.js CHANGED
@@ -104,10 +104,39 @@ exports.MAX_STOCK_ADJUSTMENT_NOTE_BYTES = 280;
104
104
  * - `OPENING_BALANCE_UNVERIFIABLE` · 409 — the product's ledger is too long to sum,
105
105
  * so the opening balance cannot be checked and is not written.
106
106
  *
107
+ * - `WAREHOUSE_NOT_FOUND` · 404 / `WAREHOUSE_INACTIVE` · 409 and
108
+ * `NO_PRIMARY_WAREHOUSE` · 409 — forwarded by the depósito resolver.
109
+ * - `UNIT_QUANTITY_UNREPRESENTABLE`, `UNIT_QUANTITY_NOT_WHOLE`,
110
+ * `UNIT_FACTOR_INVALID` · 400 — base/purchase quantity normalization.
111
+ * - `UNIT_REVISION_STALE` · 409 — product units changed before commit.
112
+ * - `PURCHASE_RECEIPT_NOT_FOUND` · 404 — receipt-linked return has no receipt.
113
+ * - `PURCHASE_ORDER_LINE_NOT_FOUND`, `PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED`,
114
+ * `PURCHASE_RECEIPT_WAREHOUSE_MISMATCH`, `PURCHASE_RECEIPT_VERSION_CONFLICT`
115
+ * · 409 — receipt line, quantity, warehouse or concurrent return refusal.
116
+ *
117
+ * The optional `Idempotency-Key` wrapper also refuses inline:
118
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
119
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
120
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
121
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
122
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
123
+ * inspect what committed before retrying with a new key.
124
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
125
+ * omitted from this refusal tuple.
126
+ *
107
127
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
108
128
  * as well.
109
129
  */
110
130
  exports.STOCK_ADJUSTMENT_ERROR_CODES = [
131
+ 'INVALID_IDEMPOTENCY_KEY',
132
+ 'IDEMPOTENCY_KEY_REUSE',
133
+ 'IDEMPOTENCY_IN_PROGRESS',
134
+ 'IDEMPOTENCY_LOCK_CONTENTION',
135
+ 'IDEMPOTENCY_KEY_FAILED',
136
+ 'WAREHOUSE_NOT_FOUND',
137
+ 'WAREHOUSE_INACTIVE',
138
+ 'NO_PRIMARY_WAREHOUSE',
139
+ 'UNIT_REVISION_STALE',
111
140
  'PURCHASE_ORDER_LINE_NOT_FOUND',
112
141
  'PURCHASE_RECEIPT_NOT_FOUND',
113
142
  'PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED',
package/dist/kitRecipe.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MAX_BASE_QUANTITY } from "./product.js";
1
2
  /**
2
3
  * Kit recipes — the components one ordinary product is assembled from.
3
4
  *
@@ -35,4 +36,4 @@ export const KIT_SALE_CHANNELS = ['counter', 'storefront'];
35
36
  */
36
37
  export const KIT_RECIPE_MAX_COMPONENTS = 30;
37
38
  /** Largest quantity of one component per kit unit — the same ceiling an order line has. */
38
- export const KIT_COMPONENT_MAX_QUANTITY = 1_000_000;
39
+ export const KIT_COMPONENT_MAX_QUANTITY = MAX_BASE_QUANTITY;
package/dist/order.d.ts CHANGED
@@ -2360,6 +2360,13 @@ export declare const orderFulfilmentProgress: (order: OrderFulfilmentProgressInp
2360
2360
  * - `ORDER_HAS_KIT_LINES` · 409 — `mode: 'edit'` on an order holding a kit line
2361
2361
  * (`CartLine.kit`). Cancel or return the order instead. Nothing was written.
2362
2362
  *
2363
+ * Unit refusals: `UNIT_QUANTITY_NOT_WHOLE` · 400 on checkout, a quantity edit
2364
+ * or a return of an `each` product (including frozen kit components);
2365
+ * `UNIT_QUANTITY_UNREPRESENTABLE` · 400 when kit components cannot be
2366
+ * represented exactly on checkout or return; `UNIT_REVISION_STALE` · 409 when
2367
+ * the product's units changed between the read and the sale commit. Re-read
2368
+ * current product units before retrying. Nothing was written.
2369
+ *
2363
2370
  * Shipment refusals (`mode: 'shipment' | 'shipment-void'`), nothing written on
2364
2371
  * any of them:
2365
2372
  * - `SHIPMENT_QUANTITY_EXCEEDED` · 409 — a line would exceed its ordered
@@ -2402,6 +2409,6 @@ export declare const orderFulfilmentProgress: (order: OrderFulfilmentProgressInp
2402
2409
  * `SUBSCRIPTION_READONLY`) sit on the order CREATE leg only: an edit is not a new
2403
2410
  * order, so `bareOrderPostHandler` routes past `requireMetered` entirely.
2404
2411
  */
2405
- export declare const ORDER_ERROR_CODES: readonly ["ORDER_COST_DENOMINATION_UNKNOWN", "SERVICE_ORDER_DELIVERY_MANAGED", "ORDER_NOT_FOUND", "ORDER_LOCKED", "ORDER_NOT_READY", "ORDER_DELIVERED_ANOTHER_DAY", "ORDER_DELIVERY_STATE_UNCHANGED", "ORDER_ILLEGAL_TRANSITION", "ORDER_VERSION_CONFLICT", "CUSTOMER_NOT_FOUND", "CUSTOMER_MERGED", "ORDER_HAS_NO_CUSTOMER", "CUSTOMER_ANONYMIZED", "FX_CONVERSION_UNAVAILABLE", "ROLE_FORBIDDEN", "ORDER_CANCELLED", "PAYMENT_SETTLED_ON_ANOTHER_ORDER", "ORDER_HAS_SHIPMENTS", "ORDER_HAS_RETURNS", "SHIPMENT_QUANTITY_EXCEEDED", "SHIPMENT_LINE_MISMATCH", "SHIPMENT_NOT_FOUND", "SHIPMENT_LIMIT_REACHED", "SHIPMENT_TOO_LARGE", "ORDER_FULFILMENT_EXTERNAL", "SHIPMENT_FAILED", "ADMIN_REQUIRED", "IDEMPOTENCY_KEY_REQUIRED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENT_RESPONSE_NOT_CACHED", "ORDER_CANCELLATION_NOT_PROTECTED", "ORDER_CANCELLATION_COMMITTED", "ORDER_CANCELLATION_LOCKED", "ORDER_CANCELLATION_TOO_LARGE", "ORDER_CANCELLATION_FAILED", "SALE_LEDGER_RETRACTION_REFUSED", "SALE_LEDGER_ROW_NOT_OWNED", "PRODUCT_NOT_FOUND", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "DEPOSIT_SOURCE_NOT_ALLOWED", "DEPOSIT_CURRENCY_MISMATCH", "DEPOSIT_EXCEEDS_TOTAL", "DEPOSIT_FREEZE_REQUIRES_INVOICE", "DEPOSIT_FREEZE_INVOICE_PENDING", "DEPOSIT_CHARACTERIZATION_CONFLICT", "DEPOSIT_FREEZE_INVOICE_NOT_QUALIFYING", "DEPOSIT_INVOICED_ORDER_MUST_FREEZE", "DEPOSIT_REFERENCE_IS_LINKED_PAYMENT", "DEPOSIT_DUPLICATE_REFERENCE", "PAYMENT_REFERENCED_BY_DEPOSIT", "ORDER_HAS_UNREFUNDED_DEPOSIT", "DEPOSIT_NOT_FOUND", "DEPOSIT_ALREADY_REFUNDED", "KIT_RECIPE_CHANGED", "ORDER_HAS_KIT_LINES"];
2412
+ export declare const ORDER_ERROR_CODES: readonly ["ORDER_COST_DENOMINATION_UNKNOWN", "SERVICE_ORDER_DELIVERY_MANAGED", "ORDER_NOT_FOUND", "ORDER_LOCKED", "ORDER_NOT_READY", "ORDER_DELIVERED_ANOTHER_DAY", "ORDER_DELIVERY_STATE_UNCHANGED", "ORDER_ILLEGAL_TRANSITION", "ORDER_VERSION_CONFLICT", "CUSTOMER_NOT_FOUND", "CUSTOMER_MERGED", "ORDER_HAS_NO_CUSTOMER", "CUSTOMER_ANONYMIZED", "FX_CONVERSION_UNAVAILABLE", "ROLE_FORBIDDEN", "ORDER_CANCELLED", "PAYMENT_SETTLED_ON_ANOTHER_ORDER", "ORDER_HAS_SHIPMENTS", "ORDER_HAS_RETURNS", "SHIPMENT_QUANTITY_EXCEEDED", "SHIPMENT_LINE_MISMATCH", "SHIPMENT_NOT_FOUND", "SHIPMENT_LIMIT_REACHED", "SHIPMENT_TOO_LARGE", "ORDER_FULFILMENT_EXTERNAL", "SHIPMENT_FAILED", "ADMIN_REQUIRED", "IDEMPOTENCY_KEY_REQUIRED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENT_RESPONSE_NOT_CACHED", "ORDER_CANCELLATION_NOT_PROTECTED", "ORDER_CANCELLATION_COMMITTED", "ORDER_CANCELLATION_LOCKED", "ORDER_CANCELLATION_TOO_LARGE", "ORDER_CANCELLATION_FAILED", "SALE_LEDGER_RETRACTION_REFUSED", "SALE_LEDGER_ROW_NOT_OWNED", "PRODUCT_NOT_FOUND", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "DEPOSIT_SOURCE_NOT_ALLOWED", "DEPOSIT_CURRENCY_MISMATCH", "DEPOSIT_EXCEEDS_TOTAL", "DEPOSIT_FREEZE_REQUIRES_INVOICE", "DEPOSIT_FREEZE_INVOICE_PENDING", "DEPOSIT_CHARACTERIZATION_CONFLICT", "DEPOSIT_FREEZE_INVOICE_NOT_QUALIFYING", "DEPOSIT_INVOICED_ORDER_MUST_FREEZE", "DEPOSIT_REFERENCE_IS_LINKED_PAYMENT", "DEPOSIT_DUPLICATE_REFERENCE", "PAYMENT_REFERENCED_BY_DEPOSIT", "ORDER_HAS_UNREFUNDED_DEPOSIT", "DEPOSIT_NOT_FOUND", "DEPOSIT_ALREADY_REFUNDED", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_REVISION_STALE", "UNIT_QUANTITY_UNREPRESENTABLE", "KIT_RECIPE_CHANGED", "ORDER_HAS_KIT_LINES"];
2406
2413
  export type OrderErrorCode = (typeof ORDER_ERROR_CODES)[number];
2407
2414
  export {};
package/dist/order.js CHANGED
@@ -253,6 +253,13 @@ export const orderFulfilmentProgress = (order) => {
253
253
  * - `ORDER_HAS_KIT_LINES` · 409 — `mode: 'edit'` on an order holding a kit line
254
254
  * (`CartLine.kit`). Cancel or return the order instead. Nothing was written.
255
255
  *
256
+ * Unit refusals: `UNIT_QUANTITY_NOT_WHOLE` · 400 on checkout, a quantity edit
257
+ * or a return of an `each` product (including frozen kit components);
258
+ * `UNIT_QUANTITY_UNREPRESENTABLE` · 400 when kit components cannot be
259
+ * represented exactly on checkout or return; `UNIT_REVISION_STALE` · 409 when
260
+ * the product's units changed between the read and the sale commit. Re-read
261
+ * current product units before retrying. Nothing was written.
262
+ *
256
263
  * Shipment refusals (`mode: 'shipment' | 'shipment-void'`), nothing written on
257
264
  * any of them:
258
265
  * - `SHIPMENT_QUANTITY_EXCEEDED` · 409 — a line would exceed its ordered
@@ -360,6 +367,10 @@ export const ORDER_ERROR_CODES = [
360
367
  // `POST /orders { mode: 'deposit-refund' }` — see `OrderDepositRefundRequest`.
361
368
  'DEPOSIT_NOT_FOUND',
362
369
  'DEPOSIT_ALREADY_REFUNDED',
370
+ // Checkout/quantity edits and commit-time product-unit revision guards.
371
+ 'UNIT_QUANTITY_NOT_WHOLE',
372
+ 'UNIT_REVISION_STALE',
373
+ 'UNIT_QUANTITY_UNREPRESENTABLE',
363
374
  // A kit's recipe changed or was retired between reading it and the sale's commit.
364
375
  'KIT_RECIPE_CHANGED',
365
376
  // `mode: 'edit'` on an order holding a kit line; cancel or return it instead.
package/dist/product.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /** Maximum decimal places in a canonical base quantity; inputs are exact, never rounded. */
2
+ export declare const BASE_QUANTITY_DECIMAL_PLACES = 6;
3
+ /** Absolute base-quantity input bound; signed adjustments admit the negative of this value. */
4
+ export declare const MAX_BASE_QUANTITY = 1000000;
1
5
  declare global {
2
6
  type ProductUnitsErrorCode = 'UNIT_REVISION_STALE' | 'UNIT_FACTOR_INVALID' | 'UNIT_FACTOR_MISMATCH' | 'UNIT_INCOMPATIBLE_DIMENSION' | 'UNIT_QUANTITY_UNREPRESENTABLE' | 'UNIT_QUANTITY_NOT_WHOLE';
3
7
  type ProductUnit = 'each' | 'gram' | 'kilogram' | 'millilitre' | 'litre' | 'metre';
package/dist/product.js CHANGED
@@ -1,3 +1,7 @@
1
+ /** Maximum decimal places in a canonical base quantity; inputs are exact, never rounded. */
2
+ export const BASE_QUANTITY_DECIMAL_PLACES = 6;
3
+ /** Absolute base-quantity input bound; signed adjustments admit the negative of this value. */
4
+ export const MAX_BASE_QUANTITY = 1_000_000;
1
5
  /**
2
6
  * The CSV header of `GET /products?mode=export`, in file order.
3
7
  *
@@ -215,9 +215,19 @@ declare global {
215
215
  * (`stacks/services/ingressLevels.ts`), which the handler forwards verbatim, so
216
216
  * they never appear as literals in `_postStock.ts`.
217
217
  *
218
+ * The optional `Idempotency-Key` wrapper also refuses inline:
219
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
220
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
221
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
222
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
223
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
224
+ * inspect what committed before retrying with a new key.
225
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
226
+ * omitted from this refusal tuple.
227
+ *
218
228
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
219
229
  * as well.
220
230
  */
221
- export declare const PURCHASE_ORDER_RECEIPT_ERROR_CODES: readonly ["PURCHASE_ORDER_NOT_FOUND", "INVALID_CURSOR", "ADMIN_REQUIRED", "PURCHASE_ORDER_NOT_SENT", "PURCHASE_ORDER_CLOSED", "PURCHASE_ORDER_ITEM_NOT_FOUND", "PURCHASE_ORDER_ITEM_PRODUCT_MISMATCH", "PURCHASE_ORDER_NOT_RECEIVABLE", "STOCK_INCOME_RACED", "PRODUCT_NOT_FOUND", "VALIDATION_FAILED", "UNIT_QUANTITY_UNREPRESENTABLE", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_FACTOR_INVALID", "UNIT_REVISION_STALE", "WAREHOUSE_NOT_FOUND", "WAREHOUSE_INACTIVE", "NO_PRIMARY_WAREHOUSE", "INVENTORY_LEVEL_VERSION_CONFLICT"];
231
+ export declare const PURCHASE_ORDER_RECEIPT_ERROR_CODES: readonly ["INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "PURCHASE_ORDER_NOT_FOUND", "INVALID_CURSOR", "ADMIN_REQUIRED", "PURCHASE_ORDER_NOT_SENT", "PURCHASE_ORDER_CLOSED", "PURCHASE_ORDER_ITEM_NOT_FOUND", "PURCHASE_ORDER_ITEM_PRODUCT_MISMATCH", "PURCHASE_ORDER_NOT_RECEIVABLE", "STOCK_INCOME_RACED", "PRODUCT_NOT_FOUND", "VALIDATION_FAILED", "UNIT_QUANTITY_UNREPRESENTABLE", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_FACTOR_INVALID", "UNIT_REVISION_STALE", "WAREHOUSE_NOT_FOUND", "WAREHOUSE_INACTIVE", "NO_PRIMARY_WAREHOUSE", "INVENTORY_LEVEL_VERSION_CONFLICT"];
222
232
  export type PurchaseOrderReceiptErrorCode = (typeof PURCHASE_ORDER_RECEIPT_ERROR_CODES)[number];
223
233
  export {};
@@ -66,10 +66,25 @@ export const PURCHASE_ORDER_STATUSES = [
66
66
  * (`stacks/services/ingressLevels.ts`), which the handler forwards verbatim, so
67
67
  * they never appear as literals in `_postStock.ts`.
68
68
  *
69
+ * The optional `Idempotency-Key` wrapper also refuses inline:
70
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
71
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
72
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
73
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
74
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
75
+ * inspect what committed before retrying with a new key.
76
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
77
+ * omitted from this refusal tuple.
78
+ *
69
79
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
70
80
  * as well.
71
81
  */
72
82
  export const PURCHASE_ORDER_RECEIPT_ERROR_CODES = [
83
+ 'INVALID_IDEMPOTENCY_KEY',
84
+ 'IDEMPOTENCY_KEY_REUSE',
85
+ 'IDEMPOTENCY_IN_PROGRESS',
86
+ 'IDEMPOTENCY_LOCK_CONTENTION',
87
+ 'IDEMPOTENCY_KEY_FAILED',
73
88
  'PURCHASE_ORDER_NOT_FOUND',
74
89
  'INVALID_CURSOR',
75
90
  'ADMIN_REQUIRED',
package/dist/stock.d.ts CHANGED
@@ -639,9 +639,29 @@ declare global {
639
639
  * - `OPENING_BALANCE_UNVERIFIABLE` · 409 — the product's ledger is too long to sum,
640
640
  * so the opening balance cannot be checked and is not written.
641
641
  *
642
+ * - `WAREHOUSE_NOT_FOUND` · 404 / `WAREHOUSE_INACTIVE` · 409 and
643
+ * `NO_PRIMARY_WAREHOUSE` · 409 — forwarded by the depósito resolver.
644
+ * - `UNIT_QUANTITY_UNREPRESENTABLE`, `UNIT_QUANTITY_NOT_WHOLE`,
645
+ * `UNIT_FACTOR_INVALID` · 400 — base/purchase quantity normalization.
646
+ * - `UNIT_REVISION_STALE` · 409 — product units changed before commit.
647
+ * - `PURCHASE_RECEIPT_NOT_FOUND` · 404 — receipt-linked return has no receipt.
648
+ * - `PURCHASE_ORDER_LINE_NOT_FOUND`, `PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED`,
649
+ * `PURCHASE_RECEIPT_WAREHOUSE_MISMATCH`, `PURCHASE_RECEIPT_VERSION_CONFLICT`
650
+ * · 409 — receipt line, quantity, warehouse or concurrent return refusal.
651
+ *
652
+ * The optional `Idempotency-Key` wrapper also refuses inline:
653
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
654
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
655
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
656
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
657
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
658
+ * inspect what committed before retrying with a new key.
659
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
660
+ * omitted from this refusal tuple.
661
+ *
642
662
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
643
663
  * as well.
644
664
  */
645
- export declare const STOCK_ADJUSTMENT_ERROR_CODES: readonly ["PURCHASE_ORDER_LINE_NOT_FOUND", "PURCHASE_RECEIPT_NOT_FOUND", "PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED", "PURCHASE_RECEIPT_VERSION_CONFLICT", "PURCHASE_RECEIPT_WAREHOUSE_MISMATCH", "UNIT_QUANTITY_UNREPRESENTABLE", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_FACTOR_INVALID", "ROLE_FORBIDDEN", "VALIDATION_FAILED", "STOCK_ADJUSTMENT_REASON_DIRECTION_MISMATCH", "PRODUCT_NOT_FOUND", "STOCK_ADJUSTMENT_RACED", "OPENING_BALANCE_EXISTS", "OPENING_BALANCE_QUANTITY_MISMATCH", "OPENING_BALANCE_UNVERIFIABLE"];
665
+ export declare const STOCK_ADJUSTMENT_ERROR_CODES: readonly ["INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_LOCK_CONTENTION", "IDEMPOTENCY_KEY_FAILED", "WAREHOUSE_NOT_FOUND", "WAREHOUSE_INACTIVE", "NO_PRIMARY_WAREHOUSE", "UNIT_REVISION_STALE", "PURCHASE_ORDER_LINE_NOT_FOUND", "PURCHASE_RECEIPT_NOT_FOUND", "PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED", "PURCHASE_RECEIPT_VERSION_CONFLICT", "PURCHASE_RECEIPT_WAREHOUSE_MISMATCH", "UNIT_QUANTITY_UNREPRESENTABLE", "UNIT_QUANTITY_NOT_WHOLE", "UNIT_FACTOR_INVALID", "ROLE_FORBIDDEN", "VALIDATION_FAILED", "STOCK_ADJUSTMENT_REASON_DIRECTION_MISMATCH", "PRODUCT_NOT_FOUND", "STOCK_ADJUSTMENT_RACED", "OPENING_BALANCE_EXISTS", "OPENING_BALANCE_QUANTITY_MISMATCH", "OPENING_BALANCE_UNVERIFIABLE"];
646
666
  export type StockAdjustmentErrorCode = (typeof STOCK_ADJUSTMENT_ERROR_CODES)[number];
647
667
  export {};
package/dist/stock.js CHANGED
@@ -101,10 +101,39 @@ export const MAX_STOCK_ADJUSTMENT_NOTE_BYTES = 280;
101
101
  * - `OPENING_BALANCE_UNVERIFIABLE` · 409 — the product's ledger is too long to sum,
102
102
  * so the opening balance cannot be checked and is not written.
103
103
  *
104
+ * - `WAREHOUSE_NOT_FOUND` · 404 / `WAREHOUSE_INACTIVE` · 409 and
105
+ * `NO_PRIMARY_WAREHOUSE` · 409 — forwarded by the depósito resolver.
106
+ * - `UNIT_QUANTITY_UNREPRESENTABLE`, `UNIT_QUANTITY_NOT_WHOLE`,
107
+ * `UNIT_FACTOR_INVALID` · 400 — base/purchase quantity normalization.
108
+ * - `UNIT_REVISION_STALE` · 409 — product units changed before commit.
109
+ * - `PURCHASE_RECEIPT_NOT_FOUND` · 404 — receipt-linked return has no receipt.
110
+ * - `PURCHASE_ORDER_LINE_NOT_FOUND`, `PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED`,
111
+ * `PURCHASE_RECEIPT_WAREHOUSE_MISMATCH`, `PURCHASE_RECEIPT_VERSION_CONFLICT`
112
+ * · 409 — receipt line, quantity, warehouse or concurrent return refusal.
113
+ *
114
+ * The optional `Idempotency-Key` wrapper also refuses inline:
115
+ * - `INVALID_IDEMPOTENCY_KEY` · 400 — malformed key.
116
+ * - `IDEMPOTENCY_KEY_REUSE` · 422 — key reused for another payload.
117
+ * - `IDEMPOTENCY_IN_PROGRESS` · 409 — the original call is still running.
118
+ * - `IDEMPOTENCY_LOCK_CONTENTION` · 409 — could not claim the key; retry.
119
+ * - `IDEMPOTENCY_KEY_FAILED` · 409 — sealed after a throw or abandoned claim;
120
+ * inspect what committed before retrying with a new key.
121
+ * `IDEMPOTENT_RESPONSE_NOT_CACHED` is a 2xx replay sentinel and is deliberately
122
+ * omitted from this refusal tuple.
123
+ *
104
124
  * ⚠️ Cross-cutting refusals are NOT repeated here — check `GATING_ERROR_CODES`
105
125
  * as well.
106
126
  */
107
127
  export const STOCK_ADJUSTMENT_ERROR_CODES = [
128
+ 'INVALID_IDEMPOTENCY_KEY',
129
+ 'IDEMPOTENCY_KEY_REUSE',
130
+ 'IDEMPOTENCY_IN_PROGRESS',
131
+ 'IDEMPOTENCY_LOCK_CONTENTION',
132
+ 'IDEMPOTENCY_KEY_FAILED',
133
+ 'WAREHOUSE_NOT_FOUND',
134
+ 'WAREHOUSE_INACTIVE',
135
+ 'NO_PRIMARY_WAREHOUSE',
136
+ 'UNIT_REVISION_STALE',
108
137
  'PURCHASE_ORDER_LINE_NOT_FOUND',
109
138
  'PURCHASE_RECEIPT_NOT_FOUND',
110
139
  'PURCHASE_RECEIPT_RETURN_EXCEEDS_RECEIVED',
@@ -575,7 +575,7 @@ declare global {
575
575
  tier: PlanTier;
576
576
  /** A tier with no `SK: PLAN` row is unpurchasable, whatever its features are. */
577
577
  hasTemplate: boolean;
578
- /** `priceMonthlyCents === 0` on the template — exempt from the provider-identifier check. */
578
+ /** Neither cycle is charged (each price `0` or absent) — exempt from the provider-identifier check. */
579
579
  free: boolean;
580
580
  /**
581
581
  * Provider-identifier KEY NAMES the template is missing (e.g.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinfactura-types",
3
- "version": "1.10.615",
3
+ "version": "1.10.617",
4
4
  "description": "Shared wire and entity contracts for the SINFACTURA platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",