@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inventory/index.d.ts +92 -0
- package/dist/inventory/index.js +500 -0
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/Order/index.d.ts +19 -2
- package/dist/modules/Order/index.js +1369 -1021
- package/dist/modules/Order/types.d.ts +5 -0
- package/dist/modules/ProductList/index.d.ts +1 -3
- package/dist/modules/ProductList/index.js +120 -192
- package/dist/modules/ProductList/types.d.ts +0 -8
- package/dist/modules/ProductList/types.js +0 -2
- package/dist/solution/BaseSales/index.d.ts +8 -0
- package/dist/solution/BaseSales/index.js +1382 -1207
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/UnifiedBookingSales/index.d.ts +1 -7
- package/dist/solution/UnifiedBookingSales/index.js +446 -586
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/inventory/index.d.ts +92 -0
- package/lib/inventory/index.js +280 -0
- package/lib/modules/Order/index.d.ts +19 -2
- package/lib/modules/Order/index.js +111 -19
- package/lib/modules/Order/types.d.ts +5 -0
- package/lib/modules/ProductList/index.d.ts +1 -3
- package/lib/modules/ProductList/index.js +22 -31
- package/lib/modules/ProductList/types.d.ts +0 -8
- package/lib/modules/ProductList/types.js +0 -1
- package/lib/solution/BaseSales/index.d.ts +8 -0
- package/lib/solution/BaseSales/index.js +82 -7
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/UnifiedBookingSales/index.d.ts +1 -7
- package/lib/solution/UnifiedBookingSales/index.js +12 -80
- 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
|
-
|
|
3708
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4221
|
-
|
|
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
|
|
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
|
-
|
|
4308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
225
|
-
await this.addProduct(
|
|
226
|
-
originalCallback(
|
|
215
|
+
assertScope();
|
|
216
|
+
await this.addProduct(products);
|
|
217
|
+
originalCallback(handledByOsServer ? result : {
|
|
227
218
|
...result,
|
|
228
219
|
data: {
|
|
229
220
|
...result.data,
|
|
230
|
-
list:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
1567
|
-
if (this.reusedSharedSubModuleNames.has(key))
|
|
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
|
-
|
|
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 |
|
|
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
|
|
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.
|