@pisell/pisellos 2.2.302 → 2.2.303

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 (153) hide show
  1. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  3. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  4. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  5. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  6. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  7. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  8. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  9. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  10. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  11. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
  12. package/dist/modules/Discount/index.d.ts +2 -0
  13. package/dist/modules/Discount/index.js +41 -5
  14. package/dist/modules/Discount/types.d.ts +21 -0
  15. package/dist/modules/OpenData/index.d.ts +15 -2
  16. package/dist/modules/OpenData/index.js +307 -19
  17. package/dist/modules/OpenData/types.d.ts +55 -0
  18. package/dist/modules/OpenData/types.js +7 -1
  19. package/dist/modules/OpenData/utils.d.ts +21 -1
  20. package/dist/modules/OpenData/utils.js +138 -0
  21. package/dist/modules/Order/index.d.ts +50 -7
  22. package/dist/modules/Order/index.js +1690 -815
  23. package/dist/modules/Order/salesNotes.d.ts +31 -0
  24. package/dist/modules/Order/salesNotes.js +492 -0
  25. package/dist/modules/Order/types.d.ts +117 -10
  26. package/dist/modules/Order/types.js +6 -1
  27. package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
  28. package/dist/modules/Order/utils/virtualSettlement.js +284 -0
  29. package/dist/modules/Order/utils.d.ts +5 -1
  30. package/dist/modules/Order/utils.js +138 -33
  31. package/dist/modules/Payment/walletpass.js +20 -21
  32. package/dist/modules/Product/types.d.ts +23 -0
  33. package/dist/modules/ProductList/index.d.ts +2 -1
  34. package/dist/modules/ProductList/index.js +100 -20
  35. package/dist/modules/ProductList/types.d.ts +10 -0
  36. package/dist/modules/Rules/index.d.ts +5 -0
  37. package/dist/modules/Rules/index.js +30 -14
  38. package/dist/modules/SalesSummary/index.d.ts +1 -0
  39. package/dist/modules/SalesSummary/index.js +4 -3
  40. package/dist/modules/SalesSummary/types.d.ts +1 -0
  41. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  42. package/dist/modules/SalesSummary/utils.js +21 -1
  43. package/dist/plugins/request.d.ts +2 -1
  44. package/dist/plugins/request.js +4 -2
  45. package/dist/server/index.d.ts +21 -0
  46. package/dist/server/index.js +1774 -1285
  47. package/dist/server/modules/order/types.d.ts +50 -0
  48. package/dist/server/modules/order/types.js +2 -0
  49. package/dist/server/utils/small-ticket.d.ts +4 -1
  50. package/dist/server/utils/small-ticket.js +461 -96
  51. package/dist/solution/BaseSales/index.d.ts +24 -3
  52. package/dist/solution/BaseSales/index.js +1484 -873
  53. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
  54. package/dist/solution/BaseSales/utils/parseSalesResponse.js +1 -1
  55. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  56. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +72 -9
  57. package/dist/solution/BookingByStep/index.d.ts +1 -1
  58. package/dist/solution/BookingTicket/index.d.ts +22 -5
  59. package/dist/solution/BookingTicket/index.js +558 -443
  60. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  61. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  62. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  63. package/dist/solution/RegisterAndLogin/config.js +95 -40
  64. package/dist/solution/RegisterAndLogin/index.js +4 -1
  65. package/dist/solution/Sales/index.d.ts +16 -1
  66. package/dist/solution/Sales/index.js +338 -63
  67. package/dist/solution/Sales/types.d.ts +10 -0
  68. package/dist/solution/ScanOrder/index.d.ts +1 -0
  69. package/dist/solution/ScanOrder/index.js +31 -27
  70. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  71. package/dist/solution/ScanOrder/utils.js +2 -0
  72. package/dist/solution/UnifiedBookingSales/index.d.ts +20 -6
  73. package/dist/solution/UnifiedBookingSales/index.js +874 -527
  74. package/dist/solution/UnifiedBookingSales/types.d.ts +21 -2
  75. package/dist/solution/VenueBooking/index.d.ts +1 -0
  76. package/dist/solution/VenueBooking/index.js +8 -4
  77. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  78. package/lib/model/strategy/adapter/dataVariant/evaluator.js +137 -6
  79. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  80. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  81. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  82. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  83. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  84. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  85. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  86. package/lib/model/strategy/adapter/promotion/index.js +1 -46
  87. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
  88. package/lib/modules/Discount/index.d.ts +2 -0
  89. package/lib/modules/Discount/index.js +34 -2
  90. package/lib/modules/Discount/types.d.ts +21 -0
  91. package/lib/modules/OpenData/index.d.ts +15 -2
  92. package/lib/modules/OpenData/index.js +230 -3
  93. package/lib/modules/OpenData/types.d.ts +55 -0
  94. package/lib/modules/OpenData/types.js +9 -1
  95. package/lib/modules/OpenData/utils.d.ts +21 -1
  96. package/lib/modules/OpenData/utils.js +126 -0
  97. package/lib/modules/Order/index.d.ts +50 -7
  98. package/lib/modules/Order/index.js +704 -64
  99. package/lib/modules/Order/salesNotes.d.ts +31 -0
  100. package/lib/modules/Order/salesNotes.js +382 -0
  101. package/lib/modules/Order/types.d.ts +117 -10
  102. package/lib/modules/Order/types.js +5 -1
  103. package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
  104. package/lib/modules/Order/utils/virtualSettlement.js +262 -0
  105. package/lib/modules/Order/utils.d.ts +5 -1
  106. package/lib/modules/Order/utils.js +127 -21
  107. package/lib/modules/Payment/walletpass.js +14 -11
  108. package/lib/modules/Product/types.d.ts +23 -0
  109. package/lib/modules/ProductList/index.d.ts +2 -1
  110. package/lib/modules/ProductList/index.js +47 -2
  111. package/lib/modules/ProductList/types.d.ts +10 -0
  112. package/lib/modules/Rules/index.d.ts +5 -0
  113. package/lib/modules/Rules/index.js +22 -12
  114. package/lib/modules/SalesSummary/index.d.ts +1 -0
  115. package/lib/modules/SalesSummary/index.js +4 -2
  116. package/lib/modules/SalesSummary/types.d.ts +1 -0
  117. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  118. package/lib/modules/SalesSummary/utils.js +17 -1
  119. package/lib/plugins/request.d.ts +2 -1
  120. package/lib/plugins/request.js +3 -2
  121. package/lib/server/index.d.ts +21 -0
  122. package/lib/server/index.js +492 -108
  123. package/lib/server/modules/order/types.d.ts +50 -0
  124. package/lib/server/modules/order/types.js +1 -0
  125. package/lib/server/utils/small-ticket.d.ts +4 -1
  126. package/lib/server/utils/small-ticket.js +427 -72
  127. package/lib/solution/BaseSales/index.d.ts +24 -3
  128. package/lib/solution/BaseSales/index.js +433 -30
  129. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
  130. package/lib/solution/BaseSales/utils/parseSalesResponse.js +1 -1
  131. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  132. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +38 -0
  133. package/lib/solution/BookingByStep/index.d.ts +1 -1
  134. package/lib/solution/BookingTicket/index.d.ts +22 -5
  135. package/lib/solution/BookingTicket/index.js +60 -15
  136. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -0
  137. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  138. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  139. package/lib/solution/RegisterAndLogin/config.js +49 -8
  140. package/lib/solution/RegisterAndLogin/index.js +4 -1
  141. package/lib/solution/Sales/index.d.ts +16 -1
  142. package/lib/solution/Sales/index.js +168 -1
  143. package/lib/solution/Sales/types.d.ts +10 -0
  144. package/lib/solution/ScanOrder/index.d.ts +1 -0
  145. package/lib/solution/ScanOrder/index.js +5 -1
  146. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  147. package/lib/solution/ScanOrder/utils.js +1 -0
  148. package/lib/solution/UnifiedBookingSales/index.d.ts +20 -6
  149. package/lib/solution/UnifiedBookingSales/index.js +169 -16
  150. package/lib/solution/UnifiedBookingSales/types.d.ts +21 -2
  151. package/lib/solution/VenueBooking/index.d.ts +1 -0
  152. package/lib/solution/VenueBooking/index.js +5 -1
  153. package/package.json +1 -1
