@pisell/pisellos 2.2.301 → 2.2.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  3. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  4. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  5. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  6. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  7. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  8. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  9. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  10. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +10 -7
  11. package/dist/modules/Discount/index.d.ts +2 -0
  12. package/dist/modules/Discount/index.js +41 -5
  13. package/dist/modules/Discount/types.d.ts +21 -0
  14. package/dist/modules/OpenData/index.d.ts +15 -2
  15. package/dist/modules/OpenData/index.js +307 -19
  16. package/dist/modules/OpenData/types.d.ts +55 -0
  17. package/dist/modules/OpenData/types.js +7 -1
  18. package/dist/modules/OpenData/utils.d.ts +21 -1
  19. package/dist/modules/OpenData/utils.js +138 -0
  20. package/dist/modules/Order/index.d.ts +50 -7
  21. package/dist/modules/Order/index.js +1690 -815
  22. package/dist/modules/Order/salesNotes.d.ts +31 -0
  23. package/dist/modules/Order/salesNotes.js +492 -0
  24. package/dist/modules/Order/types.d.ts +117 -10
  25. package/dist/modules/Order/types.js +6 -1
  26. package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
  27. package/dist/modules/Order/utils/virtualSettlement.js +284 -0
  28. package/dist/modules/Order/utils.d.ts +5 -1
  29. package/dist/modules/Order/utils.js +138 -33
  30. package/dist/modules/Payment/walletpass.js +20 -21
  31. package/dist/modules/Product/types.d.ts +23 -0
  32. package/dist/modules/ProductList/index.d.ts +2 -1
  33. package/dist/modules/ProductList/index.js +100 -20
  34. package/dist/modules/ProductList/types.d.ts +10 -0
  35. package/dist/modules/Rules/index.d.ts +5 -0
  36. package/dist/modules/Rules/index.js +30 -14
  37. package/dist/modules/SalesSummary/index.d.ts +1 -0
  38. package/dist/modules/SalesSummary/index.js +4 -3
  39. package/dist/modules/SalesSummary/types.d.ts +1 -0
  40. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  41. package/dist/modules/SalesSummary/utils.js +21 -1
  42. package/dist/plugins/request.d.ts +2 -1
  43. package/dist/plugins/request.js +4 -2
  44. package/dist/server/index.d.ts +21 -0
  45. package/dist/server/index.js +1774 -1285
  46. package/dist/server/modules/order/types.d.ts +50 -0
  47. package/dist/server/modules/order/types.js +2 -0
  48. package/dist/server/utils/small-ticket.d.ts +4 -1
  49. package/dist/server/utils/small-ticket.js +461 -96
  50. package/dist/solution/BaseSales/index.d.ts +26 -3
  51. package/dist/solution/BaseSales/index.js +1492 -873
  52. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
  53. package/dist/solution/BaseSales/utils/parseSalesResponse.js +1 -1
  54. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  55. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +72 -9
  56. package/dist/solution/BookingByStep/index.d.ts +1 -1
  57. package/dist/solution/BookingTicket/index.d.ts +8 -3
  58. package/dist/solution/BookingTicket/index.js +558 -443
  59. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  60. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  61. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  62. package/dist/solution/RegisterAndLogin/config.js +95 -40
  63. package/dist/solution/RegisterAndLogin/index.js +4 -1
  64. package/dist/solution/Sales/index.d.ts +16 -1
  65. package/dist/solution/Sales/index.js +338 -63
  66. package/dist/solution/Sales/types.d.ts +10 -0
  67. package/dist/solution/ScanOrder/index.d.ts +1 -0
  68. package/dist/solution/ScanOrder/index.js +31 -27
  69. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  70. package/dist/solution/ScanOrder/utils.js +2 -0
  71. package/dist/solution/UnifiedBookingSales/index.d.ts +20 -6
  72. package/dist/solution/UnifiedBookingSales/index.js +874 -527
  73. package/dist/solution/UnifiedBookingSales/types.d.ts +21 -2
  74. package/dist/solution/VenueBooking/index.d.ts +1 -0
  75. package/dist/solution/VenueBooking/index.js +8 -4
  76. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  77. package/lib/model/strategy/adapter/dataVariant/evaluator.js +137 -6
  78. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  79. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  80. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  81. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  82. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  83. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  84. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  85. package/lib/model/strategy/adapter/promotion/index.js +1 -46
  86. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +4 -0
  87. package/lib/modules/Discount/index.d.ts +2 -0
  88. package/lib/modules/Discount/index.js +34 -2
  89. package/lib/modules/Discount/types.d.ts +21 -0
  90. package/lib/modules/OpenData/index.d.ts +15 -2
  91. package/lib/modules/OpenData/index.js +230 -3
  92. package/lib/modules/OpenData/types.d.ts +55 -0
  93. package/lib/modules/OpenData/types.js +9 -1
  94. package/lib/modules/OpenData/utils.d.ts +21 -1
  95. package/lib/modules/OpenData/utils.js +126 -0
  96. package/lib/modules/Order/index.d.ts +50 -7
  97. package/lib/modules/Order/index.js +704 -64
  98. package/lib/modules/Order/salesNotes.d.ts +31 -0
  99. package/lib/modules/Order/salesNotes.js +382 -0
  100. package/lib/modules/Order/types.d.ts +117 -10
  101. package/lib/modules/Order/types.js +5 -1
  102. package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
  103. package/lib/modules/Order/utils/virtualSettlement.js +262 -0
  104. package/lib/modules/Order/utils.d.ts +5 -1
  105. package/lib/modules/Order/utils.js +127 -21
  106. package/lib/modules/Payment/walletpass.js +14 -11
  107. package/lib/modules/Product/types.d.ts +23 -0
  108. package/lib/modules/ProductList/index.d.ts +2 -1
  109. package/lib/modules/ProductList/index.js +47 -2
  110. package/lib/modules/ProductList/types.d.ts +10 -0
  111. package/lib/modules/Rules/index.d.ts +5 -0
  112. package/lib/modules/Rules/index.js +22 -12
  113. package/lib/modules/SalesSummary/index.d.ts +1 -0
  114. package/lib/modules/SalesSummary/index.js +4 -2
  115. package/lib/modules/SalesSummary/types.d.ts +1 -0
  116. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  117. package/lib/modules/SalesSummary/utils.js +17 -1
  118. package/lib/plugins/request.d.ts +2 -1
  119. package/lib/plugins/request.js +3 -2
  120. package/lib/server/index.d.ts +21 -0
  121. package/lib/server/index.js +492 -108
  122. package/lib/server/modules/order/types.d.ts +50 -0
  123. package/lib/server/modules/order/types.js +1 -0
  124. package/lib/server/utils/small-ticket.d.ts +4 -1
  125. package/lib/server/utils/small-ticket.js +427 -72
  126. package/lib/solution/BaseSales/index.d.ts +26 -3
  127. package/lib/solution/BaseSales/index.js +439 -30
  128. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +2 -0
  129. package/lib/solution/BaseSales/utils/parseSalesResponse.js +1 -1
  130. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  131. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +38 -0
  132. package/lib/solution/BookingByStep/index.d.ts +1 -1
  133. package/lib/solution/BookingTicket/index.d.ts +8 -3
  134. package/lib/solution/BookingTicket/index.js +60 -15
  135. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -0
  136. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  137. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  138. package/lib/solution/RegisterAndLogin/config.js +49 -8
  139. package/lib/solution/RegisterAndLogin/index.js +4 -1
  140. package/lib/solution/Sales/index.d.ts +16 -1
  141. package/lib/solution/Sales/index.js +168 -1
  142. package/lib/solution/Sales/types.d.ts +10 -0
  143. package/lib/solution/ScanOrder/index.d.ts +1 -0
  144. package/lib/solution/ScanOrder/index.js +5 -1
  145. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  146. package/lib/solution/ScanOrder/utils.js +1 -0
  147. package/lib/solution/UnifiedBookingSales/index.d.ts +20 -6
  148. package/lib/solution/UnifiedBookingSales/index.js +169 -16
  149. package/lib/solution/UnifiedBookingSales/types.d.ts +21 -2
  150. package/lib/solution/VenueBooking/index.d.ts +1 -0
  151. package/lib/solution/VenueBooking/index.js +5 -1
  152. package/package.json +1 -1
