@pisell/private-materials 6.12.200 → 6.12.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/build/lowcode/assets-daily.json +11 -11
  2. package/build/lowcode/assets-dev.json +2 -2
  3. package/build/lowcode/assets-prod.json +11 -11
  4. package/build/lowcode/meta.js +1 -1
  5. package/build/lowcode/render/default/view.css +1 -1
  6. package/build/lowcode/render/default/view.js +10 -10
  7. package/build/lowcode/view.css +1 -1
  8. package/build/lowcode/view.js +10 -10
  9. package/es/components/HDLKiosk/index.js +9 -6
  10. package/es/components/HDLKiosk/locales.js +15 -0
  11. package/es/components/HDLKiosk/runtime/entryPreparation.js +48 -0
  12. package/es/components/HDLKiosk/runtime/session/createSession.js +46 -2
  13. package/es/components/HDLKiosk/slices/customization/Screen/index.js +25 -2
  14. package/es/components/HDLKiosk/ui/controllers/useHDLKioskSessionController.js +50 -9
  15. package/es/components/HDLKiosk/ui/entry/Screen/index.js +5 -2
  16. package/es/components/HDLKiosk/ui/entry/Screen/index.less +10 -0
  17. package/es/components/bigSale/templates/HDLKiosk/index.js +3 -1
  18. package/es/components/bigSale/templates/HDLKiosk/payment/HDLKioskPaymentRuntime.js +163 -25
  19. package/lib/components/HDLKiosk/index.js +9 -6
  20. package/lib/components/HDLKiosk/locales.js +15 -0
  21. package/lib/components/HDLKiosk/runtime/entryPreparation.js +48 -0
  22. package/lib/components/HDLKiosk/runtime/session/createSession.js +46 -2
  23. package/lib/components/HDLKiosk/slices/customization/Screen/index.js +25 -2
  24. package/lib/components/HDLKiosk/ui/controllers/useHDLKioskSessionController.js +49 -8
  25. package/lib/components/HDLKiosk/ui/entry/Screen/index.js +5 -2
  26. package/lib/components/HDLKiosk/ui/entry/Screen/index.less +10 -0
  27. package/lib/components/bigSale/templates/HDLKiosk/index.js +3 -1
  28. package/lib/components/bigSale/templates/HDLKiosk/payment/HDLKioskPaymentRuntime.js +162 -24
  29. package/package.json +3 -3
@@ -57,10 +57,11 @@ function HDLKioskContent({ host, config, isActive = true, entryDataPreparing = f
57
57
  setActiveLocale(nextLocale);
58
58
  onLocaleChange === null || onLocaleChange === void 0 || onLocaleChange(nextLocale);
59
59
  }, [onLocaleChange]);
60
- const { session, starting, startErrorReference, exiting, exitError, pendingExit, start, back, requestExit, resolveExit, completeOrder, recover } = useHDLKioskSessionController({
60
+ const { session, starting, preparingEntry, entryPreparationErrorReference, startErrorReference, exiting, exitError, pendingExit, start, back, requestExit, resolveExit, completeOrder, recover, retryEntryPreparation } = useHDLKioskSessionController({
61
61
  host,
62
62
  config,
63
- isActive
63
+ isActive,
64
+ entryPreparationBlocked: entryDataPreparing || sales.hostStatus === "loading"
64
65
  });
65
66
  const observeExit = useCallback((result) => {
66
67
  if (result.status === "exited") onPaymentResultReset === null || onPaymentResultReset === void 0 || onPaymentResultReset();
@@ -167,10 +168,12 @@ function HDLKioskContent({ host, config, isActive = true, entryDataPreparing = f
167
168
  start({ entry });
168
169
  }
169
170
  },
170
- busy: starting,
171
- errorReference: startErrorReference,
172
- errorMessage: locales.getText("pisell2.hdl-kiosk.session-start-failed")
173
- }), starting ? /* @__PURE__ */ React.createElement("div", {
171
+ busy: starting || preparingEntry || Boolean(entryPreparationErrorReference),
172
+ errorReference: entryPreparationErrorReference || startErrorReference,
173
+ errorMessage: locales.getText(entryPreparationErrorReference ? "pisell2.hdl-kiosk.reset-failed-description" : "pisell2.hdl-kiosk.session-start-failed"),
174
+ retryLabel: entryPreparationErrorReference ? locales.getText("pisell2.hdl-kiosk.retry-entry-preparation") : void 0,
175
+ onRetry: entryPreparationErrorReference ? retryEntryPreparation : void 0
176
+ }), starting || preparingEntry ? /* @__PURE__ */ React.createElement("div", {
174
177
  className: "hdl-operation-overlay",
175
178
  role: "status"
176
179
  }, /* @__PURE__ */ React.createElement("span", { className: "hdl-operation-overlay__spinner" }), /* @__PURE__ */ React.createElement("strong", null, locales.getText("pisell2.hdl-kiosk.working"))) : null));
