@pisell/pisellos 0.10.16 → 0.10.18

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 (63) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/OpenData/index.d.ts +5 -2
  3. package/dist/modules/OpenData/index.js +27 -12
  4. package/dist/modules/OpenData/types.d.ts +42 -0
  5. package/dist/modules/OpenData/types.js +7 -1
  6. package/dist/modules/OpenData/utils.d.ts +15 -1
  7. package/dist/modules/OpenData/utils.js +85 -0
  8. package/dist/modules/Order/index.d.ts +3 -1
  9. package/dist/modules/Order/index.js +68 -44
  10. package/dist/modules/Order/types.d.ts +3 -0
  11. package/dist/modules/Order/types.js +1 -0
  12. package/dist/modules/ProductList/index.js +96 -74
  13. package/dist/modules/Rules/index.js +11 -8
  14. package/dist/modules/SalesSummary/index.js +6 -4
  15. package/dist/server/index.d.ts +11 -0
  16. package/dist/server/index.js +845 -481
  17. package/dist/server/modules/order/index.d.ts +8 -0
  18. package/dist/server/modules/order/index.js +251 -215
  19. package/dist/server/utils/small-ticket.d.ts +9 -1
  20. package/dist/server/utils/small-ticket.js +117 -43
  21. package/dist/solution/BaseSales/index.d.ts +7 -0
  22. package/dist/solution/BaseSales/index.js +251 -144
  23. package/dist/solution/BookingByStep/index.d.ts +2 -2
  24. package/dist/solution/BookingTicket/index.d.ts +14 -19
  25. package/dist/solution/BookingTicket/index.js +545 -429
  26. package/dist/solution/Sales/index.d.ts +20 -4
  27. package/dist/solution/Sales/index.js +348 -75
  28. package/dist/solution/Sales/types.d.ts +10 -0
  29. package/dist/solution/UnifiedBookingSales/index.d.ts +5 -4
  30. package/dist/solution/UnifiedBookingSales/index.js +495 -422
  31. package/dist/solution/UnifiedBookingSales/types.d.ts +7 -1
  32. package/lib/model/strategy/adapter/promotion/index.js +1 -46
  33. package/lib/modules/OpenData/index.d.ts +5 -2
  34. package/lib/modules/OpenData/index.js +50 -3
  35. package/lib/modules/OpenData/types.d.ts +42 -0
  36. package/lib/modules/OpenData/types.js +9 -1
  37. package/lib/modules/OpenData/utils.d.ts +15 -1
  38. package/lib/modules/OpenData/utils.js +85 -0
  39. package/lib/modules/Order/index.d.ts +3 -1
  40. package/lib/modules/Order/index.js +41 -21
  41. package/lib/modules/Order/types.d.ts +3 -0
  42. package/lib/modules/Order/types.js +1 -0
  43. package/lib/modules/ProductList/index.js +9 -6
  44. package/lib/modules/Rules/index.js +8 -6
  45. package/lib/modules/SalesSummary/index.js +3 -1
  46. package/lib/server/index.d.ts +11 -0
  47. package/lib/server/index.js +395 -96
  48. package/lib/server/modules/order/index.d.ts +8 -0
  49. package/lib/server/modules/order/index.js +23 -0
  50. package/lib/server/utils/small-ticket.d.ts +9 -1
  51. package/lib/server/utils/small-ticket.js +107 -35
  52. package/lib/solution/BaseSales/index.d.ts +7 -0
  53. package/lib/solution/BaseSales/index.js +125 -28
  54. package/lib/solution/BookingByStep/index.d.ts +2 -2
  55. package/lib/solution/BookingTicket/index.d.ts +14 -19
  56. package/lib/solution/BookingTicket/index.js +70 -17
  57. package/lib/solution/Sales/index.d.ts +20 -4
  58. package/lib/solution/Sales/index.js +183 -15
  59. package/lib/solution/Sales/types.d.ts +10 -0
  60. package/lib/solution/UnifiedBookingSales/index.d.ts +5 -4
  61. package/lib/solution/UnifiedBookingSales/index.js +41 -15
  62. package/lib/solution/UnifiedBookingSales/types.d.ts +7 -1
  63. 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,14 @@ 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
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3513
3689
  skip_wristband: true,