@@ -9,6 +9,7 @@ var _exportNames = {
9
9
  };
10
10
  exports.default = exports.SalesImpl = exports.Sales = void 0;
11
11
  var _dayjs = _interopRequireDefault(require("dayjs"));
12
+ var _lodashEs = require("lodash-es");
12
13
  var _BaseModule = require("../../modules/BaseModule");
13
14
  var _types = require("./types");
14
15
  Object.keys(_types).forEach(function (key) {
@@ -22,8 +23,11 @@ Object.keys(_types).forEach(function (key) {
22
23
  }
23
24
  });
24
25
  });
26
+ var _OpenData = require("../../modules/OpenData");
25
27
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26
28
  const UNPAID_PAYMENT_STATUSES = new Set(['unpaid', 'partially_paid', 'payment_processing']);
29
+ const INVOICE_OPEN_DATA_SECTION_CODES = ['documents'];
30
+ const INVOICE_OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
27
31
 
28
32
  /**
29
33
  * Sales 解决方案基础骨架
@@ -46,11 +50,22 @@ class SalesImpl extends _BaseModule.BaseModule {
46
50
  otherParams = {};
47
51
  request;
48
52
  logger; // LoggerManager 实例
49
-
53
+ invoiceOpenDataModule = null;
54
+ invoiceOpenDataTarget = null;
55
+ invoiceOpenDataInFlight = null;
56
+ invoiceOpenDataInFlightTarget = null;
50
57
  constructor(name, version) {
51
58
  super(name, version);
52
59
  }
53
60
  async initialize(core, options = {}) {
61
+ if (this.invoiceOpenDataModule) {
62
+ try {
63
+ this.clearOpenDataCache();
64
+ this.invoiceOpenDataModule.destroy();
65
+ } catch (error) {
66
+ console.warn('[Sales] 旧 Invoice OpenData 子模块销毁失败', error);
67
+ }
68
+ }
54
69
  this.core = core;
55
70
  this.options = options;
56
71
  this.otherParams = options.otherParams || {};
@@ -71,6 +86,18 @@ class SalesImpl extends _BaseModule.BaseModule {
71
86
  // 在这里补充从 Server/Order 读取 currentDay 原始数据并转换为预约列表。
72
87
 
73
88
  this.logger = appPlugin?.getApp().logger;
89
+ this.invoiceOpenDataTarget = null;
90
+ this.invoiceOpenDataInFlight = null;
91
+ this.invoiceOpenDataInFlightTarget = null;
92
+ this.invoiceOpenDataModule = new _OpenData.OpenDataModule(`${this.name}_invoiceOpenData`);
93
+ this.core.registerModule(this.invoiceOpenDataModule, {
94
+ initialState: {},
95
+ otherParams: {
96
+ fatherModule: this.name,
97
+ openCache: false,
98
+ platform: this.otherParams?.platform
99
+ }
100
+ });
74
101
  await this.core.effects.emit(`${this.name}:onInited`, {});
75
102
  }
76
103
 
@@ -139,11 +166,151 @@ class SalesImpl extends _BaseModule.BaseModule {
139
166
  return orderModule.changeBookingStatus(params);
140
167
  }
141
168
 
169
+ /**
170
+ * 将显式 channel 或当前运行上下文归一为 Invoice OpenData 使用的销售入口。
171
+ * Legacy Sales 的系统上下文没有可打印的 system target,因此统一回退 pos。
172
+ */
173
+ getInvoiceSalesChannel(channel) {
174
+ const contextChannel = this.core?.context?.getChannel?.();
175
+ const normalized = String(channel ?? contextChannel ?? '').trim();
176
+ return !normalized || normalized === 'system' ? 'pos' : normalized;
177
+ }
178
+ async loadInvoiceOpenData(businessCode, channel) {
179
+ if (!this.invoiceOpenDataModule) {
180
+ throw new Error('Sales Invoice OpenData 模块未初始化');
181
+ }
182
+ const target = `${businessCode}+${channel}`;
183
+ const cachedData = this.invoiceOpenDataModule.getOpenData();
184
+ const lastFetchedAt = this.invoiceOpenDataModule.getLastFetchedAt();
185
+ if (cachedData && this.invoiceOpenDataTarget === target && typeof lastFetchedAt === 'number' && Date.now() - lastFetchedAt < INVOICE_OPEN_DATA_CACHE_TTL) {
186
+ if (typeof this.invoiceOpenDataModule.ensureInvoiceTemplateSettings === 'function') {
187
+ await this.invoiceOpenDataModule.ensureInvoiceTemplateSettings(target);
188
+ }
189
+ return cachedData;
190
+ }
191
+ if (this.invoiceOpenDataInFlight) {
192
+ if (this.invoiceOpenDataInFlightTarget === target) {
193
+ return this.invoiceOpenDataInFlight;
194
+ }
195
+ try {
196
+ await this.invoiceOpenDataInFlight;
197
+ } catch {
198
+ // 另一 target 的失败不应阻断当前 target 获取自己的 Documents 配置。
199
+ }
200
+ return this.loadInvoiceOpenData(businessCode, channel);
201
+ }
202
+ const request = this.invoiceOpenDataModule.fetchOpenData({
203
+ scope: 'board',
204
+ target,
205
+ section_code: [...INVOICE_OPEN_DATA_SECTION_CODES]
206
+ });
207
+ this.invoiceOpenDataInFlight = request;
208
+ this.invoiceOpenDataInFlightTarget = target;
209
+ try {
210
+ const openData = await request;
211
+ // clearOpenDataCache() 或后续请求接管后,旧响应不得复活 target 归属。
212
+ if (this.invoiceOpenDataInFlight === request) {
213
+ this.invoiceOpenDataTarget = target;
214
+ }
215
+ return openData;
216
+ } finally {
217
+ if (this.invoiceOpenDataInFlight === request) {
218
+ this.invoiceOpenDataInFlight = null;
219
+ this.invoiceOpenDataInFlightTarget = null;
220
+ }
221
+ }
222
+ }
223
+ logInvoiceOpenDataFallback(error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ if (this.logger?.addLog) {
226
+ this.logger.addLog({
227
+ type: 'warning',
228
+ title: 'Sales Invoice OpenData 加载失败,使用默认配置',
229
+ metadata: {
230
+ message
231
+ }
232
+ });
233
+ return;
234
+ }
235
+ console.warn('[Sales] Invoice OpenData 加载失败,使用默认配置', error);
236
+ }
237
+ async resolveInvoiceConfigs(businessCode, channel) {
238
+ try {
239
+ await this.loadInvoiceOpenData(businessCode, channel);
240
+ if (this.invoiceOpenDataTarget !== `${businessCode}+${channel}`) {
241
+ return {
242
+ invoice: (0, _OpenData.getDefaultInvoiceLayoutConfig)(),
243
+ templateSettings: (0, _OpenData.getDefaultInvoiceTemplateSettings)()
244
+ };
245
+ }
246
+ return {
247
+ invoice: this.invoiceOpenDataModule?.getInvoiceLayoutConfig() ?? (0, _OpenData.getDefaultInvoiceLayoutConfig)(),
248
+ templateSettings: this.invoiceOpenDataModule?.getInvoiceTemplateSettings() ?? (0, _OpenData.getDefaultInvoiceTemplateSettings)()
249
+ };
250
+ } catch (error) {
251
+ this.logInvoiceOpenDataFallback(error);
252
+ return {
253
+ invoice: (0, _OpenData.getDefaultInvoiceLayoutConfig)(),
254
+ templateSettings: (0, _OpenData.getDefaultInvoiceTemplateSettings)()
255
+ };
256
+ }
257
+ }
258
+ async printLocalOrderReceipt(lookup, options) {
259
+ const normalizedLookup = typeof lookup === 'string' ? lookup.trim() : lookup;
260
+ if (normalizedLookup === '' || normalizedLookup === null || normalizedLookup === undefined) {
261
+ throw new Error('Sales 本地小票打印需要订单标识');
262
+ }
263
+ const businessCode = String(options?.businessCode ?? '').trim();
264
+ if (!businessCode) {
265
+ throw new Error('Sales 本地小票打印需要 businessCode');
266
+ }
267
+ const channel = this.getInvoiceSalesChannel(options?.channel);
268
+ const {
269
+ invoice,
270
+ templateSettings
271
+ } = await this.resolveInvoiceConfigs(businessCode, channel);
272
+ const lookupPayload = typeof normalizedLookup === 'number' ? {
273
+ order_id: normalizedLookup
274
+ } : {
275
+ external_sale_number: normalizedLookup
276
+ };
277
+ return this.request.post('/local/print-order', {
278
+ ...lookupPayload,
279
+ lookup_order_from_db: 1,
280
+ small_ticket_data_flag: 1,
281
+ business_code: businessCode,
282
+ order_sales_channel: channel,
283
+ invoice: (0, _lodashEs.cloneDeep)(invoice),
284
+ template_settings: (0, _lodashEs.cloneDeep)(templateSettings)
285
+ }, {
286
+ osServer: true,
287
+ customToast: () => {}
288
+ });
289
+ }
290
+
291
+ /** 清除独立 Documents 缓存;下一次打印会按当前 target 重新获取。 */
292
+ clearOpenDataCache() {
293
+ this.invoiceOpenDataInFlight = null;
294
+ this.invoiceOpenDataInFlightTarget = null;
295
+ this.invoiceOpenDataTarget = null;
296
+ this.invoiceOpenDataModule?.clearCache();
297
+ }
298
+
142
299
  // -------------------------------------------------------------------------
