@pisell/pisellos 0.10.16 → 0.10.17

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 (62) hide show
  1. package/dist/modules/OpenData/index.d.ts +5 -2
  2. package/dist/modules/OpenData/index.js +27 -12
  3. package/dist/modules/OpenData/types.d.ts +42 -0
  4. package/dist/modules/OpenData/types.js +7 -1
  5. package/dist/modules/OpenData/utils.d.ts +15 -1
  6. package/dist/modules/OpenData/utils.js +85 -0
  7. package/dist/modules/Order/index.d.ts +3 -1
  8. package/dist/modules/Order/index.js +68 -44
  9. package/dist/modules/Order/types.d.ts +3 -0
  10. package/dist/modules/Order/types.js +1 -0
  11. package/dist/modules/ProductList/index.js +96 -74
  12. package/dist/modules/Rules/index.js +11 -8
  13. package/dist/modules/SalesSummary/index.js +6 -4
  14. package/dist/server/index.d.ts +11 -0
  15. package/dist/server/index.js +852 -484
  16. package/dist/server/modules/order/index.d.ts +8 -0
  17. package/dist/server/modules/order/index.js +251 -215
  18. package/dist/server/utils/small-ticket.d.ts +9 -1
  19. package/dist/server/utils/small-ticket.js +117 -43
  20. package/dist/solution/BaseSales/index.d.ts +7 -0
  21. package/dist/solution/BaseSales/index.js +251 -144
  22. package/dist/solution/BookingByStep/index.d.ts +2 -2
  23. package/dist/solution/BookingTicket/index.d.ts +14 -19
  24. package/dist/solution/BookingTicket/index.js +545 -429
  25. package/dist/solution/Sales/index.d.ts +20 -4
  26. package/dist/solution/Sales/index.js +348 -75
  27. package/dist/solution/Sales/types.d.ts +10 -0
  28. package/dist/solution/UnifiedBookingSales/index.d.ts +5 -4
  29. package/dist/solution/UnifiedBookingSales/index.js +495 -422
  30. package/dist/solution/UnifiedBookingSales/types.d.ts +7 -1
  31. package/lib/model/strategy/adapter/promotion/index.js +1 -46
  32. package/lib/modules/OpenData/index.d.ts +5 -2
  33. package/lib/modules/OpenData/index.js +50 -3
  34. package/lib/modules/OpenData/types.d.ts +42 -0
  35. package/lib/modules/OpenData/types.js +9 -1
  36. package/lib/modules/OpenData/utils.d.ts +15 -1
  37. package/lib/modules/OpenData/utils.js +85 -0
  38. package/lib/modules/Order/index.d.ts +3 -1
  39. package/lib/modules/Order/index.js +41 -21
  40. package/lib/modules/Order/types.d.ts +3 -0
  41. package/lib/modules/Order/types.js +1 -0
  42. package/lib/modules/ProductList/index.js +9 -6
  43. package/lib/modules/Rules/index.js +8 -6
  44. package/lib/modules/SalesSummary/index.js +3 -1
  45. package/lib/server/index.d.ts +11 -0
  46. package/lib/server/index.js +399 -96
  47. package/lib/server/modules/order/index.d.ts +8 -0
  48. package/lib/server/modules/order/index.js +23 -0
  49. package/lib/server/utils/small-ticket.d.ts +9 -1
  50. package/lib/server/utils/small-ticket.js +107 -35
  51. package/lib/solution/BaseSales/index.d.ts +7 -0
  52. package/lib/solution/BaseSales/index.js +125 -28
  53. package/lib/solution/BookingByStep/index.d.ts +2 -2
  54. package/lib/solution/BookingTicket/index.d.ts +14 -19
  55. package/lib/solution/BookingTicket/index.js +70 -17
  56. package/lib/solution/Sales/index.d.ts +20 -4
  57. package/lib/solution/Sales/index.js +183 -15
  58. package/lib/solution/Sales/types.d.ts +10 -0
  59. package/lib/solution/UnifiedBookingSales/index.d.ts +5 -4
  60. package/lib/solution/UnifiedBookingSales/index.js +41 -15
  61. package/lib/solution/UnifiedBookingSales/types.d.ts +7 -1
  62. package/package.json +1 -1
@@ -27,6 +27,7 @@ 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 _OpenData = require("../modules/OpenData");
30
31
  var _modules = require("./modules");
