@pisell/pisellos 2.3.74 → 2.3.75
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/core/index.js +6 -10
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/strategy-example.js +5 -19
- package/dist/modules/Cart/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +0 -1
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.js +2 -4
- package/dist/modules/OpenData/index.js +0 -2
- package/dist/modules/Order/index.js +36 -64
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/Rules/index.js +1 -4
- package/dist/modules/Schedule/index.js +0 -1
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.js +97 -129
- package/dist/server/modules/menu/index.js +32 -41
- package/dist/server/modules/order/index.js +2 -4
- package/dist/server/modules/products/index.js +24 -42
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +3 -4
- package/dist/server/modules/schedule/index.js +27 -35
- package/dist/server/utils/product.js +0 -1
- package/dist/solution/BaseSales/index.js +38 -41
- package/dist/solution/BookingByStep/index.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.js +0 -2
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
- package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.js +177 -252
- package/dist/solution/Checkout/utils/index.js +4 -16
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/ScanOrder/index.js +50 -60
- package/dist/solution/ShopDiscount/index.js +2 -7
- package/dist/solution/VenueBooking/index.js +45 -55
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +134 -134
- package/lib/effects/index.js +46 -57
- package/lib/index.js +82 -49
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
- package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
- package/lib/model/strategy/adapter/promotion/examples.js +222 -234
- package/lib/model/strategy/adapter/promotion/index.js +1 -0
- package/lib/model/strategy/adapter/promotion/type.js +243 -36
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.js +23 -42
- package/lib/modules/BookingContext/index.js +158 -136
- package/lib/modules/BookingContext/types.js +46 -32
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
- package/lib/modules/BookingContext/utils/flexible.js +38 -55
- package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
- package/lib/modules/BookingContext/utils/index.js +124 -41
- package/lib/modules/BookingContext/utils/noResource.js +58 -70
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
- package/lib/modules/BookingContext/utils/productExtend.js +202 -155
- package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
- package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
- package/lib/modules/BookingContext/utils/resources.js +209 -167
- package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
- package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
- package/lib/modules/Cart/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +321 -251
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +70 -78
- package/lib/modules/Cart/utils/changePrice.js +22 -50
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.js +235 -172
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.js +122 -127
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.js +70 -76
- package/lib/modules/OpenData/types.js +4 -16
- package/lib/modules/OpenData/utils.js +44 -78
- package/lib/modules/Order/index.js +1713 -2118
- package/lib/modules/Order/payment-utils.js +77 -120
- package/lib/modules/Order/types.js +88 -35
- package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
- package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
- package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
- package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
- package/lib/modules/Order/utils.js +814 -647
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.js +532 -399
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.js +230 -108
- package/lib/modules/Payment/utils.js +52 -51
- package/lib/modules/Payment/walletpass.js +485 -660
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.js +113 -123
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.js +81 -118
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- package/lib/modules/ResourcePlanner/index.js +25 -23
- package/lib/modules/ResourcePlanner/localClock.js +54 -119
- package/lib/modules/ResourcePlanner/planner.js +589 -770
- package/lib/modules/ResourcePlanner/types.js +10 -29
- package/lib/modules/Rules/index.js +1218 -959
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.js +85 -90
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.js +355 -430
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
- package/lib/modules/Schedule/index.js +100 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -71
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +773 -418
- package/lib/modules/SurchargeList/index.js +46 -60
- package/lib/modules/SurchargeList/types.js +4 -16
- package/lib/modules/index.js +245 -63
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.js +3050 -2665
- package/lib/server/modules/floor-plan/index.js +118 -134
- package/lib/server/modules/floor-plan/types.js +15 -30
- package/lib/server/modules/index.js +106 -68
- package/lib/server/modules/menu/index.js +142 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.js +784 -1002
- package/lib/server/modules/order/types.js +122 -33
- package/lib/server/modules/order/utils/filterBookings.js +219 -194
- package/lib/server/modules/order/utils/filterOrders.js +118 -92
- package/lib/server/modules/payment/index.js +59 -83
- package/lib/server/modules/payment/types.js +4 -16
- package/lib/server/modules/products/index.js +583 -471
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.js +137 -172
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.js +220 -186
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.js +135 -123
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +196 -139
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.js +479 -456
- package/lib/server/utils/time.js +40 -49
- package/lib/solution/BaseSales/index.js +1186 -1410
- package/lib/solution/BaseSales/types.js +42 -38
- package/lib/solution/BaseSales/utils/cartPromotion.js +642 -498
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
- package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
- package/lib/solution/BaseSales/utils.js +158 -143
- package/lib/solution/BookingByStep/index.js +1527 -1340
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.js +607 -522
- package/lib/solution/BookingTicket/types.js +99 -77
- package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
- package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
- package/lib/solution/BookingTicket/utils/cartView.js +114 -99
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
- package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
- package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
- package/lib/solution/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.js +1451 -1158
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -156
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +493 -460
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.js +328 -324
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.js +832 -864
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.js +409 -374
- package/lib/solution/ShopDiscount/index.js +226 -232
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +300 -172
- package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
- package/lib/solution/UnifiedBookingSales/types.js +10 -32
- package/lib/solution/VenueBooking/index.js +811 -879
- package/lib/solution/VenueBooking/types.js +19 -39
- package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
- package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.js +124 -41
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.js +4 -16
- package/lib/utils/payment-number.js +26 -46
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
|
@@ -1,126 +1,120 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
"use strict";
|
|
28
2
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
__export(utils_exports, {
|
|
32
|
-
aggregateItemRuleLimit: () => aggregateItemRuleLimit,
|
|
33
|
-
attachItemRuleLimitsToTopLevelProducts: () => attachItemRuleLimitsToTopLevelProducts,
|
|
34
|
-
buildItemRuleBusinessData: () => buildItemRuleBusinessData,
|
|
35
|
-
buildProductKey: () => buildProductKey,
|
|
36
|
-
buildProductLineFingerprint: () => buildProductLineFingerprint,
|
|
37
|
-
buildQuantityLimitIndex: () => buildQuantityLimitIndex,
|
|
38
|
-
collectLinkProductIdsFromReservationRules: () => collectLinkProductIdsFromReservationRules,
|
|
39
|
-
computeResourceIsFull: () => computeResourceIsFull,
|
|
40
|
-
createEmptySummary: () => createEmptySummary,
|
|
41
|
-
extractStrategyModelIdsFromTableConfig: () => extractStrategyModelIdsFromTableConfig,
|
|
42
|
-
filterProductsForScanOrderMore: () => filterProductsForScanOrderMore,
|
|
43
|
-
findReservationRuleProductByResourceId: () => findReservationRuleProductByResourceId,
|
|
44
|
-
getProductIdentityIndex: () => getProductIdentityIndex,
|
|
45
|
-
getSafeProductNum: () => getSafeProductNum,
|
|
46
|
-
getTopLevelProductId: () => getTopLevelProductId,
|
|
47
|
-
getTopLevelVariantId: () => getTopLevelVariantId,
|
|
48
|
-
hasCustomCapacityProduct: () => hasCustomCapacityProduct,
|
|
49
|
-
isIdentityMatch: () => isIdentityMatch,
|
|
50
|
-
isSkuOnlyDeleteIdentity: () => isSkuOnlyDeleteIdentity,
|
|
51
|
-
normalizeEnabledItemRuleIds: () => normalizeEnabledItemRuleIds,
|
|
52
|
-
normalizeItemRuleStrategies: () => normalizeItemRuleStrategies,
|
|
53
|
-
normalizeOrderProduct: () => normalizeOrderProduct,
|
|
54
|
-
pickFirstCustomCapacityDimensionId: () => pickFirstCustomCapacityDimensionId,
|
|
55
|
-
pickFirstCustomCapacityPaxBounds: () => pickFirstCustomCapacityPaxBounds,
|
|
56
|
-
pickFirstDurationMinutesFromProducts: () => pickFirstDurationMinutesFromProducts,
|
|
57
|
-
resolveSkuMatchedQuantityLimits: () => resolveSkuMatchedQuantityLimits,
|
|
58
|
-
toBoolean: () => toBoolean,
|
|
59
|
-
toNonNegativeInt: () => toNonNegativeInt,
|
|
60
|
-
toNonNegativeNumber: () => toNonNegativeNumber,
|
|
61
|
-
toPositiveString: () => toPositiveString,
|
|
62
|
-
toPriceString: () => toPriceString
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
63
5
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
6
|
+
exports.aggregateItemRuleLimit = aggregateItemRuleLimit;
|
|
7
|
+
exports.attachItemRuleLimitsToTopLevelProducts = attachItemRuleLimitsToTopLevelProducts;
|
|
8
|
+
exports.buildItemRuleBusinessData = buildItemRuleBusinessData;
|
|
9
|
+
exports.buildProductKey = buildProductKey;
|
|
10
|
+
exports.buildProductLineFingerprint = buildProductLineFingerprint;
|
|
11
|
+
exports.buildQuantityLimitIndex = buildQuantityLimitIndex;
|
|
12
|
+
exports.collectLinkProductIdsFromReservationRules = collectLinkProductIdsFromReservationRules;
|
|
13
|
+
exports.computeResourceIsFull = computeResourceIsFull;
|
|
14
|
+
exports.createEmptySummary = createEmptySummary;
|
|
15
|
+
exports.extractStrategyModelIdsFromTableConfig = extractStrategyModelIdsFromTableConfig;
|
|
16
|
+
exports.filterProductsForScanOrderMore = filterProductsForScanOrderMore;
|
|
17
|
+
exports.findReservationRuleProductByResourceId = findReservationRuleProductByResourceId;
|
|
18
|
+
exports.getProductIdentityIndex = getProductIdentityIndex;
|
|
19
|
+
exports.getSafeProductNum = getSafeProductNum;
|
|
20
|
+
exports.getTopLevelProductId = getTopLevelProductId;
|
|
21
|
+
exports.getTopLevelVariantId = getTopLevelVariantId;
|
|
22
|
+
exports.hasCustomCapacityProduct = hasCustomCapacityProduct;
|
|
23
|
+
exports.isIdentityMatch = isIdentityMatch;
|
|
24
|
+
exports.isSkuOnlyDeleteIdentity = isSkuOnlyDeleteIdentity;
|
|
25
|
+
exports.normalizeEnabledItemRuleIds = normalizeEnabledItemRuleIds;
|
|
26
|
+
exports.normalizeItemRuleStrategies = normalizeItemRuleStrategies;
|
|
27
|
+
exports.normalizeOrderProduct = normalizeOrderProduct;
|
|
28
|
+
exports.pickFirstCustomCapacityDimensionId = pickFirstCustomCapacityDimensionId;
|
|
29
|
+
exports.pickFirstCustomCapacityPaxBounds = pickFirstCustomCapacityPaxBounds;
|
|
30
|
+
exports.pickFirstDurationMinutesFromProducts = pickFirstDurationMinutesFromProducts;
|
|
31
|
+
exports.resolveSkuMatchedQuantityLimits = resolveSkuMatchedQuantityLimits;
|
|
32
|
+
exports.toBoolean = toBoolean;
|
|
33
|
+
exports.toNonNegativeInt = toNonNegativeInt;
|
|
34
|
+
exports.toNonNegativeNumber = toNonNegativeNumber;
|
|
35
|
+
exports.toPositiveString = toPositiveString;
|
|
36
|
+
exports.toPriceString = toPriceString;
|
|
37
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
38
|
+
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
39
|
+
var _utils = require("../../modules/Order/utils");
|
|
40
|
+
var _manualProductDiscount = require("../../modules/Order/utils/manualProductDiscount");
|
|
41
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
42
|
+
/**
|
|
43
|
+
* 构建金额全为 0 的空 summary。
|
|
44
|
+
* 作为尚未计算金额时的兜底默认值,避免下游因 undefined 报错。
|
|
45
|
+
*/
|
|
69
46
|
function createEmptySummary() {
|
|
70
47
|
return {
|
|
71
48
|
product_quantity: 0,
|
|
72
|
-
product_original_amount:
|
|
73
|
-
product_amount:
|
|
74
|
-
product_expect_amount:
|
|
75
|
-
product_tax_fee:
|
|
76
|
-
shipping_fee:
|
|
77
|
-
shipping_tax_fee:
|
|
78
|
-
tax_fee:
|
|
79
|
-
surcharge_fee:
|
|
49
|
+
product_original_amount: '0.00',
|
|
50
|
+
product_amount: '0.00',
|
|
51
|
+
product_expect_amount: '0.00',
|
|
52
|
+
product_tax_fee: '0.00',
|
|
53
|
+
shipping_fee: '0.00',
|
|
54
|
+
shipping_tax_fee: '0.00',
|
|
55
|
+
tax_fee: '0.00',
|
|
56
|
+
surcharge_fee: '0.00',
|
|
80
57
|
surcharges: [],
|
|
81
|
-
discount_amount:
|
|
82
|
-
deposit_amount:
|
|
83
|
-
expect_amount:
|
|
84
|
-
total_amount:
|
|
85
|
-
total_refund_amount:
|
|
86
|
-
customer_paid_amount:
|
|
87
|
-
order_paid_amount:
|
|
88
|
-
amount_gap:
|
|
89
|
-
rounding_amount:
|
|
90
|
-
pay_service_charge_amount:
|
|
58
|
+
discount_amount: '0.00',
|
|
59
|
+
deposit_amount: '0.00',
|
|
60
|
+
expect_amount: '0.00',
|
|
61
|
+
total_amount: '0.00',
|
|
62
|
+
total_refund_amount: '0.00',
|
|
63
|
+
customer_paid_amount: '0.00',
|
|
64
|
+
order_paid_amount: '0.00',
|
|
65
|
+
amount_gap: '0.00',
|
|
66
|
+
rounding_amount: '0.00',
|
|
67
|
+
pay_service_charge_amount: '0.00'
|
|
91
68
|
};
|
|
92
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 商品数量安全取值。
|
|
73
|
+
* - NaN / undefined / < 1 均视作 1(最低起订量)
|
|
74
|
+
* - 有值时取整(不支持小数数量)
|
|
75
|
+
*/
|
|
93
76
|
function getSafeProductNum(num) {
|
|
94
|
-
if (!num || Number.isNaN(num))
|
|
95
|
-
|
|
96
|
-
if (num < 1)
|
|
97
|
-
return 1;
|
|
77
|
+
if (!num || Number.isNaN(num)) return 1;
|
|
78
|
+
if (num < 1) return 1;
|
|
98
79
|
return Math.floor(num);
|
|
99
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 宽松布尔值转换。
|
|
84
|
+
* 兼容后端/配置中心常见的 boolean / number / string 表达,统一归一成 true/false。
|
|
85
|
+
*/
|
|
100
86
|
function toBoolean(value) {
|
|
101
|
-
if (typeof value ===
|
|
102
|
-
|
|
103
|
-
if (typeof value ===
|
|
104
|
-
return value !== 0;
|
|
105
|
-
if (typeof value === "string") {
|
|
87
|
+
if (typeof value === 'boolean') return value;
|
|
88
|
+
if (typeof value === 'number') return value !== 0;
|
|
89
|
+
if (typeof value === 'string') {
|
|
106
90
|
const normalized = value.trim().toLowerCase();
|
|
107
|
-
return [
|
|
91
|
+
return ['1', 'true', 'yes', 'y', 'on'].includes(normalized);
|
|
108
92
|
}
|
|
109
93
|
return false;
|
|
110
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 将值归一化为有效的正向字符串。
|
|
98
|
+
* null / undefined / 空串 / '0' / 'null' 统一视为无效值并返回 undefined。
|
|
99
|
+
*/
|
|
111
100
|
function toPositiveString(value) {
|
|
112
|
-
if (value === null || value ===
|
|
113
|
-
return void 0;
|
|
101
|
+
if (value === null || value === undefined || value === '') return undefined;
|
|
114
102
|
const normalized = String(value).trim();
|
|
115
|
-
if (!normalized || normalized ===
|
|
116
|
-
return
|
|
103
|
+
if (!normalized || normalized === '0' || normalized.toLowerCase() === 'null') {
|
|
104
|
+
return undefined;
|
|
117
105
|
}
|
|
118
106
|
return normalized;
|
|
119
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 归一化 item rule 策略模型 ID 列表。
|
|
111
|
+
* - 支持 string / number 混合输入
|
|
112
|
+
* - 仅保留 > 0 的合法数字
|
|
113
|
+
* - 自动去重
|
|
114
|
+
*/
|
|
120
115
|
function normalizeEnabledItemRuleIds(rawIds) {
|
|
121
|
-
if (!Array.isArray(rawIds))
|
|
122
|
-
|
|
123
|
-
const uniqueIds = /* @__PURE__ */ new Set();
|
|
116
|
+
if (!Array.isArray(rawIds)) return [];
|
|
117
|
+
const uniqueIds = new Set();
|
|
124
118
|
for (const item of rawIds) {
|
|
125
119
|
const strategyId = Number(item);
|
|
126
120
|
if (Number.isFinite(strategyId) && strategyId > 0) {
|
|
@@ -129,27 +123,27 @@ function normalizeEnabledItemRuleIds(rawIds) {
|
|
|
129
123
|
}
|
|
130
124
|
return Array.from(uniqueIds);
|
|
131
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 从 tableConfig 中提取合法策略模型 ID 列表。
|
|
129
|
+
*/
|
|
132
130
|
function extractStrategyModelIdsFromTableConfig(tableConfig) {
|
|
133
|
-
const rawIds = Array.isArray(tableConfig
|
|
134
|
-
return rawIds.map(
|
|
131
|
+
const rawIds = Array.isArray(tableConfig?.strategy_model_ids) ? tableConfig.strategy_model_ids : [];
|
|
132
|
+
return rawIds.map(id => Number(id)).filter(id => Number.isFinite(id) && id > 0);
|
|
135
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 仅保留 item_rule 类型策略。
|
|
137
|
+
*/
|
|
136
138
|
function normalizeItemRuleStrategies(configs) {
|
|
137
|
-
if (!Array.isArray(configs))
|
|
138
|
-
|
|
139
|
-
return configs.filter(
|
|
140
|
-
(config) => {
|
|
141
|
-
var _a;
|
|
142
|
-
return Boolean(
|
|
143
|
-
config && typeof config === "object" && ((_a = config == null ? void 0 : config.metadata) == null ? void 0 : _a.type) === "item_rule"
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
);
|
|
139
|
+
if (!Array.isArray(configs)) return [];
|
|
140
|
+
return configs.filter(config => Boolean(config && typeof config === 'object' && config?.metadata?.type === 'item_rule'));
|
|
147
141
|
}
|
|
148
142
|
function toNonNegativeNumber(value) {
|
|
149
|
-
if (typeof value ===
|
|
143
|
+
if (typeof value === 'number') {
|
|
150
144
|
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
151
145
|
}
|
|
152
|
-
if (typeof value ===
|
|
146
|
+
if (typeof value === 'string') {
|
|
153
147
|
const parsed = Number(value);
|
|
154
148
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
155
149
|
}
|
|
@@ -158,49 +152,45 @@ function toNonNegativeNumber(value) {
|
|
|
158
152
|
function toNonNegativeInt(value) {
|
|
159
153
|
return Math.floor(toNonNegativeNumber(value));
|
|
160
154
|
}
|
|
161
|
-
function toPriceString(value, fallback =
|
|
155
|
+
function toPriceString(value, fallback = '0.00') {
|
|
162
156
|
const parsedValue = Number(value);
|
|
163
|
-
if (Number.isNaN(parsedValue))
|
|
164
|
-
return fallback;
|
|
157
|
+
if (Number.isNaN(parsedValue)) return fallback;
|
|
165
158
|
return parsedValue.toFixed(2);
|
|
166
159
|
}
|
|
167
160
|
function buildProductKey(productId, productVariantId) {
|
|
168
161
|
return `${productId}_${productVariantId}`;
|
|
169
162
|
}
|
|
163
|
+
|
|
164
|
+
/** 加餐接口只提交本次新增商品,过滤掉首单预约占位规则行。 */
|
|
170
165
|
function filterProductsForScanOrderMore(products = []) {
|
|
171
|
-
return products.filter(
|
|
172
|
-
var _a;
|
|
173
|
-
return ((_a = product.metadata) == null ? void 0 : _a.is_rule) !== true;
|
|
174
|
-
});
|
|
166
|
+
return products.filter(product => product.metadata?.is_rule !== true);
|
|
175
167
|
}
|
|
176
168
|
function getTopLevelProductId(product) {
|
|
177
169
|
const rawProductId = product.product_id ?? product.id;
|
|
178
170
|
const productId = Number(rawProductId);
|
|
179
|
-
if (!Number.isFinite(productId) || productId <= 0)
|
|
180
|
-
return null;
|
|
171
|
+
if (!Number.isFinite(productId) || productId <= 0) return null;
|
|
181
172
|
return productId;
|
|
182
173
|
}
|
|
183
174
|
function getTopLevelVariantId(product) {
|
|
184
175
|
const rawVariantId = product.product_variant_id ?? product.variant_id;
|
|
185
|
-
if (rawVariantId ===
|
|
176
|
+
if (rawVariantId === undefined || rawVariantId === null || rawVariantId === '') {
|
|
186
177
|
return null;
|
|
187
178
|
}
|
|
188
179
|
const variantId = Number(rawVariantId);
|
|
189
|
-
if (!Number.isFinite(variantId) || variantId <= 0)
|
|
190
|
-
return null;
|
|
180
|
+
if (!Number.isFinite(variantId) || variantId <= 0) return null;
|
|
191
181
|
return variantId;
|
|
192
182
|
}
|
|
193
183
|
function buildQuantityLimitIndex(limits) {
|
|
194
|
-
const exactLimitMap =
|
|
195
|
-
const productLimitMap =
|
|
184
|
+
const exactLimitMap = new Map();
|
|
185
|
+
const productLimitMap = new Map();
|
|
196
186
|
for (const limit of limits) {
|
|
197
187
|
const targets = limit.targets || [];
|
|
198
|
-
|
|
199
|
-
|
|
188
|
+
// 多目标规则(OR 组)只做购物车聚合校验,不下发到单品维度,
|
|
189
|
+
// 避免 UI 把「组内至少 1 份」误当作每款商品各自的 min,锁死减号按钮。
|
|
190
|
+
if (targets.length !== 1) continue;
|
|
200
191
|
const target = targets[0];
|
|
201
192
|
const productId = Number(target.product_id);
|
|
202
|
-
if (!Number.isFinite(productId) || productId <= 0)
|
|
203
|
-
continue;
|
|
193
|
+
if (!Number.isFinite(productId) || productId <= 0) continue;
|
|
204
194
|
const variantId = Number(target.product_variant_id);
|
|
205
195
|
const hasVariant = Number.isFinite(variantId) && variantId > 0;
|
|
206
196
|
if (hasVariant) {
|
|
@@ -218,44 +208,53 @@ function buildQuantityLimitIndex(limits) {
|
|
|
218
208
|
};
|
|
219
209
|
}
|
|
220
210
|
function resolveSkuMatchedQuantityLimits(params) {
|
|
221
|
-
const {
|
|
211
|
+
const {
|
|
212
|
+
productId,
|
|
213
|
+
variantId,
|
|
214
|
+
limitIndex
|
|
215
|
+
} = params;
|
|
222
216
|
const exactMatched = variantId && variantId > 0 ? limitIndex.exactLimitMap.get(buildProductKey(productId, variantId)) || [] : [];
|
|
223
217
|
const productMatched = limitIndex.productLimitMap.get(productId) || [];
|
|
224
218
|
return [...exactMatched, ...productMatched];
|
|
225
219
|
}
|
|
226
220
|
function aggregateItemRuleLimit(matchedLimits) {
|
|
227
|
-
if (!matchedLimits.length)
|
|
228
|
-
return null;
|
|
221
|
+
if (!matchedLimits.length) return null;
|
|
229
222
|
let min;
|
|
230
223
|
let max;
|
|
231
224
|
let remainingMax;
|
|
232
225
|
let exact;
|
|
233
226
|
let mustInclude = false;
|
|
227
|
+
|
|
228
|
+
// 聚合 validationMessage 按 min / max 两个方向分别输出:
|
|
229
|
+
// - maxValidationMessage:触发"当前 max / remainingMax / exact"的那条规则的文案
|
|
230
|
+
// - minValidationMessage:触发"当前 min / exact"的那条规则的文案
|
|
231
|
+
// 各自再回退到其他带文案的规则(最后回退到第一条非空 validationMessage)。
|
|
232
|
+
// 前端可根据"加号/减号被拦"的方向取对应文案。
|
|
234
233
|
let maxSourceLimit;
|
|
235
234
|
let remainingMaxSourceLimit;
|
|
236
235
|
let exactSourceLimit;
|
|
237
236
|
let minSourceLimit;
|
|
238
237
|
let firstMessagedLimit;
|
|
239
238
|
for (const limit of matchedLimits) {
|
|
240
|
-
if (typeof limit.requiredMin ===
|
|
241
|
-
if (min ===
|
|
239
|
+
if (typeof limit.requiredMin === 'number' && Number.isFinite(limit.requiredMin)) {
|
|
240
|
+
if (min === undefined || limit.requiredMin > min) {
|
|
242
241
|
min = limit.requiredMin;
|
|
243
242
|
minSourceLimit = limit;
|
|
244
243
|
}
|
|
245
244
|
}
|
|
246
|
-
if (typeof limit.requiredMax ===
|
|
247
|
-
if (max ===
|
|
245
|
+
if (typeof limit.requiredMax === 'number' && Number.isFinite(limit.requiredMax)) {
|
|
246
|
+
if (max === undefined || limit.requiredMax < max) {
|
|
248
247
|
max = limit.requiredMax;
|
|
249
248
|
maxSourceLimit = limit;
|
|
250
249
|
}
|
|
251
250
|
}
|
|
252
|
-
if (typeof limit.remainingMax ===
|
|
253
|
-
if (remainingMax ===
|
|
251
|
+
if (typeof limit.remainingMax === 'number' && Number.isFinite(limit.remainingMax)) {
|
|
252
|
+
if (remainingMax === undefined || limit.remainingMax < remainingMax) {
|
|
254
253
|
remainingMax = limit.remainingMax;
|
|
255
254
|
remainingMaxSourceLimit = limit;
|
|
256
255
|
}
|
|
257
256
|
}
|
|
258
|
-
if (typeof limit.requiredExact ===
|
|
257
|
+
if (typeof limit.requiredExact === 'number' && Number.isFinite(limit.requiredExact)) {
|
|
259
258
|
exact = limit.requiredExact;
|
|
260
259
|
exactSourceLimit = limit;
|
|
261
260
|
}
|
|
@@ -264,16 +263,19 @@ function aggregateItemRuleLimit(matchedLimits) {
|
|
|
264
263
|
firstMessagedLimit = limit;
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
|
-
if (typeof exact ===
|
|
266
|
+
if (typeof exact === 'number' && Number.isFinite(exact)) {
|
|
268
267
|
min = exact;
|
|
269
268
|
max = exact;
|
|
270
269
|
}
|
|
271
270
|
const maxMessageSource = maxSourceLimit || remainingMaxSourceLimit || exactSourceLimit || firstMessagedLimit;
|
|
272
271
|
const minMessageSource = minSourceLimit || exactSourceLimit || firstMessagedLimit;
|
|
273
|
-
const maxValidationMessage = maxMessageSource
|
|
274
|
-
const rawMaxValidationMessage = maxMessageSource
|
|
275
|
-
const minValidationMessage = minMessageSource
|
|
276
|
-
const rawMinValidationMessage = minMessageSource
|
|
272
|
+
const maxValidationMessage = maxMessageSource?.validationMessage;
|
|
273
|
+
const rawMaxValidationMessage = maxMessageSource?.rawValidationMessage;
|
|
274
|
+
const minValidationMessage = minMessageSource?.validationMessage;
|
|
275
|
+
const rawMinValidationMessage = minMessageSource?.rawValidationMessage;
|
|
276
|
+
|
|
277
|
+
// validationMessage / rawValidationMessage 保留为"最严格限制"的聚合文案,
|
|
278
|
+
// 便于旧消费者(只关心 max 侧)不破坏。优先 max → min。
|
|
277
279
|
const validationMessage = maxValidationMessage ?? minValidationMessage;
|
|
278
280
|
const rawValidationMessage = rawMaxValidationMessage ?? rawMinValidationMessage;
|
|
279
281
|
return {
|
|
@@ -291,28 +293,34 @@ function aggregateItemRuleLimit(matchedLimits) {
|
|
|
291
293
|
};
|
|
292
294
|
}
|
|
293
295
|
function buildItemRuleBusinessData(params) {
|
|
294
|
-
const {
|
|
296
|
+
const {
|
|
297
|
+
tempOrder,
|
|
298
|
+
runtimeConfig,
|
|
299
|
+
itemRuleConfigs
|
|
300
|
+
} = params;
|
|
295
301
|
const rawPax = runtimeConfig.pax || {};
|
|
296
302
|
const adultPax = toNonNegativeInt(rawPax.adult);
|
|
297
303
|
const childPax = toNonNegativeInt(rawPax.child);
|
|
298
304
|
const totalPax = toNonNegativeInt(rawPax.total) || adultPax + childPax || 1;
|
|
299
305
|
const cartItems = tempOrder.products.reduce((result, product) => {
|
|
300
|
-
if (typeof product.product_id !==
|
|
301
|
-
return result;
|
|
306
|
+
if (typeof product.product_id !== 'number') return result;
|
|
302
307
|
result.push({
|
|
303
308
|
product_id: product.product_id,
|
|
304
309
|
quantity: toNonNegativeInt(product.num),
|
|
305
|
-
...typeof product.product_variant_id ===
|
|
310
|
+
...(typeof product.product_variant_id === 'number' ? {
|
|
311
|
+
product_variant_id: product.product_variant_id
|
|
312
|
+
} : {})
|
|
306
313
|
});
|
|
307
314
|
return result;
|
|
308
315
|
}, []);
|
|
309
316
|
const historicalItems = Array.isArray(runtimeConfig.historicalItems) ? runtimeConfig.historicalItems.reduce((result, item) => {
|
|
310
|
-
if (typeof
|
|
311
|
-
return result;
|
|
317
|
+
if (typeof item?.product_id !== 'number') return result;
|
|
312
318
|
result.push({
|
|
313
319
|
product_id: item.product_id,
|
|
314
320
|
quantity: toNonNegativeInt(item.quantity),
|
|
315
|
-
...typeof item.product_variant_id ===
|
|
321
|
+
...(typeof item.product_variant_id === 'number' ? {
|
|
322
|
+
product_variant_id: item.product_variant_id
|
|
323
|
+
} : {})
|
|
316
324
|
});
|
|
317
325
|
return result;
|
|
318
326
|
}, []) : [];
|
|
@@ -324,23 +332,20 @@ function buildItemRuleBusinessData(params) {
|
|
|
324
332
|
},
|
|
325
333
|
cartItems,
|
|
326
334
|
historicalItems,
|
|
327
|
-
serviceType: runtimeConfig.serviceType ||
|
|
328
|
-
submissionIndex: typeof runtimeConfig.submissionIndex ===
|
|
335
|
+
serviceType: runtimeConfig.serviceType || 'dine-in',
|
|
336
|
+
submissionIndex: typeof runtimeConfig.submissionIndex === 'number' ? toNonNegativeInt(runtimeConfig.submissionIndex) : tempOrder.order_id ? 1 : 0,
|
|
329
337
|
custom: runtimeConfig.custom || {},
|
|
330
338
|
strategyConfigs: itemRuleConfigs
|
|
331
339
|
};
|
|
332
340
|
}
|
|
333
341
|
function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
|
|
334
|
-
if (!Array.isArray(productList))
|
|
335
|
-
return productList;
|
|
342
|
+
if (!Array.isArray(productList)) return productList;
|
|
336
343
|
const limitIndex = buildQuantityLimitIndex(limits || []);
|
|
337
344
|
for (const item of productList) {
|
|
338
|
-
if (!item || typeof item !==
|
|
339
|
-
continue;
|
|
345
|
+
if (!item || typeof item !== 'object') continue;
|
|
340
346
|
const productItem = item;
|
|
341
347
|
const productId = getTopLevelProductId(productItem);
|
|
342
|
-
if (productId === null)
|
|
343
|
-
continue;
|
|
348
|
+
if (productId === null) continue;
|
|
344
349
|
const variantId = getTopLevelVariantId(productItem);
|
|
345
350
|
const matchedLimits = limits.length ? resolveSkuMatchedQuantityLimits({
|
|
346
351
|
productId,
|
|
@@ -350,20 +355,25 @@ function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
|
|
|
350
355
|
const aggregatedLimit = aggregateItemRuleLimit(matchedLimits);
|
|
351
356
|
if (aggregatedLimit) {
|
|
352
357
|
productItem.item_rule_limit = aggregatedLimit;
|
|
353
|
-
} else if (
|
|
358
|
+
} else if ('item_rule_limit' in productItem) {
|
|
354
359
|
delete productItem.item_rule_limit;
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
362
|
return productList;
|
|
358
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
|
|
367
|
+
* 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
|
|
368
|
+
*/
|
|
359
369
|
function buildProductLineFingerprint(productOptionItem, productBundle) {
|
|
360
|
-
const normalizeOptions =
|
|
370
|
+
const normalizeOptions = options => {
|
|
361
371
|
const optionArr = Array.isArray(options) ? options : [];
|
|
362
|
-
return optionArr.map(
|
|
363
|
-
option_group_item_id: Number(item
|
|
364
|
-
option_group_id: Number(item
|
|
365
|
-
num: typeof
|
|
366
|
-
price: String(
|
|
372
|
+
return optionArr.map(item => ({
|
|
373
|
+
option_group_item_id: Number(item?.option_group_item_id) || 0,
|
|
374
|
+
option_group_id: Number(item?.option_group_id) || 0,
|
|
375
|
+
num: typeof item?.num === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
|
|
376
|
+
price: String(item?.add_price ?? item?.price ?? '')
|
|
367
377
|
})).sort((p, q) => {
|
|
368
378
|
if (p.option_group_item_id !== q.option_group_item_id) {
|
|
369
379
|
return p.option_group_item_id - q.option_group_item_id;
|
|
@@ -371,84 +381,119 @@ function buildProductLineFingerprint(productOptionItem, productBundle) {
|
|
|
371
381
|
if (p.option_group_id !== q.option_group_id) {
|
|
372
382
|
return p.option_group_id - q.option_group_id;
|
|
373
383
|
}
|
|
374
|
-
if (p.num !== q.num)
|
|
375
|
-
return p.num - q.num;
|
|
384
|
+
if (p.num !== q.num) return p.num - q.num;
|
|
376
385
|
return p.price.localeCompare(q.price);
|
|
377
386
|
});
|
|
378
387
|
};
|
|
379
388
|
const normalizedOpts = normalizeOptions(productOptionItem);
|
|
380
389
|
const bundleArr = Array.isArray(productBundle) ? productBundle : [];
|
|
381
|
-
const normalizedBundles = bundleArr.map(
|
|
382
|
-
bundle_id: Number(
|
|
383
|
-
product_id: Number(item
|
|
384
|
-
product_variant_id: Number(
|
|
385
|
-
num: typeof
|
|
386
|
-
option: normalizeOptions(item
|
|
390
|
+
const normalizedBundles = bundleArr.map(item => ({
|
|
391
|
+
bundle_id: Number(item?.bundle_id ?? item?.id ?? 0) || 0,
|
|
392
|
+
product_id: Number(item?.product_id) || 0,
|
|
393
|
+
product_variant_id: Number(item?.bundle_variant_id ?? item?.product_variant_id) || 0,
|
|
394
|
+
num: typeof item?.num === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
|
|
395
|
+
option: normalizeOptions(item?.option)
|
|
387
396
|
})).sort((p, q) => {
|
|
388
|
-
if (p.bundle_id !== q.bundle_id)
|
|
389
|
-
|
|
390
|
-
if (p.product_id !== q.product_id)
|
|
391
|
-
return p.product_id - q.product_id;
|
|
397
|
+
if (p.bundle_id !== q.bundle_id) return p.bundle_id - q.bundle_id;
|
|
398
|
+
if (p.product_id !== q.product_id) return p.product_id - q.product_id;
|
|
392
399
|
if (p.product_variant_id !== q.product_variant_id) {
|
|
393
400
|
return p.product_variant_id - q.product_variant_id;
|
|
394
401
|
}
|
|
395
|
-
if (p.num !== q.num)
|
|
396
|
-
return p.num - q.num;
|
|
402
|
+
if (p.num !== q.num) return p.num - q.num;
|
|
397
403
|
return JSON.stringify(p.option).localeCompare(JSON.stringify(q.option));
|
|
398
404
|
});
|
|
399
405
|
return JSON.stringify([normalizedOpts, normalizedBundles]);
|
|
400
406
|
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
|
|
410
|
+
* 与显式 `product_sku: { option: [] }` 区分:后者只匹配「无选项」行。
|
|
411
|
+
*/
|
|
401
412
|
function isSkuOnlyDeleteIdentity(x) {
|
|
402
|
-
if (x.unique_identification_number || x.identity_key)
|
|
403
|
-
|
|
404
|
-
return !("product_sku" in x) && !("product_bundle" in x);
|
|
413
|
+
if (x.unique_identification_number || x.identity_key) return false;
|
|
414
|
+
return !('product_sku' in x) && !('product_bundle' in x);
|
|
405
415
|
}
|
|
406
416
|
function fingerprintForIdentityWithOptionKeys(x) {
|
|
407
417
|
const row = x;
|
|
408
|
-
const opts =
|
|
409
|
-
const buds =
|
|
418
|
+
const opts = 'product_sku' in row ? (0, _utils.getProductSkuOptions)(row) : [];
|
|
419
|
+
const buds = 'product_bundle' in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
|
|
410
420
|
return buildProductLineFingerprint(opts, buds);
|
|
411
421
|
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 判断两个商品 identity 是否匹配(不透明 identity 契约)。
|
|
425
|
+
*
|
|
426
|
+
* 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
|
|
427
|
+
* - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
|
|
428
|
+
* - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
|
|
429
|
+
* 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
|
|
430
|
+
* - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
|
|
431
|
+
* 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
|
|
432
|
+
*/
|
|
412
433
|
function isIdentityMatch(a, b) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (aHasKey && bHasKey)
|
|
421
|
-
return aUid === bUid;
|
|
422
|
-
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b))
|
|
423
|
-
return true;
|
|
434
|
+
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
|
|
435
|
+
const aUid = a.metadata?.unique_identification_number ?? a.unique_identification_number ?? a.identity_key;
|
|
436
|
+
const bUid = b.metadata?.unique_identification_number ?? b.unique_identification_number ?? b.identity_key;
|
|
437
|
+
const aHasKey = typeof aUid === 'string' && aUid.length > 0;
|
|
438
|
+
const bHasKey = typeof bUid === 'string' && bUid.length > 0;
|
|
439
|
+
if (aHasKey && bHasKey) return aUid === bUid;
|
|
440
|
+
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
|
|
424
441
|
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
425
442
|
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 根据 identity 在商品列表中定位下标。
|
|
446
|
+
* 用于判断商品是否已存在(已存在则累加数量,否则新增)。
|
|
447
|
+
*/
|
|
426
448
|
function getProductIdentityIndex(products, identity) {
|
|
427
|
-
return products.findIndex(
|
|
449
|
+
return products.findIndex(item => isIdentityMatch(item, identity));
|
|
428
450
|
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 对外部传入的商品对象做归一化(v2 composite 语义):
|
|
454
|
+
* - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
|
|
455
|
+
* - 对 num 调用 getSafeProductNum 做安全处理
|
|
456
|
+
* - 保留 _origin 供后续业务流程(如促销规则)使用
|
|
457
|
+
*
|
|
458
|
+
* 价格字段语义(metadata 权威源 + composite 派生):
|
|
459
|
+
* - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option**、
|
|
460
|
+
* **不含折扣**。是推导 main_product_* 的起点。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
461
|
+
* - `metadata.main_product_original_price`:`source + Σ(option.price × option.num)`,**含 option**、
|
|
462
|
+
* **不含折扣**。
|
|
463
|
+
* - `metadata.main_product_selling_price`:`main_product_original_price - 主商品券 per-unit amount`,
|
|
464
|
+
* **含 option**、**含折扣**。
|
|
465
|
+
* - 行级 `selling_price` = `main_product_selling_price + Σ(bundle_selling_price × num)`。
|
|
466
|
+
* - 行级 `original_price` = `main_product_original_price + Σ(bundle 原价 × num)`。
|
|
467
|
+
*
|
|
468
|
+
* 迁移与幂等:
|
|
469
|
+
* - `metadata.price_schema_version === 2` → 已新语义归一化,保留 main_product_* 原值(保留折扣)。
|
|
470
|
+
* - 其它情况(v1 / 缺字段 / 无 metadata)→ 按"main_product_selling_price 曾是 main-only"的旧约定
|
|
471
|
+
* 反推 legacyDiscount,再以新 source + options 基准重算。最终统一打上 `price_schema_version: 2`。
|
|
472
|
+
* - 因此多次 normalize 不会重复叠加 option/bundle。
|
|
473
|
+
*/
|
|
429
474
|
function normalizeOrderProduct(product) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
475
|
+
const metadata = {
|
|
476
|
+
...(product.metadata || {})
|
|
477
|
+
};
|
|
478
|
+
// 不透明 identity 契约:每条订单行必须带 identity_key。
|
|
479
|
+
// 调用方未传时由 SDK 自动生成 UUID,后续 update/remove 只做严格比对,避免猜测合成 key。
|
|
480
|
+
const resolvedIdentityKey = product.metadata?.unique_identification_number ? String(product.metadata.unique_identification_number) : product.unique_identification_number && String(product.unique_identification_number).length > 0 ? String(product.unique_identification_number) : product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, _utils.createUuidV4)();
|
|
433
481
|
metadata.unique_identification_number = resolvedIdentityKey;
|
|
434
482
|
const priceOverride = metadata.price_override;
|
|
435
|
-
const originOtherBundle =
|
|
483
|
+
const originOtherBundle = metadata.origin?._extend?.other?.bundle;
|
|
436
484
|
let bundleInput = product.product_bundle || [];
|
|
437
|
-
if (priceOverride !==
|
|
438
|
-
const priceById =
|
|
439
|
-
originOtherBundle.forEach(
|
|
440
|
-
if (
|
|
441
|
-
return;
|
|
485
|
+
if (priceOverride !== undefined && priceOverride !== null && priceOverride !== '' && Array.isArray(originOtherBundle) && originOtherBundle.length > 0) {
|
|
486
|
+
const priceById = new Map();
|
|
487
|
+
originOtherBundle.forEach(item => {
|
|
488
|
+
if (item?.id == null) return;
|
|
442
489
|
const unit = item.price ?? item.bundle_selling_price;
|
|
443
|
-
if (unit ===
|
|
444
|
-
return;
|
|
490
|
+
if (unit === undefined || unit === null) return;
|
|
445
491
|
priceById.set(String(item.id), String(unit));
|
|
446
492
|
});
|
|
447
493
|
const base = Array.isArray(bundleInput) && bundleInput.length > 0 ? bundleInput : originOtherBundle;
|
|
448
|
-
bundleInput = base.map(
|
|
449
|
-
const unit = priceById.get(String(item
|
|
450
|
-
if (unit ===
|
|
451
|
-
return item;
|
|
494
|
+
bundleInput = base.map(item => {
|
|
495
|
+
const unit = priceById.get(String(item?.id));
|
|
496
|
+
if (unit === undefined) return item;
|
|
452
497
|
return {
|
|
453
498
|
...item,
|
|
454
499
|
price: unit,
|
|
@@ -456,7 +501,7 @@ function normalizeOrderProduct(product) {
|
|
|
456
501
|
};
|
|
457
502
|
});
|
|
458
503
|
}
|
|
459
|
-
const normalizedBundle = bundleInput.map(
|
|
504
|
+
const normalizedBundle = bundleInput.map(item => {
|
|
460
505
|
const magnitudeSource = {
|
|
461
506
|
...item,
|
|
462
507
|
// 计算 markdown 净额时,只把真实毛价交给 helper;若只有 bundle_selling_price,
|
|
@@ -469,94 +514,114 @@ function normalizeOrderProduct(product) {
|
|
|
469
514
|
bundle_product_id: item.bundle_product_id ?? item._bundle_product_id,
|
|
470
515
|
bundle_group_id: item.bundle_group_id ?? item.group_id,
|
|
471
516
|
// price 保留毛价(markdown 仍为 5),由 price_type 决定符号。
|
|
472
|
-
price: item.price ?? item.custom_price ?? item.bundle_selling_price ??
|
|
473
|
-
price_type: item.price_type ?? item.custom_price_type ??
|
|
474
|
-
price_type_ext: item.price_type_ext ?? item.custom_price_type_ext ??
|
|
517
|
+
price: item.price ?? item.custom_price ?? item.bundle_selling_price ?? '0.00',
|
|
518
|
+
price_type: item.price_type ?? item.custom_price_type ?? '',
|
|
519
|
+
price_type_ext: item.price_type_ext ?? item.custom_price_type_ext ?? ''
|
|
475
520
|
};
|
|
476
|
-
|
|
477
|
-
|
|
521
|
+
// bundle_selling_price 落「正净额」:markdown = |price| − Σoptions;markup/原价维持原值。
|
|
522
|
+
normalizedItem.bundle_selling_price = (0, _utils.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
|
|
523
|
+
if (normalizedItem.price_type === 'markdown' && (normalizedItem.price_type_ext === '' || normalizedItem.price_type_ext === undefined || normalizedItem.price_type_ext === null)) {
|
|
478
524
|
normalizedItem.custom_price = normalizedItem.custom_price ?? normalizedItem.price;
|
|
479
525
|
normalizedItem.price = normalizedItem.bundle_selling_price;
|
|
480
526
|
}
|
|
481
|
-
if ((normalizedItem.price_type ===
|
|
527
|
+
if ((normalizedItem.price_type === 'markdown' || normalizedItem.price_type === 'markup') && (normalizedItem.price_type_ext === '' || normalizedItem.price_type_ext === undefined || normalizedItem.price_type_ext === null)) {
|
|
482
528
|
delete normalizedItem.original_total;
|
|
483
529
|
}
|
|
484
530
|
return normalizedItem;
|
|
485
531
|
});
|
|
486
|
-
const normalizedOptions = (0,
|
|
487
|
-
const optionSum = (0,
|
|
532
|
+
const normalizedOptions = (0, _utils.normalizeProductSkuOptions)(product.product_sku?.option);
|
|
533
|
+
const optionSum = (0, _utils.sumOptionUnitPrice)(normalizedOptions);
|
|
488
534
|
const productSku = {
|
|
489
|
-
...(0,
|
|
535
|
+
...(0, _utils.ensureProductSku)(product),
|
|
490
536
|
option: normalizedOptions
|
|
491
537
|
};
|
|
492
|
-
|
|
538
|
+
|
|
539
|
+
// 1) 解析 source_product_price。
|
|
540
|
+
// 优先级:
|
|
541
|
+
// 1) metadata.source_product_price(v2 权威)
|
|
542
|
+
// 2) variantPrice(命中 variant_id,从 metadata.origin.variant[vid].price 读)
|
|
543
|
+
// 3) _origin.price / _origin.base_price(后端语义的基础价)
|
|
544
|
+
// 4) v1 兼容:无 v2 标记但存在 metadata.main_product_original_price(v1 main-only,即旧 source)
|
|
545
|
+
// 5) 入参 original_price(v1 addProduct 约定:original_price 为 pre-discount 基础价)
|
|
546
|
+
// 6) 入参 selling_price(最末兜底,新添加路径 caller 只给 selling_price)
|
|
547
|
+
const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === '2';
|
|
493
548
|
const variantId = Number(product.product_variant_id || 0);
|
|
494
|
-
const variantList = variantId ?
|
|
495
|
-
const variantPrice = Array.isArray(variantList) ?
|
|
549
|
+
const variantList = variantId ? metadata?.origin?.variant : null;
|
|
550
|
+
const variantPrice = Array.isArray(variantList) ? variantList.find(v => Number(v?.id) === variantId)?.price : undefined;
|
|
496
551
|
const resolvedSource = (() => {
|
|
497
|
-
|
|
498
|
-
if (metadata.source_product_price !== void 0) {
|
|
552
|
+
if (metadata.source_product_price !== undefined) {
|
|
499
553
|
return String(metadata.source_product_price);
|
|
500
554
|
}
|
|
501
|
-
if (variantPrice !==
|
|
555
|
+
if (variantPrice !== undefined && variantPrice !== null) {
|
|
502
556
|
return String(variantPrice);
|
|
503
557
|
}
|
|
504
|
-
const originPrice =
|
|
505
|
-
if (originPrice !==
|
|
558
|
+
const originPrice = product._origin?.price;
|
|
559
|
+
if (originPrice !== undefined && originPrice !== null) {
|
|
506
560
|
return String(originPrice);
|
|
507
561
|
}
|
|
508
|
-
const originBasePrice =
|
|
509
|
-
if (originBasePrice !==
|
|
562
|
+
const originBasePrice = product._origin?.base_price;
|
|
563
|
+
if (originBasePrice !== undefined && originBasePrice !== null) {
|
|
510
564
|
return String(originBasePrice);
|
|
511
565
|
}
|
|
512
|
-
if (!isV2 && metadata.main_product_original_price !==
|
|
566
|
+
if (!isV2 && metadata.main_product_original_price !== undefined) {
|
|
513
567
|
return String(metadata.main_product_original_price);
|
|
514
568
|
}
|
|
515
|
-
return product.original_price ?? product.selling_price ??
|
|
569
|
+
return product.original_price ?? product.selling_price ?? '0.00';
|
|
516
570
|
})();
|
|
517
|
-
|
|
571
|
+
|
|
572
|
+
// 2) 派生 main_product_original_price(含 option、不含折扣)
|
|
573
|
+
const mainOriginalDec = new _decimal.default(Number(resolvedSource) || 0).plus(optionSum);
|
|
518
574
|
const mainOriginalStr = mainOriginalDec.toDecimalPlaces(2).toFixed(2);
|
|
575
|
+
|
|
576
|
+
// 3) 派生 main_product_selling_price(含 option、含主商品折扣)
|
|
577
|
+
// - v2 数据:直接沿用 metadata.main_product_selling_price(保留折扣)
|
|
578
|
+
// - v1 metadata:main_product_selling_price 旧语义是 main-only(≈ source-level 折后价),
|
|
579
|
+
// 反推 legacyDiscount = main_original(旧) - main_selling(旧),再用新 main_original - legacyDiscount
|
|
580
|
+
// - v1 addProduct 入参:top-level selling_price < original_price 表示主商品折扣;
|
|
581
|
+
// 用差额作为 legacyDiscount 映射到新 main_selling
|
|
582
|
+
// - 其它:视为无折扣,main_selling = main_original
|
|
519
583
|
let mainSellingDec;
|
|
520
584
|
if (isV2 && metadata.main_product_selling_price != null) {
|
|
521
|
-
mainSellingDec = new
|
|
585
|
+
mainSellingDec = new _decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
522
586
|
} else if (metadata.main_product_selling_price != null && metadata.main_product_original_price != null) {
|
|
523
|
-
const legacyOriginal = new
|
|
524
|
-
const legacySelling = new
|
|
587
|
+
const legacyOriginal = new _decimal.default(Number(metadata.main_product_original_price) || 0);
|
|
588
|
+
const legacySelling = new _decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
525
589
|
const legacyDiscount = legacyOriginal.minus(legacySelling);
|
|
526
590
|
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
527
|
-
} else if (product.original_price != null && product.selling_price != null && new
|
|
528
|
-
new
|
|
529
|
-
|
|
530
|
-
const topOriginal = new import_decimal.default(Number(product.original_price) || 0);
|
|
531
|
-
const topSelling = new import_decimal.default(Number(product.selling_price) || 0);
|
|
591
|
+
} else if (product.original_price != null && product.selling_price != null && new _decimal.default(Number(product.original_price) || 0).greaterThan(new _decimal.default(Number(product.selling_price) || 0))) {
|
|
592
|
+
const topOriginal = new _decimal.default(Number(product.original_price) || 0);
|
|
593
|
+
const topSelling = new _decimal.default(Number(product.selling_price) || 0);
|
|
532
594
|
const legacyDiscount = topOriginal.minus(topSelling);
|
|
533
595
|
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
534
596
|
} else {
|
|
535
597
|
mainSellingDec = mainOriginalDec;
|
|
536
598
|
}
|
|
537
599
|
const mainSellingStr = mainSellingDec.toDecimalPlaces(2).toFixed(2);
|
|
600
|
+
|
|
601
|
+
// 4) 落盘 metadata:三字段 + schema 版本 sentinel
|
|
538
602
|
metadata.source_product_price = resolvedSource;
|
|
539
603
|
metadata.main_product_original_price = mainOriginalStr;
|
|
540
604
|
metadata.main_product_selling_price = mainSellingStr;
|
|
541
605
|
metadata.price_schema_version = 2;
|
|
542
|
-
|
|
606
|
+
|
|
607
|
+
// 5) 合成行级 composite(main 已含 option,本步只叠 bundle)
|
|
608
|
+
const composedSellingPrice = (0, _utils.composeLinePrice)({
|
|
543
609
|
mainPrice: mainSellingStr,
|
|
544
610
|
bundle: normalizedBundle
|
|
545
611
|
});
|
|
546
|
-
const composedOriginalPrice = (0,
|
|
612
|
+
const composedOriginalPrice = (0, _utils.composeLinePrice)({
|
|
547
613
|
mainPrice: mainOriginalStr,
|
|
548
614
|
bundle: normalizedBundle,
|
|
549
615
|
useOriginalBundle: true
|
|
550
616
|
});
|
|
551
|
-
const finalOriginalPrice = (0,
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
);
|
|
617
|
+
const finalOriginalPrice = (0, _utils.resolveManualOverrideLineOriginalPrice)({
|
|
618
|
+
num: product.num,
|
|
619
|
+
metadata,
|
|
620
|
+
lineOriginalPrice: product.original_price
|
|
621
|
+
}, composedOriginalPrice);
|
|
622
|
+
const hasManualProductDiscount = (product.discount_list || []).some(item => item?.type === 'product');
|
|
558
623
|
const manualOriginalPrice = hasManualProductDiscount && product.original_price != null ? String(product.original_price) : finalOriginalPrice;
|
|
559
|
-
const discountList = (0,
|
|
624
|
+
const discountList = (0, _manualProductDiscount.ensureManualProductDiscountList)({
|
|
560
625
|
discountList: product.discount_list,
|
|
561
626
|
metadata,
|
|
562
627
|
originalPrice: manualOriginalPrice,
|
|
@@ -571,177 +636,147 @@ function normalizeOrderProduct(product) {
|
|
|
571
636
|
product_sku: productSku,
|
|
572
637
|
selling_price: composedSellingPrice,
|
|
573
638
|
original_price: manualOriginalPrice,
|
|
574
|
-
tax_fee: product.tax_fee ||
|
|
639
|
+
tax_fee: product.tax_fee || '0.00',
|
|
575
640
|
is_charge_tax: product.is_charge_tax ?? 0,
|
|
576
641
|
discount_list: discountList,
|
|
577
642
|
product_bundle: normalizedBundle,
|
|
578
643
|
metadata,
|
|
579
|
-
note: product.note != null ? String(product.note) :
|
|
644
|
+
note: product.note != null ? String(product.note) : ''
|
|
580
645
|
};
|
|
581
646
|
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* 从 reservation.enabled_reservation_rules 中收集 type=link 的商品 id。
|
|
650
|
+
*/
|
|
582
651
|
function collectLinkProductIdsFromReservationRules(rules) {
|
|
583
|
-
if (!Array.isArray(rules))
|
|
584
|
-
return [];
|
|
652
|
+
if (!Array.isArray(rules)) return [];
|
|
585
653
|
const ids = [];
|
|
586
654
|
for (const entry of rules) {
|
|
587
|
-
if (!entry || typeof entry !==
|
|
588
|
-
continue;
|
|
655
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
589
656
|
const rec = entry;
|
|
590
|
-
if (rec.type !==
|
|
591
|
-
|
|
592
|
-
if (!Array.isArray(rec.value))
|
|
593
|
-
continue;
|
|
657
|
+
if (rec.type !== 'link') continue;
|
|
658
|
+
if (!Array.isArray(rec.value)) continue;
|
|
594
659
|
for (const v of rec.value) {
|
|
595
660
|
const n = Number(v);
|
|
596
|
-
if (Number.isFinite(n) && n > 0)
|
|
597
|
-
ids.push(Math.floor(n));
|
|
661
|
+
if (Number.isFinite(n) && n > 0) ids.push(Math.floor(n));
|
|
598
662
|
}
|
|
599
663
|
}
|
|
600
664
|
return [...new Set(ids)];
|
|
601
665
|
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* 找到第一个归属指定桌台的预约规则商品。
|
|
669
|
+
* 归属判断基于商品资源中的 default_resource / optional_resource。
|
|
670
|
+
*/
|
|
602
671
|
function findReservationRuleProductByResourceId(products, resourceId) {
|
|
603
|
-
var _a;
|
|
604
672
|
const numericResourceId = Number(resourceId);
|
|
605
|
-
if (!Number.isFinite(numericResourceId) || numericResourceId <= 0)
|
|
606
|
-
return void 0;
|
|
673
|
+
if (!Number.isFinite(numericResourceId) || numericResourceId <= 0) return undefined;
|
|
607
674
|
for (const product of products) {
|
|
608
|
-
const resources =
|
|
609
|
-
if (!Array.isArray(resources))
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
return [...defaultResources, ...optionalResources].some(
|
|
615
|
-
(id) => Number(id) === numericResourceId
|
|
616
|
-
);
|
|
675
|
+
const resources = product?.product_resource?.resources;
|
|
676
|
+
if (!Array.isArray(resources)) continue;
|
|
677
|
+
const hasMatchedResource = resources.some(resource => {
|
|
678
|
+
const defaultResources = Array.isArray(resource?.default_resource) ? resource.default_resource : [];
|
|
679
|
+
const optionalResources = Array.isArray(resource?.optional_resource) ? resource.optional_resource : [];
|
|
680
|
+
return [...defaultResources, ...optionalResources].some(id => Number(id) === numericResourceId);
|
|
617
681
|
});
|
|
618
|
-
if (hasMatchedResource)
|
|
619
|
-
return product;
|
|
682
|
+
if (hasMatchedResource) return product;
|
|
620
683
|
}
|
|
621
|
-
return
|
|
684
|
+
return undefined;
|
|
622
685
|
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* 返回列表中第一个带有效 duration.value(分钟)的商品时长。
|
|
689
|
+
*/
|
|
623
690
|
function pickFirstDurationMinutesFromProducts(products) {
|
|
624
|
-
var _a;
|
|
625
691
|
for (const product of products) {
|
|
626
|
-
const value =
|
|
692
|
+
const value = product?.duration?.value;
|
|
627
693
|
const n = Number(value);
|
|
628
|
-
if (Number.isFinite(n) && n > 0)
|
|
629
|
-
return Math.floor(n);
|
|
694
|
+
if (Number.isFinite(n) && n > 0) return Math.floor(n);
|
|
630
695
|
}
|
|
631
|
-
return
|
|
696
|
+
return undefined;
|
|
632
697
|
}
|
|
698
|
+
|
|
699
|
+
/** 是否存在 capacity.type === 'custom' 的商品 */
|
|
633
700
|
function hasCustomCapacityProduct(products) {
|
|
634
|
-
return products.some(
|
|
635
|
-
var _a;
|
|
636
|
-
return ((_a = p == null ? void 0 : p.capacity) == null ? void 0 : _a.type) === "custom";
|
|
637
|
-
});
|
|
701
|
+
return products.some(p => p?.capacity?.type === 'custom');
|
|
638
702
|
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
|
|
706
|
+
* - single:只要有 `lastOrderId` 即视为占用
|
|
707
|
+
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
|
|
708
|
+
* - 其他('capacity' / undefined):返回 false,不施加限制
|
|
709
|
+
*/
|
|
639
710
|
function computeResourceIsFull(params) {
|
|
640
|
-
const {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
711
|
+
const {
|
|
712
|
+
resourceSelectType,
|
|
713
|
+
lastOrderId,
|
|
714
|
+
capacityList,
|
|
715
|
+
capacity
|
|
716
|
+
} = params;
|
|
717
|
+
if (resourceSelectType === 'single') return Boolean(lastOrderId);
|
|
718
|
+
if (resourceSelectType !== 'multiple') return false;
|
|
645
719
|
const totalCapacity = Number(capacity);
|
|
646
|
-
if (!Number.isFinite(totalCapacity) || totalCapacity <= 0)
|
|
647
|
-
|
|
648
|
-
const now = (0, import_dayjs.default)();
|
|
720
|
+
if (!Number.isFinite(totalCapacity) || totalCapacity <= 0) return false;
|
|
721
|
+
const now = (0, _dayjs.default)();
|
|
649
722
|
let occupied = 0;
|
|
650
723
|
for (const slot of capacityList || []) {
|
|
651
|
-
const start = (0,
|
|
652
|
-
const end = (0,
|
|
653
|
-
if (!start.isValid() || !end.isValid())
|
|
654
|
-
continue;
|
|
724
|
+
const start = (0, _dayjs.default)(slot?.start_at);
|
|
725
|
+
const end = (0, _dayjs.default)(slot?.end_at);
|
|
726
|
+
if (!start.isValid() || !end.isValid()) continue;
|
|
655
727
|
if ((now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))) {
|
|
656
|
-
const pax = Number(slot
|
|
657
|
-
if (Number.isFinite(pax) && pax > 0)
|
|
658
|
-
occupied += pax;
|
|
728
|
+
const pax = Number(slot?.pax);
|
|
729
|
+
if (Number.isFinite(pax) && pax > 0) occupied += pax;
|
|
659
730
|
}
|
|
660
731
|
}
|
|
661
732
|
return occupied >= totalCapacity;
|
|
662
733
|
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* 在商品列表中找到第一个 `capacity.type === 'custom'` 的商品,取其 `custom` 数组第一项的 min/max。
|
|
737
|
+
* 仅返回有限数字字段;若均无法解析则返回 `undefined`。
|
|
738
|
+
*/
|
|
663
739
|
function pickFirstCustomCapacityPaxBounds(products) {
|
|
664
740
|
for (const p of products) {
|
|
665
|
-
const cap = p
|
|
666
|
-
if (!cap || cap.type !==
|
|
667
|
-
|
|
668
|
-
if (!Array.isArray(cap.custom) || cap.custom.length === 0)
|
|
669
|
-
continue;
|
|
741
|
+
const cap = p?.capacity;
|
|
742
|
+
if (!cap || cap.type !== 'custom') continue;
|
|
743
|
+
if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
|
|
670
744
|
const row = cap.custom[0];
|
|
671
|
-
if (!row || typeof row !==
|
|
672
|
-
continue;
|
|
745
|
+
if (!row || typeof row !== 'object') continue;
|
|
673
746
|
const raw = row;
|
|
674
747
|
const out = {};
|
|
675
|
-
if (raw.min !== null && raw.min !==
|
|
748
|
+
if (raw.min !== null && raw.min !== undefined && raw.min !== '') {
|
|
676
749
|
const min = Number(raw.min);
|
|
677
|
-
if (Number.isFinite(min))
|
|
678
|
-
out.min = min;
|
|
750
|
+
if (Number.isFinite(min)) out.min = min;
|
|
679
751
|
}
|
|
680
|
-
if (raw.max !== null && raw.max !==
|
|
752
|
+
if (raw.max !== null && raw.max !== undefined && raw.max !== '') {
|
|
681
753
|
const max = Number(raw.max);
|
|
682
|
-
if (Number.isFinite(max))
|
|
683
|
-
out.max = max;
|
|
754
|
+
if (Number.isFinite(max)) out.max = max;
|
|
684
755
|
}
|
|
685
|
-
if (out.min !==
|
|
686
|
-
return out;
|
|
756
|
+
if (out.min !== undefined || out.max !== undefined) return out;
|
|
687
757
|
}
|
|
688
|
-
return
|
|
758
|
+
return undefined;
|
|
689
759
|
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* 第一个 `capacity.type === 'custom'` 的商品,取其 `custom[0].id`(提交 booking metadata.capacity 维度 id)。
|
|
763
|
+
* 无匹配时返回 `undefined`,调用方应回退为 `0`。
|
|
764
|
+
*/
|
|
690
765
|
function pickFirstCustomCapacityDimensionId(products) {
|
|
691
766
|
for (const p of products) {
|
|
692
|
-
const cap = p
|
|
693
|
-
if (!cap || cap.type !==
|
|
694
|
-
|
|
695
|
-
if (!Array.isArray(cap.custom) || cap.custom.length === 0)
|
|
696
|
-
continue;
|
|
767
|
+
const cap = p?.capacity;
|
|
768
|
+
if (!cap || cap.type !== 'custom') continue;
|
|
769
|
+
if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
|
|
697
770
|
const row = cap.custom[0];
|
|
698
|
-
if (!row || typeof row !==
|
|
699
|
-
continue;
|
|
771
|
+
if (!row || typeof row !== 'object') continue;
|
|
700
772
|
const id = row.id;
|
|
701
|
-
if (id === null || id ===
|
|
702
|
-
|
|
703
|
-
if (typeof id ===
|
|
704
|
-
return id;
|
|
705
|
-
if (typeof id === "string") {
|
|
773
|
+
if (id === null || id === undefined || id === '') continue;
|
|
774
|
+
if (typeof id === 'number' && Number.isFinite(id)) return id;
|
|
775
|
+
if (typeof id === 'string') {
|
|
706
776
|
const trimmed = id.trim();
|
|
707
|
-
if (!trimmed)
|
|
708
|
-
continue;
|
|
777
|
+
if (!trimmed) continue;
|
|
709
778
|
return trimmed;
|
|
710
779
|
}
|
|
711
780
|
}
|
|
712
|
-
return
|
|
713
|
-
}
|
|
714
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
715
|
-
0 && (module.exports = {
|
|
716
|
-
aggregateItemRuleLimit,
|
|
717
|
-
attachItemRuleLimitsToTopLevelProducts,
|
|
718
|
-
buildItemRuleBusinessData,
|
|
719
|
-
buildProductKey,
|
|
720
|
-
buildProductLineFingerprint,
|
|
721
|
-
buildQuantityLimitIndex,
|
|
722
|
-
collectLinkProductIdsFromReservationRules,
|
|
723
|
-
computeResourceIsFull,
|
|
724
|
-
createEmptySummary,
|
|
725
|
-
extractStrategyModelIdsFromTableConfig,
|
|
726
|
-
filterProductsForScanOrderMore,
|
|
727
|
-
findReservationRuleProductByResourceId,
|
|
728
|
-
getProductIdentityIndex,
|
|
729
|
-
getSafeProductNum,
|
|
730
|
-
getTopLevelProductId,
|
|
731
|
-
getTopLevelVariantId,
|
|
732
|
-
hasCustomCapacityProduct,
|
|
733
|
-
isIdentityMatch,
|
|
734
|
-
isSkuOnlyDeleteIdentity,
|
|
735
|
-
normalizeEnabledItemRuleIds,
|
|
736
|
-
normalizeItemRuleStrategies,
|
|
737
|
-
normalizeOrderProduct,
|
|
738
|
-
pickFirstCustomCapacityDimensionId,
|
|
739
|
-
pickFirstCustomCapacityPaxBounds,
|
|
740
|
-
pickFirstDurationMinutesFromProducts,
|
|
741
|
-
resolveSkuMatchedQuantityLimits,
|
|
742
|
-
toBoolean,
|
|
743
|
-
toNonNegativeInt,
|
|
744
|
-
toNonNegativeNumber,
|
|
745
|
-
toPositiveString,
|
|
746
|
-
toPriceString
|
|
747
|
-
});
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|