3514
3690
  skip_voucher: true
3515
3691
  } : {
3516
3692
  order_id: printIdentity,
3517
3693
  machine_code_print_info: params.syncedOrder?.payment_info?.machine_code_print_info,
3518
3694
  machine_code_print_info_v2: params.syncedOrder?.machine_code_print_info_v2,
3695
+ template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3519
3696
  skip_receipt: true
3520
3697
  }
3521
3698
  },
@@ -3567,7 +3744,9 @@ class Server {
3567
3744
  syncedOrder: printableOrder,
3568
3745
  response: params.response,
3569
3746
  receiptOnly: true,
3570
- isManualPrint: params.isManualPrint
3747
+ isManualPrint: params.isManualPrint,
3748
+ invoiceLayoutConfig: params.invoiceLayoutConfig,
3749
+ invoiceTemplateSettings: params.invoiceTemplateSettings
3571
3750
  });
3572
3751
  return {
3573
3752
  printed: true,
@@ -3587,11 +3766,35 @@ class Server {
3587
3766
  }
3588
3767
  return undefined;
3589
3768
  }
3769
+ mergeLocalPrintOrderInput(localOrder, inputOrder) {
3770
+ const definedInput = Object.fromEntries(Object.entries(inputOrder || {}).filter(([, value]) => value !== undefined));
3771
+ return {
3772
+ ...localOrder,
3773
+ ...definedInput,
3774
+ order_id: localOrder.order_id ?? definedInput.order_id ?? definedInput.orderId,
3775
+ external_sale_number: localOrder.external_sale_number ?? definedInput.external_sale_number ?? definedInput.externalSaleNumber
3776
+ };
3777
+ }
3590
3778
  handleLocalPrintOrder = async ({
3591
3779
  data
3592
3780
  }) => {
3593
3781
  try {
3594
- const inputOrder = data?.order && typeof data.order === 'object' ? data.order : data;
3782
+ const extractedRequest = this.extractInvoiceLayoutConfig(data, {
3783
+ includePublicInvoice: true
3784
+ });
3785
+ const extractedRequestTemplateSettings = this.extractInvoiceTemplateSettings(extractedRequest.data, {
3786
+ includePublicTemplateSettings: true
3787
+ });
3788
+ const requestOrder = extractedRequestTemplateSettings.data?.order;
3789
+ const nestedOrder = requestOrder && typeof requestOrder === 'object' ? this.extractInvoiceLayoutConfig(requestOrder, {
3790
+ includePublicInvoice: true
3791
+ }) : null;
3792
+ const nestedOrderTemplateSettings = nestedOrder ? this.extractInvoiceTemplateSettings(nestedOrder.data, {
3793
+ includePublicTemplateSettings: true
3794
+ }) : null;
3795
+ const inputOrder = nestedOrderTemplateSettings?.data ?? extractedRequestTemplateSettings.data;
3796
+ const invoiceLayoutConfig = extractedRequest.invoiceLayoutConfig ?? nestedOrder?.invoiceLayoutConfig;
3797
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedRequestTemplateSettings.invoiceTemplateSettings ?? nestedOrderTemplateSettings?.invoiceTemplateSettings);
3595
3798
  if (!inputOrder || typeof inputOrder !== 'object') {
3596
3799
  return {
3597
3800
  code: 400,
@@ -3603,13 +3806,13 @@ class Server {
3603
3806
  }
3604
3807
  };
3605
3808
  }
3606
- const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(data);
3809
+ const shouldLookup = this.shouldLookupLocalPrintOrder(inputOrder) || this.shouldLookupLocalPrintOrder(extractedRequest.data);
3607
3810
  this.logInfo('handleLocalPrintOrder: 是否需要查找本地订单', {
3608
3811
  shouldLookup,
3609
3812
  inputOrder,
3610
3813
  data
3611
3814
  });
3612
- const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(data) : undefined;
3815
+ const lookup = shouldLookup ? this.getLocalPrintOrderLookup(inputOrder) ?? this.getLocalPrintOrderLookup(extractedRequest.data) : undefined;
3613
3816
  let sourceOrder = inputOrder;
3614
3817
  if (shouldLookup) {
3615
3818
  if (lookup === undefined) {
@@ -3634,24 +3837,25 @@ class Server {
3634
3837
  }
3635
3838
  };
3636
3839
  }
3637
- const localOrder = await this.order.getLocalOrderByLookup(lookup);
3840
+ let localOrder = await this.order.getLocalOrderByLookup(lookup);
3638
3841
  if (!localOrder) {
3639
- return {
3640
- code: 404,
3641
- status: false,
3642
- message: '订单不存在',
3643
- data: {
3644
- printed: false,
3645
- order: null
3646
- }
3647
- };
3842
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, {
3843
+ with: ['all']
3844
+ }, 'handleLocalPrintOrder');
3845
+ if (!remoteResult.order) {
3846
+ return {
3847
+ code: remoteResult.code,
3848
+ status: false,
3849
+ message: remoteResult.message,
3850
+ data: {
3851
+ printed: false,
3852
+ order: null
3853
+ }
3854
+ };
3855
+ }
3856
+ localOrder = remoteResult.order;
3648
3857
  }
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
- };
3858
+ sourceOrder = this.mergeLocalPrintOrderInput(localOrder, inputOrder);
3655
3859
  }