31
32
  Object.keys(_modules).forEach(function (key) {
32
33
  if (key === "default" || key === "__esModule") return;
@@ -40,6 +41,10 @@ Object.keys(_modules).forEach(function (key) {
40
41
  });
41
42
  });
42
43
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
44
+ const OS_FULFILLMENT_REF_MODE_FIELD = '_os_fulfillment_ref_mode';
45
+ const OS_INVOICE_LAYOUT_CONFIG_FIELD = '_os_invoice_layout_config';
46
+ const OS_INVOICE_TEMPLATE_SETTINGS_FIELD = '_os_invoice_template_settings';
47
+
43
48
  // 重新导出类型供外部使用
44
49
 
45
50
  /** 商品查询订阅者 */
@@ -560,7 +565,16 @@ class Server {
560
565
  async handleSyncSalesTask(ctx) {
561
566
  const payload = ctx.task?.payload || {};
562
567
  const backendPath = payload.backendPath;
563
- const data = payload.data;
568
+ const extractedInvoice = this.extractInvoiceLayoutConfig(payload.data, {
569
+ includePublicInvoice: true
570
+ });
571
+ const extractedTemplateSettings = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
572
+ includePublicTemplateSettings: true
573
+ });
574
+ const data = extractedTemplateSettings.data;
575
+ const rawInvoiceLayoutConfig = payload.invoice ?? extractedInvoice.invoiceLayoutConfig;
576
+ const invoiceLayoutConfig = rawInvoiceLayoutConfig === undefined || rawInvoiceLayoutConfig === null ? undefined : this.normalizePrintInvoiceLayoutConfig(rawInvoiceLayoutConfig);
577
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(payload.template_settings ?? extractedTemplateSettings.invoiceTemplateSettings);
564
578
  const title = payload.title || 'sync_sales';
565
579
  if (!backendPath || !data) {
566
580
  return this.taskFail('INVALID_PAYLOAD', '缺少 backendPath 或 data');
@@ -576,7 +590,9 @@ class Server {
576
590
  title,
577
591
  externalSaleNumber: payload.external_sale_number,
578
592
  deviceId: ctx.task?.device_id,
579
- persistCheckoutDataWithResponse: false
593
+ persistCheckoutDataWithResponse: false,
594
+ invoiceLayoutConfig,
595
+ invoiceTemplateSettings
580
596
  });