@@ -27,6 +27,9 @@ var _filterBookings = require("./modules/order/utils/filterBookings");
27
27
  var _smallTicket = require("./utils/small-ticket");
28
28
  var _BaseSales = require("../solution/BaseSales");
29
29
  var _paymentUtils = require("../modules/Order/payment-utils");
30
+ var _salesNotes = require("../modules/Order/salesNotes");
31
+ var _orderCollectionIdentity = require("../modules/Order/utils/orderCollectionIdentity");
32
+ var _OpenData = require("../modules/OpenData");
30
33
  var _modules = require("./modules");
31
34
  Object.keys(_modules).forEach(function (key) {
32
35
  if (key === "default" || key === "__esModule") return;
@@ -41,6 +44,8 @@ Object.keys(_modules).forEach(function (key) {
41
44
  });
42
45
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
43
46
  const OS_FULFILLMENT_REF_MODE_FIELD = '_os_fulfillment_ref_mode';
47
+ const OS_INVOICE_LAYOUT_CONFIG_FIELD = '_os_invoice_layout_config';
48
+ const OS_INVOICE_TEMPLATE_SETTINGS_FIELD = '_os_invoice_template_settings';
44
49
 
45
50
  // 重新导出类型供外部使用
46
51
 
@@ -572,7 +577,16 @@ class Server {
572
577
  async handleSyncSalesTask(ctx) {
573
578
  const payload = ctx.task?.payload || {};
574
579
  const backendPath = payload.backendPath;
575
- const data = payload.data;
580
+ const extractedInvoice = this.extractInvoiceLayoutConfig(payload.data, {
581
+ includePublicInvoice: true
582
+ });
583
+ const extractedTemplateSettings = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
584
+ includePublicTemplateSettings: true
585
+ });
586
+ const data = extractedTemplateSettings.data;
587
+ const rawInvoiceLayoutConfig = payload.invoice ?? extractedInvoice.invoiceLayoutConfig;
588
+ const invoiceLayoutConfig = rawInvoiceLayoutConfig === undefined || rawInvoiceLayoutConfig === null ? undefined : this.normalizePrintInvoiceLayoutConfig(rawInvoiceLayoutConfig);
589
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(payload.template_settings ?? extractedTemplateSettings.invoiceTemplateSettings);
576
590
  const title = payload.title || 'sync_sales';
577
591
  if (!backendPath || !data) {
578
592
  return this.taskFail('INVALID_PAYLOAD', '缺少 backendPath 或 data');
@@ -588,7 +602,9 @@ class Server {
588
602
  title,
589
603
  externalSaleNumber: payload.external_sale_number,
590
604
  deviceId: ctx.task?.device_id,
591
- persistCheckoutDataWithResponse: false
605
+ persistCheckoutDataWithResponse: false,
606
+ invoiceLayoutConfig,
607
+ invoiceTemplateSettings
592
608
  });
593
609
  if (syncResult.rejected) {
594
610
  this.logError(`${title}: sync_sales 后端明确失败`, {
@@ -627,7 +643,7 @@ class Server {
627
643
  } = params;
628
644
  const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
629
645
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
630
- const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
646
+ const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
631
647
  const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
632
648
  const deviceHeaders = await this.buildCheckoutDeviceHeaders(title);
633
649
  if (!this.app?.request?.post) {
@@ -675,7 +691,9 @@ class Server {
675
691
  checkoutData,
676
692
  syncedOrder: printableSyncedOrder,
677
693
  response,
678
- deviceId: params.deviceId
694
+ deviceId: params.deviceId,
695
+ invoiceLayoutConfig: params.invoiceLayoutConfig,
696
+ invoiceTemplateSettings: params.invoiceTemplateSettings
679
697
  });
680
698
  }
681
699
  return {
@@ -701,6 +719,7 @@ class Server {
701
719
  if (!next || typeof next !== 'object') return next;
702
720
  const record = next;
703
721
  delete record[OS_FULFILLMENT_REF_MODE_FIELD];
722
+ delete record[OS_INVOICE_LAYOUT_CONFIG_FIELD];
704
723
  this.applyKioskCheckoutOperator(record);
705
724
  if (!Array.isArray(record.payments)) return next;
706
725
  const originalPayments = record.payments;
@@ -1302,6 +1321,10 @@ class Server {
1302
1321
  method: 'post',
1303
1322
  path: '/shop/local/print-order',
1304
1323
  handler: this.handleLocalPrintOrder.bind(this)
1324
+ }, {
1325
+ method: 'post',
1326
+ path: '/shop/local/order-receipt-snapshot',
1327
+ handler: this.handleLocalOrderReceiptSnapshot.bind(this)
1305
1328
  }, {
1306
1329
  method: 'post',
1307
1330
  path: '/shop/local/getLocalPayment',
@@ -2151,10 +2174,12 @@ class Server {
2151
2174
 
2152
2175
  /**
2153
2176
  * 预约查询是否走本地:
2177
+ * - 日历周/月/资源视图通过 compact_fields 显式标识,始终走远端;
2154
2178
  * - 当 sales_time_between 的开始和结束都不是今天时,走远端;
2155
2179
  * - 其余情况走本地。
2156
2180
  */
2157
2181
  shouldUseLocalBookingQuery(data) {
2182
+ if (data?.compact_fields === true) return false;
2158
2183
  const range = data?.sales_time_between;
2159
2184
  if (!Array.isArray(range) || range.length < 2) return true;
2160
2185
  return !this.isRangeBothNotToday(range[0], range[1]);
@@ -3426,6 +3451,80 @@ class Server {
3426
3451
  isLocalCheckoutOrderId(orderId) {
3427
3452
  return typeof orderId === 'string' && orderId.startsWith('LOCAL_');
3428
3453
  }
3454
+ normalizePrintInvoiceLayoutConfig(value) {
3455
+ if (value === undefined || value === null) {
3456
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3457
+ }
3458
+ let parsed = value;
3459
+ if (typeof parsed === 'string') {
3460
+ try {
3461
+ parsed = JSON.parse(parsed);
3462
+ } catch {
3463
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3464
+ }
3465
+ }
3466
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
3467
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3468
+ }
3469
+ const record = parsed;
3470
+ if (!Array.isArray(record.module)) {
3471
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3472
+ }
3473
+ return {
3474
+ ...(typeof record.locale === 'string' ? {
3475
+ locale: record.locale
3476
+ } : {}),
3477
+ module: record.module.map(moduleConfig => moduleConfig && typeof moduleConfig === 'object' && !Array.isArray(moduleConfig) ? {
3478
+ ...moduleConfig
3479
+ } : moduleConfig)
3480
+ };
3481
+ }
3482
+ extractInvoiceLayoutConfig(data, options) {
3483
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
3484
+ return {
3485
+ data
3486
+ };
3487
+ }
3488
+ const record = data;
3489
+ const rawInvoiceLayoutConfig = options?.includePublicInvoice ? record.invoice ?? record[OS_INVOICE_LAYOUT_CONFIG_FIELD] : record[OS_INVOICE_LAYOUT_CONFIG_FIELD];
3490
+ const next = {
3491
+ ...record
3492
+ };
3493
+ delete next[OS_INVOICE_LAYOUT_CONFIG_FIELD];
3494
+ if (options?.includePublicInvoice) delete next.invoice;
3495
+ return {
3496
+ data: next,
3497
+ ...(rawInvoiceLayoutConfig !== undefined && rawInvoiceLayoutConfig !== null ? {
3498
+ invoiceLayoutConfig: this.normalizePrintInvoiceLayoutConfig(rawInvoiceLayoutConfig)
3499
+ } : {})
3500
+ };
3501
+ }
3502
+ normalizePrintInvoiceTemplateSettings(value) {
3503
+ if (value === undefined || value === null) {
3504
+ return (0, _OpenData.getDefaultInvoiceTemplateSettings)();
3505
+ }
3506
+ return (0, _OpenData.normalizeRawInvoiceTemplateSettings)(value);
3507
+ }
3508
+ extractInvoiceTemplateSettings(data, options) {
3509
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
3510
+ return {
3511
+ data
3512
+ };
3513
+ }
3514
+ const record = data;
3515
+ const rawInvoiceTemplateSettings = options?.includePublicTemplateSettings ? record.template_settings ?? record[OS_INVOICE_TEMPLATE_SETTINGS_FIELD] : record[OS_INVOICE_TEMPLATE_SETTINGS_FIELD];
3516
+ const next = {
3517
+ ...record
3518
+ };
3519
+ delete next[OS_INVOICE_TEMPLATE_SETTINGS_FIELD];
3520
+ if (options?.includePublicTemplateSettings) delete next.template_settings;
3521
+ return {
3522
+ data: next,
3523
+ ...(rawInvoiceTemplateSettings !== undefined && rawInvoiceTemplateSettings !== null ? {
3524
+ invoiceTemplateSettings: this.normalizePrintInvoiceTemplateSettings(rawInvoiceTemplateSettings)
3525
+ } : {})
3526
+ };
3527
+ }
3429
3528
  applyCheckoutFulfillmentBuzzer(data, title) {
3430
3529
  if (!data || typeof data !== 'object') return data;
3431
3530
  const next = {
@@ -3587,14 +3686,16 @@ class Server {
3587
3686
  };
3588
3687
  const needsShopOrderNumber = next.shop_order_number === undefined || next.shop_order_number === null || next.shop_order_number === '';
3589
3688
  const needsShopFullOrderNumber = next.shop_full_order_number === undefined || next.shop_full_order_number === null || next.shop_full_order_number === '';
3590
- if (!needsShopOrderNumber && !needsShopFullOrderNumber) return next;
3689
+ if (!needsShopOrderNumber && !needsShopFullOrderNumber) {
3690
+ return this.finalizeCheckoutSalesNotes(next, title);
3691
+ }
3591
3692
  const idGenerator = this.getIdGeneratorPlugin();
3592
3693
  if (!idGenerator) {
3593
3694
  this.logWarning(`${title}: idGenerator 插件不可用,无法生成订单号`, {
3594
3695
  needsShopOrderNumber,
3595
3696
  needsShopFullOrderNumber
3596
3697
  });
3597
- return next;
3698
+ return this.finalizeCheckoutSalesNotes(next, title);
3598
3699
  }
3599
3700
  try {
3600
3701
  if (needsShopOrderNumber && idGenerator.generateShopOrderNumber) {
@@ -3616,7 +3717,38 @@ class Server {
3616
3717
  error: error instanceof Error ? error.message : String(error)
3617
3718
  });
3618
3719
  }
3619
- return next;
3720
+ return this.finalizeCheckoutSalesNotes(next, title);
3721
+ }
3722
+
3723
+ /**
3724
+ * Notes 允许在本地草稿阶段暂存空订单目标;checkout 的统一出口负责用最终
3725
+ * shop_order_number 完成绑定并校验,保证后端请求、pending、设备任务和打印
3726
+ * 都使用同一份完整快照。
3727
+ */
3728
+ finalizeCheckoutSalesNotes(data, title) {
3729
+ if (!data || typeof data !== 'object') return data;
3730
+ if (!Object.prototype.hasOwnProperty.call(data, 'notes')) return data;
3731
+ if (data.notes === undefined || data.notes === null) return data;
3732
+ if (!Array.isArray(data.notes)) {
3733
+ throw new Error('[Order Notes] checkout notes 必须为数组');
3734
+ }
3735
+ if (data.notes.length === 0) return data;
3736
+ const productTargetUuids = (Array.isArray(data.products) ? data.products : []).map(product => product?.metadata?.unique_identification_number ?? product?.unique_identification_number).filter(targetUuid => targetUuid !== undefined && targetUuid !== null && String(targetUuid).trim().length > 0).map(targetUuid => String(targetUuid));
3737
+ const shopOrderNumber = String(data.shop_order_number ?? '').trim();
3738
+ const notes = (0, _salesNotes.finalizeSalesNotesForCheckout)({
3739
+ notes: data.notes,
3740
+ shopOrderNumber,
3741
+ productTargetUuids
3742
+ });
3743
+ this.logInfo(`${title}: checkout Notes 已绑定订单号`, {
3744
+ shop_order_number: shopOrderNumber,
3745
+ note_count: notes.length
3746
+ });
3747
+ return {
3748
+ ...data,
3749
+ shop_order_number: shopOrderNumber,
3750
+ notes
3751
+ };
3620
3752
  }
3621
3753
  async dispatchCheckoutSyncTask(params) {
3622
3754
  this.registerDeviceTaskActions();
@@ -3634,6 +3766,8 @@ class Server {
3634
3766
  payload: {
3635
3767
  backendPath: params.backendPath,
3636
3768
  data: params.data,
3769
+ invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig),
3770
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3637
3771
  external_sale_number: params.data?.external_sale_number,
3638
3772
  title: params.title
3639
3773
  },
@@ -3669,6 +3803,7 @@ class Server {
3669
3803
  device_id: params.deviceId ?? deviceId,
3670
3804
  payload: {
3671
3805
  type: params.isManualPrint ? '0' : '99',
3806
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3672
3807
  data: params.receiptOnly ? {
3673
3808
  order_id: printIdentity,
3674
3809
  small_ticket_data: params.syncedOrder?.payment_info?.small_ticket_data,
@@ -3712,7 +3847,8 @@ class Server {
3712
3847
  };
3713
3848
  }
3714
3849
  const printableOrder = await this.withLocalSmallTicketData(params.order, {
3715
- requireFullyPaid: params.requireFullyPaid
3850
+ requireFullyPaid: params.requireFullyPaid,
3851
+ invoiceTemplateSettings: params.invoiceTemplateSettings
3716
3852
  });
3717
3853
  if (!(0, _smallTicket.hasSmallTicketData)(printableOrder.payment_info?.small_ticket_data)) {
3718
3854
  return {
@@ -3730,7 +3866,9 @@ class Server {
3730
3866
  syncedOrder: printableOrder,
3731
3867
  response: params.response,
3732
3868
  receiptOnly: true,
3733
- isManualPrint: params.isManualPrint
3869
+ isManualPrint: params.isManualPrint,
3870
+ invoiceLayoutConfig: params.invoiceLayoutConfig,
3871
+ invoiceTemplateSettings: params.invoiceTemplateSettings
3734
3872
  });
3735
3873
  return {
3736
3874
  printed: true,
@@ -3750,11 +3888,35 @@ class Server {
3750
3888
  }
3751
3889
  return undefined;
3752
3890
  }
3891
+ mergeLocalPrintOrderInput(localOrder, inputOrder) {
3892
+ const definedInput = Object.fromEntries(Object.entries(inputOrder || {}).filter(([, value]) => value !== undefined));
3893
+ return {
3894
+ ...localOrder,
3895
+ ...definedInput,
3896
+ order_id: localOrder.order_id ?? definedInput.order_id ?? definedInput.orderId,
3897
+ external_sale_number: localOrder.external_sale_number ?? definedInput.external_sale_number ?? definedInput.externalSaleNumber
3898
+ };
3899
+ }
3753
3900
  handleLocalPrintOrder = async ({
3754
3901
  data
3755
3902
  }) => {
3756
3903
  try {
3757
- const inputOrder = data?.order && typeof data.order === 'object' ? data.order : data;
3904
+ const extractedRequest = this.extractInvoiceLayoutConfig(data, {
3905
+ includePublicInvoice: true
3906
+ });
3907
+ const extractedRequestTemplateSettings = this.extractInvoiceTemplateSettings(extractedRequest.data, {
3908
+ includePublicTemplateSettings: true
3909
+ });
3910
+ const requestOrder = extractedRequestTemplateSettings.data?.order;
3911
+ const nestedOrder = requestOrder && typeof requestOrder === 'object' ? this.extractInvoiceLayoutConfig(requestOrder, {
3912
+ includePublicInvoice: true
3913
+ }) : null;
3914
+ const nestedOrderTemplateSettings = nestedOrder ? this.extractInvoiceTemplateSettings(nestedOrder.data, {
3915
+ includePublicTemplateSettings: true
3916
+ }) : null;
3917
+ const inputOrder = nestedOrderTemplateSettings?.data ?? extractedRequestTemplateSettings.data;
3918
+ const invoiceLayoutConfig = extractedRequest.invoiceLayoutConfig ?? nestedOrder?.invoiceLayoutConfig;
3919
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedRequestTemplateSettings.invoiceTemplateSettings ?? nestedOrderTemplateSettings?.invoiceTemplateSettings);
3758
3920
  if (!inputOrder || typeof inputOrder !== 'object') {
3759
3921
  return {
3760
3922
  code: 400,
@@ -3766,13 +3928,13 @@ class Server {
3766
3928
  }
3767
3929
  };
3768
3930
  }
3769
- const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(data);
3931
+ const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(extractedRequest.data);
3770
3932
  this.logInfo('handleLocalPrintOrder: 是否需要查找本地订单', {
3771
3933
  shouldLookup,
3772
3934
  inputOrder,
3773
3935
  data
3774
3936
  });
3775
- const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(data) : undefined;
3937
+ const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(extractedRequest.data) : undefined;
3776
3938
  let sourceOrder = inputOrder;
3777
3939
  if (shouldLookup) {
3778
3940
  if (lookup === undefined) {
@@ -3797,24 +3959,25 @@ class Server {
3797
3959
  }
3798
3960
  };
3799
3961
  }
3800
- const localOrder = await this.order.getLocalOrderByLookup(lookup);
3962
+ let localOrder = await this.order.getLocalOrderByLookup(lookup);
3801
3963
  if (!localOrder) {
3802
- return {
3803
- code: 404,
3804
- status: false,
3805
- message: '订单不存在',
3806
- data: {
3807
- printed: false,
3808
- order: null
3809
- }
3810
- };
3964
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, {
3965
+ with: ['all']
3966
+ }, 'handleLocalPrintOrder');
3967
+ if (!remoteResult.order) {
3968
+ return {
3969
+ code: remoteResult.code,
3970
+ status: false,
3971
+ message: remoteResult.message,
3972
+ data: {
3973
+ printed: false,
3974
+ order: null
3975
+ }
3976
+ };
3977
+ }
3978
+ localOrder = remoteResult.order;
3811
3979
  }
3812
- sourceOrder = {
3813
- ...localOrder,
3814
- ...inputOrder,
3815
- order_id: localOrder.order_id ?? inputOrder.order_id ?? inputOrder.orderId,
3816
- external_sale_number: localOrder.external_sale_number ?? inputOrder.external_sale_number ?? inputOrder.externalSaleNumber
3817
- };
3980
+ sourceOrder = this.mergeLocalPrintOrderInput(localOrder, inputOrder);
3818
3981
  }
3819
3982
  const checkoutData = await this.ensureCheckoutOrderNumbers({
3820
3983
  ...sourceOrder,
@@ -3833,7 +3996,9 @@ class Server {
3833
3996
  order: printableOrder,
3834
3997
  response: null,
3835
3998
  requireFullyPaid: false,
3836
- isManualPrint: true
3999
+ isManualPrint: true,
4000
+ invoiceLayoutConfig,
4001
+ invoiceTemplateSettings
3837
4002
  });
3838
4003
  return {
3839
4004
  code: 200,
@@ -3860,6 +4025,99 @@ class Server {
3860
4025
  };
3861
4026
  }
3862
4027
  };
4028
+ normalizeRequestedReceiptLocales(value) {
4029
+ if (!Array.isArray(value)) return [];
4030
+ return Array.from(new Set(value.filter(locale => typeof locale === 'string').map(locale => locale.trim()).filter(Boolean)));
4031
+ }
4032
+ handleLocalOrderReceiptSnapshot = async ({
4033
+ data
4034
+ }) => {
4035
+ const input = data && typeof data === 'object' && !Array.isArray(data) ? data : {};
4036
+ const lookup = this.getLocalPrintOrderLookup(input);
4037
+ if (lookup === undefined) {
4038
+ return {
4039
+ code: 400,
4040
+ status: false,
4041
+ message: '订单 lookup 缺失',
4042
+ data: {
4043
+ small_ticket_data: null
4044
+ }
4045
+ };
4046
+ }
4047
+ if (!this.order?.getLocalOrderByLookup) {
4048
+ return {
4049
+ code: 500,
4050
+ status: false,
4051
+ message: 'Order 模块未注册',
4052
+ data: {
4053
+ small_ticket_data: null
4054
+ }
4055
+ };
4056
+ }
4057
+ try {
4058
+ let sourceOrder = await this.order.getLocalOrderByLookup(lookup);
4059
+ if (!sourceOrder) {
4060
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, {
4061
+ with: ['all']
4062
+ }, 'handleLocalOrderReceiptSnapshot');
4063
+ if (!remoteResult.order) {
4064
+ return {
4065
+ code: remoteResult.code,
4066
+ status: false,
4067
+ message: remoteResult.message,
4068
+ data: {
4069
+ small_ticket_data: null
4070
+ }
4071
+ };
4072
+ }
4073
+ sourceOrder = remoteResult.order;
4074
+ }
4075
+ const paymentInfo = {
4076
+ ...(sourceOrder.payment_info || {})
4077
+ };
4078
+ delete paymentInfo.small_ticket_data;
4079
+ const rebuiltOrder = await this.withLocalSmallTicketData({
4080
+ ...sourceOrder,
4081
+ small_ticket_data_flag: 1,
4082
+ payment_info: paymentInfo
4083
+ }, {
4084
+ locales: this.normalizeRequestedReceiptLocales(input.locales)
4085
+ });
4086
+ const smallTicketData = rebuiltOrder.payment_info?.small_ticket_data;
4087
+ if (!(0, _smallTicket.hasSmallTicketData)(smallTicketData)) {
4088
+ return {
4089
+ code: 500,
4090
+ status: false,
4091
+ message: '本地小票 Snapshot 生成失败',
4092
+ data: {
4093
+ small_ticket_data: null
4094
+ }
4095
+ };
4096
+ }
4097
+ return {
4098
+ code: 200,
4099
+ status: true,
4100
+ message: '',
4101
+ data: {
4102
+ small_ticket_data: smallTicketData
4103
+ }
4104
+ };
4105
+ } catch (error) {
4106
+ const message = error instanceof Error ? error.message : String(error);
4107
+ this.logError('handleLocalOrderReceiptSnapshot: 生成失败', {
4108
+ lookup,
4109
+ error: message
4110
+ });
4111
+ return {
4112
+ code: 500,
4113
+ status: false,
4114
+ message,
4115
+ data: {
4116
+ small_ticket_data: null
4117
+ }
4118
+ };
4119
+ }
4120
+ };
3863
4121
  handleMachinecodeBatchMeta = async ({
3864
4122
  data
3865
4123
  }) => {
@@ -4044,9 +4302,17 @@ class Server {
4044
4302
  async handleOrderCheckoutSubmit(params) {
4045
4303
  const {
4046
4304
  backendPath,
4047
- data,
4048
4305
  title
4049
4306
  } = params;
4307
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4308
+ includePublicInvoice: true
4309
+ });
4310
+ const extractedTemplateSettings = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4311
+ includePublicTemplateSettings: true
4312
+ });
4313
+ const data = extractedTemplateSettings.data;
4314
+ const invoiceLayoutConfig = extractedInvoice.invoiceLayoutConfig;
4315
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedTemplateSettings.invoiceTemplateSettings);
4050
4316
  this.logInfo(`${title}: 开始处理`, {
4051
4317
  backendPath,
4052
4318
  order_id: data?.order_id,
@@ -4057,17 +4323,20 @@ class Server {
4057
4323
  return this.handleSyncCheckoutSubmit({
4058
4324
  backendPath,
4059
4325
  data,
4060
- title
4326
+ title,
4327
+ invoiceLayoutConfig,
4328
+ invoiceTemplateSettings
4061
4329
  });
4062
4330
  }
4063
4331
  const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
4064
4332
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4065
- const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
4333
+ const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
4066
4334
  const pendingResult = await this.handlePendingSyncCheckoutOrder({
4067
4335
  backendPath,
4068
4336
  data: checkoutData,
4069
4337
  title,
4070
- reason: 'checkout 已转入设备任务同步'
4338
+ reason: 'checkout 已转入设备任务同步',
4339
+ invoiceTemplateSettings
4071
4340
  });
4072
4341
  if (pendingResult?.status === true) {
4073
4342
  const pendingOrder = pendingResult.data;
@@ -4076,13 +4345,17 @@ class Server {
4076
4345
  checkoutData,
4077
4346
  order: pendingOrder,
4078
4347
  response: pendingResult,
4079
- requireFullyPaid: true
4348
+ requireFullyPaid: true,
4349
+ invoiceLayoutConfig,
4350
+ invoiceTemplateSettings
4080
4351
  });
4081
4352
  }
4082
4353
  await this.dispatchCheckoutSyncTask({
4083
4354
  backendPath,
4084
4355
  data: checkoutData,
4085
- title
4356
+ title,
4357
+ invoiceLayoutConfig,
4358
+ invoiceTemplateSettings
4086
4359
  });
4087
4360
  }
4088
4361
  if (!pendingResult.data.order_id) {
@@ -4100,7 +4373,9 @@ class Server {
4100
4373
  const {
4101
4374
  backendPath,
4102
4375
  data,
4103
- title
4376
+ title,
4377
+ invoiceLayoutConfig,
4378
+ invoiceTemplateSettings
4104
4379
  } = params;
4105
4380
  if (!this.app?.request?.post) {
4106
4381
  this.logError(`${title}: sync checkout app.request 不可用`);
@@ -4116,7 +4391,9 @@ class Server {
4116
4391
  backendPath,
4117
4392
  data,
4118
4393
  title,
4119
- persistCheckoutDataWithResponse: true
4394
+ persistCheckoutDataWithResponse: true,
4395
+ invoiceLayoutConfig,
4396
+ invoiceTemplateSettings
4120
4397
  });
4121
4398
  if (syncResult.rejected) {
4122
4399
  const normalized = this.normalizeCheckoutResponse(syncResult.errorResponse);
@@ -4134,7 +4411,9 @@ class Server {
4134
4411
  checkoutData: syncResult.checkoutData,
4135
4412
  order: syncedOrder,
4136
4413
  response: syncResult.normalizedResponse,
4137
- requireFullyPaid: true
4414
+ requireFullyPaid: true,
4415
+ invoiceLayoutConfig,
4416
+ invoiceTemplateSettings
4138
4417
  // print_all 的 type=0 是收据打印;不要沿用同步成功后的券/手环打印 type=99。
4139
4418
  });
4140
4419
  }
@@ -4166,9 +4445,16 @@ class Server {
4166
4445
  }
4167
4446
  async handleOrderDraftSubmit(params) {
4168
4447
  const {
4169
- data,
4170
4448
  title
4171
4449
  } = params;
4450
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4451
+ includePublicInvoice: true
4452
+ });
4453
+ const {
4454
+ data
4455
+ } = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4456
+ includePublicTemplateSettings: true
4457
+ });
4172
4458
  this.logInfo(`${title}: 开始处理`, {
4173
4459
  order_id: data?.order_id,
4174
4460
  external_sale_number: data?.external_sale_number,
@@ -4229,9 +4515,10 @@ class Server {
4229
4515
  backendPath,
4230
4516
  data,
4231
4517
  title,
4232
- reason
4518
+ reason,
4519
+ invoiceTemplateSettings
4233
4520
  } = params;
4234
- const pendingOrder = await this.buildPendingSyncCheckoutOrder(data);
4521
+ const pendingOrder = await this.buildPendingSyncCheckoutOrder(data, invoiceTemplateSettings);
4235
4522
  if (!pendingOrder) {
4236
4523
  this.logError(`${title}: checkout 失败且订单缺少本地存储标识`, {
4237
4524
  backendPath,
@@ -4297,23 +4584,39 @@ class Server {
4297
4584
  };
4298
4585
  }
4299
4586
  }
4300
- async buildPendingSyncCheckoutOrder(data) {
4587
+ async buildPendingSyncCheckoutOrder(data, invoiceTemplateSettings) {
4301
4588
  if (!data || typeof data !== 'object') return null;
4302
4589
  const hasStorageKey = data.external_sale_number !== undefined && data.external_sale_number !== null && data.external_sale_number !== '' ? true : data.order_id !== undefined && data.order_id !== null && data.order_id !== '';
4303
4590
  if (!hasStorageKey) return null;
4304
- const pendingOrder = {
4591
+ const pendingOrder = this.normalizeCheckoutCollections({
4305
4592
  ...data,
4306
4593
  need_sync: 1,
4307
4594
  is_draft_order: 0
4308
- };
4595
+ });
4309
4596
  const productMap = await this.buildSmallTicketProductMap(pendingOrder);
4310
4597
  const orderWithProductTitles = this.withPendingSyncProductTitles(pendingOrder, productMap);
4311
4598
  return this.withLocalSmallTicketData(orderWithProductTitles, {
4312
4599
  requireFullyPaid: true,
4313
- productMap
4600
+ productMap,
4601
+ invoiceTemplateSettings
4314
4602
  });
4315
4603
  }
4316
4604
 
4605
+ /**
4606
+ * Checkout 与本地小票必须消费同一份规范化子项快照。
4607
+ * 商品/预约只折叠持久化 ID 或协议 UID 相同的行;支付额外兼容 payment_number。
4608
+ */
4609
+ normalizeCheckoutCollections(data) {
4610
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
4611
+ const normalized = (0, _orderCollectionIdentity.normalizeOrderCollections)(data, {
4612
+ ensureUid: false
4613
+ }).order;
4614
+ if (Array.isArray(normalized.payments)) {
4615
+ normalized.payments = (0, _paymentUtils.mergeOrderPaymentListsByIdentity)([], normalized.payments);
4616
+ }
4617
+ return normalized;
4618
+ }
4619
+
4317
4620
  /**
4318
4621
  * Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
4319
4622
  */
@@ -4631,11 +4934,27 @@ class Server {
4631
4934
  if (!this.shouldBuildSmallTicketData(order)) return order;
4632
4935
  if (options?.requireFullyPaid && !this.isSalesOrderFullyPaid(order)) return order;
4633
4936
  try {
4634
- const enrichedOrder = await this.enrichPaymentNamesByCode(order);
4937
+ const normalizedCollections = (0, _orderCollectionIdentity.normalizeOrderCollections)(order, {
4938
+ ensureUid: false
4939
+ });
4940
+ const normalizedOrder = normalizedCollections.order;
4941
+ const collapsedDuplicateCounts = {
4942
+ products: normalizedCollections.stats.product.collapsedDuplicateCount,
4943
+ bookings: normalizedCollections.stats.booking.collapsedDuplicateCount,
4944
+ payments: normalizedCollections.stats.payment.collapsedDuplicateCount
4945
+ };
4946
+ if (Object.values(collapsedDuplicateCounts).some(count => count > 0)) {
4947
+ this.logWarning('withLocalSmallTicketData: 折叠重复订单子项', {
4948
+ order_identifier: normalizedOrder.order_id ?? normalizedOrder.external_sale_number ?? normalizedOrder.shop_order_number ?? null,
4949
+ collapsed_duplicate_counts: collapsedDuplicateCounts
4950
+ });
4951
+ }
4952
+ const enrichedOrder = await this.enrichPaymentNamesByCode(normalizedOrder);
4635
4953
  const productMap = options?.productMap ?? (await this.buildSmallTicketProductMap(enrichedOrder));
4636
4954
  const smallTicketData = (0, _smallTicket.buildSmallTicketData)({
4637
4955
  order: enrichedOrder,
4638
4956
  shopInfo: this.getSmallTicketShopInfo(),
4957
+ locales: [...(0, _OpenData.resolveInvoiceReceiptLocales)(options?.invoiceTemplateSettings), ...(options?.locales || [])],
4639
4958
  productMap
4640
4959
  });
4641
4960
  return {
@@ -4652,6 +4971,122 @@ class Server {
4652
4971
  return order;
4653
4972
  }
4654
4973
  }
4974
+ normalizeRemoteSalesOrderErrorCode(value) {
4975
+ if (value === undefined || value === null || value === '') return 500;
4976
+ const code = typeof value === 'number' ? value : Number(value);
4977
+ return Number.isFinite(code) ? code : 500;
4978
+ }
4979
+ async fetchAndPersistSalesOrderByLookup(lookup, data, title) {
4980
+ if (!this.order?.getLocalOrderByLookup || !this.order?.upsertOrdersFromRemote) {
4981
+ this.logError(`${title}: Order 模块未注册`);
4982
+ return {
4983
+ code: 500,
4984
+ message: 'Order 模块未注册',
4985
+ order: null
4986
+ };
4987
+ }
4988
+ if (!this.app?.request?.get) {
4989
+ this.logError(`${title}: app.request 不可用`);
4990
+ return {
4991
+ code: 500,
4992
+ message: 'app.request 不可用',
4993
+ order: null
4994
+ };
4995
+ }
4996
+ const backendPath = this.buildOrderSalesDetailBackendPath(String(lookup), data);
4997
+ try {
4998
+ const response = await this.app.request.get(backendPath, {
4999
+ lookup_mode: 'multi'
5000
+ }, {
5001
+ isShopApi: true,
5002
+ customToast: () => {}
5003
+ });
5004
+ if (this.isCheckoutResponseRejected(response)) {
5005
+ const responseStatusCode = typeof response?.status === 'number' ? response.status : undefined;
5006
+ const code = this.normalizeRemoteSalesOrderErrorCode(response?.code ?? responseStatusCode);
5007
+ const message = response?.message || '后端拒绝订单查询';
5008
+ this.logInfo(`${title}: 后端拒绝订单查询`, {
5009
+ lookup,
5010
+ backendPath,
5011
+ code,
5012
+ message
5013
+ });
5014
+ return {
5015
+ code,
5016
+ message,
5017
+ order: null
5018
+ };
5019
+ }
5020
+ const fresh = this.extractOrderDataFromCheckoutResponse(response);
5021
+ if (!fresh || fresh.order_id === undefined || fresh.order_id === null || String(fresh.order_id).trim() === '') {
5022
+ this.logInfo(`${title}: 后端返回订单为空`, {
5023
+ lookup,
5024
+ backendPath
5025
+ });
5026
+ return {
5027
+ code: 500,
5028
+ message: '后端返回订单为空',
5029
+ order: null
5030
+ };
5031
+ }
5032
+ await this.order.upsertOrdersFromRemote([fresh]);
5033
+ let savedOrder = await this.order.getLocalOrderByLookup(lookup);
5034
+ if (!savedOrder && fresh.order_id !== undefined && fresh.order_id !== null && String(fresh.order_id) !== String(lookup)) {
5035
+ savedOrder = await this.order.getLocalOrderByLookup(fresh.order_id);
5036
+ }
5037
+ if (!savedOrder) {
5038
+ this.logError(`${title}: 订单写入后回读失败`, {
5039
+ lookup,
5040
+ remote_order_id: fresh.order_id
5041
+ });
5042
+ return {
5043
+ code: 500,
5044
+ message: '订单写入后回读失败',
5045
+ order: null
5046
+ };
5047
+ }
5048
+ this.logInfo(`${title}: 远端订单已同步到本地`, {
5049
+ lookup,
5050
+ remote_order_id: fresh.order_id
5051
+ });
5052
+ return {
5053
+ code: 200,
5054
+ message: '',
5055
+ order: savedOrder
5056
+ };
5057
+ } catch (error) {
5058
+ const backendError = this.extractBackendErrorResponse(error);
5059
+ const errorRecord = error;
5060
+ const responseStatusCode = typeof errorRecord?.response?.status === 'number' ? errorRecord.response.status : undefined;
5061
+ if (backendError || responseStatusCode !== undefined) {
5062
+ const backendStatusCode = typeof backendError?.status === 'number' ? backendError.status : undefined;
5063
+ const code = this.normalizeRemoteSalesOrderErrorCode(backendError?.code ?? backendStatusCode ?? responseStatusCode);
5064
+ const message = backendError?.message || this.getUnknownErrorMessage(error);
5065
+ this.logInfo(`${title}: 后端订单查询失败`, {
5066
+ lookup,
5067
+ backendPath,
5068
+ code,
5069
+ message
5070
+ });
5071
+ return {
5072
+ code,
5073
+ message,
5074
+ order: null
5075
+ };
5076
+ }
5077
+ const message = this.getUnknownErrorMessage(error);
5078
+ this.logInfo(`${title}: 请求失败`, {
5079
+ lookup,
5080
+ backendPath,
5081
+ error: message
5082
+ });
5083
+ return {
5084
+ code: 500,
5085
+ message,
5086
+ order: null
5087
+ };
5088
+ }
5089
+ }
4655
5090
  handleOrderSalesDetail = async ({
4656
5091
  url,
4657
5092
  data,
@@ -4700,73 +5135,22 @@ class Server {
4700
5135
  };
4701
5136
  }
4702
5137
  }
4703
- if (!this.app?.request) {
4704
- this.logError('handleOrderSalesDetail: app.request 不可用');
5138
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, data, 'handleOrderSalesDetail');
5139
+ if (!remoteResult.order) {
4705
5140
  return {
4706
- code: 500,
5141
+ code: remoteResult.code,
4707
5142
  status: false,
4708
- message: 'app.request 不可用',
4709
- data: null
4710
- };
4711
- }
4712
- const backendPath = this.buildOrderSalesDetailBackendPath(lookup, data);
4713
- try {
4714
- const response = await this.app.request.get(backendPath, {
4715
- lookup_mode: 'multi'
4716
- }, {
4717
- isShopApi: true,
4718
- customToast: () => {}
4719
- });
4720
- const fresh = response?.data ?? response;
4721
- if (!fresh || typeof fresh !== 'object' || fresh.order_id == null) {
4722
- this.logInfo('handleOrderSalesDetail: 后端返回订单为空', {
4723
- lookup,
4724
- backendPath
4725
- });
4726
- return {
4727
- code: 500,
4728
- status: false,
4729
- message: '后端返回订单为空',
4730
- data: null
4731
- };
4732
- }
4733
- await this.order.upsertOrdersFromRemote([fresh]);
4734
- const savedOrder = await this.order.getLocalOrderByLookup(lookup);
4735
- if (!savedOrder) {
4736
- this.logError('handleOrderSalesDetail: 订单写入后回读失败', {
4737
- lookup
4738
- });
4739
- return {
4740
- code: 500,
4741
- status: false,
4742
- message: '订单写入后回读失败',
4743
- data: null
4744
- };
4745
- }
4746
- this.logInfo('handleOrderSalesDetail: 远端订单已同步到本地', {
4747
- lookup
4748
- });
4749
- const enrichedOrder = await this.withSalesDetailProductSnapshots(savedOrder);
4750
- return {
4751
- code: 200,
4752
- status: true,
4753
- message: '',
4754
- data: enrichedOrder
4755
- };
4756
- } catch (error) {
4757
- const errorMessage = error instanceof Error ? error.message : String(error);
4758
- this.logInfo('handleOrderSalesDetail: 请求失败', {
4759
- lookup,
4760
- backendPath,
4761
- error: errorMessage
4762
- });
4763
- return {
4764
- code: 500,
4765
- status: false,
4766
- message: errorMessage,
5143
+ message: remoteResult.message,
4767
5144
  data: null
4768
5145
  };
4769
5146
  }
5147
+ const enrichedOrder = await this.withSalesDetailProductSnapshots(remoteResult.order);
5148
+ return {
5149
+ code: 200,
5150
+ status: true,
5151
+ message: '',
5152
+ data: enrichedOrder
5153
+ };
4770
5154
  };
4771
5155
 
4772
5156
  /**