3656
3860
  const checkoutData = await this.ensureCheckoutOrderNumbers({
3657
3861
  ...sourceOrder,
@@ -3670,7 +3874,9 @@ class Server {
3670
3874
  order: printableOrder,
3671
3875
  response: null,
3672
3876
  requireFullyPaid: false,
3673
- isManualPrint: true
3877
+ isManualPrint: true,
3878
+ invoiceLayoutConfig,
3879
+ invoiceTemplateSettings
3674
3880
  });
3675
3881
  return {
3676
3882
  code: 200,
@@ -3881,9 +4087,17 @@ class Server {
3881
4087
  async handleOrderCheckoutSubmit(params) {
3882
4088
  const {
3883
4089
  backendPath,
3884
- data,
3885
4090
  title
3886
4091
  } = params;
4092
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4093
+ includePublicInvoice: true
4094
+ });
4095
+ const extractedTemplateSettings = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4096
+ includePublicTemplateSettings: true
4097
+ });
4098
+ const data = extractedTemplateSettings.data;
4099
+ const invoiceLayoutConfig = extractedInvoice.invoiceLayoutConfig;
4100
+ const invoiceTemplateSettings = this.normalizePrintInvoiceTemplateSettings(extractedTemplateSettings.invoiceTemplateSettings);
3887
4101
  this.logInfo(`${title}: 开始处理`, {
3888
4102
  backendPath,
3889
4103
  order_id: data?.order_id,
@@ -3894,11 +4108,14 @@ class Server {
3894
4108
  return this.handleSyncCheckoutSubmit({
3895
4109
  backendPath,
3896
4110
  data,
3897
- title
4111
+ title,
4112
+ invoiceLayoutConfig,
4113
+ invoiceTemplateSettings
3898
4114
  });
3899
4115
  }
3900
4116
  const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
3901
- const checkoutData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4117
+ const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4118
+ const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
3902
4119
  const pendingResult = await this.handlePendingSyncCheckoutOrder({
3903
4120
  backendPath,
3904
4121
  data: checkoutData,
@@ -3912,13 +4129,17 @@ class Server {
3912
4129
  checkoutData,
3913
4130
  order: pendingOrder,
3914
4131
  response: pendingResult,
3915
- requireFullyPaid: true
4132
+ requireFullyPaid: true,
4133
+ invoiceLayoutConfig,
4134
+ invoiceTemplateSettings
3916
4135
  });
3917
4136
  }
3918
4137
  await this.dispatchCheckoutSyncTask({
3919
4138
  backendPath,
3920
4139
  data: checkoutData,
3921
- title
4140
+ title,
4141
+ invoiceLayoutConfig,
4142
+ invoiceTemplateSettings
3922
4143
  });
3923
4144
  }
3924
4145
  if (!pendingResult.data.order_id) {
@@ -3936,7 +4157,9 @@ class Server {
3936
4157
  const {
3937
4158
  backendPath,
3938
4159
  data,
3939
- title
4160
+ title,
4161
+ invoiceLayoutConfig,
4162
+ invoiceTemplateSettings
3940
4163
  } = params;
3941
4164
  if (!this.app?.request?.post) {
3942
4165
  this.logError(`${title}: sync checkout app.request 不可用`);
@@ -3952,7 +4175,9 @@ class Server {
3952
4175
  backendPath,
3953
4176
  data,
3954
4177
  title,
3955
- persistCheckoutDataWithResponse: true
4178
+ persistCheckoutDataWithResponse: true,
4179
+ invoiceLayoutConfig,
4180
+ invoiceTemplateSettings
3956
4181
  });
3957
4182
  if (syncResult.rejected) {
3958
4183
  const normalized = this.normalizeCheckoutResponse(syncResult.errorResponse);
@@ -3970,7 +4195,9 @@ class Server {
3970
4195
  checkoutData: syncResult.checkoutData,
3971
4196
  order: syncedOrder,
3972
4197
  response: syncResult.normalizedResponse,
3973
- requireFullyPaid: true
4198
+ requireFullyPaid: true,
4199
+ invoiceLayoutConfig,
4200
+ invoiceTemplateSettings
3974
4201
  // print_all 的 type=0 是收据打印;不要沿用同步成功后的券/手环打印 type=99。
3975
4202
  });
3976
4203
  }
@@ -4002,9 +4229,16 @@ class Server {
4002
4229
  }
4003
4230
  async handleOrderDraftSubmit(params) {
4004
4231
  const {
4005
- data,
4006
4232
  title
4007
4233
  } = params;
4234
+ const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4235
+ includePublicInvoice: true
4236
+ });
4237
+ const {
4238
+ data
4239
+ } = this.extractInvoiceTemplateSettings(extractedInvoice.data, {
4240
+ includePublicTemplateSettings: true
4241
+ });
4008
4242
  this.logInfo(`${title}: 开始处理`, {
4009
4243
  order_id: data?.order_id,
4010
4244
  external_sale_number: data?.external_sale_number,
@@ -4488,6 +4722,122 @@ class Server {
4488
4722
  return order;
4489
4723
  }
4490
4724
  }
4725
+ normalizeRemoteSalesOrderErrorCode(value) {
4726
+ if (value === undefined || value === null || value === '') return 500;
4727
+ const code = typeof value === 'number' ? value : Number(value);
4728
+ return Number.isFinite(code) ? code : 500;
4729
+ }
4730
+ async fetchAndPersistSalesOrderByLookup(lookup, data, title) {
4731
+ if (!this.order?.getLocalOrderByLookup || !this.order?.upsertOrdersFromRemote) {
4732
+ this.logError(`${title}: Order 模块未注册`);
4733
+ return {
4734
+ code: 500,
4735
+ message: 'Order 模块未注册',
4736
+ order: null
4737
+ };
4738
+ }
4739
+ if (!this.app?.request?.get) {
4740
+ this.logError(`${title}: app.request 不可用`);
4741
+ return {
4742
+ code: 500,
4743
+ message: 'app.request 不可用',
4744
+ order: null
4745
+ };
4746
+ }
4747
+ const backendPath = this.buildOrderSalesDetailBackendPath(String(lookup), data);
4748
+ try {
4749
+ const response = await this.app.request.get(backendPath, {
4750
+ lookup_mode: 'multi'
4751
+ }, {
4752
+ isShopApi: true,
4753
+ customToast: () => {}
4754
+ });
4755
+ if (this.isCheckoutResponseRejected(response)) {
4756
+ const responseStatusCode = typeof response?.status === 'number' ? response.status : undefined;
4757
+ const code = this.normalizeRemoteSalesOrderErrorCode(response?.code ?? responseStatusCode);
4758
+ const message = response?.message || '后端拒绝订单查询';
4759
+ this.logInfo(`${title}: 后端拒绝订单查询`, {
4760
+ lookup,
4761
+ backendPath,
4762
+ code,
4763
+ message
4764
+ });
4765
+ return {
4766
+ code,
4767
+ message,
4768
+ order: null
4769
+ };
4770
+ }
4771
+ const fresh = this.extractOrderDataFromCheckoutResponse(response);
4772
+ if (!fresh || fresh.order_id === undefined || fresh.order_id === null || String(fresh.order_id).trim() === '') {
4773
+ this.logInfo(`${title}: 后端返回订单为空`, {
4774
+ lookup,
4775
+ backendPath
4776
+ });
4777
+ return {
4778
+ code: 500,
4779
+ message: '后端返回订单为空',
4780
+ order: null
4781
+ };
4782
+ }
4783
+ await this.order.upsertOrdersFromRemote([fresh]);
4784
+ let savedOrder = await this.order.getLocalOrderByLookup(lookup);
4785
+ if (!savedOrder && fresh.order_id !== undefined && fresh.order_id !== null && String(fresh.order_id) !== String(lookup)) {
4786
+ savedOrder = await this.order.getLocalOrderByLookup(fresh.order_id);
4787
+ }
4788
+ if (!savedOrder) {
4789
+ this.logError(`${title}: 订单写入后回读失败`, {
4790
+ lookup,
4791
+ remote_order_id: fresh.order_id
4792
+ });
4793
+ return {
4794
+ code: 500,
4795
+ message: '订单写入后回读失败',
4796
+ order: null
4797
+ };
4798
+ }
4799
+ this.logInfo(`${title}: 远端订单已同步到本地`, {
4800
+ lookup,
4801
+ remote_order_id: fresh.order_id
4802
+ });
4803
+ return {
4804
+ code: 200,
4805
+ message: '',
4806
+ order: savedOrder
4807
+ };
4808
+ } catch (error) {
4809
+ const backendError = this.extractBackendErrorResponse(error);
4810
+ const errorRecord = error;
4811
+ const responseStatusCode = typeof errorRecord?.response?.status === 'number' ? errorRecord.response.status : undefined;
4812
+ if (backendError || responseStatusCode !== undefined) {
4813
+ const backendStatusCode = typeof backendError?.status === 'number' ? backendError.status : undefined;
4814
+ const code = this.normalizeRemoteSalesOrderErrorCode(backendError?.code ?? backendStatusCode ?? responseStatusCode);
4815
+ const message = backendError?.message || this.getUnknownErrorMessage(error);
4816
+ this.logInfo(`${title}: 后端订单查询失败`, {
4817
+ lookup,
4818
+ backendPath,
4819
+ code,
4820
+ message
4821
+ });
4822
+ return {
4823
+ code,
4824
+ message,
4825
+ order: null
4826
+ };
4827
+ }
4828
+ const message = this.getUnknownErrorMessage(error);
4829
+ this.logInfo(`${title}: 请求失败`, {
4830
+ lookup,
4831
+ backendPath,
4832
+ error: message
4833
+ });
4834
+ return {
4835
+ code: 500,
4836
+ message,
4837
+ order: null
4838
+ };
4839
+ }
4840
+ }
4491
4841
  handleOrderSalesDetail = async ({
4492
4842
  url,
4493
4843
  data,
@@ -4536,73 +4886,22 @@ class Server {
4536
4886
  };
4537
4887
  }
4538
4888
  }
4539
- if (!this.app?.request) {
4540
- this.logError('handleOrderSalesDetail: app.request 不可用');
4889
+ const remoteResult = await this.fetchAndPersistSalesOrderByLookup(lookup, data, 'handleOrderSalesDetail');
4890
+ if (!remoteResult.order) {
4541
4891
  return {
4542
- code: 500,
4892
+ code: remoteResult.code,
4543
4893
  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,
4894
+ message: remoteResult.message,
4603
4895
  data: null
4604
4896
  };
4605
4897
  }
4898
+ const enrichedOrder = await this.withSalesDetailProductSnapshots(remoteResult.order);
4899
+ return {
4900
+ code: 200,
4901
+ status: true,
4902
+ message: '',
4903
+ data: enrichedOrder
4904
+ };
4606
4905
  };
4607
4906
 
4608
4907
  /**