143
300
  // 生命周期
144
301
  // -------------------------------------------------------------------------
145
302
 
146
303
  async destroy() {
304
+ this.clearOpenDataCache();
305
+ const invoiceOpenDataModule = this.invoiceOpenDataModule;
306
+ this.invoiceOpenDataModule = null;
307
+ if (invoiceOpenDataModule) {
308
+ try {
309
+ await invoiceOpenDataModule.destroy();
310
+ } catch (error) {
311
+ console.warn('[Sales] Invoice OpenData 子模块销毁失败', error);
312
+ }
313
+ }
147
314
  await this.core.effects.emit(`${this.name}:onDestroy`, {});
148
315
  super.destroy();
149
316
  }
@@ -50,6 +50,12 @@ export interface GetResourceBookingItemParams {
50
50
  bookingList: BookingData[];
51
51
  deviceTime?: string;
52
52
  }
53
+ export interface SalesPrintLocalOrderReceiptOptions {
54
+ /** 当前销售业务类型,用于隔离 Documents Invoice 配置。 */
55
+ businessCode: string;
56
+ /** 当前销售入口;缺失或为 system 时按 pos 处理。 */
57
+ channel?: string;
58
+ }
53
59
  /**
54
60
  * Sales 解决方案状态
55
61
  *
@@ -82,6 +88,10 @@ export interface SalesModuleAPI {
82
88
  cancelOrder: (orderIds: Array<number | string>) => Promise<any>;
83
89
  /** 变更预约状态 */