581
597
  if (syncResult.rejected) {
582
598
  this.logError(`${title}: sync_sales 后端明确失败`, {
@@ -614,7 +630,8 @@ class Server {
614
630
  title
615
631
  } = params;
616
632
  const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
617
- const checkoutData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
633
+ const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
634
+ const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
618
635
  const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
619
636
  if (!this.app?.request?.post) {
620
637
  throw new Error('app.request 不可用');
@@ -658,7 +675,9 @@ class Server {
658
675
  checkoutData,
659
676
  syncedOrder: printableSyncedOrder,
660
677
  response,
661
- deviceId: params.deviceId
678
+ deviceId: params.deviceId,
679
+ invoiceLayoutConfig: params.invoiceLayoutConfig,
680
+ invoiceTemplateSettings: params.invoiceTemplateSettings
662
681
  });
663
682
  }
664
683
  return {
@@ -683,6 +702,8 @@ class Server {
683
702
  const next = (0, _lodashEs.cloneDeep)(this.omitCheckoutSyncMode(data));
684
703
  if (!next || typeof next !== 'object') return next;
685
704
  const record = next;
705
+ delete record[OS_FULFILLMENT_REF_MODE_FIELD];
706
+ delete record[OS_INVOICE_LAYOUT_CONFIG_FIELD];
686
707
  if (!Array.isArray(record.payments)) return next;
687
708
  const originalPayments = record.payments;
688
709
  const filteredPayments = originalPayments.filter(payment => !(0, _paymentUtils.isPendingOrderPayment)(payment));
@@ -3342,6 +3363,158 @@ class Server {
3342
3363
  isLocalCheckoutOrderId(orderId) {
3343
3364
  return typeof orderId === 'string' && orderId.startsWith('LOCAL_');
3344
3365
  }
3366
+ normalizePrintInvoiceLayoutConfig(value) {
3367
+ if (value === undefined || value === null) {
3368
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3369
+ }
3370
+ let parsed = value;
3371
+ if (typeof parsed === 'string') {
3372
+ try {
3373
+ parsed = JSON.parse(parsed);
3374
+ } catch {
3375
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3376
+ }
3377
+ }
3378
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
3379
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3380
+ }
3381
+ const record = parsed;
3382
+ if (!Array.isArray(record.module)) {
3383
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
3384
+ }
3385
+ return {
3386
+ ...(typeof record.locale === 'string' ? {
3387
+ locale: record.locale
3388
+ } : {}),
3389
+ module: record.module.map(moduleConfig => moduleConfig && typeof moduleConfig === 'object' && !Array.isArray(moduleConfig) ? {
3390
+ ...moduleConfig
3391
+ } : moduleConfig)
3392
+ };
3393
+ }
3394
+ extractInvoiceLayoutConfig(data, options) {
3395
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
3396
+ return {
3397
+ data
3398
+ };
3399
+ }
3400
+ const record = data;
3401
+ const rawInvoiceLayoutConfig = options?.includePublicInvoice ? record.invoice ?? record[OS_INVOICE_LAYOUT_CONFIG_FIELD] : record[OS_INVOICE_LAYOUT_CONFIG_FIELD];
3402
+ const next = {
3403
+ ...record
3404
+ };
3405
+ delete next[OS_INVOICE_LAYOUT_CONFIG_FIELD];
3406
+ if (options?.includePublicInvoice) delete next.invoice;
3407
+ return {
3408
+ data: next,
3409
+ ...(rawInvoiceLayoutConfig !== undefined && rawInvoiceLayoutConfig !== null ? {
3410
+ invoiceLayoutConfig: this.normalizePrintInvoiceLayoutConfig(rawInvoiceLayoutConfig)
3411
+ } : {})
3412
+ };
3413
+ }
3414
+ normalizePrintInvoiceTemplateSettings(value) {
3415
+ if (value === undefined || value === null) {
3416
+ return (0, _OpenData.getDefaultInvoiceTemplateSettings)();
3417
+ }
3418
+ return (0, _OpenData.normalizeRawInvoiceTemplateSettings)(value);
3419
+ }
3420
+ extractInvoiceTemplateSettings(data, options) {
3421
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
3422
+ return {
3423
+ data
3424
+ };
3425
+ }
3426
+ const record = data;
3427
+ const rawInvoiceTemplateSettings = options?.includePublicTemplateSettings ? record.template_settings ?? record[OS_INVOICE_TEMPLATE_SETTINGS_FIELD] : record[OS_INVOICE_TEMPLATE_SETTINGS_FIELD];
3428
+ const next = {
3429
+ ...record
3430
+ };
3431
+ delete next[OS_INVOICE_TEMPLATE_SETTINGS_FIELD];
3432
+ if (options?.includePublicTemplateSettings) delete next.template_settings;
3433
+ return {
3434
+ data: next,
3435
+ ...(rawInvoiceTemplateSettings !== undefined && rawInvoiceTemplateSettings !== null ? {
3436
+ invoiceTemplateSettings: this.normalizePrintInvoiceTemplateSettings(rawInvoiceTemplateSettings)
3437
+ } : {})
3438
+ };
3439
+ }
3440
+ applyCheckoutFulfillmentBuzzer(data, title) {
3441
+ if (!data || typeof data !== 'object') return data;
3442
+ const next = {
3443
+ ...data
3444
+ };
3445
+ const refMode = String(next[OS_FULFILLMENT_REF_MODE_FIELD] || '').trim();
3446
+ delete next[OS_FULFILLMENT_REF_MODE_FIELD];
3447
+ if (!refMode) return next;
3448
+ if (refMode === 'manual_input') {
3449
+ this.logInfo(`${title}: fulfillment ref mode 保留手动 buzzer`, {
3450
+ fulfillment_ref_mode: refMode,
3451
+ has_buzzer: !this.isBlankCheckoutValue(next.buzzer)
3452
+ });
3453
+ return next;
3454
+ }
3455
+ if (refMode === 'resource_identifier') {
3456
+ next.buzzer = this.buildCheckoutResourceIdentifierBuzzer(next.bookings);
3457
+ } else if (refMode === 'sale_number') {
3458
+ next.buzzer = this.isBlankCheckoutValue(next.shop_order_number) ? '' : String(next.shop_order_number);
3459
+ } else if (refMode === 'short_number') {
3460
+ next.buzzer = this.isBlankCheckoutValue(next.shop_full_order_number) ? '' : String(next.shop_full_order_number);
3461
+ } else {
3462
+ this.logWarning(`${title}: 未识别的 fulfillment ref mode,跳过 buzzer 自动填充`, {
3463
+ fulfillment_ref_mode: refMode
3464
+ });
3465
+ return next;
3466
+ }
3467
+ this.logInfo(`${title}: fulfillment ref mode 已填充 buzzer`, {
3468
+ fulfillment_ref_mode: refMode,
3469
+ buzzer: next.buzzer
3470
+ });
3471
+ return next;
3472
+ }
3473
+ buildCheckoutResourceIdentifierBuzzer(bookings) {
3474
+ if (!Array.isArray(bookings)) return '';
3475
+ const values = [];
3476
+ const seen = new Set();
3477
+ const visitResource = (resource, fallbackName) => {
3478
+ const identifier = this.getCheckoutResourceIdentifier(resource, fallbackName);
3479
+ if (identifier && !seen.has(identifier)) {
3480
+ seen.add(identifier);
3481
+ values.push(identifier);
3482
+ }
3483
+ if (Array.isArray(resource?.children)) {
3484
+ resource.children.forEach(child => visitResource(child));
3485
+ }
3486
+ };
3487
+ bookings.forEach(booking => {
3488
+ if (Array.isArray(booking?.resources)) {
3489
+ booking.resources.forEach((resource, index) => {
3490
+ visitResource(resource, index === 0 ? booking?.relation_form_name : undefined);
3491
+ });
3492
+ }
3493
+ });
3494
+ return values.join(',');
3495
+ }
3496
+ getCheckoutResourceIdentifier(resource, fallbackName) {
3497
+ if (!resource || typeof resource !== 'object') return '';
3498
+ const candidates = [resource.main_field, resource.name, resource.title, resource.metadata?.resource_name, resource.metadata?.name, fallbackName, resource.relation_id, resource.id];
3499
+ for (const candidate of candidates) {
3500
+ const value = this.stringifyCheckoutResourceIdentifier(candidate);
3501
+ if (value) return value;
3502
+ }
3503
+ return '';
3504
+ }
3505
+ stringifyCheckoutResourceIdentifier(value) {
3506
+ if (value === undefined || value === null) return '';
3507
+ if (typeof value === 'string' || typeof value === 'number') {
3508
+ return String(value).trim();
3509
+ }
3510
+ if (typeof value !== 'object' || Array.isArray(value)) return '';
3511
+ const localeCandidates = [value.original, value.auto, value.en, value['zh-CN'], value.zh_CN, value['zh-HK'], value.zh_HK];
3512
+ for (const candidate of localeCandidates) {
3513
+ const normalized = this.stringifyCheckoutResourceIdentifier(candidate);
3514
+ if (normalized) return normalized;
3515
+ }
3516
+ return '';
3517
+ }
3345
3518
  async normalizeCheckoutSubmitData(data, title) {
3346
3519
  if (!data || typeof data !== 'object') return data;
3347
3520
  const next = {
@@ -3472,6 +3645,8 @@ class Server {
3472
3645
  payload: {
3473
3646
  backendPath: params.backendPath,
3474
3647
  data: params.data,
3648
+ invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig),
3649
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3475
3650
  external_sale_number: params.data?.external_sale_number,
3476
3651
  title: params.title
3477
3652
  },
@@ -3510,12 +3685,18 @@ class Server {
3510
3685
  data: params.receiptOnly ? {
3511
3686
  order_id: printIdentity,
3512
3687
  small_ticket_data: params.syncedOrder?.payment_info?.small_ticket_data,
3688
+ invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig),
3689
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3513
3690
  skip_wristband: true,
3514
3691
  skip_voucher: true
3515
3692
  } : {
3516
3693
  order_id: printIdentity,
3517
3694
  machine_code_print_info: params.syncedOrder?.payment_info?.machine_code_print_info,
3518
3695
  machine_code_print_info_v2: params.syncedOrder?.machine_code_print_info_v2,
3696
+ ...(params.invoiceLayoutConfig ? {
3697
+ invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig)
3698
+ } : {}),
3699
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3519
3700
  skip_receipt: true
3520
3701
  }
3521
3702
  },
@@ -3567,7 +3748,9 @@ class Server {
3567
3748
  syncedOrder: printableOrder,
3568
3749
  response: params.response,
3569
3750
  receiptOnly: true,
3570
- isManualPrint: params.isManualPrint
3751
+ isManualPrint: params.isManualPrint,
3752
+ invoiceLayoutConfig: params.invoiceLayoutConfig,
3753
+ invoiceTemplateSettings: params.invoiceTemplateSettings
3571
3754
  });
3572
3755
  return {
3573
3756
  printed: true,
@@ -3587,11 +3770,35 @@ class Server {
3587
3770
  }
3588
3771
  return undefined;
3589
3772
  }
3773
+ mergeLocalPrintOrderInput(localOrder, inputOrder) {
3774
+ const definedInput = Object.fromEntries(Object.entries(inputOrder || {}).filter(([, value]) => value !== undefined));
3775
+ return {
3776
+ ...localOrder,
3777
+ ...definedInput,
3778
+ order_id: localOrder.order_id ?? definedInput.order_id ?? definedInput.orderId,
3779
+ external_sale_number: localOrder.external_sale_number ?? definedInput.external_sale_number ?? definedInput.externalSaleNumber
3780
+ };
3781
+ }
3590
3782
  handleLocalPrintOrder = async ({
3591
3783
  data
3592
3784
  }) => {
3593
3785
  try {
3594
- const inputOrder = data?.order && typeof data.order === 'object' ? data.order : data;
3786
+ const extractedRequest = this.extractInvoiceLayoutConfig(data, {
3787
+ includePublicInvoice: true
3788
+ });
3789
+ const extractedRequestTemplateSettings = this.extractInvoiceTemplateSettings(extractedRequest.data, {
3790
+ includePublicTemplateSettings: true
3791
+ });
3792
+ const requestOrder = extractedRequestTemplateSettings.data?.order;
3793
+ const nestedOrder = requestOrder && typeof requestOrder === 'object' ? this.extractInvoiceLayoutConfig(requestOrder, {
3794
+ includePublicInvoice: true
3795
+ }) : null;
3796
+ const nestedOrderTemplateSettings = nestedOrder ? this.extractInvoiceTemplateSettings(nestedOrder.data, {
3797
+ includePublicTemplateSettings: true
3798
+ }) : null;
3799
+ const inputOrder = nestedOrderTemplateSettings?.data ?? extractedRequestTemplateSettings.data;
3800
+ const invoiceLayoutConfig = extractedRequest.invoiceLayoutConfig ?? nestedOrder?.invoiceLayoutConfig;
3801
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedRequestTemplateSettings.invoiceTemplateSettings ?? nestedOrderTemplateSettings?.invoiceTemplateSettings);
3595
3802
  if (!inputOrder || typeof inputOrder !== 'object') {
3596
3803
  return {
3597
3804
  code: 400,
@@ -3603,13 +3810,13 @@ class Server {
3603
3810
  }
3604
3811
  };
3605
3812
  }
3606
- const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(data);
3813
+ const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(extractedRequest.data);
3607
3814
  this.logInfo('handleLocalPrintOrder: 是否需要查找本地订单', {
3608
3815
  shouldLookup,
3609
3816
  inputOrder,
3610
3817
  data
3611
3818
  });
3612
- const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(data) : undefined;
3819
+ const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(extractedRequest.data) : undefined;
3613
3820
  let sourceOrder = inputOrder;
3614
3821
  if (shouldLookup) {
3615
3822
  if (lookup === undefined) {
@@ -3634,24 +3841,25 @@ class Server {
3634
3841
  }
3635
3842
  };
3636
3843
  }
3637
- const localOrder = await this.order.getLocalOrderByLookup(lookup);
3844
+ let localOrder = await this.order.getLocalOrderByLookup(lookup);
3638
3845
  if (!localOrder) {
3639
- return {
3640
- code: 404,
3641
- status: false,
3642
- message: '订单不存在',
3643
- data: {
3644
- printed: false,
3645
- order: null
3646
- }
3647
- };
3846
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, {
3847
+ with: ['all']
3848
+ }, 'handleLocalPrintOrder');
3849
+ if (!remoteResult.order) {
3850
+ return {
3851
+ code: remoteResult.code,
3852
+ status: false,
3853
+ message: remoteResult.message,
3854
+ data: {
3855
+ printed: false,
3856
+ order: null
3857
+ }
3858
+ };
3859
+ }
3860
+ localOrder = remoteResult.order;
3648
3861
  }
3649
- sourceOrder = {
3650
- ...localOrder,
3651
- ...inputOrder,
3652
- order_id: localOrder.order_id ?? inputOrder.order_id ?? inputOrder.orderId,
3653
- external_sale_number: localOrder.external_sale_number ?? inputOrder.external_sale_number ?? inputOrder.externalSaleNumber
3654
- };
3862
+ sourceOrder = this.mergeLocalPrintOrderInput(localOrder, inputOrder);
3655
3863
  }
