@pisell/pisellos 0.10.58 → 0.10.59

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 (34) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/inventory/index.d.ts +92 -0
  4. package/dist/inventory/index.js +500 -0
  5. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  6. package/dist/modules/Order/index.d.ts +19 -2
  7. package/dist/modules/Order/index.js +1369 -1021
  8. package/dist/modules/Order/types.d.ts +5 -0
  9. package/dist/modules/ProductList/index.d.ts +1 -3
  10. package/dist/modules/ProductList/index.js +120 -192
  11. package/dist/modules/ProductList/types.d.ts +0 -8
  12. package/dist/modules/ProductList/types.js +0 -2
  13. package/dist/solution/BaseSales/index.d.ts +8 -0
  14. package/dist/solution/BaseSales/index.js +1382 -1207
  15. package/dist/solution/BookingByStep/index.d.ts +1 -1
  16. package/dist/solution/UnifiedBookingSales/index.d.ts +1 -7
  17. package/dist/solution/UnifiedBookingSales/index.js +446 -586
  18. package/lib/index.d.ts +1 -0
  19. package/lib/index.js +12 -0
  20. package/lib/inventory/index.d.ts +92 -0
  21. package/lib/inventory/index.js +280 -0
  22. package/lib/modules/Order/index.d.ts +19 -2
  23. package/lib/modules/Order/index.js +111 -19
  24. package/lib/modules/Order/types.d.ts +5 -0
  25. package/lib/modules/ProductList/index.d.ts +1 -3
  26. package/lib/modules/ProductList/index.js +22 -31
  27. package/lib/modules/ProductList/types.d.ts +0 -8
  28. package/lib/modules/ProductList/types.js +0 -1
  29. package/lib/solution/BaseSales/index.d.ts +8 -0
  30. package/lib/solution/BaseSales/index.js +82 -7
  31. package/lib/solution/BookingByStep/index.d.ts +1 -1
  32. package/lib/solution/UnifiedBookingSales/index.d.ts +1 -7
  33. package/lib/solution/UnifiedBookingSales/index.js +12 -80
  34. package/package.json +1 -1
