@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
@@ -20,6 +20,8 @@ export declare class OrderModule extends BaseModule implements Module {
20
20
  private storage;
21
21
  private orderSQLiteSaveQueue; /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
22
22
  private recentSqliteWriteByStorageKey;
23
+ /** 最近一次清理过期 SQLite 去重记录的时间,避免每次写入都遍历 Map */
24
+ private lastRecentSqliteWriteCleanupAtMs;
23
25
  /** 最近一次 SSE 全量拉取完成时间 */
24
26
  private lastServerFullFetchAtMs;
25
27
  constructor(name?: string, version?: string);
@@ -58,6 +60,12 @@ export declare class OrderModule extends BaseModule implements Module {
58
60
  */
59
61
  private mergeAuthoritativeOrderCollection;
60
62
  private reconcileOverwriteOrderCollections;
63
+ /**
64
+ * 惰性清理已经超过去重窗口的 SQLite 写入记录。
65
+ *
66
+ * 清理频率与去重窗口一致,既限制 Map 长期增长,也不会移除仍参与去重的记录。
67
+ */
68
+ private cleanupExpiredRecentSqliteWrites;
61
69
  /**
62
70
  * 记录订单最近一次 SQLite 写入,供 pubsub 回声去重使用。
63
71
  *
@@ -56,6 +56,8 @@ class OrderModule extends _BaseModule.BaseModule {
56
56
  storage;
57
57
  orderSQLiteSaveQueue = Promise.resolve(); /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
58
58
  recentSqliteWriteByStorageKey = new Map();
59
+ /** 最近一次清理过期 SQLite 去重记录的时间,避免每次写入都遍历 Map */
60
+ lastRecentSqliteWriteCleanupAtMs = 0;
59
61
  /** 最近一次 SSE 全量拉取完成时间 */
60
62
  lastServerFullFetchAtMs = 0;
61
63
  constructor(name, version) {
@@ -285,6 +287,24 @@ class OrderModule extends _BaseModule.BaseModule {
285
287
  return this.normalizeOrderCollectionsForIngress((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(reconciled, uidRemaps), 'overwriteExistingOrder.reconciled');
286
288
  }
287
289
 
290
+ /**
291
+ * 惰性清理已经超过去重窗口的 SQLite 写入记录。
292
+ *
293
+ * 清理频率与去重窗口一致,既限制 Map 长期增长,也不会移除仍参与去重的记录。
294
+ */
295
+ cleanupExpiredRecentSqliteWrites(now) {
296
+ const elapsedSinceCleanup = now - this.lastRecentSqliteWriteCleanupAtMs;
297
+ if (this.lastRecentSqliteWriteCleanupAtMs > 0 && elapsedSinceCleanup >= 0 && elapsedSinceCleanup < ORDER_SQLITE_DEDUPE_MS) {
298
+ return;
299
+ }
300
+ for (const [storageKey, recentWrite] of this.recentSqliteWriteByStorageKey) {
301
+ if (now - recentWrite.ts >= ORDER_SQLITE_DEDUPE_MS) {
302
+ this.recentSqliteWriteByStorageKey.delete(storageKey);
303
+ }
304
+ }
305
+ this.lastRecentSqliteWriteCleanupAtMs = now;
306
+ }
307
+
288
308
  /**
289
309
  * 记录订单最近一次 SQLite 写入,供 pubsub 回声去重使用。
290
310
  *
@@ -293,6 +313,7 @@ class OrderModule extends _BaseModule.BaseModule {
293
313
  */
294
314
  recordRecentSqliteWrite(source, orders) {
295
315
  const now = Date.now();
316
+ this.cleanupExpiredRecentSqliteWrites(now);
296
317
  for (const order of orders) {
297
318
  const storageKey = this.getOrderStorageKey(order);
298
319
  if (!storageKey) continue;
@@ -1176,6 +1197,8 @@ class OrderModule extends _BaseModule.BaseModule {
1176
1197
  }
1177
1198
  this.isProcessingSyncBatch = false;
1178
1199
  this.pendingSyncMessages = [];
1200
+ this.recentSqliteWriteByStorageKey.clear();
1201
+ this.lastRecentSqliteWriteCleanupAtMs = 0;
1179
1202
  if (this.orderDataSource?.destroy) this.orderDataSource.destroy();
1180
1203
  super.destroy();
1181
1204
  }
@@ -35,6 +35,8 @@ export interface SmallTicketProduct {
35
35
  original_price: string;
36
36
  total_original_price: string;
37
37
  extension_list: any[];
38
+ product_uid?: string | number;
39
+ booking_uid?: string | number;
38
40
  options?: Array<{
39
41
  name: string;
40
42
  num?: number;
@@ -44,6 +46,11 @@ export interface SmallTicketProduct {
44
46
  variant?: string;
45
47
  combinations?: any[];
46
48
  }
49
+ export interface SmallTicketBookingSnapshot extends Record<string, any> {
50
+ product_uid?: string | number;
51
+ booking_uid?: string | number;
52
+ }
53
+ export type SmallTicketAppointment = SmallTicketItem[] | SmallTicketBookingSnapshot[];
47
54
  export interface SmallTicketLocaleData {
48
55
  base_data: Record<string, any>;
49
56
  products: SmallTicketProduct[];
@@ -52,7 +59,7 @@ export interface SmallTicketLocaleData {
52
59
  countFee: Record<string, any>;
53
60
  };
54
61
  customer: SmallTicketItem[];
55
- appointment: SmallTicketItem[];
62
+ appointment: SmallTicketAppointment;
56
63
  delivery?: SmallTicketItem[];
57
64
  resources: Array<{
58
65
  id: string | number;
@@ -61,6 +68,7 @@ export interface SmallTicketLocaleData {
61
68
  form_title: string;
62
69
  }>;
63
70
  payment_data: SmallTicketItem[][];
71
+ wallets: SmallTicketItem[][];
64
72
  tickets: any[];
65
73
  source_shop: any[];
66
74
  refund_data: any[];
@@ -141,6 +141,11 @@ function buildLocaleData(params) {
141
141
  locale,
142
142
  currencySymbol
143
143
  }),
144
+ wallets: buildWallets({
145
+ order,
146
+ locale,
147
+ currencySymbol
148
+ }),
144
149
  tickets: Array.isArray(order.tickets) ? order.tickets : [],
145
150
  source_shop: [],
146
151
  refund_data: [],
@@ -161,12 +166,15 @@ function buildBaseData(params) {
161
166
  order_number: orderNumber,
162
167
  shop_tax_number: stringify(shopInfo.tax_number || order.shop_tax_number),
163
168
  shop_phone: stringify(shopInfo.phone || order.shop_phone),
164
- shop_address: stringify(shopInfo.address || buildShopAddress(shopInfo) || order.shop_address),
169
+ shop_address: stringify(shopInfo.receipt_address || shopInfo.address || buildShopAddress(shopInfo) || order.shop_address),
165
170
  original_payment_status: stringify(order.payment_status),
166
171
  is_repeat: 0,
167
172
  buzzer: stringify(order.buzzer || order.pickup_number || order.metadata?.buzzer),
168
173
  order_no: orderNumber,
169
- order_time: formatDateTime(order.created_at || order.sales_time || new Date(), locale)
174
+ order_time: formatDateTime(order.created_at || order.sales_time || new Date(), locale),
175
+ ...(isPresentId(order.updated_at) ? {
176
+ updated_at: formatDateTime(order.updated_at, locale)
177
+ } : {})
170
178
  };
171
179
  }
172
180
  function buildProducts(order, locale, productMap) {
@@ -200,6 +208,8 @@ function buildProducts(order, locale, productMap) {
200
208
  parentProduct,
201
209
  locale
202
210
  });
211
+ const productUid = firstPresentValue(product.metadata?.unique_identification_number, product.unique_identification_number);
212
+ const bookingUid = firstPresentValue(product.booking_uid, product.metadata?.booking_uid);
203
213
  const extensionList = buildProductExtensionList({
204
214
  product,
205
215
  locale,
@@ -214,6 +224,12 @@ function buildProducts(order, locale, productMap) {
214
224
  original_price: formatMoney(originalPrice, currencySymbol),
215
225
  total_original_price: formatMoney(new _decimal.default(toMoneyNumber(originalPrice)).mul(quantity), currencySymbol),
216
226
  extension_list: extensionList,
227
+ ...(isPresentId(productUid) ? {
228
+ product_uid: productUid
229
+ } : {}),
230
+ ...(isPresentId(bookingUid) ? {
231
+ booking_uid: bookingUid
232
+ } : {}),
217
233
  ...(options.length ? {
218
234
  options
219
235
  } : {}),
@@ -579,6 +595,9 @@ function isNonZeroId(value) {
579
595
  function isPresentId(value) {
580
596
  return value !== undefined && value !== null && value !== '';
581
597
  }
598
+ function firstPresentValue(...values) {
599
+ return values.find(value => isPresentId(value));
600
+ }
582
601
  function isSameId(left, right) {
583
602
  if (!isPresentId(left) || !isPresentId(right)) return false;
584
603
  return String(left) === String(right);
@@ -647,12 +666,13 @@ function buildFees(params) {
647
666
  });
648
667
  }
649
668
  const productExpectAmount = summary.product_expect_amount || summary.product_amount || order.product_expect_amount;
650
- const productOriginalAmount = summary.product_original_amount ?? order.product_original_amount ?? productExpectAmount;
669
+ const productOriginalAmount = firstPresentValue(summary.product_original_amount, order.product_original_amount) ?? calculateProductOriginalAmount(order);
651
670
  const totalSavingsAmount = new _decimal.default(toMoneyNumber(productOriginalAmount)).minus(toMoneyNumber(productExpectAmount)).plus(toMoneyNumber(discountAmount));
652
671
  return {
653
672
  fees,
654
673
  countFee: {
655
674
  total_amount: formatMoney(summary.total_amount || order.total_amount, currencySymbol),
675
+ original_item_amount: formatMoney(productOriginalAmount, currencySymbol),
656
676
  product_expect_amount: formatMoney(productExpectAmount, currencySymbol),
657
677
  expect_amount: formatMoney(summary.expect_amount || order.expect_amount || summary.total_amount || order.total_amount, currencySymbol),
658
678
  product_quantity: toNumber(summary.product_quantity || products.reduce((sum, product) => sum + product.product_quantity, 0), 0),
@@ -675,6 +695,13 @@ function buildFees(params) {
675
695
  }
676
696
  };
677
697
  }
698
+ function calculateProductOriginalAmount(order) {
699
+ return normalizeArray(order.products).reduce((total, product) => {
700
+ const quantity = toNumber(product.product_quantity ?? product.quantity ?? product.num ?? 1, 1);
701
+ const originalPrice = product.original_price ?? product.selling_price ?? product.payment_price ?? product.price ?? 0;
702
+ return total.plus(new _decimal.default(toMoneyNumber(originalPrice)).mul(quantity));
703
+ }, new _decimal.default(0));
704
+ }
678
705
  function buildCustomer(order, locale) {
679
706
  return [{
680
707
  item: label(locale, 'contactName'),
@@ -712,6 +739,27 @@ function buildDelivery(order, locale) {
712
739
  return items;
713
740
  }
714
741
  function buildAppointment(order, locale) {
742
+ const bookings = normalizeArray(order.bookings);
743
+ if (bookings.length) {
744
+ return bookings.map(booking => {
745
+ const snapshot = {
746
+ ...booking
747
+ };
748
+ delete snapshot.product_uid;
749
+ delete snapshot.booking_uid;
750
+ const productUid = firstPresentValue(booking.product_uid, booking.metadata?.product_uid);
751
+ const bookingUid = firstPresentValue(booking.metadata?.unique_identification_number, booking.booking_uid);
752
+ return {
753
+ ...snapshot,
754
+ ...(isPresentId(productUid) ? {
755
+ product_uid: productUid
756
+ } : {}),
757
+ ...(isPresentId(bookingUid) ? {
758
+ booking_uid: bookingUid
759
+ } : {})
760
+ };
761
+ });
762
+ }
715
763
  const tableName = order.table_name || order.metadata?.table_name || order.metadata?.table?.name || order.form_data?.table_name;
716
764
  if (!tableName) return [];
717
765
  return [{
@@ -729,41 +777,63 @@ function buildResources(order, locale) {
729
777
  }
730
778
  function buildPaymentData(params) {
731
779
  const payments = normalizeArray(params.order.payments);
732
- return payments.filter(payment => isPrintablePayment(payment)).map(payment => {
733
- const items = [{
734
- item: paymentMethodName(payment, params.locale),
735
- value: formatMoney(payment.amount ?? payment.origin_amount, params.currencySymbol)
736
- }];
737
- const fundRecord = getFundRecord(payment);
738
- if (fundRecord) {
739
- items.push(...buildFundRecordPaymentItems({
740
- payment,
741
- fundRecord,
742
- locale: params.locale,
743
- currencySymbol: params.currencySymbol
744
- }));
745
- } else {
746
- const remainingAmount = payment.remaining_amount ?? payment.balance ?? payment.metadata?.remaining_amount;
747
- if (remainingAmount !== undefined && remainingAmount !== null) {
748
- items.push({
749
- item: label(params.locale, 'remainingAmount'),
750
- value: formatMoney(remainingAmount, params.currencySymbol)
751
- });
752
- }
780
+ return payments.filter(payment => isPrintablePayment(payment)).map(payment => buildPaymentItems({
781
+ payment,
782
+ locale: params.locale,
783
+ currencySymbol: params.currencySymbol,
784
+ includeFundRecordBalance: false
785
+ }));
786
+ }
787
+ function buildWallets(params) {
788
+ return normalizeArray(params.order.payments).filter(payment => isPrintablePayment(payment) && !!getFundRecord(payment)).map(payment => buildPaymentItems({
789
+ payment,
790
+ locale: params.locale,
791
+ currencySymbol: params.currencySymbol,
792
+ includeFundRecordBalance: true
793
+ }));
794
+ }
795
+ function buildPaymentItems(params) {
796
+ const {
797
+ payment,
798
+ locale,
799
+ currencySymbol,
800
+ includeFundRecordBalance
801
+ } = params;
802
+ const items = [{
803
+ item: paymentMethodName(payment, locale),
804
+ value: formatMoney(payment.amount ?? payment.origin_amount, currencySymbol)
805
+ }];
806
+ const fundRecord = getFundRecord(payment);
807
+ if (fundRecord) {
808
+ items.push(...buildFundRecordPaymentItems({
809
+ payment,
810
+ fundRecord,
811
+ locale,
812
+ currencySymbol,
813
+ includeBalance: includeFundRecordBalance
814
+ }));
815
+ } else {
816
+ const remainingAmount = payment.remaining_amount ?? payment.balance ?? payment.metadata?.remaining_amount;
817
+ if (remainingAmount !== undefined && remainingAmount !== null) {
818
+ items.push({
819
+ item: label(locale, 'remainingAmount'),
820
+ value: formatMoney(remainingAmount, currencySymbol)
821
+ });
753
822
  }
754
- items.push({
755
- item: formatDateTime(payment.paid_at || payment.created_at || payment.updated_at || new Date(), params.locale),
756
- value: ''
757
- });
758
- return items;
823
+ }
824
+ items.push({
825
+ item: formatDateTime(payment.paid_at || payment.created_at || payment.updated_at || new Date(), locale),
826
+ value: ''
759
827
  });
828
+ return items;
760
829
  }
761
830
  function buildFundRecordPaymentItems(params) {
762
831
  const {
763
832
  payment,
764
833
  fundRecord,
765
834
  locale,
766
- currencySymbol
835
+ currencySymbol,
836
+ includeBalance
767
837
  } = params;
768
838
  const items = [];
769
839
  const usingBeforeValue = fundRecord.using_before_value;
@@ -774,13 +844,15 @@ function buildFundRecordPaymentItems(params) {
774
844
  item: label(locale, 'redeemPoints'),
775
845
  value: trimDecimal(usedPoints)
776
846
  });
777
- items.push({
778
- item: label(locale, 'remainingPoints'),
779
- value: trimDecimal(new _decimal.default(toMoneyNumber(usingAfterValue)))
780
- });
847
+ if (includeBalance) {
848
+ items.push({
849
+ item: label(locale, 'remainingPoints'),
850
+ value: trimDecimal(new _decimal.default(toMoneyNumber(usingAfterValue)))
851
+ });
852
+ }
781
853
  return items;
782
854
  }
783
- if (usingAfterValue !== undefined && usingAfterValue !== null) {
855
+ if (includeBalance && usingAfterValue !== undefined && usingAfterValue !== null) {
784
856
  items.push({
785
857
  item: label(locale, 'remainingAmount'),
786
858
  value: formatMoney(usingAfterValue, currencySymbol)
@@ -136,6 +136,13 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
136
136
  private getAuthoritativeBundleUnitPrice;
137
137
  private mergeProductForPriceQuery;
138
138
  protected getSubmitOrderSalesChannel(): string | undefined;
139
+ /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
140
+ protected isInvoiceOpenDataCacheCurrent(): boolean;
141
+ private getFulfillmentRefModeConfigs;
142
+ private getFulfillmentRefMode;
143
+ private getCachedInvoiceLayoutConfig;
144
+ private getCachedInvoiceTemplateSettings;
145
+ private buildSubmitPayloadEnhancerWithOpenDataConfig;
139
146
  /**
140
147
  * 工厂入口:根据子模块名实例化对应模块。
141
148
  * 默认使用 BookingByStep 公共 `createModule`。子类若需引入额外类型(如 customer),
@@ -35,6 +35,9 @@ var _transformBaseProductToOrderProduct = require("./utils/transformBaseProductT
35
35
  var _quotationPrice = require("./utils/quotationPrice");
36
36
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
37
37
  const SERVER_ORDER_CHANGED_EVENT = 'order:onOrdersChanged';
38
+ const OS_FULFILLMENT_REF_MODE_FIELD = '_os_fulfillment_ref_mode';
39
+ const OS_INVOICE_LAYOUT_CONFIG_FIELD = '_os_invoice_layout_config';
40
+ const OS_INVOICE_TEMPLATE_SETTINGS_FIELD = '_os_invoice_template_settings';
38
41
  const WALLET_PAYMENT_CODE_BY_TAG = {
39
42
  product_voucher: 'PRODUCTVOUCHER',
40
43
  gift_card: 'GIFTCARD',
@@ -847,6 +850,73 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
847
850
  return this.otherParams?.channel;
848
851
  }
849
852
 
853
+ /** Subclasses with target-scoped OpenData override this guard before reusing the cache. */
854
+ isInvoiceOpenDataCacheCurrent() {
855
+ return true;
856
+ }
857
+ getFulfillmentRefModeConfigs() {
858
+ const candidates = [this.otherParams?.openData, this.otherParams?.dineInConfig, this.store?.openData?.getOpenData?.()];
859
+ return candidates.filter(candidate => candidate && typeof candidate === 'object' && !Array.isArray(candidate));
860
+ }
861
+ getFulfillmentRefMode() {
862
+ for (const config of this.getFulfillmentRefModeConfigs()) {
863
+ const mode = config['fulfillment.fulfillment_ref_mode'];
864
+ if (mode === undefined || mode === null) continue;
865
+ const normalized = String(mode).trim();
866
+ if (normalized) return normalized;
867
+ }
868
+ return undefined;
869
+ }
870
+ getCachedInvoiceLayoutConfig() {
871
+ const openDataModule = this.store?.openData;
872
+ if (typeof openDataModule?.getInvoiceLayoutConfig !== 'function') return undefined;
873
+ if (!this.isInvoiceOpenDataCacheCurrent()) return undefined;
874
+ try {
875
+ return openDataModule.getInvoiceLayoutConfig();
876
+ } catch (error) {
877
+ this.logWarning('读取 Invoice OpenData 布局失败,提交时不注入布局配置', {
878
+ error: error instanceof Error ? error.message : String(error)
879
+ });
880
+ return undefined;
881
+ }
882
+ }
883
+ getCachedInvoiceTemplateSettings() {
884
+ const openDataModule = this.store?.openData;
885
+ if (typeof openDataModule?.getInvoiceTemplateSettings !== 'function') return undefined;
886
+ if (!this.isInvoiceOpenDataCacheCurrent()) return undefined;
887
+ try {
888
+ return openDataModule.getInvoiceTemplateSettings();
889
+ } catch (error) {
890
+ this.logWarning('读取 Invoice OpenData 模板配置失败,提交时不注入模板配置', {
891
+ error: error instanceof Error ? error.message : String(error)
892
+ });
893
+ return undefined;
894
+ }
895
+ }
896
+ buildSubmitPayloadEnhancerWithOpenDataConfig(enhancePayload) {
897
+ const fulfillmentRefMode = this.getFulfillmentRefMode();
898
+ const invoiceLayoutConfig = this.getCachedInvoiceLayoutConfig();
899
+ const invoiceTemplateSettings = this.getCachedInvoiceTemplateSettings();
900
+ if (!fulfillmentRefMode && !invoiceLayoutConfig && !invoiceTemplateSettings) {
901
+ return enhancePayload;
902
+ }
903
+ return (payload, ctx) => {
904
+ const enhancedPayload = enhancePayload ? enhancePayload(payload, ctx) : payload;
905
+ return {
906
+ ...enhancedPayload,
907
+ ...(fulfillmentRefMode ? {
908
+ [OS_FULFILLMENT_REF_MODE_FIELD]: fulfillmentRefMode
909
+ } : {}),
910
+ ...(invoiceLayoutConfig ? {
911
+ [OS_INVOICE_LAYOUT_CONFIG_FIELD]: (0, _lodashEs.cloneDeep)(invoiceLayoutConfig)
912
+ } : {}),
913
+ ...(invoiceTemplateSettings ? {
914
+ [OS_INVOICE_TEMPLATE_SETTINGS_FIELD]: (0, _lodashEs.cloneDeep)(invoiceTemplateSettings)
915
+ } : {})
916
+ };
917
+ };
918
+ }
919
+
850
920
  /**
851
921
  * 工厂入口:根据子模块名实例化对应模块。
852
922
  * 默认使用 BookingByStep 公共 `createModule`。子类若需引入额外类型(如 customer),
@@ -1442,9 +1512,20 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1442
1512
  if (!params.products.length) return false;
1443
1513
  const hasDraftProduct = params.products.some(product => !this.isPersistedOrderProduct(product));
1444
1514
  if (hasDraftProduct) return false;
1445
- const hasSelectedDiscount = params.currentDiscountList.some(discount => discount.isSelected || discount.isEditMode || discount.isManualSelect);
1446
- if (hasSelectedDiscount) return false;
1447
- return !params.nextDiscountList.some(discount => discount.isSelected || discount.isEditMode || discount.isManualSelect);
1515
+
1516
+ // 编辑详情 hydrate 时,商品行上的历史券卡会被还原为 isEditMode=true、
1517
+ // isSelected=true 的只读记录。它们表示“该折扣已在原订单结算”,不是用户在
1518
+ // 当前编辑会话中新做的选择,不能据此放行刚从 prepare/config 拉回来的折扣。
1519
+ // 否则后续 Rules.calcDiscount 会把没有 isManualSelect 标记的可用 runtime 卡
1520
+ // 当作自动候选,再次写入 tempOrder。这里只识别非 isEditMode 的 runtime 状态;
1521
+ // 新建单、编辑态新增商品以及用户本次明确选择/取消折扣的原有流程保持不变。
1522
+ const hasRuntimeDiscountSelection = params.currentDiscountList.some(discount => !discount.isEditMode && (discount.isSelected || discount.isManualSelect));
1523
+ if (hasRuntimeDiscountSelection) return false;
1524
+
1525
+ // nextDiscountList 已经合并了历史只读折扣与本次获取的 runtime 折扣,
1526
+ // 因此这里同样必须排除 isEditMode;只有 runtime 条目已有明确选择状态时,
1527
+ // 才允许继续进入折扣重算。
1528
+ return !params.nextDiscountList.some(discount => !discount.isEditMode && (discount.isSelected || discount.isManualSelect));
1448
1529
  }
1449
1530
  mergeCurrentDiscountSelectionState(discountList, currentDiscountList) {
1450
1531
  if (!currentDiscountList.length) return discountList;
@@ -1989,6 +2070,26 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1989
2070
  // this.store.order.persistTempOrder();
1990
2071
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
1991
2072
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2073
+ const virtualSettlementEnhancePayload = virtualSettlement ? (payload, ctx) => {
2074
+ const callerPayload = params?.enhancePayload ? params.enhancePayload(payload, ctx) : payload;
2075
+ const currencySettlementConfig = callerPayload.metadata?.currency_settlement_config || {};
2076
+ return {
2077
+ ...callerPayload,
2078
+ metadata: {
2079
+ ...(callerPayload.metadata || {}),
2080
+ currency_settlement_config: {
2081
+ ...currencySettlementConfig,
2082
+ settlement_type: 'virtual_currency',
2083
+ settlement_ids: [virtualSettlement.currencyProductId],
2084
+ currency_display_snapshot: (0, _lodashEs.cloneDeep)(currencySettlementConfig.currency_display_snapshot ?? (0, _virtualSettlement.buildCurrencyDisplaySnapshot)(virtualSettlement.snapshot))
2085
+ }
2086
+ },
2087
+ currency_code: virtualSettlement.currencyCode,
2088
+ currency_symbol: virtualSettlement.currencySymbol,
2089
+ currency_format: virtualSettlement.currencyFormat
2090
+ };
2091
+ } : params?.enhancePayload;
2092
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(virtualSettlementEnhancePayload);
1992
2093
  const submitParams = {
1993
2094
  cacheId: this.cacheId,
1994
2095
  platform: this.otherParams?.platform,
@@ -2012,28 +2113,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2012
2113
  } : params?.confirmPendingVoucherPayments !== undefined ? {
2013
2114
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
2014
2115
  } : {}),
2015
- ...(virtualSettlement ? {
2016
- enhancePayload: (payload, ctx) => {
2017
- const callerPayload = params?.enhancePayload ? params.enhancePayload(payload, ctx) : payload;
2018
- const currencySettlementConfig = callerPayload.metadata?.currency_settlement_config || {};
2019
- return {
2020
- ...callerPayload,
2021
- metadata: {
2022
- ...(callerPayload.metadata || {}),
2023
- currency_settlement_config: {
2024
- ...currencySettlementConfig,
2025
- settlement_type: 'virtual_currency',
2026
- settlement_ids: [virtualSettlement.currencyProductId],
2027
- currency_display_snapshot: (0, _lodashEs.cloneDeep)(currencySettlementConfig.currency_display_snapshot ?? (0, _virtualSettlement.buildCurrencyDisplaySnapshot)(virtualSettlement.snapshot))
2028
- }
2029
- },
2030
- currency_code: virtualSettlement.currencyCode,
2031
- currency_symbol: virtualSettlement.currencySymbol,
2032
- currency_format: virtualSettlement.currencyFormat
2033
- };
2034
- }
2035
- } : params?.enhancePayload !== undefined ? {
2036
- enhancePayload: params.enhancePayload
2116
+ ...(enhancePayload !== undefined ? {
2117
+ enhancePayload
2037
2118
  } : {})
2038
2119
  };
2039
2120
  return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
@@ -2059,6 +2140,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2059
2140
  }
2060
2141
  const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
2061
2142
  const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
2143
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(params?.enhancePayload);
2062
2144
  const submitParams = {
2063
2145
  cacheId: this.cacheId,
2064
2146
  platform: this.otherParams?.platform,
@@ -2078,8 +2160,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2078
2160
  ...(params?.confirmPendingVoucherPayments !== undefined ? {
2079
2161
  confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
2080
2162
  } : {}),
2081
- ...(params?.enhancePayload !== undefined ? {
2082
- enhancePayload: params.enhancePayload
2163
+ ...(enhancePayload !== undefined ? {
2164
+ enhancePayload
2083
2165
  } : {})
2084
2166
  };
2085
2167
  return this.store.order.submitTempOrderAsync(submitParams);
@@ -2129,7 +2211,18 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2129
2211
  channel: context.channel,
2130
2212
  type: context.type
2131
2213
  });
2132
- const response = await this.request.post('/local/print-order', payload, {
2214
+ const invoiceLayoutConfig = this.getCachedInvoiceLayoutConfig();
2215
+ const invoiceTemplateSettings = this.getCachedInvoiceTemplateSettings();
2216
+ const printPayload = {
2217
+ ...payload,
2218
+ ...(invoiceLayoutConfig ? {
2219
+ invoice: (0, _lodashEs.cloneDeep)(invoiceLayoutConfig)
2220
+ } : {}),
2221
+ ...(invoiceTemplateSettings ? {
2222
+ template_settings: (0, _lodashEs.cloneDeep)(invoiceTemplateSettings)
2223
+ } : {})
2224
+ };
2225
+ const response = await this.request.post('/local/print-order', printPayload, {
2133
2226
  osServer: true,
2134
2227
  customToast: () => {}
2135
2228
  });
@@ -2152,6 +2245,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2152
2245
  if (!this.store.order) throw new Error('order 模块未初始化');
2153
2246
  const businessCode = params.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code;
2154
2247
  const channel = params.channel ?? this.getSubmitOrderSalesChannel();
2248
+ const enhancePayload = this.buildSubmitPayloadEnhancerWithOpenDataConfig(params.enhancePayload);
2155
2249
  const syncParams = {
2156
2250
  ...params,
2157
2251
  checkoutSyncTaskEnabled: this.shouldUseCheckoutSyncTask(),
@@ -2160,6 +2254,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2160
2254
  } : {}),
2161
2255
  ...(channel !== undefined ? {
2162
2256
  channel
2257
+ } : {}),
2258
+ ...(enhancePayload !== undefined ? {
2259
+ enhancePayload
2163
2260
  } : {})
2164
2261
  };
2165
2262
  const syncState = this.store.order.getOrderSyncState();
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 5 | 4 | 3 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -345,7 +345,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
345
345
  count: number;
346
346
  left: number;
347
347
  summaryCount: number;
348
- status: "lots_of_space" | "filling_up_fast" | "sold_out";
348
+ status: "sold_out" | "lots_of_space" | "filling_up_fast";
349
349
  }[];
350
350
  /**
351
351
  * 找到多个资源的公共可用时间段
@@ -1,9 +1,9 @@
1
1
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BookingTicketCartView, BookingTicketProductCatalogView, BookingTicketState, IGetCustomerListParams, ICustomer, ILoadProductsParams, ILoadProductDetailParams, IScanResult } from './types';
3
3
  import type { ProductData } from '../../modules';
4
- import type { OpenDataConfig } from '../../modules/OpenData';
4
+ import { type InvoiceLayoutConfig, type InvoiceTemplateSettings, type OpenDataConfig } from '../../modules/OpenData';
5
5
  import { type BookingContextConfig, type BookingContextDateInput, type BookingContextResource, type BookingContextResourceMap, type BookingContextState, type InitBookingContextParams, type LoadBookingConfigParams, type LoadBookingResourcesParams, type ResourceError, type BookingCalcContext } from '../../modules/BookingContext';
6
- import type { OrderCustomerView } from '../../modules/Order/types';
6
+ import type { OrderCustomerView, OrderCustomerChangePayload } from '../../modules/Order/types';
7
7
  import { BaseSalesImpl } from '../BaseSales';
8
8
  import type { BaseSalesScanCodeResult } from '../BaseSales/types';
9
9
  import type { ISalesDetail } from '../BaseSales/utils/parseSalesResponse';
@@ -30,7 +30,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
30
30
  private orderCustomerDiscountRefreshInFlight;
31
31
  private orderCustomerDiscountRefreshCustomerId;
32
32
  private orderCustomerPromotionRefreshInFlight;
33
- private loadOpenDataConfigInFlight;
33
+ protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
34
+ protected loadOpenDataInFlightTarget: string | null;
35
+ protected openDataTarget: string | null;
34
36
  /**
35
37
  * 在 BaseSales 默认模块清单(products/order/salesSummary/schedule)基础上追加 customer + bookingContext。
36
38
  * bookingContext 是 SalesSdk 加车下沉所需的「预约 UI 上下文」(bookingConfig / resourcesOrigin / date)。
@@ -43,10 +45,13 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
43
45
  */
44
46
  protected createSubModule(moduleName: string): Module | null;
45
47
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
46
- private getBookingTicketBusinessCode;
48
+ protected getBookingTicketBusinessCode(): string | null;
47
49
  private getIdGeneratorPlugin;
48
50
  private loadOpenDataConfig;
49
51
  getOpenData(): Promise<OpenDataConfig | null>;
52
+ getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
53
+ getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
54
+ protected isInvoiceOpenDataCacheCurrent(): boolean;
50
55
  /** 清除店铺级 OpenData;下一次 getOpenData 时会重新拉取。 */
51
56
  clearOpenDataCache(): void;
52
57
  private getShortNumberOrDeviceId;
@@ -260,7 +265,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
260
265
  * 同时透传 OrderModule 内部 emit 的 `order:onOrderCustomerChange` 之外,
261
266
  * 在 BookingTicket 命名空间也 emit 一次,方便上层统一从 Solution 订阅。
262
267
  */
263
- setOrderCustomer(customer: ICustomer | null): void;
268
+ setOrderCustomer(customer: ICustomer | null, options?: {
269
+ source?: OrderCustomerChangePayload['source'];
270
+ }): void;
264
271
  /**
265
272
  * 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
266
273
  */
@@ -332,7 +339,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
332
339
  * 获取当前的客户搜索条件
333
340
  * @returns 当前搜索条件
334
341
  */
335
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
342
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
336
343
  /**
337
344
  * 获取客户列表状态(包含滚动加载相关状态)
338
345
  * @returns 客户状态
@@ -451,19 +458,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
451
458
  * 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
452
459
  * 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
453
460
  */
454
- decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
455
- action: "reloadCatalog";
456
- } | {
457
- action: "add";
458
- cacheItem: any;
459
- } | {
460
- action: "requiresDetail";
461
- payload: AddProductRequiresDetailPayload;
462
- } | {
463
- action: "requiresBookingEdit";
464
- cacheItem: any;
465
- payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
466
- };
461
+ decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
467
462
  /** 规格弹窗 callback 后的第二段决策。 */
468
463
  decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
469
464
  /**