@@ -45,6 +45,7 @@ const localeTexts = {
45
45
  "pisell2.hdl-kiosk.payment-conflict-title": "Payment needs attention",
46
46
  "pisell2.hdl-kiosk.payment-conflict-description": "Please do not pay again. Ask a staff member to check your payment.",
47
47
  "pisell2.hdl-kiosk.resetting-order": "Preparing next order…",
48
+ "pisell2.hdl-kiosk.retry-entry-preparation": "Retry preparation",
48
49
  "pisell2.hdl-kiosk.order-needs-attention": "The previous action did not complete. Review the order before continuing.",
49
50
  "pisell2.hdl-kiosk.fulfillment-unavailable": "This device cannot save the dining option yet. Please ask a staff member for help.",
50
51
  "pisell2.hdl-kiosk.keep-ordering": "Keep ordering",
@@ -86,6 +87,8 @@ const localeTexts = {
86
87
  "pisell2.hdl-kiosk.customization-unit-price": "Unit price",
87
88
  "pisell2.hdl-kiosk.customization-per100g": "/100g",
88
89
  "pisell2.hdl-kiosk.customization-next": "Next",
90
+ "pisell2.hdl-kiosk.customization-required-options": "Please select an option for every required group.",
91
+ "pisell2.hdl-kiosk.customization-required-group": (groupName) => `Please select an option in “${groupName}”.`,
89
92
  "pisell2.hdl-kiosk.primary-product-prompt-eyebrow": "Before Checkout",
90
93
  "pisell2.hdl-kiosk.primary-product-prompt-title": "Would you like to add a Malatang bowl?",
91
94
  "pisell2.hdl-kiosk.primary-product-prompt-accept": "Yes",
@@ -202,6 +205,7 @@ const localeTexts = {
202
205
  "pisell2.hdl-kiosk.payment-conflict-title": "请确认支付状态",
203
206
  "pisell2.hdl-kiosk.payment-conflict-description": "请勿重复付款,并联系工作人员确认。",
204
207
  "pisell2.hdl-kiosk.resetting-order": "正在准备下一单…",
208
+ "pisell2.hdl-kiosk.retry-entry-preparation": "重新准备",
205
209
  "pisell2.hdl-kiosk.order-needs-attention": "上一步未完成,请核对订单后再继续。",
206
210
  "pisell2.hdl-kiosk.fulfillment-unavailable": "当前设备尚未接入用餐方式写入能力,请联系工作人员处理。",
207
211
  "pisell2.hdl-kiosk.keep-ordering": "继续点餐",
@@ -243,6 +247,8 @@ const localeTexts = {
243
247
  "pisell2.hdl-kiosk.customization-unit-price": "单价",
244
248
  "pisell2.hdl-kiosk.customization-per100g": "/100克",
245
249
  "pisell2.hdl-kiosk.customization-next": "下一步",
250
+ "pisell2.hdl-kiosk.customization-required-options": "请完成所有必选规格的选择。",
251
+ "pisell2.hdl-kiosk.customization-required-group": (groupName) => `请选择「${groupName}」中的必选项。`,
246
252
  "pisell2.hdl-kiosk.primary-product-prompt-eyebrow": "结账前确认",
247
253
  "pisell2.hdl-kiosk.primary-product-prompt-title": "需要添加一份麻辣烫吗?",
248
254
  "pisell2.hdl-kiosk.primary-product-prompt-accept": "需要",
@@ -359,6 +365,7 @@ const localeTexts = {
359
365
  "pisell2.hdl-kiosk.payment-conflict-title": "請確認付款狀態",
360
366
  "pisell2.hdl-kiosk.payment-conflict-description": "請勿重複付款,並聯絡工作人員確認。",
361
367
  "pisell2.hdl-kiosk.resetting-order": "正在準備下一單…",
368
+ "pisell2.hdl-kiosk.retry-entry-preparation": "重新準備",
362
369
  "pisell2.hdl-kiosk.order-needs-attention": "上一步未完成,請核對訂單後再繼續。",
363
370
  "pisell2.hdl-kiosk.fulfillment-unavailable": "當前設備尚未接入用餐方式寫入能力,請聯絡工作人員處理。",
364
371
  "pisell2.hdl-kiosk.keep-ordering": "繼續點餐",
@@ -400,6 +407,8 @@ const localeTexts = {
400
407
  "pisell2.hdl-kiosk.customization-unit-price": "單價",
401
408
  "pisell2.hdl-kiosk.customization-per100g": "/100克",
402
409
  "pisell2.hdl-kiosk.customization-next": "下一步",
410
+ "pisell2.hdl-kiosk.customization-required-options": "請完成所有必選規格的選擇。",
411
+ "pisell2.hdl-kiosk.customization-required-group": (groupName) => `請選擇「${groupName}」中的必選項。`,
403
412
  "pisell2.hdl-kiosk.primary-product-prompt-eyebrow": "結帳前確認",
404
413
  "pisell2.hdl-kiosk.primary-product-prompt-title": "需要加入一份麻辣燙嗎?",
405
414
  "pisell2.hdl-kiosk.primary-product-prompt-accept": "需要",
@@ -516,6 +525,7 @@ const localeTexts = {
516
525
  "pisell2.hdl-kiosk.payment-conflict-title": "お支払い状況をご確認ください",
517
526
  "pisell2.hdl-kiosk.payment-conflict-description": "再度支払わず、スタッフにお声がけください。",
518
527
  "pisell2.hdl-kiosk.resetting-order": "次の注文を準備中…",
528
+ "pisell2.hdl-kiosk.retry-entry-preparation": "もう一度準備する",
519
529
  "pisell2.hdl-kiosk.order-needs-attention": "前の操作が完了していません。注文を確認してから続けてください。",
520
530
  "pisell2.hdl-kiosk.fulfillment-unavailable": "この端末では現在、飲食方法を保存できません。スタッフにお声がけください。",
521
531
  "pisell2.hdl-kiosk.keep-ordering": "注文を続ける",
@@ -557,6 +567,8 @@ const localeTexts = {
557
567
  "pisell2.hdl-kiosk.customization-unit-price": "単価",
558
568
  "pisell2.hdl-kiosk.customization-per100g": "/100g",
559
569
  "pisell2.hdl-kiosk.customization-next": "次へ",
570
+ "pisell2.hdl-kiosk.customization-required-options": "必須項目をすべて選択してください。",
571
+ "pisell2.hdl-kiosk.customization-required-group": (groupName) => `「${groupName}」の必須項目を選択してください。`,
560
572
  "pisell2.hdl-kiosk.primary-product-prompt-eyebrow": "お会計前の確認",
561
573
  "pisell2.hdl-kiosk.primary-product-prompt-title": "麻辣湯を1杯追加しますか?",
562
574
  "pisell2.hdl-kiosk.primary-product-prompt-accept": "はい",
@@ -673,6 +685,7 @@ const localeTexts = {
673
685
  "pisell2.hdl-kiosk.payment-conflict-title": "Confirme o estado do pagamento",
674
686
  "pisell2.hdl-kiosk.payment-conflict-description": "Não pague novamente. Peça ajuda a um funcionário.",
675
687
  "pisell2.hdl-kiosk.resetting-order": "A preparar o próximo pedido…",
688
+ "pisell2.hdl-kiosk.retry-entry-preparation": "Tentar preparar novamente",
676
689
  "pisell2.hdl-kiosk.order-needs-attention": "A ação anterior não foi concluída. Verifique o pedido antes de continuar.",
677
690
  "pisell2.hdl-kiosk.fulfillment-unavailable": "Este dispositivo ainda não consegue guardar a opção de consumo. Peça ajuda a um funcionário.",
678
691
  "pisell2.hdl-kiosk.keep-ordering": "Continuar a pedir",
@@ -714,6 +727,8 @@ const localeTexts = {
714
727
  "pisell2.hdl-kiosk.customization-unit-price": "Preço unitário",
715
728
  "pisell2.hdl-kiosk.customization-per100g": "/100 g",
716
729
  "pisell2.hdl-kiosk.customization-next": "Seguinte",
730
+ "pisell2.hdl-kiosk.customization-required-options": "Selecione uma opção em cada grupo obrigatório.",
731
+ "pisell2.hdl-kiosk.customization-required-group": (groupName) => `Selecione uma opção obrigatória em “${groupName}”.`,
717
732
  "pisell2.hdl-kiosk.primary-product-prompt-eyebrow": "Antes de finalizar",
718
733
  "pisell2.hdl-kiosk.primary-product-prompt-title": "Deseja adicionar uma tigela de Malatang?",
719
734
  "pisell2.hdl-kiosk.primary-product-prompt-accept": "Sim",
@@ -0,0 +1,48 @@
1
+ import { _asyncToGenerator } from "../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/asyncToGenerator.js";
2
+ import { createOperationError, operationUnknown } from "./operations/errors.js";
3
+ //#region src/components/HDLKiosk/runtime/entryPreparation.ts
4
+ function forwardFailure(result) {
5
+ return result;
6
+ }
7
+ /** Entry 首次展示或重新激活时,必须先证明 Host 已不存在上一笔购物车。 */
8
+ function prepareHDLKioskEntry(_x, _x2) {
9
+ return _prepareHDLKioskEntry.apply(this, arguments);
10
+ }
11
+ function _prepareHDLKioskEntry() {
12
+ _prepareHDLKioskEntry = _asyncToGenerator(function* (host, signal) {
13
+ let result;
14
+ try {
15
+ const payment = host.sales.readTransactionState().payment;
16
+ if (payment.status === "pending" || payment.status === "unknown") result = operationUnknown("entryPreparationBlockedByPaymentState");
17
+ else if (signal.aborted) result = {
18
+ status: "cancelled",
19
+ reason: "entryPreparationAborted"
20
+ };
21
+ else {
22
+ const reset = yield host.sales.clearAndReset(signal);
23
+ if (reset.status !== "succeeded") result = forwardFailure(reset);
24
+ else if (!reset.data.cartEmpty) result = operationUnknown("entryPreparationHostResultNotEmpty");
25
+ else if (signal.aborted) result = {
26
+ status: "cancelled",
27
+ reason: "entryPreparationAborted"
28
+ };
29
+ else {
30
+ const consistency = yield host.sales.waitForCartConsistency(signal);
31
+ result = consistency.status === "succeeded" ? {
32
+ status: "succeeded",
33
+ data: void 0
34
+ } : forwardFailure(consistency);
35
+ }
36
+ }
37
+ } catch (cause) {
38
+ result = {
39
+ status: "failed",
40
+ error: createOperationError("entryPreparationUnhandled", "staff", cause)
41
+ };
42
+ }
43
+ return result;
44
+ });
45
+ return _prepareHDLKioskEntry.apply(this, arguments);
46
+ }
47
+ //#endregion
48
+ export { prepareHDLKioskEntry };
@@ -61,9 +61,30 @@ function createHDLKioskWorkflowSession(entry, host, options = {}) {
61
61
  let staffPaymentPending = false;
62
62
  let staffPaymentConfirmed = false;
63
63
  let staffPaymentRejected = false;
64
+ let staffPaymentObserved = false;
65
+ let staffPaymentAdvanceDispatched = false;
66
+ let lastStaffPaymentAdvanceDiagnostic = "";
64
67
  let completedOrderFinalized = false;
65
68
  let completedOrderFinalizePromise;
66
69
  const staffPaymentReceipt = Object.freeze({ paymentConfirmed: true });
70
+ const emitStaffPaymentWorkflow = (phase, authorizationStatus, reason) => {
71
+ const snapshot = workflowActor.getSnapshot();
72
+ try {
73
+ host.telemetry.emit({
74
+ name: "hdlKiosk.staffPayment.workflow",
75
+ traceId: `${snapshot.workflowId}:${snapshot.sessionEpoch}:staff-payment`,
76
+ occurredAt: Date.now(),
77
+ attributes: _objectSpread2({
78
+ phase,
79
+ authorizationStatus,
80
+ workflowId: snapshot.workflowId,
81
+ sessionEpoch: snapshot.sessionEpoch,
82
+ nodeId: snapshot.nodeId,
83
+ workflowStatus: snapshot.status
84
+ }, reason ? { reason } : {})
85
+ });
86
+ } catch (_unused) {}
87
+ };
67
88
  const dispatchConfirmedPayment = (receipt, intentId) => {
68
89
  var _definition$nodes$sna;
69
90
  const snapshot = workflowActor.getSnapshot();
@@ -79,8 +100,26 @@ function createHDLKioskWorkflowSession(entry, host, options = {}) {
79
100
  };
80
101
  const advanceConfirmedStaffPayment = () => {
81
102
  var _definition$nodes$sna2;
103
+ if (!staffPaymentObserved || staffPaymentPending || staffPaymentAdvanceDispatched) return;
82
104
  const snapshot = workflowActor.getSnapshot();
83
- if (snapshot.status !== "idle" || ((_definition$nodes$sna2 = definition.nodes[snapshot.nodeId]) === null || _definition$nodes$sna2 === void 0 ? void 0 : _definition$nodes$sna2.activity) !== "paymentMethod" || staffPaymentRejected || staffPaymentPending && !staffPaymentConfirmed || !staffPaymentConfirmed) return;
105
+ if (staffPaymentRejected || !staffPaymentConfirmed) return;
106
+ const skipReason = snapshot.status !== "idle" ? "workflow-not-idle" : ((_definition$nodes$sna2 = definition.nodes[snapshot.nodeId]) === null || _definition$nodes$sna2 === void 0 ? void 0 : _definition$nodes$sna2.activity) !== "paymentMethod" ? "not-payment-method" : void 0;
107
+ if (skipReason) {
108
+ const diagnostic = [
109
+ snapshot.sessionEpoch,
110
+ snapshot.nodeId,
111
+ snapshot.status,
112
+ skipReason
113
+ ].join(":");
114
+ if (diagnostic !== lastStaffPaymentAdvanceDiagnostic) {
115
+ lastStaffPaymentAdvanceDiagnostic = diagnostic;
116
+ emitStaffPaymentWorkflow("advanceSkipped", "succeeded", skipReason);
117
+ }
118
+ return;
119
+ }
120
+ staffPaymentAdvanceDispatched = true;
121
+ lastStaffPaymentAdvanceDiagnostic = `${snapshot.sessionEpoch}:advanced`;
122
+ emitStaffPaymentWorkflow("advanced", "succeeded");
84
123
  dispatchConfirmedPayment(staffPaymentReceipt, `staff-payment:${snapshot.sessionEpoch}`);
85
124
  };
86
125
  const finalizeCompletedOrder = (intentId) => {
@@ -115,15 +154,20 @@ function createHDLKioskWorkflowSession(entry, host, options = {}) {
115
154
  try {
116
155
  unsubscribePaymentWorkflow = workflowActor.subscribe(handleWorkflowUpdate);
117
156
  unsubscribeStaffPayment = host.sales.subscribeStaffPaymentAuthorizationResult((result) => {
157
+ staffPaymentObserved = true;
118
158
  if (result.status === "pending") {
119
159
  staffPaymentPending = true;
120
160
  staffPaymentConfirmed = false;
121
161
  staffPaymentRejected = false;
162
+ staffPaymentAdvanceDispatched = false;
163
+ lastStaffPaymentAdvanceDiagnostic = "";
164
+ emitStaffPaymentWorkflow("pending", result.status);
122
165
  return;
123
166
  }
124
167
  staffPaymentPending = false;
125
168
  staffPaymentConfirmed = result.status === "succeeded";
126
169
  staffPaymentRejected = !staffPaymentConfirmed;
170
+ emitStaffPaymentWorkflow(staffPaymentConfirmed ? "confirmed" : "rejected", result.status);
127
171
  if (staffPaymentConfirmed) coordinator.confirmPaymentSucceeded();
128
172
  advanceConfirmedStaffPayment();
129
173
  });
@@ -153,7 +197,7 @@ function createHDLKioskWorkflowSession(entry, host, options = {}) {
153
197
  sessionEpoch: snapshot.sessionEpoch
154
198
  }
155
199
  });
156
- } catch (_unused) {}
200
+ } catch (_unused2) {}
157
201
  const disposeSession = () => {
158
202
  if (disposed) return;
159
203
  disposed = true;
@@ -1,5 +1,6 @@
1
1
  import { _objectSpread2 } from "../../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/objectSpread2.js";
2
2
  import { _asyncToGenerator } from "../../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/asyncToGenerator.js";
3
+ import { resolveLocalizedText } from "../../../../../plus/salesSdk/utils/resolveLocalizedText.js";
3
4
  import { useHDLKioskBottomBar, useHDLKioskBottomBarController } from "../../../components/shell/HDLKioskBottomBarHost/index.js";
4
5
  import { HDLKioskOrderSummary } from "../../../components/shell/HDLKioskOrderSummary/index.js";
5
6
  import { createHDLKioskSkuValue } from "../../../components/common/HDLKioskSkuOptionsSelection/model.js";
@@ -9,6 +10,7 @@ import { useHDLScaleRuntime, useHDLScaleSnapshot } from "../../../ui/scale.js";
9
10
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
11
  import { STANDARD_WEIGHT_UNIT, formatAmount, getWeightUnitSymbol, locales } from "@pisell/utils";
11
12
  import Decimal from "decimal.js";
13
+ import { useToast } from "@pisell/materials";
12
14
  //#region src/components/HDLKiosk/slices/customization/Screen/index.tsx
13
15
  /**
14
16
  * 文件角色:呈现主称重商品的本地重量与标准 SkuValue 草稿。
@@ -47,6 +49,22 @@ function formatMoney(value, currencySymbol) {
47
49
  const amount = value.toDecimalPlaces(2).toNumber();
48
50
  return String(formatAmount(amount, 2, currencySymbol || ""));
49
51
  }
52
+ function resolveInvalidSkuGroupName(error, product) {
53
+ var _group$name;
54
+ if (!error || typeof error !== "object" || Array.isArray(error) || !product) return;
55
+ const firstInvalidGroupId = Object.keys(error)[0];
56
+ if (!firstInvalidGroupId) return void 0;
57
+ const source = product;
58
+ const group = [
59
+ "variant_group",
60
+ "option_group",
61
+ "bundle_group"
62
+ ].flatMap((key) => Array.isArray(source[key]) ? source[key] : []).find((candidate) => {
63
+ var _candidate$id;
64
+ return String((_candidate$id = candidate === null || candidate === void 0 ? void 0 : candidate.id) !== null && _candidate$id !== void 0 ? _candidate$id : "") === firstInvalidGroupId;
65
+ });
66
+ return resolveLocalizedText((_group$name = group === null || group === void 0 ? void 0 : group.name) !== null && _group$name !== void 0 ? _group$name : group === null || group === void 0 ? void 0 : group.title) || void 0;
67
+ }
50
68
  function resolveScaleMeasurement(scale, productFacts, minimumWeightKg) {
51
69
  var _scale$grossWeightKg, _productFacts$fixedTa, _productFacts$fixedTa2;
52
70
  const gross = new Decimal((_scale$grossWeightKg = scale.grossWeightKg) !== null && _scale$grossWeightKg !== void 0 ? _scale$grossWeightKg : NaN);
@@ -148,6 +166,7 @@ function ProductFallback() {
148
166
  }
149
167
  function CustomizationScreen({ product, loading, errorReference, errorMessage, busy, syncing, canGoBack, minimumWeightKg, orderBar, send }) {
150
168
  var _productFacts$product, _orderBar$primaryLine, _orderBar$primaryLine2, _orderBar$primaryLine3, _orderBar$primaryLine4, _orderBar$primaryLine7, _productFacts$unitPri, _orderBar$primaryLine9, _orderBar$primaryLine11;
169
+ const Toast = useToast();
151
170
  const scaleRuntime = useHDLScaleRuntime();
152
171
  const scale = useHDLScaleSnapshot();
153
172
  const skuOptionsRef = useRef(null);
@@ -187,7 +206,9 @@ function CustomizationScreen({ product, loading, errorReference, errorMessage, b
187
206
  try {
188
207
  var _skuOptionsRef$curren;
189
208
  yield (_skuOptionsRef$curren = skuOptionsRef.current) === null || _skuOptionsRef$curren === void 0 ? void 0 : _skuOptionsRef$curren.validate();
190
- } catch (_unused) {
209
+ } catch (error) {
210
+ const invalidGroupName = resolveInvalidSkuGroupName(error, product);
211
+ Toast.fail(invalidGroupName ? locales.getText("pisell2.hdl-kiosk.customization-required-group")(invalidGroupName) : locales.getText("pisell2.hdl-kiosk.customization-required-options"));
191
212
  return;
192
213
  }
193
214
  const submittedSkuValue = (_skuOptionsRef$curren2 = (_skuOptionsRef$curren3 = skuOptionsRef.current) === null || _skuOptionsRef$curren3 === void 0 ? void 0 : _skuOptionsRef$curren3.getTransformValues()) !== null && _skuOptionsRef$curren2 !== void 0 ? _skuOptionsRef$curren2 : skuValue;
@@ -213,11 +234,13 @@ function CustomizationScreen({ product, loading, errorReference, errorMessage, b
213
234
  busy,
214
235
  minimumWeightKg,
215
236
  (_orderBar$primaryLine9 = orderBar.primaryLine) === null || _orderBar$primaryLine9 === void 0 ? void 0 : _orderBar$primaryLine9.lineId,
237
+ product,
216
238
  productFacts,
217
239
  scaleRuntime,
218
240
  skuValue,
219
241
  send,
220
- syncing
242
+ syncing,
243
+ Toast
221
244
  ]);
222
245
  const omitPrimaryProduct = useCallback(() => {
223
246
  var _orderBar$primaryLine10;
@@ -1,43 +1,83 @@
1
1
  import { _asyncToGenerator } from "../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/asyncToGenerator.js";
2
2
  import "../../runtime/admissionPolicy.js";
3
3
  import { hdlKioskApplication } from "../../runtime/application.js";
4
- import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
4
+ import { prepareHDLKioskEntry } from "../../runtime/entryPreparation.js";
5
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
5
6
  //#region src/components/HDLKiosk/ui/controllers/useHDLKioskSessionController.ts
6
7
  /**
7
8
  * 文件角色:把 React Entry 状态适配到无头 Application Runtime。
8
- * 运行机制:Controller 只处理能力/可见性和 UI pending;Trigger 路由、Session 创建、
9
- * ensure、单会话准入与销毁均由 Runtime 持有。
9
+ * 运行机制:Controller 先建立 Entry Host 清理屏障,再激活 Trigger;Session 创建、ensure、
10
+ * 单会话准入与销毁均由 Runtime 持有。
10
11
  * 边界:不直接创建 Actor,也不消费具体 Screen 的局部交互。
11
12
  */
12
- function useHDLKioskSessionController({ host, config, isActive }) {
13
+ function useHDLKioskSessionController({ host, config, isActive, entryPreparationBlocked = false }) {
13
14
  const applicationRuntime = useMemo(() => hdlKioskApplication.bind({
14
15
  host,
15
16
  config
16
17
  }), [config, host]);
17
18
  const snapshot = useSyncExternalStore(applicationRuntime.subscribe, applicationRuntime.getSnapshot, applicationRuntime.getSnapshot);
19
+ const [entryPreparationAttempt, setEntryPreparationAttempt] = useState(0);
20
+ const [entryPreparation, setEntryPreparation] = useState(() => ({ status: isActive ? "preparing" : "inactive" }));
18
21
  useEffect(() => {
19
22
  return () => applicationRuntime.dispose();
20
23
  }, [applicationRuntime]);
21
24
  useEffect(() => {
22
25
  if (!isActive) {
23
26
  applicationRuntime.deactivate();
27
+ setEntryPreparation({ status: "inactive" });
24
28
  return;
25
29
  }
26
- applicationRuntime.activate();
27
- return () => applicationRuntime.deactivate();
28
- }, [applicationRuntime, isActive]);
30
+ if (applicationRuntime.getSnapshot().session) {
31
+ applicationRuntime.activate();
32
+ setEntryPreparation({ status: "ready" });
33
+ return () => applicationRuntime.deactivate();
34
+ }
35
+ if (entryPreparationBlocked) {
36
+ applicationRuntime.deactivate();
37
+ setEntryPreparation({ status: "preparing" });
38
+ return () => applicationRuntime.deactivate();
39
+ }
40
+ applicationRuntime.deactivate();
41
+ setEntryPreparation({ status: "preparing" });
42
+ const abortController = new AbortController();
43
+ prepareHDLKioskEntry(host, abortController.signal).then((result) => {
44
+ if (abortController.signal.aborted) return;
45
+ if (result.status === "succeeded") {
46
+ setEntryPreparation({ status: "ready" });
47
+ applicationRuntime.activate();
48
+ return;
49
+ }
50
+ setEntryPreparation({
51
+ status: "failed",
52
+ errorReference: result.status === "failed" || result.status === "partialFailure" || result.status === "unknown" ? result.error.traceId : result.reason || "entryPreparationCancelled"
53
+ });
54
+ });
55
+ return () => {
56
+ abortController.abort();
57
+ applicationRuntime.deactivate();
58
+ };
59
+ }, [
60
+ applicationRuntime,
61
+ entryPreparationBlocked,
62
+ entryPreparationAttempt,
63
+ host,
64
+ isActive
65
+ ]);
29
66
  const start = useCallback(function() {
30
67
  var _ref = _asyncToGenerator(function* ({ entry }) {
68
+ if (entryPreparation.status !== "ready") return void 0;
31
69
  const result = yield applicationRuntime.start(entry);
32
70
  return result.status === "started" || result.status === "duplicate" ? result.session : void 0;
33
71
  });
34
72
  return function(_x) {
35
73
  return _ref.apply(this, arguments);
36
74
  };
37
- }(), [applicationRuntime]);
75
+ }(), [applicationRuntime, entryPreparation.status]);
38
76
  return {
39
77
  session: snapshot.session,
40
78
  starting: snapshot.status === "starting",
79
+ preparingEntry: entryPreparation.status === "preparing",
80
+ entryPreparationErrorReference: entryPreparation.status === "failed" ? entryPreparation.errorReference : void 0,
41
81
  startErrorReference: snapshot.startFailure ? typeof snapshot.startFailure.error === "object" && snapshot.startFailure.error !== null && "traceId" in snapshot.startFailure.error && typeof snapshot.startFailure.error.traceId === "string" ? snapshot.startFailure.error.traceId : snapshot.startFailure.reason : void 0,
42
82
  exiting: snapshot.status === "exiting",
43
83
  exitError: snapshot.exitError,
@@ -47,7 +87,8 @@ function useHDLKioskSessionController({ host, config, isActive }) {
47
87
  requestExit: applicationRuntime.requestExit,
48
88
  resolveExit: applicationRuntime.resolveExit,
49
89
  completeOrder: applicationRuntime.completeOrder,
50
- recover: applicationRuntime.recover
90
+ recover: applicationRuntime.recover,
91
+ retryEntryPreparation: () => setEntryPreparationAttempt((attempt) => attempt + 1)
51
92
  };
52
93
  }
53
94
  //#endregion
@@ -177,7 +177,7 @@ function EntryGraphic({ entry }) {
177
177
  if (entry === "barcode") return /* @__PURE__ */ React.createElement(BarcodeGraphic, null);
178
178
  return /* @__PURE__ */ React.createElement(ScaleGraphic, null);
179
179
  }
180
- function EntryScreen({ locale, model, onLocaleChange, onTechnicalSupportClick, onSelect, busy = false, errorReference, errorMessage = "Unable to start a safe order session." }) {
180
+ function EntryScreen({ locale, model, onLocaleChange, onTechnicalSupportClick, onSelect, busy = false, errorReference, errorMessage = "Unable to start a safe order session.", retryLabel, onRetry }) {
181
181
  const [reachMode, setReachMode] = useState(false);
182
182
  const entryLocale = locale || "en";
183
183
  return /* @__PURE__ */ React.createElement("section", {
@@ -236,7 +236,10 @@ function EntryScreen({ locale, model, onLocaleChange, onTechnicalSupportClick, o
236
236
  }, model.assistanceMessage) : null, errorReference ? /* @__PURE__ */ React.createElement("div", {
237
237
  className: "hdl-inline-error hdl-entry-screen__error",
238
238
  role: "alert"
239
- }, /* @__PURE__ */ React.createElement("strong", null, errorMessage), /* @__PURE__ */ React.createElement("small", null, errorReference)) : null), /* @__PURE__ */ React.createElement("footer", { className: "hdl-entry-screen__utilities" }, /* @__PURE__ */ React.createElement("button", {
239
+ }, /* @__PURE__ */ React.createElement("strong", null, errorMessage), /* @__PURE__ */ React.createElement("small", null, errorReference), onRetry && retryLabel ? /* @__PURE__ */ React.createElement("button", {
240
+ type: "button",
241
+ onClick: onRetry
242
+ }, retryLabel) : null) : null), /* @__PURE__ */ React.createElement("footer", { className: "hdl-entry-screen__utilities" }, /* @__PURE__ */ React.createElement("button", {
240
243
  className: `hdl-entry-reach${reachMode ? " is-active" : ""}`,
241
244
  type: "button",
242
245
  "aria-pressed": reachMode,
@@ -745,6 +745,16 @@
745
745
  color: rgba(255, 255, 255, 0.68);
746
746
  }
747
747
 
748
+ .hdl-entry-screen__error button {
749
+ align-self: flex-start;
750
+ border: 1px solid rgba(255, 255, 255, 0.45);
751
+ border-radius: 999px;
752
+ padding: 7px 14px;
753
+ background: rgba(255, 255, 255, 0.12);
754
+ color: #fff;
755
+ cursor: pointer;
756
+ }
757
+
748
758
  .hdl-entry-screen__utilities {
749
759
  position: absolute;
750
760
  right: 52px;
@@ -156,9 +156,11 @@ function HDLKioskBigSaleTemplate({ isActive = true, context, businessCode, local
156
156
  renderCartContent,
157
157
  cartOverlayActions
158
158
  }), /* @__PURE__ */ React.createElement(HDLKioskPaymentRuntime, {
159
+ active: isActive,
159
160
  context,
160
161
  controllerRef: paymentControllerRef,
161
- onPaymentResultChange: handlePaymentResultChange
162
+ onPaymentResultChange: handlePaymentResultChange,
163
+ telemetry: host.telemetry
162
164
  }), orderNoteModal);
163
165
  }
164
166
  //#endregion