@pisell/pisellos 2.3.73 → 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.d.ts +7 -0
- package/dist/core/index.js +111 -71
- 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.d.ts +1 -1
- package/dist/modules/Order/index.js +36 -64
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/Quotation/index.d.ts +6 -0
- package/dist/modules/Quotation/index.js +75 -19
- 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.d.ts +1 -0
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.d.ts +8 -2
- package/dist/server/index.js +303 -269
- package/dist/server/modules/floor-plan/index.d.ts +4 -0
- package/dist/server/modules/floor-plan/index.js +240 -98
- package/dist/server/modules/menu/index.js +75 -59
- package/dist/server/modules/order/index.d.ts +17 -7
- package/dist/server/modules/order/index.js +421 -211
- package/dist/server/modules/products/index.d.ts +8 -2
- package/dist/server/modules/products/index.js +191 -117
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +24 -17
- 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.d.ts +1 -1
- 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.d.ts +7 -0
- package/lib/core/index.js +149 -131
- 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.d.ts +1 -1
- 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.d.ts +6 -0
- package/lib/modules/Quotation/index.js +110 -103
- 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.d.ts +1 -0
- 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.d.ts +8 -2
- package/lib/server/index.js +3083 -2663
- package/lib/server/modules/floor-plan/index.d.ts +4 -0
- package/lib/server/modules/floor-plan/index.js +159 -127
- 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 +170 -124
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.d.ts +17 -7
- package/lib/server/modules/order/index.js +916 -977
- 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.d.ts +8 -2
- package/lib/server/modules/products/index.js +656 -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 +225 -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.d.ts +1 -1
- 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,93 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
buildPlannerDiscountResult: true,
|
|
8
|
+
normalizeProductForAvailability: true,
|
|
9
|
+
buildAvailabilityResourcesFromV2: true,
|
|
10
|
+
collectAvailabilityResourceIds: true,
|
|
11
|
+
buildAvailabilityLineIdentity: true,
|
|
12
|
+
buildBookingFromAvailabilityAssignment: true,
|
|
13
|
+
buildBookingFromAvailabilityAssignments: true,
|
|
14
|
+
UnifiedBookingSalesImpl: true,
|
|
15
|
+
UnifiedBookingSales: true,
|
|
16
|
+
AvailabilityError: true
|
|
10
17
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
Object.defineProperty(exports, "AvailabilityError", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _ResourcePlanner.AvailabilityError;
|
|
16
22
|
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/solution/UnifiedBookingSales/index.ts
|
|
31
|
-
var UnifiedBookingSales_exports = {};
|
|
32
|
-
__export(UnifiedBookingSales_exports, {
|
|
33
|
-
AvailabilityError: () => import_ResourcePlanner2.AvailabilityError,
|
|
34
|
-
UnifiedBookingSales: () => UnifiedBookingSales,
|
|
35
|
-
UnifiedBookingSalesImpl: () => UnifiedBookingSalesImpl,
|
|
36
|
-
buildAvailabilityLineIdentity: () => buildAvailabilityLineIdentity,
|
|
37
|
-
buildAvailabilityResourcesFromV2: () => buildAvailabilityResourcesFromV2,
|
|
38
|
-
buildBookingFromAvailabilityAssignment: () => buildBookingFromAvailabilityAssignment,
|
|
39
|
-
buildBookingFromAvailabilityAssignments: () => buildBookingFromAvailabilityAssignments,
|
|
40
|
-
buildPlannerDiscountResult: () => buildPlannerDiscountResult,
|
|
41
|
-
collectAvailabilityResourceIds: () => collectAvailabilityResourceIds,
|
|
42
|
-
normalizeProductForAvailability: () => normalizeProductForAvailability
|
|
43
23
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
var
|
|
56
|
-
var
|
|
57
|
-
var
|
|
24
|
+
exports.UnifiedBookingSalesImpl = exports.UnifiedBookingSales = void 0;
|
|
25
|
+
exports.buildAvailabilityLineIdentity = buildAvailabilityLineIdentity;
|
|
26
|
+
exports.buildAvailabilityResourcesFromV2 = buildAvailabilityResourcesFromV2;
|
|
27
|
+
exports.buildBookingFromAvailabilityAssignment = buildBookingFromAvailabilityAssignment;
|
|
28
|
+
exports.buildBookingFromAvailabilityAssignments = buildBookingFromAvailabilityAssignments;
|
|
29
|
+
exports.buildPlannerDiscountResult = buildPlannerDiscountResult;
|
|
30
|
+
exports.collectAvailabilityResourceIds = collectAvailabilityResourceIds;
|
|
31
|
+
exports.normalizeProductForAvailability = normalizeProductForAvailability;
|
|
32
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
33
|
+
var _lodashEs = require("lodash-es");
|
|
34
|
+
var _modules = require("../../modules");
|
|
35
|
+
var _getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
|
|
36
|
+
var _ResourcePlanner = require("../../modules/ResourcePlanner");
|
|
37
|
+
var _types = require("./types");
|
|
38
|
+
Object.keys(_types).forEach(function (key) {
|
|
39
|
+
if (key === "default" || key === "__esModule") return;
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
41
|
+
if (key in exports && exports[key] === _types[key]) return;
|
|
42
|
+
Object.defineProperty(exports, key, {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () {
|
|
45
|
+
return _types[key];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
var _remoteOccupancyCache = require("./remoteOccupancyCache");
|
|
50
|
+
var _BookingTicket = require("../BookingTicket");
|
|
51
|
+
var _localClock = require("../../modules/ResourcePlanner/localClock");
|
|
52
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
53
|
+
const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'basic', 'checkout', 'availability'];
|
|
54
|
+
const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
|
|
58
55
|
function normalizePositiveInteger(value) {
|
|
59
56
|
const normalized = Number(value);
|
|
60
|
-
if (!Number.isInteger(normalized) || normalized <= 0)
|
|
61
|
-
return void 0;
|
|
57
|
+
if (!Number.isInteger(normalized) || normalized <= 0) return undefined;
|
|
62
58
|
return normalized;
|
|
63
59
|
}
|
|
64
60
|
function isRecord(value) {
|
|
65
|
-
return !!value && typeof value ===
|
|
61
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
66
62
|
}
|
|
67
63
|
function pickLocalizedText(value) {
|
|
68
|
-
if (!value)
|
|
69
|
-
|
|
70
|
-
if (typeof value ===
|
|
71
|
-
return value;
|
|
72
|
-
if (typeof value === "object") {
|
|
64
|
+
if (!value) return undefined;
|
|
65
|
+
if (typeof value === 'string') return value;
|
|
66
|
+
if (typeof value === 'object') {
|
|
73
67
|
return value.en || value.zh || value.default || Object.values(value)[0];
|
|
74
68
|
}
|
|
75
69
|
return String(value);
|
|
76
70
|
}
|
|
77
71
|
function resolvePlannerDiscountId(discount) {
|
|
78
|
-
|
|
79
|
-
return ((_a = discount.discount) == null ? void 0 : _a.resource_id) ?? discount.resource_id ?? discount.id;
|
|
72
|
+
return discount.discount?.resource_id ?? discount.resource_id ?? discount.id;
|
|
80
73
|
}
|
|
81
74
|
function resolvePlannerDiscountTitle(discount) {
|
|
82
|
-
|
|
83
|
-
return pickLocalizedText(
|
|
84
|
-
discount.name || discount.format_title || ((_a = discount.discount) == null ? void 0 : _a.title) || discount.title
|
|
85
|
-
);
|
|
75
|
+
return pickLocalizedText(discount.name || discount.format_title || discount.discount?.title || discount.title);
|
|
86
76
|
}
|
|
87
77
|
function isPlannerDiscountAvailable(discount) {
|
|
88
|
-
|
|
89
|
-
return discount.isDisabled !== true && discount.isAvailable !== false && ((_a = discount.config) == null ? void 0 : _a.isAvailable) !== false;
|
|
78
|
+
return discount.isDisabled !== true && discount.isAvailable !== false && discount.config?.isAvailable !== false;
|
|
90
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Converts Rules/Order discount internals into a small UI-facing diagnostic. The result keeps
|
|
83
|
+
* candidate-card state separate from line-level applications: a selected card is not proof that
|
|
84
|
+
* it actually discounted a planner product.
|
|
85
|
+
*/
|
|
91
86
|
function buildPlannerDiscountResult(params) {
|
|
92
87
|
const {
|
|
93
88
|
customerId,
|
|
@@ -101,39 +96,34 @@ function buildPlannerDiscountResult(params) {
|
|
|
101
96
|
} = params;
|
|
102
97
|
const items = (discountList || []).reduce((result, discount) => {
|
|
103
98
|
const id = resolvePlannerDiscountId(discount);
|
|
104
|
-
if (id !==
|
|
99
|
+
if (id !== undefined && id !== null) {
|
|
105
100
|
result.push({
|
|
106
101
|
id,
|
|
107
102
|
name: resolvePlannerDiscountTitle(discount),
|
|
108
103
|
type: discount.tag || discount.type,
|
|
109
104
|
available: isPlannerDiscountAvailable(discount),
|
|
110
105
|
selected: discount.isSelected === true,
|
|
111
|
-
savedAmount: Number(discount.savedAmount || 0) ||
|
|
106
|
+
savedAmount: Number(discount.savedAmount || 0) || undefined,
|
|
112
107
|
unavailableReason: discount.unavailableReason || discount.reason
|
|
113
108
|
});
|
|
114
109
|
}
|
|
115
110
|
return result;
|
|
116
111
|
}, []);
|
|
117
|
-
const discountNameById = new Map(items.map(
|
|
112
|
+
const discountNameById = new Map(items.map(item => [String(item.id), item.name]));
|
|
118
113
|
const applications = [];
|
|
119
|
-
(products || []).forEach(
|
|
120
|
-
const productDiscounts = [
|
|
121
|
-
|
|
122
|
-
...Array.isArray(product.product_bundle) ? product.product_bundle.flatMap((bundle) => (bundle == null ? void 0 : bundle.discount_list) || []) : []
|
|
123
|
-
];
|
|
124
|
-
productDiscounts.forEach((discount) => {
|
|
125
|
-
var _a;
|
|
114
|
+
(products || []).forEach(product => {
|
|
115
|
+
const productDiscounts = [...(Array.isArray(product.discount_list) ? product.discount_list : []), ...(Array.isArray(product.product_bundle) ? product.product_bundle.flatMap(bundle => bundle?.discount_list || []) : [])];
|
|
116
|
+
productDiscounts.forEach(discount => {
|
|
126
117
|
const type = discount.tag || discount.type;
|
|
127
|
-
if (![
|
|
128
|
-
return;
|
|
118
|
+
if (!['good_pass', 'discount_card', 'product_discount_card'].includes(type)) return;
|
|
129
119
|
const discountId = resolvePlannerDiscountId(discount);
|
|
130
120
|
applications.push({
|
|
131
121
|
discountId,
|
|
132
|
-
discountName: discountId ===
|
|
122
|
+
discountName: discountId === undefined ? undefined : discountNameById.get(String(discountId)),
|
|
133
123
|
discountType: type,
|
|
134
124
|
productId: product.product_id ?? product.id,
|
|
135
125
|
productTitle: pickLocalizedText(product.title || product.name || product.product_title),
|
|
136
|
-
productUid:
|
|
126
|
+
productUid: product.metadata?.unique_identification_number || product.unique_identification_number,
|
|
137
127
|
amount: Number(discount.amount || 0)
|
|
138
128
|
});
|
|
139
129
|
});
|
|
@@ -142,17 +132,16 @@ function buildPlannerDiscountResult(params) {
|
|
|
142
132
|
status,
|
|
143
133
|
customerId,
|
|
144
134
|
preferredDiscountFound,
|
|
145
|
-
preferredDiscountApplied: preferredDiscountId ===
|
|
146
|
-
availableDiscounts: items.filter(
|
|
147
|
-
selectedDiscounts: items.filter(
|
|
135
|
+
preferredDiscountApplied: preferredDiscountId === undefined || preferredDiscountId === null ? undefined : items.some(item => String(item.id) === String(preferredDiscountId) && item.selected) && applications.some(item => String(item.discountId) === String(preferredDiscountId)),
|
|
136
|
+
availableDiscounts: items.filter(item => item.available),
|
|
137
|
+
selectedDiscounts: items.filter(item => item.selected),
|
|
148
138
|
applications,
|
|
149
139
|
summaryBefore,
|
|
150
140
|
summaryAfter
|
|
151
141
|
};
|
|
152
142
|
}
|
|
153
143
|
function toNumberId(value) {
|
|
154
|
-
if (value ===
|
|
155
|
-
return void 0;
|
|
144
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
156
145
|
const parsed = Number(value);
|
|
157
146
|
return Number.isFinite(parsed) ? parsed : String(value);
|
|
158
147
|
}
|
|
@@ -161,51 +150,45 @@ function toCustomerId(value) {
|
|
|
161
150
|
return Number.isFinite(parsed) && parsed > 1 ? parsed : null;
|
|
162
151
|
}
|
|
163
152
|
function resolveTempOrderCustomerId(tempOrder) {
|
|
164
|
-
|
|
165
|
-
return toCustomerId(
|
|
166
|
-
(tempOrder == null ? void 0 : tempOrder.customer_id) || ((_a = tempOrder == null ? void 0 : tempOrder.customer) == null ? void 0 : _a.id) || ((_c = (_b = tempOrder == null ? void 0 : tempOrder._extend) == null ? void 0 : _b.customerSnapshot) == null ? void 0 : _c.id) || ((_e = (_d = tempOrder == null ? void 0 : tempOrder._extend) == null ? void 0 : _d.customerSnapshot) == null ? void 0 : _e.customer_id)
|
|
167
|
-
);
|
|
153
|
+
return toCustomerId(tempOrder?.customer_id || tempOrder?.customer?.id || tempOrder?._extend?.customerSnapshot?.id || tempOrder?._extend?.customerSnapshot?.customer_id);
|
|
168
154
|
}
|
|
169
155
|
function isLocalOrderId(orderId) {
|
|
170
|
-
return typeof orderId ===
|
|
156
|
+
return typeof orderId === 'string' && orderId.startsWith('local_order');
|
|
171
157
|
}
|
|
172
158
|
function isCustomerBoundDiscount(discount) {
|
|
173
|
-
const type =
|
|
174
|
-
return type ===
|
|
159
|
+
const type = discount?.type ?? discount?.tag;
|
|
160
|
+
return type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
|
|
175
161
|
}
|
|
176
162
|
function filterCustomerBoundDiscounts(discountList) {
|
|
177
|
-
if (!Array.isArray(discountList))
|
|
178
|
-
|
|
179
|
-
return discountList.filter((discount) => !isCustomerBoundDiscount(discount));
|
|
163
|
+
if (!Array.isArray(discountList)) return [];
|
|
164
|
+
return discountList.filter(discount => !isCustomerBoundDiscount(discount));
|
|
180
165
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
startTime:
|
|
184
|
-
endTime:
|
|
166
|
+
const DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES = 30;
|
|
167
|
+
const DEFAULT_AVAILABILITY_BUSINESS_HOURS = {
|
|
168
|
+
startTime: '09:00',
|
|
169
|
+
endTime: '18:00'
|
|
185
170
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
171
|
+
const MAX_AVAILABILITY_RANGE_DAYS = 90;
|
|
172
|
+
const MAX_AVAILABILITY_CONTEXT_REGISTRY = 128;
|
|
173
|
+
const MAX_AVAILABILITY_PROJECTION_POOL = 16;
|
|
174
|
+
const MAX_REMOTE_OCCUPANCY_RESOURCE_DATE_CELLS = 4096;
|
|
190
175
|
function isValidClockTime(value) {
|
|
191
176
|
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
|
192
|
-
if (!match)
|
|
193
|
-
return false;
|
|
177
|
+
if (!match) return false;
|
|
194
178
|
return Number(match[1]) < 24 && Number(match[2]) < 60;
|
|
195
179
|
}
|
|
196
180
|
function assertValidAvailabilityTimezone(timezoneName) {
|
|
197
|
-
if (!String(timezoneName ||
|
|
198
|
-
throw new
|
|
181
|
+
if (!String(timezoneName || '').trim()) {
|
|
182
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'timezone label 不能为空', {
|
|
199
183
|
timezone: timezoneName
|
|
200
184
|
});
|
|
201
185
|
}
|
|
202
186
|
}
|
|
203
187
|
function normalizeAvailabilityLocalDateTime(value, fixedOffsetMinutes) {
|
|
204
|
-
if (value == null || value ===
|
|
205
|
-
return null;
|
|
188
|
+
if (value == null || value === '') return null;
|
|
206
189
|
try {
|
|
207
|
-
const localText = typeof
|
|
208
|
-
return (0,
|
|
190
|
+
const localText = typeof value?.format === 'function' ? value.format('YYYY-MM-DDTHH:mm:ss') : String(value);
|
|
191
|
+
return (0, _localClock.formatLocalDateTime)((0, _localClock.parseLocalDateTime)(localText), fixedOffsetMinutes);
|
|
209
192
|
} catch {
|
|
210
193
|
return null;
|
|
211
194
|
}
|
|
@@ -213,46 +196,42 @@ function normalizeAvailabilityLocalDateTime(value, fixedOffsetMinutes) {
|
|
|
213
196
|
function normalizeAvailabilityLocalDateTimeRange(params) {
|
|
214
197
|
const startText = normalizeAvailabilityLocalDateTime(params.startAt, params.fixedOffsetMinutes);
|
|
215
198
|
const endText = normalizeAvailabilityLocalDateTime(params.endAt, params.fixedOffsetMinutes);
|
|
216
|
-
if (!startText || !endText)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
let endScalar = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(endText));
|
|
199
|
+
if (!startText || !endText) return null;
|
|
200
|
+
const startScalar = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(startText));
|
|
201
|
+
let endScalar = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(endText));
|
|
220
202
|
if (endScalar <= startScalar && params.rollOverEnd) {
|
|
221
|
-
endScalar = (0,
|
|
203
|
+
endScalar = (0, _localClock.addLocalCalendarDays)(endScalar, 1);
|
|
222
204
|
}
|
|
223
|
-
if (endScalar <= startScalar)
|
|
224
|
-
return null;
|
|
205
|
+
if (endScalar <= startScalar) return null;
|
|
225
206
|
return {
|
|
226
207
|
startAt: startText,
|
|
227
|
-
endAt: (0,
|
|
208
|
+
endAt: (0, _localClock.formatLocalDateTime)(endScalar, params.fixedOffsetMinutes),
|
|
228
209
|
startScalar,
|
|
229
210
|
endScalar
|
|
230
211
|
};
|
|
231
212
|
}
|
|
232
213
|
function sameAvailabilityId(left, right) {
|
|
233
|
-
return left !==
|
|
214
|
+
return left !== undefined && left !== null && right !== undefined && right !== null && String(left) === String(right);
|
|
234
215
|
}
|
|
235
216
|
function uniqueAvailabilityIds(values) {
|
|
236
|
-
const result =
|
|
237
|
-
values.forEach(
|
|
217
|
+
const result = new Map();
|
|
218
|
+
values.forEach(value => {
|
|
238
219
|
const id = toNumberId(value);
|
|
239
|
-
if (id !==
|
|
240
|
-
result.set(String(id), id);
|
|
220
|
+
if (id !== undefined) result.set(String(id), id);
|
|
241
221
|
});
|
|
242
222
|
return Array.from(result.values());
|
|
243
223
|
}
|
|
244
224
|
function assertAvailabilityCatalogProductIds(productIds, context) {
|
|
245
|
-
if (productIds == null)
|
|
246
|
-
return;
|
|
225
|
+
if (productIds == null) return;
|
|
247
226
|
if (!Array.isArray(productIds)) {
|
|
248
|
-
throw new
|
|
227
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', `${context} 必须是 number[]`, {
|
|
249
228
|
context,
|
|
250
229
|
productIds
|
|
251
230
|
});
|
|
252
231
|
}
|
|
253
|
-
const invalidIds = productIds.filter(
|
|
232
|
+
const invalidIds = productIds.filter(id => typeof id !== 'number' || !Number.isFinite(id));
|
|
254
233
|
if (invalidIds.length > 0) {
|
|
255
|
-
throw new
|
|
234
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', `${context} 只能包含有限数值商品 ID`, {
|
|
256
235
|
context,
|
|
257
236
|
invalidIds,
|
|
258
237
|
productIds
|
|
@@ -260,74 +239,63 @@ function assertAvailabilityCatalogProductIds(productIds, context) {
|
|
|
260
239
|
}
|
|
261
240
|
}
|
|
262
241
|
function normalizeResourceType(_value) {
|
|
263
|
-
|
|
242
|
+
// 当前业务只支持每个资源组选择一个具体资源。
|
|
243
|
+
// 保留公共联合类型以兼容已有调用方,但暂不启用 multiple/capacity 分支。
|
|
244
|
+
return 'single';
|
|
264
245
|
}
|
|
265
246
|
function resolveProductKind(product) {
|
|
266
|
-
|
|
267
|
-
if (product.
|
|
268
|
-
|
|
269
|
-
if (product.
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
const scheduleIds = product["schedule.ids"] || product.schedule_ids;
|
|
277
|
-
if (Array.isArray(scheduleIds) && scheduleIds.length > 0)
|
|
278
|
-
return "session";
|
|
279
|
-
return "normal";
|
|
247
|
+
if (product.duration || product.service_duration) return 'duration';
|
|
248
|
+
if (product.extension_type === 'venue_slot') return 'venue_slot';
|
|
249
|
+
if (['session_product', 'session_ticket'].includes(product.extension_type)) return 'session';
|
|
250
|
+
if (product.cartDetailValue?.session || product._extend?.other?.session || product.session) {
|
|
251
|
+
return 'session';
|
|
252
|
+
}
|
|
253
|
+
const scheduleIds = product['schedule.ids'] || product.schedule_ids;
|
|
254
|
+
if (Array.isArray(scheduleIds) && scheduleIds.length > 0) return 'session';
|
|
255
|
+
return 'normal';
|
|
280
256
|
}
|
|
281
257
|
function expandScheduleDateRanges(values) {
|
|
282
|
-
if (!Array.isArray(values))
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (typeof value === "string") {
|
|
258
|
+
if (!Array.isArray(values)) return [];
|
|
259
|
+
const dates = new Set();
|
|
260
|
+
values.forEach(value => {
|
|
261
|
+
if (typeof value === 'string') {
|
|
287
262
|
try {
|
|
288
|
-
dates.add((0,
|
|
263
|
+
dates.add((0, _localClock.formatLocalDate)((0, _localClock.parseLocalDate)(value.slice(0, 10))));
|
|
289
264
|
} catch {
|
|
265
|
+
// Ignore invalid configuration entries.
|
|
290
266
|
}
|
|
291
267
|
return;
|
|
292
268
|
}
|
|
293
|
-
const startValue =
|
|
294
|
-
const endValue =
|
|
269
|
+
const startValue = value?.start || value?.start_date || value?.date;
|
|
270
|
+
const endValue = value?.end || value?.end_date || startValue;
|
|
295
271
|
let start;
|
|
296
272
|
let end;
|
|
297
273
|
try {
|
|
298
|
-
start = (0,
|
|
299
|
-
end = (0,
|
|
274
|
+
start = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(String(startValue).slice(0, 10)));
|
|
275
|
+
end = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(String(endValue).slice(0, 10)));
|
|
300
276
|
} catch {
|
|
301
277
|
return;
|
|
302
278
|
}
|
|
303
|
-
if (end < start)
|
|
304
|
-
return;
|
|
279
|
+
if (end < start) return;
|
|
305
280
|
let cursor = start;
|
|
306
281
|
let guard = 0;
|
|
307
282
|
while (cursor <= end && guard < MAX_AVAILABILITY_RANGE_DAYS) {
|
|
308
|
-
dates.add((0,
|
|
309
|
-
cursor = (0,
|
|
283
|
+
dates.add((0, _localClock.formatLocalDate)(cursor));
|
|
284
|
+
cursor = (0, _localClock.addLocalCalendarDays)(cursor, 1);
|
|
310
285
|
guard += 1;
|
|
311
286
|
}
|
|
312
287
|
});
|
|
313
288
|
return Array.from(dates).sort();
|
|
314
289
|
}
|
|
315
290
|
function normalizeRequirementGroups(product) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return [];
|
|
320
|
-
return resources.filter((resource) => (resource == null ? void 0 : resource.status) !== 0).map((resource, index) => {
|
|
321
|
-
var _a2, _b, _c;
|
|
291
|
+
const resources = product.product_resource?.resources;
|
|
292
|
+
if (!Array.isArray(resources)) return [];
|
|
293
|
+
return resources.filter(resource => resource?.status !== 0).map((resource, index) => {
|
|
322
294
|
const formId = toNumberId(resource.id ?? resource.form_id);
|
|
323
295
|
const required = resource.required ?? resource.status === 1;
|
|
324
296
|
const resourceType = normalizeResourceType(resource.type);
|
|
325
|
-
const combinedIds =
|
|
326
|
-
const resourceIds = uniqueAvailabilityIds([
|
|
327
|
-
...Array.isArray(resource.default_resource) ? resource.default_resource : [],
|
|
328
|
-
...Array.isArray(resource.optional_resource) ? resource.optional_resource : [],
|
|
329
|
-
...Array.isArray(combinedIds) ? combinedIds : []
|
|
330
|
-
]);
|
|
297
|
+
const combinedIds = resource.combined_resource?.status === 1 ? resource.combined_resource?.resource_ids : [];
|
|
298
|
+
const resourceIds = uniqueAvailabilityIds([...(Array.isArray(resource.default_resource) ? resource.default_resource : []), ...(Array.isArray(resource.optional_resource) ? resource.optional_resource : []), ...(Array.isArray(combinedIds) ? combinedIds : [])]);
|
|
331
299
|
const min = required ? 1 : 0;
|
|
332
300
|
const max = 1;
|
|
333
301
|
return {
|
|
@@ -340,30 +308,25 @@ function normalizeRequirementGroups(product) {
|
|
|
340
308
|
min,
|
|
341
309
|
max,
|
|
342
310
|
resourceIds,
|
|
343
|
-
capacityRequired: Math.max(1, Number(
|
|
344
|
-
resource.capacity_required ?? ((_c = product.capacity) == null ? void 0 : _c.default) ?? product.capacity ?? 1
|
|
345
|
-
) || 1),
|
|
311
|
+
capacityRequired: Math.max(1, Number(resource.capacity_required ?? product.capacity?.default ?? product.capacity ?? 1) || 1),
|
|
346
312
|
raw: resource
|
|
347
313
|
};
|
|
348
314
|
});
|
|
349
315
|
}
|
|
350
316
|
function normalizeScheduleRepeatType(value) {
|
|
351
|
-
if (value ===
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return "weekly";
|
|
355
|
-
return "none";
|
|
317
|
+
if (value === 'daily' || value === 1 || value === '1') return 'daily';
|
|
318
|
+
if (value === 'weekly' || value === 2 || value === '2') return 'weekly';
|
|
319
|
+
return 'none';
|
|
356
320
|
}
|
|
357
321
|
function normalizeAbsoluteSessionRange(params) {
|
|
358
|
-
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0,
|
|
322
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
359
323
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
360
324
|
startAt: params.startAt,
|
|
361
325
|
endAt: params.endAt,
|
|
362
326
|
fixedOffsetMinutes,
|
|
363
327
|
rollOverEnd: true
|
|
364
328
|
});
|
|
365
|
-
if (!range)
|
|
366
|
-
return null;
|
|
329
|
+
if (!range) return null;
|
|
367
330
|
return {
|
|
368
331
|
startAt: range.startAt,
|
|
369
332
|
endAt: range.endAt,
|
|
@@ -375,7 +338,6 @@ function normalizeAbsoluteSessionRange(params) {
|
|
|
375
338
|
};
|
|
376
339
|
}
|
|
377
340
|
function normalizeScheduleItem(schedule, fixedOffsetMinutes) {
|
|
378
|
-
var _a, _b;
|
|
379
341
|
const rules = [];
|
|
380
342
|
const ranges = [];
|
|
381
343
|
const scheduleId = toNumberId(schedule.id ?? schedule.schedule_id);
|
|
@@ -383,61 +345,61 @@ function normalizeScheduleItem(schedule, fixedOffsetMinutes) {
|
|
|
383
345
|
const repeatRule = schedule.repeat_rule || {};
|
|
384
346
|
const startAt = schedule.start_at || schedule.start || schedule.start_time;
|
|
385
347
|
const endAt = schedule.end_at || schedule.end || schedule.end_time;
|
|
386
|
-
const startDate = startAt ? String(startAt).slice(0, 10) :
|
|
387
|
-
const configuredEndDate =
|
|
388
|
-
const endDate = configuredEndDate ? String(configuredEndDate).slice(0, 10) :
|
|
348
|
+
const startDate = startAt ? String(startAt).slice(0, 10) : undefined;
|
|
349
|
+
const configuredEndDate = repeatRule.end?.type === 'date' ? repeatRule.end?.end_date : undefined;
|
|
350
|
+
const endDate = configuredEndDate ? String(configuredEndDate).slice(0, 10) : undefined;
|
|
389
351
|
const includedDates = expandScheduleDateRanges(repeatRule.included_date);
|
|
390
352
|
const excludedDates = expandScheduleDateRanges(repeatRule.excluded_date);
|
|
391
|
-
const addRule =
|
|
392
|
-
const mode = repeatType ===
|
|
353
|
+
const addRule = params => {
|
|
354
|
+
const mode = repeatType === 'weekly' ? 'weekly' : 'daily';
|
|
393
355
|
rules.push({
|
|
394
|
-
id: `${String(scheduleId ??
|
|
356
|
+
id: `${String(scheduleId ?? 'schedule')}:${params.idSuffix}`,
|
|
395
357
|
mode,
|
|
396
358
|
startTime: params.startTime.slice(0, 5),
|
|
397
359
|
endTime: params.endTime.slice(0, 5),
|
|
398
360
|
startDate,
|
|
399
361
|
endDate,
|
|
400
362
|
frequency: Math.max(1, Number(repeatRule.frequency || 1)),
|
|
401
|
-
weekdays: repeatType ===
|
|
363
|
+
weekdays: repeatType === 'weekly' ? (repeatRule.frequency_date || []).map(Number).filter(Number.isFinite) : undefined,
|
|
402
364
|
includeDates: includedDates,
|
|
403
365
|
excludeDates: excludedDates,
|
|
404
366
|
scheduleRef: {
|
|
405
367
|
scheduleId,
|
|
406
368
|
timeSlotId: params.timeSlotId,
|
|
407
|
-
source: `schedule_${schedule.type ||
|
|
369
|
+
source: `schedule_${schedule.type || 'standard'}`
|
|
408
370
|
}
|
|
409
371
|
});
|
|
410
372
|
};
|
|
411
|
-
if (schedule.type ===
|
|
373
|
+
if (schedule.type === 'designation') {
|
|
412
374
|
const designations = Array.isArray(schedule.designation) ? schedule.designation : schedule.designation ? [schedule.designation] : [];
|
|
413
|
-
designations.forEach(
|
|
375
|
+
designations.forEach(designation => {
|
|
414
376
|
const range = normalizeAbsoluteSessionRange({
|
|
415
377
|
startAt: `${designation.start_date} ${designation.start_time}`,
|
|
416
378
|
endAt: `${designation.end_date || designation.start_date} ${designation.end_time}`,
|
|
417
379
|
scheduleId,
|
|
418
|
-
source:
|
|
380
|
+
source: 'schedule_designation',
|
|
419
381
|
fixedOffsetMinutes
|
|
420
382
|
});
|
|
421
|
-
if (range)
|
|
422
|
-
ranges.push(range);
|
|
383
|
+
if (range) ranges.push(range);
|
|
423
384
|
});
|
|
424
|
-
return {
|
|
385
|
+
return {
|
|
386
|
+
rules,
|
|
387
|
+
ranges
|
|
388
|
+
};
|
|
425
389
|
}
|
|
426
|
-
if (schedule.type ===
|
|
390
|
+
if (schedule.type === 'time-slots' && Array.isArray(schedule.time_slot)) {
|
|
427
391
|
schedule.time_slot.forEach((slot, index) => {
|
|
428
|
-
if (!slot.start_time || !slot.end_time)
|
|
429
|
-
|
|
430
|
-
if (repeatType === "none" && startDate) {
|
|
392
|
+
if (!slot.start_time || !slot.end_time) return;
|
|
393
|
+
if (repeatType === 'none' && startDate) {
|
|
431
394
|
const range = normalizeAbsoluteSessionRange({
|
|
432
395
|
startAt: `${startDate} ${slot.start_time}`,
|
|
433
396
|
endAt: `${startDate} ${slot.end_time}`,
|
|
434
397
|
scheduleId,
|
|
435
398
|
timeSlotId: toNumberId(slot.id),
|
|
436
|
-
source:
|
|
399
|
+
source: 'schedule_time_slot',
|
|
437
400
|
fixedOffsetMinutes
|
|
438
401
|
});
|
|
439
|
-
if (range)
|
|
440
|
-
ranges.push(range);
|
|
402
|
+
if (range) ranges.push(range);
|
|
441
403
|
return;
|
|
442
404
|
}
|
|
443
405
|
addRule({
|
|
@@ -447,69 +409,70 @@ function normalizeScheduleItem(schedule, fixedOffsetMinutes) {
|
|
|
447
409
|
timeSlotId: toNumberId(slot.id)
|
|
448
410
|
});
|
|
449
411
|
});
|
|
450
|
-
return {
|
|
412
|
+
return {
|
|
413
|
+
rules,
|
|
414
|
+
ranges
|
|
415
|
+
};
|
|
451
416
|
}
|
|
452
417
|
if (startAt && endAt) {
|
|
453
|
-
if (repeatType ===
|
|
418
|
+
if (repeatType === 'none') {
|
|
454
419
|
const range = normalizeAbsoluteSessionRange({
|
|
455
420
|
startAt,
|
|
456
421
|
endAt,
|
|
457
422
|
scheduleId,
|
|
458
|
-
source:
|
|
423
|
+
source: 'schedule_standard',
|
|
459
424
|
fixedOffsetMinutes
|
|
460
425
|
});
|
|
461
|
-
if (range)
|
|
462
|
-
ranges.push(range);
|
|
426
|
+
if (range) ranges.push(range);
|
|
463
427
|
} else {
|
|
464
428
|
try {
|
|
465
429
|
addRule({
|
|
466
|
-
idSuffix:
|
|
467
|
-
startTime: (0,
|
|
468
|
-
endTime: (0,
|
|
430
|
+
idSuffix: 'standard',
|
|
431
|
+
startTime: (0, _localClock.formatLocalTime)(String(startAt), false),
|
|
432
|
+
endTime: (0, _localClock.formatLocalTime)(String(endAt), false)
|
|
469
433
|
});
|
|
470
434
|
} catch {
|
|
435
|
+
// Invalid schedule boundaries do not produce a recurring rule.
|
|
471
436
|
}
|
|
472
437
|
}
|
|
473
438
|
}
|
|
474
|
-
return {
|
|
439
|
+
return {
|
|
440
|
+
rules,
|
|
441
|
+
ranges
|
|
442
|
+
};
|
|
475
443
|
}
|
|
476
444
|
function resolveProductScheduleItems(product, scheduleList) {
|
|
477
|
-
const scheduleMap = new Map(scheduleList.map(
|
|
478
|
-
const rawSchedules = [product.schedules, product.schedule, product._schedule].flatMap(
|
|
479
|
-
const explicitIds = uniqueAvailabilityIds([
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
...rawSchedules.filter((item) => typeof item !== "object")
|
|
483
|
-
]);
|
|
484
|
-
const result = /* @__PURE__ */ new Map();
|
|
485
|
-
rawSchedules.filter((item) => item && typeof item === "object").forEach((schedule) => {
|
|
445
|
+
const scheduleMap = new Map(scheduleList.map(schedule => [String(schedule.id), schedule]));
|
|
446
|
+
const rawSchedules = [product.schedules, product.schedule, product._schedule].flatMap(value => Array.isArray(value) ? value : value ? [value] : []);
|
|
447
|
+
const explicitIds = uniqueAvailabilityIds([...(Array.isArray(product['schedule.ids']) ? product['schedule.ids'] : []), ...(Array.isArray(product.schedule_ids) ? product.schedule_ids : []), ...rawSchedules.filter(item => typeof item !== 'object')]);
|
|
448
|
+
const result = new Map();
|
|
449
|
+
rawSchedules.filter(item => item && typeof item === 'object').forEach(schedule => {
|
|
486
450
|
result.set(String(schedule.id ?? JSON.stringify(schedule)), schedule);
|
|
487
451
|
});
|
|
488
|
-
explicitIds.forEach(
|
|
452
|
+
explicitIds.forEach(id => {
|
|
489
453
|
const schedule = scheduleMap.get(String(id));
|
|
490
|
-
if (schedule)
|
|
491
|
-
result.set(String(id), schedule);
|
|
454
|
+
if (schedule) result.set(String(id), schedule);
|
|
492
455
|
});
|
|
493
456
|
return Array.from(result.values());
|
|
494
457
|
}
|
|
495
|
-
|
|
496
|
-
|
|
458
|
+
|
|
459
|
+
/** Converts ProductList and schedule records into the pure Availability vocabulary. */
|
|
460
|
+
function normalizeProductForAvailability(product, scheduleList = [], fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)()) {
|
|
497
461
|
const raw = product;
|
|
498
462
|
const kind = resolveProductKind(raw);
|
|
499
463
|
const sessionRules = [];
|
|
500
464
|
const sessionRanges = [];
|
|
501
|
-
const explicitSession =
|
|
465
|
+
const explicitSession = raw.cartDetailValue?.session || raw._extend?.other?.session || raw.session;
|
|
502
466
|
const explicitRange = normalizeAbsoluteSessionRange({
|
|
503
|
-
startAt: explicitSession
|
|
504
|
-
endAt: explicitSession
|
|
505
|
-
scheduleId: toNumberId(explicitSession
|
|
506
|
-
timeSlotId: toNumberId(explicitSession
|
|
507
|
-
source:
|
|
467
|
+
startAt: explicitSession?.start_at,
|
|
468
|
+
endAt: explicitSession?.end_at,
|
|
469
|
+
scheduleId: toNumberId(explicitSession?.schedule_id),
|
|
470
|
+
timeSlotId: toNumberId(explicitSession?.time_slot_id),
|
|
471
|
+
source: 'product_session',
|
|
508
472
|
fixedOffsetMinutes
|
|
509
473
|
});
|
|
510
|
-
if (explicitRange)
|
|
511
|
-
|
|
512
|
-
resolveProductScheduleItems(raw, scheduleList).forEach((schedule) => {
|
|
474
|
+
if (explicitRange) sessionRanges.push(explicitRange);
|
|
475
|
+
resolveProductScheduleItems(raw, scheduleList).forEach(schedule => {
|
|
513
476
|
const normalized = normalizeScheduleItem(schedule, fixedOffsetMinutes);
|
|
514
477
|
sessionRules.push(...normalized.rules);
|
|
515
478
|
sessionRanges.push(...normalized.ranges);
|
|
@@ -518,11 +481,11 @@ function normalizeProductForAvailability(product, scheduleList = [], fixedOffset
|
|
|
518
481
|
id: raw.id ?? raw.product_id,
|
|
519
482
|
title: pickLocalizedText(raw.title || raw.name || raw.product_title),
|
|
520
483
|
kind,
|
|
521
|
-
durationMinutes: kind ===
|
|
484
|
+
durationMinutes: kind === 'duration' ? Math.max(1, Number(raw.duration?.value || raw.duration || raw.service_duration || 30) || 30) : undefined,
|
|
522
485
|
requirementGroups: normalizeRequirementGroups(raw),
|
|
523
486
|
sessionRules,
|
|
524
487
|
sessionRanges,
|
|
525
|
-
capacityRequired: Math.max(1, Number(
|
|
488
|
+
capacityRequired: Math.max(1, Number(raw.capacity?.default || raw.capacity || 1) || 1),
|
|
526
489
|
quantity: Math.max(1, Number(raw.num || raw.quantity || 1) || 1),
|
|
527
490
|
cutOffPolicy: normalizeAvailabilityCutOffPolicy(raw.cut_off_time),
|
|
528
491
|
raw
|
|
@@ -530,102 +493,121 @@ function normalizeProductForAvailability(product, scheduleList = [], fixedOffset
|
|
|
530
493
|
}
|
|
531
494
|
function normalizeAvailabilityCutOffNumber(value, context) {
|
|
532
495
|
if (value == null) {
|
|
533
|
-
return {
|
|
496
|
+
return {
|
|
497
|
+
reason: `${context} 缺失`
|
|
498
|
+
};
|
|
534
499
|
}
|
|
535
|
-
if (typeof value ===
|
|
536
|
-
return {
|
|
500
|
+
if (typeof value === 'string' && !value.trim()) {
|
|
501
|
+
return {
|
|
502
|
+
reason: `${context} 缺失`
|
|
503
|
+
};
|
|
537
504
|
}
|
|
538
505
|
const normalized = Number(value);
|
|
539
506
|
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
540
|
-
return {
|
|
507
|
+
return {
|
|
508
|
+
reason: `${context} 必须是非负有限数`
|
|
509
|
+
};
|
|
541
510
|
}
|
|
542
|
-
return {
|
|
511
|
+
return {
|
|
512
|
+
value: normalized
|
|
513
|
+
};
|
|
543
514
|
}
|
|
544
515
|
function normalizeAvailabilityCutOffInteger(value, context) {
|
|
545
516
|
const numeric = normalizeAvailabilityCutOffNumber(value, context);
|
|
546
|
-
if (numeric.reason)
|
|
547
|
-
return numeric;
|
|
517
|
+
if (numeric.reason) return numeric;
|
|
548
518
|
if (!Number.isInteger(numeric.value)) {
|
|
549
|
-
return {
|
|
519
|
+
return {
|
|
520
|
+
reason: `${context} 必须是非负整数`
|
|
521
|
+
};
|
|
550
522
|
}
|
|
551
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
value: numeric.value
|
|
525
|
+
};
|
|
552
526
|
}
|
|
553
527
|
function invalidAvailabilityCutOffPolicy(reason) {
|
|
554
|
-
return {
|
|
528
|
+
return {
|
|
529
|
+
type: 'invalid',
|
|
530
|
+
reason
|
|
531
|
+
};
|
|
555
532
|
}
|
|
556
533
|
function normalizeAvailabilityFutureDay(raw) {
|
|
557
|
-
if (!Object.prototype.hasOwnProperty.call(raw,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
534
|
+
if (!Object.prototype.hasOwnProperty.call(raw, 'future_day')) return {};
|
|
535
|
+
const futureDay = normalizeAvailabilityCutOffInteger(raw.future_day, 'cut_off_time.future_day');
|
|
536
|
+
return futureDay.reason ? {
|
|
537
|
+
reason: futureDay.reason
|
|
538
|
+
} : {
|
|
539
|
+
futureDay: futureDay.value
|
|
540
|
+
};
|
|
561
541
|
}
|
|
562
542
|
function normalizeAvailabilityCutOffPolicy(raw) {
|
|
563
|
-
if (raw == null || raw ===
|
|
564
|
-
return void 0;
|
|
543
|
+
if (raw == null || raw === '') return undefined;
|
|
565
544
|
if (!isRecord(raw)) {
|
|
566
|
-
return invalidAvailabilityCutOffPolicy(
|
|
545
|
+
return invalidAvailabilityCutOffPolicy('cut_off_time 必须是对象');
|
|
567
546
|
}
|
|
568
547
|
const futureDay = normalizeAvailabilityFutureDay(raw);
|
|
569
548
|
if (futureDay.reason) {
|
|
570
549
|
return invalidAvailabilityCutOffPolicy(futureDay.reason);
|
|
571
550
|
}
|
|
572
|
-
const type = String(raw.type ??
|
|
551
|
+
const type = String(raw.type ?? '').trim();
|
|
573
552
|
if (!type) {
|
|
574
|
-
return invalidAvailabilityCutOffPolicy(
|
|
553
|
+
return invalidAvailabilityCutOffPolicy('cut_off_time.type 缺失');
|
|
575
554
|
}
|
|
576
|
-
if (type ===
|
|
555
|
+
if (type === 'ongoing') {
|
|
577
556
|
if (!isRecord(raw.ongoing)) {
|
|
578
|
-
return invalidAvailabilityCutOffPolicy(
|
|
557
|
+
return invalidAvailabilityCutOffPolicy('cut_off_time.ongoing 缺失');
|
|
579
558
|
}
|
|
580
|
-
const ongoingType = String(raw.ongoing.type ??
|
|
581
|
-
if (ongoingType ===
|
|
559
|
+
const ongoingType = String(raw.ongoing.type ?? '').trim();
|
|
560
|
+
if (ongoingType === 'before_end') {
|
|
582
561
|
return {
|
|
583
|
-
type:
|
|
584
|
-
mode:
|
|
585
|
-
...futureDay.futureDay ===
|
|
562
|
+
type: 'ongoing',
|
|
563
|
+
mode: 'before_end',
|
|
564
|
+
...(futureDay.futureDay === undefined ? {} : {
|
|
565
|
+
futureDay: futureDay.futureDay
|
|
566
|
+
})
|
|
586
567
|
};
|
|
587
568
|
}
|
|
588
|
-
if (ongoingType ===
|
|
589
|
-
const graceMinutes = normalizeAvailabilityCutOffNumber(
|
|
590
|
-
raw.ongoing.unit,
|
|
591
|
-
"cut_off_time.ongoing.unit"
|
|
592
|
-
);
|
|
569
|
+
if (ongoingType === 'after_start') {
|
|
570
|
+
const graceMinutes = normalizeAvailabilityCutOffNumber(raw.ongoing.unit, 'cut_off_time.ongoing.unit');
|
|
593
571
|
return graceMinutes.reason ? invalidAvailabilityCutOffPolicy(graceMinutes.reason) : {
|
|
594
|
-
type:
|
|
595
|
-
mode:
|
|
572
|
+
type: 'ongoing',
|
|
573
|
+
mode: 'after_start',
|
|
596
574
|
graceMinutes: graceMinutes.value,
|
|
597
|
-
...futureDay.futureDay ===
|
|
575
|
+
...(futureDay.futureDay === undefined ? {} : {
|
|
576
|
+
futureDay: futureDay.futureDay
|
|
577
|
+
})
|
|
598
578
|
};
|
|
599
579
|
}
|
|
600
580
|
return invalidAvailabilityCutOffPolicy(`未知的 cut_off_time.ongoing.type: ${ongoingType}`);
|
|
601
581
|
}
|
|
602
|
-
if (type ===
|
|
582
|
+
if (type === 'before_start') {
|
|
603
583
|
return {
|
|
604
|
-
type:
|
|
605
|
-
...futureDay.futureDay ===
|
|
584
|
+
type: 'before_start',
|
|
585
|
+
...(futureDay.futureDay === undefined ? {} : {
|
|
586
|
+
futureDay: futureDay.futureDay
|
|
587
|
+
})
|
|
606
588
|
};
|
|
607
589
|
}
|
|
608
|
-
if (type ===
|
|
609
|
-
const unit = normalizeAvailabilityCutOffNumber(raw.unit,
|
|
610
|
-
if (unit.reason)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
return invalidAvailabilityCutOffPolicy("cut_off_time.unit_type 必须是 days|hours|minutes");
|
|
590
|
+
if (type === 'advance') {
|
|
591
|
+
const unit = normalizeAvailabilityCutOffNumber(raw.unit, 'cut_off_time.unit');
|
|
592
|
+
if (unit.reason) return invalidAvailabilityCutOffPolicy(unit.reason);
|
|
593
|
+
const unitType = String(raw.unit_type ?? '').trim();
|
|
594
|
+
if (!['days', 'hours', 'minutes'].includes(unitType)) {
|
|
595
|
+
return invalidAvailabilityCutOffPolicy('cut_off_time.unit_type 必须是 days|hours|minutes');
|
|
615
596
|
}
|
|
616
597
|
return {
|
|
617
|
-
type:
|
|
598
|
+
type: 'advance',
|
|
618
599
|
unit: unit.value,
|
|
619
|
-
unitType,
|
|
620
|
-
...futureDay.futureDay ===
|
|
600
|
+
unitType: unitType,
|
|
601
|
+
...(futureDay.futureDay === undefined ? {} : {
|
|
602
|
+
futureDay: futureDay.futureDay
|
|
603
|
+
})
|
|
621
604
|
};
|
|
622
605
|
}
|
|
623
606
|
return invalidAvailabilityCutOffPolicy(`未知的 cut_off_time.type: ${type}`);
|
|
624
607
|
}
|
|
625
|
-
function normalizeResource(raw, fixedOffsetMinutes = (0,
|
|
608
|
+
function normalizeResource(raw, fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)()) {
|
|
626
609
|
const id = raw.resourceId ?? raw.resource_id ?? raw.id;
|
|
627
|
-
if (id ===
|
|
628
|
-
return null;
|
|
610
|
+
if (id === undefined || id === null || id === '') return null;
|
|
629
611
|
const rawWindows = raw.windows || raw.times || [];
|
|
630
612
|
return {
|
|
631
613
|
id,
|
|
@@ -634,123 +616,129 @@ function normalizeResource(raw, fixedOffsetMinutes = (0, import_localClock.captu
|
|
|
634
616
|
code: raw.code,
|
|
635
617
|
resourceType: normalizeResourceType(raw.type || raw.resourceType),
|
|
636
618
|
capacity: Math.max(1, Number(raw.capacity || raw.max_capacity || 1) || 1),
|
|
637
|
-
windows: (Array.isArray(rawWindows) ? rawWindows : []).flatMap(
|
|
619
|
+
windows: (Array.isArray(rawWindows) ? rawWindows : []).flatMap(window => {
|
|
638
620
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
639
621
|
startAt: window.startAt || window.start_at || window.start,
|
|
640
622
|
endAt: window.endAt || window.end_at || window.end,
|
|
641
623
|
fixedOffsetMinutes,
|
|
642
624
|
rollOverEnd: true
|
|
643
625
|
});
|
|
644
|
-
if (!range)
|
|
645
|
-
|
|
646
|
-
|
|
626
|
+
if (!range) return [];
|
|
627
|
+
return [{
|
|
628
|
+
startAt: range.startAt,
|
|
629
|
+
endAt: range.endAt,
|
|
630
|
+
source: window.source,
|
|
631
|
+
raw: window
|
|
632
|
+
}];
|
|
647
633
|
}),
|
|
648
634
|
raw
|
|
649
635
|
};
|
|
650
636
|
}
|
|
651
637
|
function normalizeScheduleIds(raw) {
|
|
652
638
|
const ids = raw.schedule ?? raw.schedule_ids ?? raw.scheduleIds ?? [];
|
|
653
|
-
return (Array.isArray(ids) ? ids : [ids]).map(
|
|
639
|
+
return (Array.isArray(ids) ? ids : [ids]).map(id => toNumberId(id)).filter(id => id !== undefined);
|
|
654
640
|
}
|
|
655
641
|
function dateRangeDays(dateRange) {
|
|
656
|
-
if (!
|
|
657
|
-
return [];
|
|
642
|
+
if (!dateRange?.startDate || !dateRange?.endDate) return [];
|
|
658
643
|
const dates = [];
|
|
659
|
-
const startScalar = (0,
|
|
660
|
-
const endScalar = (0,
|
|
661
|
-
if (endScalar < startScalar)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
dates.push((0, import_localClock.formatLocalDate)(cursor));
|
|
644
|
+
const startScalar = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(dateRange.startDate));
|
|
645
|
+
const endScalar = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(dateRange.endDate));
|
|
646
|
+
if (endScalar < startScalar) return dates;
|
|
647
|
+
for (let cursor = startScalar; cursor <= endScalar; cursor = (0, _localClock.addLocalCalendarDays)(cursor, 1)) {
|
|
648
|
+
dates.push((0, _localClock.formatLocalDate)(cursor));
|
|
665
649
|
}
|
|
666
650
|
return dates;
|
|
667
651
|
}
|
|
668
|
-
function normalizeResourceOccupancy(rawEvent, resourceId, source =
|
|
669
|
-
var _a, _b;
|
|
652
|
+
function normalizeResourceOccupancy(rawEvent, resourceId, source = 'remote', fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)()) {
|
|
670
653
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
671
654
|
startAt: rawEvent.start_at ?? rawEvent.startAt ?? rawEvent.start_time ?? rawEvent.startTime,
|
|
672
655
|
endAt: rawEvent.end_at ?? rawEvent.endAt ?? rawEvent.end_time ?? rawEvent.endTime,
|
|
673
656
|
fixedOffsetMinutes,
|
|
674
657
|
rollOverEnd: false
|
|
675
658
|
});
|
|
676
|
-
if (!range)
|
|
677
|
-
return null;
|
|
659
|
+
if (!range) return null;
|
|
678
660
|
return {
|
|
679
|
-
id: rawEvent.id == null ?
|
|
661
|
+
id: rawEvent.id == null ? undefined : String(rawEvent.id),
|
|
680
662
|
resourceId,
|
|
681
663
|
startAt: range.startAt,
|
|
682
664
|
endAt: range.endAt,
|
|
683
665
|
pax: Number(rawEvent.pax ?? rawEvent.number ?? rawEvent.capacity ?? 1) || 1,
|
|
684
|
-
source: rawEvent.source ===
|
|
666
|
+
source: rawEvent.source === 'cart' || rawEvent.source === 'selection' ? rawEvent.source : source,
|
|
685
667
|
scheduleEventId: toNumberId(rawEvent.schedule_event_id ?? rawEvent.scheduleEventId),
|
|
686
668
|
serverEventId: toNumberId(rawEvent.schedule_event_id ?? rawEvent.server_event_id ?? rawEvent.id),
|
|
687
|
-
bookingUid: rawEvent.booking_uid ?? rawEvent.bookingUid ??
|
|
688
|
-
productUid: rawEvent.product_uid ?? rawEvent.productUid ??
|
|
669
|
+
bookingUid: rawEvent.booking_uid ?? rawEvent.bookingUid ?? rawEvent.metadata?.unique_identification_number,
|
|
670
|
+
productUid: rawEvent.product_uid ?? rawEvent.productUid ?? rawEvent.metadata?.product_uid,
|
|
689
671
|
raw: rawEvent
|
|
690
672
|
};
|
|
691
673
|
}
|
|
692
674
|
function buildResourceWindowFallbackTimes(params) {
|
|
693
|
-
const {
|
|
694
|
-
|
|
675
|
+
const {
|
|
676
|
+
raw,
|
|
677
|
+
dates,
|
|
678
|
+
scheduleIds
|
|
679
|
+
} = params;
|
|
680
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
695
681
|
const boundary = normalizeAvailabilityLocalDateTimeRange({
|
|
696
682
|
startAt: raw.start_time || raw.start_at || raw.startTime,
|
|
697
683
|
endAt: raw.end_time || raw.end_at || raw.endTime,
|
|
698
684
|
fixedOffsetMinutes,
|
|
699
685
|
rollOverEnd: true
|
|
700
686
|
});
|
|
701
|
-
if (!boundary)
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
const dayEnd = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(`${date}T23:59:59`));
|
|
687
|
+
if (!boundary) return [];
|
|
688
|
+
return dates.flatMap(date => {
|
|
689
|
+
const dayStart = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(`${date}T00:00:00`));
|
|
690
|
+
const dayEnd = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(`${date}T23:59:59`));
|
|
706
691
|
const start = Math.max(boundary.startScalar, dayStart);
|
|
707
692
|
const end = Math.min(boundary.endScalar, dayEnd);
|
|
708
|
-
if (end <= start)
|
|
709
|
-
return [];
|
|
693
|
+
if (end <= start) return [];
|
|
710
694
|
return [{
|
|
711
|
-
start_at: (0,
|
|
712
|
-
end_at: (0,
|
|
695
|
+
start_at: (0, _localClock.formatLocalDateTime)(start, fixedOffsetMinutes),
|
|
696
|
+
end_at: (0, _localClock.formatLocalDateTime)(end, fixedOffsetMinutes),
|
|
713
697
|
schedule_ids: scheduleIds,
|
|
714
|
-
source:
|
|
698
|
+
source: 'resource_window_fallback'
|
|
715
699
|
}];
|
|
716
700
|
});
|
|
717
701
|
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Materializes resource work windows locally from v2's schedule ids, then keeps its flat
|
|
705
|
+
* event_list as resource occupancy records. This deliberately avoids the older API's times -> event_list
|
|
706
|
+
* response nesting and lets one resource response serve any requested date range.
|
|
707
|
+
*/
|
|
718
708
|
function buildAvailabilityResourcesFromV2(params) {
|
|
719
|
-
const scheduleMap = new Map(
|
|
720
|
-
(params.scheduleList || []).map((schedule) => [String(schedule.id), schedule])
|
|
721
|
-
);
|
|
709
|
+
const scheduleMap = new Map((params.scheduleList || []).map(schedule => [String(schedule.id), schedule]));
|
|
722
710
|
const dates = dateRangeDays(params.dateRange);
|
|
723
711
|
const resourceOccupancies = [];
|
|
724
|
-
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0,
|
|
725
|
-
const resources = (params.rawResources || []).map(
|
|
712
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
713
|
+
const resources = (params.rawResources || []).map(raw => {
|
|
726
714
|
const resource = normalizeResource(raw, fixedOffsetMinutes);
|
|
727
|
-
if (!resource)
|
|
728
|
-
return null;
|
|
715
|
+
if (!resource) return null;
|
|
729
716
|
const explicitWindows = [...resource.windows];
|
|
730
717
|
const scheduleIds = normalizeScheduleIds(raw);
|
|
731
|
-
const schedules = scheduleIds.map(
|
|
732
|
-
const matchedScheduleIds = new Set(schedules.map(
|
|
733
|
-
const missingScheduleIds = scheduleIds.filter(
|
|
718
|
+
const schedules = scheduleIds.map(id => scheduleMap.get(String(id))).filter(Boolean);
|
|
719
|
+
const matchedScheduleIds = new Set(schedules.map(schedule => String(schedule.id)));
|
|
720
|
+
const missingScheduleIds = scheduleIds.filter(id => !matchedScheduleIds.has(String(id)));
|
|
734
721
|
if (missingScheduleIds.length > 0) {
|
|
735
722
|
resource.raw = {
|
|
736
|
-
...resource.raw || raw,
|
|
723
|
+
...(resource.raw || raw),
|
|
737
724
|
_availabilityMissingScheduleIds: missingScheduleIds
|
|
738
725
|
};
|
|
739
726
|
}
|
|
740
|
-
const materializedWindows = dates.flatMap(
|
|
727
|
+
const materializedWindows = dates.flatMap(date => schedules.flatMap(schedule => (0, _getDateIsInSchedule.getScheduleStartEndTimePoints)(date, [schedule]).flatMap(slot => {
|
|
741
728
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
742
729
|
startAt: slot.start_at,
|
|
743
730
|
endAt: slot.end_at,
|
|
744
731
|
fixedOffsetMinutes,
|
|
745
732
|
rollOverEnd: true
|
|
746
733
|
});
|
|
747
|
-
if (!range)
|
|
748
|
-
return [];
|
|
734
|
+
if (!range) return [];
|
|
749
735
|
return [{
|
|
750
736
|
startAt: range.startAt,
|
|
751
737
|
endAt: range.endAt,
|
|
752
738
|
source: `resource_schedule:${schedule.id}`,
|
|
753
|
-
raw: {
|
|
739
|
+
raw: {
|
|
740
|
+
scheduleId: schedule.id
|
|
741
|
+
}
|
|
754
742
|
}];
|
|
755
743
|
})));
|
|
756
744
|
resource.windows = materializedWindows.length > 0 ? materializedWindows : explicitWindows;
|
|
@@ -760,7 +748,7 @@ function buildAvailabilityResourcesFromV2(params) {
|
|
|
760
748
|
dates,
|
|
761
749
|
scheduleIds,
|
|
762
750
|
fixedOffsetMinutes
|
|
763
|
-
}).map(
|
|
751
|
+
}).map(window => ({
|
|
764
752
|
startAt: window.start_at,
|
|
765
753
|
endAt: window.end_at,
|
|
766
754
|
source: window.source,
|
|
@@ -768,40 +756,35 @@ function buildAvailabilityResourcesFromV2(params) {
|
|
|
768
756
|
}));
|
|
769
757
|
}
|
|
770
758
|
const eventList = Array.isArray(raw.event_list) ? raw.event_list : [];
|
|
771
|
-
eventList.forEach(
|
|
772
|
-
const occupancy = normalizeResourceOccupancy(event, resource.id,
|
|
773
|
-
if (occupancy)
|
|
774
|
-
resourceOccupancies.push(occupancy);
|
|
759
|
+
eventList.forEach(event => {
|
|
760
|
+
const occupancy = normalizeResourceOccupancy(event, resource.id, 'remote', fixedOffsetMinutes);
|
|
761
|
+
if (occupancy) resourceOccupancies.push(occupancy);
|
|
775
762
|
});
|
|
776
763
|
return resource;
|
|
777
|
-
}).filter(
|
|
764
|
+
}).filter(resource => resource !== null);
|
|
778
765
|
return {
|
|
779
766
|
resources,
|
|
780
767
|
resourceOccupancies
|
|
781
768
|
};
|
|
782
769
|
}
|
|
770
|
+
|
|
771
|
+
/** Combines URL-locked resources with every resource referenced by the selected products. */
|
|
783
772
|
function collectAvailabilityResourceIds(products, explicitResourceIds = []) {
|
|
784
|
-
const ids =
|
|
785
|
-
const add =
|
|
786
|
-
if (value ===
|
|
787
|
-
return;
|
|
773
|
+
const ids = new Map();
|
|
774
|
+
const add = value => {
|
|
775
|
+
if (value === undefined || value === null || value === '') return;
|
|
788
776
|
ids.set(String(value), value);
|
|
789
777
|
};
|
|
790
778
|
explicitResourceIds.forEach(add);
|
|
791
|
-
products.forEach(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
var _a2;
|
|
795
|
-
(_a2 = requirement.resourceIds) == null ? void 0 : _a2.forEach(add);
|
|
779
|
+
products.forEach(product => {
|
|
780
|
+
product.requirementGroups?.forEach(requirement => {
|
|
781
|
+
requirement.resourceIds?.forEach(add);
|
|
796
782
|
});
|
|
797
783
|
});
|
|
798
784
|
return Array.from(ids.values());
|
|
799
785
|
}
|
|
800
786
|
function getProductLineUid(product) {
|
|
801
|
-
|
|
802
|
-
return String(
|
|
803
|
-
((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.unique_identification_number) ?? (product == null ? void 0 : product.unique_identification_number) ?? ""
|
|
804
|
-
);
|
|
787
|
+
return String(product?.metadata?.unique_identification_number ?? product?.unique_identification_number ?? '');
|
|
805
788
|
}
|
|
806
789
|
function buildAvailabilityLineIdentity(product) {
|
|
807
790
|
return {
|
|
@@ -821,25 +804,22 @@ function buildBookingFromAvailabilityAssignment(params) {
|
|
|
821
804
|
});
|
|
822
805
|
}
|
|
823
806
|
function buildBookingFromAvailabilityAssignments(params) {
|
|
824
|
-
const normalizedAssignments = (params.assignments || []).map(
|
|
807
|
+
const normalizedAssignments = (params.assignments || []).map(assignment => {
|
|
825
808
|
const resourceId = assignment.resourceId ?? assignment.resource_id;
|
|
826
|
-
const matchedGroup = (params.product.requirementGroups || []).find(
|
|
827
|
-
var _a;
|
|
828
|
-
return (_a = group.resourceIds) == null ? void 0 : _a.some((id) => sameAvailabilityId(id, resourceId));
|
|
829
|
-
});
|
|
809
|
+
const matchedGroup = (params.product.requirementGroups || []).find(group => group.resourceIds?.some(id => sameAvailabilityId(id, resourceId)));
|
|
830
810
|
return {
|
|
831
811
|
productId: assignment.productId ?? assignment.product_id,
|
|
832
812
|
resourceId,
|
|
833
|
-
formId: assignment.formId ?? assignment.form_id ??
|
|
813
|
+
formId: assignment.formId ?? assignment.form_id ?? matchedGroup?.formId,
|
|
834
814
|
startAt: assignment.startAt ?? assignment.start_at,
|
|
835
815
|
endAt: assignment.endAt ?? assignment.end_at,
|
|
836
|
-
capacityRequired: assignment.capacityRequired ?? assignment.capacity_required ??
|
|
816
|
+
capacityRequired: assignment.capacityRequired ?? assignment.capacity_required ?? matchedGroup?.capacityRequired,
|
|
837
817
|
scheduleId: assignment.scheduleId ?? assignment.schedule_id,
|
|
838
818
|
timeSlotId: assignment.timeSlotId ?? assignment.time_slot_id
|
|
839
819
|
};
|
|
840
820
|
});
|
|
841
821
|
const first = normalizedAssignments[0];
|
|
842
|
-
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0,
|
|
822
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
843
823
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
844
824
|
startAt: first.startAt,
|
|
845
825
|
endAt: first.endAt,
|
|
@@ -847,24 +827,24 @@ function buildBookingFromAvailabilityAssignments(params) {
|
|
|
847
827
|
rollOverEnd: false
|
|
848
828
|
});
|
|
849
829
|
if (!range) {
|
|
850
|
-
throw new
|
|
830
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'assignment 时间范围非法', {
|
|
851
831
|
assignments: params.assignments
|
|
852
832
|
});
|
|
853
833
|
}
|
|
854
834
|
const candidate = {
|
|
855
835
|
key: `${String(first.productId)}:${range.startAt}:${range.endAt}`,
|
|
856
836
|
productId: first.productId,
|
|
857
|
-
source:
|
|
837
|
+
source: 'duration_window',
|
|
858
838
|
startAt: range.startAt,
|
|
859
839
|
endAt: range.endAt,
|
|
860
840
|
bookingStartAt: range.startAt,
|
|
861
841
|
bookingEndAt: range.endAt,
|
|
862
842
|
date: range.startAt.slice(0, 10),
|
|
863
|
-
startTime: (0,
|
|
864
|
-
endTime: (0,
|
|
865
|
-
timezone: String(params.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone ||
|
|
843
|
+
startTime: (0, _localClock.formatLocalTime)(range.startAt, false),
|
|
844
|
+
endTime: (0, _localClock.formatLocalTime)(range.endAt, false),
|
|
845
|
+
timezone: String(params.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'runtime'),
|
|
866
846
|
scheduleRefs: [],
|
|
867
|
-
primaryScheduleRef:
|
|
847
|
+
primaryScheduleRef: undefined
|
|
868
848
|
};
|
|
869
849
|
return buildBookingFromAvailabilitySelection({
|
|
870
850
|
assignments: normalizedAssignments,
|
|
@@ -873,18 +853,13 @@ function buildBookingFromAvailabilityAssignments(params) {
|
|
|
873
853
|
});
|
|
874
854
|
}
|
|
875
855
|
function getBookingUid(booking) {
|
|
876
|
-
|
|
877
|
-
return String(
|
|
878
|
-
((_a = booking == null ? void 0 : booking.metadata) == null ? void 0 : _a.unique_identification_number) ?? (booking == null ? void 0 : booking.booking_uid) ?? ""
|
|
879
|
-
);
|
|
856
|
+
return String(booking?.metadata?.unique_identification_number ?? booking?.booking_uid ?? '');
|
|
880
857
|
}
|
|
881
858
|
function stableValue(value) {
|
|
882
|
-
if (Array.isArray(value))
|
|
883
|
-
|
|
884
|
-
if (!value || typeof value !== "object")
|
|
885
|
-
return value;
|
|
859
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
860
|
+
if (!value || typeof value !== 'object') return value;
|
|
886
861
|
return Object.keys(value).sort().reduce((result, key) => {
|
|
887
|
-
if (value[key] !==
|
|
862
|
+
if (value[key] !== undefined && typeof value[key] !== 'function') {
|
|
888
863
|
result[key] = stableValue(value[key]);
|
|
889
864
|
}
|
|
890
865
|
return result;
|
|
@@ -899,21 +874,17 @@ function hashText(value) {
|
|
|
899
874
|
return (hash >>> 0).toString(36);
|
|
900
875
|
}
|
|
901
876
|
function resolveOrderRevision(tempOrder) {
|
|
902
|
-
if (!tempOrder)
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
productBundle: product.product_bundle
|
|
914
|
-
};
|
|
915
|
-
}).sort((left, right) => left.uid.localeCompare(right.uid));
|
|
916
|
-
const bookings = (Array.isArray(tempOrder.bookings) ? tempOrder.bookings : []).map((booking) => ({
|
|
877
|
+
if (!tempOrder) return 'order:none';
|
|
878
|
+
const products = (Array.isArray(tempOrder.products) ? tempOrder.products : []).map(product => ({
|
|
879
|
+
uid: getProductLineUid(product),
|
|
880
|
+
productId: product.product_id ?? product.id,
|
|
881
|
+
variantId: product.product_variant_id ?? 0,
|
|
882
|
+
num: product.num ?? product.quantity ?? 1,
|
|
883
|
+
bookingUid: product.booking_uid ?? product.metadata?.booking_uid,
|
|
884
|
+
productSku: product.product_sku,
|
|
885
|
+
productBundle: product.product_bundle
|
|
886
|
+
})).sort((left, right) => left.uid.localeCompare(right.uid));
|
|
887
|
+
const bookings = (Array.isArray(tempOrder.bookings) ? tempOrder.bookings : []).map(booking => ({
|
|
917
888
|
uid: getBookingUid(booking),
|
|
918
889
|
scheduleEventId: booking.schedule_event_id,
|
|
919
890
|
productUid: booking.product_uid,
|
|
@@ -924,10 +895,12 @@ function resolveOrderRevision(tempOrder) {
|
|
|
924
895
|
endTime: booking.end_time,
|
|
925
896
|
resources: booking.resources
|
|
926
897
|
})).sort((left, right) => left.uid.localeCompare(right.uid));
|
|
927
|
-
return `order:${hashText(JSON.stringify(stableValue({
|
|
898
|
+
return `order:${hashText(JSON.stringify(stableValue({
|
|
899
|
+
products,
|
|
900
|
+
bookings
|
|
901
|
+
})))}`;
|
|
928
902
|
}
|
|
929
903
|
function buildBookingFromAvailabilitySelection(params) {
|
|
930
|
-
var _a, _b, _c, _d;
|
|
931
904
|
const {
|
|
932
905
|
assignments,
|
|
933
906
|
candidate,
|
|
@@ -937,12 +910,12 @@ function buildBookingFromAvailabilitySelection(params) {
|
|
|
937
910
|
} = params;
|
|
938
911
|
const bookingStartAt = candidate.bookingStartAt || candidate.startAt;
|
|
939
912
|
const bookingEndAt = candidate.bookingEndAt || candidate.endAt;
|
|
940
|
-
const startScalar = (0,
|
|
941
|
-
const endScalar = (0,
|
|
942
|
-
const scheduleId =
|
|
943
|
-
const timeSlotId =
|
|
944
|
-
if (product.kind ===
|
|
945
|
-
throw new
|
|
913
|
+
const startScalar = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(bookingStartAt));
|
|
914
|
+
const endScalar = (0, _localClock.localDateTimeToScalar)((0, _localClock.parseLocalDateTime)(bookingEndAt));
|
|
915
|
+
const scheduleId = candidate.primaryScheduleRef?.scheduleId ?? assignments.find(assignment => assignment.scheduleId != null)?.scheduleId;
|
|
916
|
+
const timeSlotId = candidate.primaryScheduleRef?.timeSlotId ?? assignments.find(assignment => assignment.timeSlotId != null)?.timeSlotId;
|
|
917
|
+
if (product.kind === 'session' && scheduleId == null) {
|
|
918
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', 'session 候选缺少真实 schedule identity', {
|
|
946
919
|
productId: product.id,
|
|
947
920
|
candidateKey: candidate.key,
|
|
948
921
|
scheduleRefs: candidate.scheduleRefs
|
|
@@ -953,123 +926,120 @@ function buildBookingFromAvailabilitySelection(params) {
|
|
|
953
926
|
start_time: bookingStartAt.slice(11, 16),
|
|
954
927
|
end_date: bookingEndAt.slice(0, 10),
|
|
955
928
|
end_time: bookingEndAt.slice(11, 16),
|
|
956
|
-
duration: Math.max(1, Math.trunc((endScalar - startScalar) / (60 *
|
|
957
|
-
like_status:
|
|
929
|
+
duration: Math.max(1, Math.trunc((endScalar - startScalar) / (60 * 1000))),
|
|
930
|
+
like_status: 'common',
|
|
958
931
|
schedule_id: scheduleId ?? 0,
|
|
959
|
-
resources: assignments.map(
|
|
960
|
-
|
|
961
|
-
|
|
932
|
+
resources: assignments.map(assignment => {
|
|
933
|
+
// requirementSelections 的提交协议只携带资源 ID;购物车展示则需要名称。
|
|
934
|
+
// commit 此时仍持有本次不可变 projection,因此直接按 ID 取回名称并
|
|
935
|
+
// 固化到 booking,避免 UI 再依赖 legacy BookingContext resourcesOriginMap。
|
|
936
|
+
const matchedResource = resources.find(resource => sameAvailabilityId(resource.id, assignment.resourceId));
|
|
937
|
+
const resourceName = String(matchedResource?.name || '').trim();
|
|
962
938
|
return {
|
|
963
939
|
id: assignment.resourceId,
|
|
964
940
|
resource_id: assignment.resourceId,
|
|
965
941
|
form_id: assignment.formId,
|
|
966
|
-
relation_type:
|
|
942
|
+
relation_type: 'form',
|
|
967
943
|
relation_id: assignment.resourceId,
|
|
968
|
-
like_status:
|
|
944
|
+
like_status: 'common',
|
|
969
945
|
capacity: assignment.capacityRequired || 1,
|
|
970
|
-
...resourceName ? {
|
|
946
|
+
...(resourceName ? {
|
|
947
|
+
metadata: {
|
|
948
|
+
resource_name: resourceName
|
|
949
|
+
}
|
|
950
|
+
} : {})
|
|
971
951
|
};
|
|
972
952
|
}),
|
|
973
953
|
metadata: {
|
|
974
|
-
...bookingUid ? {
|
|
954
|
+
...(bookingUid ? {
|
|
955
|
+
unique_identification_number: bookingUid
|
|
956
|
+
} : {}),
|
|
975
957
|
unified_booking_sales: true,
|
|
976
958
|
availability_candidate_key: candidate.key,
|
|
977
959
|
availability_source: candidate.source,
|
|
978
960
|
availability_slice_start_at: candidate.startAt,
|
|
979
961
|
availability_slice_end_at: candidate.endAt,
|
|
980
962
|
availability_schedule_refs: candidate.scheduleRefs,
|
|
981
|
-
...timeSlotId == null ? {} : {
|
|
963
|
+
...(timeSlotId == null ? {} : {
|
|
964
|
+
time_slot_id: timeSlotId
|
|
965
|
+
}),
|
|
982
966
|
product_kind: product.kind
|
|
983
967
|
}
|
|
984
968
|
};
|
|
985
969
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
this.availabilityRemoteResourceQueryInFlight = /* @__PURE__ */ new Map();
|
|
1009
|
-
this.availabilityRemoteResourceQuerySequence = 0;
|
|
1010
|
-
this.availabilityRemoteResourceEpoch = 0;
|
|
1011
|
-
this.availabilityCommitLocks = /* @__PURE__ */ new Set();
|
|
1012
|
-
}
|
|
970
|
+
class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
971
|
+
defaultName = 'unifiedBookingSales';
|
|
972
|
+
defaultVersion = '1.0.0';
|
|
973
|
+
store = {};
|
|
974
|
+
unifiedProductCatalog = [];
|
|
975
|
+
openDataTarget = null;
|
|
976
|
+
loadOpenDataInFlight = null;
|
|
977
|
+
loadOpenDataInFlightTarget = null;
|
|
978
|
+
availabilityRuntimeOffsetMinutes = null;
|
|
979
|
+
availabilityScheduleCatalog = null;
|
|
980
|
+
availabilityProjectionSequence = 0;
|
|
981
|
+
availabilityContextSequence = 0;
|
|
982
|
+
availabilityLifecycleGeneration = 0;
|
|
983
|
+
availabilityContextRegistry = new Map();
|
|
984
|
+
availabilityProjectionPool = new Map();
|
|
985
|
+
availabilityPrepareInFlight = new Map();
|
|
986
|
+
availabilityProjectionRefreshInFlight = new Map();
|
|
987
|
+
availabilityRemoteOccupancyCache = new _remoteOccupancyCache.AvailabilityRemoteOccupancyCache(MAX_REMOTE_OCCUPANCY_RESOURCE_DATE_CELLS);
|
|
988
|
+
availabilityRemoteResourceQueryInFlight = new Map();
|
|
989
|
+
availabilityRemoteResourceQuerySequence = 0;
|
|
990
|
+
availabilityRemoteResourceEpoch = 0;
|
|
991
|
+
availabilityCommitLocks = new Set();
|
|
1013
992
|
async initialize(core, options = {}) {
|
|
1014
|
-
var _a, _b, _c, _d;
|
|
1015
993
|
this.availabilityScheduleCatalog = null;
|
|
1016
994
|
this.openDataTarget = null;
|
|
1017
|
-
this.availabilityRuntimeOffsetMinutes = (0,
|
|
995
|
+
this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
1018
996
|
this.clearAvailabilityRemoteResourceState();
|
|
1019
997
|
await super.initialize(core, options);
|
|
1020
|
-
const openDataBusinessCode = String(
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
const openDataChannel = String(((_c = this.otherParams) == null ? void 0 : _c.channel) ?? "").trim();
|
|
1024
|
-
if (((_d = this.otherParams) == null ? void 0 : _d.openData) && openDataBusinessCode && openDataChannel) {
|
|
998
|
+
const openDataBusinessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
999
|
+
const openDataChannel = String(this.otherParams?.channel ?? '').trim();
|
|
1000
|
+
if (this.otherParams?.openData && openDataBusinessCode && openDataChannel) {
|
|
1025
1001
|
this.openDataTarget = `${openDataBusinessCode}+${openDataChannel}`;
|
|
1026
1002
|
}
|
|
1027
1003
|
this.captureAvailabilityScheduleCatalog();
|
|
1028
1004
|
}
|
|
1029
1005
|
getSubmitOrderSalesChannel() {
|
|
1030
|
-
|
|
1031
|
-
const channel = String(((_a = this.otherParams) == null ? void 0 : _a.channel) ?? "").trim();
|
|
1006
|
+
const channel = String(this.otherParams?.channel ?? '').trim();
|
|
1032
1007
|
return channel || super.getSubmitOrderSalesChannel();
|
|
1033
1008
|
}
|
|
1034
1009
|
createAvailabilityFacadeError(code, message, details) {
|
|
1035
|
-
return new
|
|
1010
|
+
return new _ResourcePlanner.AvailabilityError(code, message, details);
|
|
1036
1011
|
}
|
|
1037
1012
|
getAvailabilityScopeDescriptor(otherParams = this.otherParams || {}) {
|
|
1038
1013
|
return {
|
|
1039
|
-
cacheId: this.cacheId ||
|
|
1040
|
-
businessCode: String(otherParams.businessCode ?? otherParams.business_code ??
|
|
1041
|
-
channel: String(otherParams.channel ??
|
|
1042
|
-
platform: String(otherParams.platform ??
|
|
1043
|
-
businessTimezone: String(otherParams.businessTimezone ??
|
|
1014
|
+
cacheId: this.cacheId || '',
|
|
1015
|
+
businessCode: String(otherParams.businessCode ?? otherParams.business_code ?? ''),
|
|
1016
|
+
channel: String(otherParams.channel ?? ''),
|
|
1017
|
+
platform: String(otherParams.platform ?? ''),
|
|
1018
|
+
businessTimezone: String(otherParams.businessTimezone ?? '')
|
|
1044
1019
|
};
|
|
1045
1020
|
}
|
|
1046
1021
|
normalizeAvailabilityIdList(values) {
|
|
1047
|
-
if (!Array.isArray(values) || values.length === 0)
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
values.forEach((value) => {
|
|
1022
|
+
if (!Array.isArray(values) || values.length === 0) return undefined;
|
|
1023
|
+
const uniqueMap = new Map();
|
|
1024
|
+
values.forEach(value => {
|
|
1051
1025
|
uniqueMap.set(String(value), value);
|
|
1052
1026
|
});
|
|
1053
1027
|
return Array.from(uniqueMap.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([, value]) => value);
|
|
1054
1028
|
}
|
|
1055
1029
|
normalizeAvailabilityStringList(values) {
|
|
1056
|
-
if (!Array.isArray(values) || values.length === 0)
|
|
1057
|
-
return void 0;
|
|
1030
|
+
if (!Array.isArray(values) || values.length === 0) return undefined;
|
|
1058
1031
|
return Array.from(new Set(values.map(String))).sort((left, right) => left.localeCompare(right));
|
|
1059
1032
|
}
|
|
1060
1033
|
normalizeAvailabilityPrepareDescriptor(params) {
|
|
1061
|
-
var _a;
|
|
1062
1034
|
this.assertAvailabilityPrepareParams(params);
|
|
1063
|
-
const timezone = String(
|
|
1064
|
-
params.timezone || ((_a = this.otherParams) == null ? void 0 : _a.businessTimezone) || Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai"
|
|
1065
|
-
);
|
|
1035
|
+
const timezone = String(params.timezone || this.otherParams?.businessTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone || 'Asia/Shanghai');
|
|
1066
1036
|
assertValidAvailabilityTimezone(timezone);
|
|
1067
1037
|
const dateRange = this.normalizeAvailabilityDateRange(params, timezone);
|
|
1068
1038
|
const slotStepMinutes = params.slotStepMinutes || DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES;
|
|
1069
1039
|
const businessHours = params.businessHours || DEFAULT_AVAILABILITY_BUSINESS_HOURS;
|
|
1070
|
-
const mode = params.mode ||
|
|
1040
|
+
const mode = params.mode || 'appointment';
|
|
1071
1041
|
if (!isValidClockTime(businessHours.startTime) || !isValidClockTime(businessHours.endTime)) {
|
|
1072
|
-
throw new
|
|
1042
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'businessHours 必须使用 HH:mm', {
|
|
1073
1043
|
businessHours
|
|
1074
1044
|
});
|
|
1075
1045
|
}
|
|
@@ -1081,63 +1051,58 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1081
1051
|
dateRange,
|
|
1082
1052
|
timezone,
|
|
1083
1053
|
slotStepMinutes,
|
|
1084
|
-
businessHours: {
|
|
1054
|
+
businessHours: {
|
|
1055
|
+
...businessHours
|
|
1056
|
+
},
|
|
1085
1057
|
_availabilityOperatingHours: params._availabilityOperatingHours ? {
|
|
1086
1058
|
source: params._availabilityOperatingHours.source,
|
|
1087
1059
|
scheduleIds: [...params._availabilityOperatingHours.scheduleIds]
|
|
1088
|
-
} :
|
|
1060
|
+
} : undefined,
|
|
1089
1061
|
mode
|
|
1090
1062
|
});
|
|
1091
1063
|
}
|
|
1092
1064
|
async resolveAvailabilityPrepareDefaults(params) {
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
const needsTimeSlice = params.slotStepMinutes === void 0;
|
|
1065
|
+
const needsOperatingHours = params.businessHours === undefined;
|
|
1066
|
+
const needsTimeSlice = params.slotStepMinutes === undefined;
|
|
1096
1067
|
if (!needsOperatingHours && !needsTimeSlice) {
|
|
1097
1068
|
return {
|
|
1098
1069
|
...params,
|
|
1099
1070
|
_availabilityOperatingHours: {
|
|
1100
|
-
source:
|
|
1071
|
+
source: 'prepare_business_hours',
|
|
1101
1072
|
scheduleIds: []
|
|
1102
1073
|
}
|
|
1103
1074
|
};
|
|
1104
1075
|
}
|
|
1105
|
-
const businessCode = String(
|
|
1106
|
-
|
|
1107
|
-
).trim();
|
|
1108
|
-
const channel = String(((_c = this.otherParams) == null ? void 0 : _c.channel) ?? "").trim();
|
|
1076
|
+
const businessCode = String(this.otherParams?.businessCode ?? this.otherParams?.business_code ?? '').trim();
|
|
1077
|
+
const channel = String(this.otherParams?.channel ?? '').trim();
|
|
1109
1078
|
const currentTarget = businessCode && channel ? `${businessCode}+${channel}` : null;
|
|
1110
|
-
let openData = !currentTarget || this.openDataTarget === currentTarget ?
|
|
1079
|
+
let openData = !currentTarget || this.openDataTarget === currentTarget ? this.otherParams?.openData : null;
|
|
1111
1080
|
if (!openData && this.store.openData) {
|
|
1112
1081
|
try {
|
|
1113
1082
|
openData = await this.getOpenData();
|
|
1114
1083
|
} catch (error) {
|
|
1115
1084
|
if (needsOperatingHours) {
|
|
1116
|
-
throw this.createAvailabilityFacadeError(
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
channel,
|
|
1122
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
1123
|
-
}
|
|
1124
|
-
);
|
|
1085
|
+
throw this.createAvailabilityFacadeError('OPERATING_HOURS_RESOLVE_FAILED', '无法读取店铺营业时间配置,请重试', {
|
|
1086
|
+
businessCode,
|
|
1087
|
+
channel,
|
|
1088
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1089
|
+
});
|
|
1125
1090
|
}
|
|
1126
|
-
console.warn(
|
|
1091
|
+
console.warn('[UnifiedBookingSales] OpenData 时间切片配置加载失败,回退为默认值', error);
|
|
1127
1092
|
}
|
|
1128
1093
|
}
|
|
1129
|
-
const operatingScheduleIds = this.normalizeAvailabilityIdList(
|
|
1130
|
-
|
|
1131
|
-
) || [];
|
|
1132
|
-
const configuredTimeSlice = needsTimeSlice ? normalizePositiveInteger(openData == null ? void 0 : openData["reservation.time_slice"]) : void 0;
|
|
1094
|
+
const operatingScheduleIds = this.normalizeAvailabilityIdList(Array.isArray(openData?.['availability.operating_hours']) ? openData?.['availability.operating_hours'] : undefined) || [];
|
|
1095
|
+
const configuredTimeSlice = needsTimeSlice ? normalizePositiveInteger(openData?.['reservation.time_slice']) : undefined;
|
|
1133
1096
|
return {
|
|
1134
1097
|
...params,
|
|
1135
|
-
...configuredTimeSlice ? {
|
|
1098
|
+
...(configuredTimeSlice ? {
|
|
1099
|
+
slotStepMinutes: configuredTimeSlice
|
|
1100
|
+
} : {}),
|
|
1136
1101
|
_availabilityOperatingHours: needsOperatingHours ? {
|
|
1137
|
-
source: operatingScheduleIds.length > 0 ?
|
|
1102
|
+
source: operatingScheduleIds.length > 0 ? 'open_data_schedule' : 'default_business_hours',
|
|
1138
1103
|
scheduleIds: operatingScheduleIds
|
|
1139
1104
|
} : {
|
|
1140
|
-
source:
|
|
1105
|
+
source: 'prepare_business_hours',
|
|
1141
1106
|
scheduleIds: []
|
|
1142
1107
|
}
|
|
1143
1108
|
};
|
|
@@ -1169,24 +1134,27 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1169
1134
|
resourceIds: this.normalizeAvailabilityIdList(params.resourceIds) || []
|
|
1170
1135
|
}));
|
|
1171
1136
|
}
|
|
1172
|
-
async setOtherParams(params, {
|
|
1173
|
-
|
|
1137
|
+
async setOtherParams(params, {
|
|
1138
|
+
cover = false
|
|
1139
|
+
} = {}) {
|
|
1174
1140
|
const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1175
|
-
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params,
|
|
1176
|
-
await super.setOtherParams(params, {
|
|
1141
|
+
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, 'openData');
|
|
1142
|
+
await super.setOtherParams(params, {
|
|
1143
|
+
cover
|
|
1144
|
+
});
|
|
1177
1145
|
const nextScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1178
1146
|
if (explicitlySetsOpenData) {
|
|
1179
|
-
this.openDataTarget =
|
|
1147
|
+
this.openDataTarget = this.otherParams?.openData && nextScope.businessCode && nextScope.channel ? `${nextScope.businessCode}+${nextScope.channel}` : null;
|
|
1180
1148
|
}
|
|
1181
1149
|
const shouldResetAvailabilityState = previousScope.cacheId !== nextScope.cacheId || previousScope.businessCode !== nextScope.businessCode || previousScope.channel !== nextScope.channel || previousScope.platform !== nextScope.platform || previousScope.businessTimezone !== nextScope.businessTimezone;
|
|
1182
1150
|
if (shouldResetAvailabilityState) {
|
|
1183
|
-
await this.resetAvailabilityProjectionState(
|
|
1151
|
+
await this.resetAvailabilityProjectionState('set_other_params_reset');
|
|
1184
1152
|
}
|
|
1185
1153
|
}
|
|
1186
1154
|
async destroy() {
|
|
1187
1155
|
this.availabilityScheduleCatalog = null;
|
|
1188
1156
|
this.availabilityRuntimeOffsetMinutes = null;
|
|
1189
|
-
await this.resetAvailabilityProjectionState(
|
|
1157
|
+
await this.resetAvailabilityProjectionState('destroy');
|
|
1190
1158
|
await super.destroy();
|
|
1191
1159
|
}
|
|
1192
1160
|
createAvailabilityContextId() {
|
|
@@ -1198,35 +1166,45 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1198
1166
|
}
|
|
1199
1167
|
getAvailabilityRuntimeOffsetMinutes() {
|
|
1200
1168
|
if (this.availabilityRuntimeOffsetMinutes === null) {
|
|
1201
|
-
this.availabilityRuntimeOffsetMinutes = (0,
|
|
1169
|
+
this.availabilityRuntimeOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)();
|
|
1202
1170
|
}
|
|
1203
1171
|
return this.availabilityRuntimeOffsetMinutes;
|
|
1204
1172
|
}
|
|
1205
|
-
getAvailabilityRuntimeDateTime(value =
|
|
1206
|
-
return (0,
|
|
1173
|
+
getAvailabilityRuntimeDateTime(value = new Date()) {
|
|
1174
|
+
return (0, _localClock.formatLocalDateTime)((0, _localClock.localDateToScalar)(value), this.getAvailabilityRuntimeOffsetMinutes());
|
|
1207
1175
|
}
|
|
1208
1176
|
cloneAvailabilityPrepareParams(params) {
|
|
1209
|
-
|
|
1210
|
-
return (0, import_lodash_es.cloneDeep)({
|
|
1177
|
+
return (0, _lodashEs.cloneDeep)({
|
|
1211
1178
|
...params,
|
|
1212
|
-
productIds: params.productIds ? [...params.productIds] :
|
|
1213
|
-
resourceIds: params.resourceIds ? [...params.resourceIds] :
|
|
1214
|
-
rawProducts: params.rawProducts ? (0,
|
|
1215
|
-
rawResources: params.rawResources ? (0,
|
|
1216
|
-
rawOccupancies: params.rawOccupancies ? (0,
|
|
1217
|
-
editingProductLineUids: params.editingProductLineUids ? [...params.editingProductLineUids] :
|
|
1218
|
-
dateRange: params.dateRange ? {
|
|
1219
|
-
|
|
1179
|
+
productIds: params.productIds ? [...params.productIds] : undefined,
|
|
1180
|
+
resourceIds: params.resourceIds ? [...params.resourceIds] : undefined,
|
|
1181
|
+
rawProducts: params.rawProducts ? (0, _lodashEs.cloneDeep)(params.rawProducts) : undefined,
|
|
1182
|
+
rawResources: params.rawResources ? (0, _lodashEs.cloneDeep)(params.rawResources) : undefined,
|
|
1183
|
+
rawOccupancies: params.rawOccupancies ? (0, _lodashEs.cloneDeep)(params.rawOccupancies) : undefined,
|
|
1184
|
+
editingProductLineUids: params.editingProductLineUids ? [...params.editingProductLineUids] : undefined,
|
|
1185
|
+
dateRange: params.dateRange ? {
|
|
1186
|
+
...params.dateRange
|
|
1187
|
+
} : undefined,
|
|
1188
|
+
businessHours: params.businessHours ? {
|
|
1189
|
+
...params.businessHours
|
|
1190
|
+
} : undefined,
|
|
1220
1191
|
_availabilityOperatingHours: params._availabilityOperatingHours ? {
|
|
1221
1192
|
source: params._availabilityOperatingHours.source,
|
|
1222
1193
|
scheduleIds: [...params._availabilityOperatingHours.scheduleIds]
|
|
1223
|
-
} :
|
|
1224
|
-
catalogScope:
|
|
1194
|
+
} : undefined,
|
|
1195
|
+
catalogScope: params.catalogScope?.type === 'schedule' ? {
|
|
1196
|
+
...params.catalogScope
|
|
1197
|
+
} : params.catalogScope?.type === 'productList' ? {
|
|
1198
|
+
type: 'productList',
|
|
1199
|
+
params: {
|
|
1200
|
+
...params.catalogScope.params
|
|
1201
|
+
}
|
|
1202
|
+
} : undefined
|
|
1225
1203
|
});
|
|
1226
1204
|
}
|
|
1227
1205
|
getAvailabilityDurationCandidateAnchorAt(projection) {
|
|
1228
|
-
const value = projection
|
|
1229
|
-
return typeof value ===
|
|
1206
|
+
const value = projection?.meta.durationCandidateAnchorAt;
|
|
1207
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
1230
1208
|
}
|
|
1231
1209
|
createAvailabilityContextRef(context) {
|
|
1232
1210
|
return {
|
|
@@ -1234,19 +1212,14 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1234
1212
|
};
|
|
1235
1213
|
}
|
|
1236
1214
|
async emitAvailabilityDiagnostic(payload) {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
...payload,
|
|
1243
|
-
registrySize: this.availabilityContextRegistry.size,
|
|
1244
|
-
projectionPoolSize: this.availabilityProjectionPool.size
|
|
1245
|
-
}
|
|
1246
|
-
));
|
|
1215
|
+
await this.core?.effects?.emit?.(`${this.name}:${_types.UnifiedBookingSalesHooks.onAvailabilityProjectionDiagnostic}`, {
|
|
1216
|
+
...payload,
|
|
1217
|
+
registrySize: this.availabilityContextRegistry.size,
|
|
1218
|
+
projectionPoolSize: this.availabilityProjectionPool.size
|
|
1219
|
+
});
|
|
1247
1220
|
}
|
|
1248
1221
|
touchAvailabilityContext(context) {
|
|
1249
|
-
context.lastAccessedAt =
|
|
1222
|
+
context.lastAccessedAt = new Date().toISOString();
|
|
1250
1223
|
this.availabilityContextRegistry.delete(context.contextId);
|
|
1251
1224
|
this.availabilityContextRegistry.set(context.contextId, context);
|
|
1252
1225
|
}
|
|
@@ -1256,14 +1229,12 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1256
1229
|
return entry;
|
|
1257
1230
|
}
|
|
1258
1231
|
removeAvailabilityProjection(projectionKey) {
|
|
1259
|
-
if (!projectionKey)
|
|
1260
|
-
return;
|
|
1232
|
+
if (!projectionKey) return;
|
|
1261
1233
|
this.availabilityProjectionPool.delete(projectionKey);
|
|
1262
1234
|
}
|
|
1263
1235
|
removeAvailabilityContext(contextId) {
|
|
1264
1236
|
const context = this.availabilityContextRegistry.get(contextId);
|
|
1265
|
-
if (!context)
|
|
1266
|
-
return;
|
|
1237
|
+
if (!context) return;
|
|
1267
1238
|
this.removeAvailabilityProjection(context.activeProjectionKey);
|
|
1268
1239
|
this.availabilityProjectionRefreshInFlight.delete(contextId);
|
|
1269
1240
|
this.availabilityCommitLocks.delete(contextId);
|
|
@@ -1272,12 +1243,11 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1272
1243
|
evictAvailabilityProjectionPool() {
|
|
1273
1244
|
while (this.availabilityProjectionPool.size > MAX_AVAILABILITY_PROJECTION_POOL) {
|
|
1274
1245
|
const oldestEntry = this.availabilityProjectionPool.entries().next().value;
|
|
1275
|
-
if (!oldestEntry)
|
|
1276
|
-
break;
|
|
1246
|
+
if (!oldestEntry) break;
|
|
1277
1247
|
const [projectionKey, projectionEntry] = oldestEntry;
|
|
1278
1248
|
this.availabilityProjectionPool.delete(projectionKey);
|
|
1279
1249
|
const context = this.availabilityContextRegistry.get(projectionEntry.contextId);
|
|
1280
|
-
if (
|
|
1250
|
+
if (context?.activeProjectionKey === projectionKey) {
|
|
1281
1251
|
context.activeProjectionKey = null;
|
|
1282
1252
|
context.refreshNonce += 1;
|
|
1283
1253
|
}
|
|
@@ -1286,8 +1256,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1286
1256
|
evictAvailabilityContextRegistry() {
|
|
1287
1257
|
while (this.availabilityContextRegistry.size > MAX_AVAILABILITY_CONTEXT_REGISTRY) {
|
|
1288
1258
|
const oldestContext = this.availabilityContextRegistry.entries().next().value;
|
|
1289
|
-
if (!oldestContext)
|
|
1290
|
-
break;
|
|
1259
|
+
if (!oldestContext) break;
|
|
1291
1260
|
this.removeAvailabilityContext(oldestContext[0]);
|
|
1292
1261
|
}
|
|
1293
1262
|
}
|
|
@@ -1297,7 +1266,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1297
1266
|
this.availabilityProjectionPool.clear();
|
|
1298
1267
|
this.availabilityPrepareInFlight.clear();
|
|
1299
1268
|
this.availabilityProjectionRefreshInFlight.clear();
|
|
1300
|
-
this.availabilityContextRegistry.forEach(
|
|
1269
|
+
this.availabilityContextRegistry.forEach(context => {
|
|
1301
1270
|
context.activeVersion += 1;
|
|
1302
1271
|
context.activeProjectionKey = null;
|
|
1303
1272
|
context.lastKnownOrderRevision = nextOrderRevision;
|
|
@@ -1322,40 +1291,39 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1322
1291
|
});
|
|
1323
1292
|
}
|
|
1324
1293
|
isSessionStoragePersistEnabled() {
|
|
1325
|
-
|
|
1326
|
-
return Boolean(
|
|
1327
|
-
this.cacheId && ((_a = this.otherParams) == null ? void 0 : _a.enableSessionStoragePersist) === true
|
|
1328
|
-
);
|
|
1294
|
+
return Boolean(this.cacheId && this.otherParams?.enableSessionStoragePersist === true);
|
|
1329
1295
|
}
|
|
1330
1296
|
shouldUseSessionStorageForSubModule(moduleName) {
|
|
1331
|
-
return this.isSessionStoragePersistEnabled() && moduleName ===
|
|
1297
|
+
return this.isSessionStoragePersistEnabled() && moduleName === 'order';
|
|
1332
1298
|
}
|
|
1299
|
+
|
|
1333
1300
|
/**
|
|
1334
1301
|
* 读取与当前 cacheId 同分桶的 Solution 会话状态。
|
|
1335
1302
|
* 该状态独立于 otherParams,适合页面皮肤保存短生命周期 UI 状态。
|
|
1336
1303
|
*/
|
|
1337
1304
|
getSessionStateValue(key) {
|
|
1338
|
-
if (!key || !this.isSessionStoragePersistEnabled())
|
|
1339
|
-
return void 0;
|
|
1305
|
+
if (!key || !this.isSessionStoragePersistEnabled()) return undefined;
|
|
1340
1306
|
const cacheData = this.readSessionCacheData();
|
|
1341
|
-
const solutionCache = cacheData
|
|
1307
|
+
const solutionCache = cacheData?.[this.name];
|
|
1342
1308
|
if (!isRecord(solutionCache) || !isRecord(solutionCache.sessionState)) {
|
|
1343
|
-
return
|
|
1309
|
+
return undefined;
|
|
1344
1310
|
}
|
|
1345
1311
|
return solutionCache.sessionState[key];
|
|
1346
1312
|
}
|
|
1313
|
+
|
|
1347
1314
|
/**
|
|
1348
1315
|
* 写入与当前 cacheId 同分桶的 Solution 会话状态。
|
|
1349
1316
|
* value 为 undefined 时删除该 key;存储失败不阻断业务流程。
|
|
1350
1317
|
*/
|
|
1351
1318
|
setSessionStateValue(key, value) {
|
|
1352
|
-
if (!key || !this.isSessionStoragePersistEnabled())
|
|
1353
|
-
return;
|
|
1319
|
+
if (!key || !this.isSessionStoragePersistEnabled()) return;
|
|
1354
1320
|
const cacheData = this.readSessionCacheData();
|
|
1355
|
-
const solutionCache = cacheData
|
|
1321
|
+
const solutionCache = cacheData?.[this.name];
|
|
1356
1322
|
const currentSessionState = isRecord(solutionCache) && isRecord(solutionCache.sessionState) ? solutionCache.sessionState : {};
|
|
1357
|
-
const nextSessionState = {
|
|
1358
|
-
|
|
1323
|
+
const nextSessionState = {
|
|
1324
|
+
...currentSessionState
|
|
1325
|
+
};
|
|
1326
|
+
if (value === undefined) {
|
|
1359
1327
|
delete nextSessionState[key];
|
|
1360
1328
|
} else {
|
|
1361
1329
|
nextSessionState[key] = value;
|
|
@@ -1363,52 +1331,54 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1363
1331
|
this.checkSaveCache({
|
|
1364
1332
|
cacheId: this.cacheId,
|
|
1365
1333
|
fatherModule: this.name,
|
|
1366
|
-
store: {
|
|
1367
|
-
|
|
1334
|
+
store: {
|
|
1335
|
+
sessionState: nextSessionState
|
|
1336
|
+
},
|
|
1337
|
+
cacheKey: ['sessionState']
|
|
1368
1338
|
});
|
|
1369
1339
|
}
|
|
1340
|
+
|
|
1370
1341
|
/**
|
|
1371
1342
|
* 清除当前 cacheId 的可恢复订单快照,同时保留 Solution UI sessionState。
|
|
1372
1343
|
* 用于支付跳转等终态场景,避免浏览器返回时恢复已提交订单。
|
|
1373
1344
|
*/
|
|
1374
1345
|
clearSessionStorageOrderSnapshot() {
|
|
1375
|
-
|
|
1376
|
-
(_b = (_a = this.store.order) == null ? void 0 : _a.clearSessionStoragePersistedTempOrder) == null ? void 0 : _b.call(_a);
|
|
1346
|
+
this.store.order?.clearSessionStoragePersistedTempOrder?.();
|
|
1377
1347
|
}
|
|
1348
|
+
|
|
1378
1349
|
/**
|
|
1379
1350
|
* Kiosk 不能把已收款订单留在本地等待重试:默认直接等待云端 checkout。
|
|
1380
1351
|
* 兼容仍需要 WebPOS 待同步模式的入口可显式传 checkoutSyncTaskEnabled: true。
|
|
1381
1352
|
*/
|
|
1382
1353
|
shouldUseCheckoutSyncTask() {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return typeof configured === "boolean" ? configured : false;
|
|
1354
|
+
const configured = this.otherParams?.checkoutSyncTaskEnabled;
|
|
1355
|
+
return typeof configured === 'boolean' ? configured : false;
|
|
1386
1356
|
}
|
|
1357
|
+
|
|
1387
1358
|
/** UnifiedBookingSales 的真实结账默认请求收据数据,供云端确认后再本地打印。 */
|
|
1388
1359
|
getDefaultCheckoutSmallTicketDataFlag() {
|
|
1389
1360
|
return 1;
|
|
1390
1361
|
}
|
|
1391
1362
|
getRegisteredModuleNames() {
|
|
1392
|
-
return [...super.getRegisteredModuleNames(),
|
|
1363
|
+
return [...super.getRegisteredModuleNames(), 'openData'];
|
|
1393
1364
|
}
|
|
1394
1365
|
createSubModule(moduleName) {
|
|
1395
|
-
if (moduleName ===
|
|
1396
|
-
return new
|
|
1366
|
+
if (moduleName === 'openData') {
|
|
1367
|
+
return new _modules.OpenDataModule(`${this.name}_openData`);
|
|
1397
1368
|
}
|
|
1398
1369
|
return super.createSubModule(moduleName);
|
|
1399
1370
|
}
|
|
1400
1371
|
async addNewOrder() {
|
|
1401
|
-
|
|
1402
|
-
const sessionRecord = this.isSessionStoragePersistEnabled() ? (_b = (_a = this.store.order) == null ? void 0 : _a.consumeSessionStorageRestore) == null ? void 0 : _b.call(_a) : null;
|
|
1372
|
+
const sessionRecord = this.isSessionStoragePersistEnabled() ? this.store.order?.consumeSessionStorageRestore?.() : null;
|
|
1403
1373
|
if (sessionRecord) {
|
|
1404
1374
|
await this.loadSalesDetail({
|
|
1405
1375
|
preloadedSalesDetail: sessionRecord,
|
|
1406
|
-
hydrateSource:
|
|
1376
|
+
hydrateSource: 'sessionStorage'
|
|
1407
1377
|
});
|
|
1408
1378
|
}
|
|
1409
1379
|
const tempOrder = await super.addNewOrder();
|
|
1410
1380
|
if (sessionRecord) {
|
|
1411
|
-
|
|
1381
|
+
this.store.order?.persistTempOrder();
|
|
1412
1382
|
}
|
|
1413
1383
|
if (this.isAuthenticatedCustomerUserPlatform()) {
|
|
1414
1384
|
try {
|
|
@@ -1417,102 +1387,82 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1417
1387
|
await this.refreshLegacySessionCustomerDiscounts(tempOrder);
|
|
1418
1388
|
}
|
|
1419
1389
|
} catch (error) {
|
|
1420
|
-
console.warn(
|
|
1421
|
-
"[UnifiedBookingSales] Failed to sync authenticated order customer after addNewOrder",
|
|
1422
|
-
error
|
|
1423
|
-
);
|
|
1390
|
+
console.warn('[UnifiedBookingSales] Failed to sync authenticated order customer after addNewOrder', error);
|
|
1424
1391
|
}
|
|
1425
1392
|
} else if (sessionRecord) {
|
|
1426
1393
|
try {
|
|
1427
1394
|
await this.refreshLegacySessionCustomerDiscounts(tempOrder);
|
|
1428
1395
|
} catch (error) {
|
|
1429
|
-
console.warn(
|
|
1430
|
-
"[UnifiedBookingSales] Failed to refresh cached customer discounts after addNewOrder",
|
|
1431
|
-
error
|
|
1432
|
-
);
|
|
1396
|
+
console.warn('[UnifiedBookingSales] Failed to refresh cached customer discounts after addNewOrder', error);
|
|
1433
1397
|
}
|
|
1434
1398
|
}
|
|
1435
|
-
return
|
|
1399
|
+
return this.store.order?.getTempOrder() || tempOrder;
|
|
1436
1400
|
}
|
|
1437
1401
|
async syncAuthenticatedOrderCustomer() {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
const authenticatedCustomerId = toCustomerId(authenticatedCustomer == null ? void 0 : authenticatedCustomer.id);
|
|
1443
|
-
if (!authenticatedCustomerId)
|
|
1444
|
-
return false;
|
|
1402
|
+
if (!this.isAuthenticatedCustomerUserPlatform()) return false;
|
|
1403
|
+
const authenticatedCustomer = this.core?.getPlugin?.('user')?.get?.();
|
|
1404
|
+
const authenticatedCustomerId = toCustomerId(authenticatedCustomer?.id);
|
|
1405
|
+
if (!authenticatedCustomerId) return false;
|
|
1445
1406
|
const tempOrder = this.getTempOrder();
|
|
1446
1407
|
const cachedCustomerId = resolveTempOrderCustomerId(tempOrder);
|
|
1447
1408
|
if (cachedCustomerId !== null && cachedCustomerId !== authenticatedCustomerId) {
|
|
1448
1409
|
await this.resetCachedCustomerDiscountState();
|
|
1449
1410
|
}
|
|
1450
|
-
|
|
1411
|
+
this.store.customer?.setSelectedCustomer?.(authenticatedCustomer);
|
|
1451
1412
|
this.setOrderCustomer(authenticatedCustomer);
|
|
1452
|
-
await Promise.all([
|
|
1453
|
-
this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId),
|
|
1454
|
-
this.waitForOrderCustomerPromotionRefresh()
|
|
1455
|
-
]);
|
|
1413
|
+
await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
|
|
1456
1414
|
return true;
|
|
1457
1415
|
}
|
|
1458
1416
|
isAuthenticatedCustomerUserPlatform() {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
return platform === "pc" || platform === "h5";
|
|
1417
|
+
const platform = String(this.otherParams?.platform || '').toLowerCase();
|
|
1418
|
+
return platform === 'pc' || platform === 'h5';
|
|
1462
1419
|
}
|
|
1463
1420
|
resolveDiscountOrderIdentity(tempOrder) {
|
|
1464
|
-
|
|
1465
|
-
return (tempOrder == null ? void 0 : tempOrder.order_id) || ((_c = (_b = (_a = this.store.order) == null ? void 0 : _a.getOrderIdentity) == null ? void 0 : _b.call(_a)) == null ? void 0 : _c.orderId) || null;
|
|
1421
|
+
return tempOrder?.order_id || this.store.order?.getOrderIdentity?.()?.orderId || null;
|
|
1466
1422
|
}
|
|
1467
1423
|
resolveDiscountAction(orderId) {
|
|
1468
|
-
if (!orderId || isLocalOrderId(orderId))
|
|
1469
|
-
|
|
1470
|
-
return "edit";
|
|
1424
|
+
if (!orderId || isLocalOrderId(orderId)) return 'create';
|
|
1425
|
+
return 'edit';
|
|
1471
1426
|
}
|
|
1472
1427
|
async resetCachedCustomerDiscountState() {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
(_d = (_c = this.store.customer) == null ? void 0 : _c.setSelectedCustomer) == null ? void 0 : _d.call(_c, null);
|
|
1428
|
+
this.store.order?.clearOrderCustomer?.();
|
|
1429
|
+
this.store.customer?.setSelectedCustomer?.(null);
|
|
1476
1430
|
this.discountConfigCacheKey = null;
|
|
1477
1431
|
this.hasManualDiscountSelection = false;
|
|
1478
|
-
const orderStore =
|
|
1432
|
+
const orderStore = this.store.order?.store || {};
|
|
1479
1433
|
const discountModule = orderStore.discount;
|
|
1480
|
-
const filteredOriginalDiscounts = filterCustomerBoundDiscounts(
|
|
1481
|
-
|
|
1482
|
-
);
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
await ((_m = (_l = this.store.order) == null ? void 0 : _l.recalculateSummary) == null ? void 0 : _m.call(_l, { createIfMissing: true }));
|
|
1490
|
-
(_o = (_n = this.store.order) == null ? void 0 : _n.persistTempOrder) == null ? void 0 : _o.call(_n);
|
|
1434
|
+
const filteredOriginalDiscounts = filterCustomerBoundDiscounts(discountModule?.getOriginalDiscountList?.() || []);
|
|
1435
|
+
const filteredCurrentDiscounts = filterCustomerBoundDiscounts(discountModule?.getDiscountList?.() || []);
|
|
1436
|
+
await discountModule?.setOriginalDiscountList?.(filteredOriginalDiscounts);
|
|
1437
|
+
await discountModule?.setDiscountList?.(filteredCurrentDiscounts);
|
|
1438
|
+
this.store.order?.applyDiscount?.();
|
|
1439
|
+
await this.store.order?.recalculateSummary?.({
|
|
1440
|
+
createIfMissing: true
|
|
1441
|
+
});
|
|
1442
|
+
this.store.order?.persistTempOrder?.();
|
|
1491
1443
|
}
|
|
1492
1444
|
async refreshLegacySessionCustomerDiscounts(tempOrder) {
|
|
1493
1445
|
const customerId = resolveTempOrderCustomerId(tempOrder);
|
|
1494
|
-
if (!customerId)
|
|
1495
|
-
return void 0;
|
|
1446
|
+
if (!customerId) return undefined;
|
|
1496
1447
|
const loadDiscountConfig = super.loadDiscountConfig.bind(this);
|
|
1497
1448
|
return await loadDiscountConfig({
|
|
1498
1449
|
customerId,
|
|
1499
|
-
action: this.resolveDiscountAction(
|
|
1500
|
-
this.resolveDiscountOrderIdentity(tempOrder)
|
|
1501
|
-
)
|
|
1450
|
+
action: this.resolveDiscountAction(this.resolveDiscountOrderIdentity(tempOrder))
|
|
1502
1451
|
});
|
|
1503
1452
|
}
|
|
1453
|
+
|
|
1504
1454
|
/**
|
|
1505
1455
|
* Loads OpenData without borrowing BookingTicket. Cache ownership is tied to the exact
|
|
1506
1456
|
* business/channel target so a reused solution instance cannot leak another entry's menus.
|
|
1507
1457
|
*/
|
|
1508
1458
|
async loadOpenData(params) {
|
|
1509
1459
|
if (!this.store.openData) {
|
|
1510
|
-
throw new Error(
|
|
1460
|
+
throw new Error('[UnifiedBookingSales] openData 模块未初始化');
|
|
1511
1461
|
}
|
|
1512
|
-
const businessCode = String(params.businessCode ||
|
|
1513
|
-
const channel = String(params.channel ||
|
|
1462
|
+
const businessCode = String(params.businessCode || '').trim();
|
|
1463
|
+
const channel = String(params.channel || '').trim();
|
|
1514
1464
|
if (!businessCode || !channel) {
|
|
1515
|
-
throw new Error(
|
|
1465
|
+
throw new Error('[UnifiedBookingSales] OpenData 需要 businessCode 与 channel');
|
|
1516
1466
|
}
|
|
1517
1467
|
const target = `${businessCode}+${channel}`;
|
|
1518
1468
|
const cachedData = this.store.openData.getOpenData();
|
|
@@ -1529,7 +1479,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1529
1479
|
}
|
|
1530
1480
|
this.loadOpenDataInFlightTarget = target;
|
|
1531
1481
|
this.loadOpenDataInFlight = this.store.openData.fetchOpenData({
|
|
1532
|
-
scope:
|
|
1482
|
+
scope: 'board',
|
|
1533
1483
|
target,
|
|
1534
1484
|
section_code: [...OPEN_DATA_SECTION_CODES]
|
|
1535
1485
|
});
|
|
@@ -1546,28 +1496,28 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1546
1496
|
}
|
|
1547
1497
|
}
|
|
1548
1498
|
async getOpenData() {
|
|
1549
|
-
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
if (!businessCode || !channel)
|
|
1553
|
-
return null;
|
|
1499
|
+
const businessCode = String(this.otherParams?.businessCode || '').trim();
|
|
1500
|
+
const channel = String(this.otherParams?.channel || '').trim();
|
|
1501
|
+
if (!businessCode || !channel) return null;
|
|
1554
1502
|
const target = `${businessCode}+${channel}`;
|
|
1555
|
-
const cachedData =
|
|
1556
|
-
if (cachedData && this.openDataTarget === target)
|
|
1557
|
-
|
|
1558
|
-
|
|
1503
|
+
const cachedData = this.store.openData?.getOpenData() || null;
|
|
1504
|
+
if (cachedData && this.openDataTarget === target) return cachedData;
|
|
1505
|
+
return this.loadOpenData({
|
|
1506
|
+
businessCode,
|
|
1507
|
+
channel
|
|
1508
|
+
});
|
|
1559
1509
|
}
|
|
1510
|
+
|
|
1560
1511
|
/**
|
|
1561
1512
|
* Loads the product catalog used by both UI and Planner. Schedule data is requested with each
|
|
1562
1513
|
* product because session products carry their fixed intervals there.
|
|
1563
1514
|
*/
|
|
1564
1515
|
async loadProducts(params = {}, options) {
|
|
1565
|
-
if (!this.store.products)
|
|
1566
|
-
|
|
1567
|
-
const
|
|
1568
|
-
const schedule_datetime = params.schedule_datetime || `${schedule_date} ${(0, import_dayjs.default)().format("HH:mm:ss")}`;
|
|
1516
|
+
if (!this.store.products) throw new Error('[UnifiedBookingSales] products 模块未初始化');
|
|
1517
|
+
const schedule_date = params.schedule_date || (0, _dayjs.default)().format('YYYY-MM-DD');
|
|
1518
|
+
const schedule_datetime = params.schedule_datetime || `${schedule_date} ${(0, _dayjs.default)().format('HH:mm:ss')}`;
|
|
1569
1519
|
const result = await this.store.products.loadProducts({
|
|
1570
|
-
with_count: [
|
|
1520
|
+
with_count: ['bundleGroup', 'optionGroup'],
|
|
1571
1521
|
with_schedule: 1,
|
|
1572
1522
|
...params,
|
|
1573
1523
|
schedule_date,
|
|
@@ -1578,19 +1528,19 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1578
1528
|
await this.core.effects.emit(`${this.name}:onProductsLoaded`, result);
|
|
1579
1529
|
return result;
|
|
1580
1530
|
}
|
|
1531
|
+
|
|
1581
1532
|
/** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
|
|
1582
1533
|
async loadScheduleAvailableDate(params) {
|
|
1583
|
-
if (!this.store.schedule)
|
|
1584
|
-
throw new Error("[UnifiedBookingSales] schedule 模块未初始化");
|
|
1534
|
+
if (!this.store.schedule) throw new Error('[UnifiedBookingSales] schedule 模块未初始化');
|
|
1585
1535
|
return this.store.schedule.loadScheduleAvailableDate(params);
|
|
1586
1536
|
}
|
|
1537
|
+
|
|
1587
1538
|
/**
|
|
1588
1539
|
* Implements the date-first product pool: the chosen date's product ids plus globally sellable
|
|
1589
1540
|
* other_product_ids. If a batch product query is unexpectedly empty, retry one id at a time so
|
|
1590
1541
|
* a partial backend incompatibility does not hide the entire date's catalog.
|
|
1591
1542
|
*/
|
|
1592
1543
|
async loadProductsByScheduleDate(params) {
|
|
1593
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1594
1544
|
const {
|
|
1595
1545
|
date,
|
|
1596
1546
|
custom_page_id,
|
|
@@ -1598,11 +1548,10 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1598
1548
|
product_ids,
|
|
1599
1549
|
...productLoadParams
|
|
1600
1550
|
} = params;
|
|
1601
|
-
if (!this.store.schedule)
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
const hasOtherProducts = (((_d = (_c = this.store.schedule).getOtherProductsIds) == null ? void 0 : _d.call(_c)) || []).length > 0;
|
|
1551
|
+
if (!this.store.schedule) throw new Error('[UnifiedBookingSales] schedule 模块未初始化');
|
|
1552
|
+
const cachedDates = this.store.schedule.getAvailabilityScheduleDateList?.() || [];
|
|
1553
|
+
const hasCachedDate = cachedDates.some(item => item.date === date);
|
|
1554
|
+
const hasOtherProducts = (this.store.schedule.getOtherProductsIds?.() || []).length > 0;
|
|
1606
1555
|
if (!hasCachedDate && !hasOtherProducts) {
|
|
1607
1556
|
await this.loadScheduleAvailableDate({
|
|
1608
1557
|
startDate: date,
|
|
@@ -1611,12 +1560,9 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1611
1560
|
channel
|
|
1612
1561
|
});
|
|
1613
1562
|
}
|
|
1614
|
-
const scheduleDate =
|
|
1615
|
-
const productIdFilter =
|
|
1616
|
-
const availableProductIds = [
|
|
1617
|
-
...(scheduleDate == null ? void 0 : scheduleDate.product_ids) || [],
|
|
1618
|
-
...((_i = (_h = this.store.schedule).getOtherProductsIds) == null ? void 0 : _i.call(_h)) || []
|
|
1619
|
-
].filter((id, index, self) => self.indexOf(id) === index).filter((id) => !productIdFilter || productIdFilter.has(String(id)));
|
|
1563
|
+
const scheduleDate = this.store.schedule.getAvailabilityScheduleDateList?.()?.find(item => item.date === date);
|
|
1564
|
+
const productIdFilter = product_ids?.length ? new Set(product_ids.map(id => String(id))) : null;
|
|
1565
|
+
const availableProductIds = [...(scheduleDate?.product_ids || []), ...(this.store.schedule.getOtherProductsIds?.() || [])].filter((id, index, self) => self.indexOf(id) === index).filter(id => !productIdFilter || productIdFilter.has(String(id)));
|
|
1620
1566
|
if (!availableProductIds.length) {
|
|
1621
1567
|
this.unifiedProductCatalog = [];
|
|
1622
1568
|
await this.core.effects.emit(`${this.name}:onProductsLoaded`, []);
|
|
@@ -1627,8 +1573,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1627
1573
|
product_ids: availableProductIds,
|
|
1628
1574
|
schedule_date: date
|
|
1629
1575
|
});
|
|
1630
|
-
if (products.length || availableProductIds.length <= 1)
|
|
1631
|
-
return products;
|
|
1576
|
+
if (products.length || availableProductIds.length <= 1) return products;
|
|
1632
1577
|
const fallbackProducts = [];
|
|
1633
1578
|
for (const productId of availableProductIds) {
|
|
1634
1579
|
const result = await this.loadProducts({
|
|
@@ -1636,9 +1581,9 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1636
1581
|
product_ids: [productId],
|
|
1637
1582
|
schedule_date: date
|
|
1638
1583
|
});
|
|
1639
|
-
result.forEach(
|
|
1640
|
-
const id =
|
|
1641
|
-
if (!fallbackProducts.some(
|
|
1584
|
+
result.forEach(product => {
|
|
1585
|
+
const id = product?.id ?? product?.product_id;
|
|
1586
|
+
if (!fallbackProducts.some(item => String(item?.id ?? item?.product_id) === String(id))) {
|
|
1642
1587
|
fallbackProducts.push(product);
|
|
1643
1588
|
}
|
|
1644
1589
|
});
|
|
@@ -1647,10 +1592,14 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1647
1592
|
await this.core.effects.emit(`${this.name}:onProductsLoaded`, fallbackProducts);
|
|
1648
1593
|
return fallbackProducts;
|
|
1649
1594
|
}
|
|
1595
|
+
|
|
1650
1596
|
/** Returns a copy so UI consumers cannot accidentally mutate the solution's product cache. */
|
|
1651
1597
|
getProductCatalog() {
|
|
1652
|
-
return {
|
|
1598
|
+
return {
|
|
1599
|
+
products: [...this.unifiedProductCatalog]
|
|
1600
|
+
};
|
|
1653
1601
|
}
|
|
1602
|
+
|
|
1654
1603
|
/**
|
|
1655
1604
|
* Read-only compatibility view for salesSdk skins. It reconnects a booking to its product but
|
|
1656
1605
|
* must not be used to mutate tempOrder; all writes go through BaseSales / planner APIs.
|
|
@@ -1682,6 +1631,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1682
1631
|
// }),
|
|
1683
1632
|
// };
|
|
1684
1633
|
// }
|
|
1634
|
+
|
|
1685
1635
|
/**
|
|
1686
1636
|
* Loads the current customer's product coupons and discount cards after planner lines exist,
|
|
1687
1637
|
* then optionally lets the existing Rules engine select the best valid combination. This is a
|
|
@@ -1689,7 +1639,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1689
1639
|
*/
|
|
1690
1640
|
async applyPlannerDiscounts(params = {}) {
|
|
1691
1641
|
const tempOrder = this.getTempOrder();
|
|
1692
|
-
const requestedCustomerId = params.customerId ??
|
|
1642
|
+
const requestedCustomerId = params.customerId ?? tempOrder?.customer_id;
|
|
1693
1643
|
const parsedCustomerId = Number(requestedCustomerId);
|
|
1694
1644
|
const customerId = Number.isFinite(parsedCustomerId) && parsedCustomerId > 1 ? parsedCustomerId : null;
|
|
1695
1645
|
const summaryBefore = await this.getSummary();
|
|
@@ -1697,31 +1647,39 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1697
1647
|
return buildPlannerDiscountResult({
|
|
1698
1648
|
customerId: null,
|
|
1699
1649
|
discountList: this.getDiscountList(),
|
|
1700
|
-
products:
|
|
1650
|
+
products: tempOrder?.products || [],
|
|
1701
1651
|
summaryBefore,
|
|
1702
1652
|
summaryAfter: summaryBefore,
|
|
1703
|
-
status:
|
|
1653
|
+
status: 'no_customer'
|
|
1704
1654
|
});
|
|
1705
1655
|
}
|
|
1706
|
-
await this.loadDiscountConfig({
|
|
1656
|
+
await this.loadDiscountConfig({
|
|
1657
|
+
customerId,
|
|
1658
|
+
action: 'create'
|
|
1659
|
+
});
|
|
1707
1660
|
const preferredDiscountId = params.preferredDiscountId;
|
|
1708
|
-
const preferredDiscount = preferredDiscountId ===
|
|
1661
|
+
const preferredDiscount = preferredDiscountId === undefined || preferredDiscountId === null ? undefined : this.getDiscountList().find(discount => String(resolvePlannerDiscountId(discount) ?? '') === String(preferredDiscountId));
|
|
1709
1662
|
if (preferredDiscount) {
|
|
1663
|
+
// A requested card is an explicit UI choice, not a best-effort preference. Clear prior
|
|
1664
|
+
// automatic selections first; otherwise Rules may keep the old selected card and silently
|
|
1665
|
+
// apply it instead of the card the customer clicked.
|
|
1710
1666
|
const preferredId = resolvePlannerDiscountId(preferredDiscount);
|
|
1711
|
-
const selectedOtherDiscounts = this.getDiscountList().filter(
|
|
1667
|
+
const selectedOtherDiscounts = this.getDiscountList().filter(discount => discount.isSelected === true && String(resolvePlannerDiscountId(discount) ?? '') !== String(preferredId));
|
|
1712
1668
|
for (const discount of selectedOtherDiscounts) {
|
|
1713
1669
|
const discountId = Number(resolvePlannerDiscountId(discount));
|
|
1714
1670
|
if (Number.isFinite(discountId)) {
|
|
1715
|
-
await this.setDiscountSelected({
|
|
1671
|
+
await this.setDiscountSelected({
|
|
1672
|
+
discountId,
|
|
1673
|
+
isSelected: false
|
|
1674
|
+
});
|
|
1716
1675
|
}
|
|
1717
1676
|
}
|
|
1718
1677
|
await this.setDiscountSelected({
|
|
1719
1678
|
discountId: Number(preferredId),
|
|
1720
1679
|
isSelected: true
|
|
1721
1680
|
});
|
|
1722
|
-
} else if (preferredDiscountId ===
|
|
1723
|
-
if (params.applyBestDiscount !== false)
|
|
1724
|
-
await this.bestDiscount();
|
|
1681
|
+
} else if (preferredDiscountId === undefined || preferredDiscountId === null) {
|
|
1682
|
+
if (params.applyBestDiscount !== false) await this.bestDiscount();
|
|
1725
1683
|
} else if (params.applyBestDiscount !== false) {
|
|
1726
1684
|
await this.bestDiscount();
|
|
1727
1685
|
}
|
|
@@ -1729,206 +1687,204 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1729
1687
|
return buildPlannerDiscountResult({
|
|
1730
1688
|
customerId,
|
|
1731
1689
|
discountList: this.getDiscountList(),
|
|
1732
|
-
products:
|
|
1690
|
+
products: nextTempOrder?.products || [],
|
|
1733
1691
|
summaryBefore,
|
|
1734
1692
|
summaryAfter: await this.getSummary(),
|
|
1735
|
-
status: params.applyBestDiscount === false && !preferredDiscount ?
|
|
1693
|
+
status: params.applyBestDiscount === false && !preferredDiscount ? 'loaded' : 'applied',
|
|
1736
1694
|
preferredDiscountId,
|
|
1737
|
-
preferredDiscountFound: preferredDiscountId ===
|
|
1695
|
+
preferredDiscountFound: preferredDiscountId === undefined || preferredDiscountId === null ? undefined : Boolean(preferredDiscount)
|
|
1738
1696
|
});
|
|
1739
1697
|
}
|
|
1740
1698
|
buildUnifiedOrderProduct(sourceProduct, ownershipKey, product) {
|
|
1741
|
-
const normalizedProductRaw =
|
|
1699
|
+
const normalizedProductRaw = product?.raw || {};
|
|
1742
1700
|
const orderProduct = this.transformBaseProductToOrderProduct({
|
|
1743
1701
|
payload: {
|
|
1744
1702
|
...sourceProduct,
|
|
1745
|
-
id: sourceProduct.id ??
|
|
1746
|
-
product_id: sourceProduct.product_id ?? sourceProduct.id ??
|
|
1747
|
-
quantity: sourceProduct.quantity ?? sourceProduct.num ??
|
|
1703
|
+
id: sourceProduct.id ?? product?.id,
|
|
1704
|
+
product_id: sourceProduct.product_id ?? sourceProduct.id ?? product?.id,
|
|
1705
|
+
quantity: sourceProduct.quantity ?? sourceProduct.num ?? product?.quantity ?? 1,
|
|
1748
1706
|
price: sourceProduct.price ?? normalizedProductRaw.price,
|
|
1749
1707
|
selling_price: sourceProduct.selling_price ?? sourceProduct.price ?? normalizedProductRaw.selling_price ?? normalizedProductRaw.price,
|
|
1750
1708
|
metadata: {
|
|
1751
|
-
...sourceProduct.metadata || {},
|
|
1709
|
+
...(sourceProduct.metadata || {}),
|
|
1752
1710
|
[ownershipKey]: true
|
|
1753
1711
|
}
|
|
1754
1712
|
},
|
|
1755
1713
|
sourceProduct
|
|
1756
1714
|
});
|
|
1757
|
-
if (orderProduct && typeof orderProduct ===
|
|
1715
|
+
if (orderProduct && typeof orderProduct === 'object') {
|
|
1716
|
+
// BaseSales may rebuild metadata from its price source. Stamp ownership after that transform
|
|
1717
|
+
// so each UnifiedBookingSales entry point can replace only its own temporary lines.
|
|
1758
1718
|
orderProduct.metadata = {
|
|
1759
|
-
...orderProduct.metadata || {},
|
|
1719
|
+
...(orderProduct.metadata || {}),
|
|
1760
1720
|
[ownershipKey]: true
|
|
1761
1721
|
};
|
|
1762
1722
|
}
|
|
1763
1723
|
return orderProduct;
|
|
1764
1724
|
}
|
|
1725
|
+
|
|
1765
1726
|
/** Replacing a staged selection is idempotent and never removes unrelated cart lines. */
|
|
1766
1727
|
async removeExistingStagedLines(ownershipKey) {
|
|
1767
|
-
|
|
1768
|
-
const existingLines = (((_a = this.getTempOrder()) == null ? void 0 : _a.products) || []).filter((product) => {
|
|
1769
|
-
var _a2;
|
|
1770
|
-
return ((_a2 = product == null ? void 0 : product.metadata) == null ? void 0 : _a2[ownershipKey]) === true;
|
|
1771
|
-
});
|
|
1728
|
+
const existingLines = (this.getTempOrder()?.products || []).filter(product => product?.metadata?.[ownershipKey] === true);
|
|
1772
1729
|
if (existingLines.length > 0) {
|
|
1773
1730
|
await this.removeProductsFromOrder(existingLines.map(buildAvailabilityLineIdentity));
|
|
1774
1731
|
}
|
|
1775
1732
|
}
|
|
1776
1733
|
async stageProducts(params, ownershipKey) {
|
|
1777
|
-
var _a;
|
|
1778
1734
|
const productIds = params.productIds || [];
|
|
1779
|
-
if (!productIds.length)
|
|
1780
|
-
|
|
1781
|
-
if ((_a = params.rawProducts) == null ? void 0 : _a.length) {
|
|
1735
|
+
if (!productIds.length) return this.getTempOrder();
|
|
1736
|
+
if (params.rawProducts?.length) {
|
|
1782
1737
|
this.unifiedProductCatalog = params.rawProducts;
|
|
1783
1738
|
} else {
|
|
1784
|
-
const existingIds = new Set((this.unifiedProductCatalog || []).map(
|
|
1785
|
-
const missingIds = productIds.filter(
|
|
1739
|
+
const existingIds = new Set((this.unifiedProductCatalog || []).map(product => String(product.id ?? product.product_id)));
|
|
1740
|
+
const missingIds = productIds.filter(id => !existingIds.has(String(id)));
|
|
1786
1741
|
if (missingIds.length || params.productLoadParams) {
|
|
1787
1742
|
await this.loadProducts({
|
|
1788
|
-
...params.productLoadParams || {},
|
|
1743
|
+
...(params.productLoadParams || {}),
|
|
1789
1744
|
product_ids: productIds.map(Number).filter(Number.isFinite)
|
|
1790
1745
|
});
|
|
1791
1746
|
}
|
|
1792
1747
|
}
|
|
1793
1748
|
await this.removeExistingStagedLines(ownershipKey);
|
|
1794
|
-
const productIdSet = new Set(productIds.map(
|
|
1795
|
-
const selectedProducts = (this.unifiedProductCatalog || []).filter(
|
|
1749
|
+
const productIdSet = new Set(productIds.map(id => String(id)));
|
|
1750
|
+
const selectedProducts = (this.unifiedProductCatalog || []).filter(product => productIdSet.has(String(product.id ?? product.product_id)));
|
|
1796
1751
|
for (const sourceProduct of selectedProducts) {
|
|
1797
1752
|
const productInput = this.buildUnifiedOrderProduct(sourceProduct, ownershipKey);
|
|
1798
|
-
if (productInput)
|
|
1799
|
-
await this.addProductToOrder(productInput);
|
|
1753
|
+
if (productInput) await this.addProductToOrder(productInput);
|
|
1800
1754
|
}
|
|
1801
1755
|
return this.getTempOrder();
|
|
1802
1756
|
}
|
|
1757
|
+
|
|
1803
1758
|
/**
|
|
1804
1759
|
* Adds ordinary retail products without asking ResourcePlanner for booking facts.
|
|
1805
1760
|
* Its staged lines have separate ownership so planner and retail skins can coexist safely.
|
|
1806
1761
|
*/
|
|
1807
1762
|
async stageRetailProducts(params) {
|
|
1808
|
-
return this.stageProducts(params,
|
|
1763
|
+
return this.stageProducts(params, 'unified_booking_sales_retail');
|
|
1809
1764
|
}
|
|
1765
|
+
|
|
1810
1766
|
/**
|
|
1811
1767
|
* Adds one retail line without replacing the cart's existing retail selection.
|
|
1812
1768
|
* This is intentionally separate from stageRetailProducts(), whose contract is
|
|
1813
1769
|
* "replace the staged selection" for demo and wizard-like callers.
|
|
1814
1770
|
*/
|
|
1815
1771
|
async addRetailProduct(params) {
|
|
1816
|
-
var _a, _b;
|
|
1817
1772
|
const quantity = Math.floor(Number(params.quantity ?? 1));
|
|
1818
1773
|
if (!Number.isFinite(quantity) || quantity <= 0) {
|
|
1819
|
-
throw new Error(
|
|
1774
|
+
throw new Error('[UnifiedBookingSales] retail 商品数量必须大于 0');
|
|
1820
1775
|
}
|
|
1821
|
-
const productId = params.productId ??
|
|
1776
|
+
const productId = params.productId ?? params.product?.id ?? params.product?.product_id;
|
|
1822
1777
|
let sourceProduct = params.product;
|
|
1823
|
-
if (!sourceProduct && productId !==
|
|
1824
|
-
sourceProduct = this.unifiedProductCatalog.find(
|
|
1825
|
-
(product) => String(product.id ?? product.product_id) === String(productId)
|
|
1826
|
-
);
|
|
1778
|
+
if (!sourceProduct && productId !== undefined && productId !== null) {
|
|
1779
|
+
sourceProduct = this.unifiedProductCatalog.find(product => String(product.id ?? product.product_id) === String(productId));
|
|
1827
1780
|
}
|
|
1828
|
-
if (!sourceProduct && productId !==
|
|
1781
|
+
if (!sourceProduct && productId !== undefined && productId !== null) {
|
|
1829
1782
|
const products = await this.loadProducts({
|
|
1830
|
-
...params.productLoadParams || {},
|
|
1783
|
+
...(params.productLoadParams || {}),
|
|
1831
1784
|
product_ids: [Number(productId)].filter(Number.isFinite)
|
|
1832
1785
|
});
|
|
1833
|
-
sourceProduct = products.find(
|
|
1834
|
-
(product) => String(product.id ?? product.product_id) === String(productId)
|
|
1835
|
-
);
|
|
1786
|
+
sourceProduct = products.find(product => String(product.id ?? product.product_id) === String(productId));
|
|
1836
1787
|
}
|
|
1837
1788
|
if (!sourceProduct) {
|
|
1838
|
-
throw new Error(`[UnifiedBookingSales] 未找到零售商品 ${String(productId ??
|
|
1789
|
+
throw new Error(`[UnifiedBookingSales] 未找到零售商品 ${String(productId ?? '')}`.trim());
|
|
1839
1790
|
}
|
|
1840
|
-
const orderProduct = this.buildUnifiedOrderProduct(
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
},
|
|
1845
|
-
"unified_booking_sales_retail"
|
|
1846
|
-
);
|
|
1791
|
+
const orderProduct = this.buildUnifiedOrderProduct({
|
|
1792
|
+
...sourceProduct,
|
|
1793
|
+
quantity
|
|
1794
|
+
}, 'unified_booking_sales_retail');
|
|
1847
1795
|
if (!orderProduct) {
|
|
1848
|
-
throw new Error(
|
|
1796
|
+
throw new Error('[UnifiedBookingSales] 无法生成零售商品订单行');
|
|
1849
1797
|
}
|
|
1850
1798
|
return this.addProductToOrder(orderProduct);
|
|
1851
1799
|
}
|
|
1852
1800
|
normalizeAvailabilityDateRange(params, timezone) {
|
|
1853
|
-
|
|
1854
|
-
const
|
|
1855
|
-
const
|
|
1856
|
-
const endDate = ((_b = params.dateRange) == null ? void 0 : _b.endDate) || (0, import_localClock.formatLocalDate)((0, import_localClock.addLocalCalendarDays)((0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(startDate)), 31));
|
|
1801
|
+
const today = (0, _localClock.formatLocalDate)((0, _localClock.localDateToScalar)(new Date()));
|
|
1802
|
+
const startDate = params.dateRange?.startDate || today;
|
|
1803
|
+
const endDate = params.dateRange?.endDate || (0, _localClock.formatLocalDate)((0, _localClock.addLocalCalendarDays)((0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(startDate)), 31));
|
|
1857
1804
|
let startScalar;
|
|
1858
1805
|
let endScalar;
|
|
1859
1806
|
try {
|
|
1860
|
-
startScalar = (0,
|
|
1861
|
-
endScalar = (0,
|
|
1807
|
+
startScalar = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(startDate));
|
|
1808
|
+
endScalar = (0, _localClock.localDateToScalarStart)((0, _localClock.parseLocalDate)(endDate));
|
|
1862
1809
|
} catch (error) {
|
|
1863
|
-
throw new
|
|
1864
|
-
dateRange: {
|
|
1810
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_DATE_RANGE', '日期范围必须是有效的 YYYY-MM-DD 且结束日期不早于开始日期', {
|
|
1811
|
+
dateRange: {
|
|
1812
|
+
startDate,
|
|
1813
|
+
endDate
|
|
1814
|
+
},
|
|
1865
1815
|
timezone,
|
|
1866
1816
|
cause: error instanceof Error ? error.message : String(error)
|
|
1867
1817
|
});
|
|
1868
1818
|
}
|
|
1869
1819
|
if (endScalar < startScalar) {
|
|
1870
|
-
throw new
|
|
1871
|
-
dateRange: {
|
|
1820
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_DATE_RANGE', '日期范围必须是有效的 YYYY-MM-DD 且结束日期不早于开始日期', {
|
|
1821
|
+
dateRange: {
|
|
1822
|
+
startDate,
|
|
1823
|
+
endDate
|
|
1824
|
+
},
|
|
1872
1825
|
timezone
|
|
1873
1826
|
});
|
|
1874
1827
|
}
|
|
1875
|
-
const inclusiveDays = Math.trunc((endScalar - startScalar) / (24 * 60 * 60 *
|
|
1828
|
+
const inclusiveDays = Math.trunc((endScalar - startScalar) / (24 * 60 * 60 * 1000)) + 1;
|
|
1876
1829
|
if (inclusiveDays > MAX_AVAILABILITY_RANGE_DAYS) {
|
|
1877
|
-
throw new
|
|
1878
|
-
dateRange: {
|
|
1830
|
+
throw new _ResourcePlanner.AvailabilityError('RANGE_TOO_LARGE', '日期范围最多包含 90 个自然日', {
|
|
1831
|
+
dateRange: {
|
|
1832
|
+
startDate,
|
|
1833
|
+
endDate
|
|
1834
|
+
},
|
|
1879
1835
|
inclusiveDays
|
|
1880
1836
|
});
|
|
1881
1837
|
}
|
|
1882
|
-
return {
|
|
1838
|
+
return {
|
|
1839
|
+
startDate,
|
|
1840
|
+
endDate
|
|
1841
|
+
};
|
|
1883
1842
|
}
|
|
1884
1843
|
assertAvailabilityPrepareParams(params) {
|
|
1885
|
-
|
|
1886
|
-
assertAvailabilityCatalogProductIds(params.productIds, "prepare.productIds");
|
|
1844
|
+
assertAvailabilityCatalogProductIds(params.productIds, 'prepare.productIds');
|
|
1887
1845
|
const hasProductAnchor = Array.isArray(params.productIds) && params.productIds.length > 0;
|
|
1888
1846
|
const hasResourceAnchor = Array.isArray(params.resourceIds) && params.resourceIds.length > 0;
|
|
1889
|
-
const hasDateAnchor = Boolean(
|
|
1847
|
+
const hasDateAnchor = Boolean(params.dateRange?.startDate || params.dateRange?.endDate);
|
|
1890
1848
|
if (!hasProductAnchor && !hasResourceAnchor && !hasDateAnchor) {
|
|
1891
|
-
throw new
|
|
1849
|
+
throw new _ResourcePlanner.AvailabilityError('MISSING_ANCHOR', 'prepare 至少需要 productIds、resourceIds 或 dateRange 之一', {
|
|
1892
1850
|
params
|
|
1893
1851
|
});
|
|
1894
1852
|
}
|
|
1895
1853
|
if (!hasProductAnchor && !params.catalogScope) {
|
|
1896
|
-
throw new
|
|
1854
|
+
throw new _ResourcePlanner.AvailabilityError('MISSING_CATALOG_SCOPE', '缺少 productIds 时必须提供 catalogScope', {
|
|
1897
1855
|
params
|
|
1898
1856
|
});
|
|
1899
1857
|
}
|
|
1900
|
-
if (params.slotStepMinutes !==
|
|
1858
|
+
if (params.slotStepMinutes !== undefined) {
|
|
1901
1859
|
const step = Number(params.slotStepMinutes);
|
|
1902
1860
|
if (!Number.isInteger(step) || step <= 0) {
|
|
1903
|
-
throw new
|
|
1861
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'slotStepMinutes 必须是正整数', {
|
|
1904
1862
|
slotStepMinutes: params.slotStepMinutes
|
|
1905
1863
|
});
|
|
1906
1864
|
}
|
|
1907
1865
|
}
|
|
1908
1866
|
}
|
|
1909
1867
|
captureAvailabilityScheduleCatalog() {
|
|
1910
|
-
var _a, _b;
|
|
1911
1868
|
if (!this.store.schedule) {
|
|
1912
|
-
throw new Error(
|
|
1869
|
+
throw new Error('[UnifiedBookingSales] schedule 模块未初始化');
|
|
1913
1870
|
}
|
|
1914
|
-
this.availabilityScheduleCatalog = (0,
|
|
1915
|
-
((_b = (_a = this.store.schedule).getScheduleList) == null ? void 0 : _b.call(_a)) || []
|
|
1916
|
-
);
|
|
1871
|
+
this.availabilityScheduleCatalog = (0, _lodashEs.cloneDeep)(this.store.schedule.getScheduleList?.() || []);
|
|
1917
1872
|
}
|
|
1873
|
+
|
|
1918
1874
|
/** Returns the solution-lifetime schedule snapshot captured after initialization. */
|
|
1919
1875
|
getAvailabilityScheduleCatalog() {
|
|
1920
1876
|
if (this.availabilityScheduleCatalog === null) {
|
|
1921
|
-
throw new Error(
|
|
1877
|
+
throw new Error('[UnifiedBookingSales] schedule 快照未初始化');
|
|
1922
1878
|
}
|
|
1923
1879
|
return this.availabilityScheduleCatalog;
|
|
1924
1880
|
}
|
|
1925
1881
|
buildAvailabilityOperatingHoursSnapshot(params) {
|
|
1926
1882
|
const descriptor = params.prepare._availabilityOperatingHours || {
|
|
1927
|
-
source: params.prepare.businessHours ?
|
|
1883
|
+
source: params.prepare.businessHours ? 'prepare_business_hours' : 'default_business_hours',
|
|
1928
1884
|
scheduleIds: []
|
|
1929
1885
|
};
|
|
1930
|
-
if (descriptor.source !==
|
|
1931
|
-
const
|
|
1886
|
+
if (descriptor.source !== 'open_data_schedule') {
|
|
1887
|
+
const windows = dateRangeDays(params.dateRange).flatMap(date => {
|
|
1932
1888
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
1933
1889
|
startAt: `${date}T${params.businessHours.startTime}:00`,
|
|
1934
1890
|
endAt: `${date}T${params.businessHours.endTime}:00`,
|
|
@@ -1945,18 +1901,14 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1945
1901
|
source: descriptor.source,
|
|
1946
1902
|
scheduleIds: [],
|
|
1947
1903
|
missingScheduleIds: [],
|
|
1948
|
-
windows
|
|
1904
|
+
windows
|
|
1949
1905
|
};
|
|
1950
1906
|
}
|
|
1951
|
-
const scheduleMap = new Map(
|
|
1952
|
-
|
|
1953
|
-
);
|
|
1954
|
-
const
|
|
1955
|
-
const
|
|
1956
|
-
const missingScheduleIds = descriptor.scheduleIds.filter(
|
|
1957
|
-
(scheduleId) => !matchedScheduleIds.has(String(scheduleId))
|
|
1958
|
-
);
|
|
1959
|
-
const windows = dateRangeDays(params.dateRange).flatMap((date) => (0, import_getDateIsInSchedule.getScheduleStartEndTimePoints)(date, schedules).flatMap((slot) => {
|
|
1907
|
+
const scheduleMap = new Map(params.scheduleList.map(schedule => [String(schedule.id), schedule]));
|
|
1908
|
+
const schedules = descriptor.scheduleIds.map(scheduleId => scheduleMap.get(String(scheduleId))).filter(schedule => Boolean(schedule));
|
|
1909
|
+
const matchedScheduleIds = new Set(schedules.map(schedule => String(schedule.id)));
|
|
1910
|
+
const missingScheduleIds = descriptor.scheduleIds.filter(scheduleId => !matchedScheduleIds.has(String(scheduleId)));
|
|
1911
|
+
const windows = dateRangeDays(params.dateRange).flatMap(date => (0, _getDateIsInSchedule.getScheduleStartEndTimePoints)(date, schedules).flatMap(slot => {
|
|
1960
1912
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
1961
1913
|
startAt: slot.start_at,
|
|
1962
1914
|
endAt: slot.end_at,
|
|
@@ -1966,7 +1918,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1966
1918
|
return range ? [{
|
|
1967
1919
|
startAt: range.startAt,
|
|
1968
1920
|
endAt: range.endAt,
|
|
1969
|
-
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(
|
|
1921
|
+
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(',')}`
|
|
1970
1922
|
}] : [];
|
|
1971
1923
|
}));
|
|
1972
1924
|
return {
|
|
@@ -1977,8 +1929,10 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1977
1929
|
};
|
|
1978
1930
|
}
|
|
1979
1931
|
async loadAvailabilityProducts(params) {
|
|
1980
|
-
|
|
1981
|
-
|
|
1932
|
+
const {
|
|
1933
|
+
prepare,
|
|
1934
|
+
dateRange
|
|
1935
|
+
} = params;
|
|
1982
1936
|
const explicitProductIds = prepare.productIds || [];
|
|
1983
1937
|
let rawProducts = [];
|
|
1984
1938
|
if (Array.isArray(prepare.rawProducts)) {
|
|
@@ -1989,21 +1943,18 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1989
1943
|
product_ids: explicitProductIds,
|
|
1990
1944
|
schedule_date: dateRange.startDate
|
|
1991
1945
|
});
|
|
1992
|
-
} else if (
|
|
1946
|
+
} else if (prepare.catalogScope?.type === 'schedule') {
|
|
1993
1947
|
await this.loadScheduleAvailableDate({
|
|
1994
1948
|
startDate: dateRange.startDate,
|
|
1995
1949
|
endDate: dateRange.endDate,
|
|
1996
|
-
custom_page_id: Number(prepare.catalogScope.customPageId) ||
|
|
1950
|
+
custom_page_id: Number(prepare.catalogScope.customPageId) || undefined,
|
|
1997
1951
|
channel: prepare.catalogScope.channel
|
|
1998
1952
|
});
|
|
1999
1953
|
const dateSet = new Set(dateRangeDays(dateRange));
|
|
2000
|
-
const scheduleProductIds = (
|
|
2001
|
-
const productIds = uniqueAvailabilityIds([
|
|
2002
|
-
...scheduleProductIds,
|
|
2003
|
-
...((_e = (_d = this.store.schedule).getOtherProductsIds) == null ? void 0 : _e.call(_d)) || []
|
|
2004
|
-
]);
|
|
1954
|
+
const scheduleProductIds = (this.store.schedule.getAvailabilityScheduleDateList?.() || []).filter(item => dateSet.has(item.date)).flatMap(item => item.product_ids || []);
|
|
1955
|
+
const productIds = uniqueAvailabilityIds([...scheduleProductIds, ...(this.store.schedule.getOtherProductsIds?.() || [])]);
|
|
2005
1956
|
if (productIds.length > 0) {
|
|
2006
|
-
assertAvailabilityCatalogProductIds(productIds,
|
|
1957
|
+
assertAvailabilityCatalogProductIds(productIds, 'prepare.catalogScope.scheduleProductIds');
|
|
2007
1958
|
rawProducts = await this.loadProducts({
|
|
2008
1959
|
product_ids: productIds,
|
|
2009
1960
|
schedule_date: dateRange.startDate
|
|
@@ -2011,36 +1962,27 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2011
1962
|
} else {
|
|
2012
1963
|
this.unifiedProductCatalog = [];
|
|
2013
1964
|
}
|
|
2014
|
-
} else if (
|
|
1965
|
+
} else if (prepare.catalogScope?.type === 'productList') {
|
|
2015
1966
|
rawProducts = await this.loadProducts({
|
|
2016
1967
|
...prepare.catalogScope.params,
|
|
2017
1968
|
schedule_date: prepare.catalogScope.params.schedule_date || dateRange.startDate
|
|
2018
1969
|
});
|
|
2019
1970
|
}
|
|
2020
|
-
const normalized = rawProducts.map(
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
this.getAvailabilityRuntimeOffsetMinutes()
|
|
2024
|
-
));
|
|
2025
|
-
if (!explicitProductIds.length && ((_g = prepare.resourceIds) == null ? void 0 : _g.length)) {
|
|
2026
|
-
return normalized.filter((product) => {
|
|
2027
|
-
var _a2;
|
|
2028
|
-
return (_a2 = product.requirementGroups) == null ? void 0 : _a2.some((group) => {
|
|
2029
|
-
var _a3;
|
|
2030
|
-
return (_a3 = group.resourceIds) == null ? void 0 : _a3.some((resourceId) => prepare.resourceIds.some((id) => sameAvailabilityId(id, resourceId)));
|
|
2031
|
-
});
|
|
2032
|
-
});
|
|
1971
|
+
const normalized = rawProducts.map(product => normalizeProductForAvailability(product, params.scheduleList, this.getAvailabilityRuntimeOffsetMinutes()));
|
|
1972
|
+
if (!explicitProductIds.length && prepare.resourceIds?.length) {
|
|
1973
|
+
return normalized.filter(product => product.requirementGroups?.some(group => group.resourceIds?.some(resourceId => prepare.resourceIds.some(id => sameAvailabilityId(id, resourceId)))));
|
|
2033
1974
|
}
|
|
2034
1975
|
const productIdSet = explicitProductIds.length ? new Set(explicitProductIds.map(String)) : null;
|
|
2035
|
-
return productIdSet ? normalized.filter(
|
|
1976
|
+
return productIdSet ? normalized.filter(product => productIdSet.has(String(product.id))) : normalized;
|
|
2036
1977
|
}
|
|
1978
|
+
|
|
2037
1979
|
/**
|
|
2038
1980
|
* Fetches resource metadata plus flat occupied events. Work windows are intentionally derived
|
|
2039
1981
|
* locally in buildAvailabilityResourcesFromV2 from /schedule, rather than requested from the legacy
|
|
2040
1982
|
* resource dates endpoint.
|
|
2041
1983
|
*/
|
|
2042
1984
|
async fetchAvailabilityResourcesV2(params) {
|
|
2043
|
-
const response = await this.request.get(
|
|
1985
|
+
const response = await this.request.get('/schedule/resource/list/v2', {
|
|
2044
1986
|
start_date: params.dateRange.startDate,
|
|
2045
1987
|
end_date: params.dateRange.endDate,
|
|
2046
1988
|
resource_ids: params.resourceIds,
|
|
@@ -2048,17 +1990,14 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2048
1990
|
}, {
|
|
2049
1991
|
useCache: false
|
|
2050
1992
|
});
|
|
2051
|
-
const data = response
|
|
2052
|
-
if (Array.isArray(data))
|
|
2053
|
-
|
|
2054
|
-
if (Array.isArray(data == null ? void 0 : data.list))
|
|
2055
|
-
return data.list;
|
|
1993
|
+
const data = response?.data;
|
|
1994
|
+
if (Array.isArray(data)) return data;
|
|
1995
|
+
if (Array.isArray(data?.list)) return data.list;
|
|
2056
1996
|
return [];
|
|
2057
1997
|
}
|
|
2058
1998
|
async fetchAndPublishAvailabilityResourcesV2(params) {
|
|
2059
1999
|
const normalizedResourceIds = this.normalizeAvailabilityIdList(params.resourceIds) || [];
|
|
2060
|
-
if (normalizedResourceIds.length === 0)
|
|
2061
|
-
return [];
|
|
2000
|
+
if (normalizedResourceIds.length === 0) return [];
|
|
2062
2001
|
const scopeKey = this.buildAvailabilityRemoteResourceScopeKey(params.timezone);
|
|
2063
2002
|
const cacheParams = {
|
|
2064
2003
|
scopeKey,
|
|
@@ -2072,8 +2011,8 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2072
2011
|
timezone: params.timezone,
|
|
2073
2012
|
dateRange: params.dateRange,
|
|
2074
2013
|
resourceIds: normalizedResourceIds
|
|
2075
|
-
}).catch(
|
|
2076
|
-
console.warn(
|
|
2014
|
+
}).catch(error => {
|
|
2015
|
+
console.warn('[UnifiedBookingSales] Remote occupancy background refresh failed', error);
|
|
2077
2016
|
});
|
|
2078
2017
|
}
|
|
2079
2018
|
return cachedRawResources;
|
|
@@ -2086,8 +2025,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2086
2025
|
}
|
|
2087
2026
|
async refreshAvailabilityRemoteResourceQuery(params) {
|
|
2088
2027
|
const normalizedResourceIds = this.normalizeAvailabilityIdList(params.resourceIds) || [];
|
|
2089
|
-
if (normalizedResourceIds.length === 0)
|
|
2090
|
-
return [];
|
|
2028
|
+
if (normalizedResourceIds.length === 0) return [];
|
|
2091
2029
|
const queryKey = this.buildAvailabilityRemoteResourceQueryKey({
|
|
2092
2030
|
timezone: params.timezone,
|
|
2093
2031
|
dateRange: params.dateRange,
|
|
@@ -2095,7 +2033,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2095
2033
|
});
|
|
2096
2034
|
const epoch = this.availabilityRemoteResourceEpoch;
|
|
2097
2035
|
const existing = this.availabilityRemoteResourceQueryInFlight.get(queryKey);
|
|
2098
|
-
if (
|
|
2036
|
+
if (existing?.epoch === epoch) {
|
|
2099
2037
|
return await existing.promise;
|
|
2100
2038
|
}
|
|
2101
2039
|
this.availabilityRemoteResourceQuerySequence += 1;
|
|
@@ -2123,7 +2061,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2123
2061
|
return rawResources;
|
|
2124
2062
|
} finally {
|
|
2125
2063
|
const current = this.availabilityRemoteResourceQueryInFlight.get(queryKey);
|
|
2126
|
-
if (
|
|
2064
|
+
if (current?.promise === promise) {
|
|
2127
2065
|
this.availabilityRemoteResourceQueryInFlight.delete(queryKey);
|
|
2128
2066
|
}
|
|
2129
2067
|
}
|
|
@@ -2144,7 +2082,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2144
2082
|
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes()
|
|
2145
2083
|
});
|
|
2146
2084
|
}
|
|
2147
|
-
if (params.fetchPolicy ===
|
|
2085
|
+
if (params.fetchPolicy === 'cache_only') {
|
|
2148
2086
|
const cachedRawResources = this.availabilityRemoteOccupancyCache.read({
|
|
2149
2087
|
scopeKey: this.buildAvailabilityRemoteResourceScopeKey(params.timezone),
|
|
2150
2088
|
resourceIds,
|
|
@@ -2158,7 +2096,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2158
2096
|
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes()
|
|
2159
2097
|
});
|
|
2160
2098
|
}
|
|
2161
|
-
return params.fallbackResources ? (0,
|
|
2099
|
+
return params.fallbackResources ? (0, _lodashEs.cloneDeep)(params.fallbackResources) : {
|
|
2162
2100
|
resources: [],
|
|
2163
2101
|
resourceOccupancies: []
|
|
2164
2102
|
};
|
|
@@ -2177,44 +2115,46 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2177
2115
|
});
|
|
2178
2116
|
}
|
|
2179
2117
|
resolveEditingOccupancyExclusions(tempOrder, productLineUids = []) {
|
|
2180
|
-
const productUids =
|
|
2181
|
-
const bookingUids =
|
|
2182
|
-
const scheduleEventIds =
|
|
2183
|
-
if (!productLineUids.length)
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2118
|
+
const productUids = new Set();
|
|
2119
|
+
const bookingUids = new Set();
|
|
2120
|
+
const scheduleEventIds = new Set();
|
|
2121
|
+
if (!productLineUids.length) return {
|
|
2122
|
+
productUids,
|
|
2123
|
+
bookingUids,
|
|
2124
|
+
scheduleEventIds
|
|
2125
|
+
};
|
|
2126
|
+
const products = Array.isArray(tempOrder?.products) ? tempOrder.products : [];
|
|
2127
|
+
const bookings = Array.isArray(tempOrder?.bookings) ? tempOrder.bookings : [];
|
|
2128
|
+
productLineUids.forEach(requestedUid => {
|
|
2129
|
+
const product = products.find(line => getProductLineUid(line) === String(requestedUid));
|
|
2190
2130
|
if (!product) {
|
|
2191
|
-
throw new
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
{ productLineUid: requestedUid }
|
|
2195
|
-
);
|
|
2131
|
+
throw new _ResourcePlanner.AvailabilityError('SELF_EXCLUSION_RESOLVE_FAILED', '无法解析编辑商品行,不能安全排除自身占用', {
|
|
2132
|
+
productLineUid: requestedUid
|
|
2133
|
+
});
|
|
2196
2134
|
}
|
|
2197
2135
|
const productUid = getProductLineUid(product);
|
|
2198
|
-
const linkedBookingUid = String(product.booking_uid ??
|
|
2199
|
-
const linkedBookings = bookings.filter(
|
|
2136
|
+
const linkedBookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
|
|
2137
|
+
const linkedBookings = bookings.filter(booking => linkedBookingUid && getBookingUid(booking) === linkedBookingUid || productUid && String(booking.product_uid ?? '') === productUid);
|
|
2200
2138
|
if (!productUid || !linkedBookings.length) {
|
|
2201
|
-
throw new
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
);
|
|
2139
|
+
throw new _ResourcePlanner.AvailabilityError('SELF_EXCLUSION_RESOLVE_FAILED', '编辑商品行缺少可验证的 booking 关联', {
|
|
2140
|
+
productLineUid: requestedUid,
|
|
2141
|
+
linkedBookingUid
|
|
2142
|
+
});
|
|
2206
2143
|
}
|
|
2207
2144
|
productUids.add(productUid);
|
|
2208
|
-
linkedBookings.forEach(
|
|
2145
|
+
linkedBookings.forEach(booking => {
|
|
2209
2146
|
const bookingUid = getBookingUid(booking);
|
|
2210
|
-
if (bookingUid)
|
|
2211
|
-
bookingUids.add(bookingUid);
|
|
2147
|
+
if (bookingUid) bookingUids.add(bookingUid);
|
|
2212
2148
|
if (booking.schedule_event_id != null) {
|
|
2213
2149
|
scheduleEventIds.add(String(booking.schedule_event_id));
|
|
2214
2150
|
}
|
|
2215
2151
|
});
|
|
2216
2152
|
});
|
|
2217
|
-
return {
|
|
2153
|
+
return {
|
|
2154
|
+
productUids,
|
|
2155
|
+
bookingUids,
|
|
2156
|
+
scheduleEventIds
|
|
2157
|
+
};
|
|
2218
2158
|
}
|
|
2219
2159
|
occupancyIsExcluded(occupancy, exclusions) {
|
|
2220
2160
|
if (occupancy.scheduleEventId != null) {
|
|
@@ -2229,37 +2169,37 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2229
2169
|
return false;
|
|
2230
2170
|
}
|
|
2231
2171
|
collectCartResourceOccupancies(tempOrder, exclusions) {
|
|
2232
|
-
const bookings = Array.isArray(tempOrder
|
|
2233
|
-
return bookings.flatMap(
|
|
2234
|
-
var _a;
|
|
2172
|
+
const bookings = Array.isArray(tempOrder?.bookings) ? tempOrder.bookings : [];
|
|
2173
|
+
return bookings.flatMap(booking => {
|
|
2235
2174
|
const bookingUid = getBookingUid(booking);
|
|
2236
|
-
const productUid = String(booking.product_uid ??
|
|
2175
|
+
const productUid = String(booking.product_uid ?? booking.metadata?.product_uid ?? '');
|
|
2237
2176
|
const scheduleEventId = toNumberId(booking.schedule_event_id);
|
|
2238
|
-
const startAt = booking.start_at || `${booking.start_date ||
|
|
2239
|
-
const endAt = booking.end_at || `${booking.end_date || booking.start_date ||
|
|
2177
|
+
const startAt = booking.start_at || `${booking.start_date || ''} ${booking.start_time || ''}`.trim();
|
|
2178
|
+
const endAt = booking.end_at || `${booking.end_date || booking.start_date || ''} ${booking.end_time || ''}`.trim();
|
|
2240
2179
|
const resources = Array.isArray(booking.resources) ? booking.resources : [];
|
|
2241
|
-
return resources.flatMap(
|
|
2180
|
+
return resources.flatMap(resource => {
|
|
2242
2181
|
const resourceId = resource.resource_id ?? resource.relation_id ?? resource.id;
|
|
2243
|
-
if (resourceId == null || !startAt || !endAt)
|
|
2244
|
-
return [];
|
|
2182
|
+
if (resourceId == null || !startAt || !endAt) return [];
|
|
2245
2183
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
2246
2184
|
startAt,
|
|
2247
2185
|
endAt,
|
|
2248
2186
|
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
2249
2187
|
rollOverEnd: false
|
|
2250
2188
|
});
|
|
2251
|
-
if (!range)
|
|
2252
|
-
return [];
|
|
2189
|
+
if (!range) return [];
|
|
2253
2190
|
const occupancy = {
|
|
2254
2191
|
resourceId,
|
|
2255
2192
|
startAt: range.startAt,
|
|
2256
2193
|
endAt: range.endAt,
|
|
2257
2194
|
pax: Math.max(1, Number(resource.capacity ?? booking.capacity ?? 1) || 1),
|
|
2258
|
-
source:
|
|
2195
|
+
source: 'cart',
|
|
2259
2196
|
scheduleEventId,
|
|
2260
|
-
bookingUid: bookingUid ||
|
|
2261
|
-
productUid: productUid ||
|
|
2262
|
-
raw: {
|
|
2197
|
+
bookingUid: bookingUid || undefined,
|
|
2198
|
+
productUid: productUid || undefined,
|
|
2199
|
+
raw: {
|
|
2200
|
+
booking,
|
|
2201
|
+
resource
|
|
2202
|
+
}
|
|
2263
2203
|
};
|
|
2264
2204
|
return this.occupancyIsExcluded(occupancy, exclusions) ? [] : [occupancy];
|
|
2265
2205
|
});
|
|
@@ -2271,30 +2211,22 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2271
2211
|
getAvailabilityContextEntry(contextId) {
|
|
2272
2212
|
const context = this.availabilityContextRegistry.get(contextId);
|
|
2273
2213
|
if (!context) {
|
|
2274
|
-
throw this.createAvailabilityFacadeError(
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
contextId,
|
|
2279
|
-
lifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2280
|
-
}
|
|
2281
|
-
);
|
|
2214
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_CONTEXT_NOT_FOUND', 'availability context 不存在或已失效,请重新 prepare', {
|
|
2215
|
+
contextId,
|
|
2216
|
+
lifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2217
|
+
});
|
|
2282
2218
|
}
|
|
2283
2219
|
this.touchAvailabilityContext(context);
|
|
2284
2220
|
return context;
|
|
2285
2221
|
}
|
|
2286
2222
|
getAvailabilityProjectionByKey(projectionKey) {
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
return null;
|
|
2290
|
-
return ((_a = this.availabilityProjectionPool.get(projectionKey)) == null ? void 0 : _a.projection) || null;
|
|
2223
|
+
if (!projectionKey) return null;
|
|
2224
|
+
return this.availabilityProjectionPool.get(projectionKey)?.projection || null;
|
|
2291
2225
|
}
|
|
2292
2226
|
getActiveAvailabilityProjectionEntry(context) {
|
|
2293
|
-
if (!context.activeProjectionKey)
|
|
2294
|
-
return null;
|
|
2227
|
+
if (!context.activeProjectionKey) return null;
|
|
2295
2228
|
const entry = this.availabilityProjectionPool.get(context.activeProjectionKey);
|
|
2296
|
-
if (!entry)
|
|
2297
|
-
return null;
|
|
2229
|
+
if (!entry) return null;
|
|
2298
2230
|
return this.touchAvailabilityProjection(entry);
|
|
2299
2231
|
}
|
|
2300
2232
|
decorateAvailabilityCandidate(contextId, contextVersion, candidate) {
|
|
@@ -2306,38 +2238,30 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2306
2238
|
}
|
|
2307
2239
|
assertAvailabilityCandidateFreshness(context, candidate) {
|
|
2308
2240
|
if (candidate.availabilityContextId !== context.contextId) {
|
|
2309
|
-
throw this.createAvailabilityFacadeError(
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
candidateContextId: candidate.availabilityContextId,
|
|
2314
|
-
contextId: context.contextId
|
|
2315
|
-
}
|
|
2316
|
-
);
|
|
2241
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '候选与 availability context 不匹配,请重新获取候选', {
|
|
2242
|
+
candidateContextId: candidate.availabilityContextId,
|
|
2243
|
+
contextId: context.contextId
|
|
2244
|
+
});
|
|
2317
2245
|
}
|
|
2318
2246
|
const currentContextVersion = context.activeVersion;
|
|
2319
2247
|
if (candidate.availabilityContextVersion !== currentContextVersion) {
|
|
2320
|
-
throw this.createAvailabilityFacadeError(
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
currentContextVersion,
|
|
2327
|
-
snapshotId: context.lastSnapshotId
|
|
2328
|
-
}
|
|
2329
|
-
);
|
|
2248
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '候选已过期,请重新获取最新时间段', {
|
|
2249
|
+
contextId: context.contextId,
|
|
2250
|
+
candidateContextVersion: candidate.availabilityContextVersion,
|
|
2251
|
+
currentContextVersion,
|
|
2252
|
+
snapshotId: context.lastSnapshotId
|
|
2253
|
+
});
|
|
2330
2254
|
}
|
|
2331
2255
|
}
|
|
2332
2256
|
assertContextualQueryFreshness(context, request) {
|
|
2333
|
-
if (request.target ===
|
|
2334
|
-
request.candidates.forEach(
|
|
2257
|
+
if (request.target === 'time') {
|
|
2258
|
+
request.candidates.forEach(candidate => {
|
|
2335
2259
|
this.assertAvailabilityCandidateFreshness(context, candidate);
|
|
2336
2260
|
});
|
|
2337
2261
|
return;
|
|
2338
2262
|
}
|
|
2339
|
-
if (request.target ===
|
|
2340
|
-
request.selectedRanges.forEach(
|
|
2263
|
+
if (request.target === 'resource') {
|
|
2264
|
+
request.selectedRanges.forEach(candidate => {
|
|
2341
2265
|
this.assertAvailabilityCandidateFreshness(context, candidate);
|
|
2342
2266
|
});
|
|
2343
2267
|
}
|
|
@@ -2355,7 +2279,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2355
2279
|
preparedAt: params.projection.meta.preparedAt,
|
|
2356
2280
|
snapshotId: params.projection.meta.snapshotId
|
|
2357
2281
|
};
|
|
2358
|
-
if (
|
|
2282
|
+
if (previousContext?.activeProjectionKey && previousContext.activeProjectionKey !== projectionKey) {
|
|
2359
2283
|
this.removeAvailabilityProjection(previousContext.activeProjectionKey);
|
|
2360
2284
|
}
|
|
2361
2285
|
this.availabilityProjectionPool.set(projectionKey, entry);
|
|
@@ -2366,13 +2290,13 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2366
2290
|
prepareKey: params.prepareKey,
|
|
2367
2291
|
activeVersion: contextVersion,
|
|
2368
2292
|
activeProjectionKey: projectionKey,
|
|
2369
|
-
createdAt:
|
|
2370
|
-
lastAccessedAt:
|
|
2293
|
+
createdAt: previousContext?.createdAt ?? params.projection.meta.preparedAt,
|
|
2294
|
+
lastAccessedAt: new Date().toISOString(),
|
|
2371
2295
|
lastKnownOrderRevision: params.orderRevision,
|
|
2372
2296
|
lastPreparedAt: params.projection.meta.preparedAt,
|
|
2373
2297
|
lastSnapshotId: params.projection.meta.snapshotId,
|
|
2374
|
-
durationCandidateAnchorAt:
|
|
2375
|
-
refreshNonce:
|
|
2298
|
+
durationCandidateAnchorAt: previousContext?.durationCandidateAnchorAt ?? this.getAvailabilityDurationCandidateAnchorAt(params.projection),
|
|
2299
|
+
refreshNonce: previousContext?.refreshNonce ?? 0,
|
|
2376
2300
|
lifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2377
2301
|
};
|
|
2378
2302
|
this.touchAvailabilityContext(nextContext);
|
|
@@ -2385,7 +2309,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2385
2309
|
const dateRange = normalizedPrepareParams.dateRange;
|
|
2386
2310
|
const slotStepMinutes = normalizedPrepareParams.slotStepMinutes || DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES;
|
|
2387
2311
|
const businessHours = normalizedPrepareParams.businessHours || DEFAULT_AVAILABILITY_BUSINESS_HOURS;
|
|
2388
|
-
const mode = normalizedPrepareParams.mode ||
|
|
2312
|
+
const mode = normalizedPrepareParams.mode || 'appointment';
|
|
2389
2313
|
const scheduleList = this.getAvailabilityScheduleCatalog();
|
|
2390
2314
|
const operatingHours = this.buildAvailabilityOperatingHoursSnapshot({
|
|
2391
2315
|
prepare: normalizedPrepareParams,
|
|
@@ -2405,39 +2329,32 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2405
2329
|
products,
|
|
2406
2330
|
rawResources: normalizedPrepareParams.rawResources,
|
|
2407
2331
|
scheduleList,
|
|
2408
|
-
fetchPolicy: options.resourceFetchPolicy ||
|
|
2332
|
+
fetchPolicy: options.resourceFetchPolicy || 'default',
|
|
2409
2333
|
fallbackResources: options.fallbackResources
|
|
2410
2334
|
});
|
|
2411
2335
|
const tempOrder = this.getTempOrder();
|
|
2412
|
-
const exclusions = this.resolveEditingOccupancyExclusions(
|
|
2413
|
-
|
|
2414
|
-
normalizedPrepareParams.editingProductLineUids
|
|
2415
|
-
);
|
|
2416
|
-
const injectedRawOccupancies = (normalizedPrepareParams.rawOccupancies || []).flatMap((occupancy) => {
|
|
2336
|
+
const exclusions = this.resolveEditingOccupancyExclusions(tempOrder, normalizedPrepareParams.editingProductLineUids);
|
|
2337
|
+
const injectedRawOccupancies = (normalizedPrepareParams.rawOccupancies || []).flatMap(occupancy => {
|
|
2417
2338
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
2418
2339
|
startAt: occupancy.startAt,
|
|
2419
2340
|
endAt: occupancy.endAt,
|
|
2420
2341
|
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
2421
2342
|
rollOverEnd: false
|
|
2422
2343
|
});
|
|
2423
|
-
if (!range)
|
|
2424
|
-
return [];
|
|
2344
|
+
if (!range) return [];
|
|
2425
2345
|
return [{
|
|
2426
2346
|
...occupancy,
|
|
2427
2347
|
startAt: range.startAt,
|
|
2428
2348
|
endAt: range.endAt
|
|
2429
2349
|
}];
|
|
2430
2350
|
});
|
|
2431
|
-
const remoteOccupancies = [
|
|
2432
|
-
...loadedResources.resourceOccupancies,
|
|
2433
|
-
...loadedResources.includesInjectedRawOccupancies ? [] : injectedRawOccupancies
|
|
2434
|
-
].filter((occupancy) => !this.occupancyIsExcluded(occupancy, exclusions));
|
|
2351
|
+
const remoteOccupancies = [...loadedResources.resourceOccupancies, ...(loadedResources.includesInjectedRawOccupancies ? [] : injectedRawOccupancies)].filter(occupancy => !this.occupancyIsExcluded(occupancy, exclusions));
|
|
2435
2352
|
const cartOccupancies = this.collectCartResourceOccupancies(tempOrder, exclusions);
|
|
2436
2353
|
const preparedAt = this.getAvailabilityRuntimeDateTime();
|
|
2437
2354
|
const durationCandidateAnchorAt = options.durationCandidateAnchorAt || preparedAt;
|
|
2438
2355
|
this.availabilityProjectionSequence += 1;
|
|
2439
2356
|
const orderRevision = this.getCurrentOrderRevision();
|
|
2440
|
-
const projection = (0,
|
|
2357
|
+
const projection = (0, _ResourcePlanner.createAvailabilityProjection)({
|
|
2441
2358
|
products,
|
|
2442
2359
|
resources: loadedResources.resources,
|
|
2443
2360
|
resourceOccupancies: [...remoteOccupancies, ...cartOccupancies],
|
|
@@ -2468,38 +2385,37 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2468
2385
|
async getSharedPreparedAvailabilitySnapshot(params) {
|
|
2469
2386
|
const lifecycleGeneration = this.availabilityLifecycleGeneration;
|
|
2470
2387
|
const normalizedParams = this.normalizeAvailabilityPrepareDescriptor(params);
|
|
2471
|
-
const hasRawInputs = params.rawProducts !==
|
|
2388
|
+
const hasRawInputs = params.rawProducts !== undefined || params.rawResources !== undefined || params.rawOccupancies !== undefined;
|
|
2472
2389
|
const prepareKey = hasRawInputs ? JSON.stringify(stableValue({
|
|
2473
2390
|
scope: this.getAvailabilityScopeDescriptor(),
|
|
2474
2391
|
prepare: {
|
|
2475
2392
|
...normalizedParams,
|
|
2476
|
-
rawProducts:
|
|
2477
|
-
rawResources:
|
|
2478
|
-
rawOccupancies:
|
|
2393
|
+
rawProducts: undefined,
|
|
2394
|
+
rawResources: undefined,
|
|
2395
|
+
rawOccupancies: undefined
|
|
2479
2396
|
},
|
|
2480
2397
|
rawInputs: {
|
|
2481
|
-
products: params.rawProducts !==
|
|
2482
|
-
resources: params.rawResources !==
|
|
2483
|
-
occupancies: params.rawOccupancies !==
|
|
2398
|
+
products: params.rawProducts !== undefined,
|
|
2399
|
+
resources: params.rawResources !== undefined,
|
|
2400
|
+
occupancies: params.rawOccupancies !== undefined
|
|
2484
2401
|
}
|
|
2485
2402
|
})) : this.buildAvailabilityPrepareKey(normalizedParams);
|
|
2486
2403
|
if (hasRawInputs) {
|
|
2487
2404
|
const snapshot = await this.buildAvailabilityProjectionSnapshot(normalizedParams);
|
|
2488
2405
|
if (lifecycleGeneration !== this.availabilityLifecycleGeneration) {
|
|
2489
|
-
throw this.createAvailabilityFacadeError(
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2495
|
-
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2496
|
-
}
|
|
2497
|
-
);
|
|
2406
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_CONTEXT_NOT_FOUND', 'prepare 期间业务作用域或实例生命周期已变化,请重新 prepare', {
|
|
2407
|
+
prepareKey,
|
|
2408
|
+
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2409
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2410
|
+
});
|
|
2498
2411
|
}
|
|
2499
|
-
return {
|
|
2412
|
+
return {
|
|
2413
|
+
prepareKey,
|
|
2414
|
+
snapshot
|
|
2415
|
+
};
|
|
2500
2416
|
}
|
|
2501
2417
|
const existing = this.availabilityPrepareInFlight.get(prepareKey);
|
|
2502
|
-
const promise =
|
|
2418
|
+
const promise = existing?.lifecycleGeneration === lifecycleGeneration ? existing.promise : this.buildAvailabilityProjectionSnapshot(normalizedParams);
|
|
2503
2419
|
if (!existing || existing.lifecycleGeneration !== lifecycleGeneration) {
|
|
2504
2420
|
this.availabilityPrepareInFlight.set(prepareKey, {
|
|
2505
2421
|
lifecycleGeneration,
|
|
@@ -2510,42 +2426,36 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2510
2426
|
try {
|
|
2511
2427
|
const snapshot = await promise;
|
|
2512
2428
|
if (lifecycleGeneration !== this.availabilityLifecycleGeneration) {
|
|
2513
|
-
throw this.createAvailabilityFacadeError(
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2519
|
-
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2520
|
-
}
|
|
2521
|
-
);
|
|
2429
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_CONTEXT_NOT_FOUND', 'prepare 期间业务作用域或实例生命周期已变化,请重新 prepare', {
|
|
2430
|
+
prepareKey,
|
|
2431
|
+
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2432
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2433
|
+
});
|
|
2522
2434
|
}
|
|
2523
|
-
return {
|
|
2435
|
+
return {
|
|
2436
|
+
prepareKey,
|
|
2437
|
+
snapshot
|
|
2438
|
+
};
|
|
2524
2439
|
} finally {
|
|
2525
2440
|
const current = this.availabilityPrepareInFlight.get(prepareKey);
|
|
2526
|
-
if (
|
|
2441
|
+
if (current?.promise === promise) {
|
|
2527
2442
|
this.availabilityPrepareInFlight.delete(prepareKey);
|
|
2528
2443
|
}
|
|
2529
2444
|
}
|
|
2530
2445
|
}
|
|
2531
2446
|
async refreshAvailabilityContextProjection(params) {
|
|
2532
2447
|
const existingInFlight = this.availabilityProjectionRefreshInFlight.get(params.contextId);
|
|
2533
|
-
if (existingInFlight)
|
|
2534
|
-
return await existingInFlight;
|
|
2448
|
+
if (existingInFlight) return await existingInFlight;
|
|
2535
2449
|
const context = this.getAvailabilityContextEntry(params.contextId);
|
|
2536
2450
|
const expectedVersion = context.activeVersion;
|
|
2537
2451
|
const expectedRefreshNonce = context.refreshNonce;
|
|
2538
2452
|
const expectedLifecycleGeneration = this.availabilityLifecycleGeneration;
|
|
2539
2453
|
const refreshPromise = (async () => {
|
|
2540
|
-
const snapshot = await this.buildAvailabilityProjectionSnapshot(
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
durationCandidateAnchorAt: context.durationCandidateAnchorAt
|
|
2544
|
-
}
|
|
2545
|
-
);
|
|
2454
|
+
const snapshot = await this.buildAvailabilityProjectionSnapshot(this.cloneAvailabilityPrepareParams(context.prepareParams), {
|
|
2455
|
+
durationCandidateAnchorAt: context.durationCandidateAnchorAt
|
|
2456
|
+
});
|
|
2546
2457
|
const currentContext = this.availabilityContextRegistry.get(params.contextId);
|
|
2547
|
-
if (!currentContext)
|
|
2548
|
-
return null;
|
|
2458
|
+
if (!currentContext) return null;
|
|
2549
2459
|
if (expectedLifecycleGeneration !== this.availabilityLifecycleGeneration || currentContext.activeVersion !== expectedVersion || currentContext.refreshNonce !== expectedRefreshNonce || currentContext.lifecycleGeneration !== expectedLifecycleGeneration) {
|
|
2550
2460
|
return null;
|
|
2551
2461
|
}
|
|
@@ -2558,7 +2468,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2558
2468
|
incrementVersion: true
|
|
2559
2469
|
});
|
|
2560
2470
|
await this.emitAvailabilityDiagnostic({
|
|
2561
|
-
action:
|
|
2471
|
+
action: 'refresh',
|
|
2562
2472
|
contextId: currentContext.contextId,
|
|
2563
2473
|
contextVersion: projectionEntry.contextVersion,
|
|
2564
2474
|
projection: projectionEntry.projection,
|
|
@@ -2586,7 +2496,11 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2586
2496
|
const currentOrderRevision = this.getCurrentOrderRevision();
|
|
2587
2497
|
const projectionEntry = this.getActiveAvailabilityProjectionEntry(context);
|
|
2588
2498
|
if (projectionEntry && projectionEntry.orderRevision === currentOrderRevision) {
|
|
2589
|
-
return {
|
|
2499
|
+
return {
|
|
2500
|
+
context,
|
|
2501
|
+
projectionEntry,
|
|
2502
|
+
refreshed: false
|
|
2503
|
+
};
|
|
2590
2504
|
}
|
|
2591
2505
|
const refreshedProjectionEntry = await this.refreshAvailabilityContextProjection({
|
|
2592
2506
|
contextId,
|
|
@@ -2607,7 +2521,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2607
2521
|
refreshed: true
|
|
2608
2522
|
};
|
|
2609
2523
|
}
|
|
2610
|
-
throw new
|
|
2524
|
+
throw new _ResourcePlanner.AvailabilityError('STALE_PROJECTION', 'availability projection 刷新期间购物车持续变化,请重新 prepare', {
|
|
2611
2525
|
contextId,
|
|
2612
2526
|
reason
|
|
2613
2527
|
});
|
|
@@ -2616,50 +2530,52 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2616
2530
|
if (this.availabilityCommitLocks.has(contextId)) {
|
|
2617
2531
|
const waitingContext = this.availabilityContextRegistry.get(contextId);
|
|
2618
2532
|
await this.emitAvailabilityDiagnostic({
|
|
2619
|
-
action:
|
|
2533
|
+
action: 'commit_lock_wait',
|
|
2620
2534
|
contextId,
|
|
2621
|
-
contextVersion:
|
|
2622
|
-
projection: this.getAvailabilityProjectionByKey(waitingContext
|
|
2623
|
-
snapshotId: waitingContext
|
|
2624
|
-
orderRevision: waitingContext
|
|
2625
|
-
reason:
|
|
2535
|
+
contextVersion: waitingContext?.activeVersion ?? null,
|
|
2536
|
+
projection: this.getAvailabilityProjectionByKey(waitingContext?.activeProjectionKey),
|
|
2537
|
+
snapshotId: waitingContext?.lastSnapshotId,
|
|
2538
|
+
orderRevision: waitingContext?.lastKnownOrderRevision,
|
|
2539
|
+
reason: 'AVAILABILITY_COMMIT_IN_PROGRESS'
|
|
2540
|
+
});
|
|
2541
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_COMMIT_IN_PROGRESS', '同一 availability context 已有 commit 正在进行中', {
|
|
2542
|
+
contextId
|
|
2626
2543
|
});
|
|
2627
|
-
throw this.createAvailabilityFacadeError(
|
|
2628
|
-
"AVAILABILITY_COMMIT_IN_PROGRESS",
|
|
2629
|
-
"同一 availability context 已有 commit 正在进行中",
|
|
2630
|
-
{ contextId }
|
|
2631
|
-
);
|
|
2632
2544
|
}
|
|
2633
2545
|
this.availabilityCommitLocks.add(contextId);
|
|
2634
2546
|
const currentContext = this.availabilityContextRegistry.get(contextId);
|
|
2635
2547
|
await this.emitAvailabilityDiagnostic({
|
|
2636
|
-
action:
|
|
2548
|
+
action: 'commit_lock_acquired',
|
|
2637
2549
|
contextId,
|
|
2638
|
-
contextVersion:
|
|
2639
|
-
projection: this.getAvailabilityProjectionByKey(currentContext
|
|
2640
|
-
snapshotId: currentContext
|
|
2641
|
-
orderRevision: currentContext
|
|
2642
|
-
reason:
|
|
2550
|
+
contextVersion: currentContext?.activeVersion ?? null,
|
|
2551
|
+
projection: this.getAvailabilityProjectionByKey(currentContext?.activeProjectionKey),
|
|
2552
|
+
snapshotId: currentContext?.lastSnapshotId,
|
|
2553
|
+
orderRevision: currentContext?.lastKnownOrderRevision,
|
|
2554
|
+
reason: 'commit_start'
|
|
2643
2555
|
});
|
|
2644
2556
|
}
|
|
2645
2557
|
async releaseAvailabilityCommitLock(contextId) {
|
|
2646
2558
|
this.availabilityCommitLocks.delete(contextId);
|
|
2647
2559
|
const releasedContext = this.availabilityContextRegistry.get(contextId);
|
|
2648
2560
|
await this.emitAvailabilityDiagnostic({
|
|
2649
|
-
action:
|
|
2561
|
+
action: 'commit_lock_released',
|
|
2650
2562
|
contextId,
|
|
2651
|
-
contextVersion:
|
|
2652
|
-
projection: this.getAvailabilityProjectionByKey(releasedContext
|
|
2653
|
-
snapshotId: releasedContext
|
|
2654
|
-
orderRevision: releasedContext
|
|
2655
|
-
reason:
|
|
2563
|
+
contextVersion: releasedContext?.activeVersion ?? null,
|
|
2564
|
+
projection: this.getAvailabilityProjectionByKey(releasedContext?.activeProjectionKey),
|
|
2565
|
+
snapshotId: releasedContext?.lastSnapshotId,
|
|
2566
|
+
orderRevision: releasedContext?.lastKnownOrderRevision,
|
|
2567
|
+
reason: 'commit_end'
|
|
2656
2568
|
});
|
|
2657
2569
|
}
|
|
2570
|
+
|
|
2658
2571
|
/** IO boundary: loads catalog, schedules, resources, occupancies, and registers one context. */
|
|
2659
2572
|
async prepareAvailabilityProjection(params) {
|
|
2660
2573
|
this.assertAvailabilityPrepareParams(params);
|
|
2661
2574
|
const paramsWithDefaults = await this.resolveAvailabilityPrepareDefaults(params);
|
|
2662
|
-
const {
|
|
2575
|
+
const {
|
|
2576
|
+
prepareKey,
|
|
2577
|
+
snapshot
|
|
2578
|
+
} = await this.getSharedPreparedAvailabilitySnapshot(paramsWithDefaults);
|
|
2663
2579
|
const contextId = this.createAvailabilityContextId();
|
|
2664
2580
|
const projectionEntry = this.storeAvailabilityProjectionEntry({
|
|
2665
2581
|
contextId,
|
|
@@ -2676,62 +2592,59 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2676
2592
|
contextVersion: context.activeVersion,
|
|
2677
2593
|
projection: projectionEntry.projection,
|
|
2678
2594
|
refreshed: false,
|
|
2679
|
-
reason:
|
|
2595
|
+
reason: 'prepare'
|
|
2680
2596
|
};
|
|
2681
2597
|
await this.emitAvailabilityDiagnostic({
|
|
2682
|
-
action:
|
|
2598
|
+
action: 'prepare',
|
|
2683
2599
|
contextId,
|
|
2684
2600
|
contextVersion: context.activeVersion,
|
|
2685
2601
|
projection: projectionEntry.projection,
|
|
2686
2602
|
snapshotId: context.lastSnapshotId,
|
|
2687
2603
|
orderRevision: context.lastKnownOrderRevision,
|
|
2688
|
-
reason:
|
|
2604
|
+
reason: 'prepare'
|
|
2689
2605
|
});
|
|
2690
|
-
await this.core.effects.emit(
|
|
2691
|
-
`${this.name}:${import_types.UnifiedBookingSalesHooks.onAvailabilityProjectionPrepared}`,
|
|
2692
|
-
payload
|
|
2693
|
-
);
|
|
2606
|
+
await this.core.effects.emit(`${this.name}:${_types.UnifiedBookingSalesHooks.onAvailabilityProjectionPrepared}`, payload);
|
|
2694
2607
|
return handle;
|
|
2695
2608
|
}
|
|
2609
|
+
|
|
2696
2610
|
/** Pure configuration calculation over the current context projection. */
|
|
2697
2611
|
async getProductTimeRanges(contextId, request = {}) {
|
|
2698
|
-
const {
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
);
|
|
2612
|
+
const {
|
|
2613
|
+
context,
|
|
2614
|
+
projectionEntry
|
|
2615
|
+
} = await this.ensureAvailabilityContextProjection(contextId, 'get_product_time_ranges');
|
|
2702
2616
|
const contextVersion = context.activeVersion;
|
|
2703
|
-
return (0,
|
|
2617
|
+
return (0, _ResourcePlanner.getProductTimeRanges)(projectionEntry.projection, request).map(group => ({
|
|
2704
2618
|
...group,
|
|
2705
|
-
ranges: group.ranges.map(
|
|
2619
|
+
ranges: group.ranges.map(candidate => this.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate))
|
|
2706
2620
|
}));
|
|
2707
2621
|
}
|
|
2622
|
+
|
|
2708
2623
|
/** Thin facade: auto-refresh on cart drift, then delegate to the pure projection query. */
|
|
2709
2624
|
async queryBookingAvailability(contextId, request) {
|
|
2710
|
-
const {
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2625
|
+
const {
|
|
2626
|
+
context,
|
|
2627
|
+
projectionEntry,
|
|
2628
|
+
refreshed
|
|
2629
|
+
} = await this.ensureAvailabilityContextProjection(contextId, 'query_booking_availability');
|
|
2714
2630
|
this.assertContextualQueryFreshness(context, request);
|
|
2715
|
-
const result = (0,
|
|
2716
|
-
projectionEntry.projection,
|
|
2717
|
-
request
|
|
2718
|
-
);
|
|
2631
|
+
const result = (0, _ResourcePlanner.queryAvailabilityProjection)(projectionEntry.projection, request);
|
|
2719
2632
|
const contextVersion = context.activeVersion;
|
|
2720
|
-
const contextualResult = result.target ===
|
|
2633
|
+
const contextualResult = result.target === 'time' ? {
|
|
2721
2634
|
...result,
|
|
2722
|
-
data: result.data.map(
|
|
2635
|
+
data: result.data.map(group => ({
|
|
2723
2636
|
...group,
|
|
2724
|
-
times: group.times.map(
|
|
2637
|
+
times: group.times.map(item => ({
|
|
2725
2638
|
...item,
|
|
2726
2639
|
availabilityContextId: context.contextId,
|
|
2727
2640
|
availabilityContextVersion: contextVersion
|
|
2728
2641
|
}))
|
|
2729
2642
|
}))
|
|
2730
|
-
} : result.target ===
|
|
2643
|
+
} : result.target === 'resource' ? {
|
|
2731
2644
|
...result,
|
|
2732
|
-
data: result.data.map(
|
|
2645
|
+
data: result.data.map(group => ({
|
|
2733
2646
|
...group,
|
|
2734
|
-
ranges: group.ranges.map(
|
|
2647
|
+
ranges: group.ranges.map(rangeGroup => ({
|
|
2735
2648
|
...rangeGroup,
|
|
2736
2649
|
range: {
|
|
2737
2650
|
...rangeGroup.range,
|
|
@@ -2749,65 +2662,55 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2749
2662
|
};
|
|
2750
2663
|
}
|
|
2751
2664
|
releaseAvailabilityContext(contextId) {
|
|
2752
|
-
var _a;
|
|
2753
2665
|
const context = this.availabilityContextRegistry.get(contextId);
|
|
2754
|
-
if (!context)
|
|
2755
|
-
return;
|
|
2666
|
+
if (!context) return;
|
|
2756
2667
|
this.removeAvailabilityContext(contextId);
|
|
2757
2668
|
void this.emitAvailabilityDiagnostic({
|
|
2758
|
-
action:
|
|
2669
|
+
action: 'release',
|
|
2759
2670
|
contextId,
|
|
2760
2671
|
contextVersion: context.activeVersion,
|
|
2761
|
-
projection:
|
|
2672
|
+
projection: this.getActiveAvailabilityProjectionEntry(context)?.projection || null,
|
|
2762
2673
|
snapshotId: context.lastSnapshotId,
|
|
2763
2674
|
orderRevision: context.lastKnownOrderRevision,
|
|
2764
|
-
reason:
|
|
2675
|
+
reason: 'explicit_release'
|
|
2765
2676
|
});
|
|
2766
2677
|
}
|
|
2767
2678
|
assertAvailabilityCommitWriteCAS(params) {
|
|
2768
2679
|
const context = this.availabilityContextRegistry.get(params.contextId);
|
|
2769
2680
|
if (!context) {
|
|
2770
|
-
throw this.createAvailabilityFacadeError(
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
{ contextId: params.contextId }
|
|
2774
|
-
);
|
|
2681
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_CONTEXT_NOT_FOUND', 'availability context 不存在或已失效,请重新 prepare', {
|
|
2682
|
+
contextId: params.contextId
|
|
2683
|
+
});
|
|
2775
2684
|
}
|
|
2776
2685
|
if (this.availabilityLifecycleGeneration !== params.expectedLifecycleGeneration || context.lifecycleGeneration !== params.expectedLifecycleGeneration || context.activeVersion !== params.expectedVersion || context.refreshNonce !== params.expectedRefreshNonce || this.getCurrentOrderRevision() !== params.expectedOrderRevision) {
|
|
2777
|
-
throw this.createAvailabilityFacadeError(
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
expectedOrderRevision: params.expectedOrderRevision,
|
|
2789
|
-
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2790
|
-
}
|
|
2791
|
-
);
|
|
2686
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', 'availability context 已变化,当前选择已失效,请重新获取候选', {
|
|
2687
|
+
contextId: params.contextId,
|
|
2688
|
+
expectedVersion: params.expectedVersion,
|
|
2689
|
+
currentVersion: context.activeVersion,
|
|
2690
|
+
expectedRefreshNonce: params.expectedRefreshNonce,
|
|
2691
|
+
currentRefreshNonce: context.refreshNonce,
|
|
2692
|
+
expectedLifecycleGeneration: params.expectedLifecycleGeneration,
|
|
2693
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
2694
|
+
expectedOrderRevision: params.expectedOrderRevision,
|
|
2695
|
+
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2696
|
+
});
|
|
2792
2697
|
}
|
|
2793
2698
|
return context;
|
|
2794
2699
|
}
|
|
2795
2700
|
findProductLineByUid(productLineUid) {
|
|
2796
|
-
|
|
2797
|
-
return (((_a = this.getTempOrder()) == null ? void 0 : _a.products) || []).find((product) => getProductLineUid(product) === String(productLineUid));
|
|
2701
|
+
return (this.getTempOrder()?.products || []).find(product => getProductLineUid(product) === String(productLineUid));
|
|
2798
2702
|
}
|
|
2799
2703
|
findLinkedBooking(product) {
|
|
2800
|
-
var _a, _b;
|
|
2801
2704
|
const productUid = getProductLineUid(product);
|
|
2802
|
-
const bookingUid = String(product.booking_uid ??
|
|
2803
|
-
return (
|
|
2705
|
+
const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
|
|
2706
|
+
return (this.getTempOrder()?.bookings || []).find(booking => bookingUid && getBookingUid(booking) === bookingUid || productUid && String(booking.product_uid ?? '') === productUid);
|
|
2804
2707
|
}
|
|
2708
|
+
|
|
2805
2709
|
/** Revalidates against the latest landed occupancy cache, then mutates exactly one product line. */
|
|
2806
2710
|
async commitAvailabilitySelection(contextId, params) {
|
|
2807
|
-
|
|
2808
|
-
const normalizedContextId = String(contextId || "").trim();
|
|
2711
|
+
const normalizedContextId = String(contextId || '').trim();
|
|
2809
2712
|
if (!normalizedContextId) {
|
|
2810
|
-
throw new
|
|
2713
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'commitAvailabilitySelection 缺少 contextId', {
|
|
2811
2714
|
candidate: params.candidate
|
|
2812
2715
|
});
|
|
2813
2716
|
}
|
|
@@ -2815,25 +2718,25 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2815
2718
|
try {
|
|
2816
2719
|
const bookingCount = Number(params.bookingCount ?? 1);
|
|
2817
2720
|
if (!Number.isInteger(bookingCount) || bookingCount <= 0) {
|
|
2818
|
-
throw new
|
|
2721
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_RESERVATION_QUANTITY', 'bookingCount 必须是正整数', {
|
|
2819
2722
|
bookingCount: params.bookingCount
|
|
2820
2723
|
});
|
|
2821
2724
|
}
|
|
2822
|
-
if (params.mode ===
|
|
2823
|
-
throw new
|
|
2725
|
+
if (params.mode === 'edit' && bookingCount !== 1) {
|
|
2726
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_RESERVATION_QUANTITY', '编辑预约商品时 bookingCount 必须为 1', {
|
|
2824
2727
|
bookingCount
|
|
2825
2728
|
});
|
|
2826
2729
|
}
|
|
2827
|
-
const orderProductQuantity =
|
|
2828
|
-
if (orderProductQuantity !==
|
|
2829
|
-
throw new
|
|
2730
|
+
const orderProductQuantity = params.orderProduct?.num ?? params.orderProduct?.quantity ?? params.orderProduct?._extend?.quantity;
|
|
2731
|
+
if (orderProductQuantity !== undefined && orderProductQuantity !== null && Number(orderProductQuantity) !== bookingCount) {
|
|
2732
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_RESERVATION_QUANTITY', 'orderProduct 数量与 bookingCount 不一致', {
|
|
2830
2733
|
bookingCount,
|
|
2831
2734
|
orderProductQuantity
|
|
2832
2735
|
});
|
|
2833
2736
|
}
|
|
2834
2737
|
const partySize = Number(params.partySize);
|
|
2835
2738
|
if (!Number.isInteger(partySize) || partySize <= 0) {
|
|
2836
|
-
throw new
|
|
2739
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_RESERVATION_QUANTITY', 'partySize 必须是正整数', {
|
|
2837
2740
|
partySize: params.partySize
|
|
2838
2741
|
});
|
|
2839
2742
|
}
|
|
@@ -2841,104 +2744,77 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2841
2744
|
context,
|
|
2842
2745
|
projectionEntry,
|
|
2843
2746
|
refreshed
|
|
2844
|
-
} = await this.ensureAvailabilityContextProjection(
|
|
2845
|
-
normalizedContextId,
|
|
2846
|
-
"commit_precheck"
|
|
2847
|
-
);
|
|
2747
|
+
} = await this.ensureAvailabilityContextProjection(normalizedContextId, 'commit_precheck');
|
|
2848
2748
|
if (refreshed) {
|
|
2849
|
-
throw this.createAvailabilityFacadeError(
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
currentContextVersion: context.activeVersion,
|
|
2855
|
-
candidateContextVersion: params.candidate.availabilityContextVersion
|
|
2856
|
-
}
|
|
2857
|
-
);
|
|
2749
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', 'availability context 已刷新,请重新获取候选', {
|
|
2750
|
+
contextId: normalizedContextId,
|
|
2751
|
+
currentContextVersion: context.activeVersion,
|
|
2752
|
+
candidateContextVersion: params.candidate.availabilityContextVersion
|
|
2753
|
+
});
|
|
2858
2754
|
}
|
|
2859
2755
|
const boundEditingUids = context.prepareParams.editingProductLineUids || [];
|
|
2860
|
-
if (params.mode ===
|
|
2756
|
+
if (params.mode === 'edit') {
|
|
2861
2757
|
if (!params.productLineUid || !boundEditingUids.includes(String(params.productLineUid))) {
|
|
2862
|
-
throw this.createAvailabilityFacadeError(
|
|
2863
|
-
"AVAILABILITY_SELECTION_STALE",
|
|
2864
|
-
"edit context 与当前商品行不匹配,请重新 prepare 编辑上下文",
|
|
2865
|
-
{
|
|
2866
|
-
contextId: normalizedContextId,
|
|
2867
|
-
productLineUid: params.productLineUid,
|
|
2868
|
-
editingProductLineUids: boundEditingUids
|
|
2869
|
-
}
|
|
2870
|
-
);
|
|
2871
|
-
}
|
|
2872
|
-
} else if (boundEditingUids.length > 0) {
|
|
2873
|
-
throw this.createAvailabilityFacadeError(
|
|
2874
|
-
"AVAILABILITY_SELECTION_STALE",
|
|
2875
|
-
"edit context 不能用于 add,请重新 prepare 新增上下文",
|
|
2876
|
-
{
|
|
2758
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', 'edit context 与当前商品行不匹配,请重新 prepare 编辑上下文', {
|
|
2877
2759
|
contextId: normalizedContextId,
|
|
2760
|
+
productLineUid: params.productLineUid,
|
|
2878
2761
|
editingProductLineUids: boundEditingUids
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
} else if (boundEditingUids.length > 0) {
|
|
2765
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', 'edit context 不能用于 add,请重新 prepare 新增上下文', {
|
|
2766
|
+
contextId: normalizedContextId,
|
|
2767
|
+
editingProductLineUids: boundEditingUids
|
|
2768
|
+
});
|
|
2881
2769
|
}
|
|
2882
2770
|
if (projectionEntry.contextVersion !== context.activeVersion) {
|
|
2883
|
-
throw this.createAvailabilityFacadeError(
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
currentContextVersion: context.activeVersion,
|
|
2889
|
-
projectionContextVersion: projectionEntry.contextVersion
|
|
2890
|
-
}
|
|
2891
|
-
);
|
|
2771
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', 'availability context 已刷新,请重新获取候选', {
|
|
2772
|
+
contextId: normalizedContextId,
|
|
2773
|
+
currentContextVersion: context.activeVersion,
|
|
2774
|
+
projectionContextVersion: projectionEntry.contextVersion
|
|
2775
|
+
});
|
|
2892
2776
|
}
|
|
2893
2777
|
if (projectionEntry.orderRevision !== this.getCurrentOrderRevision()) {
|
|
2894
|
-
throw this.createAvailabilityFacadeError(
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
projectionOrderRevision: projectionEntry.orderRevision,
|
|
2901
|
-
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2902
|
-
}
|
|
2903
|
-
);
|
|
2778
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '购物车已变化,请重新获取候选', {
|
|
2779
|
+
contextId: normalizedContextId,
|
|
2780
|
+
currentContextVersion: context.activeVersion,
|
|
2781
|
+
projectionOrderRevision: projectionEntry.orderRevision,
|
|
2782
|
+
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2783
|
+
});
|
|
2904
2784
|
}
|
|
2905
2785
|
if (projectionEntry.contextVersion !== params.candidate.availabilityContextVersion) {
|
|
2906
|
-
throw this.createAvailabilityFacadeError(
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
candidateContextVersion: params.candidate.availabilityContextVersion,
|
|
2912
|
-
currentContextVersion: context.activeVersion
|
|
2913
|
-
}
|
|
2914
|
-
);
|
|
2786
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '候选已过期,请重新获取最新时间段', {
|
|
2787
|
+
contextId: normalizedContextId,
|
|
2788
|
+
candidateContextVersion: params.candidate.availabilityContextVersion,
|
|
2789
|
+
currentContextVersion: context.activeVersion
|
|
2790
|
+
});
|
|
2915
2791
|
}
|
|
2916
2792
|
this.assertAvailabilityCandidateFreshness(context, params.candidate);
|
|
2917
|
-
const orderProductId =
|
|
2793
|
+
const orderProductId = params.orderProduct?.product_id ?? params.orderProduct?.id;
|
|
2918
2794
|
if (!sameAvailabilityId(orderProductId, params.candidate.productId)) {
|
|
2919
|
-
throw new
|
|
2795
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'orderProduct 与 candidate 商品不一致', {
|
|
2920
2796
|
orderProductId,
|
|
2921
2797
|
candidateProductId: params.candidate.productId
|
|
2922
2798
|
});
|
|
2923
2799
|
}
|
|
2924
|
-
if (params.mode ===
|
|
2925
|
-
throw new
|
|
2800
|
+
if (params.mode === 'edit' && !params.productLineUid) {
|
|
2801
|
+
throw new _ResourcePlanner.AvailabilityError('PRODUCT_LINE_NOT_FOUND', 'edit 模式必须提供 productLineUid');
|
|
2926
2802
|
}
|
|
2927
|
-
const editingLine = params.mode ===
|
|
2928
|
-
if (params.mode ===
|
|
2929
|
-
throw new
|
|
2803
|
+
const editingLine = params.mode === 'edit' ? this.findProductLineByUid(String(params.productLineUid)) : undefined;
|
|
2804
|
+
if (params.mode === 'edit' && !editingLine) {
|
|
2805
|
+
throw new _ResourcePlanner.AvailabilityError('PRODUCT_LINE_NOT_FOUND', '找不到要编辑的预约商品行', {
|
|
2930
2806
|
productLineUid: params.productLineUid
|
|
2931
2807
|
});
|
|
2932
2808
|
}
|
|
2933
2809
|
if (editingLine && !sameAvailabilityId(editingLine.product_id ?? editingLine.id, params.candidate.productId)) {
|
|
2934
|
-
throw new
|
|
2810
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', '编辑商品行与 candidate 商品不一致', {
|
|
2935
2811
|
productLineUid: params.productLineUid,
|
|
2936
2812
|
productLineProductId: editingLine.product_id ?? editingLine.id,
|
|
2937
2813
|
candidateProductId: params.candidate.productId
|
|
2938
2814
|
});
|
|
2939
2815
|
}
|
|
2940
2816
|
if (!Array.isArray(params.requirementSelections)) {
|
|
2941
|
-
throw new
|
|
2817
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'requirementSelections 必须是数组', {
|
|
2942
2818
|
requirementSelections: params.requirementSelections
|
|
2943
2819
|
});
|
|
2944
2820
|
}
|
|
@@ -2948,82 +2824,75 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
2948
2824
|
expectedVersion: context.activeVersion,
|
|
2949
2825
|
expectedRefreshNonce: context.refreshNonce,
|
|
2950
2826
|
expectedLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
2951
|
-
expectedOrderRevision: String(baseProjection.meta.orderRevision ??
|
|
2827
|
+
expectedOrderRevision: String(baseProjection.meta.orderRevision ?? '')
|
|
2952
2828
|
};
|
|
2953
2829
|
const prepareParams = baseProjection.meta.prepareParams;
|
|
2954
2830
|
if (!prepareParams) {
|
|
2955
|
-
throw new
|
|
2831
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', 'projection 缺少 prepareParams,无法执行缓存重校验');
|
|
2956
2832
|
}
|
|
2957
2833
|
const fallbackResources = {
|
|
2958
|
-
resources: (0,
|
|
2959
|
-
resourceOccupancies: (0,
|
|
2960
|
-
[...baseProjection.resourceOccupancies || []].filter((occupancy) => occupancy.source !== "cart")
|
|
2961
|
-
),
|
|
2834
|
+
resources: (0, _lodashEs.cloneDeep)([...(baseProjection.resources || [])]),
|
|
2835
|
+
resourceOccupancies: (0, _lodashEs.cloneDeep)([...(baseProjection.resourceOccupancies || [])].filter(occupancy => occupancy.source !== 'cart')),
|
|
2962
2836
|
includesInjectedRawOccupancies: true
|
|
2963
2837
|
};
|
|
2964
2838
|
const commitSnapshot = await this.buildAvailabilityProjectionSnapshot({
|
|
2965
2839
|
...this.cloneAvailabilityPrepareParams(prepareParams),
|
|
2966
|
-
editingProductLineUids: params.mode ===
|
|
2840
|
+
editingProductLineUids: params.mode === 'edit' && params.productLineUid ? [params.productLineUid] : []
|
|
2967
2841
|
}, {
|
|
2968
|
-
resourceFetchPolicy:
|
|
2842
|
+
resourceFetchPolicy: 'cache_only',
|
|
2969
2843
|
fallbackResources,
|
|
2970
2844
|
durationCandidateAnchorAt: this.getAvailabilityDurationCandidateAnchorAt(baseProjection) || context.durationCandidateAnchorAt
|
|
2971
2845
|
});
|
|
2972
2846
|
const refreshedProjection = commitSnapshot.projection;
|
|
2973
|
-
if (String(refreshedProjection.meta.orderRevision ??
|
|
2847
|
+
if (String(refreshedProjection.meta.orderRevision ?? '') !== String(baseProjection.meta.orderRevision ?? '')) {
|
|
2974
2848
|
await this.refreshAvailabilityContextProjection({
|
|
2975
2849
|
contextId: normalizedContextId,
|
|
2976
|
-
reason:
|
|
2977
|
-
previousOrderRevision: String(baseProjection.meta.orderRevision ??
|
|
2850
|
+
reason: 'commit_order_revision_changed',
|
|
2851
|
+
previousOrderRevision: String(baseProjection.meta.orderRevision ?? '')
|
|
2978
2852
|
});
|
|
2979
2853
|
const latestContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
2980
|
-
throw this.createAvailabilityFacadeError(
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
snapshotId: baseProjection.meta.snapshotId,
|
|
2990
|
-
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
2991
|
-
}
|
|
2992
|
-
);
|
|
2854
|
+
throw this.createAvailabilityFacadeError('AVAILABILITY_SELECTION_STALE', '最终缓存重校验期间购物车已变化,请重新获取候选', {
|
|
2855
|
+
contextId: normalizedContextId,
|
|
2856
|
+
candidateContextVersion: params.candidate.availabilityContextVersion,
|
|
2857
|
+
currentContextVersion: latestContext.activeVersion,
|
|
2858
|
+
projectionOrderRevision: baseProjection.meta.orderRevision,
|
|
2859
|
+
refreshedOrderRevision: refreshedProjection.meta.orderRevision,
|
|
2860
|
+
snapshotId: baseProjection.meta.snapshotId,
|
|
2861
|
+
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
2862
|
+
});
|
|
2993
2863
|
}
|
|
2994
2864
|
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
2995
|
-
const refreshedRanges = (0,
|
|
2865
|
+
const refreshedRanges = (0, _ResourcePlanner.getProductTimeRanges)(refreshedProjection, {
|
|
2996
2866
|
productIds: [params.candidate.productId],
|
|
2997
2867
|
dateRange: {
|
|
2998
2868
|
startDate: params.candidate.startAt.slice(0, 10),
|
|
2999
2869
|
endDate: params.candidate.startAt.slice(0, 10)
|
|
3000
2870
|
}
|
|
3001
|
-
}).flatMap(
|
|
3002
|
-
const refreshedCandidateBase = refreshedRanges.find(
|
|
2871
|
+
}).flatMap(group => group.ranges);
|
|
2872
|
+
const refreshedCandidateBase = refreshedRanges.find(candidate => candidate.startAt === params.candidate.startAt && candidate.endAt === params.candidate.endAt && candidate.bookingStartAt === params.candidate.bookingStartAt && candidate.bookingEndAt === params.candidate.bookingEndAt);
|
|
3003
2873
|
if (!refreshedCandidateBase) {
|
|
3004
|
-
throw new
|
|
2874
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '缓存重校验后候选时间已不在商品配置中', {
|
|
3005
2875
|
contextId: normalizedContextId,
|
|
3006
2876
|
candidate: params.candidate,
|
|
3007
2877
|
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
3008
2878
|
});
|
|
3009
2879
|
}
|
|
3010
|
-
const validation = (0,
|
|
2880
|
+
const validation = (0, _ResourcePlanner.validateAvailabilitySelection)(refreshedProjection, {
|
|
3011
2881
|
candidate: refreshedCandidateBase,
|
|
3012
2882
|
requirementSelections: params.requirementSelections,
|
|
3013
2883
|
partySize,
|
|
3014
2884
|
bookingCount
|
|
3015
2885
|
});
|
|
3016
2886
|
if (!validation.ok) {
|
|
3017
|
-
const capacityConflict = validation.conflicts.find(
|
|
3018
|
-
throw new
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
validation,
|
|
3023
|
-
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
3024
|
-
}
|
|
3025
|
-
);
|
|
2887
|
+
const capacityConflict = validation.conflicts.find(conflict => conflict.code === 'resource_capacity_exceeded');
|
|
2888
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', capacityConflict?.message || '缓存重校验后资源已不可提交', {
|
|
2889
|
+
validation,
|
|
2890
|
+
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
2891
|
+
});
|
|
3026
2892
|
}
|
|
2893
|
+
// Revalidation failure must leave the caller's candidate reusable. Only publish the
|
|
2894
|
+
// refreshed projection/version after candidate and resource validation has passed;
|
|
2895
|
+
// otherwise a second Confirm from the still-open modal would incorrectly become stale.
|
|
3027
2896
|
const revalidationEntry = this.storeAvailabilityProjectionEntry({
|
|
3028
2897
|
contextId: normalizedContextId,
|
|
3029
2898
|
prepareParams: commitSnapshot.prepareParams,
|
|
@@ -3034,14 +2903,14 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
3034
2903
|
});
|
|
3035
2904
|
const revalidatedContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
3036
2905
|
await this.emitAvailabilityDiagnostic({
|
|
3037
|
-
action:
|
|
2906
|
+
action: 'refresh',
|
|
3038
2907
|
contextId: normalizedContextId,
|
|
3039
2908
|
contextVersion: revalidatedContext.activeVersion,
|
|
3040
2909
|
projection: refreshedProjection,
|
|
3041
2910
|
snapshotId: revalidationEntry.snapshotId,
|
|
3042
2911
|
orderRevision: revalidationEntry.orderRevision,
|
|
3043
2912
|
previousOrderRevision: baseProjection.meta.orderRevision,
|
|
3044
|
-
reason:
|
|
2913
|
+
reason: 'commit_cached_occupancy_revalidation'
|
|
3045
2914
|
});
|
|
3046
2915
|
commitWriteGuard = {
|
|
3047
2916
|
contextId: normalizedContextId,
|
|
@@ -3050,21 +2919,17 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
3050
2919
|
expectedLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
3051
2920
|
expectedOrderRevision: revalidationEntry.orderRevision
|
|
3052
2921
|
};
|
|
3053
|
-
const refreshedCandidate = this.decorateAvailabilityCandidate(
|
|
3054
|
-
|
|
3055
|
-
revalidatedContext.activeVersion,
|
|
3056
|
-
refreshedCandidateBase
|
|
3057
|
-
);
|
|
3058
|
-
const product = refreshedProjection.products.find((item) => sameAvailabilityId(item.id, refreshedCandidate.productId));
|
|
2922
|
+
const refreshedCandidate = this.decorateAvailabilityCandidate(normalizedContextId, revalidatedContext.activeVersion, refreshedCandidateBase);
|
|
2923
|
+
const product = refreshedProjection.products.find(item => sameAvailabilityId(item.id, refreshedCandidate.productId));
|
|
3059
2924
|
if (!product) {
|
|
3060
|
-
throw new
|
|
2925
|
+
throw new _ResourcePlanner.AvailabilityError('REVALIDATION_FAILED', '缓存重校验后商品不在 projection 中', {
|
|
3061
2926
|
productId: refreshedCandidate.productId
|
|
3062
2927
|
});
|
|
3063
2928
|
}
|
|
3064
|
-
const existingBooking = editingLine ? this.findLinkedBooking(editingLine) :
|
|
3065
|
-
const existingBookingUid = existingBooking ? getBookingUid(existingBooking) :
|
|
3066
|
-
if (params.mode ===
|
|
3067
|
-
throw new
|
|
2929
|
+
const existingBooking = editingLine ? this.findLinkedBooking(editingLine) : undefined;
|
|
2930
|
+
const existingBookingUid = existingBooking ? getBookingUid(existingBooking) : '';
|
|
2931
|
+
if (params.mode === 'edit' && !existingBookingUid) {
|
|
2932
|
+
throw new _ResourcePlanner.AvailabilityError('PRODUCT_LINE_NOT_FOUND', '编辑商品行缺少关联 booking', {
|
|
3068
2933
|
productLineUid: params.productLineUid
|
|
3069
2934
|
});
|
|
3070
2935
|
}
|
|
@@ -3074,37 +2939,37 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
3074
2939
|
candidate: refreshedCandidate,
|
|
3075
2940
|
product,
|
|
3076
2941
|
resources: refreshedProjection.resources,
|
|
3077
|
-
bookingUid: existingBookingUid ||
|
|
2942
|
+
bookingUid: existingBookingUid || undefined
|
|
3078
2943
|
});
|
|
3079
2944
|
const transformed = this.buildUnifiedOrderProduct({
|
|
3080
2945
|
...params.orderProduct,
|
|
3081
2946
|
num: bookingCount,
|
|
3082
2947
|
quantity: bookingCount
|
|
3083
|
-
},
|
|
2948
|
+
}, 'unified_booking_sales', product);
|
|
3084
2949
|
if (!transformed) {
|
|
3085
|
-
throw new
|
|
2950
|
+
throw new _ResourcePlanner.AvailabilityError('INVALID_TARGET_FILTER', 'orderProduct 无法转换为订单行', {
|
|
3086
2951
|
orderProduct: params.orderProduct
|
|
3087
2952
|
});
|
|
3088
2953
|
}
|
|
3089
2954
|
transformed.num = bookingCount;
|
|
3090
2955
|
transformed.metadata = {
|
|
3091
|
-
...transformed.metadata || {},
|
|
2956
|
+
...(transformed.metadata || {}),
|
|
3092
2957
|
unified_booking_sales: true,
|
|
3093
2958
|
availability_candidate_key: refreshedCandidate.key,
|
|
3094
2959
|
availability_context_id: normalizedContextId,
|
|
3095
2960
|
availability_context_version: refreshedCandidate.availabilityContextVersion
|
|
3096
2961
|
};
|
|
3097
|
-
let productLineUid =
|
|
3098
|
-
let bookingUid =
|
|
2962
|
+
let productLineUid = '';
|
|
2963
|
+
let bookingUid = '';
|
|
3099
2964
|
let productLineUids = [];
|
|
3100
2965
|
let bookingUids = [];
|
|
3101
|
-
if (params.mode ===
|
|
2966
|
+
if (params.mode === 'edit' && editingLine && params.productLineUid) {
|
|
3102
2967
|
transformed.product_sku = transformed.product_sku ?? editingLine.product_sku;
|
|
3103
2968
|
transformed.product_bundle = transformed.product_bundle ?? editingLine.product_bundle;
|
|
3104
2969
|
transformed.booking_uid = existingBookingUid;
|
|
3105
2970
|
transformed.metadata = {
|
|
3106
|
-
...editingLine.metadata || {},
|
|
3107
|
-
...transformed.metadata || {},
|
|
2971
|
+
...(editingLine.metadata || {}),
|
|
2972
|
+
...(transformed.metadata || {}),
|
|
3108
2973
|
unique_identification_number: params.productLineUid,
|
|
3109
2974
|
booking_uid: existingBookingUid
|
|
3110
2975
|
};
|
|
@@ -3120,33 +2985,18 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
3120
2985
|
productLineUids = [productLineUid];
|
|
3121
2986
|
bookingUids = [bookingUid];
|
|
3122
2987
|
} else {
|
|
3123
|
-
const beforeProductLineUids = new Set(
|
|
3124
|
-
|
|
3125
|
-
);
|
|
3126
|
-
const beforeBookingUids = new Set(
|
|
3127
|
-
(((_h = this.getTempOrder()) == null ? void 0 : _h.bookings) || []).map((item) => getBookingUid(item)).filter(Boolean)
|
|
3128
|
-
);
|
|
2988
|
+
const beforeProductLineUids = new Set((this.getTempOrder()?.products || []).map(line => getProductLineUid(line)).filter(Boolean));
|
|
2989
|
+
const beforeBookingUids = new Set((this.getTempOrder()?.bookings || []).map(item => getBookingUid(item)).filter(Boolean));
|
|
3129
2990
|
const products = await this.addProductToOrder(transformed, booking);
|
|
3130
|
-
const addedLines = products.filter((line)
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
productLineUids = addedLines.map((line) => getProductLineUid(line)).filter(Boolean);
|
|
3135
|
-
const addedBookings = (((_i = this.getTempOrder()) == null ? void 0 : _i.bookings) || []).filter((item) => {
|
|
3136
|
-
var _a2;
|
|
3137
|
-
return ((_a2 = item.metadata) == null ? void 0 : _a2.availability_candidate_key) === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item));
|
|
3138
|
-
});
|
|
3139
|
-
bookingUids = addedBookings.map((item) => getBookingUid(item)).filter(Boolean);
|
|
2991
|
+
const addedLines = products.filter(line => sameAvailabilityId(line.product_id ?? line.id, refreshedCandidate.productId) && line.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeProductLineUids.has(getProductLineUid(line)));
|
|
2992
|
+
productLineUids = addedLines.map(line => getProductLineUid(line)).filter(Boolean);
|
|
2993
|
+
const addedBookings = (this.getTempOrder()?.bookings || []).filter(item => item.metadata?.availability_candidate_key === refreshedCandidate.key && !beforeBookingUids.has(getBookingUid(item)));
|
|
2994
|
+
bookingUids = addedBookings.map(item => getBookingUid(item)).filter(Boolean);
|
|
3140
2995
|
if (bookingUids.length === 0) {
|
|
3141
|
-
bookingUids = addedLines.map(
|
|
3142
|
-
var _a2;
|
|
3143
|
-
return String(
|
|
3144
|
-
line.booking_uid ?? ((_a2 = line.metadata) == null ? void 0 : _a2.booking_uid) ?? ""
|
|
3145
|
-
);
|
|
3146
|
-
}).filter(Boolean);
|
|
2996
|
+
bookingUids = addedLines.map(line => String(line.booking_uid ?? line.metadata?.booking_uid ?? '')).filter(Boolean);
|
|
3147
2997
|
}
|
|
3148
|
-
productLineUid = productLineUids[productLineUids.length - 1] ||
|
|
3149
|
-
bookingUid = bookingUids[bookingUids.length - 1] ||
|
|
2998
|
+
productLineUid = productLineUids[productLineUids.length - 1] || '';
|
|
2999
|
+
bookingUid = bookingUids[bookingUids.length - 1] || '';
|
|
3150
3000
|
}
|
|
3151
3001
|
this.invalidateAvailabilityContextsAfterCommit();
|
|
3152
3002
|
const nextContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
@@ -3161,28 +3011,12 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
3161
3011
|
productLineUids,
|
|
3162
3012
|
bookingUids
|
|
3163
3013
|
};
|
|
3164
|
-
await this.core.effects.emit(
|
|
3165
|
-
`${this.name}:${import_types.UnifiedBookingSalesHooks.onAvailabilitySelectionCommitted}`,
|
|
3166
|
-
result
|
|
3167
|
-
);
|
|
3014
|
+
await this.core.effects.emit(`${this.name}:${_types.UnifiedBookingSalesHooks.onAvailabilitySelectionCommitted}`, result);
|
|
3168
3015
|
return result;
|
|
3169
3016
|
} finally {
|
|
3170
3017
|
await this.releaseAvailabilityCommitLock(normalizedContextId);
|
|
3171
3018
|
}
|
|
3172
3019
|
}
|
|
3173
|
-
}
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
0 && (module.exports = {
|
|
3177
|
-
AvailabilityError,
|
|
3178
|
-
UnifiedBookingSales,
|
|
3179
|
-
UnifiedBookingSalesImpl,
|
|
3180
|
-
buildAvailabilityLineIdentity,
|
|
3181
|
-
buildAvailabilityResourcesFromV2,
|
|
3182
|
-
buildBookingFromAvailabilityAssignment,
|
|
3183
|
-
buildBookingFromAvailabilityAssignments,
|
|
3184
|
-
buildPlannerDiscountResult,
|
|
3185
|
-
collectAvailabilityResourceIds,
|
|
3186
|
-
normalizeProductForAvailability,
|
|
3187
|
-
...require("./types")
|
|
3188
|
-
});
|
|
3020
|
+
}
|
|
3021
|
+
exports.UnifiedBookingSalesImpl = UnifiedBookingSalesImpl;
|
|
3022
|
+
const UnifiedBookingSales = exports.UnifiedBookingSales = UnifiedBookingSalesImpl;
|