platform-types 1.10.854 → 1.10.855

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.
@@ -7,6 +7,7 @@ exports.WEB_NOTIFICATIONS_MAX_LIMIT = exports.CUSTOMER_NOTIFICATION_TYPES = expo
7
7
  // Addressed ERROR / ML_FACTURADOR_COLLISION / ML_SELLER_SWITCHED /
8
8
  // ML_CONNECTION_BROKEN / MENTION / CUSTOMER_PENDING_APPROVAL /
9
9
  // PAYMENT_ATTEMPT_RESOLUTION / CUSTOMER_WHOLESALE_REQUEST / STOREFRONT_QUOTE_REQUEST /
10
+ // SERVICE_QUOTE_DECIDED /
10
11
  // REFUND_OBLIGATION_OVERDUE / MESSAGE_CREDITS_LOW / MESSAGE_SKIPPED_NO_CREDITS do not select a
11
12
  // producer subscriber audience;
12
13
  // the consumer still reads the live USER row for each delivery channel.
@@ -237,6 +238,24 @@ var NotificationTypeEnum;
237
238
  * socket).
238
239
  */
239
240
  NotificationTypeEnum["STOREFRONT_QUOTE_REQUEST"] = "STOREFRONT_QUOTE_REQUEST";
241
+ /**
242
+ * A customer approved or rejected a presupuesto online — by the tracking link
243
+ * (`POST /services/tracking/quote`) or signed in
244
+ * (`POST /user { mode: 'service-quote-decision' }`). The decision does not
245
+ * move the ticket's stage; the bell is the shop's cue to do it. Names the
246
+ * ticket number and the decision only — never the customer.
247
+ *
248
+ * ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
249
+ * non-disabled ADMIN of the store (`listActiveAdmins`), the same recipients
250
+ * as `STOREFRONT_QUOTE_REQUEST`. No legacy opt-in scan: the consumer enforces
251
+ * explicit type/channel preferences
252
+ * (`notificationPreferences.SERVICE_QUOTE_DECIDED`), default-on when absent.
253
+ * Additive — no backfill or consumer migration owed.
254
+ *
255
+ * ⚠️ Carries `serviceOrderRef` and NEVER a top-level `customerId` (an
256
+ * audience — the bell would reach the customer's own storefront socket).
257
+ */
258
+ NotificationTypeEnum["SERVICE_QUOTE_DECIDED"] = "SERVICE_QUOTE_DECIDED";
240
259
  /**
241
260
  * A refund obligation went overdue: its due date passed and the hourly
242
261
  * sweep, after reconciling the payment, still found the money owed
@@ -305,6 +305,11 @@ exports.SERVICE_ORDER_ERROR_CODES = [
305
305
  'SERVICE_TRACKING_DISABLED',
306
306
  'SERVICE_TRACKING_NO_ADDRESS',
307
307
  'SERVICE_TRACKING_LINK_LAPSED',
308
+ // The customer's online quote decision (`_customerDecision.ts`, shared by the
309
+ // web routes) — see `SERVICE_TRACKING_DECISION_ERROR_CODES`
310
+ 'SERVICE_TRACKING_VERIFICATION_FAILED',
311
+ // A tracking link revoked between the handle check and the decision's commit.
312
+ 'SERVICE_TRACKING_TOKEN_INVALID',
308
313
  'ADMIN_REQUIRED',
309
314
  'CUSTODY_ILLEGAL_TRANSITION',
310
315
  'SERVICE_ORDER_BILLABLE_FROZEN',
@@ -6,18 +6,24 @@
6
6
  *
7
7
  * The HANDLE is 16 base62 characters (~95 bits), stored server-side and a
8
8
  * bearer credential over exactly ONE service order of one store. It authorises
9
- * the read (`GET /services/tracking?handle=`) and the current presupuesto PDF
10
- * (`GET /services/tracking?handle=&mode=quote-pdf`), and nothing else. Every
9
+ * the read (`GET /services/tracking?handle=`), the current presupuesto PDF
10
+ * (`GET /services/tracking?handle=&mode=quote-pdf`) and the online decision on
11
+ * that presupuesto, and nothing else. Every
11
12
  * earlier link of an order dies when staff revoke it. A link stops reading
12
13
  * `serviceTrackingLinkDaysAfterClose` days (platform global, default 90) after
13
14
  * the order reached a terminal status.
14
15
  *
16
+ * With the gate on, the customer may also approve (with a 6-digit code sent to
17
+ * the contact on file) or reject the current presupuesto
18
+ * (`POST /services/tracking/quote`, or signed in
19
+ * `POST /user { mode: 'service-quote-decision' }`).
20
+ *
15
21
  * The gate is `ecommerce.config.serviceTracking` AND the `workshop` module on.
16
22
  * With the gate off, staff cannot mint and the signed-in reads refuse, but a
17
23
  * link minted earlier keeps reading (STORE_MODULES rule 4).
18
24
  */
