@pisell/pisellos 2.2.314 → 2.2.316
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 +2 -0
- package/dist/index.js +3 -1
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
- package/dist/model/strategy/adapter/itemRule/adapter.js +74 -7
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
- package/dist/model/strategy/adapter/itemRule/evaluator.js +13 -0
- package/dist/model/strategy/adapter/itemRule/type.d.ts +40 -1
- package/dist/model/strategy/adapter/itemRule/type.js +10 -0
- package/dist/model/strategy/adapter/promotion/evaluator.js +89 -104
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +28 -4
- package/dist/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/dist/modules/BookingContext/utils/productExtend.js +62 -18
- package/dist/modules/BookingContext/utils/resources.js +1 -1
- package/dist/modules/BusinessAvailability/index.d.ts +30 -0
- package/dist/modules/BusinessAvailability/index.js +204 -0
- package/dist/modules/Cart/index.js +4 -3
- package/dist/modules/Cart/types.d.ts +2 -0
- package/dist/modules/Cart/utils/cartProduct.d.ts +3 -1
- package/dist/modules/Cart/utils/cartProduct.js +59 -32
- package/dist/modules/FulfillmentTiming/index.d.ts +11 -0
- package/dist/modules/FulfillmentTiming/index.js +407 -0
- package/dist/modules/FulfillmentTiming/types.d.ts +67 -0
- package/dist/modules/FulfillmentTiming/types.js +1 -0
- package/dist/modules/Holder/utils.d.ts +2 -0
- package/dist/modules/Holder/utils.js +32 -2
- package/dist/modules/OpenData/contactInfo.d.ts +20 -0
- package/dist/modules/OpenData/contactInfo.js +70 -0
- package/dist/modules/OpenData/index.d.ts +1 -0
- package/dist/modules/OpenData/index.js +1 -0
- package/dist/modules/OpenData/types.d.ts +23 -0
- package/dist/modules/Order/holderRelations.d.ts +14 -0
- package/dist/modules/Order/holderRelations.js +154 -0
- package/dist/modules/Order/index.d.ts +16 -4
- package/dist/modules/Order/index.js +926 -511
- package/dist/modules/Order/localEftposState.d.ts +13 -0
- package/dist/modules/Order/localEftposState.js +39 -0
- package/dist/modules/Order/pickupTiming.d.ts +46 -0
- package/dist/modules/Order/pickupTiming.js +53 -0
- package/dist/modules/Order/types.d.ts +49 -3
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
- package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
- package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
- package/dist/modules/Order/utils.d.ts +2 -1
- package/dist/modules/Order/utils.js +61 -17
- package/dist/modules/Payment/index.js +9 -10
- package/dist/modules/Product/types.d.ts +11 -0
- package/dist/modules/ProductList/index.js +1 -1
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -0
- package/dist/modules/ResourcePlanner/planner.js +332 -23
- package/dist/modules/ResourcePlanner/types.d.ts +29 -0
- package/dist/modules/Rules/index.js +62 -61
- package/dist/modules/Schedule/index.d.ts +8 -0
- package/dist/modules/Schedule/index.js +141 -39
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/plugins/request.d.ts +7 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1557 -1322
- package/dist/server/modules/form/index.d.ts +19 -0
- package/dist/server/modules/form/index.js +205 -0
- package/dist/server/modules/form/types.d.ts +13 -0
- package/dist/server/modules/form/types.js +3 -0
- package/dist/server/modules/index.d.ts +3 -0
- package/dist/server/modules/index.js +4 -0
- package/dist/server/modules/order/index.d.ts +19 -0
- package/dist/server/modules/order/index.js +1071 -774
- package/dist/server/modules/order/types.d.ts +4 -0
- package/dist/server/modules/products/index.d.ts +28 -7
- package/dist/server/modules/products/index.js +1297 -770
- package/dist/server/modules/products/replication.d.ts +8 -0
- package/dist/server/modules/products/replication.js +69 -0
- package/dist/server/modules/products/types.d.ts +12 -1
- package/dist/server/modules/products/types.js +4 -0
- package/dist/server/test.json +1 -1
- package/dist/server/utils/small-ticket.d.ts +1 -85
- package/dist/server/utils/small-ticket.js +2 -2542
- package/dist/shared/payment/loadPaymentMethods.d.ts +9 -0
- package/dist/shared/payment/loadPaymentMethods.js +66 -0
- package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
- package/dist/shared/receipt/small-ticket.d.ts +96 -0
- package/dist/shared/receipt/small-ticket.js +2763 -0
- package/dist/shared/receipt/templateVisibility.d.ts +26 -0
- package/dist/shared/receipt/templateVisibility.js +68 -0
- package/dist/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
- package/dist/shared/sales/loadSalesOrderSnapshot.js +21 -0
- package/dist/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
- package/dist/solution/BaseSales/fulfillmentItemRules.js +750 -0
- package/dist/solution/BaseSales/index.d.ts +43 -16
- package/dist/solution/BaseSales/index.js +2028 -1247
- package/dist/solution/BaseSales/pickupTiming.d.ts +47 -0
- package/dist/solution/BaseSales/pickupTiming.js +616 -0
- package/dist/solution/BaseSales/types.d.ts +12 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +10 -3
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -4
- package/dist/solution/BookingByStep/index.d.ts +4 -2
- package/dist/solution/BookingByStep/index.js +8 -3
- package/dist/solution/BookingTicket/index.d.ts +23 -3
- package/dist/solution/BookingTicket/index.js +867 -507
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/dist/solution/BookingTicket/utils/cartView.js +3 -1
- package/dist/solution/OrderInvoicePreview/index.d.ts +66 -0
- package/dist/solution/OrderInvoicePreview/index.js +340 -0
- package/dist/solution/RegisterAndLogin/config.js +54 -11
- package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
- package/dist/solution/RegisterAndLogin/index.js +1 -1
- package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/dist/solution/RegisterAndLogin/utils.js +36 -16
- package/dist/solution/ScanOrder/index.js +65 -31
- package/dist/solution/ScanOrder/types.d.ts +3 -0
- package/dist/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
- package/dist/solution/UnifiedBookingSales/businessAvailability.js +292 -0
- package/dist/solution/UnifiedBookingSales/index.d.ts +36 -4
- package/dist/solution/UnifiedBookingSales/index.js +1715 -906
- package/dist/solution/UnifiedBookingSales/types.d.ts +16 -2
- package/dist/solution/VenueBooking/index.d.ts +15 -2
- package/dist/solution/VenueBooking/index.js +1062 -785
- 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/dist/solution/index.d.ts +1 -0
- package/dist/solution/index.js +2 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +21 -1
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +3 -1
- package/lib/model/strategy/adapter/itemRule/adapter.js +54 -4
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +3 -1
- package/lib/model/strategy/adapter/itemRule/evaluator.js +9 -0
- package/lib/model/strategy/adapter/itemRule/type.d.ts +40 -1
- package/lib/model/strategy/adapter/itemRule/type.js +10 -0
- package/lib/model/strategy/adapter/promotion/evaluator.js +9 -14
- package/lib/model/strategy/adapter/promotion/index.js +46 -1
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +17 -1
- package/lib/modules/BookingContext/utils/productExtend.d.ts +7 -0
- package/lib/modules/BookingContext/utils/productExtend.js +45 -2
- package/lib/modules/BookingContext/utils/resources.js +1 -1
- package/lib/modules/BusinessAvailability/index.d.ts +30 -0
- package/lib/modules/BusinessAvailability/index.js +146 -0
- package/lib/modules/Cart/index.js +4 -2
- package/lib/modules/Cart/types.d.ts +2 -0
- package/lib/modules/Cart/utils/cartProduct.d.ts +3 -1
- package/lib/modules/Cart/utils/cartProduct.js +39 -20
- package/lib/modules/FulfillmentTiming/index.d.ts +11 -0
- package/lib/modules/FulfillmentTiming/index.js +359 -0
- package/lib/modules/FulfillmentTiming/types.d.ts +67 -0
- package/lib/modules/FulfillmentTiming/types.js +5 -0
- package/lib/modules/Holder/utils.d.ts +2 -0
- package/lib/modules/Holder/utils.js +27 -1
- package/lib/modules/OpenData/contactInfo.d.ts +20 -0
- package/lib/modules/OpenData/contactInfo.js +56 -0
- package/lib/modules/OpenData/index.d.ts +1 -0
- package/lib/modules/OpenData/index.js +12 -0
- package/lib/modules/OpenData/types.d.ts +23 -0
- package/lib/modules/Order/holderRelations.d.ts +14 -0
- package/lib/modules/Order/holderRelations.js +98 -0
- package/lib/modules/Order/index.d.ts +16 -4
- package/lib/modules/Order/index.js +284 -50
- package/lib/modules/Order/localEftposState.d.ts +13 -0
- package/lib/modules/Order/localEftposState.js +44 -0
- package/lib/modules/Order/pickupTiming.d.ts +46 -0
- package/lib/modules/Order/pickupTiming.js +61 -0
- package/lib/modules/Order/types.d.ts +49 -3
- package/lib/modules/Order/types.js +1 -0
- package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
- package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
- package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
- package/lib/modules/Order/utils.d.ts +2 -1
- package/lib/modules/Order/utils.js +49 -11
- package/lib/modules/Payment/index.js +3 -4
- package/lib/modules/Product/types.d.ts +11 -0
- package/lib/modules/ProductList/index.js +1 -1
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -0
- package/lib/modules/ResourcePlanner/planner.js +252 -8
- package/lib/modules/ResourcePlanner/types.d.ts +29 -0
- package/lib/modules/Rules/index.js +2 -1
- package/lib/modules/Schedule/index.d.ts +8 -0
- package/lib/modules/Schedule/index.js +68 -12
- package/lib/modules/index.d.ts +1 -0
- package/lib/modules/index.js +11 -0
- package/lib/plugins/request.d.ts +7 -0
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +166 -39
- package/lib/server/modules/form/index.d.ts +19 -0
- package/lib/server/modules/form/index.js +73 -0
- package/lib/server/modules/form/types.d.ts +13 -0
- package/lib/server/modules/form/types.js +9 -0
- package/lib/server/modules/index.d.ts +3 -0
- package/lib/server/modules/index.js +15 -1
- package/lib/server/modules/order/index.d.ts +19 -0
- package/lib/server/modules/order/index.js +160 -7
- package/lib/server/modules/order/types.d.ts +4 -0
- package/lib/server/modules/products/index.d.ts +28 -7
- package/lib/server/modules/products/index.js +226 -63
- package/lib/server/modules/products/replication.d.ts +8 -0
- package/lib/server/modules/products/replication.js +30 -0
- package/lib/server/modules/products/types.d.ts +12 -1
- package/lib/server/modules/products/types.js +3 -0
- package/lib/server/test.json +1 -1
- package/lib/server/utils/small-ticket.d.ts +1 -85
- package/lib/server/utils/small-ticket.js +9 -2305
- package/lib/shared/payment/loadPaymentMethods.d.ts +9 -0
- package/lib/shared/payment/loadPaymentMethods.js +40 -0
- package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
- package/lib/shared/receipt/small-ticket.d.ts +96 -0
- package/lib/shared/receipt/small-ticket.js +2555 -0
- package/lib/shared/receipt/templateVisibility.d.ts +26 -0
- package/lib/shared/receipt/templateVisibility.js +67 -0
- package/lib/shared/sales/loadSalesOrderSnapshot.d.ts +9 -0
- package/lib/shared/sales/loadSalesOrderSnapshot.js +22 -0
- package/lib/solution/BaseSales/fulfillmentItemRules.d.ts +27 -0
- package/lib/solution/BaseSales/fulfillmentItemRules.js +259 -0
- package/lib/solution/BaseSales/index.d.ts +43 -16
- package/lib/solution/BaseSales/index.js +401 -33
- package/lib/solution/BaseSales/pickupTiming.d.ts +47 -0
- package/lib/solution/BaseSales/pickupTiming.js +290 -0
- package/lib/solution/BaseSales/types.d.ts +12 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +7 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -8
- package/lib/solution/BookingByStep/index.d.ts +4 -2
- package/lib/solution/BookingByStep/index.js +7 -3
- package/lib/solution/BookingTicket/index.d.ts +23 -3
- package/lib/solution/BookingTicket/index.js +197 -9
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +0 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -1
- package/lib/solution/BookingTicket/utils/cartView.js +3 -1
- package/lib/solution/OrderInvoicePreview/index.d.ts +66 -0
- package/lib/solution/OrderInvoicePreview/index.js +246 -0
- package/lib/solution/RegisterAndLogin/config.js +57 -11
- package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
- package/lib/solution/RegisterAndLogin/index.js +9 -1
- package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/lib/solution/RegisterAndLogin/utils.js +24 -17
- package/lib/solution/ScanOrder/index.js +59 -20
- package/lib/solution/ScanOrder/types.d.ts +3 -0
- package/lib/solution/UnifiedBookingSales/businessAvailability.d.ts +27 -0
- package/lib/solution/UnifiedBookingSales/businessAvailability.js +138 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +36 -4
- package/lib/solution/UnifiedBookingSales/index.js +562 -75
- package/lib/solution/UnifiedBookingSales/types.d.ts +16 -2
- package/lib/solution/VenueBooking/index.d.ts +15 -2
- package/lib/solution/VenueBooking/index.js +243 -96
- 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/lib/solution/index.d.ts +1 -0
- package/lib/solution/index.js +11 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StrategyConfig } from '../../type';
|
|
2
|
-
import { type ItemRuleBusinessData, type ItemRuleEvaluationResult, type QuantityCheckResult, type QuantityLimitResult, type PrefillItemResult, type AllergyRequirement } from './type';
|
|
2
|
+
import { type ItemRuleBusinessData, type ItemRuleEvaluationResult, type QuantityCheckResult, type QuantityLimitResult, type PrefillItemResult, type ItemRuleCartAction, type AllergyRequirement } from './type';
|
|
3
3
|
/**
|
|
4
4
|
* ItemRule 评估器
|
|
5
5
|
*
|
|
@@ -47,4 +47,6 @@ export declare class ItemRuleEvaluator {
|
|
|
47
47
|
* 便捷方法:仅获取需要预填到购物车的商品
|
|
48
48
|
*/
|
|
49
49
|
getPrefillItems(businessData: ItemRuleBusinessData): PrefillItemResult[];
|
|
50
|
+
/** 获取按优先级排序的购物车动作,交由销售业务层执行。 */
|
|
51
|
+
getCartActions(businessData: ItemRuleBusinessData): ItemRuleCartAction[];
|
|
50
52
|
}
|
|
@@ -43,6 +43,7 @@ class ItemRuleEvaluator {
|
|
|
43
43
|
const configs = businessData.strategyConfigs ? businessData.strategyConfigs.filter(c => c.metadata.type === 'item_rule') : this.strategyConfigs;
|
|
44
44
|
const allQuantityChecks = [];
|
|
45
45
|
const allPrefillItems = [];
|
|
46
|
+
const allCartActions = [];
|
|
46
47
|
const allAllergyRequirements = [];
|
|
47
48
|
const rawResults = [];
|
|
48
49
|
const context = this.adapter.prepareContext(businessData);
|
|
@@ -52,16 +53,19 @@ class ItemRuleEvaluator {
|
|
|
52
53
|
const {
|
|
53
54
|
quantityChecks,
|
|
54
55
|
prefillItems,
|
|
56
|
+
cartActions,
|
|
55
57
|
allergyRequirements
|
|
56
58
|
} = this.adapter.transformResult(evalResult, businessData);
|
|
57
59
|
allQuantityChecks.push(...quantityChecks);
|
|
58
60
|
allPrefillItems.push(...prefillItems);
|
|
61
|
+
allCartActions.push(...cartActions);
|
|
59
62
|
allAllergyRequirements.push(...allergyRequirements);
|
|
60
63
|
}
|
|
61
64
|
const failedChecks = allQuantityChecks.filter(c => !c.passed);
|
|
62
65
|
return {
|
|
63
66
|
quantityChecks: allQuantityChecks,
|
|
64
67
|
prefillItems: allPrefillItems,
|
|
68
|
+
cartActions: allCartActions.sort((left, right) => right.priority - left.priority),
|
|
65
69
|
allergyRequirements: allAllergyRequirements,
|
|
66
70
|
allPassed: failedChecks.length === 0,
|
|
67
71
|
failedChecks,
|
|
@@ -130,5 +134,10 @@ class ItemRuleEvaluator {
|
|
|
130
134
|
const result = this.evaluate(businessData);
|
|
131
135
|
return result.prefillItems;
|
|
132
136
|
}
|
|
137
|
+
|
|
138
|
+
/** 获取按优先级排序的购物车动作,交由销售业务层执行。 */
|
|
139
|
+
getCartActions(businessData) {
|
|
140
|
+
return this.evaluate(businessData).cartActions;
|
|
141
|
+
}
|
|
133
142
|
}
|
|
134
143
|
exports.ItemRuleEvaluator = ItemRuleEvaluator;
|
|
@@ -4,6 +4,8 @@ export declare const ITEM_RULE_ACTION_TYPES: {
|
|
|
4
4
|
readonly QUANTITY_CHECK: "QUANTITY_CHECK";
|
|
5
5
|
/** 预填购物车 */
|
|
6
6
|
readonly PREFILL_CART: "PREFILL_CART";
|
|
7
|
+
/** 按商品 ID 删除购物车商品 */
|
|
8
|
+
readonly DELETE_PRODUCT: "DELETE_PRODUCT";
|
|
7
9
|
/** 加购前过敏信息确认 */
|
|
8
10
|
readonly ALLERGY_CHECK: "ALLERGY_CHECK";
|
|
9
11
|
};
|
|
@@ -101,6 +103,10 @@ export interface AllergyRequirement {
|
|
|
101
103
|
promptScope: AllergyPromptScope;
|
|
102
104
|
allowNote: boolean;
|
|
103
105
|
}
|
|
106
|
+
/** 购物车动作触发时机,与数量校验的提交范围相互独立。 */
|
|
107
|
+
export type ItemRuleExecutionScope = 'set_handover';
|
|
108
|
+
/** 预填数量来源;主商品数量不扩展到 QUANTITY_CHECK。 */
|
|
109
|
+
export type PrefillQuantitySource = QuantitySource | 'main_product_number';
|
|
104
110
|
/**
|
|
105
111
|
* 预填商品项
|
|
106
112
|
*/
|
|
@@ -110,9 +116,11 @@ export interface PrefillProduct {
|
|
|
110
116
|
/** 变体 ID(可选,0 或不填表示无变体/默认) */
|
|
111
117
|
product_variant_id?: number;
|
|
112
118
|
/** 数量来源 */
|
|
113
|
-
quantityFrom:
|
|
119
|
+
quantityFrom: PrefillQuantitySource;
|
|
114
120
|
/** 固定数量(当 quantityFrom 为 'fixed' 时使用) */
|
|
115
121
|
quantity?: number;
|
|
122
|
+
/** 设置取餐方式时同步自动附加行的数量,重复执行不累加。 */
|
|
123
|
+
repeat_skip?: boolean;
|
|
116
124
|
}
|
|
117
125
|
/**
|
|
118
126
|
* Prefill Cart Action 的详细配置
|
|
@@ -120,9 +128,18 @@ export interface PrefillProduct {
|
|
|
120
128
|
* 存储在 ActionEffect.config 中
|
|
121
129
|
*/
|
|
122
130
|
export interface PrefillCartConfig {
|
|
131
|
+
/** 不填时保持原有预填时机;set_handover 仅在设置取餐方式时执行。 */
|
|
132
|
+
scope?: ItemRuleExecutionScope;
|
|
123
133
|
/** 要预填的商品列表 */
|
|
124
134
|
products: PrefillProduct[];
|
|
125
135
|
}
|
|
136
|
+
/** 删除指定商品的全部购物车行,不区分变体或是否为自动添加。 */
|
|
137
|
+
export interface DeleteProductConfig {
|
|
138
|
+
scope: ItemRuleExecutionScope;
|
|
139
|
+
products: Array<{
|
|
140
|
+
product_id: number;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
126
143
|
/**
|
|
127
144
|
* 购物车商品摘要
|
|
128
145
|
*/
|
|
@@ -157,6 +174,10 @@ export interface ItemRuleBusinessData {
|
|
|
157
174
|
historicalItems?: CartItemSummary[];
|
|
158
175
|
/** 服务类型(如 'dine-in', 'takeaway') */
|
|
159
176
|
serviceType?: string;
|
|
177
|
+
/** 当前购物车动作触发时机;未填时不评估 set_handover 动作。 */
|
|
178
|
+
executionScope?: ItemRuleExecutionScope;
|
|
179
|
+
/** 由销售业务层提供的主商品总数量,不包含附加费用商品。 */
|
|
180
|
+
mainProductNumber?: number;
|
|
160
181
|
/** 当前业务渠道(如 'kiosk', 'pos', 'online_store') */
|
|
161
182
|
channel?: string;
|
|
162
183
|
/** 当前正在加入购物车的商品;ALLERGY_CHECK 只读取该商品的 Target。 */
|
|
@@ -209,7 +230,23 @@ export interface PrefillItemResult {
|
|
|
209
230
|
product_variant_id?: number;
|
|
210
231
|
/** 应添加的数量 */
|
|
211
232
|
quantity: number;
|
|
233
|
+
/** true 时执行器同步自动附加行数量;quantity=0 表示移除该自动行。 */
|
|
234
|
+
repeat_skip?: boolean;
|
|
212
235
|
}
|
|
236
|
+
/** 保留不同购物车动作之间的优先级与执行顺序。 */
|
|
237
|
+
export type ItemRuleCartAction = {
|
|
238
|
+
strategyId: string;
|
|
239
|
+
ruleId: string;
|
|
240
|
+
priority: number;
|
|
241
|
+
} & ({
|
|
242
|
+
type: 'PREFILL_CART';
|
|
243
|
+
products: PrefillItemResult[];
|
|
244
|
+
} | {
|
|
245
|
+
type: 'DELETE_PRODUCT';
|
|
246
|
+
products: Array<{
|
|
247
|
+
product_id: number;
|
|
248
|
+
}>;
|
|
249
|
+
});
|
|
213
250
|
/**
|
|
214
251
|
* 单条规则的数量限制(不含校验结果,仅描述配置中的约束)
|
|
215
252
|
*/
|
|
@@ -258,6 +295,8 @@ export interface ItemRuleEvaluationResult {
|
|
|
258
295
|
quantityChecks: QuantityCheckResult[];
|
|
259
296
|
/** 需要预填的商品列表 */
|
|
260
297
|
prefillItems: PrefillItemResult[];
|
|
298
|
+
/** 按 priority 降序排列的购物车动作;同优先级保持策略与动作顺序。 */
|
|
299
|
+
cartActions: ItemRuleCartAction[];
|
|
261
300
|
/** 当前商品命中的过敏门禁要求;不同 Action/商品不会合并。 */
|
|
262
301
|
allergyRequirements: AllergyRequirement[];
|
|
263
302
|
/** 所有校验是否全部通过 */
|
|
@@ -13,6 +13,8 @@ const ITEM_RULE_ACTION_TYPES = exports.ITEM_RULE_ACTION_TYPES = {
|
|
|
13
13
|
QUANTITY_CHECK: 'QUANTITY_CHECK',
|
|
14
14
|
/** 预填购物车 */
|
|
15
15
|
PREFILL_CART: 'PREFILL_CART',
|
|
16
|
+
/** 按商品 ID 删除购物车商品 */
|
|
17
|
+
DELETE_PRODUCT: 'DELETE_PRODUCT',
|
|
16
18
|
/** 加购前过敏信息确认 */
|
|
17
19
|
ALLERGY_CHECK: 'ALLERGY_CHECK'
|
|
18
20
|
};
|
|
@@ -78,6 +80,10 @@ const ITEM_RULE_TEMPLATES = exports.ITEM_RULE_TEMPLATES = {
|
|
|
78
80
|
// Prefill Cart 相关类型
|
|
79
81
|
// ============================================
|
|
80
82
|
|
|
83
|
+
/** 购物车动作触发时机,与数量校验的提交范围相互独立。 */
|
|
84
|
+
|
|
85
|
+
/** 预填数量来源;主商品数量不扩展到 QUANTITY_CHECK。 */
|
|
86
|
+
|
|
81
87
|
/**
|
|
82
88
|
* 预填商品项
|
|
83
89
|
*/
|
|
@@ -88,6 +94,8 @@ const ITEM_RULE_TEMPLATES = exports.ITEM_RULE_TEMPLATES = {
|
|
|
88
94
|
* 存储在 ActionEffect.config 中
|
|
89
95
|
*/
|
|
90
96
|
|
|
97
|
+
/** 删除指定商品的全部购物车行,不区分变体或是否为自动添加。 */
|
|
98
|
+
|
|
91
99
|
// ============================================
|
|
92
100
|
// 业务数据类型(Evaluator 输入)
|
|
93
101
|
// ============================================
|
|
@@ -116,6 +124,8 @@ const ITEM_RULE_TEMPLATES = exports.ITEM_RULE_TEMPLATES = {
|
|
|
116
124
|
* 单个 Prefill Cart 的预填结果
|
|
117
125
|
*/
|
|
118
126
|
|
|
127
|
+
/** 保留不同购物车动作之间的优先级与执行顺序。 */
|
|
128
|
+
|
|
119
129
|
/**
|
|
120
130
|
* 单条规则的数量限制(不含校验结果,仅描述配置中的约束)
|
|
121
131
|
*/
|
|
@@ -227,10 +227,13 @@ class PromotionEvaluator {
|
|
|
227
227
|
const productResults = this.evaluateProducts(input);
|
|
228
228
|
|
|
229
229
|
// 2. 收集所有适用的促销(去重)
|
|
230
|
+
// strategyId is metadata and is not guaranteed to be unique. Use the actual
|
|
231
|
+
// configuration as the key so one strategy cannot overwrite another strategy's
|
|
232
|
+
// label/action when their IDs happen to be the same.
|
|
230
233
|
const promotionMap = new Map();
|
|
231
234
|
for (const productResult of productResults) {
|
|
232
235
|
for (const promo of productResult.applicablePromotions) {
|
|
233
|
-
const existing = promotionMap.get(promo.
|
|
236
|
+
const existing = promotionMap.get(promo.strategyConfig);
|
|
234
237
|
const product = productResult.product;
|
|
235
238
|
|
|
236
239
|
// 计算促销数量:如果是 bundle 子商品匹配,数量 = 主商品数量 × 子商品数量
|
|
@@ -253,7 +256,7 @@ class PromotionEvaluator {
|
|
|
253
256
|
// 新促销
|
|
254
257
|
const productMatchedBundleIndexMap = new Map();
|
|
255
258
|
productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
|
|
256
|
-
promotionMap.set(promo.
|
|
259
|
+
promotionMap.set(promo.strategyConfig, {
|
|
257
260
|
...promo,
|
|
258
261
|
applicableProducts: [product],
|
|
259
262
|
totalQuantity: promoQuantity,
|
|
@@ -305,7 +308,7 @@ class PromotionEvaluator {
|
|
|
305
308
|
const cartResult = this.evaluateCart(input);
|
|
306
309
|
|
|
307
310
|
// 2. 按 action priority 排序促销组(从 strategyConfig 获取 priority)
|
|
308
|
-
const sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups
|
|
311
|
+
const sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups);
|
|
309
312
|
|
|
310
313
|
// 3. 追踪已处理的商品(避免重复处理)
|
|
311
314
|
// key: `${product_id}_${product_variant_id}`
|
|
@@ -544,19 +547,11 @@ class PromotionEvaluator {
|
|
|
544
547
|
* 按优先级排序促销组
|
|
545
548
|
* 优先级从 strategyConfig.actions[0].priority 获取,数值越小优先级越高
|
|
546
549
|
*/
|
|
547
|
-
sortPromotionGroupsByPriority(groups
|
|
548
|
-
// 创建策略ID到优先级的映射
|
|
549
|
-
const priorityMap = new Map();
|
|
550
|
-
for (const promo of promotions) {
|
|
551
|
-
const actions = promo.strategyConfig?.actions || [];
|
|
552
|
-
const priority = actions[0]?.priority ?? 999;
|
|
553
|
-
priorityMap.set(promo.strategyId, priority);
|
|
554
|
-
}
|
|
555
|
-
|
|
550
|
+
sortPromotionGroupsByPriority(groups) {
|
|
556
551
|
// 按优先级排序(数值越小优先级越高)
|
|
557
552
|
return [...groups].sort((a, b) => {
|
|
558
|
-
const priorityA =
|
|
559
|
-
const priorityB =
|
|
553
|
+
const priorityA = a.strategyConfig?.actions?.[0]?.priority ?? 999;
|
|
554
|
+
const priorityB = b.strategyConfig?.actions?.[0]?.priority ?? 999;
|
|
560
555
|
return priorityA - priorityB;
|
|
561
556
|
});
|
|
562
557
|
}
|
|
@@ -1 +1,46 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _examples.BUY_X_GET_Y_FREE_STRATEGY;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _examples.ITEM_REWARD_STRATEGY;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "PromotionAdapter", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _adapter.PromotionAdapter;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "PromotionEvaluator", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _evaluator.PromotionEvaluator;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, "default", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _adapter.default;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var _evaluator = require("./evaluator");
|
|
43
|
+
var _adapter = _interopRequireWildcard(require("./adapter"));
|
|
44
|
+
var _examples = require("./examples");
|
|
45
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
46
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { OrderProduct } from '../../Order/types';
|
|
1
|
+
import type { OrderHolderRelation, OrderProduct } from '../../Order/types';
|
|
2
2
|
export interface BuildNormalProductCacheItemFromOrderLineInput {
|
|
3
3
|
/** tempOrder.products 中的一行(普通商品,无关联 booking) */
|
|
4
4
|
product: OrderProduct;
|
|
5
5
|
/** 商品 catalog / 详情,用于 variant / option / bundle 模板字段 */
|
|
6
6
|
sourceProduct?: Record<string, any> | null;
|
|
7
|
+
/** 整单关系集合,按商品行 UID 回填。 */
|
|
8
|
+
holder_relations?: OrderHolderRelation[];
|
|
7
9
|
}
|
|
8
10
|
export declare function buildNormalProductCacheItemFromOrderLine(input: BuildNormalProductCacheItemFromOrderLineInput): Record<string, any>;
|
|
@@ -4,13 +4,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.buildNormalProductCacheItemFromOrderLine = buildNormalProductCacheItemFromOrderLine;
|
|
7
|
+
var _lodashEs = require("lodash-es");
|
|
8
|
+
var _holderRelations = require("../../Order/holderRelations");
|
|
7
9
|
var _utils = require("../../Order/utils");
|
|
8
10
|
var _entitlementPass = require("../../Discount/entitlementPass");
|
|
11
|
+
var _utils2 = require("../../Holder/utils");
|
|
9
12
|
var _orderLineDisplay = require("./orderLineDisplay");
|
|
10
13
|
/**
|
|
11
14
|
* 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
|
|
12
15
|
*
|
|
13
|
-
* 与 `buildCacheItemFromOrderLine`(预约行 + booking
|
|
16
|
+
* 与 `buildCacheItemFromOrderLine`(预约行 + booking)并列;Holder 从订单关系回填,不生成预约字段。
|
|
14
17
|
*/
|
|
15
18
|
/**
|
|
16
19
|
* 解析 v2 主商品 source 价(不含 option、不含 bundle),供 SkuDetailModal / transform 使用。
|
|
@@ -84,6 +87,15 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
84
87
|
const original = Number(product.original_price ?? selling);
|
|
85
88
|
const uid = product.metadata?.unique_identification_number;
|
|
86
89
|
const originExtend = product.metadata?.origin?._extend;
|
|
90
|
+
const holderRelations = (0, _lodashEs.cloneDeep)((0, _holderRelations.getProductHolderRelations)(input, product));
|
|
91
|
+
const formRelations = holderRelations.filter(relation => relation.holder_type === 'form');
|
|
92
|
+
const holderIds = formRelations.map(relation => relation.holder_id);
|
|
93
|
+
const holderNames = formRelations.map(relation => relation.metadata?.name).filter(name => typeof name === 'string' && name.length > 0);
|
|
94
|
+
const holder = formRelations.length > 0 ? {
|
|
95
|
+
...(formRelations[0].metadata || {}),
|
|
96
|
+
form_record: holderIds,
|
|
97
|
+
name: holderNames.join(',')
|
|
98
|
+
} : null;
|
|
87
99
|
const shouldPreserveEntitlementPriceOverride = hasEntitlementPassDiscount(product) && (originExtend?.priceOverride !== undefined || product.metadata?.price_override != null);
|
|
88
100
|
const hasPriceOverride = originExtend?.priceOverride !== undefined || shouldPreserveEntitlementPriceOverride;
|
|
89
101
|
const option = (0, _utils.getProductSkuOptions)(product);
|
|
@@ -115,6 +127,9 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
115
127
|
skuValue: JSON.parse(JSON.stringify(skuValue)),
|
|
116
128
|
_skuValue: JSON.parse(JSON.stringify(skuValue)),
|
|
117
129
|
isNormalProduct: true,
|
|
130
|
+
holder_relations: holderRelations,
|
|
131
|
+
holder,
|
|
132
|
+
holder_id: holderIds,
|
|
118
133
|
metadata: {
|
|
119
134
|
product_add_schedule_time: product.metadata?.product_add_schedule_time
|
|
120
135
|
},
|
|
@@ -134,6 +149,7 @@ function buildNormalProductCacheItemFromOrderLine(input) {
|
|
|
134
149
|
}
|
|
135
150
|
return {
|
|
136
151
|
...(sourceProduct || {}),
|
|
152
|
+
holder_config: (0, _utils2.resolveProductHolderConfig)(sourceProduct) ?? (0, _utils2.resolveProductHolderConfig)(product) ?? undefined,
|
|
137
153
|
id: resolvedId,
|
|
138
154
|
product_id: resolvedId,
|
|
139
155
|
_id: uid,
|
|
@@ -7,6 +7,13 @@ import { TimeSlicesResult, TimeSlice, DurationSlice } from './timeSlices';
|
|
|
7
7
|
* 让 OS 这层完全可在 Node 单测下运行(dayjs 在 Node 也是纯函数)。
|
|
8
8
|
*/
|
|
9
9
|
export interface BookingCalcContext {
|
|
10
|
+
/** Sales channel; remembered duration starts are accepted only by POS. */
|
|
11
|
+
channel?: string;
|
|
12
|
+
/** Start-only device preference for this decision; never reuse a cached end/resource. */
|
|
13
|
+
rememberedTime?: {
|
|
14
|
+
businessDate: string;
|
|
15
|
+
startAt: string;
|
|
16
|
+
};
|
|
10
17
|
/** 来自 `/core/board/management/config` 的整套配置;常用路径见 sliceTimeSlices / appointmentTimingPreference。 */
|
|
11
18
|
bookingConfig?: any;
|
|
12
19
|
/** 资源 origin map(id → resource origin)。 */
|
|
@@ -28,6 +28,44 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
28
28
|
* 让 OS 这层完全可在 Node 单测下运行(dayjs 在 Node 也是纯函数)。
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
/** Rebuild only the derived booking fields; SKU, quantity, notes and Holder stay intact. */
|
|
32
|
+
function seedRememberedBookingStart(cacheItem, ctx) {
|
|
33
|
+
const remembered = ctx.rememberedTime;
|
|
34
|
+
const invalid = () => Object.assign(new Error('Remembered time requires a valid POS duration start.'), {
|
|
35
|
+
code: 'INVALID_REMEMBERED_TIME'
|
|
36
|
+
});
|
|
37
|
+
const duration = cacheItem?.duration;
|
|
38
|
+
const durationType = typeof duration === 'object' && duration ? String(duration.type || '').toLowerCase() : '';
|
|
39
|
+
const durationValue = typeof duration === 'object' && duration ? duration.value : duration;
|
|
40
|
+
const isDuration = (0, _flexible.isProductFlexibleDuration)(duration) || ['', 'fixed', 'minute', 'minutes', 'hour', 'hours'].includes(durationType) && Number.isFinite(Number(durationValue)) && Number(durationValue) > 0;
|
|
41
|
+
const isCalendarDate = value => {
|
|
42
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
43
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
44
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
45
|
+
};
|
|
46
|
+
const parts = typeof remembered?.startAt === 'string' ? /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(?:Z|[+-](\d{2}):(\d{2}))?$/.exec(remembered.startAt) : null;
|
|
47
|
+
if (String(ctx.channel || '').trim().toLowerCase() !== 'pos' || !isDuration || (0, _serviceTimes.isMultiDayProduct)(cacheItem) || (0, _serviceTimes.isSessionProduct)(cacheItem) || cacheItem?.cartDetailValue?.session || cacheItem?._extend?.other?.session || !isCalendarDate(remembered?.businessDate) || !parts || !isCalendarDate(parts[1]) || Number(parts[2]) > 23 || Number(parts[3]) > 59 || Number(parts[4] || 0) > 59 || Number(parts[5] || 0) > 23 || Number(parts[6] || 0) > 59) throw invalid();
|
|
48
|
+
// Parse the actual instant with the same runtime clock used by the legacy booking helpers.
|
|
49
|
+
// The business-day label must not replace the calendar date of an overnight start.
|
|
50
|
+
const start = (0, _dayjs.default)(remembered.startAt);
|
|
51
|
+
if (!start.isValid()) throw invalid();
|
|
52
|
+
const extend = {
|
|
53
|
+
...(cacheItem?._extend || {})
|
|
54
|
+
};
|
|
55
|
+
for (const key of ['start_date', 'startDate', 'start_time', 'end_date', 'endDate', 'end_time', 'duration', 'resource', 'resources', 'capacity', 'booking', 'availabilitySelection']) delete extend[key];
|
|
56
|
+
return {
|
|
57
|
+
...cacheItem,
|
|
58
|
+
new: 1,
|
|
59
|
+
autoClose: false,
|
|
60
|
+
_data: {},
|
|
61
|
+
_extend: {
|
|
62
|
+
...extend,
|
|
63
|
+
start_date: start.format('YYYY-MM-DD'),
|
|
64
|
+
start_time: start.format(start.millisecond() ? 'HH:mm:ss.SSS' : start.second() ? 'HH:mm:ss' : 'HH:mm')
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/** 从 BookingContext.date 提取 HH:mm(仅当日期带有效时分时)。 */
|
|
32
70
|
function derivePresetStartTimeFromDate(date) {
|
|
33
71
|
if (date == null) return undefined;
|
|
@@ -195,10 +233,15 @@ function getServiceStartTimeAndEndTime(params) {
|
|
|
195
233
|
*/
|
|
196
234
|
function getProductExtend(params) {
|
|
197
235
|
const {
|
|
198
|
-
cacheItem,
|
|
199
|
-
ctx,
|
|
200
236
|
isDisabledTime
|
|
201
237
|
} = params;
|
|
238
|
+
const hasRememberedTime = params.ctx.rememberedTime !== undefined;
|
|
239
|
+
const cacheItem = hasRememberedTime ? seedRememberedBookingStart(params.cacheItem, params.ctx) : params.cacheItem;
|
|
240
|
+
const ctx = hasRememberedTime ? {
|
|
241
|
+
...params.ctx,
|
|
242
|
+
previousService: undefined,
|
|
243
|
+
presetStartTime: undefined
|
|
244
|
+
} : params.ctx;
|
|
202
245
|
const isNew = cacheItem.new || !cacheItem._extend?.endDate;
|
|
203
246
|
const _cacheItem = {
|
|
204
247
|
...cacheItem
|
|
@@ -241,7 +241,7 @@ function getResourceMaxFreeTime(resource, isAutoAllocationOn) {
|
|
|
241
241
|
if (!isAutoAllocationOn(form_id)) return [];
|
|
242
242
|
const usableList = (renderList || []).filter(d => d.usable && d.id !== 0);
|
|
243
243
|
let defaultResource = [];
|
|
244
|
-
let maxFreeTime =
|
|
244
|
+
let maxFreeTime = -1;
|
|
245
245
|
usableList.forEach(d => {
|
|
246
246
|
const {
|
|
247
247
|
times = []
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MultiLangObject } from '../OpenData/types';
|
|
2
|
+
import type { ScheduleItem } from '../Schedule/types';
|
|
3
|
+
export type BusinessClosedBehavior = 'show_menu_disabled' | 'hide_all';
|
|
4
|
+
export interface BusinessAvailability {
|
|
5
|
+
status: 'open' | 'paused' | 'closed' | 'error';
|
|
6
|
+
behavior: BusinessClosedBehavior | null;
|
|
7
|
+
message: string | MultiLangObject | null;
|
|
8
|
+
/** Epoch milliseconds. Re-evaluate the cached configuration at this time. */
|
|
9
|
+
nextChangeAt: number;
|
|
10
|
+
requirePaymentOnSubmit: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface BusinessAvailabilityConfig {
|
|
14
|
+
paused: boolean;
|
|
15
|
+
pauseBehavior: BusinessClosedBehavior;
|
|
16
|
+
closedBehavior: BusinessClosedBehavior;
|
|
17
|
+
pauseMessage: BusinessAvailability['message'];
|
|
18
|
+
closedMessage: BusinessAvailability['message'];
|
|
19
|
+
scheduleIds: number[];
|
|
20
|
+
invalidScheduleIds: boolean;
|
|
21
|
+
requirePaymentOnSubmit: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function normalizeBusinessAvailabilityConfig(data: Record<string, unknown>): BusinessAvailabilityConfig;
|
|
24
|
+
/** Business ordering is independent of reservation switches and Pickup slot availability. */
|
|
25
|
+
export declare function computeBusinessAvailability(input: {
|
|
26
|
+
config: BusinessAvailabilityConfig;
|
|
27
|
+
schedules: ScheduleItem[];
|
|
28
|
+
timezone: string;
|
|
29
|
+
now: Date;
|
|
30
|
+
}): BusinessAvailability;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.computeBusinessAvailability = computeBusinessAvailability;
|
|
7
|
+
exports.normalizeBusinessAvailabilityConfig = normalizeBusinessAvailabilityConfig;
|
|
8
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
9
|
+
var _isSameOrAfter = _interopRequireDefault(require("dayjs/plugin/isSameOrAfter"));
|
|
10
|
+
var _isSameOrBefore = _interopRequireDefault(require("dayjs/plugin/isSameOrBefore"));
|
|
11
|
+
var _getDateIsInSchedule = require("../Schedule/getDateIsInSchedule");
|
|
12
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
_dayjs.default.extend(_isSameOrAfter.default);
|
|
14
|
+
_dayjs.default.extend(_isSameOrBefore.default);
|
|
15
|
+
const DAY = 86_400_000;
|
|
16
|
+
const behavior = value => value === 'show_menu_disabled' ? value : 'hide_all';
|
|
17
|
+
const message = value => typeof value === 'string' || value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
18
|
+
function normalizeBusinessAvailabilityConfig(data) {
|
|
19
|
+
const rawIds = data['availability.operating_hours'];
|
|
20
|
+
const ids = Array.isArray(rawIds) ? rawIds.map(id => typeof id === 'number' || typeof id === 'string' ? Number(id) : NaN) : [];
|
|
21
|
+
const validIds = ids.filter(id => Number.isInteger(id) && id > 0);
|
|
22
|
+
const paused = data['availability.paused'];
|
|
23
|
+
return {
|
|
24
|
+
paused: paused === true || paused === 1 || paused === 'true' || paused === '1',
|
|
25
|
+
pauseBehavior: behavior(data['availability.pause_behavior']),
|
|
26
|
+
closedBehavior: behavior(data['availability.closed_behavior']),
|
|
27
|
+
pauseMessage: message(data['availability.pause_message']),
|
|
28
|
+
closedMessage: message(data['availability.closed_message']),
|
|
29
|
+
scheduleIds: [...new Set(validIds)],
|
|
30
|
+
invalidScheduleIds: rawIds != null && (!Array.isArray(rawIds) || validIds.length !== ids.length),
|
|
31
|
+
// Only an explicitly disabled setting changes the existing payment flow.
|
|
32
|
+
requirePaymentOnSubmit: data['sale.require_payment_on_submit'] !== false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createClock(timezone) {
|
|
36
|
+
if (!timezone) throw new Error('Store timezone is unavailable.');
|
|
37
|
+
const formatter = new Intl.DateTimeFormat('en-GB', {
|
|
38
|
+
timeZone: timezone,
|
|
39
|
+
year: 'numeric',
|
|
40
|
+
month: '2-digit',
|
|
41
|
+
day: '2-digit',
|
|
42
|
+
hour: '2-digit',
|
|
43
|
+
minute: '2-digit',
|
|
44
|
+
second: '2-digit',
|
|
45
|
+
hourCycle: 'h23'
|
|
46
|
+
});
|
|
47
|
+
const local = timestamp => {
|
|
48
|
+
const parts = {};
|
|
49
|
+
formatter.formatToParts(timestamp).forEach(part => {
|
|
50
|
+
parts[part.type] = part.value;
|
|
51
|
+
});
|
|
52
|
+
const date = `${parts.year}-${parts.month}-${parts.day}`;
|
|
53
|
+
return {
|
|
54
|
+
date,
|
|
55
|
+
scalar: Date.parse(`${date}T${parts.hour}:${parts.minute}:${parts.second}Z`)
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const offsets = new Set();
|
|
59
|
+
const instants = scalar => {
|
|
60
|
+
offsets.clear();
|
|
61
|
+
for (let hours = -36; hours <= 36; hours += 6) {
|
|
62
|
+
const sample = scalar + hours * 3_600_000;
|
|
63
|
+
offsets.add(local(sample).scalar - sample);
|
|
64
|
+
}
|
|
65
|
+
return [...offsets].map(offset => scalar - offset).filter(instant => local(instant).scalar === scalar).sort((a, b) => a - b);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
local,
|
|
69
|
+
instants
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Business ordering is independent of reservation switches and Pickup slot availability. */
|
|
74
|
+
function computeBusinessAvailability(input) {
|
|
75
|
+
const {
|
|
76
|
+
config,
|
|
77
|
+
schedules,
|
|
78
|
+
timezone,
|
|
79
|
+
now
|
|
80
|
+
} = input;
|
|
81
|
+
const nowAt = now.getTime();
|
|
82
|
+
const base = {
|
|
83
|
+
requirePaymentOnSubmit: config.requirePaymentOnSubmit,
|
|
84
|
+
nextChangeAt: nowAt + DAY
|
|
85
|
+
};
|
|
86
|
+
if (config.paused) {
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
status: 'paused',
|
|
90
|
+
behavior: config.pauseBehavior,
|
|
91
|
+
message: config.pauseMessage
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (config.invalidScheduleIds) throw new Error('Business operating-hours configuration is invalid.');
|
|
95
|
+
if (!config.scheduleIds.length) return {
|
|
96
|
+
...base,
|
|
97
|
+
status: 'open',
|
|
98
|
+
behavior: null,
|
|
99
|
+
message: null
|
|
100
|
+
};
|
|
101
|
+
const selected = schedules.filter(schedule => config.scheduleIds.includes(Number(schedule.id)));
|
|
102
|
+
if (config.scheduleIds.some(id => !selected.some(schedule => Number(schedule.id) === id))) {
|
|
103
|
+
throw new Error('A configured business operating-hours schedule is unavailable.');
|
|
104
|
+
}
|
|
105
|
+
const clock = createClock(timezone);
|
|
106
|
+
const current = clock.local(nowAt);
|
|
107
|
+
const today = Date.parse(`${current.date}T00:00:00Z`);
|
|
108
|
+
const nextMidnight = clock.instants(today + DAY).find(instant => instant > nowAt);
|
|
109
|
+
let nextChangeAt = nextMidnight ?? nowAt + 60_000;
|
|
110
|
+
let open = false;
|
|
111
|
+
// Yesterday contributes the tail of overnight windows; today supplies the next boundary.
|
|
112
|
+
for (const dateAt of [today - DAY, today]) {
|
|
113
|
+
const date = new Date(dateAt).toISOString().slice(0, 10);
|
|
114
|
+
// Schedule's resolver merges by time-of-day, so resolve each schedule independently.
|
|
115
|
+
for (const schedule of selected) {
|
|
116
|
+
for (const window of (0, _getDateIsInSchedule.getScheduleStartEndTimePoints)(date, [schedule])) {
|
|
117
|
+
const toMinutes = value => {
|
|
118
|
+
if (!/^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value) && value !== '24:00') {
|
|
119
|
+
throw new Error('Business operating-hours schedule contains an invalid time.');
|
|
120
|
+
}
|
|
121
|
+
const [hours, minutes] = value.split(':').map(Number);
|
|
122
|
+
return hours * 60 + minutes;
|
|
123
|
+
};
|
|
124
|
+
const start = dateAt + toMinutes(window.start_time) * 60_000;
|
|
125
|
+
let end = dateAt + toMinutes(window.end_time) * 60_000;
|
|
126
|
+
if (end <= start) end += DAY;
|
|
127
|
+
if (current.scalar >= start && current.scalar < end) open = true;
|
|
128
|
+
for (const boundary of [start, end]) {
|
|
129
|
+
for (const instant of clock.instants(boundary)) {
|
|
130
|
+
if (instant > nowAt) nextChangeAt = Math.min(nextChangeAt, instant);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// A timezone offset change can repeat/skip a wall-clock boundary. A local-only safety
|
|
137
|
+
// check also covers throttled timers; it does not fetch either configuration source.
|
|
138
|
+
nextChangeAt = Math.min(nextChangeAt, nowAt + 60_000);
|
|
139
|
+
return {
|
|
140
|
+
...base,
|
|
141
|
+
nextChangeAt,
|
|
142
|
+
status: open ? 'open' : 'closed',
|
|
143
|
+
behavior: open ? null : config.closedBehavior,
|
|
144
|
+
message: open ? null : config.closedMessage
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -89,7 +89,8 @@ class CartModule extends _BaseModule.BaseModule {
|
|
|
89
89
|
bundle,
|
|
90
90
|
options,
|
|
91
91
|
date,
|
|
92
|
-
quantity = 1
|
|
92
|
+
quantity = 1,
|
|
93
|
+
isLegacyOrder
|
|
93
94
|
} = params;
|
|
94
95
|
// 生成一个唯一的 ID
|
|
95
96
|
let cartItem = {
|
|
@@ -108,7 +109,8 @@ class CartModule extends _BaseModule.BaseModule {
|
|
|
108
109
|
bundle,
|
|
109
110
|
options,
|
|
110
111
|
product_variant_id: newProduct.product_variant_id,
|
|
111
|
-
quantity
|
|
112
|
+
quantity,
|
|
113
|
+
isLegacyOrder
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
if (account) {
|