84
90
  changeBookingStatus: (params: ChangeBookingStatusParams) => Promise<any>;
91
+ /** 按本地订单标识打印小票;本地未命中时由 OS Server 负责远端兜底。 */
92
+ printLocalOrderReceipt: <T = any>(lookup: string | number, options: SalesPrintLocalOrderReceiptOptions) => Promise<T>;
93
+ /** 清除 Legacy Sales 独立维护的 Documents OpenData 缓存。 */
94
+ clearOpenDataCache: () => void;
85
95
  /** 获取资源维度的预约占用列表 */
86
96
  getResourceBookingList: (currentTime: string, bookingList: BookingData[], deviceTime?: string) => Promise<SalesResourceBookingItem[]>;
87
97
  /** 获取单个资源维度的预约占用、已完成、未支付列表 */
@@ -13,6 +13,7 @@ interface ScanOrderItemRuleRuntimeConfig {
13
13
  pax?: Partial<PaxInfo>;
14
14
  historicalItems?: CartItemSummary[];
15
15
  serviceType?: string;
16
+ channel?: string;
16
17
  submissionIndex?: number;
17
18
  custom?: Record<string, any>;
18
19
  }
@@ -1200,7 +1200,11 @@ class ScanOrderImpl extends _BaseSales.BaseSalesImpl {
1200
1200
  return sourceMap;
1201
1201
  }