19
25
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.SERVICE_TRACKING_HANDLE_RE = exports.SERVICE_TRACKING_PATH_PREFIX = exports.SERVICE_TRACKING_LINK_ACTIONS = exports.SERVICE_TRACKING_OPERATOR_ERROR_CODES = exports.SERVICE_TRACKING_ACCOUNT_ERROR_CODES = exports.SERVICE_TRACKING_ERROR_CODES = void 0;
26
+ exports.SERVICE_TRACKING_HANDLE_RE = exports.SERVICE_TRACKING_PATH_PREFIX = exports.SERVICE_TRACKING_LINK_ACTIONS = exports.SERVICE_TRACKING_CODE_RE = exports.SERVICE_TRACKING_CHALLENGE_HANDLE_RE = exports.SERVICE_TRACKING_QUOTE_DECISIONS = exports.SERVICE_TRACKING_CODE_VIAS = exports.SERVICE_TRACKING_DECISION_ERROR_CODES = exports.SERVICE_TRACKING_OPERATOR_ERROR_CODES = exports.SERVICE_TRACKING_ACCOUNT_ERROR_CODES = exports.SERVICE_TRACKING_ERROR_CODES = void 0;
21
27
  /**
22
28
  * The refusals the PUBLIC tracking routes answer themselves.
23
29
  *
@@ -56,6 +62,53 @@ exports.SERVICE_TRACKING_OPERATOR_ERROR_CODES = [
56
62
  'SERVICE_TRACKING_LINK_LAPSED',
57
63
  'ADMIN_REQUIRED',
58
64
  ];
65
+ /**
66
+ * The refusals of the customer's online quote decision — by link
67
+ * (`POST /services/tracking/code`, `POST /services/tracking/quote`) and signed in
68
+ * (`POST /user { mode: 'service-quote-decision' }`).
69
+ *
70
+ * - SERVICE_TRACKING_TOKEN_INVALID (401, link only): the handle cannot be
71
+ * honoured — the same one body as the read.
72
+ * - SERVICE_TRACKING_VERIFICATION_FAILED (401, link approve only): no code, a
73
+ * wrong, used, expired or burnt one, or a challenge minted for another order.
74
+ * One body for every reason.
75
+ * - SERVICE_TRACKING_DISABLED (403): the store's gate is off. The read keeps working.
76
+ * - SERVICE_ORDER_NOT_FOUND (404, signed in only): no such order, a disabled
77
+ * one, or another customer's — one body.
78
+ * - SERVICE_QUOTE_NOT_FOUND (404): no version with that `quoteId`.
79
+ * - SERVICE_QUOTE_SUPERSEDED (409): a newer version replaced it.
80
+ * - SERVICE_QUOTE_ALREADY_RESOLVED (409): it already carries the OTHER decision
81
+ * (the same decision again answers 200 with the current view and writes nothing).
82
+ * - SERVICE_QUOTE_LAPSED (409, approve only): past `expiresAt`. A rejection is
83
+ * still accepted.
84
+ * - SERVICE_ORDER_QUOTE_CHANGED (409): the order moved under the decision — retry.
85
+ * - SERVICE_ORDER_TERMINAL / SERVICE_ORDER_DISABLED (409): the ticket is closed
86
+ * or deleted.
87
+ * - SERVICE_QUOTE_HOLDS_TOO_MANY (400, approve only): the version's parts cannot
88
+ * be reserved in one write; the shop must re-quote.
89
+ */
90
+ exports.SERVICE_TRACKING_DECISION_ERROR_CODES = [
91
+ 'SERVICE_TRACKING_TOKEN_INVALID',
92
+ 'SERVICE_TRACKING_VERIFICATION_FAILED',
93
+ 'SERVICE_TRACKING_DISABLED',
94
+ 'SERVICE_ORDER_NOT_FOUND',
95
+ 'SERVICE_QUOTE_NOT_FOUND',
96
+ 'SERVICE_QUOTE_SUPERSEDED',
97
+ 'SERVICE_QUOTE_ALREADY_RESOLVED',
98
+ 'SERVICE_QUOTE_LAPSED',
99
+ 'SERVICE_ORDER_QUOTE_CHANGED',
100
+ 'SERVICE_ORDER_TERMINAL',
101
+ 'SERVICE_ORDER_DISABLED',
102
+ 'SERVICE_QUOTE_HOLDS_TOO_MANY',
103
+ ];
104
+ /** Where the 6-digit decision code is sent: the phone or the email on file. */
105
+ exports.SERVICE_TRACKING_CODE_VIAS = ['sms', 'email'];
106
+ /** The customer's two online answers to a presupuesto. */
107
+ exports.SERVICE_TRACKING_QUOTE_DECISIONS = ['approve', 'reject'];
108
+ /** A decision challenge handle: 16 random bytes, base64url (22 characters). */
109
+ exports.SERVICE_TRACKING_CHALLENGE_HANDLE_RE = /^[\w-]{22}$/;
110
+ /** The decision code: 6 digits. */
111
+ exports.SERVICE_TRACKING_CODE_RE = /^\d{6}$/;
59
112
  /** `POST /services { mode: 'trackingLink' }` actions. */
60
113
  exports.SERVICE_TRACKING_LINK_ACTIONS = ['mint', 'revoke'];
61
114
  /** The storefront path a tracking link opens: `/servicio/{handle}`. */
@@ -214,6 +214,24 @@ export declare enum NotificationTypeEnum {
214
214
  * socket).
215
215
  */
216
216
  STOREFRONT_QUOTE_REQUEST = "STOREFRONT_QUOTE_REQUEST",
217
+ /**
218
+ * A customer approved or rejected a presupuesto online — by the tracking link
219
+ * (`POST /services/tracking/quote`) or signed in
220
+ * (`POST /user { mode: 'service-quote-decision' }`). The decision does not
221
+ * move the ticket's stage; the bell is the shop's cue to do it. Names the
222
+ * ticket number and the decision only — never the customer.
223
+ *
224
+ * ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
225
+ * non-disabled ADMIN of the store (`listActiveAdmins`), the same recipients
226
+ * as `STOREFRONT_QUOTE_REQUEST`. No legacy opt-in scan: the consumer enforces
227
+ * explicit type/channel preferences
228
+ * (`notificationPreferences.SERVICE_QUOTE_DECIDED`), default-on when absent.
229
+ * Additive — no backfill or consumer migration owed.
230
+ *
231
+ * ⚠️ Carries `serviceOrderRef` and NEVER a top-level `customerId` (an
232
+ * audience — the bell would reach the customer's own storefront socket).
233
+ */
234
+ SERVICE_QUOTE_DECIDED = "SERVICE_QUOTE_DECIDED",
217
235
  /**
218
236
  * A refund obligation went overdue: its due date passed and the hourly
219
237
  * sweep, after reconciling the payment, still found the money owed
@@ -383,6 +401,11 @@ declare global {
383
401
  * an audience — see {@link QuoteRequestRef}. Operator surfaces only.
384
402
  */
385
403
  quoteRequestRef?: QuoteRequestRef;
404
+ /**
405
+ * `SERVICE_QUOTE_DECIDED` only: which service order to open. A POINTER, not
406
+ * an audience — see {@link ServiceOrderRef}. Operator surfaces only.
407
+ */
408
+ serviceOrderRef?: ServiceOrderRef;
386
409
  /** `MENTION` only: the colleague who wrote the mention (a snapshot). */