3656
3864
  const checkoutData = await this.ensureCheckoutOrderNumbers({
3657
3865
  ...sourceOrder,
@@ -3670,7 +3878,9 @@ class Server {
3670
3878
  order: printableOrder,
3671
3879
  response: null,
3672
3880
  requireFullyPaid: false,
3673
- isManualPrint: true
3881
+ isManualPrint: true,
3882
+ invoiceLayoutConfig,
3883
+ invoiceTemplateSettings
3674
3884
  });
3675
3885
  return {
3676
3886
  code: 200,
@@ -3881,9 +4091,17 @@ class Server {
3881
4091
  async handleOrderCheckoutSubmit(params) {
3882
4092
  const {
3883
4093
  backendPath,
3884
- data,
3885
4094
  title
3886
4095
  } = params;
4096
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4097
+ includePublicInvoice: true
4098
+ });
4099
+ const extractedTemplateSettings = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4100
+ includePublicTemplateSettings: true
4101
+ });
4102
+ const data = extractedTemplateSettings.data;
4103
+ const invoiceLayoutConfig = extractedInvoice.invoiceLayoutConfig;
4104
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedTemplateSettings.invoiceTemplateSettings);
3887
4105
  this.logInfo(`${title}: 开始处理`, {
3888
4106
  backendPath,
3889
4107
  order_id: data?.order_id,
@@ -3894,11 +4112,14 @@ class Server {
3894
4112
  return this.handleSyncCheckoutSubmit({
3895
4113
  backendPath,
3896
4114
  data,
3897
- title
4115
+ title,
4116
+ invoiceLayoutConfig,
4117
+ invoiceTemplateSettings
3898
4118
  });
3899
4119
  }
3900
4120
  const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
3901
- const checkoutData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4121
+ const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4122
+ const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
3902
4123
  const pendingResult = await this.handlePendingSyncCheckoutOrder({
3903
4124
  backendPath,
3904
4125
  data: checkoutData,
@@ -3912,13 +4133,17 @@ class Server {
3912
4133
  checkoutData,
3913
4134
  order: pendingOrder,
3914
4135
  response: pendingResult,
3915
- requireFullyPaid: true
4136
+ requireFullyPaid: true,
4137
+ invoiceLayoutConfig,
4138
+ invoiceTemplateSettings
3916
4139
  });
3917
4140
  }
3918
4141
  await this.dispatchCheckoutSyncTask({
3919
4142
  backendPath,
3920
4143
  data: checkoutData,
3921
- title
4144
+ title,
4145
+ invoiceLayoutConfig,
4146
+ invoiceTemplateSettings
3922
4147
  });
3923
4148
  }
3924
4149
  if (!pendingResult.data.order_id) {
@@ -3936,7 +4161,9 @@ class Server {
3936
4161
  const {
3937
4162
  backendPath,
3938
4163
  data,
3939
- title
4164
+ title,
4165
+ invoiceLayoutConfig,
4166
+ invoiceTemplateSettings
3940
4167
  } = params;
3941
4168
  if (!this.app?.request?.post) {
3942
4169
  this.logError(`${title}: sync checkout app.request 不可用`);
@@ -3952,7 +4179,9 @@ class Server {
3952
4179
  backendPath,
3953
4180
  data,
3954
4181
  title,
3955
- persistCheckoutDataWithResponse: true
4182
+ persistCheckoutDataWithResponse: true,
4183
+ invoiceLayoutConfig,
4184
+ invoiceTemplateSettings
3956
4185
  });
3957
4186
  if (syncResult.rejected) {
3958
4187
  const normalized = this.normalizeCheckoutResponse(syncResult.errorResponse);
@@ -3970,7 +4199,9 @@ class Server {
3970
4199
  checkoutData: syncResult.checkoutData,
3971
4200
  order: syncedOrder,
3972
4201
  response: syncResult.normalizedResponse,
3973
- requireFullyPaid: true
4202
+ requireFullyPaid: true,
4203
+ invoiceLayoutConfig,
4204
+ invoiceTemplateSettings
3974
4205
  // print_all 的 type=0 是收据打印;不要沿用同步成功后的券/手环打印 type=99。
3975
4206
  });
3976
4207
  }
@@ -4002,9 +4233,16 @@ class Server {
4002
4233
  }
4003
4234
  async handleOrderDraftSubmit(params) {
4004
4235
  const {
4005
- data,
4006
4236
  title
4007
4237
  } = params;
4238
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4239
+ includePublicInvoice: true
4240
+ });
4241
+ const {
4242
+ data
4243
+ } = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4244
+ includePublicTemplateSettings: true
4245
+ });
4008
4246
  this.logInfo(`${title}: 开始处理`, {
4009
4247
  order_id: data?.order_id,
4010
4248
  external_sale_number: data?.external_sale_number,
@@ -4488,6 +4726,122 @@ class Server {
4488
4726
  return order;
4489
4727
  }
4490
4728
  }
4729
+ normalizeRemoteSalesOrderErrorCode(value) {
4730
+ if (value === undefined || value === null || value === '') return 500;
4731
+ const code = typeof value === 'number' ? value : Number(value);
4732
+ return Number.isFinite(code) ? code : 500;
4733
+ }
4734
+ async fetchAndPersistSalesOrderByLookup(lookup, data, title) {
4735
+ if (!this.order?.getLocalOrderByLookup || !this.order?.upsertOrdersFromRemote) {
4736
+ this.logError(`${title}: Order 模块未注册`);
4737
+ return {
4738
+ code: 500,
4739
+ message: 'Order 模块未注册',
4740
+ order: null
4741
+ };
4742
+ }
4743
+ if (!this.app?.request?.get) {
4744
+ this.logError(`${title}: app.request 不可用`);
4745
+ return {
4746
+ code: 500,
4747
+ message: 'app.request 不可用',
4748
+ order: null
4749
+ };
4750
+ }
4751
+ const backendPath = this.buildOrderSalesDetailBackendPath(String(lookup), data);
4752
+ try {
4753
+ const response = await this.app.request.get(backendPath, {
4754
+ lookup_mode: 'multi'
4755
+ }, {
4756
+ isShopApi: true,
4757
+ customToast: () => {}
4758
+ });
4759
+ if (this.isCheckoutResponseRejected(response)) {
4760
+ const responseStatusCode = typeof response?.status === 'number' ? response.status : undefined;
4761
+ const code = this.normalizeRemoteSalesOrderErrorCode(response?.code ?? responseStatusCode);
4762
+ const message = response?.message || '后端拒绝订单查询';
4763
+ this.logInfo(`${title}: 后端拒绝订单查询`, {
4764
+ lookup,
4765
+ backendPath,
4766
+ code,
4767
+ message
4768
+ });
4769
+ return {
4770
+ code,
4771
+ message,
4772
+ order: null
4773
+ };
4774
+ }
4775
+ const fresh = this.extractOrderDataFromCheckoutResponse(response);
4776
+ if (!fresh || fresh.order_id === undefined || fresh.order_id === null || String(fresh.order_id).trim() === '') {
4777
+ this.logInfo(`${title}: 后端返回订单为空`, {
4778
+ lookup,
4779
+ backendPath
4780
+ });
4781
+ return {
4782
+ code: 500,
4783
+ message: '后端返回订单为空',
4784
+ order: null
4785
+ };
4786
+ }
4787
+ await this.order.upsertOrdersFromRemote([fresh]);
4788
+ let savedOrder = await this.order.getLocalOrderByLookup(lookup);
4789
+ if (!savedOrder && fresh.order_id !== undefined && fresh.order_id !== null && String(fresh.order_id) !== String(lookup)) {
4790
+ savedOrder = await this.order.getLocalOrderByLookup(fresh.order_id);
4791
+ }
4792
+ if (!savedOrder) {
4793
+ this.logError(`${title}: 订单写入后回读失败`, {
4794
+ lookup,
4795
+ remote_order_id: fresh.order_id
4796
+ });
4797
+ return {
4798
+ code: 500,
4799
+ message: '订单写入后回读失败',
4800
+ order: null
4801
+ };
4802
+ }
4803
+ this.logInfo(`${title}: 远端订单已同步到本地`, {
4804
+ lookup,
4805
+ remote_order_id: fresh.order_id
4806
+ });
4807
+ return {
4808
+ code: 200,
4809
+ message: '',
4810
+ order: savedOrder
4811
+ };
4812
+ } catch (error) {
4813
+ const backendError = this.extractBackendErrorResponse(error);
4814
+ const errorRecord = error;
4815
+ const responseStatusCode = typeof errorRecord?.response?.status === 'number' ? errorRecord.response.status : undefined;
4816
+ if (backendError || responseStatusCode !== undefined) {
4817
+ const backendStatusCode = typeof backendError?.status === 'number' ? backendError.status : undefined;
4818
+ const code = this.normalizeRemoteSalesOrderErrorCode(backendError?.code ?? backendStatusCode ?? responseStatusCode);
4819
+ const message = backendError?.message || this.getUnknownErrorMessage(error);
4820
+ this.logInfo(`${title}: 后端订单查询失败`, {
4821
+ lookup,
4822
+ backendPath,
4823
+ code,
4824
+ message
4825
+ });
4826
+ return {
4827
+ code,
4828
+ message,
4829
+ order: null
4830
+ };
4831
+ }
4832
+ const message = this.getUnknownErrorMessage(error);
4833
+ this.logInfo(`${title}: 请求失败`, {
4834
+ lookup,
4835
+ backendPath,
4836
+ error: message
4837
+ });
4838
+ return {
4839
+ code: 500,
4840
+ message,
4841
+ order: null
4842
+ };
4843
+ }
4844
+ }
4491
4845
  handleOrderSalesDetail = async ({
4492
4846
  url,
4493
4847
  data,
@@ -4536,73 +4890,22 @@ class Server {
4536
4890
  };
4537
4891
  }
4538
4892
  }
4539
- if (!this.app?.request) {
4540
- this.logError('handleOrderSalesDetail: app.request 不可用');
4893
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, data, 'handleOrderSalesDetail');
4894
+ if (!remoteResult.order) {
4541
4895
  return {
4542
- code: 500,
4896
+ code: remoteResult.code,
4543
4897
  status: false,
4544
- message: 'app.request 不可用',
4545
- data: null
4546
- };
4547
- }
4548
- const backendPath = this.buildOrderSalesDetailBackendPath(lookup, data);
4549
- try {
4550
- const response = await this.app.request.get(backendPath, {
4551
- lookup_mode: 'multi'
4552
- }, {
4553
- isShopApi: true,
4554
- customToast: () => {}
4555
- });
4556
- const fresh = response?.data ?? response;
4557
- if (!fresh || typeof fresh !== 'object' || fresh.order_id == null) {
4558
- this.logInfo('handleOrderSalesDetail: 后端返回订单为空', {
4559
- lookup,
4560
- backendPath
4561
- });
4562
- return {
4563
- code: 500,
4564
- status: false,
4565
- message: '后端返回订单为空',
4566
- data: null
4567
- };
4568
- }
4569
- await this.order.upsertOrdersFromRemote([fresh]);
4570
- const savedOrder = await this.order.getLocalOrderByLookup(lookup);
4571
- if (!savedOrder) {
4572
- this.logError('handleOrderSalesDetail: 订单写入后回读失败', {
4573
- lookup
4574
- });
4575
- return {
4576
- code: 500,
4577
- status: false,
4578
- message: '订单写入后回读失败',
4579
- data: null
4580
- };
4581
- }
4582
- this.logInfo('handleOrderSalesDetail: 远端订单已同步到本地', {
4583
- lookup
4584
- });
4585
- const enrichedOrder = await this.withSalesDetailProductSnapshots(savedOrder);
4586
- return {
4587
- code: 200,
4588
- status: true,
4589
- message: '',
4590
- data: enrichedOrder
4591
- };
4592
- } catch (error) {
4593
- const errorMessage = error instanceof Error ? error.message : String(error);
4594
- this.logInfo('handleOrderSalesDetail: 请求失败', {
4595
- lookup,
4596
- backendPath,
4597
- error: errorMessage
4598
- });
4599
- return {
4600
- code: 500,
4601
- status: false,
4602
- message: errorMessage,
4898
+ message: remoteResult.message,
4603
4899
  data: null
4604
4900
  };
4605
4901
  }
4902
+ const enrichedOrder = await this.withSalesDetailProductSnapshots(remoteResult.order);
4903
+ return {
4904
+ code: 200,
4905
+ status: true,
4906
+ message: '',
4907
+ data: enrichedOrder
4908
+ };
4606
4909
  };
4607
4910
 
4608
4911
  /**