1202
1202
  getItemRuleRuntimeConfig() {
1203
- return this.itemRuleRuntimeConfig || {};
1203
+ const runtimeConfig = this.itemRuleRuntimeConfig || {};
1204
+ return {
1205
+ ...runtimeConfig,
1206
+ channel: runtimeConfig.channel ?? runtimeConfig.custom?.channel ?? this.otherParams?.channel
1207
+ };
1204
1208
  }
1205
1209
  async ensureItemRuleConfigsLoaded() {
1206
1210
  if (this.itemRuleConfigs.length > 0) return this.itemRuleConfigs;
@@ -78,6 +78,7 @@ export declare function buildItemRuleBusinessData(params: {
78
78
  pax?: Partial<PaxInfo>;
79
79
  historicalItems?: CartItemSummary[];
80
80
  serviceType?: string;
81
+ channel?: string;
81
82
  submissionIndex?: number;
82
83
  custom?: Record<string, any>;
83
84
  };
@@ -333,6 +333,7 @@ function buildItemRuleBusinessData(params) {
333
333
  cartItems,
334
334
  historicalItems,
335
335
  serviceType: runtimeConfig.serviceType || 'dine-in',
336
+ channel: runtimeConfig.channel ?? runtimeConfig.custom?.channel,
336
337
  submissionIndex: typeof runtimeConfig.submissionIndex === 'number' ? toNonNegativeInt(runtimeConfig.submissionIndex) : tempOrder.order_id ? 1 : 0,
337
338
  custom: runtimeConfig.custom || {},
338
339
  strategyConfigs: itemRuleConfigs
@@ -1,11 +1,12 @@
1
1
  import type { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import type { ProductData } from '../../modules';
3
- import type { OpenDataConfig } from '../../modules/OpenData';
3
+ import { type InvoiceLayoutConfig, type InvoiceTemplateSettings, type OpenDataConfig } from '../../modules/OpenData';
4
4
  import type { LoadScheduleAvailableDateParams, ScheduleItem } from '../../modules/Schedule/types';
5
- import type { ProductListLoadProductsParams } from '../../modules/ProductList';
5
+ import type { ProductListLoadProductsParams, ProductListStoreQueryParams } from '../../modules/ProductList';
6
6
  import type { AvailabilityDateRange, AvailabilityOccupancy, AvailabilityProduct, AvailabilityResource, GetProductTimeRangesRequest } from '../../modules/ResourcePlanner';
7
- import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
7
+ import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesResolveProductAllergyRequirementParams, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
8
8
  import { BookingTicket } from '../BookingTicket';
9
+ import type { AllergyRequirement } from '../../model/strategy/adapter/itemRule';
9
10
  export * from './types';
10
11
  export { AvailabilityError } from '../../modules/ResourcePlanner';
11
12
  /**
@@ -59,9 +60,6 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
59
60
  protected defaultVersion: string;
60
61
  protected store: UnifiedBookingSalesState;
61
62
  private unifiedProductCatalog;
62
- private openDataTarget;
63
- private loadOpenDataInFlight;
64
- private loadOpenDataInFlightTarget;
65
63
  private availabilityRuntimeOffsetMinutes;
66
64
  private availabilityScheduleCatalog;
67
65
  private availabilityProjectionSequence;
@@ -76,6 +74,9 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
76
74
  private availabilityRemoteResourceQuerySequence;
77
75
  private availabilityRemoteResourceEpoch;
78
76
  private availabilityCommitLocks;
77
+ private allergyItemRuleEvaluator;
78
+ private allergyItemRuleConfigs;
79
+ private allergyItemRuleConfigsPromise;
79
80
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
80
81
  protected getSubmitOrderSalesChannel(): string;
81
82
  private createAvailabilityFacadeError;
@@ -147,6 +148,18 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
147
148
  */
148
149
  loadOpenData(params: UnifiedBookingSalesLoadOpenDataParams): Promise<OpenDataConfig>;
149
150
  getOpenData(): Promise<OpenDataConfig | null>;
151
+ private resetAllergyItemRuleState;
152
+ private fetchAllergyItemRuleConfigs;
153
+ private ensureAllergyItemRuleConfigsLoaded;
154
+ /**
155
+ * 评估当前渠道与商品是否命中 ALLERGY_CHECK。
156
+ * 只返回优先级最高 Action 中当前商品 Target 的 dataSource,不跨商品或 Action 合并。
157
+ */
158
+ resolveProductAllergyRequirement(params: UnifiedBookingSalesResolveProductAllergyRequirementParams): Promise<AllergyRequirement | null>;
159
+ clearOpenDataCache(): void;
160
+ getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
161
+ getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
162
+ protected isInvoiceOpenDataCacheCurrent(): boolean;
150
163
  /**
151
164
  * Load a resource policy without depending on the legacy appointmentBooking solution.
152
165
  */
@@ -159,6 +172,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
159
172
  callback?: (result: any) => void;
160
173
  subscriberId?: string;
161
174
  }): Promise<ProductData[]>;
175
+ queryProducts(params?: ProductListStoreQueryParams): Promise<ProductData[]>;
162
176
  /** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
163
177
  loadScheduleAvailableDate(params: LoadScheduleAvailableDateParams): Promise<import("../../modules/Date/types").ITime[]>;
164
178
  /**
@@ -33,6 +33,7 @@ var _dayjs = _interopRequireDefault(require("dayjs"));
33
33
  var _lodashEs = require("lodash-es");
34
34
  var _modules = require("../../modules");
35
35
  var _utils = require("../../modules/BookingContext/utils");
36
+ var _OpenData = require("../../modules/OpenData");
36
37
  var _getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
37
38
  var _ResourcePlanner = require("../../modules/ResourcePlanner");
38
39
  var _types = require("./types");
@@ -51,8 +52,9 @@ var _remoteOccupancyCache = require("./remoteOccupancyCache");
51
52
  var _BookingTicket = require("../BookingTicket");
52
53
  var _localClock = require("../../modules/ResourcePlanner/localClock");
53
54
  var _platform = require("../../utils/platform");
55
+ var _itemRule = require("../../model/strategy/adapter/itemRule");
54
56
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
55
- const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'basic', 'checkout', 'availability'];
57
+ const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents', 'basic', 'checkout', 'availability'];
56
58
  const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
57
59
  function normalizePositiveInteger(value) {
58
60
  const normalized = Number(value);
@@ -83,6 +85,22 @@ function getCandidateStartTime(candidate) {
83
85
  return undefined;
84
86
  }
85
87
  }
88
+ function normalizePositiveIntegerList(value) {
89
+ if (!Array.isArray(value)) return [];
90
+ return Array.from(new Set(value.map(item => normalizePositiveInteger(item)).filter(item => item !== undefined)));
91
+ }
92
+ function toConfiguredBoolean(value) {
93
+ if (typeof value === 'boolean') return value;
94
+ if (typeof value === 'number') return value !== 0;
95
+ if (typeof value === 'string') {
96
+ return ['1', 'true', 'yes', 'y', 'on'].includes(value.trim().toLowerCase());
97
+ }
98
+ return false;
99
+ }
100
+ function normalizeItemRuleStrategyConfigs(value) {
101
+ if (!Array.isArray(value)) return [];
102
+ return value.filter(config => Boolean(config && typeof config === 'object' && config.metadata?.type === 'item_rule'));
103
+ }
86
104
  function isRecord(value) {
87
105
  return !!value && typeof value === 'object' && !Array.isArray(value);
88
106
  }
@@ -1182,9 +1200,6 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1182
1200
  defaultVersion = '1.0.0';
1183
1201
  store = {};
1184
1202
  unifiedProductCatalog = [];
1185
- openDataTarget = null;
1186
- loadOpenDataInFlight = null;
1187
- loadOpenDataInFlightTarget = null;
1188
1203
  availabilityRuntimeOffsetMinutes = null;
1189
1204
  availabilityScheduleCatalog = null;
1190
1205
  availabilityProjectionSequence = 0;
@@ -1199,11 +1214,15 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1199
1214
  availabilityRemoteResourceQuerySequence = 0;
1200
1215
  availabilityRemoteResourceEpoch = 0;
1201
1216
  availabilityCommitLocks = new Set();
1217
+ allergyItemRuleEvaluator = new _itemRule.ItemRuleEvaluator();
1218
+ allergyItemRuleConfigs = [];
1219
+ allergyItemRuleConfigsPromise = null;
1202
1220
  async initialize(core, options = {}) {
1203
1221
  this.availabilityScheduleCatalog = null;
1204
1222
  this.openDataTarget = null;
1205
1223
  this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
1206
1224
  this.clearAvailabilityRemoteResourceState();
1225
+ this.resetAllergyItemRuleState();
1207
1226
  await super.initialize(core, options);
1208
1227
  const openDataBusinessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
1209
1228
  const openDataChannel = String(this.otherParams?.channel ?? '').trim();
@@ -1348,6 +1367,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1348
1367
  cover = false
1349
1368
  } = {}) {
1350
1369
  const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
1370
+ const previousItemRuleConfig = this.otherParams?.itemRule;
1351
1371
  const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, 'openData');
1352
1372
  await super.setOtherParams(params, {
1353
1373
  cover
@@ -1360,11 +1380,15 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1360
1380
  if (shouldResetAvailabilityState) {
1361
1381
  await this.resetAvailabilityProjectionState('set_other_params_reset');
1362
1382
  }
1383
+ if (shouldResetAvailabilityState || explicitlySetsOpenData || previousItemRuleConfig !== this.otherParams?.itemRule) {
1384
+ this.resetAllergyItemRuleState();
1385
+ }
1363
1386
  }
1364
1387
  async destroy() {
1365
1388
  this.availabilityScheduleCatalog = null;
1366
1389
  this.availabilityRuntimeOffsetMinutes = null;
1367
1390
  await this.resetAvailabilityProjectionState('destroy');
1391
+ this.resetAllergyItemRuleState();
1368
1392
  await super.destroy();
1369
1393
  }
1370
1394
  createAvailabilityContextId() {
@@ -1679,6 +1703,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1679
1703
  const cachedData = this.store.openData.getOpenData();
1680
1704
  const lastFetchedAt = this.store.openData.getLastFetchedAt();
1681
1705
  if (!params.force && this.openDataTarget === target && cachedData && lastFetchedAt && Date.now() - lastFetchedAt < OPEN_DATA_CACHE_TTL) {
1706
+ if (typeof this.store.openData.ensureInvoiceTemplateSettings === 'function') {
1707
+ await this.store.openData.ensureInvoiceTemplateSettings(target);
1708
+ }
1682
1709
  return cachedData;
1683
1710
  }
1684
1711
  if (this.loadOpenDataInFlight) {
@@ -1689,35 +1716,155 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1689
1716
  return this.loadOpenData(params);
1690
1717
  }
1691
1718
  this.loadOpenDataInFlightTarget = target;
1692
- this.loadOpenDataInFlight = this.store.openData.fetchOpenData({
1719
+ const request = this.store.openData.fetchOpenData({
1693
1720
  scope: 'board',
1694
1721
  target,
1695
1722
  section_code: [...OPEN_DATA_SECTION_CODES]
1696
1723
  });
1724
+ this.loadOpenDataInFlight = request;
1697
1725
  try {
1698
- const openData = await this.loadOpenDataInFlight;
1699
- this.openDataTarget = target;
1700
- this.otherParams.openData = openData;
1701
- this.otherParams.businessCode = businessCode;
1702
- this.otherParams.channel = channel;
1726
+ const openData = await request;
1727
+ if (this.loadOpenDataInFlight === request) {
1728
+ this.openDataTarget = target;
1729
+ this.otherParams.openData = openData;
1730
+ this.otherParams.businessCode = businessCode;
1731
+ this.otherParams.channel = channel;
1732
+ }
1703
1733
  return openData;
1704
1734
  } finally {
1705
- this.loadOpenDataInFlight = null;
1706
- this.loadOpenDataInFlightTarget = null;
1735
+ if (this.loadOpenDataInFlight === request) {
1736
+ this.loadOpenDataInFlight = null;
1737
+ this.loadOpenDataInFlightTarget = null;
1738
+ }
1707
1739
  }
1708
1740
  }
1709
1741
  async getOpenData() {
1710
1742
  const businessCode = String(this.otherParams?.businessCode || '').trim();
1711
1743
  const channel = String(this.otherParams?.channel || '').trim();
1712
1744
  if (!businessCode || !channel) return null;
1713
- const target = `${businessCode}+${channel}`;
1714
- const cachedData = this.store.openData?.getOpenData() || null;
1715
- if (cachedData && this.openDataTarget === target) return cachedData;
1716
1745
  return this.loadOpenData({
1717
1746
  businessCode,
1718
1747
  channel
1719
1748
  });
1720
1749
  }
1750
+ resetAllergyItemRuleState() {
1751
+ this.allergyItemRuleConfigs = [];
1752
+ this.allergyItemRuleConfigsPromise = null;
1753
+ this.allergyItemRuleEvaluator.setStrategyConfigs([]);
1754
+ }
1755
+ async fetchAllergyItemRuleConfigs(strategyModelIds) {
1756
+ if (!strategyModelIds.length) return [];
1757
+ try {
1758
+ const response = await this.request.get('/promotion', {
1759
+ skip: 1,
1760
+ num: 99,
1761
+ status: 'active',
1762
+ ids: strategyModelIds
1763
+ });
1764
+ if (response?.code !== 200 || !Array.isArray(response?.data?.list)) {
1765
+ throw new Error('[UnifiedBookingSales] 商品过敏策略加载失败');
1766
+ }
1767
+ const list = response.data.list;
1768
+ return normalizeItemRuleStrategyConfigs(list.map(item => item?.metadata));
1769
+ } catch (error) {
1770
+ // this.logMethodError('fetchAllergyItemRuleConfigs', error, {
1771
+ // strategyModelIds,
1772
+ // });
1773
+ throw error;
1774
+ }
1775
+ }
1776
+ async ensureAllergyItemRuleConfigsLoaded() {
1777
+ if (this.allergyItemRuleConfigs.length > 0) {
1778
+ return this.allergyItemRuleConfigs;
1779
+ }
1780
+ if (this.allergyItemRuleConfigsPromise) {
1781
+ return this.allergyItemRuleConfigsPromise;
1782
+ }
1783
+ this.allergyItemRuleConfigsPromise = (async () => {
1784
+ const staticConfigs = normalizeItemRuleStrategyConfigs(this.otherParams?.itemRule?.strategyConfigs);
1785
+ if (staticConfigs.length > 0) {
1786
+ this.allergyItemRuleConfigs = staticConfigs;
1787
+ this.allergyItemRuleEvaluator.setStrategyConfigs(staticConfigs);
1788
+ return staticConfigs;
1789
+ }
1790
+ const openData = await this.getOpenData();
1791
+ const enabled = toConfiguredBoolean(openData?.['sale.enable_item_rules']);
1792
+ const strategyModelIds = normalizePositiveIntegerList(openData?.['sale.enabled_item_rules']);
1793
+ if (!enabled || !strategyModelIds.length) {
1794
+ this.allergyItemRuleEvaluator.setStrategyConfigs([]);
1795
+ return [];
1796
+ }
1797
+ const loaded = await this.fetchAllergyItemRuleConfigs(strategyModelIds);
1798
+ this.allergyItemRuleConfigs = loaded;
1799
+ this.allergyItemRuleEvaluator.setStrategyConfigs(loaded);
1800
+ return loaded;
1801
+ })();
1802
+ return this.allergyItemRuleConfigsPromise;
1803
+ }
1804
+
1805
+ /**
1806
+ * 评估当前渠道与商品是否命中 ALLERGY_CHECK。
1807
+ * 只返回优先级最高 Action 中当前商品 Target 的 dataSource,不跨商品或 Action 合并。
1808
+ */
1809
+ async resolveProductAllergyRequirement(params) {
1810
+ const productId = normalizePositiveInteger(params.productId);
1811
+ if (!productId) return null;
1812
+ const strategyConfigs = await this.ensureAllergyItemRuleConfigsLoaded();
1813
+ if (!strategyConfigs.length) return null;
1814
+ const productVariantId = normalizePositiveInteger(params.productVariantId);
1815
+ const runtimeConfig = this.otherParams?.itemRule || {};
1816
+ const runtimePax = runtimeConfig.pax || {};
1817
+ const cartItems = (this.getOrderProducts?.() || []).map(item => ({
1818
+ product_id: Number(item?.product_id ?? item?.id),
1819
+ product_variant_id: Number(item?.product_variant_id ?? 0) || undefined,
1820
+ quantity: Number(item?.num ?? item?.quantity ?? 0)
1821
+ })).filter(item => Number.isFinite(item.product_id) && item.product_id > 0 && Number.isFinite(item.quantity) && item.quantity > 0);
1822
+ return this.allergyItemRuleEvaluator.getAllergyRequirement({
1823
+ pax: {
1824
+ total: Number(runtimePax.total ?? 0) || 0,
1825
+ adult: Number(runtimePax.adult ?? 0) || 0,
1826
+ child: Number(runtimePax.child ?? 0) || 0
1827
+ },
1828
+ cartItems,
1829
+ serviceType: String(runtimeConfig.serviceType ?? this.otherParams?.serviceType ?? ''),
1830
+ channel: String(params.channel ?? this.otherParams?.channel ?? this.otherParams?.platform ?? ''),
1831
+ currentProduct: {
1832
+ product_id: productId,
1833
+ ...(productVariantId ? {
1834
+ product_variant_id: productVariantId
1835
+ } : {})
1836
+ },
1837
+ submissionIndex: Number(runtimeConfig.submissionIndex ?? 0) || 0,
1838
+ strategyConfigs,
1839
+ custom: runtimeConfig.custom
1840
+ });
1841
+ }
1842
+ clearOpenDataCache() {
1843
+ this.openDataTarget = null;
1844
+ this.loadOpenDataInFlight = null;
1845
+ this.loadOpenDataInFlightTarget = null;
1846
+ super.clearOpenDataCache();
1847
+ }
1848
+ async getInvoiceLayoutConfig() {
1849
+ const openData = await this.getOpenData();
1850
+ if (!openData || !this.store.openData) {
1851
+ return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
1852
+ }
1853
+ return this.store.openData.getInvoiceLayoutConfig();
1854
+ }
1855
+ async getInvoiceTemplateSettings() {
1856
+ const openData = await this.getOpenData();
1857
+ if (!openData || !this.store.openData) {
1858
+ return (0, _OpenData.getDefaultInvoiceTemplateSettings)();
1859
+ }
1860
+ return this.store.openData.getInvoiceTemplateSettings();
1861
+ }
1862
+ isInvoiceOpenDataCacheCurrent() {
1863
+ const businessCode = String(this.otherParams?.businessCode || '').trim();
1864
+ const channel = String(this.otherParams?.channel || '').trim();
1865
+ if (!businessCode || !channel) return false;
1866
+ return this.openDataTarget === `${businessCode}+${channel}`;
1867
+ }
1721
1868
 
1722
1869
  /**
1723
1870
  * Load a resource policy without depending on the legacy appointmentBooking solution.
@@ -1754,6 +1901,10 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1754
1901
  await this.core.effects.emit(`${this.name}:onProductsLoaded`, result);
1755
1902
  return result;
1756
1903
  }
1904
+ async queryProducts(params = {}) {
1905
+ if (!this.store.products) throw new Error('[UnifiedBookingSales] products 模块未初始化');
1906
+ return this.store.products.queryProducts(params);
1907
+ }
1757
1908
 
1758
1909
  /** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
1759
1910
  async loadScheduleAvailableDate(params) {
@@ -3304,7 +3455,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
3304
3455
  } else {
3305
3456
  const beforeProductLineUids = new Set((this.getTempOrder()?.products || []).map(line => getProductLineUid(line)).filter(Boolean));
3306
3457
  const beforeBookingUids = new Set((this.getTempOrder()?.bookings || []).map(item => getBookingUid(item)).filter(Boolean));
3307
- const products = await this.addProductToOrder(transformed, booking);
3458
+ const products = await this.addProductToOrder(transformed, booking, params.salesNotes?.length ? {
3459
+ salesNotes: params.salesNotes
3460
+ } : undefined);
3308
3461
  const addedLines = products.filter(line => sameAvailabilityId(line.product_id ?? line.id, refreshedCandidate.productId) && line.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeProductLineUids.has(getProductLineUid(line)));
3309
3462
  productLineUids = addedLines.map(line => getProductLineUid(line)).filter(Boolean);
3310
3463
  const addedBookings = (this.getTempOrder()?.bookings || []).filter(item => item.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item)));