@pisell/pisellos 2.3.132 → 2.3.134
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/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/VenueBooking/index.d.ts +15 -2
- package/dist/solution/VenueBooking/index.js +1036 -760
- package/dist/solution/VenueBooking/types.d.ts +2 -1
- package/dist/solution/VenueBooking/types.js +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/VenueBooking/index.d.ts +15 -2
- package/lib/solution/VenueBooking/index.js +234 -88
- package/lib/solution/VenueBooking/types.d.ts +2 -1
- package/lib/solution/VenueBooking/types.js +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
- package/package.json +1 -1
|
@@ -34,6 +34,7 @@ var _itemRule = require("../../model/strategy/adapter/itemRule");
|
|
|
34
34
|
var _resource = require("./utils/resource");
|
|
35
35
|
var _timeSlot = require("./utils/timeSlot");
|
|
36
36
|
var _dateSummary = require("./utils/dateSummary");
|
|
37
|
+
var _smartPricing = require("./utils/smartPricing");
|
|
37
38
|
var _slotMerge = require("./utils/slotMerge");
|
|
38
39
|
var _utils3 = require("../../modules/Order/utils");
|
|
39
40
|
var _Order = require("../../modules/Order");
|
|
@@ -94,6 +95,12 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
94
95
|
customerLoginRefreshIdInFlight = null;
|
|
95
96
|
loadAllProductsInFlight = null;
|
|
96
97
|
productsLoaded = false;
|
|
98
|
+
// undefined 尚未同步宿主身份;null 表示已明确退出,禁止回退到旧用户插件。
|
|
99
|
+
smartPricingCustomerId;
|
|
100
|
+
smartPricingProductCatalog = [];
|
|
101
|
+
slotPriceMapCache = new Map();
|
|
102
|
+
slotPriceStrategyConfigs;
|
|
103
|
+
slotPriceScheduleList = undefined;
|
|
97
104
|
loadOpenDataConfigInFlight = null;
|
|
98
105
|
static OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
|
|
99
106
|
getLoggerContext() {
|
|
@@ -287,6 +294,9 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
287
294
|
return null;
|
|
288
295
|
}
|
|
289
296
|
resolveHolderCustomerId() {
|
|
297
|
+
if (this.smartPricingCustomerId !== undefined) {
|
|
298
|
+
return this.smartPricingCustomerId ?? undefined;
|
|
299
|
+
}
|
|
290
300
|
const customer = JSON.parse(this.window?.getLocalStorage?.('customer') || '{}');
|
|
291
301
|
if (customer?.id) return Number(customer.id);
|
|
292
302
|
return undefined;
|
|
@@ -338,52 +348,65 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
338
348
|
}
|
|
339
349
|
registerCustomerLoginListeners() {
|
|
340
350
|
this.clearLoginEffectListeners();
|
|
341
|
-
const
|
|
342
|
-
const registerAndLoginModule = this.core?.getModule('registerAndLogin');
|
|
343
|
-
const currentAccount = accountModule?.getCurrentAccount?.() || accountModule?.getAccount?.() || null;
|
|
344
|
-
const createHandleLogin = eventName => async payload => {
|
|
351
|
+
const handleLogin = async payload => {
|
|
345
352
|
const customerId = this.resolveCustomerIdFromLoginPayload(payload);
|
|
346
353
|
if (!customerId) return;
|
|
347
|
-
await this.
|
|
354
|
+
await this.refreshOrderMarketingForCustomer({
|
|
348
355
|
customerId
|
|
349
356
|
});
|
|
350
357
|
};
|
|
351
|
-
this.registerLoginEffect(_types3.AccountHooks.OnLogin,
|
|
352
|
-
this.registerLoginEffect(_types4.RegisterAndLoginHooks.onLoginSuccess,
|
|
358
|
+
this.registerLoginEffect(_types3.AccountHooks.OnLogin, handleLogin);
|
|
359
|
+
this.registerLoginEffect(_types4.RegisterAndLoginHooks.onLoginSuccess, handleLogin);
|
|
360
|
+
this.registerLoginEffect(_types3.AccountHooks.OnLogout, () => this.onCustomerLogout());
|
|
353
361
|
}
|
|
354
|
-
async
|
|
362
|
+
async refreshOrderMarketingForCustomer(params) {
|
|
355
363
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
356
|
-
if (this.customerLoginRefreshInFlight) {
|
|
357
|
-
if (this.customerLoginRefreshIdInFlight === params.customerId) {
|
|
358
|
-
await this.customerLoginRefreshInFlight;
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
364
|
+
if (this.customerLoginRefreshInFlight && this.customerLoginRefreshIdInFlight === params.customerId) {
|
|
361
365
|
await this.customerLoginRefreshInFlight;
|
|
366
|
+
return;
|
|
362
367
|
}
|
|
368
|
+
const previousRefresh = this.customerLoginRefreshInFlight;
|
|
363
369
|
this.customerLoginRefreshIdInFlight = params.customerId;
|
|
364
370
|
const refreshTask = (async () => {
|
|
365
|
-
//
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
// 身份切换按调用顺序执行;上一请求失败也不能阻止退出清理。
|
|
372
|
+
if (previousRefresh) await previousRefresh.catch(() => undefined);
|
|
373
|
+
this.smartPricingCustomerId = params.customerId;
|
|
374
|
+
const order = this.store.order;
|
|
375
|
+
if (params.customerId === null) {
|
|
376
|
+
order.clearOrderCustomer();
|
|
377
|
+
// 清除客户记录,保留表单定义供下次登录刷新。
|
|
378
|
+
const holder = this.store.holder;
|
|
379
|
+
for (const formId of Object.keys(holder?.getHolderFormMap() || {})) {
|
|
380
|
+
holder?.setFormData(formId, {
|
|
381
|
+
records: []
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
const tempOrder = order.ensureTempOrder();
|
|
386
|
+
if (Number(tempOrder.customer_id) !== params.customerId) {
|
|
387
|
+
const user = this.core.getPlugin('user')?.get?.();
|
|
388
|
+
order.updateTempOrderCustomer({
|
|
389
|
+
...(Number(user?.id) === params.customerId ? user : {}),
|
|
390
|
+
customer_id: params.customerId
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
// 先获取新客户的钱包/优惠资产,再按该上下文计算智能价。
|
|
394
|
+
await order.loadDiscountConfig({
|
|
395
|
+
customerId: params.customerId,
|
|
396
|
+
apply: false
|
|
375
397
|
});
|
|
398
|
+
if (this.store.holder) {
|
|
399
|
+
await this.store.holder.refreshAllFormRecords({
|
|
400
|
+
customer_id: params.customerId,
|
|
401
|
+
useCache: false
|
|
402
|
+
});
|
|
403
|
+
}
|
|
376
404
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
await this.
|
|
382
|
-
createIfMissing: true
|
|
383
|
-
});
|
|
384
|
-
this.store.order.persistTempOrder();
|
|
385
|
-
await this.refreshItemRuleQuantityLimits();
|
|
386
|
-
await this.refreshCartValidationPassed();
|
|
405
|
+
|
|
406
|
+
// 不复用身份切换前尚未返回的商品请求,避免旧客户的结果覆盖新价格。
|
|
407
|
+
if (this.loadAllProductsInFlight) await this.loadAllProductsInFlight;
|
|
408
|
+
if (this.productsLoaded) await this.loadAllProducts();
|
|
409
|
+
await this.refreshSmartPricing();
|
|
387
410
|
})();
|
|
388
411
|
this.customerLoginRefreshInFlight = refreshTask;
|
|
389
412
|
try {
|
|
@@ -407,6 +430,12 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
407
430
|
this.store.status = 'initializing';
|
|
408
431
|
this.store.error = null;
|
|
409
432
|
this.store.rawResourceData = [];
|
|
433
|
+
this.smartPricingCustomerId = undefined;
|
|
434
|
+
this.smartPricingProductCatalog = [];
|
|
435
|
+
this.productsLoaded = false;
|
|
436
|
+
this.slotPriceStrategyConfigs = undefined;
|
|
437
|
+
this.slotPriceScheduleList = undefined;
|
|
438
|
+
this.slotPriceMapCache.clear();
|
|
410
439
|
this.baseSlotConfig = {
|
|
411
440
|
..._types.DEFAULT_SLOT_CONFIG,
|
|
412
441
|
...(options.otherParams?.slotConfig || {})
|
|
@@ -644,6 +673,80 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
644
673
|
failures: []
|
|
645
674
|
};
|
|
646
675
|
}
|
|
676
|
+
resolveCustomerIdForSmartPricing() {
|
|
677
|
+
if (this.smartPricingCustomerId !== undefined) return this.smartPricingCustomerId ?? undefined;
|
|
678
|
+
try {
|
|
679
|
+
const user = this.core?.getPlugin('user');
|
|
680
|
+
const customerId = Number(user?.get?.()?.id);
|
|
681
|
+
return Number.isFinite(customerId) && customerId > 0 ? customerId : undefined;
|
|
682
|
+
} catch {
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
buildSmartPricingContext() {
|
|
687
|
+
const strategyContext = this.otherParams?.strategy_context || {};
|
|
688
|
+
const orderWalletIds = this.store.order?.getAvailableWalletIds?.();
|
|
689
|
+
const fallbackWalletIds = this.otherParams?.available_wallet_ids ?? this.otherParams?.availableWalletIds ?? strategyContext.available_wallet_ids ?? strategyContext.availableWalletIds;
|
|
690
|
+
const walletIds = this.smartPricingCustomerId === null ? [] : orderWalletIds?.length ? orderWalletIds : fallbackWalletIds;
|
|
691
|
+
return {
|
|
692
|
+
evaluator: this.core?.context?.dataVariantEvaluator,
|
|
693
|
+
scheduleList: this.store.schedule?.getScheduleList?.() || [],
|
|
694
|
+
menuList: this.otherParams?.menuList || this.otherParams?.openData?.menuList || this.core?.context?.menuList || [],
|
|
695
|
+
customerId: this.resolveCustomerIdForSmartPricing(),
|
|
696
|
+
availableWalletIds: Array.isArray(walletIds) ? walletIds : [],
|
|
697
|
+
channel: this.otherParams?.channel ?? strategyContext.channel,
|
|
698
|
+
businessCode: this.otherParams?.businessCode ?? strategyContext.business_code ?? strategyContext.businessCode,
|
|
699
|
+
orderType: this.otherParams?.orderType || this.otherParams?.type || strategyContext.orderType || strategyContext.order_type,
|
|
700
|
+
strategy_context: strategyContext
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
buildSlotPriceMapForDate(date, config, productIds) {
|
|
704
|
+
const context = this.buildSmartPricingContext();
|
|
705
|
+
const strategyConfigs = context.evaluator?.getStrategyConfigs?.();
|
|
706
|
+
if (strategyConfigs !== this.slotPriceStrategyConfigs) {
|
|
707
|
+
this.slotPriceMapCache.clear();
|
|
708
|
+
this.slotPriceStrategyConfigs = strategyConfigs;
|
|
709
|
+
}
|
|
710
|
+
if (context.scheduleList !== this.slotPriceScheduleList) {
|
|
711
|
+
this.slotPriceMapCache.clear();
|
|
712
|
+
this.slotPriceScheduleList = context.scheduleList;
|
|
713
|
+
}
|
|
714
|
+
const cacheKey = JSON.stringify([date, config, productIds, context.customerId, context.availableWalletIds, context.channel, context.businessCode, context.orderType]);
|
|
715
|
+
const cached = this.slotPriceMapCache.get(cacheKey);
|
|
716
|
+
if (cached) return cached;
|
|
717
|
+
const priceMap = (0, _smartPricing.buildSlotPriceMapByScheduleSegments)({
|
|
718
|
+
products: this.smartPricingProductCatalog,
|
|
719
|
+
productIds,
|
|
720
|
+
date,
|
|
721
|
+
timeLabels: (0, _timeSlot.generateTimeLabels)(config),
|
|
722
|
+
config,
|
|
723
|
+
context
|
|
724
|
+
});
|
|
725
|
+
this.slotPriceMapCache.set(cacheKey, priceMap);
|
|
726
|
+
return priceMap;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** 规则或客户上下文更新后,同步商品展示、已选时段和订单汇总。 */
|
|
730
|
+
async refreshSmartPricing() {
|
|
731
|
+
if (this.loadAllProductsInFlight) await this.loadAllProductsInFlight;
|
|
732
|
+
this.slotPriceMapCache.clear();
|
|
733
|
+
if (!this.productsLoaded || !this.store.order) return;
|
|
734
|
+
await this.store.schedule?.loadAllSchedule();
|
|
735
|
+
const products = (0, _smartPricing.formatProductsWithDataVariant)(this.smartPricingProductCatalog, this.buildSmartPricingContext());
|
|
736
|
+
this.store.venueProducts?.addProduct(products.filter(product => product.duration != null));
|
|
737
|
+
this.store.addonProducts?.addProduct(products.filter(product => product.duration == null));
|
|
738
|
+
this.recalculateOrderPricesFromSmartPricing();
|
|
739
|
+
this.store.order.applyDiscount();
|
|
740
|
+
await this.store.order.recalculateSummary({
|
|
741
|
+
createIfMissing: false
|
|
742
|
+
});
|
|
743
|
+
this.store.order.persistTempOrder();
|
|
744
|
+
await this.refreshItemRuleQuantityLimits();
|
|
745
|
+
await this.refreshCartValidationPassed();
|
|
746
|
+
await this.core.effects.emit(_types.VenueBookingHooks.onSmartPricingUpdated, {
|
|
747
|
+
customerId: this.resolveCustomerIdForSmartPricing()
|
|
748
|
+
});
|
|
749
|
+
}
|
|
647
750
|
|
|
648
751
|
// ─── 场地商品 & 附加商品 ───
|
|
649
752
|
|
|
@@ -660,19 +763,34 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
660
763
|
if (!this.store.venueProducts) throw new Error('venueProducts 模块未初始化');
|
|
661
764
|
if (!this.store.addonProducts) throw new Error('addonProducts 模块未初始化');
|
|
662
765
|
await this.loadOpenDataConfig();
|
|
766
|
+
if (!this.store.schedule) throw new Error('schedule 模块未初始化');
|
|
767
|
+
await this.store.schedule.loadAllSchedule();
|
|
663
768
|
const associatedMenus = this.otherParams?.openData?.['menu.associated_menus'] || [];
|
|
664
769
|
if (!associatedMenus.length) {
|
|
665
770
|
this.logMethodError('loadAllProducts', new Error('未获取到餐牌配置(menu.associated_menus),请检查 OpenData 配置'));
|
|
666
771
|
throw new Error('未获取到餐牌配置(menu.associated_menus),请检查 OpenData 配置');
|
|
667
772
|
}
|
|
668
773
|
const menuListIds = associatedMenus.map(n => Number(n.value));
|
|
774
|
+
const pricingContext = this.buildSmartPricingContext();
|
|
669
775
|
const allProducts = await this.store.venueProducts.loadProducts({
|
|
670
776
|
menu_list_ids: menuListIds,
|
|
671
777
|
cacheId: this.cacheId,
|
|
672
778
|
schedule_date: (0, _dayjs.default)().format('YYYY-MM-DD'),
|
|
673
|
-
schedule_datetime: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
|
|
779
|
+
schedule_datetime: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'),
|
|
780
|
+
customer_id: this.resolveCustomerIdForSmartPricing(),
|
|
781
|
+
strategy_context: {
|
|
782
|
+
...pricingContext.strategy_context,
|
|
783
|
+
channel: pricingContext.channel,
|
|
784
|
+
business_code: pricingContext.businessCode,
|
|
785
|
+
order_type: pricingContext.orderType,
|
|
786
|
+
available_wallet_ids: pricingContext.availableWalletIds
|
|
787
|
+
}
|
|
674
788
|
});
|
|
675
|
-
const
|
|
789
|
+
const loadedList = Array.isArray(allProducts) ? allProducts : [];
|
|
790
|
+
const rawProducts = this.store.venueProducts.getRawProductPriceSources(loadedList.map(product => product.id));
|
|
791
|
+
this.smartPricingProductCatalog = await Promise.all(loadedList.map(async product => rawProducts.get(product.id) ?? (await this.core.server?.getProducts?.()?.getProductById(product.id)) ?? product));
|
|
792
|
+
const list = (0, _smartPricing.formatProductsWithDataVariant)(this.smartPricingProductCatalog, this.buildSmartPricingContext());
|
|
793
|
+
this.slotPriceMapCache.clear();
|
|
676
794
|
const venueList = list.filter(p => p.duration != null);
|
|
677
795
|
const addonList = list.filter(p => p.duration == null);
|
|
678
796
|
const venueStore = this.store.venueProducts?.store;
|
|
@@ -680,7 +798,9 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
680
798
|
venueStore.list = venueList.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
|
|
681
799
|
}
|
|
682
800
|
this.store.addonProducts.addProduct(addonList);
|
|
683
|
-
|
|
801
|
+
|
|
802
|
+
// 无规则命中时仍应回到原始商品价,不能回退到加载时刻的智能价。
|
|
803
|
+
this.resourceProductMap = (0, _resource.buildResourceProductMap)(this.smartPricingProductCatalog.filter(product => product.duration != null));
|
|
684
804
|
|
|
685
805
|
// 加载 holder 表单
|
|
686
806
|
if (this.store.order) {
|
|
@@ -704,8 +824,10 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
704
824
|
}
|
|
705
825
|
}
|
|
706
826
|
async loadVenueProducts() {
|
|
707
|
-
|
|
708
|
-
|
|
827
|
+
await this.loadAllProducts();
|
|
828
|
+
// 同步会话中已选的时段,避免新网格价格与恢复的购物车价格不一致。
|
|
829
|
+
await this.refreshSmartPricing();
|
|
830
|
+
return this.getVenueProducts();
|
|
709
831
|
}
|
|
710
832
|
async loadAddonProducts() {
|
|
711
833
|
const result = await this.loadAllProducts();
|
|
@@ -866,31 +988,22 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
866
988
|
config: this.baseSlotConfig,
|
|
867
989
|
rawResources: this.store.rawResourceData,
|
|
868
990
|
resourceProductMap: this.resourceProductMap,
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
resolveConfig: date => this.resolveSlotConfigForDate(date)
|
|
991
|
+
resolveConfig: date => this.resolveSlotConfigForDate(date),
|
|
992
|
+
buildSlotPriceMap: (date, config, productIds) => this.buildSlotPriceMapForDate(date, config, productIds)
|
|
872
993
|
});
|
|
873
994
|
}
|
|
874
995
|
getTimeSlotGrid(date) {
|
|
875
996
|
const resolvedSlotConfig = this.syncOperatingHoursToSlotConfig(date);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const timeLabels = (0, _timeSlot.generateTimeLabels)(resolvedSlotConfig);
|
|
880
|
-
const timePoints = timeLabels.map(label => `${date} ${label}`);
|
|
881
|
-
quotationPriceMap = this.store.quotation.buildProductPriceMap({
|
|
882
|
-
productIds,
|
|
883
|
-
timePoints,
|
|
884
|
-
channel: this.otherParams?.channel
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
return (0, _timeSlot.buildTimeSlotGrid)({
|
|
997
|
+
const productIds = [...new Set([...this.resourceProductMap.values()].flat().map(mapping => mapping.productId))];
|
|
998
|
+
const slotPriceMap = this.buildSlotPriceMapForDate(date, resolvedSlotConfig, productIds);
|
|
999
|
+
const grid = (0, _timeSlot.buildTimeSlotGrid)({
|
|
888
1000
|
date,
|
|
889
1001
|
config: resolvedSlotConfig,
|
|
890
1002
|
rawResources: this.store.rawResourceData,
|
|
891
1003
|
resourceProductMap: this.resourceProductMap,
|
|
892
|
-
|
|
1004
|
+
slotPriceMap
|
|
893
1005
|
});
|
|
1006
|
+
return grid;
|
|
894
1007
|
}
|
|
895
1008
|
|
|
896
1009
|
// ─── 时间槽订单操作 ───
|
|
@@ -1199,9 +1312,7 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1199
1312
|
booking_uid: bookingUuid,
|
|
1200
1313
|
price_breakdown: (0, _slotMerge.buildPriceBreakdown)({
|
|
1201
1314
|
group,
|
|
1202
|
-
productId: mapping.productId
|
|
1203
|
-
quotation: this.store.quotation,
|
|
1204
|
-
channel: this.otherParams?.channel
|
|
1315
|
+
productId: mapping.productId
|
|
1205
1316
|
})
|
|
1206
1317
|
},
|
|
1207
1318
|
_origin: venueProductOrigin
|
|
@@ -1252,6 +1363,7 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1252
1363
|
// ─── 配置 ───
|
|
1253
1364
|
|
|
1254
1365
|
setSlotConfig(config) {
|
|
1366
|
+
this.slotPriceMapCache.clear();
|
|
1255
1367
|
this.baseSlotConfig = {
|
|
1256
1368
|
...this.baseSlotConfig,
|
|
1257
1369
|
...config
|
|
@@ -1274,6 +1386,8 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1274
1386
|
if (!this.store.schedule) throw new Error('schedule 模块未初始化');
|
|
1275
1387
|
await this.store.schedule.loadAllSchedule();
|
|
1276
1388
|
this.injectScheduleResolverToQuotation();
|
|
1389
|
+
this.slotPriceMapCache.clear();
|
|
1390
|
+
await this.refreshSmartPricing();
|
|
1277
1391
|
this.logMethodSuccess('loadSchedules');
|
|
1278
1392
|
} catch (error) {
|
|
1279
1393
|
this.logMethodError('loadSchedules', error);
|
|
@@ -1313,7 +1427,7 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1313
1427
|
customerId: params.customerId
|
|
1314
1428
|
});
|
|
1315
1429
|
try {
|
|
1316
|
-
await this.
|
|
1430
|
+
await this.refreshOrderMarketingForCustomer({
|
|
1317
1431
|
customerId: params.customerId
|
|
1318
1432
|
});
|
|
1319
1433
|
this.logMethodSuccess('onCustomerLogin', {
|
|
@@ -1324,8 +1438,15 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1324
1438
|
throw error;
|
|
1325
1439
|
}
|
|
1326
1440
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1441
|
+
|
|
1442
|
+
/** 宿主退出登录或以匿名身份重新进入时,清理客户资产并恢复匿名价格。 */
|
|
1443
|
+
async onCustomerLogout() {
|
|
1444
|
+
await this.refreshOrderMarketingForCustomer({
|
|
1445
|
+
customerId: null
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
recalculateOrderPricesFromSmartPricing() {
|
|
1449
|
+
if (!this.store.order) return;
|
|
1329
1450
|
const tempOrder = this.store.order.getTempOrder();
|
|
1330
1451
|
if (!tempOrder?.products?.length) return;
|
|
1331
1452
|
const now = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
|
|
@@ -1336,40 +1457,67 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1336
1457
|
const mapping = mappings.find(m => Number(m.productId) === Number(product.product_id)) || mappings[0];
|
|
1337
1458
|
if (!mapping) continue;
|
|
1338
1459
|
const slots = (0, _slotMerge.expandMergedSlotToIndividual)(product, this.store.slotConfig.slotDurationMinutes);
|
|
1339
|
-
const updatedSlots = slots.map(slot =>
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1460
|
+
const updatedSlots = slots.map(slot => {
|
|
1461
|
+
const slotStart = (0, _dayjs.default)(slot.startTime);
|
|
1462
|
+
let date = slotStart.format('YYYY-MM-DD');
|
|
1463
|
+
const previousDate = slotStart.subtract(1, 'day').format('YYYY-MM-DD');
|
|
1464
|
+
const previousConfig = this.resolveSlotConfigForDate(previousDate);
|
|
1465
|
+
if ((0, _timeSlot.isBusinessHoursCrossDay)(previousConfig) && slotStart.format('HH:mm') < previousConfig.businessStartTime) {
|
|
1466
|
+
date = previousDate;
|
|
1467
|
+
}
|
|
1468
|
+
const slotPriceMap = this.buildSlotPriceMapForDate(date, this.resolveSlotConfigForDate(date), [mapping.productId]);
|
|
1469
|
+
return {
|
|
1470
|
+
...slot,
|
|
1343
1471
|
productId: mapping.productId,
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1472
|
+
price: (0, _smartPricing.getSlotPriceFromMap)({
|
|
1473
|
+
slotPriceMap,
|
|
1474
|
+
productId: mapping.productId,
|
|
1475
|
+
slotStartTime: slot.startTime,
|
|
1476
|
+
fallbackPrice: mapping.price
|
|
1477
|
+
})
|
|
1478
|
+
};
|
|
1479
|
+
});
|
|
1348
1480
|
const merged = (0, _slotMerge.mergeConsecutiveSlots)(updatedSlots);
|
|
1349
1481
|
if (merged.length === 1) {
|
|
1350
|
-
product
|
|
1351
|
-
product.original_price = merged[0].totalPrice;
|
|
1482
|
+
this.resetProductPriceForSmartPricing(product, merged[0].totalPrice);
|
|
1352
1483
|
product.metadata.price_breakdown = (0, _slotMerge.buildPriceBreakdown)({
|
|
1353
1484
|
group: merged[0],
|
|
1354
|
-
productId: mapping.productId
|
|
1355
|
-
quotation: this.store.quotation,
|
|
1356
|
-
channel: this.otherParams?.channel
|
|
1485
|
+
productId: mapping.productId
|
|
1357
1486
|
});
|
|
1358
1487
|
}
|
|
1359
1488
|
} else if (product.product_id != null) {
|
|
1360
|
-
const
|
|
1489
|
+
const price = (0, _smartPricing.getPriceForProductAtDatetime)({
|
|
1490
|
+
products: this.smartPricingProductCatalog,
|
|
1491
|
+
context: this.buildSmartPricingContext(),
|
|
1361
1492
|
productId: product.product_id,
|
|
1362
1493
|
variantId: product.product_variant_id ?? undefined,
|
|
1363
1494
|
datetime: now,
|
|
1364
|
-
|
|
1495
|
+
fallbackPrice: product.metadata?.source_product_price ?? product.original_price
|
|
1365
1496
|
});
|
|
1366
|
-
|
|
1367
|
-
product.selling_price = quotationPrice;
|
|
1368
|
-
product.original_price = quotationPrice;
|
|
1369
|
-
}
|
|
1497
|
+
this.resetProductPriceForSmartPricing(product, price);
|
|
1370
1498
|
}
|
|
1371
1499
|
}
|
|
1372
1500
|
}
|
|
1501
|
+
resetProductPriceForSmartPricing(product, price) {
|
|
1502
|
+
const mainPrice = new _decimal.default(price).plus((0, _utils3.sumOptionUnitPrice)((0, _utils3.getProductSkuOptions)(product))).toFixed(2);
|
|
1503
|
+
product.metadata = {
|
|
1504
|
+
...product.metadata,
|
|
1505
|
+
source_product_price: price,
|
|
1506
|
+
main_product_original_price: mainPrice,
|
|
1507
|
+
main_product_selling_price: mainPrice,
|
|
1508
|
+
price_schema_version: 2
|
|
1509
|
+
};
|
|
1510
|
+
product.discount_list = [];
|
|
1511
|
+
product.selling_price = (0, _utils3.composeLinePrice)({
|
|
1512
|
+
mainPrice,
|
|
1513
|
+
bundle: product.product_bundle
|
|
1514
|
+
});
|
|
1515
|
+
product.original_price = (0, _utils3.composeLinePrice)({
|
|
1516
|
+
mainPrice,
|
|
1517
|
+
bundle: product.product_bundle,
|
|
1518
|
+
useOriginalBundle: true
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1373
1521
|
async scanCode(code, customerId) {
|
|
1374
1522
|
this.logMethodStart('scanCode', {
|
|
1375
1523
|
code
|
|
@@ -1565,19 +1713,16 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1565
1713
|
});
|
|
1566
1714
|
try {
|
|
1567
1715
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1568
|
-
if (!product.metadata?.venue_booking &&
|
|
1569
|
-
const
|
|
1716
|
+
if (!product.metadata?.venue_booking && product.product_id != null) {
|
|
1717
|
+
const price = (0, _smartPricing.getPriceForProductAtDatetime)({
|
|
1718
|
+
products: this.smartPricingProductCatalog,
|
|
1719
|
+
context: this.buildSmartPricingContext(),
|
|
1570
1720
|
productId: product.product_id,
|
|
1571
1721
|
variantId: product.product_variant_id ?? undefined,
|
|
1572
1722
|
datetime: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'),
|
|
1573
|
-
|
|
1723
|
+
fallbackPrice: product.metadata?.source_product_price ?? product.selling_price
|
|
1574
1724
|
});
|
|
1575
|
-
|
|
1576
|
-
product.selling_price = quotationPrice;
|
|
1577
|
-
product.original_price = quotationPrice;
|
|
1578
|
-
} else if (product.selling_price != null) {
|
|
1579
|
-
product.original_price = product.selling_price;
|
|
1580
|
-
}
|
|
1725
|
+
this.resetProductPriceForSmartPricing(product, price);
|
|
1581
1726
|
}
|
|
1582
1727
|
const products = await this.store.order.addProductToOrder(product);
|
|
1583
1728
|
await this.refreshItemRuleQuantityLimits();
|
|
@@ -2093,6 +2238,7 @@ class VenueBookingImpl extends _BaseSales.BaseSalesImpl {
|
|
|
2093
2238
|
async setOtherParams(params, {
|
|
2094
2239
|
cover = false
|
|
2095
2240
|
} = {}) {
|
|
2241
|
+
this.slotPriceMapCache.clear();
|
|
2096
2242
|
if (cover) {
|
|
2097
2243
|
this.otherParams = params;
|
|
2098
2244
|
} else {
|
|
@@ -7,7 +7,8 @@ export declare enum VenueBookingHooks {
|
|
|
7
7
|
onDestroy = "venueBooking:onDestroy",
|
|
8
8
|
onRetryInit = "venueBooking:onRetryInit",
|
|
9
9
|
onRefresh = "venueBooking:onRefresh",
|
|
10
|
-
onCartValidationChanged = "venueBooking:onCartValidationChanged"
|
|
10
|
+
onCartValidationChanged = "venueBooking:onCartValidationChanged",
|
|
11
|
+
onSmartPricingUpdated = "venueBooking:onSmartPricingUpdated"
|
|
11
12
|
}
|
|
12
13
|
export type VenueBookingStatus = 'idle' | 'initializing' | 'ready' | 'error';
|
|
13
14
|
export interface VenueBookingEntryContext {
|
|
@@ -10,6 +10,7 @@ let VenueBookingHooks = exports.VenueBookingHooks = /*#__PURE__*/function (Venue
|
|
|
10
10
|
VenueBookingHooks["onRetryInit"] = "venueBooking:onRetryInit";
|
|
11
11
|
VenueBookingHooks["onRefresh"] = "venueBooking:onRefresh";
|
|
12
12
|
VenueBookingHooks["onCartValidationChanged"] = "venueBooking:onCartValidationChanged";
|
|
13
|
+
VenueBookingHooks["onSmartPricingUpdated"] = "venueBooking:onSmartPricingUpdated";
|
|
13
14
|
return VenueBookingHooks;
|
|
14
15
|
}({}); // ─── 场地预定核心类型 ───
|
|
15
16
|
const DEFAULT_SLOT_CONFIG = exports.DEFAULT_SLOT_CONFIG = {
|
|
@@ -9,4 +9,5 @@ export declare function buildDateRangeSummary(params: {
|
|
|
9
9
|
quotationModule?: QuotationModule;
|
|
10
10
|
channel?: string;
|
|
11
11
|
resolveConfig?: (date: string) => VenueBookingSlotConfig;
|
|
12
|
+
buildSlotPriceMap?: (date: string, config: VenueBookingSlotConfig, productIds: number[]) => Map<string, string | null>;
|
|
12
13
|
}): VenueDateSummaryItem[];
|
|
@@ -16,17 +16,19 @@ function buildDateRangeSummary(params) {
|
|
|
16
16
|
resourceProductMap,
|
|
17
17
|
quotationModule,
|
|
18
18
|
channel,
|
|
19
|
-
resolveConfig
|
|
19
|
+
resolveConfig,
|
|
20
|
+
buildSlotPriceMap
|
|
20
21
|
} = params;
|
|
21
22
|
const result = [];
|
|
22
|
-
const productIds = quotationModule ? [...new Set([...resourceProductMap.values()].flat().map(m => m.productId))] : [];
|
|
23
|
+
const productIds = buildSlotPriceMap || quotationModule ? [...new Set([...resourceProductMap.values()].flat().map(m => m.productId))] : [];
|
|
23
24
|
let cursor = (0, _dayjs.default)(startDate);
|
|
24
25
|
const end = (0, _dayjs.default)(endDate);
|
|
25
26
|
while (!cursor.isAfter(end)) {
|
|
26
27
|
const date = cursor.format('YYYY-MM-DD');
|
|
27
28
|
const effectiveConfig = resolveConfig?.(date) || config;
|
|
28
29
|
let quotationPriceMap;
|
|
29
|
-
|
|
30
|
+
const slotPriceMap = buildSlotPriceMap?.(date, effectiveConfig, productIds);
|
|
31
|
+
if (!slotPriceMap && quotationModule && productIds.length) {
|
|
30
32
|
const timeLabels = (0, _timeSlot.generateTimeLabels)(effectiveConfig);
|
|
31
33
|
const timePoints = timeLabels.map(label => `${date} ${label}`);
|
|
32
34
|
quotationPriceMap = quotationModule.buildProductPriceMap({
|
|
@@ -40,7 +42,8 @@ function buildDateRangeSummary(params) {
|
|
|
40
42
|
config: effectiveConfig,
|
|
41
43
|
rawResources,
|
|
42
44
|
resourceProductMap,
|
|
43
|
-
quotationPriceMap
|
|
45
|
+
quotationPriceMap,
|
|
46
|
+
slotPriceMap
|
|
44
47
|
});
|
|
45
48
|
let totalSlots = 0;
|
|
46
49
|
let availableSlots = 0;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ProductData } from '../../../modules/Product/types';
|
|
2
|
+
import type { ScheduleItem } from '../../../modules/Schedule/types';
|
|
3
|
+
import type { DataVariantBusinessData, DataVariantMenu, DataVariantProduct } from '../../../model/strategy/adapter/dataVariant/type';
|
|
4
|
+
import type { DataVariantEvaluator } from '../../../model/strategy/adapter/dataVariant/evaluator';
|
|
5
|
+
import type { StrategyConfig } from '../../../model/strategy/type';
|
|
6
|
+
import type { VenueBookingSlotConfig } from '../types';
|
|
7
|
+
/** VenueBooking 智能定价运行时上下文 */
|
|
8
|
+
export interface VenueBookingSmartPricingContext {
|
|
9
|
+
evaluator?: DataVariantEvaluator | null;
|
|
10
|
+
scheduleList?: ScheduleItem[];
|
|
11
|
+
menuList?: DataVariantMenu[];
|
|
12
|
+
customerId?: number | string;
|
|
13
|
+
availableWalletIds?: Array<number | string>;
|
|
14
|
+
channel?: string;
|
|
15
|
+
businessCode?: string;
|
|
16
|
+
orderType?: string;
|
|
17
|
+
strategy_context?: Record<string, any>;
|
|
18
|
+
strategyConfigs?: StrategyConfig[];
|
|
19
|
+
}
|
|
20
|
+
/** 从商品上读取展示/算价用的 price 字段 */
|
|
21
|
+
export declare function extractProductPrice(product: DataVariantProduct): string | null;
|
|
22
|
+
/** 组装 DataVariant 评估入参,对齐 OS Server buildProductFormatterContext 语义 */
|
|
23
|
+
export declare function buildDataVariantBusinessData(products: ProductData[], context: VenueBookingSmartPricingContext, scheduleDateTime: string): DataVariantBusinessData;
|
|
24
|
+
/**
|
|
25
|
+
* 对商品列表执行一次智能定价格式化(loadProducts 后使用)。
|
|
26
|
+
* evaluator 未注入时原样返回并打 warning。
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatProductsWithDataVariant(products: ProductData[], context: VenueBookingSmartPricingContext, scheduleDateTime?: string): ProductData[];
|
|
29
|
+
/**
|
|
30
|
+
* 根据 schedule_time_points 与营业开始时间,构造当天需要评估的 datetime 列表(升序)。
|
|
31
|
+
* 跨日营业时,早于营业开始小时的时间点归到次日。
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildEvaluationDatetimes(params: {
|
|
34
|
+
date: string;
|
|
35
|
+
config: VenueBookingSlotConfig;
|
|
36
|
+
scheduleTimePoints: string[];
|
|
37
|
+
}): string[];
|
|
38
|
+
/** 找到 slot 时间所属分段的评估 datetime(max(evalDt <= slotDt)) */
|
|
39
|
+
export declare function findSegmentEvalDatetime(slotDatetime: string, evalDatetimes: string[]): string;
|
|
40
|
+
/** 将 slot 标签转为与 timeSlot 网格一致的 slotStart 字符串 */
|
|
41
|
+
export declare function buildSlotStartDatetime(date: string, label: string, config: VenueBookingSlotConfig): string;
|
|
42
|
+
/**
|
|
43
|
+
* 按 schedule 分段批量 resolveProducts,输出与 quotation 相同 key 格式的 slot 价格 Map。
|
|
44
|
+
* key: `${productId}:${slotStart}`,value: price | null
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildSlotPriceMapByScheduleSegments(params: {
|
|
47
|
+
products: ProductData[];
|
|
48
|
+
productIds: number[];
|
|
49
|
+
date: string;
|
|
50
|
+
timeLabels: string[];
|
|
51
|
+
config: VenueBookingSlotConfig;
|
|
52
|
+
context: VenueBookingSmartPricingContext;
|
|
53
|
+
}): Map<string, string | null>;
|
|
54
|
+
/**
|
|
55
|
+
* 指定商品在某一 datetime 的智能定价(addon / 单点重算用)。
|
|
56
|
+
*/
|
|
57
|
+
export declare function getPriceForProductAtDatetime(params: {
|
|
58
|
+
products: ProductData[];
|
|
59
|
+
context: VenueBookingSmartPricingContext;
|
|
60
|
+
productId: number;
|
|
61
|
+
variantId?: number;
|
|
62
|
+
datetime: string;
|
|
63
|
+
fallbackPrice?: string;
|
|
64
|
+
}): string;
|
|
65
|
+
/** 从 slot 价格 Map 读取单价,找不到则回退 fallback */
|
|
66
|
+
export declare function getSlotPriceFromMap(params: {
|
|
67
|
+
slotPriceMap: Map<string, string | null>;
|
|
68
|
+
productId: number;
|
|
69
|
+
slotStartTime: string;
|
|
70
|
+
fallbackPrice?: string;
|
|
71
|
+
}): string;
|