387
410
  actor?: NotificationActor;
388
411
  userId?: string;
@@ -446,7 +469,7 @@ declare global {
446
469
  * customer socket carry — the stored row minus the operator-only pointers.
447
470
  * A union keyed on `type`; `ORDER` is its only member today.
448
471
  */
449
- type CustomerNotification = Omit<NotificationInterface, 'cartRef' | 'pendingCustomerRef' | 'wholesaleRequestRef' | 'quoteRequestRef' | 'TableName' | 'type' | 'orderId'> & {
472
+ type CustomerNotification = Omit<NotificationInterface, 'cartRef' | 'pendingCustomerRef' | 'wholesaleRequestRef' | 'quoteRequestRef' | 'serviceOrderRef' | 'TableName' | 'type' | 'orderId'> & {
450
473
  type: NotificationTypeEnum.ORDER;
451
474
  /** Set on every customer row: the order the bell is about. */
452
475
  orderId: string;
@@ -4,6 +4,7 @@
4
4
  // Addressed ERROR / ML_FACTURADOR_COLLISION / ML_SELLER_SWITCHED /
5
5
  // ML_CONNECTION_BROKEN / MENTION / CUSTOMER_PENDING_APPROVAL /
6
6
  // PAYMENT_ATTEMPT_RESOLUTION / CUSTOMER_WHOLESALE_REQUEST / STOREFRONT_QUOTE_REQUEST /
7
+ // SERVICE_QUOTE_DECIDED /
7
8
  // REFUND_OBLIGATION_OVERDUE / MESSAGE_CREDITS_LOW / MESSAGE_SKIPPED_NO_CREDITS do not select a
8
9
  // producer subscriber audience;
9
10
  // the consumer still reads the live USER row for each delivery channel.
@@ -234,6 +235,24 @@ export var NotificationTypeEnum;
234
235
  * socket).
235
236
  */
236
237
  NotificationTypeEnum["STOREFRONT_QUOTE_REQUEST"] = "STOREFRONT_QUOTE_REQUEST";
238
+ /**
239
+ * A customer approved or rejected a presupuesto online — by the tracking link
240
+ * (`POST /services/tracking/quote`) or signed in
241
+ * (`POST /user { mode: 'service-quote-decision' }`). The decision does not
242
+ * move the ticket's stage; the bell is the shop's cue to do it. Names the
243
+ * ticket number and the decision only — never the customer.
244
+ *
245
+ * ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
246
+ * non-disabled ADMIN of the store (`listActiveAdmins`), the same recipients
247
+ * as `STOREFRONT_QUOTE_REQUEST`. No legacy opt-in scan: the consumer enforces
248
+ * explicit type/channel preferences
249
+ * (`notificationPreferences.SERVICE_QUOTE_DECIDED`), default-on when absent.
250
+ * Additive — no backfill or consumer migration owed.
251
+ *
252
+ * ⚠️ Carries `serviceOrderRef` and NEVER a top-level `customerId` (an
253
+ * audience — the bell would reach the customer's own storefront socket).
254
+ */
255
+ NotificationTypeEnum["SERVICE_QUOTE_DECIDED"] = "SERVICE_QUOTE_DECIDED";
237
256
  /**
238
257
  * A refund obligation went overdue: its due date passed and the hourly
239
258
  * sweep, after reconciling the payment, still found the money owed
@@ -361,14 +361,13 @@ declare global {
361
361
  /**
362
362
  * How the customer's decision on a quote reached the shop.
363
363
  *
364
- * ⚠️ `'portal'` is RESERVED and must not be offered to an operator. The other
365
- * four are things an operator records second-hand; `'portal'` means the
366
- * customer approved it themselves, and no customer-facing service endpoint
367
- * exists on any gateway yet — so an operator selecting it would be recording
368
- * an event that did not happen. The api rejects it: it is excluded from the
369
- * `POST /services` quote-resolve enum and returns 400. It stays in the union
370
- * so rows already carrying it still read back, and so the value is ready the
371
- * day a customer portal ships. Filter it out of any channel picker.
364
+ * ⚠️ `'portal'` is stamped ONLY by the customer's own decision — the tracking
365
+ * link (`POST /services/tracking/quote`) or the signed-in account
366
+ * (`POST /user { mode: 'service-quote-decision' }`). The other four are
367
+ * things an operator records second-hand; an operator selecting `'portal'`
368
+ * would be recording an event that did not happen, so staff
369
+ * `POST /services` quote-resolve still refuses it with
370
+ * `400 SERVICE_QUOTE_CHANNEL_RESERVED`. Filter it out of any channel picker.
372
371
  */
373
372
  type ServiceQuoteChannel = 'in_person' | 'phone' | 'whatsapp' | 'email' | 'portal';
374
373
  /**
@@ -404,11 +403,14 @@ declare global {
404
403
  * while an approval recorded all three, so a quote history could show who
405
404
  * approved a version and nothing at all for a rejected one.
406
405
  *
407
- * No `channel`: a channel describes how an APPROVAL reached the shop, and
408
- * there is no approval here.
406
+ * `channel` is set ONLY to `'portal'`, when the customer refused the version
407
+ * themselves; absent means an operator recorded it.
409
408
  */
410
409
  interface ServiceQuoteRejection {
411
- /** userId of the operator who recorded it. Server-stamped, never from the body. */
410
+ /**
411
+ * userId of the operator who recorded it, or the customerId who refused it
412
+ * online (`channel: 'portal'`). Server-stamped, never from the body.
413
+ */
412
414
  by: string;
413
415
  /** Unix ms. Server-stamped. */
414
416
  at: number;
@@ -419,7 +421,19 @@ declare global {
419
421
  * constancia — those are statutory documents that go to a customer.
420
422
  */
421
423
  note?: string;
424
+ /** `'portal'`: the customer refused it online. Absent: an operator recorded it. */
425
+ channel?: 'portal';
426
+ /**
427
+ * The customer's display name when they decided online — a snapshot for the
428
+ * evidence. Cleared by the customer's erasure.
429
+ */
430
+ byName?: string;
422
431
  }
432
+ /**
433
+ * How a customer who decided online proved they were the contact on file:
434
+ * the 6-digit code sent by SMS or by email, or their signed-in session.
435
+ */
436
+ type ServiceQuoteVerifiedBy = 'sms_code' | 'email_code' | 'session';
423
437
  /**
424
438
  * A customer decision on a quote version, recorded as an event rather than a
425
439
  * boolean so that who/when/how survives a re-quote.
@@ -430,6 +444,13 @@ declare global {
430
444
  /** Unix ms. */
431
445
  at: number;
432
446
  channel: ServiceQuoteChannel;
447
+ /** `channel: 'portal'` only: how the customer proved they were the contact on file. */
448
+ verifiedBy?: ServiceQuoteVerifiedBy;
449
+ /**
450
+ * `channel: 'portal'` only: the customer's display name at decision time — a
451
+ * snapshot for the evidence. Cleared by the customer's erasure.
452
+ */
453
+ byName?: string;
433
454
  }
434
455
  /**
435
456
  * One version of the presupuesto. `ServiceOrder.quote` is the full ordered
@@ -1531,6 +1552,6 @@ export declare const MAX_SERVICE_PICTURES = 20;
1531
1552
  * check `GATING_ERROR_CODES` in addition. The deposit's opt-in idempotency
1532
1553
  * refusals are included here.
1533
1554
  */
1534
- export declare const SERVICE_ORDER_ERROR_CODES: readonly ["UNIT_QUANTITY_NOT_WHOLE", "UNIT_REVISION_STALE", "ROLE_FORBIDDEN", "PARENT_FILTER_CONFLICT", "VALIDATION_FAILED", "INVALID_CURSOR", "INVALID_FROM_DATE", "INVALID_TO_DATE", "INVALID_DATE_RANGE", "REWORK_INDEX_UNAVAILABLE", "STORE_NOT_FOUND", "SERVICE_ORDER_NOT_FOUND", "PRINT_JOB_DOCUMENT_NOT_FOUND", "CUSTOMER_NOT_FOUND", "PARENT_SERVICE_ORDER_NOT_FOUND", "SERVICE_TEMPLATE_NOT_FOUND", "SERVICE_TYPE_REQUIRED", "INVALID_IMAGE_REFERENCE", "MISSING_STORE_CONTEXT", "WARRANTY_BELOW_LEGAL_MINIMUM", "TECHNICIAN_NOT_FOUND", "TECHNICIAN_NOT_ELIGIBLE", "SERVICE_TRACKING_DISABLED", "SERVICE_TRACKING_NO_ADDRESS", "SERVICE_TRACKING_LINK_LAPSED", "ADMIN_REQUIRED", "CUSTODY_ILLEGAL_TRANSITION", "SERVICE_ORDER_BILLABLE_FROZEN", "SERVICE_ORDER_EDIT_CHANGED", "SERVICE_ORDER_DISABLED_STATE_UNCHANGED", "APPOINTMENT_NOT_FOUND", "APPOINTMENT_NOT_LINKABLE", "APPOINTMENT_NOT_OPEN", "APPOINTMENT_CUSTOMER_MISMATCH", "APPOINTMENT_VERSION_CONFLICT", "SERVICE_ORDER_TERMINAL", "SERVICE_ORDER_DISABLED", "SERVICE_ORDER_ILLEGAL_TRANSITION", "SERVICE_ORDER_STATUS_CHANGED", "SERVICE_ORDER_OUTCOME_REQUIRED", "SERVICE_ORDER_OUTCOME_NOT_ALLOWED", "SERVICE_ORDER_CUSTODY_NOT_RECEIVED", "SERVICE_ORDER_BILLABLE_NOT_AT_THIS_TRANSITION", "PAYMENT_METHODS_NOT_CONFIGURED", "PAYMENT_METHOD_NOT_FOUND", "SERVICE_ORDER_DEPOSIT_CHANGED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENCY_LOCK_CONTENTION", "SERVICE_DEPOSIT_CURRENCY_MISMATCH", "SERVICE_DEPOSIT_INVALID", "INVALID_AMOUNT", "DEFAULT_DRAWER_UNAVAILABLE", "CURRENCY_MISMATCH", "SHIFT_NOT_OPEN", "SERVICE_BILLING_PRODUCTS_UNAVAILABLE", "CATEGORY_NOT_FOUND", "CATEGORY_DISABLED", "CATEGORY_DISABLE_IN_PROGRESS", "SERVICE_BILLING_PRODUCT_IN_BASKET", "SERVICE_DELIVERY_BASKET_DECISION_REQUIRED", "SERVICE_DELIVERY_BASKET_CURRENCY_MISMATCH", "PRODUCT_NOT_AVAILABLE", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "FX_CONVERSION_UNAVAILABLE", "TRANSACT_ITEMS_LIMIT_EXCEEDED", "SERVICE_LINE_PRICE_TOO_LARGE", "SERVICE_PARTS_RESTORE_EXCEEDS_CONSUMED", "SERVICE_PARTS_RESTORE_EXCEEDS_AMOUNT", "SERVICE_PARTS_STOCK_INSUFFICIENT", "SERVICE_PARTS_PRODUCT_NOT_FOUND", "SERVICE_PARTS_LEDGER_FULL", "SERVICE_PARTS_TOO_MANY", "SERVICE_ORDER_PARTS_CHANGED", "SERVICE_QUOTE_NOT_FOUND", "SERVICE_QUOTE_NOT_OFFERABLE", "SERVICE_QUOTE_HOLDS_TOO_MANY", "SERVICE_QUOTE_SUPERSEDED", "SERVICE_QUOTE_ALREADY_RESOLVED", "SERVICE_QUOTE_LAPSED", "SERVICE_QUOTE_CHANNEL_RESERVED", "SERVICE_QUOTE_CHANNEL_NOT_APPLICABLE", "SERVICE_QUOTE_REASON_NOT_APPLICABLE", "SERVICE_ORDER_QUOTE_CHANGED", "SERVICE_ORDER_NOT_READY", "SERVICE_RENOTIFY_IMPERSONATED", "SERVICE_RENOTIFY_TOO_SOON", "SERVICE_RENOTIFY_CONFLICT", "SERVICE_RENOTIFY_NOT_RECORDED", "SERVICE_RENOTIFY_LEDGER_UNREADABLE", "SERVICE_RENOTIFY_NOT_DISPATCHED", "SERVICE_TIMER_ALREADY_RUNNING", "SERVICE_TIMER_NOT_RUNNING", "SERVICE_TIMER_STOP_CONFLICT", "TECHNICIAN_NOT_CLOCKED_IN", "SERVICE_ORDER_GONE", "SERVICE_WORKLOG_NOT_FOUND", "SERVICE_WORKLOG_NOT_REVERSIBLE", "SERVICE_WORKLOG_ALREADY_REVERSED", "SERVICE_ORDER_WORKLOG_CHANGED", "CART_PROMOTION_UNAVAILABLE", "COUPON_NOT_FOUND", "COUPON_NOT_ACTIVE", "COUPON_EXPIRED", "COUPON_EXHAUSTED", "COUPON_EXHAUSTED_AT_CHECKOUT", "COUPON_MIN_SUBTOTAL", "COUPON_REQUIRES_CUSTOMER", "COUPON_BUDGET_EXHAUSTED", "COUPON_CURRENCY_MISMATCH"];
1555
+ export declare const SERVICE_ORDER_ERROR_CODES: readonly ["UNIT_QUANTITY_NOT_WHOLE", "UNIT_REVISION_STALE", "ROLE_FORBIDDEN", "PARENT_FILTER_CONFLICT", "VALIDATION_FAILED", "INVALID_CURSOR", "INVALID_FROM_DATE", "INVALID_TO_DATE", "INVALID_DATE_RANGE", "REWORK_INDEX_UNAVAILABLE", "STORE_NOT_FOUND", "SERVICE_ORDER_NOT_FOUND", "PRINT_JOB_DOCUMENT_NOT_FOUND", "CUSTOMER_NOT_FOUND", "PARENT_SERVICE_ORDER_NOT_FOUND", "SERVICE_TEMPLATE_NOT_FOUND", "SERVICE_TYPE_REQUIRED", "INVALID_IMAGE_REFERENCE", "MISSING_STORE_CONTEXT", "WARRANTY_BELOW_LEGAL_MINIMUM", "TECHNICIAN_NOT_FOUND", "TECHNICIAN_NOT_ELIGIBLE", "SERVICE_TRACKING_DISABLED", "SERVICE_TRACKING_NO_ADDRESS", "SERVICE_TRACKING_LINK_LAPSED", "SERVICE_TRACKING_VERIFICATION_FAILED", "SERVICE_TRACKING_TOKEN_INVALID", "ADMIN_REQUIRED", "CUSTODY_ILLEGAL_TRANSITION", "SERVICE_ORDER_BILLABLE_FROZEN", "SERVICE_ORDER_EDIT_CHANGED", "SERVICE_ORDER_DISABLED_STATE_UNCHANGED", "APPOINTMENT_NOT_FOUND", "APPOINTMENT_NOT_LINKABLE", "APPOINTMENT_NOT_OPEN", "APPOINTMENT_CUSTOMER_MISMATCH", "APPOINTMENT_VERSION_CONFLICT", "SERVICE_ORDER_TERMINAL", "SERVICE_ORDER_DISABLED", "SERVICE_ORDER_ILLEGAL_TRANSITION", "SERVICE_ORDER_STATUS_CHANGED", "SERVICE_ORDER_OUTCOME_REQUIRED", "SERVICE_ORDER_OUTCOME_NOT_ALLOWED", "SERVICE_ORDER_CUSTODY_NOT_RECEIVED", "SERVICE_ORDER_BILLABLE_NOT_AT_THIS_TRANSITION", "PAYMENT_METHODS_NOT_CONFIGURED", "PAYMENT_METHOD_NOT_FOUND", "SERVICE_ORDER_DEPOSIT_CHANGED", "INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_REUSE", "IDEMPOTENCY_IN_PROGRESS", "IDEMPOTENCY_KEY_FAILED", "IDEMPOTENCY_LOCK_CONTENTION", "SERVICE_DEPOSIT_CURRENCY_MISMATCH", "SERVICE_DEPOSIT_INVALID", "INVALID_AMOUNT", "DEFAULT_DRAWER_UNAVAILABLE", "CURRENCY_MISMATCH", "SHIFT_NOT_OPEN", "SERVICE_BILLING_PRODUCTS_UNAVAILABLE", "CATEGORY_NOT_FOUND", "CATEGORY_DISABLED", "CATEGORY_DISABLE_IN_PROGRESS", "SERVICE_BILLING_PRODUCT_IN_BASKET", "SERVICE_DELIVERY_BASKET_DECISION_REQUIRED", "SERVICE_DELIVERY_BASKET_CURRENCY_MISMATCH", "PRODUCT_NOT_AVAILABLE", "PRODUCT_SALE_LIMIT_EXCEEDED", "PRODUCT_SALE_LIMIT_INVALID", "FX_CONVERSION_UNAVAILABLE", "TRANSACT_ITEMS_LIMIT_EXCEEDED", "SERVICE_LINE_PRICE_TOO_LARGE", "SERVICE_PARTS_RESTORE_EXCEEDS_CONSUMED", "SERVICE_PARTS_RESTORE_EXCEEDS_AMOUNT", "SERVICE_PARTS_STOCK_INSUFFICIENT", "SERVICE_PARTS_PRODUCT_NOT_FOUND", "SERVICE_PARTS_LEDGER_FULL", "SERVICE_PARTS_TOO_MANY", "SERVICE_ORDER_PARTS_CHANGED", "SERVICE_QUOTE_NOT_FOUND", "SERVICE_QUOTE_NOT_OFFERABLE", "SERVICE_QUOTE_HOLDS_TOO_MANY", "SERVICE_QUOTE_SUPERSEDED", "SERVICE_QUOTE_ALREADY_RESOLVED", "SERVICE_QUOTE_LAPSED", "SERVICE_QUOTE_CHANNEL_RESERVED", "SERVICE_QUOTE_CHANNEL_NOT_APPLICABLE", "SERVICE_QUOTE_REASON_NOT_APPLICABLE", "SERVICE_ORDER_QUOTE_CHANGED", "SERVICE_ORDER_NOT_READY", "SERVICE_RENOTIFY_IMPERSONATED", "SERVICE_RENOTIFY_TOO_SOON", "SERVICE_RENOTIFY_CONFLICT", "SERVICE_RENOTIFY_NOT_RECORDED", "SERVICE_RENOTIFY_LEDGER_UNREADABLE", "SERVICE_RENOTIFY_NOT_DISPATCHED", "SERVICE_TIMER_ALREADY_RUNNING", "SERVICE_TIMER_NOT_RUNNING", "SERVICE_TIMER_STOP_CONFLICT", "TECHNICIAN_NOT_CLOCKED_IN", "SERVICE_ORDER_GONE", "SERVICE_WORKLOG_NOT_FOUND", "SERVICE_WORKLOG_NOT_REVERSIBLE", "SERVICE_WORKLOG_ALREADY_REVERSED", "SERVICE_ORDER_WORKLOG_CHANGED", "CART_PROMOTION_UNAVAILABLE", "COUPON_NOT_FOUND", "COUPON_NOT_ACTIVE", "COUPON_EXPIRED", "COUPON_EXHAUSTED", "COUPON_EXHAUSTED_AT_CHECKOUT", "COUPON_MIN_SUBTOTAL", "COUPON_REQUIRES_CUSTOMER", "COUPON_BUDGET_EXHAUSTED", "COUPON_CURRENCY_MISMATCH"];
1535
1556
  export type ServiceOrderErrorCode = (typeof SERVICE_ORDER_ERROR_CODES)[number];
1536
1557
  export {};
@@ -302,6 +302,11 @@ export const SERVICE_ORDER_ERROR_CODES = [
302
302
  'SERVICE_TRACKING_DISABLED',
303
303
  'SERVICE_TRACKING_NO_ADDRESS',
304
304
  'SERVICE_TRACKING_LINK_LAPSED',
305
+ // The customer's online quote decision (`_customerDecision.ts`, shared by the
306
+ // web routes) — see `SERVICE_TRACKING_DECISION_ERROR_CODES`
307
+ 'SERVICE_TRACKING_VERIFICATION_FAILED',
308
+ // A tracking link revoked between the handle check and the decision's commit.
309
+ 'SERVICE_TRACKING_TOKEN_INVALID',
305
310
  'ADMIN_REQUIRED',
306
311
  'CUSTODY_ILLEGAL_TRANSITION',
307
312
  'SERVICE_ORDER_BILLABLE_FROZEN',
@@ -5,12 +5,18 @@
5
5
  *
6
6
  * The HANDLE is 16 base62 characters (~95 bits), stored server-side and a
7
7
  * bearer credential over exactly ONE service order of one store. It authorises
8
- * the read (`GET /services/tracking?handle=`) and the current presupuesto PDF
9
- * (`GET /services/tracking?handle=&mode=quote-pdf`), and nothing else. Every
8
+ * the read (`GET /services/tracking?handle=`), the current presupuesto PDF
9
+ * (`GET /services/tracking?handle=&mode=quote-pdf`) and the online decision on
10
+ * that presupuesto, and nothing else. Every
10
11
  * earlier link of an order dies when staff revoke it. A link stops reading
11
12
  * `serviceTrackingLinkDaysAfterClose` days (platform global, default 90) after
12
13
  * the order reached a terminal status.
13
14
  *
15
+ * With the gate on, the customer may also approve (with a 6-digit code sent to
16
+ * the contact on file) or reject the current presupuesto
17
+ * (`POST /services/tracking/quote`, or signed in
18
+ * `POST /user { mode: 'service-quote-decision' }`).
19
+ *
14
20
  * The gate is `ecommerce.config.serviceTracking` AND the `workshop` module on.
15
21
  * With the gate off, staff cannot mint and the signed-in reads refuse, but a
16
22
  * link minted earlier keeps reading (STORE_MODULES rule 4).
@@ -51,6 +57,41 @@ export type ServiceTrackingAccountErrorCode = (typeof SERVICE_TRACKING_ACCOUNT_E
51
57
  */
52
58
  export declare const SERVICE_TRACKING_OPERATOR_ERROR_CODES: readonly ["SERVICE_TRACKING_DISABLED", "SERVICE_TRACKING_NO_ADDRESS", "SERVICE_TRACKING_LINK_LAPSED", "ADMIN_REQUIRED"];
53
59
  export type ServiceTrackingOperatorErrorCode = (typeof SERVICE_TRACKING_OPERATOR_ERROR_CODES)[number];
60
+ /**
61
+ * The refusals of the customer's online quote decision — by link
62
+ * (`POST /services/tracking/code`, `POST /services/tracking/quote`) and signed in
63
+ * (`POST /user { mode: 'service-quote-decision' }`).
64
+ *
65
+ * - SERVICE_TRACKING_TOKEN_INVALID (401, link only): the handle cannot be
66
+ * honoured — the same one body as the read.
67
+ * - SERVICE_TRACKING_VERIFICATION_FAILED (401, link approve only): no code, a
68
+ * wrong, used, expired or burnt one, or a challenge minted for another order.
69
+ * One body for every reason.
70
+ * - SERVICE_TRACKING_DISABLED (403): the store's gate is off. The read keeps working.
71
+ * - SERVICE_ORDER_NOT_FOUND (404, signed in only): no such order, a disabled
72
+ * one, or another customer's — one body.
73
+ * - SERVICE_QUOTE_NOT_FOUND (404): no version with that `quoteId`.
74
+ * - SERVICE_QUOTE_SUPERSEDED (409): a newer version replaced it.
75
+ * - SERVICE_QUOTE_ALREADY_RESOLVED (409): it already carries the OTHER decision
76
+ * (the same decision again answers 200 with the current view and writes nothing).
77
+ * - SERVICE_QUOTE_LAPSED (409, approve only): past `expiresAt`. A rejection is
78
+ * still accepted.
79
+ * - SERVICE_ORDER_QUOTE_CHANGED (409): the order moved under the decision — retry.
80
+ * - SERVICE_ORDER_TERMINAL / SERVICE_ORDER_DISABLED (409): the ticket is closed
81
+ * or deleted.
82
+ * - SERVICE_QUOTE_HOLDS_TOO_MANY (400, approve only): the version's parts cannot
83
+ * be reserved in one write; the shop must re-quote.
84
+ */
85
+ export declare const SERVICE_TRACKING_DECISION_ERROR_CODES: readonly ["SERVICE_TRACKING_TOKEN_INVALID", "SERVICE_TRACKING_VERIFICATION_FAILED", "SERVICE_TRACKING_DISABLED", "SERVICE_ORDER_NOT_FOUND", "SERVICE_QUOTE_NOT_FOUND", "SERVICE_QUOTE_SUPERSEDED", "SERVICE_QUOTE_ALREADY_RESOLVED", "SERVICE_QUOTE_LAPSED", "SERVICE_ORDER_QUOTE_CHANGED", "SERVICE_ORDER_TERMINAL", "SERVICE_ORDER_DISABLED", "SERVICE_QUOTE_HOLDS_TOO_MANY"];
86
+ export type ServiceTrackingDecisionErrorCode = (typeof SERVICE_TRACKING_DECISION_ERROR_CODES)[number];
87
+ /** Where the 6-digit decision code is sent: the phone or the email on file. */
88
+ export declare const SERVICE_TRACKING_CODE_VIAS: readonly ["sms", "email"];
89
+ /** The customer's two online answers to a presupuesto. */
90
+ export declare const SERVICE_TRACKING_QUOTE_DECISIONS: readonly ["approve", "reject"];
91
+ /** A decision challenge handle: 16 random bytes, base64url (22 characters). */
92
+ export declare const SERVICE_TRACKING_CHALLENGE_HANDLE_RE: RegExp;
93
+ /** The decision code: 6 digits. */
94
+ export declare const SERVICE_TRACKING_CODE_RE: RegExp;
54
95
  /** `POST /services { mode: 'trackingLink' }` actions. */
55
96
  export declare const SERVICE_TRACKING_LINK_ACTIONS: readonly ["mint", "revoke"];
56
97
  /** The storefront path a tracking link opens: `/servicio/{handle}`. */
@@ -148,11 +189,73 @@ declare global {
148
189
  }
149
190
  /** Whether the customer may decide the presupuesto online. */
150
191
  interface ServiceTrackingDecision {
151
- /** `false` until the online decision ships. */
192
+ /**
193
+ * `true` when the store's gate is on, the order is open and not deleted,
194
+ * and the current presupuesto is `sent` (awaiting an answer). A lapsed one
195
+ * stays decidable: it can still be rejected (an approval answers
196
+ * `409 SERVICE_QUOTE_LAPSED`).
197
+ */
152
198
  canDecide: boolean;
153
- /** Whether an approval needs the 6-digit code sent to the contact on file. */
199
+ /**
200
+ * Whether an approval needs the 6-digit code sent to the contact on file:
201
+ * `true` on the link, `false` signed in (the session is the proof).
202
+ */
154
203
  approveNeedsCode: boolean;
155
204
  }
205
+ type ServiceTrackingCodeVia = (typeof SERVICE_TRACKING_CODE_VIAS)[number];
206
+ type ServiceTrackingQuoteDecision = (typeof SERVICE_TRACKING_QUOTE_DECISIONS)[number];
207
+ /**
208
+ * `POST /services/tracking/code` body (strict): send the 6-digit decision code
209
+ * to the phone (`sms`) or the email ON FILE for the order's customer — never
210
+ * to an address in the request. Always `202 { challengeHandle }` once the
211
+ * handle is honoured and the gate is on, whether or not a code could be sent
212
+ * (no contact for that channel, a spent daily budget): the page offers only
213
+ * the channels whose masked contact it shows. The code lives
214
+ * `CUSTOMER_OTP_LIMITS.ttlSeconds` and takes `maxAttempts` tries.
215
+ */
216
+ interface ServiceTrackingCodeRequest {
217
+ handle: string;
218
+ via: ServiceTrackingCodeVia;
219
+ }
220
+ /** `POST /services/tracking/code` `data`. */
221
+ interface ServiceTrackingCodeResult {
222
+ /** 22 characters; sent back with the code on the approval. */
223
+ challengeHandle: string;
224
+ }
225
+ /**
226
+ * `POST /services/tracking/quote` body (strict): the customer's answer to the
227
+ * current presupuesto, by link. `quoteId` plus the order's own state are the
228
+ * concurrency check — there is no `version`.
229
+ *
230
+ * - `approve` needs `challengeHandle` + `code` (from `POST /services/tracking/code`);
231
+ * `reason` is refused.
232
+ * - `reject` needs `reason` only, and is accepted past `expiresAt`.
233
+ *
234
+ * Answers `200` with the current `ServiceTrackingView`. The same decision again
235
+ * on the same `quoteId` answers `200` with the view and writes nothing. The
236
+ * decision does NOT move the order's stage; the shop does.
237
+ */
238
+ interface ServiceTrackingQuoteDecisionRequest {
239
+ handle: string;
240
+ quoteId: string;
241
+ decision: ServiceTrackingQuoteDecision;
242
+ reason?: ServiceQuoteRejectionReason;
243
+ challengeHandle?: string;
244
+ code?: string;
245
+ }
246
+ /**
247
+ * `POST /user { mode: 'service-quote-decision' }` body: the same answer by the
248
+ * signed-in owner of the order (`verifiedBy: 'session'`, no code). Another
249
+ * customer's `serviceOrderId` answers the same `404 SERVICE_ORDER_NOT_FOUND`
250
+ * as a missing one. Answers `200` with the current `ServiceTrackingView`.
251
+ */
252
+ interface ServiceQuoteDecisionAccountRequest {
253
+ mode: 'service-quote-decision';
254
+ serviceOrderId: string;
255
+ quoteId: string;
256
+ decision: ServiceTrackingQuoteDecision;
257
+ reason?: ServiceQuoteRejectionReason;
258
+ }
156
259
  /** The customer's contact as the shop holds it, masked. */
157
260
  interface ServiceTrackingContact {
158
261
  firstName: string;
@@ -196,6 +299,10 @@ declare global {
196
299
  contact: ServiceTrackingContact;
197
300
  store: ServiceTrackingStore;
198
301
  }
302
+ /** What a `SERVICE_QUOTE_DECIDED` operator bell points at. */
303
+ interface ServiceOrderRef {
304
+ serviceOrderId: string;
305
+ }
199
306
  /** One row of `GET /user?mode=service-orders`. */
200
307
  interface ServiceTrackingAccountListItem {
201
308
  serviceOrderId: string;
@@ -5,12 +5,18 @@
5
5
  *
6
6
  * The HANDLE is 16 base62 characters (~95 bits), stored server-side and a
7
7
  * bearer credential over exactly ONE service order of one store. It authorises
8
- * the read (`GET /services/tracking?handle=`) and the current presupuesto PDF
9
- * (`GET /services/tracking?handle=&mode=quote-pdf`), and nothing else. Every
8
+ * the read (`GET /services/tracking?handle=`), the current presupuesto PDF
9
+ * (`GET /services/tracking?handle=&mode=quote-pdf`) and the online decision on
10
+ * that presupuesto, and nothing else. Every
10
11
  * earlier link of an order dies when staff revoke it. A link stops reading
11
12
  * `serviceTrackingLinkDaysAfterClose` days (platform global, default 90) after
12
13
  * the order reached a terminal status.
13
14
  *
15
+ * With the gate on, the customer may also approve (with a 6-digit code sent to
16
+ * the contact on file) or reject the current presupuesto
17
+ * (`POST /services/tracking/quote`, or signed in
18
+ * `POST /user { mode: 'service-quote-decision' }`).
19
+ *
14
20
  * The gate is `ecommerce.config.serviceTracking` AND the `workshop` module on.
15
21
  * With the gate off, staff cannot mint and the signed-in reads refuse, but a
16
22
  * link minted earlier keeps reading (STORE_MODULES rule 4).
@@ -53,6 +59,53 @@ export const SERVICE_TRACKING_OPERATOR_ERROR_CODES = [
53
59
  'SERVICE_TRACKING_LINK_LAPSED',
54
60
  'ADMIN_REQUIRED',
55
61
  ];
62
+ /**
63
+ * The refusals of the customer's online quote decision — by link
64
+ * (`POST /services/tracking/code`, `POST /services/tracking/quote`) and signed in
65
+ * (`POST /user { mode: 'service-quote-decision' }`).
66
+ *
67
+ * - SERVICE_TRACKING_TOKEN_INVALID (401, link only): the handle cannot be
68
+ * honoured — the same one body as the read.
69
+ * - SERVICE_TRACKING_VERIFICATION_FAILED (401, link approve only): no code, a
70
+ * wrong, used, expired or burnt one, or a challenge minted for another order.
71
+ * One body for every reason.
72
+ * - SERVICE_TRACKING_DISABLED (403): the store's gate is off. The read keeps working.
73
+ * - SERVICE_ORDER_NOT_FOUND (404, signed in only): no such order, a disabled
74
+ * one, or another customer's — one body.
75
+ * - SERVICE_QUOTE_NOT_FOUND (404): no version with that `quoteId`.
76
+ * - SERVICE_QUOTE_SUPERSEDED (409): a newer version replaced it.
77
+ * - SERVICE_QUOTE_ALREADY_RESOLVED (409): it already carries the OTHER decision
78
+ * (the same decision again answers 200 with the current view and writes nothing).
79
+ * - SERVICE_QUOTE_LAPSED (409, approve only): past `expiresAt`. A rejection is
80
+ * still accepted.
81
+ * - SERVICE_ORDER_QUOTE_CHANGED (409): the order moved under the decision — retry.
82
+ * - SERVICE_ORDER_TERMINAL / SERVICE_ORDER_DISABLED (409): the ticket is closed
83
+ * or deleted.
84
+ * - SERVICE_QUOTE_HOLDS_TOO_MANY (400, approve only): the version's parts cannot
85
+ * be reserved in one write; the shop must re-quote.
86
+ */
87
+ export const SERVICE_TRACKING_DECISION_ERROR_CODES = [
88
+ 'SERVICE_TRACKING_TOKEN_INVALID',
89
+ 'SERVICE_TRACKING_VERIFICATION_FAILED',
90
+ 'SERVICE_TRACKING_DISABLED',
91
+ 'SERVICE_ORDER_NOT_FOUND',
92
+ 'SERVICE_QUOTE_NOT_FOUND',
93
+ 'SERVICE_QUOTE_SUPERSEDED',
94
+ 'SERVICE_QUOTE_ALREADY_RESOLVED',
95
+ 'SERVICE_QUOTE_LAPSED',
96
+ 'SERVICE_ORDER_QUOTE_CHANGED',
97
+ 'SERVICE_ORDER_TERMINAL',
98
+ 'SERVICE_ORDER_DISABLED',
99
+ 'SERVICE_QUOTE_HOLDS_TOO_MANY',
100
+ ];
101
+ /** Where the 6-digit decision code is sent: the phone or the email on file. */
102
+ export const SERVICE_TRACKING_CODE_VIAS = ['sms', 'email'];
103
+ /** The customer's two online answers to a presupuesto. */
104
+ export const SERVICE_TRACKING_QUOTE_DECISIONS = ['approve', 'reject'];
105
+ /** A decision challenge handle: 16 random bytes, base64url (22 characters). */
106
+ export const SERVICE_TRACKING_CHALLENGE_HANDLE_RE = /^[\w-]{22}$/;
107
+ /** The decision code: 6 digits. */
108
+ export const SERVICE_TRACKING_CODE_RE = /^\d{6}$/;
56
109
  /** `POST /services { mode: 'trackingLink' }` actions. */
57
110
  export const SERVICE_TRACKING_LINK_ACTIONS = ['mint', 'revoke'];
58
111
  /** The storefront path a tracking link opens: `/servicio/{handle}`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.854",
3
+ "version": "1.10.855",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",