@@ -145,6 +145,63 @@ class OrderModule extends _BaseModule.BaseModule {
145
145
  rulesHooksOverride;
146
146
  moduleHooksOverride;
147
147
  orderHooks;
148
+ cartCommandQueue = Promise.resolve();
149
+ queueCartCommand(run) {
150
+ if (!this.orderHooks?.onBeforeCartCommit) return run();
151
+ const pending = this.cartCommandQueue.then(run, run);
152
+ this.cartCommandQueue = pending.catch(() => undefined);
153
+ return pending;
154
+ }
155
+
156
+ /** Use the exact Order normalizers/merge rules on a private draft, not a second projection. */
157
+ beginCartMutation() {
158
+ const current = this.ensureTempOrder();
159
+ const guard = this.orderHooks?.onBeforeCartCommit;
160
+ if (!guard) return {
161
+ current,
162
+ draft: current,
163
+ commit: undefined
164
+ };
165
+ const before = (0, _lodashEs.cloneDeep)(current);
166
+ const draft = (0, _lodashEs.cloneDeep)(current);
167
+ return {
168
+ current,
169
+ draft,
170
+ commit: async beforeCommit => {
171
+ await guard({
172
+ currentItems: (0, _lodashEs.cloneDeep)(before.products),
173
+ proposedItems: (0, _lodashEs.cloneDeep)(draft.products)
174
+ });
175
+ if (this.store.tempOrder !== current || guard !== this.orderHooks?.onBeforeCartCommit || !(0, _lodashEs.isEqual)(current, before)) {
176
+ throw Object.assign(new Error('The cart changed. Please try again.'), {
177
+ code: 'SALES_GUARD_REJECTED'
178
+ });
179
+ }
180
+ // Recheck the accepted intent's scope after asynchronous inventory validation.
181
+ beforeCommit?.();
182
+ Object.assign(current, draft);
183
+ return current;
184
+ }
185
+ };
186
+ }
187
+
188
+ /** Serializes checkout preparation with local cart commands; hydration remains outside this guard. */
189
+ async prepareCheckout(prepare) {
190
+ const run = async () => {
191
+ const order = this.ensureTempOrder();
192
+ const items = (0, _lodashEs.cloneDeep)(order.products);
193
+ const result = await prepare(items);
194
+ if (this.store.tempOrder !== order || !(0, _lodashEs.isEqual)(order.products, items)) {
195
+ throw Object.assign(new Error('The cart changed. Please try again.'), {
196
+ code: 'SALES_GUARD_REJECTED'
197
+ });
198
+ }
199
+ return result;
200
+ };
201
+ const pending = this.cartCommandQueue.then(run, run);
202
+ this.cartCommandQueue = pending.catch(() => undefined);
203
+ return pending;
204
+ }
148
205
  otherParams;
149
206
  // ─── 促销/赠品 ────────────────────────────────────
150
207
  /** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
@@ -432,8 +489,11 @@ class OrderModule extends _BaseModule.BaseModule {
432
489
  * @example
433
490
  * orderModule.updateOtherParams({ cacheId: 'cache-2', channel: 'pos' });
434
491
  */
435
- updateOtherParams(params) {
492
+ updateOtherParams(params, options) {
436
493
  const otherParams = params || {};
494
+ if (options && Object.prototype.hasOwnProperty.call(options, 'hooks')) {
495
+ this.moduleHooksOverride = options.hooks;
496
+ }
437
497
  this.syncSessionStorageOptions(otherParams);
438
498
  this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
439
499
  this.rulesHooksOverride = otherParams.rules?.hooks;
@@ -3704,8 +3764,12 @@ class OrderModule extends _BaseModule.BaseModule {
3704
3764
  * // → tempOrder.products 新增 3 行,每行 num=1,bookings 3 条
3705
3765
  */
3706
3766
  async addProductToOrder(product, booking, options) {
3707
- const tempOrder = this.ensureTempOrder();
3708
- const beforeCommit = this.createCartIntentCommitGuard('add_product', tempOrder);
3767
+ return this.queueCartCommand(() => this.addProductToOrderDraft(product, booking, options));
3768
+ }
3769
+ async addProductToOrderDraft(product, booking, options) {
3770
+ const mutation = this.beginCartMutation();
3771
+ let tempOrder = mutation.draft;
3772
+ const beforeCommit = this.createCartIntentCommitGuard('add_product', mutation.current);
3709
3773
  const salesNoteDrafts = (options?.salesNotes || []).map(draft => ({
3710
3774
  note_type: String(draft.note_type || '').trim(),
3711
3775
  text: {
@@ -3802,6 +3866,7 @@ class OrderModule extends _BaseModule.BaseModule {
3802
3866
  rollbackProductAndNotesMutation();
3803
3867
  throw error;
3804
3868
  }
3869
+ if (mutation.commit) tempOrder = await mutation.commit(beforeCommit);
3805
3870
  await this.finalizeProductOrderMutation(tempOrder);
3806
3871
  this.logInfo('addProductToOrder success', {
3807
3872
  product_id: product.product_id,
@@ -3820,9 +3885,13 @@ class OrderModule extends _BaseModule.BaseModule {
3820
3885
  * ]);
3821
3886
  */
3822
3887
  async addProductsToOrder(items, options) {
3823
- const tempOrder = this.ensureTempOrder();
3888
+ return this.queueCartCommand(() => this.addProductsToOrderDraft(items, options));
3889
+ }
3890
+ async addProductsToOrderDraft(items, options) {
3891
+ const mutation = this.beginCartMutation();
3892
+ let tempOrder = mutation.draft;
3824
3893
  if (!Array.isArray(items) || items.length === 0) return tempOrder.products;
3825
- const beforeCommit = this.createCartIntentCommitGuard('add_product', tempOrder);
3894
+ const beforeCommit = this.createCartIntentCommitGuard('add_product', mutation.current);
3826
3895
  for (const item of items || []) {
3827
3896
  const {
3828
3897
  product,
@@ -3848,6 +3917,7 @@ class OrderModule extends _BaseModule.BaseModule {
3848
3917
  });
3849
3918
  if (appendResult) await appendResult;
3850
3919
  }
3920
+ if (mutation.commit) tempOrder = await mutation.commit(beforeCommit);
3851
3921
  await this.finalizeProductOrderMutation(tempOrder, options);
3852
3922
  this.logInfo('addProductsToOrder success', {
3853
3923
  itemsCount: items.length
@@ -4190,12 +4260,16 @@ class OrderModule extends _BaseModule.BaseModule {
4190
4260
  return tempOrder.products[productIndex].metadata?.unique_identification_number;
4191
4261
  }
4192
4262
  async updateOrderProduct(params) {
4193
- const tempOrder = this.ensureTempOrder();
4194
- if (this.isRestrictedCartProductUpdate(tempOrder, params)) {
4195
- this.createCartIntentCommitGuard('update_product', tempOrder)();
4196
- }
4263
+ return this.queueCartCommand(() => this.updateOrderProductDraft(params));
4264
+ }
4265
+ async updateOrderProductDraft(params) {
4266
+ const mutation = this.beginCartMutation();
4267
+ let tempOrder = mutation.draft;
4268
+ const beforeCommit = this.isRestrictedCartProductUpdate(tempOrder, params) ? this.createCartIntentCommitGuard('update_product', mutation.current) : undefined;
4269
+ beforeCommit?.();
4197
4270
  const reapplyWeighedDiscountProductUids = [];
4198
4271
  const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
4272
+ if (mutation.commit) tempOrder = await mutation.commit(beforeCommit);
4199
4273
  const promotionPayload = await this.evaluatePromotion();
4200
4274
  this.applyDiscount({
4201
4275
  reapplyWeighedDiscountProductUids,
@@ -4213,13 +4287,16 @@ class OrderModule extends _BaseModule.BaseModule {
4213
4287
  return tempOrder.products;
4214
4288
  }
4215
4289
  async updateOrderProducts(paramsList) {
4216
- const tempOrder = this.ensureTempOrder();
4290
+ return this.queueCartCommand(() => this.updateOrderProductsDraft(paramsList));
4291
+ }
4292
+ async updateOrderProductsDraft(paramsList) {
4293
+ const mutation = this.beginCartMutation();
4294
+ let tempOrder = mutation.draft;
4217
4295
  if (!paramsList.length) {
4218
4296
  return tempOrder.products;
4219
4297
  }
4220
- if (paramsList.some(params => this.isRestrictedCartProductUpdate(tempOrder, params))) {
4221
- this.createCartIntentCommitGuard('update_product', tempOrder)();
4222
- }
4298
+ const beforeCommit = paramsList.some(params => this.isRestrictedCartProductUpdate(tempOrder, params)) ? this.createCartIntentCommitGuard('update_product', mutation.current) : undefined;
4299
+ beforeCommit?.();
4223
4300
  const reapplyWeighedDiscountProductUids = [];
4224
4301
  const reapplyBookingDiscountProductUids = paramsList.reduce((acc, params) => {
4225
4302
  const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
@@ -4229,6 +4306,7 @@ class OrderModule extends _BaseModule.BaseModule {
4229
4306
  }
4230
4307
  return acc;
4231
4308
  }, []);
4309
+ if (mutation.commit) tempOrder = await mutation.commit(beforeCommit);
4232
4310
  await this.finalizeAfterProductsMutation(tempOrder, {
4233
4311
  reapplyWeighedDiscountProductUids,
4234
4312
  reapplyBookingDiscountProductUids: reapplyBookingDiscountProductUids.length ? reapplyBookingDiscountProductUids : undefined
@@ -4239,6 +4317,9 @@ class OrderModule extends _BaseModule.BaseModule {
4239
4317
  return tempOrder.products;
4240
4318
  }
4241
4319
  async updateOrderProductQuantity(params) {
4320
+ return this.queueCartCommand(() => this.updateOrderProductQuantityDraft(params));
4321
+ }
4322
+ async updateOrderProductQuantityDraft(params) {
4242
4323
  const {
4243
4324
  product_id,
4244
4325
  product_variant_id,
@@ -4248,7 +4329,8 @@ class OrderModule extends _BaseModule.BaseModule {
4248
4329
  product_sku,
4249
4330
  product_bundle
4250
4331
  } = params;
4251
- const tempOrder = this.ensureTempOrder();
4332
+ const mutation = this.beginCartMutation();
4333
+ let tempOrder = mutation.draft;
4252
4334
  this.repairDuplicateProductLineIdentities(tempOrder);
4253
4335
  const identityLookup = {
4254
4336
  product_id,
@@ -4304,10 +4386,10 @@ class OrderModule extends _BaseModule.BaseModule {
4304
4386
  throw new _virtualSettlement.VirtualCurrencyBalanceError(balance.toFixed(2), requiredAmount.toFixed(2));
4305
4387
  }
4306
4388
  }
4307
- if (normalizedProduct.num > targetProduct.num) {
4308
- this.createCartIntentCommitGuard('increase_quantity', tempOrder)();
4309
- }
4389
+ const beforeCommit = normalizedProduct.num > targetProduct.num ? this.createCartIntentCommitGuard('increase_quantity', mutation.current) : undefined;
4390
+ beforeCommit?.();
4310
4391
  tempOrder.products[productIndex] = normalizedProduct;
4392
+ if (mutation.commit) tempOrder = await mutation.commit(beforeCommit);
4311
4393
  this.prepareVirtualSettlementTempOrder(tempOrder);
4312
4394
  const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder);
4313
4395
  const promotionPayload = !isVirtualSettlement ? await this.evaluatePromotion() : null;
@@ -4384,7 +4466,11 @@ class OrderModule extends _BaseModule.BaseModule {
4384
4466
  * await order.removeProductsFromOrder([identityA, identityB]);
4385
4467
  */
4386
4468
  async removeProductsFromOrder(identities, options) {
4387
- const tempOrder = this.ensureTempOrder();
4469
+ return this.queueCartCommand(() => this.removeProductsFromOrderDraft(identities, options));
4470
+ }
4471
+ async removeProductsFromOrderDraft(identities, options) {
4472
+ const mutation = this.beginCartMutation();
4473
+ let tempOrder = mutation.draft;
4388
4474
  if (!identities.length) {
4389
4475
  return tempOrder.products;
4390
4476
  }
@@ -4451,6 +4537,7 @@ class OrderModule extends _BaseModule.BaseModule {
4451
4537
  }
4452
4538
  }
4453
4539
  this.removeSalesNotesForMissingProductLines(tempOrder);
4540
+ if (mutation.commit) tempOrder = await mutation.commit();
4454
4541
  await this.finalizeAfterProductsMutation(tempOrder);
4455
4542
  this.logInfo('removeProductsFromOrder success', {
4456
4543
  identities
@@ -4466,7 +4553,11 @@ class OrderModule extends _BaseModule.BaseModule {
4466
4553
  * 普通整单 note 保持不变;以商品行为主体的 Sales Notes 同步删除。
4467
4554
  */
4468
4555
  async clearOrderCartLines() {
4469
- const tempOrder = this.ensureTempOrder();
4556
+ return this.queueCartCommand(() => this.clearOrderCartLinesDraft());
4557
+ }
4558
+ async clearOrderCartLinesDraft() {
4559
+ const mutation = this.beginCartMutation();
4560
+ let tempOrder = mutation.draft;
4470
4561
  this.ensureSalesNotesLoadedForMutation(tempOrder);
4471
4562
  tempOrder.products = [];
4472
4563
  tempOrder.bookings = [];
@@ -4479,6 +4570,7 @@ class OrderModule extends _BaseModule.BaseModule {
4479
4570
  productsByUid: {}
4480
4571
  };
4481
4572
  }
4573
+ if (mutation.commit) tempOrder = await mutation.commit();
4482
4574
  const promotionPayload = await this.evaluatePromotion();
4483
4575
  this.applyDiscount();
4484
4576
  this.sanitizeTempOrderProducts(tempOrder);
@@ -526,6 +526,11 @@ export interface OrderHooksConfig {
526
526
  beforeCartIntentCommit?: (context: OrderCartIntentCommitContext) => void;
527
527
  /** Captures tenant/business runtime identity for asynchronous cart intents. */
528
528
  getCartIntentScope?: () => unknown;
529
+ /** Awaited before a local cart draft is published. Reject to leave the live order unchanged. */
530
+ onBeforeCartCommit?: (context: {
531
+ currentItems: readonly OrderProduct[];
532
+ proposedItems: readonly OrderProduct[];
533
+ }) => void | Promise<void>;
529
534
  onBeforeMergeProductToOrder?: (context: OrderProductMergeDecisionContext) => OrderProductMergeDecisionResult | Promise<OrderProductMergeDecisionResult>;
530
535
  }
531
536
  /**
@@ -1,6 +1,6 @@
1
1
  import { Module, PisellCore } from '../../types';
2
2
  import { BaseModule } from '../BaseModule';
3
- import { ProductListLoadProductsParams, ProductListStoreQueryParams, ProductEnrichmentContext } from './types';
3
+ import { ProductListLoadProductsParams, ProductListStoreQueryParams } from './types';
4
4
  import { ProductData } from '../Product/types';
5
5
  export * from './types';
6
6
  export declare class ProductList extends BaseModule implements Module {
@@ -19,8 +19,6 @@ export declare class ProductList extends BaseModule implements Module {
19
19
  * productList.updateOtherParams({ channel: 'pos' });
20
20
  */
21
21
  updateOtherParams(params: Record<string, any>): void;
22
- enrichProducts(products: ProductData[], context: ProductEnrichmentContext): Promise<ProductData[]>;
23
- private enrichCatalog;
24
22
  storeChange(path?: string, value?: any): Promise<void>;
25
23
  /**
26
24
  * 获取加时商品列表。
@@ -66,30 +66,12 @@ class ProductList extends _BaseModule.BaseModule {
66
66
  * productList.updateOtherParams({ channel: 'pos' });
67
67
  */
68
68
  updateOtherParams(params) {
69
+ const previousScope = this.otherParams.catalogScopeKey;
69
70
  this.otherParams = params || {};
70
- }
71
- async enrichProducts(products, context) {
72
- const enrich = this.otherParams.enrichProducts;
73
- if (!enrich) return products;
74
- if (context.signal?.aborted) throw new Error('Product enrichment cancelled');
75
- const result = await enrich((0, _lodashEs.cloneDeep)(products), {
76
- businessCode: this.otherParams.businessCode,
77
- channel: this.otherParams.channel,
78
- ...context
79
- });
80
- if (context.signal?.aborted) throw new Error('Product enrichment cancelled');
81
- if (!Array.isArray(result)) throw new Error('Product enrichment must return an array');
82
- return result;
83
- }
84
- async enrichCatalog(products, reason) {
85
- try {
86
- return await this.enrichProducts(products, {
87
- reason
88
- });
89
- } catch {
90
- // An unavailable supplementary source is not zero stock or an empty catalog.
91
- console.warn('[ProductList] Supplementary product data could not be refreshed');
92
- return products;
71
+ if (previousScope !== this.otherParams.catalogScopeKey && (previousScope || this.otherParams.catalogScopeKey)) {
72
+ this.store.list = [];
73
+ this.rawProductPriceSources.clear();
74
+ this.core.effects.emit(`${this.name}:changed`, this.store.list);
93
75
  }
94
76
  }
95
77
  async storeChange(path, value) {
@@ -125,6 +107,14 @@ class ProductList extends _BaseModule.BaseModule {
125
107
  status = 'published',
126
108
  strategy_context
127
109
  } = {}, options) {
110
+ const scope = this.otherParams.catalogScopeKey;
111
+ const assertScope = () => {
112
+ if (scope !== this.otherParams.catalogScopeKey) {
113
+ throw Object.assign(new Error('Product catalog context changed'), {
114
+ name: 'AbortError'
115
+ });
116
+ }
117
+ };
128
118
  // // 如果 schedule_ids 为空,则需要尝试从 schedule 模块里获取
129
119
  // if (!schedule_ids?.length) {
130
120
  // const schedule_ids_data = this.store.schedule
@@ -204,6 +194,7 @@ class ProductList extends _BaseModule.BaseModule {
204
194
  const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
205
195
  const originalCallback = typeof options?.callback === 'function' ? options.callback : undefined;
206
196
  const subscriptionCallback = originalCallback ? async result => {
197
+ if (scope !== this.otherParams.catalogScopeKey) return;
207
198
  const callbackList = result?.data?.list;
208
199
  if (!Array.isArray(callbackList)) {
209
200
  originalCallback(result);
@@ -214,20 +205,20 @@ class ProductList extends _BaseModule.BaseModule {
214
205
  // the existing raw catalog scope and upsert the changed records.
215
206
  this.rememberRawProductQueryResult(callbackList);
216
207
  }
217
- const resolvedCallbackList = (0, _clientDataVariants.resolveClientDataVariants)({
208
+ const products = (0, _clientDataVariants.resolveClientDataVariants)({
218
209
  core: this.core,
219
210
  otherParams: this.otherParams,
220
211
  products: callbackList,
221
212
  queryPayload,
222
213
  handledByOsServer
223
214
  });
224
- const enrichedCallbackList = await this.enrichCatalog(resolvedCallbackList, 'catalog-update');
225
- await this.addProduct(enrichedCallbackList);
226
- originalCallback(enrichedCallbackList === callbackList ? result : {
215
+ assertScope();
216
+ await this.addProduct(products);
217
+ originalCallback(handledByOsServer ? result : {
227
218
  ...result,
228
219
  data: {
229
220
  ...result.data,
230
- list: enrichedCallbackList
221
+ list: products
231
222
  }
232
223
  });
233
224
  } : undefined;
@@ -237,6 +228,7 @@ class ProductList extends _BaseModule.BaseModule {
237
228
  subscriberId: options?.subscriberId,
238
229
  customToast: () => {}
239
230
  });
231
+ assertScope();
240
232
  if (!handledByOsServer && Array.isArray(productsData.data.list)) {
241
233
  this.replaceRawProductQueryResult(productsData.data.list, product_ids);
242
234
  }
@@ -247,8 +239,7 @@ class ProductList extends _BaseModule.BaseModule {
247
239
  queryPayload,
248
240
  handledByOsServer
249
241
  });
250
- const enrichedList = await this.enrichCatalog(resolvedList, 'catalog');
251
- const sortedList = enrichedList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
242
+ const sortedList = resolvedList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
252
243
  // if (sortedList.length) {
253
244
  // sortedList.forEach((n: any) => {
254
245
  // if (n.is_eject !== 1 && n['schedule.ids'] && n['schedule.ids'].length) {
@@ -256,7 +247,7 @@ class ProductList extends _BaseModule.BaseModule {
256
247
  // }
257
248
  // })
258
249
  // }
259
- await this.addProduct(sortedList);
250
+ this.addProduct(sortedList);
260
251
  return sortedList;
261
252
  }
262
253
  async loadProductsPrice({
@@ -1,12 +1,4 @@
1
1
  import { ProductData } from '../Product/types';
2
- /** Runtime-only enrichment; never serialized into a product query. */
3
- export interface ProductEnrichmentContext {
4
- reason: 'catalog' | 'catalog-update' | 'detail-open';
5
- businessCode?: string;
6
- channel?: string;
7
- signal?: AbortSignal;
8
- }
9
- export type ProductEnricher = (products: ProductData[], context: ProductEnrichmentContext) => Promise<ProductData[]>;
10
2
  export interface ProductListLoadProductsParams {
11
3
  category_ids?: number[];
12
4
  product_ids?: number[];
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ProductListHooks = void 0;
7
- /** Runtime-only enrichment; never serialized into a product query. */
8
7
  let ProductListHooks = exports.ProductListHooks = /*#__PURE__*/function (ProductListHooks) {
9
8
  ProductListHooks["onGetProducts"] = "productList:onGetProducts";
10
9
  ProductListHooks["onGetProduct"] = "productList:onGetProduct";
@@ -1,3 +1,4 @@
1
+ import { InventoryController, type CheckoutRuntimeSnapshot } from '../../inventory';
1
2
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
3
  import { BaseModule } from '../../modules/BaseModule';
3
4
  import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesSubmitSalesOrderParams, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentOptions, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
@@ -49,6 +50,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
49
50
  protected appPlugin: AppPlugin;
50
51
  protected store: BaseSalesState;
51
52
  protected otherParams: Record<string, any>;
53
+ getInventory(): InventoryController | undefined;
54
+ prepareCheckout(signal?: AbortSignal): Promise<CheckoutRuntimeSnapshot | undefined>;
52
55
  protected cacheId: string | undefined;
53
56
  /**
54
57
  * 由已在初始化阶段解析过设备短号的业务解决方案写入。
@@ -187,6 +190,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
187
190
  * 可覆盖此方法并把未识别的 name 委托给 `super.createSubModule(name)`。
188
191
  */
189
192
  protected createSubModule(moduleName: string): Module | null;
193
+ private inventoryOrderHooks?;
190
194
  protected getSubModuleHooks(moduleName: string): any;
191
195
  /**
192
196
  * 构造传给 BaseSales 子模块的完整运行态参数,保证初始化和后续同步语义一致。
@@ -224,7 +228,11 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
224
228
  private isActiveVirtualSettlementOrder;
225
229
  private isHistoricalVirtualSettlementOrder;
226
230
  private syncAppDataToTempOrder;
231
+ private initialization?;
232
+ /** Hosts can await readiness without changing Core's synchronous registration API. */
233
+ whenInitialized(): Promise<void>;
227
234
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
235
+ private initializeSalesRuntime;
228
236
  destroy(): Promise<void>;
229
237
  /**
230
238
  * 加载销售订单到统一 tempOrder 工作区。
@@ -7,6 +7,7 @@ var _exportNames = {
7
7
  BaseSalesImpl: true
8
8
  };
9
9
  exports.BaseSalesImpl = void 0;
10
+ var _inventory = require("../../inventory");
10
11
  var _BaseModule = require("../../modules/BaseModule");
11
12
  var _types = require("./types");
12
13
  Object.keys(_types).forEach(function (key) {
@@ -470,6 +471,40 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
470
471
  order: undefined
471
472
  };
472
473
  otherParams = {};
474
+ getInventory() {
475
+ return this.otherParams.inventory;
476
+ }
477
+ async prepareCheckout(signal) {
478
+ const inventory = this.getInventory();
479
+ const hooks = this.otherParams.checkoutHooks;
480
+ if (!inventory && !hooks?.beforeCheckout) return undefined;
481
+ if (!this.store.order) throw new Error('Order module is not ready');
482
+ return this.store.order.prepareCheckout(async items => {
483
+ const assertCurrent = () => {
484
+ if (signal?.aborted) throw Object.assign(new Error('Checkout cancelled'), {
485
+ name: 'AbortError'
486
+ });
487
+ if (inventory !== this.getInventory() || hooks !== this.otherParams.checkoutHooks) throw (0, _inventory.salesGuardError)('The sales context changed. Please try again.');
488
+ };
489
+ assertCurrent();
490
+ try {
491
+ await hooks?.beforeCheckout?.({
492
+ items,
493
+ signal
494
+ });
495
+ if (inventory) await inventory.refresh({
496
+ reason: 'checkout',
497
+ force: true,
498
+ signal
499
+ });
500
+ assertCurrent();
501
+ return inventory?.freezeCheckout(items);
502
+ } catch (error) {
503
+ if (error?.name === 'AbortError') throw error;
504
+ throw (0, _inventory.salesGuardError)(error instanceof Error ? error.message : String(error));
505
+ }
506
+ });
507
+ }
473
508
  cacheId;
474
509
  /**
475
510
  * 由已在初始化阶段解析过设备短号的业务解决方案写入。
@@ -1302,12 +1337,37 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1302
1337
  }
1303
1338
  return (0, _types2.createModule)(moduleName, this.name);
1304
1339
  }
1340
+ inventoryOrderHooks;
1305
1341
  getSubModuleHooks(moduleName) {
1306
1342
  const optionHooks = this.initializeOptions.hooks || {};
1307
1343
  const otherHooks = this.otherParams.hooks || {};
1308
1344
  if (moduleName === 'order') {
1309
1345
  const directOrderHooks = optionHooks;
1310
- return optionHooks.order || this.otherParams.order?.hooks || otherHooks.order || (directOrderHooks.onBeforeMergeProductToOrder ? directOrderHooks : undefined);
1346
+ const hooks = optionHooks.order || this.otherParams.order?.hooks || otherHooks.order || (directOrderHooks.onBeforeMergeProductToOrder || directOrderHooks.onBeforeCartCommit || directOrderHooks.beforeCartIntentCommit || directOrderHooks.getCartIntentScope ? directOrderHooks : undefined);
1347
+ const inventory = this.getInventory();
1348
+ if (!inventory) {
1349
+ this.inventoryOrderHooks = undefined;
1350
+ return hooks;
1351
+ }
1352
+ if (this.inventoryOrderHooks?.inventory === inventory && this.inventoryOrderHooks.base === hooks) {
1353
+ return this.inventoryOrderHooks.hooks;
1354
+ }
1355
+ const combined = {
1356
+ ...hooks,
1357
+ onBeforeCartCommit: async context => {
1358
+ await hooks?.onBeforeCartCommit?.(context);
1359
+ if (inventory !== this.getInventory()) {
1360
+ throw (0, _inventory.salesGuardError)('The sales context changed. Please try again.');
1361
+ }
1362
+ inventory.validate(context.proposedItems, context.currentItems);
1363
+ }
1364
+ };
1365
+ this.inventoryOrderHooks = {
1366
+ inventory,
1367
+ base: hooks,
1368
+ hooks: combined
1369
+ };
1370
+ return combined;
1311
1371
  }
1312
1372
  return optionHooks[moduleName] || this.otherParams[moduleName]?.hooks || otherHooks[moduleName];
1313
1373
  }
@@ -1355,7 +1415,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1355
1415
  const nextSubModuleOtherParams = this.buildSubModuleOtherParams(moduleName);
1356
1416
  await targetModule.updateOtherParams(nextSubModuleOtherParams, {
1357
1417
  changedParams,
1358
- cover
1418
+ cover,
1419
+ ...(moduleName === 'order' ? {
1420
+ hooks: this.getSubModuleHooks(moduleName)
1421
+ } : {})
1359
1422
  });
1360
1423
  }));
1361
1424
  }
@@ -1504,7 +1567,17 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1504
1567
  }
1505
1568
  return isChanged;
1506
1569
  }
1507
- async initialize(core, options = {}) {
1570
+ initialization;
1571
+
1572
+ /** Hosts can await readiness without changing Core's synchronous registration API. */
1573
+ whenInitialized() {
1574
+ return this.initialization || Promise.reject(new Error('Sales module is not registered'));
1575
+ }
1576
+ initialize(core, options = {}) {
1577
+ this.initialization = this.initializeSalesRuntime(core, options);
1578
+ return this.initialization;
1579
+ }
1580
+ async initializeSalesRuntime(core, options = {}) {
1508
1581
  this.resetPickupTimingCache();
1509
1582
  this.core = core;
1510
1583
  this.initializeOptions = options || {};
@@ -1563,17 +1636,19 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1563
1636
  this.serverOrderChangeUnsubscribe = null;
1564
1637
  this.paymentMethodsChangedUnsubscribe?.();
1565
1638
  this.paymentMethodsChangedUnsubscribe = null;
1566
- Object.keys(this.store).forEach(key => {
1567
- if (this.reusedSharedSubModuleNames.has(key)) return;
1639
+ for (const key of Object.keys(this.store)) {
1640
+ if (this.reusedSharedSubModuleNames.has(key)) continue;
1568
1641
  const sub = this.store[key];
1569
1642
  if (sub && typeof sub.destroy === 'function') {
1570
1643
  try {
1571
- sub.destroy();
1644
+ // A host may register a replacement after destroy resolves. Wait for
1645
+ // nested modules so their cleanup cannot unregister the replacement.
1646
+ await sub.destroy();
1572
1647
  } catch (error) {
1573
1648
  console.warn(`[BaseSales] 子模块 ${key} 销毁失败`, error);
1574
1649
  }
1575
1650
  }
1576
- });
1651
+ }
1577
1652
  this.currentSalesDetail = null;
1578
1653
  this.paymentNumberDevicePrefix = null;
1579
1654
  this.queuedServerOrderChanges = [];
@@ -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 | 6 | 4 | 5 | 3;
329
+ weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -2,7 +2,7 @@ import type { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import type { ProductData } from '../../modules';
3
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, ProductListStoreQueryParams, ProductEnrichmentContext } from '../../modules/ProductList';
5
+ import type { ProductListLoadProductsParams, ProductListStoreQueryParams } from '../../modules/ProductList';
6
6
  import type { AvailabilityDateRange, AvailabilityOccupancy, AvailabilityProduct, AvailabilityResource, GetProductTimeRangesRequest } from '../../modules/ResourcePlanner';
7
7
  import type { AvailabilityContextRef, ContextualAvailabilityQuery, ContextualAvailabilityQueryResult, ContextualCommitAvailabilitySelectionParams, ContextualProductTimeRangeGroup, UnifiedBookingSalesAddRetailProductParams, UnifiedBookingSalesApplyPlannerDiscountsParams, UnifiedBookingSalesCommitAvailabilitySelectionResult, UnifiedBookingSalesDiscountResult, UnifiedBookingSalesLoadProductsByScheduleDateParams, UnifiedBookingSalesLoadOpenDataParams, UnifiedBookingSalesPrepareAvailabilityProjectionParams, UnifiedBookingSalesPolicyResponse, UnifiedBookingSalesResolveProductAllergyRequirementParams, UnifiedBookingSalesStageRetailProductsParams, UnifiedBookingSalesState } from './types';
8
8
  import type { BusinessAvailability } from '../../modules/BusinessAvailability';
@@ -205,12 +205,6 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
205
205
  getProductCatalog(): {
206
206
  products: ProductData[];
207
207
  };
208
- /** Detail reads share the catalog adapter, including when basic detail is cached. */
209
- enrichProducts(products: ProductData[], context: ProductEnrichmentContext): Promise<ProductData[]>;
210
- /** Apply only enrichment changes, never the cart's selection/price snapshot. */
211
- applyProductEnrichment(before: ProductData, after: ProductData, context?: {
212
- signal?: AbortSignal;
213
- }): Promise<void>;
214
208
  /**
215
209
  * Read-only compatibility view for salesSdk skins. It reconnects a booking to its product but
216
210
  * must not be used to mutate tempOrder; all writes go through BaseSales / planner APIs.