@pisell/pisellos 2.2.283 → 2.2.285
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.js +6 -10
- package/dist/model/strategy/adapter/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 +64 -86
- package/dist/modules/Order/types.d.ts +3 -1
- package/dist/modules/Order/types.js +1 -0
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/ProductList/index.js +91 -65
- package/dist/modules/Rules/index.js +1 -4
- package/dist/modules/Schedule/index.js +0 -1
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.js +97 -129
- package/dist/server/modules/menu/index.js +32 -41
- package/dist/server/modules/order/index.js +2 -4
- package/dist/server/modules/products/index.js +24 -42
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +3 -4
- package/dist/server/modules/schedule/index.js +27 -35
- package/dist/server/utils/product.js +0 -1
- package/dist/solution/BaseSales/index.js +115 -76
- 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.d.ts +6 -16
- package/dist/solution/BookingTicket/index.js +13 -6
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
- package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.js +177 -252
- package/dist/solution/Checkout/utils/index.js +4 -16
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/ScanOrder/index.js +50 -60
- package/dist/solution/ShopDiscount/index.js +2 -7
- package/dist/solution/VenueBooking/index.js +45 -55
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +131 -132
- 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 +46 -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 +1728 -2123
- package/lib/modules/Order/payment-utils.js +77 -120
- package/lib/modules/Order/types.d.ts +3 -1
- 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 +119 -123
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.js +66 -100
- 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 +60 -61
- package/lib/modules/ResourcePlanner/planner.js +346 -384
- package/lib/modules/ResourcePlanner/types.js +4 -16
- package/lib/modules/Rules/index.js +1218 -959
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.js +85 -90
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.js +355 -430
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
- package/lib/modules/Schedule/index.js +100 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -71
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +773 -418
- package/lib/modules/SurchargeList/index.js +46 -60
- package/lib/modules/SurchargeList/types.js +4 -16
- package/lib/modules/index.js +245 -63
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.js +3027 -2655
- package/lib/server/modules/floor-plan/index.js +102 -123
- 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 +146 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.js +758 -956
- package/lib/server/modules/order/types.js +122 -33
- package/lib/server/modules/order/utils/filterBookings.js +219 -194
- package/lib/server/modules/order/utils/filterOrders.js +118 -92
- package/lib/server/modules/payment/index.js +59 -83
- package/lib/server/modules/payment/types.js +4 -16
- package/lib/server/modules/products/index.js +575 -459
- 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 +213 -184
- 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 +1220 -1412
- package/lib/solution/BaseSales/types.js +42 -38
- package/lib/solution/BaseSales/utils/cartPromotion.js +650 -502
- 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.d.ts +6 -16
- package/lib/solution/BookingTicket/index.js +608 -517
- package/lib/solution/BookingTicket/types.js +99 -77
- package/lib/solution/BookingTicket/utils/addProductDecision.js +225 -187
- 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 +437 -487
- package/lib/solution/UnifiedBookingSales/types.js +12 -31
- 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,55 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
1
|
+
"use strict";
|
|
28
2
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
__export(planner_exports, {
|
|
32
|
-
DEFAULT_RESOURCE_PLANNER_CONTEXT: () => DEFAULT_RESOURCE_PLANNER_CONTEXT,
|
|
33
|
-
DEFAULT_RESOURCE_PLANNER_SELECTION: () => DEFAULT_RESOURCE_PLANNER_SELECTION,
|
|
34
|
-
autoAssignResources: () => autoAssignResources,
|
|
35
|
-
buildDemandIntervals: () => buildDemandIntervals,
|
|
36
|
-
evaluateResourceForInterval: () => evaluateResourceForInterval,
|
|
37
|
-
normalizePlannerContext: () => normalizePlannerContext,
|
|
38
|
-
queryPlannerAvailability: () => queryPlannerAvailability,
|
|
39
|
-
resolveAssignableSlots: () => resolveAssignableSlots,
|
|
40
|
-
validateAssignments: () => validateAssignments
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
41
5
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
6
|
+
exports.DEFAULT_RESOURCE_PLANNER_SELECTION = exports.DEFAULT_RESOURCE_PLANNER_CONTEXT = void 0;
|
|
7
|
+
exports.autoAssignResources = autoAssignResources;
|
|
8
|
+
exports.buildDemandIntervals = buildDemandIntervals;
|
|
9
|
+
exports.evaluateResourceForInterval = evaluateResourceForInterval;
|
|
10
|
+
exports.normalizePlannerContext = normalizePlannerContext;
|
|
11
|
+
exports.queryPlannerAvailability = queryPlannerAvailability;
|
|
12
|
+
exports.resolveAssignableSlots = resolveAssignableSlots;
|
|
13
|
+
exports.validateAssignments = validateAssignments;
|
|
14
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
15
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
+
const DEFAULT_RESOURCE_PLANNER_CONTEXT = exports.DEFAULT_RESOURCE_PLANNER_CONTEXT = {
|
|
45
17
|
products: [],
|
|
46
18
|
resources: [],
|
|
47
19
|
externalEvents: [],
|
|
48
20
|
slotDurationMinutes: 30,
|
|
49
|
-
businessStartTime:
|
|
50
|
-
businessEndTime:
|
|
21
|
+
businessStartTime: '09:00',
|
|
22
|
+
businessEndTime: '18:00'
|
|
51
23
|
};
|
|
52
|
-
|
|
24
|
+
const DEFAULT_RESOURCE_PLANNER_SELECTION = exports.DEFAULT_RESOURCE_PLANNER_SELECTION = {
|
|
53
25
|
productIds: [],
|
|
54
26
|
resourceIds: [],
|
|
55
27
|
assignments: []
|
|
@@ -68,15 +40,14 @@ function sameId(left, right) {
|
|
|
68
40
|
return String(left) === String(right);
|
|
69
41
|
}
|
|
70
42
|
function dateOf(value) {
|
|
71
|
-
return (0,
|
|
43
|
+
return (0, _dayjs.default)(value).format('YYYY-MM-DD');
|
|
72
44
|
}
|
|
73
45
|
function timeOf(value) {
|
|
74
|
-
return (0,
|
|
46
|
+
return (0, _dayjs.default)(value).format('HH:mm');
|
|
75
47
|
}
|
|
76
48
|
function toDateTime(date, time) {
|
|
77
|
-
if (/^\d{4}-\d{2}-\d{2}/.test(time))
|
|
78
|
-
|
|
79
|
-
return (0, import_dayjs.default)(`${date} ${time.length === 5 ? `${time}:00` : time}`);
|
|
49
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(time)) return (0, _dayjs.default)(time);
|
|
50
|
+
return (0, _dayjs.default)(`${date} ${time.length === 5 ? `${time}:00` : time}`);
|
|
80
51
|
}
|
|
81
52
|
function overlaps(startA, endA, startB, endB) {
|
|
82
53
|
return startA.isBefore(endB) && endA.isAfter(startB);
|
|
@@ -92,44 +63,36 @@ function minDayjs(left, right) {
|
|
|
92
63
|
}
|
|
93
64
|
function addDays(start, end) {
|
|
94
65
|
const result = [];
|
|
95
|
-
let cursor = (0,
|
|
96
|
-
const finalDay = (0,
|
|
97
|
-
while (!cursor.isAfter(finalDay,
|
|
98
|
-
result.push(cursor.format(
|
|
99
|
-
cursor = cursor.add(1,
|
|
66
|
+
let cursor = (0, _dayjs.default)(start);
|
|
67
|
+
const finalDay = (0, _dayjs.default)(end);
|
|
68
|
+
while (!cursor.isAfter(finalDay, 'day')) {
|
|
69
|
+
result.push(cursor.format('YYYY-MM-DD'));
|
|
70
|
+
cursor = cursor.add(1, 'day');
|
|
100
71
|
}
|
|
101
72
|
return result;
|
|
102
73
|
}
|
|
103
74
|
function getQueryProducts(products, query) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return products;
|
|
107
|
-
return products.filter((product) => query.productIds.some((id) => sameId(id, product.id)));
|
|
75
|
+
if (!query.productIds?.length) return products;
|
|
76
|
+
return products.filter(product => query.productIds.some(id => sameId(id, product.id)));
|
|
108
77
|
}
|
|
109
78
|
function getQueryResources(resources, query) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return resources;
|
|
113
|
-
return resources.filter((resource) => query.resourceIds.some((id) => sameId(id, resource.id)));
|
|
79
|
+
if (!query.resourceIds?.length) return resources;
|
|
80
|
+
return resources.filter(resource => query.resourceIds.some(id => sameId(id, resource.id)));
|
|
114
81
|
}
|
|
115
82
|
function matchesResourceRequirement(resource, requirement) {
|
|
116
|
-
var _a;
|
|
117
83
|
const isFormlessCandidate = resource.formId == null;
|
|
118
84
|
const formMatches = requirement.formId == null || isFormlessCandidate || sameId(requirement.formId, resource.formId);
|
|
119
|
-
const resourceMatches = !
|
|
85
|
+
const resourceMatches = !requirement.resourceIds?.length || isFormlessCandidate || requirement.resourceIds.some(id => sameId(id, resource.id));
|
|
120
86
|
return formMatches && resourceMatches;
|
|
121
87
|
}
|
|
122
88
|
function resolveProductTitle(product) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (typeof
|
|
126
|
-
|
|
127
|
-
if (typeof rawTitle === "string")
|
|
128
|
-
return rawTitle;
|
|
129
|
-
if (rawTitle && typeof rawTitle === "object") {
|
|
89
|
+
const rawTitle = product.raw?.title || product.raw?.name || product.raw?.product_title;
|
|
90
|
+
if (typeof product.title === 'string') return product.title;
|
|
91
|
+
if (typeof rawTitle === 'string') return rawTitle;
|
|
92
|
+
if (rawTitle && typeof rawTitle === 'object') {
|
|
130
93
|
return rawTitle.en || rawTitle.zh || rawTitle.default || Object.values(rawTitle)[0];
|
|
131
94
|
}
|
|
132
|
-
return
|
|
95
|
+
return undefined;
|
|
133
96
|
}
|
|
134
97
|
function buildDemandIntervals(products, query, context) {
|
|
135
98
|
const dates = query.date ? [query.date] : query.dateRange ? addDays(query.dateRange.start, query.dateRange.end) : [];
|
|
@@ -139,14 +102,13 @@ function buildDemandIntervals(products, query, context) {
|
|
|
139
102
|
const quantity = Math.max(1, Number(product.quantity || 1));
|
|
140
103
|
const capacityRequired = Math.max(1, Number(query.pax || product.capacityRequired || 1));
|
|
141
104
|
const requirements = product.resourceRequirements || [];
|
|
142
|
-
if (product.kind ===
|
|
105
|
+
if (product.kind === 'session' || product.kind === 'venue_slot') {
|
|
143
106
|
const schedules = product.schedule || [];
|
|
144
107
|
for (const schedule of schedules) {
|
|
145
108
|
const scheduleDate = dateOf(schedule.start_at);
|
|
146
|
-
if (dates.length > 0 && !dates.includes(scheduleDate))
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
const end = (0, import_dayjs.default)(schedule.end_at);
|
|
109
|
+
if (dates.length > 0 && !dates.includes(scheduleDate)) continue;
|
|
110
|
+
const start = (0, _dayjs.default)(schedule.start_at);
|
|
111
|
+
const end = (0, _dayjs.default)(schedule.end_at);
|
|
150
112
|
intervals.push({
|
|
151
113
|
id: `${product.id}:${start.toISOString()}:${end.toISOString()}`,
|
|
152
114
|
productId: product.id,
|
|
@@ -154,7 +116,7 @@ function buildDemandIntervals(products, query, context) {
|
|
|
154
116
|
date: dateOf(schedule.start_at),
|
|
155
117
|
start_at: start.toISOString(),
|
|
156
118
|
end_at: end.toISOString(),
|
|
157
|
-
durationMinutes: Math.max(1, end.diff(start,
|
|
119
|
+
durationMinutes: Math.max(1, end.diff(start, 'minute')),
|
|
158
120
|
quantity,
|
|
159
121
|
capacityRequired,
|
|
160
122
|
resourceRequirements: requirements
|
|
@@ -162,17 +124,16 @@ function buildDemandIntervals(products, query, context) {
|
|
|
162
124
|
}
|
|
163
125
|
continue;
|
|
164
126
|
}
|
|
165
|
-
if (product.kind ===
|
|
127
|
+
if (product.kind === 'duration') {
|
|
166
128
|
const duration = Math.max(1, Number(product.durationMinutes || 30));
|
|
167
129
|
for (const currentDate of dates) {
|
|
168
|
-
const start = query.startTime ? toDateTime(currentDate, query.startTime) : toDateTime(currentDate,
|
|
169
|
-
let end = query.startTime && query.endTime ? toDateTime(currentDate, query.endTime) : query.startTime ? start.add(duration,
|
|
170
|
-
if (!end.isAfter(start))
|
|
171
|
-
end = end.add(1, "day");
|
|
130
|
+
const start = query.startTime ? toDateTime(currentDate, query.startTime) : toDateTime(currentDate, context?.businessStartTime || DEFAULT_RESOURCE_PLANNER_CONTEXT.businessStartTime);
|
|
131
|
+
let end = query.startTime && query.endTime ? toDateTime(currentDate, query.endTime) : query.startTime ? start.add(duration, 'minute') : toDateTime(currentDate, context?.businessEndTime || DEFAULT_RESOURCE_PLANNER_CONTEXT.businessEndTime);
|
|
132
|
+
if (!end.isAfter(start)) end = end.add(1, 'day');
|
|
172
133
|
intervals.push({
|
|
173
|
-
id: `${product.id}:${currentDate}:${query.startTime ||
|
|
134
|
+
id: `${product.id}:${currentDate}:${query.startTime || 'range'}`,
|
|
174
135
|
productId: product.id,
|
|
175
|
-
source:
|
|
136
|
+
source: 'duration',
|
|
176
137
|
date: currentDate,
|
|
177
138
|
start_at: start.toISOString(),
|
|
178
139
|
end_at: end.toISOString(),
|
|
@@ -187,75 +148,83 @@ function buildDemandIntervals(products, query, context) {
|
|
|
187
148
|
return intervals;
|
|
188
149
|
}
|
|
189
150
|
function getEventsForResource(resource, contextEvents) {
|
|
190
|
-
const embeddedEvents = resource.times.flatMap(
|
|
191
|
-
return [
|
|
192
|
-
...embeddedEvents,
|
|
193
|
-
...contextEvents.filter((event) => sameId(event.resourceId ?? event.resource_id, resource.id))
|
|
194
|
-
].filter(Boolean);
|
|
151
|
+
const embeddedEvents = resource.times.flatMap(time => time.event_list || []);
|
|
152
|
+
return [...embeddedEvents, ...contextEvents.filter(event => sameId(event.resourceId ?? event.resource_id, resource.id))].filter(Boolean);
|
|
195
153
|
}
|
|
196
154
|
function collectEventsByResource(resources, contextEvents) {
|
|
197
|
-
const eventsByResource =
|
|
198
|
-
resources.forEach(
|
|
155
|
+
const eventsByResource = new Map();
|
|
156
|
+
resources.forEach(resource => {
|
|
199
157
|
eventsByResource.set(String(resource.id), getEventsForResource(resource, contextEvents));
|
|
200
158
|
});
|
|
201
159
|
return eventsByResource;
|
|
202
160
|
}
|
|
203
161
|
function getWorkingSegmentsForInterval(resource, interval) {
|
|
204
|
-
const intervalStart = (0,
|
|
205
|
-
const intervalEnd = (0,
|
|
206
|
-
return resource.times.map(
|
|
207
|
-
const start = maxDayjs((0,
|
|
208
|
-
const end = minDayjs((0,
|
|
209
|
-
return {
|
|
210
|
-
|
|
162
|
+
const intervalStart = (0, _dayjs.default)(interval.start_at);
|
|
163
|
+
const intervalEnd = (0, _dayjs.default)(interval.end_at);
|
|
164
|
+
return resource.times.map(time => {
|
|
165
|
+
const start = maxDayjs((0, _dayjs.default)(time.start_at), intervalStart);
|
|
166
|
+
const end = minDayjs((0, _dayjs.default)(time.end_at), intervalEnd);
|
|
167
|
+
return {
|
|
168
|
+
start,
|
|
169
|
+
end
|
|
170
|
+
};
|
|
171
|
+
}).filter(segment => segment.end.isAfter(segment.start));
|
|
211
172
|
}
|
|
212
173
|
function mergeBusySegments(segments) {
|
|
213
|
-
const sorted = [...segments].filter(
|
|
174
|
+
const sorted = [...segments].filter(segment => segment.end.isAfter(segment.start)).sort((left, right) => left.start.valueOf() - right.start.valueOf());
|
|
214
175
|
const merged = [];
|
|
215
|
-
sorted.forEach(
|
|
176
|
+
sorted.forEach(segment => {
|
|
216
177
|
const previous = merged[merged.length - 1];
|
|
217
178
|
if (previous && !segment.start.isAfter(previous.end)) {
|
|
218
179
|
previous.end = maxDayjs(previous.end, segment.end);
|
|
219
180
|
} else {
|
|
220
|
-
merged.push({
|
|
181
|
+
merged.push({
|
|
182
|
+
...segment
|
|
183
|
+
});
|
|
221
184
|
}
|
|
222
185
|
});
|
|
223
186
|
return merged;
|
|
224
187
|
}
|
|
225
188
|
function subtractBusyIntervals(workingSegments, events) {
|
|
226
189
|
const available = [];
|
|
227
|
-
workingSegments.forEach(
|
|
228
|
-
const busySegments = mergeBusySegments(events.filter(
|
|
229
|
-
start: maxDayjs((0,
|
|
230
|
-
end: minDayjs((0,
|
|
190
|
+
workingSegments.forEach(working => {
|
|
191
|
+
const busySegments = mergeBusySegments(events.filter(event => overlaps(working.start, working.end, (0, _dayjs.default)(event.start_at), (0, _dayjs.default)(event.end_at))).map(event => ({
|
|
192
|
+
start: maxDayjs((0, _dayjs.default)(event.start_at), working.start),
|
|
193
|
+
end: minDayjs((0, _dayjs.default)(event.end_at), working.end)
|
|
231
194
|
})));
|
|
232
195
|
let cursor = working.start;
|
|
233
|
-
busySegments.forEach(
|
|
196
|
+
busySegments.forEach(busy => {
|
|
234
197
|
if (busy.start.isAfter(cursor)) {
|
|
235
|
-
available.push({
|
|
198
|
+
available.push({
|
|
199
|
+
start: cursor,
|
|
200
|
+
end: busy.start
|
|
201
|
+
});
|
|
236
202
|
}
|
|
237
|
-
if (busy.end.isAfter(cursor))
|
|
238
|
-
cursor = busy.end;
|
|
203
|
+
if (busy.end.isAfter(cursor)) cursor = busy.end;
|
|
239
204
|
});
|
|
240
205
|
if (working.end.isAfter(cursor)) {
|
|
241
|
-
available.push({
|
|
206
|
+
available.push({
|
|
207
|
+
start: cursor,
|
|
208
|
+
end: working.end
|
|
209
|
+
});
|
|
242
210
|
}
|
|
243
211
|
});
|
|
244
212
|
return available;
|
|
245
213
|
}
|
|
246
214
|
function buildCapacityRanges(params) {
|
|
247
|
-
const {
|
|
215
|
+
const {
|
|
216
|
+
workingSegments,
|
|
217
|
+
events,
|
|
218
|
+
maxCapacity,
|
|
219
|
+
requiredCapacity
|
|
220
|
+
} = params;
|
|
248
221
|
const ranges = [];
|
|
249
|
-
workingSegments.forEach(
|
|
250
|
-
const boundaries =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const eventStart = (0, import_dayjs.default)(event.start_at);
|
|
256
|
-
const eventEnd = (0, import_dayjs.default)(event.end_at);
|
|
257
|
-
if (!overlaps(working.start, working.end, eventStart, eventEnd))
|
|
258
|
-
return;
|
|
222
|
+
workingSegments.forEach(working => {
|
|
223
|
+
const boundaries = new Map([[working.start.valueOf(), working.start], [working.end.valueOf(), working.end]]);
|
|
224
|
+
events.forEach(event => {
|
|
225
|
+
const eventStart = (0, _dayjs.default)(event.start_at);
|
|
226
|
+
const eventEnd = (0, _dayjs.default)(event.end_at);
|
|
227
|
+
if (!overlaps(working.start, working.end, eventStart, eventEnd)) return;
|
|
259
228
|
const clippedStart = maxDayjs(eventStart, working.start);
|
|
260
229
|
const clippedEnd = minDayjs(eventEnd, working.end);
|
|
261
230
|
boundaries.set(clippedStart.valueOf(), clippedStart);
|
|
@@ -265,13 +234,11 @@ function buildCapacityRanges(params) {
|
|
|
265
234
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
266
235
|
const start = points[index];
|
|
267
236
|
const end = points[index + 1];
|
|
268
|
-
if (!end.isAfter(start))
|
|
269
|
-
|
|
270
|
-
const usedCapacity = events.filter((event) => overlaps(start, end, (0, import_dayjs.default)(event.start_at), (0, import_dayjs.default)(event.end_at))).reduce((sum, event) => sum + Math.max(1, Number(event.pax || 1)), 0);
|
|
237
|
+
if (!end.isAfter(start)) continue;
|
|
238
|
+
const usedCapacity = events.filter(event => overlaps(start, end, (0, _dayjs.default)(event.start_at), (0, _dayjs.default)(event.end_at))).reduce((sum, event) => sum + Math.max(1, Number(event.pax || 1)), 0);
|
|
271
239
|
const remainingCapacity = Math.max(0, maxCapacity - usedCapacity);
|
|
272
|
-
if (remainingCapacity < requiredCapacity)
|
|
273
|
-
|
|
274
|
-
const status = remainingCapacity < maxCapacity ? "limited" : "available";
|
|
240
|
+
if (remainingCapacity < requiredCapacity) continue;
|
|
241
|
+
const status = remainingCapacity < maxCapacity ? 'limited' : 'available';
|
|
275
242
|
const previous = ranges[ranges.length - 1];
|
|
276
243
|
if (previous && previous.end.isSame(start) && previous.remainingCapacity === remainingCapacity && previous.maxCapacity === maxCapacity && previous.status === status) {
|
|
277
244
|
previous.end = end;
|
|
@@ -289,13 +256,17 @@ function buildCapacityRanges(params) {
|
|
|
289
256
|
return ranges;
|
|
290
257
|
}
|
|
291
258
|
function rangeFitsDuration(segment, durationMinutes) {
|
|
292
|
-
return segment.end.diff(segment.start,
|
|
259
|
+
return segment.end.diff(segment.start, 'minute') >= durationMinutes;
|
|
293
260
|
}
|
|
294
261
|
function makeAvailabilityRangeCell(params) {
|
|
295
|
-
const {
|
|
262
|
+
const {
|
|
263
|
+
interval,
|
|
264
|
+
resource,
|
|
265
|
+
segment
|
|
266
|
+
} = params;
|
|
296
267
|
const maxCapacity = Math.max(1, Number(segment.maxCapacity || resource.capacity || 1));
|
|
297
|
-
const remainingCapacity = resource.resourceType ===
|
|
298
|
-
const status = resource.resourceType ===
|
|
268
|
+
const remainingCapacity = resource.resourceType === 'single' ? 1 : Math.max(0, Number(segment.remainingCapacity ?? maxCapacity));
|
|
269
|
+
const status = resource.resourceType === 'single' ? 'available' : segment.status || (remainingCapacity < maxCapacity ? 'limited' : 'available');
|
|
299
270
|
return {
|
|
300
271
|
key: `${interval.productId}:${resource.id}:${segment.start.toISOString()}:${segment.end.toISOString()}`,
|
|
301
272
|
productId: interval.productId,
|
|
@@ -303,10 +274,10 @@ function makeAvailabilityRangeCell(params) {
|
|
|
303
274
|
date: interval.date,
|
|
304
275
|
start_at: segment.start.toISOString(),
|
|
305
276
|
end_at: segment.end.toISOString(),
|
|
306
|
-
startTime: segment.start.format(
|
|
307
|
-
endTime: segment.end.format(
|
|
277
|
+
startTime: segment.start.format('HH:mm'),
|
|
278
|
+
endTime: segment.end.format('HH:mm'),
|
|
308
279
|
durationMinutes: interval.durationMinutes,
|
|
309
|
-
cellType:
|
|
280
|
+
cellType: 'availability_range',
|
|
310
281
|
status,
|
|
311
282
|
remainingCapacity,
|
|
312
283
|
maxCapacity,
|
|
@@ -315,94 +286,98 @@ function makeAvailabilityRangeCell(params) {
|
|
|
315
286
|
};
|
|
316
287
|
}
|
|
317
288
|
function buildAvailabilityCellsForResource(params) {
|
|
318
|
-
const {
|
|
289
|
+
const {
|
|
290
|
+
resource,
|
|
291
|
+
interval,
|
|
292
|
+
events
|
|
293
|
+
} = params;
|
|
319
294
|
const requiredCapacity = Math.max(1, Number(interval.capacityRequired || 1));
|
|
320
295
|
const durationMinutes = Math.max(1, Number(interval.durationMinutes || 1));
|
|
321
296
|
const maxCapacity = Math.max(1, Number(resource.capacity || 1));
|
|
322
297
|
let workingSegments = getWorkingSegmentsForInterval(resource, interval);
|
|
323
|
-
const now = (0,
|
|
324
|
-
workingSegments = workingSegments.map(
|
|
298
|
+
const now = (0, _dayjs.default)();
|
|
299
|
+
workingSegments = workingSegments.map(segment => ({
|
|
325
300
|
start: segment.end.isAfter(now) ? maxDayjs(segment.start, now) : segment.start,
|
|
326
301
|
end: segment.end
|
|
327
|
-
})).filter(
|
|
328
|
-
const availableSegments = resource.resourceType ===
|
|
302
|
+
})).filter(segment => segment.end.isAfter(segment.start));
|
|
303
|
+
const availableSegments = resource.resourceType === 'single' ? subtractBusyIntervals(workingSegments, events).map(segment => ({
|
|
329
304
|
...segment,
|
|
330
305
|
remainingCapacity: 1,
|
|
331
306
|
maxCapacity: 1,
|
|
332
|
-
status:
|
|
307
|
+
status: 'available'
|
|
333
308
|
})) : buildCapacityRanges({
|
|
334
309
|
workingSegments,
|
|
335
310
|
events,
|
|
336
311
|
maxCapacity,
|
|
337
312
|
requiredCapacity
|
|
338
313
|
});
|
|
339
|
-
return availableSegments.filter(
|
|
314
|
+
return availableSegments.filter(segment => rangeFitsDuration(segment, durationMinutes)).map(segment => makeAvailabilityRangeCell({
|
|
340
315
|
interval,
|
|
341
316
|
resource,
|
|
342
317
|
segment
|
|
343
318
|
}));
|
|
344
319
|
}
|
|
345
320
|
function evaluateResourceForInterval(params) {
|
|
346
|
-
const {
|
|
347
|
-
|
|
348
|
-
|
|
321
|
+
const {
|
|
322
|
+
resource,
|
|
323
|
+
interval,
|
|
324
|
+
context
|
|
325
|
+
} = params;
|
|
326
|
+
const start = (0, _dayjs.default)(interval.start_at);
|
|
327
|
+
const end = (0, _dayjs.default)(interval.end_at);
|
|
349
328
|
const conflicts = [];
|
|
350
329
|
const reasonCodes = [];
|
|
351
330
|
const maxCapacity = Math.max(1, Number(resource.capacity || 1));
|
|
352
331
|
const requiredCapacity = Math.max(1, Number(interval.capacityRequired || 1));
|
|
353
|
-
if (start.isBefore((0,
|
|
354
|
-
reasonCodes.push(
|
|
332
|
+
if (start.isBefore((0, _dayjs.default)())) {
|
|
333
|
+
reasonCodes.push('past');
|
|
355
334
|
conflicts.push({
|
|
356
|
-
type:
|
|
357
|
-
message:
|
|
335
|
+
type: 'past',
|
|
336
|
+
message: '时间已过期',
|
|
358
337
|
resourceId: resource.id,
|
|
359
338
|
productId: interval.productId
|
|
360
339
|
});
|
|
361
340
|
}
|
|
362
|
-
const insideResourceTime = resource.times.some(
|
|
363
|
-
return containsRange((0,
|
|
341
|
+
const insideResourceTime = resource.times.some(time => {
|
|
342
|
+
return containsRange((0, _dayjs.default)(time.start_at), (0, _dayjs.default)(time.end_at), start, end);
|
|
364
343
|
});
|
|
365
344
|
if (!insideResourceTime) {
|
|
366
|
-
reasonCodes.push(
|
|
345
|
+
reasonCodes.push('outside_resource_time');
|
|
367
346
|
conflicts.push({
|
|
368
|
-
type:
|
|
369
|
-
message:
|
|
347
|
+
type: 'outside_resource_time',
|
|
348
|
+
message: '所选时间不在资源可用时间内',
|
|
370
349
|
resourceId: resource.id,
|
|
371
350
|
productId: interval.productId
|
|
372
351
|
});
|
|
373
352
|
}
|
|
374
353
|
const events = getEventsForResource(resource, context.externalEvents);
|
|
375
|
-
const overlapping = events.filter(
|
|
376
|
-
const usedCapacity = resource.resourceType ===
|
|
377
|
-
const remainingCapacity = resource.resourceType ===
|
|
378
|
-
if (overlapping.length > 0 && resource.resourceType ===
|
|
379
|
-
reasonCodes.push(
|
|
380
|
-
conflicts.push(...overlapping.map(
|
|
381
|
-
type:
|
|
382
|
-
message:
|
|
354
|
+
const overlapping = events.filter(event => overlaps(start, end, (0, _dayjs.default)(event.start_at), (0, _dayjs.default)(event.end_at)));
|
|
355
|
+
const usedCapacity = resource.resourceType === 'single' ? overlapping.length : overlapping.reduce((sum, event) => sum + Math.max(1, Number(event.pax || 1)), 0);
|
|
356
|
+
const remainingCapacity = resource.resourceType === 'single' ? overlapping.length > 0 ? 0 : 1 : Math.max(0, maxCapacity - usedCapacity);
|
|
357
|
+
if (overlapping.length > 0 && resource.resourceType === 'single') {
|
|
358
|
+
reasonCodes.push('remote_event_overlap');
|
|
359
|
+
conflicts.push(...overlapping.map(event => ({
|
|
360
|
+
type: 'remote_event_overlap',
|
|
361
|
+
message: '资源时间被已有预约占用',
|
|
383
362
|
event,
|
|
384
363
|
resourceId: resource.id,
|
|
385
364
|
productId: interval.productId
|
|
386
365
|
})));
|
|
387
366
|
}
|
|
388
|
-
if (resource.resourceType !==
|
|
389
|
-
reasonCodes.push(
|
|
367
|
+
if (resource.resourceType !== 'single' && remainingCapacity < requiredCapacity) {
|
|
368
|
+
reasonCodes.push('capacity_full');
|
|
390
369
|
conflicts.push({
|
|
391
|
-
type:
|
|
392
|
-
message:
|
|
370
|
+
type: 'capacity_full',
|
|
371
|
+
message: '资源剩余容量不足',
|
|
393
372
|
resourceId: resource.id,
|
|
394
373
|
productId: interval.productId
|
|
395
374
|
});
|
|
396
375
|
}
|
|
397
|
-
let status =
|
|
398
|
-
if (reasonCodes.includes(
|
|
399
|
-
status =
|
|
400
|
-
else if (reasonCodes.includes("outside_resource_time"))
|
|
401
|
-
status = "unavailable";
|
|
402
|
-
else if (reasonCodes.includes("remote_event_overlap") || reasonCodes.includes("capacity_full")) {
|
|
403
|
-
status = "full";
|
|
376
|
+
let status = 'available';
|
|
377
|
+
if (reasonCodes.includes('past')) status = 'past';else if (reasonCodes.includes('outside_resource_time')) status = 'unavailable';else if (reasonCodes.includes('remote_event_overlap') || reasonCodes.includes('capacity_full')) {
|
|
378
|
+
status = 'full';
|
|
404
379
|
} else if (remainingCapacity < maxCapacity) {
|
|
405
|
-
status =
|
|
380
|
+
status = 'limited';
|
|
406
381
|
}
|
|
407
382
|
return {
|
|
408
383
|
key: `${interval.productId}:${resource.id}:${interval.start_at}:${interval.end_at}`,
|
|
@@ -414,7 +389,7 @@ function evaluateResourceForInterval(params) {
|
|
|
414
389
|
startTime: timeOf(interval.start_at),
|
|
415
390
|
endTime: timeOf(interval.end_at),
|
|
416
391
|
durationMinutes: interval.durationMinutes,
|
|
417
|
-
cellType: status ===
|
|
392
|
+
cellType: status === 'available' || status === 'limited' ? 'fixed_interval' : 'unavailable',
|
|
418
393
|
status,
|
|
419
394
|
remainingCapacity,
|
|
420
395
|
maxCapacity,
|
|
@@ -423,28 +398,22 @@ function evaluateResourceForInterval(params) {
|
|
|
423
398
|
};
|
|
424
399
|
}
|
|
425
400
|
function aggregateStatus(cells) {
|
|
426
|
-
if (!cells.length)
|
|
427
|
-
|
|
428
|
-
if (cells.some(
|
|
429
|
-
|
|
430
|
-
if (cells.some(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return "past";
|
|
434
|
-
if (cells.some((cell) => cell.status === "full"))
|
|
435
|
-
return "full";
|
|
436
|
-
if (cells.some((cell) => cell.status === "conflict"))
|
|
437
|
-
return "conflict";
|
|
438
|
-
return "unavailable";
|
|
401
|
+
if (!cells.length) return 'unavailable';
|
|
402
|
+
if (cells.some(cell => cell.status === 'available')) return 'available';
|
|
403
|
+
if (cells.some(cell => cell.status === 'limited')) return 'limited';
|
|
404
|
+
if (cells.some(cell => cell.status === 'past')) return 'past';
|
|
405
|
+
if (cells.some(cell => cell.status === 'full')) return 'full';
|
|
406
|
+
if (cells.some(cell => cell.status === 'conflict')) return 'conflict';
|
|
407
|
+
return 'unavailable';
|
|
439
408
|
}
|
|
440
409
|
function uniqueReasonCodes(cells) {
|
|
441
|
-
return Array.from(new Set(cells.flatMap(
|
|
410
|
+
return Array.from(new Set(cells.flatMap(cell => cell.reasonCodes || [])));
|
|
442
411
|
}
|
|
443
412
|
function buildProductOptions(products, cells, query) {
|
|
444
413
|
const queryProducts = getQueryProducts(products, query);
|
|
445
|
-
return queryProducts.map(
|
|
446
|
-
const productCells = cells.filter(
|
|
447
|
-
const status = product.kind ===
|
|
414
|
+
return queryProducts.map(product => {
|
|
415
|
+
const productCells = cells.filter(cell => sameId(cell.productId, product.id));
|
|
416
|
+
const status = product.kind === 'normal' ? 'available' : aggregateStatus(productCells);
|
|
448
417
|
return {
|
|
449
418
|
productId: product.id,
|
|
450
419
|
title: resolveProductTitle(product),
|
|
@@ -455,57 +424,55 @@ function buildProductOptions(products, cells, query) {
|
|
|
455
424
|
}
|
|
456
425
|
function buildResourceOptions(resources, cells, query) {
|
|
457
426
|
const queryResources = getQueryResources(resources, query);
|
|
458
|
-
return queryResources.map(
|
|
459
|
-
const resourceCells = cells.filter(
|
|
460
|
-
const bestCell = resourceCells.find(
|
|
427
|
+
return queryResources.map(resource => {
|
|
428
|
+
const resourceCells = cells.filter(cell => cell.resourceId !== undefined && sameId(cell.resourceId, resource.id));
|
|
429
|
+
const bestCell = resourceCells.find(cell => cell.status === 'available') || resourceCells.find(cell => cell.status === 'limited') || resourceCells[0];
|
|
461
430
|
return {
|
|
462
431
|
resourceId: resource.id,
|
|
463
432
|
resourceName: resource.name,
|
|
464
433
|
resourceFormId: resource.formId,
|
|
465
434
|
resourceType: resource.resourceType,
|
|
466
|
-
status: resourceCells.length ? aggregateStatus(resourceCells) :
|
|
467
|
-
remainingCapacity: bestCell
|
|
468
|
-
maxCapacity:
|
|
435
|
+
status: resourceCells.length ? aggregateStatus(resourceCells) : 'unavailable',
|
|
436
|
+
remainingCapacity: bestCell?.remainingCapacity,
|
|
437
|
+
maxCapacity: bestCell?.maxCapacity || resource.capacity,
|
|
469
438
|
reasonCodes: uniqueReasonCodes(resourceCells)
|
|
470
439
|
};
|
|
471
440
|
});
|
|
472
441
|
}
|
|
473
442
|
function buildDateSummary(cells, query) {
|
|
474
|
-
const dates = query.date ? [query.date] : query.dateRange ? addDays(query.dateRange.start, query.dateRange.end) : Array.from(new Set(cells.map(
|
|
475
|
-
return dates.map(
|
|
476
|
-
const dateCells = cells.filter(
|
|
477
|
-
const availableCount = dateCells.filter(
|
|
443
|
+
const dates = query.date ? [query.date] : query.dateRange ? addDays(query.dateRange.start, query.dateRange.end) : Array.from(new Set(cells.map(cell => cell.date).filter(Boolean)));
|
|
444
|
+
return dates.map(date => {
|
|
445
|
+
const dateCells = cells.filter(cell => cell.date === date);
|
|
446
|
+
const availableCount = dateCells.filter(cell => cell.status === 'available' || cell.status === 'limited').length;
|
|
478
447
|
return {
|
|
479
448
|
date,
|
|
480
|
-
status: dateCells.length ? aggregateStatus(dateCells) :
|
|
449
|
+
status: dateCells.length ? aggregateStatus(dateCells) : 'unavailable',
|
|
481
450
|
availableCount,
|
|
482
451
|
totalCount: dateCells.length
|
|
483
452
|
};
|
|
484
453
|
});
|
|
485
454
|
}
|
|
486
455
|
function buildTimeLabels(date, context, query) {
|
|
487
|
-
if (!date)
|
|
488
|
-
return [];
|
|
456
|
+
if (!date) return [];
|
|
489
457
|
const slotMinutes = query.slotDurationMinutes || context.slotDurationMinutes;
|
|
490
458
|
const labels = [];
|
|
491
459
|
let cursor = toDateTime(date, context.businessStartTime);
|
|
492
460
|
let end = toDateTime(date, context.businessEndTime);
|
|
493
|
-
if (!end.isAfter(cursor))
|
|
494
|
-
end = end.add(1, "day");
|
|
461
|
+
if (!end.isAfter(cursor)) end = end.add(1, 'day');
|
|
495
462
|
while (cursor.isBefore(end)) {
|
|
496
|
-
labels.push(cursor.format(
|
|
497
|
-
cursor = cursor.add(slotMinutes,
|
|
463
|
+
labels.push(cursor.format('HH:mm'));
|
|
464
|
+
cursor = cursor.add(slotMinutes, 'minute');
|
|
498
465
|
}
|
|
499
466
|
return labels;
|
|
500
467
|
}
|
|
501
468
|
function buildGridSlotInterval(product, date, label, context, query) {
|
|
502
|
-
const slotMinutes = product.kind ===
|
|
469
|
+
const slotMinutes = product.kind === 'duration' ? Math.max(1, Number(product.durationMinutes || query.slotDurationMinutes || context.slotDurationMinutes)) : query.slotDurationMinutes || context.slotDurationMinutes;
|
|
503
470
|
const start = toDateTime(date, label);
|
|
504
|
-
const end = start.add(slotMinutes,
|
|
471
|
+
const end = start.add(slotMinutes, 'minute');
|
|
505
472
|
return {
|
|
506
473
|
id: `${product.id}:${date}:${label}`,
|
|
507
474
|
productId: product.id,
|
|
508
|
-
source: product.kind ===
|
|
475
|
+
source: product.kind === 'session' ? 'session' : product.kind === 'venue_slot' ? 'venue_slot' : 'duration',
|
|
509
476
|
date,
|
|
510
477
|
start_at: start.toISOString(),
|
|
511
478
|
end_at: end.toISOString(),
|
|
@@ -516,30 +483,33 @@ function buildGridSlotInterval(product, date, label, context, query) {
|
|
|
516
483
|
};
|
|
517
484
|
}
|
|
518
485
|
function buildTimeGrid(context, query, cells) {
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
const products = getQueryProducts(context.products, query).filter((product) => product.kind !== "normal");
|
|
486
|
+
const date = query.date || query.dateRange?.start;
|
|
487
|
+
const products = getQueryProducts(context.products, query).filter(product => product.kind !== 'normal');
|
|
522
488
|
const resources = getQueryResources(context.resources, query);
|
|
523
489
|
const timeLabels = buildTimeLabels(date, context, query);
|
|
524
490
|
if (!date || !products.length || !resources.length) {
|
|
525
|
-
return {
|
|
491
|
+
return {
|
|
492
|
+
date,
|
|
493
|
+
timeLabels,
|
|
494
|
+
resources: []
|
|
495
|
+
};
|
|
526
496
|
}
|
|
527
|
-
const rows = resources.map(
|
|
528
|
-
const slots = timeLabels.map(
|
|
497
|
+
const rows = resources.map(resource => {
|
|
498
|
+
const slots = timeLabels.map(label => {
|
|
529
499
|
const product = products[0];
|
|
530
500
|
const interval = buildGridSlotInterval(product, date, label, context, query);
|
|
531
|
-
const start = (0,
|
|
532
|
-
const end = (0,
|
|
533
|
-
const cell = cells.find(
|
|
501
|
+
const start = (0, _dayjs.default)(interval.start_at);
|
|
502
|
+
const end = (0, _dayjs.default)(interval.end_at);
|
|
503
|
+
const cell = cells.find(item => sameId(item.productId, product.id) && sameId(item.resourceId, resource.id) && item.date === date && (item.status === 'available' || item.status === 'limited') && containsRange((0, _dayjs.default)(item.start_at), (0, _dayjs.default)(item.end_at), start, end));
|
|
534
504
|
return {
|
|
535
505
|
startTime: label,
|
|
536
|
-
endTime: (0,
|
|
537
|
-
status:
|
|
506
|
+
endTime: (0, _dayjs.default)(interval.end_at).format('HH:mm'),
|
|
507
|
+
status: cell?.status || 'unavailable',
|
|
538
508
|
resourceId: resource.id,
|
|
539
509
|
productId: product.id,
|
|
540
|
-
remainingCapacity: cell
|
|
541
|
-
maxCapacity:
|
|
542
|
-
reasonCodes:
|
|
510
|
+
remainingCapacity: cell?.remainingCapacity,
|
|
511
|
+
maxCapacity: cell?.maxCapacity || resource.capacity,
|
|
512
|
+
reasonCodes: cell?.reasonCodes || ['outside_available_range']
|
|
543
513
|
};
|
|
544
514
|
});
|
|
545
515
|
return {
|
|
@@ -549,7 +519,11 @@ function buildTimeGrid(context, query, cells) {
|
|
|
549
519
|
slots
|
|
550
520
|
};
|
|
551
521
|
});
|
|
552
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
date,
|
|
524
|
+
timeLabels,
|
|
525
|
+
resources: rows
|
|
526
|
+
};
|
|
553
527
|
}
|
|
554
528
|
function queryPlannerAvailability(contextInput, query = {}) {
|
|
555
529
|
const context = normalizePlannerContext(contextInput);
|
|
@@ -558,11 +532,10 @@ function queryPlannerAvailability(contextInput, query = {}) {
|
|
|
558
532
|
const eventsByResource = collectEventsByResource(resources, context.externalEvents);
|
|
559
533
|
const cells = [];
|
|
560
534
|
for (const interval of intervals) {
|
|
561
|
-
const candidateResources = resources.filter(
|
|
535
|
+
const candidateResources = resources.filter(resource => {
|
|
562
536
|
const requirements = interval.resourceRequirements || [];
|
|
563
|
-
if (!requirements.length)
|
|
564
|
-
|
|
565
|
-
return requirements.some((requirement) => matchesResourceRequirement(resource, requirement));
|
|
537
|
+
if (!requirements.length) return true;
|
|
538
|
+
return requirements.some(requirement => matchesResourceRequirement(resource, requirement));
|
|
566
539
|
});
|
|
567
540
|
if (!candidateResources.length) {
|
|
568
541
|
cells.push({
|
|
@@ -574,19 +547,19 @@ function queryPlannerAvailability(contextInput, query = {}) {
|
|
|
574
547
|
startTime: timeOf(interval.start_at),
|
|
575
548
|
endTime: timeOf(interval.end_at),
|
|
576
549
|
durationMinutes: interval.durationMinutes,
|
|
577
|
-
cellType:
|
|
578
|
-
status:
|
|
579
|
-
reasonCodes: [
|
|
550
|
+
cellType: 'unavailable',
|
|
551
|
+
status: 'unavailable',
|
|
552
|
+
reasonCodes: ['missing_resource'],
|
|
580
553
|
conflicts: [{
|
|
581
|
-
type:
|
|
582
|
-
message:
|
|
554
|
+
type: 'missing_resource',
|
|
555
|
+
message: '没有匹配商品需求的资源',
|
|
583
556
|
productId: interval.productId
|
|
584
557
|
}]
|
|
585
558
|
});
|
|
586
559
|
continue;
|
|
587
560
|
}
|
|
588
|
-
candidateResources.forEach(
|
|
589
|
-
if (interval.source ===
|
|
561
|
+
candidateResources.forEach(resource => {
|
|
562
|
+
if (interval.source === 'duration' && !query.startTime) {
|
|
590
563
|
cells.push(...buildAvailabilityCellsForResource({
|
|
591
564
|
resource,
|
|
592
565
|
interval,
|
|
@@ -594,7 +567,11 @@ function queryPlannerAvailability(contextInput, query = {}) {
|
|
|
594
567
|
}));
|
|
595
568
|
return;
|
|
596
569
|
}
|
|
597
|
-
cells.push(evaluateResourceForInterval({
|
|
570
|
+
cells.push(evaluateResourceForInterval({
|
|
571
|
+
resource,
|
|
572
|
+
interval,
|
|
573
|
+
context
|
|
574
|
+
}));
|
|
598
575
|
});
|
|
599
576
|
}
|
|
600
577
|
return {
|
|
@@ -614,86 +591,84 @@ function getIntervalRequirements(interval) {
|
|
|
614
591
|
}];
|
|
615
592
|
}
|
|
616
593
|
function buildAssignableCandidateGroups(params) {
|
|
617
|
-
const {
|
|
594
|
+
const {
|
|
595
|
+
interval,
|
|
596
|
+
cells,
|
|
597
|
+
resources
|
|
598
|
+
} = params;
|
|
618
599
|
const conflicts = [];
|
|
619
|
-
const groups = getIntervalRequirements(interval).map(
|
|
620
|
-
const candidateCells = cells.filter(
|
|
621
|
-
if (!sameId(cell.productId, interval.productId))
|
|
622
|
-
|
|
623
|
-
if (cell.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const cellEnd = (0, import_dayjs.default)(cell.end_at);
|
|
631
|
-
const intervalStart = (0, import_dayjs.default)(interval.start_at);
|
|
632
|
-
const intervalEnd = (0, import_dayjs.default)(interval.end_at);
|
|
633
|
-
return containsRange(cellStart, cellEnd, intervalStart, intervalEnd) || interval.source === "duration";
|
|
600
|
+
const groups = getIntervalRequirements(interval).map(requirement => {
|
|
601
|
+
const candidateCells = cells.filter(cell => {
|
|
602
|
+
if (!sameId(cell.productId, interval.productId)) return false;
|
|
603
|
+
if (cell.date !== interval.date) return false;
|
|
604
|
+
if (cell.resourceId === undefined) return false;
|
|
605
|
+
if (cell.status !== 'available' && cell.status !== 'limited') return false;
|
|
606
|
+
const cellStart = (0, _dayjs.default)(cell.start_at);
|
|
607
|
+
const cellEnd = (0, _dayjs.default)(cell.end_at);
|
|
608
|
+
const intervalStart = (0, _dayjs.default)(interval.start_at);
|
|
609
|
+
const intervalEnd = (0, _dayjs.default)(interval.end_at);
|
|
610
|
+
return containsRange(cellStart, cellEnd, intervalStart, intervalEnd) || interval.source === 'duration';
|
|
634
611
|
});
|
|
635
|
-
return candidateCells.flatMap(
|
|
636
|
-
const resource = resources.find(
|
|
637
|
-
if (!resource || !matchesResourceRequirement(resource, requirement))
|
|
638
|
-
|
|
639
|
-
|
|
612
|
+
return candidateCells.flatMap(cell => {
|
|
613
|
+
const resource = resources.find(item => sameId(item.id, cell.resourceId));
|
|
614
|
+
if (!resource || !matchesResourceRequirement(resource, requirement)) return [];
|
|
615
|
+
return [{
|
|
616
|
+
requirement,
|
|
617
|
+
resource,
|
|
618
|
+
cell
|
|
619
|
+
}];
|
|
640
620
|
});
|
|
641
621
|
});
|
|
642
622
|
groups.forEach((group, index) => {
|
|
643
|
-
|
|
644
|
-
if (group.length)
|
|
645
|
-
return;
|
|
623
|
+
if (group.length) return;
|
|
646
624
|
conflicts.push({
|
|
647
|
-
type:
|
|
648
|
-
message:
|
|
625
|
+
type: 'missing_resource',
|
|
626
|
+
message: '没有匹配商品需求的可用资源',
|
|
649
627
|
productId: interval.productId,
|
|
650
|
-
resourceId:
|
|
628
|
+
resourceId: interval.resourceRequirements[index]?.resourceIds?.[0]
|
|
651
629
|
});
|
|
652
630
|
});
|
|
653
|
-
return {
|
|
631
|
+
return {
|
|
632
|
+
groups,
|
|
633
|
+
conflicts
|
|
634
|
+
};
|
|
654
635
|
}
|
|
655
636
|
function combineCandidateGroups(groups) {
|
|
656
|
-
if (!groups.length || groups.some(
|
|
657
|
-
|
|
658
|
-
return groups.reduce((combinations, group) => combinations.flatMap((combination) => group.map((candidate) => [...combination, candidate])), [[]]);
|
|
637
|
+
if (!groups.length || groups.some(group => group.length === 0)) return [];
|
|
638
|
+
return groups.reduce((combinations, group) => combinations.flatMap(combination => group.map(candidate => [...combination, candidate])), [[]]);
|
|
659
639
|
}
|
|
660
640
|
function buildSlotFromCandidates(params) {
|
|
661
|
-
const {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
641
|
+
const {
|
|
642
|
+
interval,
|
|
643
|
+
candidates,
|
|
644
|
+
start,
|
|
645
|
+
end
|
|
646
|
+
} = params;
|
|
647
|
+
if (!end.isAfter(start)) return null;
|
|
648
|
+
const status = candidates.some(candidate => candidate.cell.status === 'limited') ? 'limited' : 'available';
|
|
649
|
+
const assignments = candidates.map(candidate => ({
|
|
666
650
|
productId: interval.productId,
|
|
667
651
|
resourceId: candidate.resource.id,
|
|
668
652
|
start_at: start.toISOString(),
|
|
669
653
|
end_at: end.toISOString(),
|
|
670
|
-
capacityRequired: Math.max(1, Number(
|
|
671
|
-
candidate.requirement.capacityRequired || interval.capacityRequired || 1
|
|
672
|
-
))
|
|
654
|
+
capacityRequired: Math.max(1, Number(candidate.requirement.capacityRequired || interval.capacityRequired || 1))
|
|
673
655
|
}));
|
|
674
656
|
return {
|
|
675
|
-
key: [
|
|
676
|
-
interval.productId,
|
|
677
|
-
start.toISOString(),
|
|
678
|
-
end.toISOString(),
|
|
679
|
-
...candidates.map((candidate) => candidate.resource.id)
|
|
680
|
-
].join(":"),
|
|
657
|
+
key: [interval.productId, start.toISOString(), end.toISOString(), ...candidates.map(candidate => candidate.resource.id)].join(':'),
|
|
681
658
|
productId: interval.productId,
|
|
682
659
|
date: interval.date,
|
|
683
660
|
start_at: start.toISOString(),
|
|
684
661
|
end_at: end.toISOString(),
|
|
685
|
-
startTime: start.format(
|
|
686
|
-
endTime: end.format(
|
|
687
|
-
durationMinutes: Math.max(1, end.diff(start,
|
|
662
|
+
startTime: start.format('HH:mm'),
|
|
663
|
+
endTime: end.format('HH:mm'),
|
|
664
|
+
durationMinutes: Math.max(1, end.diff(start, 'minute')),
|
|
688
665
|
status,
|
|
689
|
-
resources: candidates.map(
|
|
666
|
+
resources: candidates.map(candidate => ({
|
|
690
667
|
resourceId: candidate.resource.id,
|
|
691
668
|
resourceName: candidate.resource.name,
|
|
692
669
|
resourceFormId: candidate.resource.formId,
|
|
693
670
|
resourceType: candidate.resource.resourceType,
|
|
694
|
-
capacityRequired: Math.max(1, Number(
|
|
695
|
-
candidate.requirement.capacityRequired || interval.capacityRequired || 1
|
|
696
|
-
)),
|
|
671
|
+
capacityRequired: Math.max(1, Number(candidate.requirement.capacityRequired || interval.capacityRequired || 1)),
|
|
697
672
|
remainingCapacity: candidate.cell.remainingCapacity,
|
|
698
673
|
maxCapacity: candidate.cell.maxCapacity
|
|
699
674
|
})),
|
|
@@ -702,20 +677,19 @@ function buildSlotFromCandidates(params) {
|
|
|
702
677
|
};
|
|
703
678
|
}
|
|
704
679
|
function buildSlotsForInterval(params) {
|
|
705
|
-
const {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
);
|
|
680
|
+
const {
|
|
681
|
+
interval,
|
|
682
|
+
candidates,
|
|
683
|
+
query,
|
|
684
|
+
context
|
|
685
|
+
} = params;
|
|
686
|
+
const start = candidates.reduce((current, candidate) => maxDayjs(current, (0, _dayjs.default)(candidate.cell.start_at)), (0, _dayjs.default)(interval.start_at));
|
|
687
|
+
const end = candidates.reduce((current, candidate) => minDayjs(current, (0, _dayjs.default)(candidate.cell.end_at)), (0, _dayjs.default)(interval.end_at));
|
|
714
688
|
const durationMinutes = Math.max(1, Number(interval.durationMinutes || 1));
|
|
715
|
-
if (interval.source !==
|
|
716
|
-
const fixedStart = (0,
|
|
717
|
-
const fixedEnd = (0,
|
|
718
|
-
if (containsRange(start, end, fixedStart, fixedEnd) || candidates.every(
|
|
689
|
+
if (interval.source !== 'duration' || query.startTime) {
|
|
690
|
+
const fixedStart = (0, _dayjs.default)(interval.start_at);
|
|
691
|
+
const fixedEnd = (0, _dayjs.default)(interval.end_at);
|
|
692
|
+
if (containsRange(start, end, fixedStart, fixedEnd) || candidates.every(candidate => candidate.cell.cellType === 'fixed_interval')) {
|
|
719
693
|
const slot = buildSlotFromCandidates({
|
|
720
694
|
interval,
|
|
721
695
|
candidates,
|
|
@@ -729,17 +703,16 @@ function buildSlotsForInterval(params) {
|
|
|
729
703
|
const stepMinutes = Math.max(1, Number(query.slotDurationMinutes || context.slotDurationMinutes || 30));
|
|
730
704
|
const slots = [];
|
|
731
705
|
let cursor = start;
|
|
732
|
-
while (!cursor.add(durationMinutes,
|
|
733
|
-
const slotEnd = cursor.add(durationMinutes,
|
|
706
|
+
while (!cursor.add(durationMinutes, 'minute').isAfter(end)) {
|
|
707
|
+
const slotEnd = cursor.add(durationMinutes, 'minute');
|
|
734
708
|
const slot = buildSlotFromCandidates({
|
|
735
709
|
interval,
|
|
736
710
|
candidates,
|
|
737
711
|
start: cursor,
|
|
738
712
|
end: slotEnd
|
|
739
713
|
});
|
|
740
|
-
if (slot)
|
|
741
|
-
|
|
742
|
-
cursor = cursor.add(stepMinutes, "minute");
|
|
714
|
+
if (slot) slots.push(slot);
|
|
715
|
+
cursor = cursor.add(stepMinutes, 'minute');
|
|
743
716
|
}
|
|
744
717
|
return slots;
|
|
745
718
|
}
|
|
@@ -747,24 +720,26 @@ function resolveAssignableSlots(contextInput, query = {}) {
|
|
|
747
720
|
const context = normalizePlannerContext(contextInput);
|
|
748
721
|
const projection = queryPlannerAvailability(context, query);
|
|
749
722
|
const resources = getQueryResources(context.resources, query);
|
|
750
|
-
const slotsByKey =
|
|
723
|
+
const slotsByKey = new Map();
|
|
751
724
|
const conflicts = [];
|
|
752
|
-
projection.demandIntervals.forEach(
|
|
753
|
-
const {
|
|
725
|
+
projection.demandIntervals.forEach(interval => {
|
|
726
|
+
const {
|
|
727
|
+
groups,
|
|
728
|
+
conflicts: groupConflicts
|
|
729
|
+
} = buildAssignableCandidateGroups({
|
|
754
730
|
interval,
|
|
755
731
|
cells: projection.cells,
|
|
756
732
|
resources
|
|
757
733
|
});
|
|
758
734
|
conflicts.push(...groupConflicts);
|
|
759
|
-
combineCandidateGroups(groups).forEach(
|
|
735
|
+
combineCandidateGroups(groups).forEach(candidates => {
|
|
760
736
|
buildSlotsForInterval({
|
|
761
737
|
interval,
|
|
762
738
|
candidates,
|
|
763
739
|
query,
|
|
764
740
|
context
|
|
765
|
-
}).forEach(
|
|
766
|
-
if (!slotsByKey.has(slot.key))
|
|
767
|
-
slotsByKey.set(slot.key, slot);
|
|
741
|
+
}).forEach(slot => {
|
|
742
|
+
if (!slotsByKey.has(slot.key)) slotsByKey.set(slot.key, slot);
|
|
768
743
|
});
|
|
769
744
|
});
|
|
770
745
|
});
|
|
@@ -779,36 +754,35 @@ function autoAssignResources(contextInput, query = {}) {
|
|
|
779
754
|
const projection = queryPlannerAvailability(baseContext, query);
|
|
780
755
|
const assignments = [];
|
|
781
756
|
const conflicts = [];
|
|
782
|
-
const missingTimeProductIds =
|
|
757
|
+
const missingTimeProductIds = new Set();
|
|
783
758
|
for (const interval of projection.demandIntervals) {
|
|
784
|
-
if (interval.source ===
|
|
759
|
+
if (interval.source === 'duration' && !query.startTime) {
|
|
785
760
|
const productKey = String(interval.productId);
|
|
786
761
|
if (!missingTimeProductIds.has(productKey)) {
|
|
787
762
|
missingTimeProductIds.add(productKey);
|
|
788
763
|
conflicts.push({
|
|
789
|
-
type:
|
|
790
|
-
message:
|
|
764
|
+
type: 'missing_time',
|
|
765
|
+
message: 'duration 商品需要先确定开始时间后才能自动分配资源',
|
|
791
766
|
productId: interval.productId
|
|
792
767
|
});
|
|
793
768
|
}
|
|
794
769
|
continue;
|
|
795
770
|
}
|
|
796
771
|
const contextWithAssignments = withAssignmentEvents(baseContext, assignments);
|
|
797
|
-
const candidateResources = getQueryResources(contextWithAssignments.resources, query).filter(
|
|
772
|
+
const candidateResources = getQueryResources(contextWithAssignments.resources, query).filter(resource => {
|
|
798
773
|
const requirements = interval.resourceRequirements || [];
|
|
799
|
-
if (!requirements.length)
|
|
800
|
-
|
|
801
|
-
return requirements.some((requirement) => matchesResourceRequirement(resource, requirement));
|
|
774
|
+
if (!requirements.length) return true;
|
|
775
|
+
return requirements.some(requirement => matchesResourceRequirement(resource, requirement));
|
|
802
776
|
});
|
|
803
|
-
const availableCell = candidateResources.map(
|
|
777
|
+
const availableCell = candidateResources.map(resource => evaluateResourceForInterval({
|
|
804
778
|
resource,
|
|
805
779
|
interval,
|
|
806
780
|
context: contextWithAssignments
|
|
807
|
-
})).find(
|
|
808
|
-
if (!
|
|
781
|
+
})).find(cell => cell.status === 'available' || cell.status === 'limited');
|
|
782
|
+
if (!availableCell?.resourceId) {
|
|
809
783
|
conflicts.push({
|
|
810
|
-
type:
|
|
811
|
-
message:
|
|
784
|
+
type: 'missing_resource',
|
|
785
|
+
message: '无法为商品自动分配可用资源',
|
|
812
786
|
productId: interval.productId
|
|
813
787
|
});
|
|
814
788
|
continue;
|
|
@@ -821,88 +795,88 @@ function autoAssignResources(contextInput, query = {}) {
|
|
|
821
795
|
capacityRequired: interval.capacityRequired
|
|
822
796
|
});
|
|
823
797
|
}
|
|
824
|
-
return {
|
|
798
|
+
return {
|
|
799
|
+
assignments,
|
|
800
|
+
conflicts
|
|
801
|
+
};
|
|
825
802
|
}
|
|
826
803
|
function withAssignmentEvents(context, assignments) {
|
|
827
|
-
if (!assignments.length)
|
|
828
|
-
return context;
|
|
804
|
+
if (!assignments.length) return context;
|
|
829
805
|
return {
|
|
830
806
|
...context,
|
|
831
|
-
externalEvents: [
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
source: "selection"
|
|
839
|
-
}))
|
|
840
|
-
]
|
|
807
|
+
externalEvents: [...context.externalEvents, ...assignments.map(assignment => ({
|
|
808
|
+
resourceId: assignment.resourceId,
|
|
809
|
+
start_at: assignment.start_at,
|
|
810
|
+
end_at: assignment.end_at,
|
|
811
|
+
pax: assignment.capacityRequired || 1,
|
|
812
|
+
source: 'selection'
|
|
813
|
+
}))]
|
|
841
814
|
};
|
|
842
815
|
}
|
|
843
816
|
function validateAssignments(contextInput, selection) {
|
|
844
817
|
if (selection.assignments.length > 0) {
|
|
845
818
|
const context = normalizePlannerContext(contextInput);
|
|
846
|
-
const
|
|
819
|
+
const conflicts = [];
|
|
847
820
|
for (let index = 0; index < selection.assignments.length; index += 1) {
|
|
848
821
|
const assignment = selection.assignments[index];
|
|
849
|
-
const product = context.products.find(
|
|
850
|
-
const resource = context.resources.find(
|
|
822
|
+
const product = context.products.find(item => sameId(item.id, assignment.productId));
|
|
823
|
+
const resource = context.resources.find(item => sameId(item.id, assignment.resourceId));
|
|
851
824
|
if (!product) {
|
|
852
|
-
|
|
853
|
-
type:
|
|
854
|
-
message:
|
|
825
|
+
conflicts.push({
|
|
826
|
+
type: 'missing_resource',
|
|
827
|
+
message: 'assignment 对应的商品不存在',
|
|
855
828
|
productId: assignment.productId
|
|
856
829
|
});
|
|
857
830
|
continue;
|
|
858
831
|
}
|
|
859
832
|
if (!resource) {
|
|
860
|
-
|
|
861
|
-
type:
|
|
862
|
-
message:
|
|
833
|
+
conflicts.push({
|
|
834
|
+
type: 'missing_resource',
|
|
835
|
+
message: 'assignment 对应的资源不存在',
|
|
863
836
|
productId: assignment.productId,
|
|
864
837
|
resourceId: assignment.resourceId
|
|
865
838
|
});
|
|
866
839
|
continue;
|
|
867
840
|
}
|
|
868
|
-
const start = (0,
|
|
869
|
-
const end = (0,
|
|
841
|
+
const start = (0, _dayjs.default)(assignment.start_at);
|
|
842
|
+
const end = (0, _dayjs.default)(assignment.end_at);
|
|
870
843
|
const interval = {
|
|
871
844
|
id: `${assignment.productId}:${assignment.resourceId}:${assignment.start_at}:${assignment.end_at}`,
|
|
872
845
|
productId: assignment.productId,
|
|
873
|
-
source: product.kind ===
|
|
874
|
-
date: start.format(
|
|
846
|
+
source: product.kind === 'session' ? 'session' : product.kind === 'venue_slot' ? 'venue_slot' : 'duration',
|
|
847
|
+
date: start.format('YYYY-MM-DD'),
|
|
875
848
|
start_at: assignment.start_at,
|
|
876
849
|
end_at: assignment.end_at,
|
|
877
|
-
durationMinutes: Math.max(1, end.diff(start,
|
|
850
|
+
durationMinutes: Math.max(1, end.diff(start, 'minute')),
|
|
878
851
|
quantity: Math.max(1, Number(product.quantity || 1)),
|
|
879
852
|
capacityRequired: Math.max(1, Number(assignment.capacityRequired || product.capacityRequired || 1)),
|
|
880
853
|
resourceRequirements: product.resourceRequirements || []
|
|
881
854
|
};
|
|
882
|
-
const cell = evaluateResourceForInterval({
|
|
883
|
-
|
|
884
|
-
|
|
855
|
+
const cell = evaluateResourceForInterval({
|
|
856
|
+
resource,
|
|
857
|
+
interval,
|
|
858
|
+
context
|
|
859
|
+
});
|
|
860
|
+
if (cell.status !== 'available' && cell.status !== 'limited') {
|
|
861
|
+
conflicts.push(...cell.conflicts);
|
|
885
862
|
}
|
|
886
|
-
const priorContext = withAssignmentEvents(
|
|
887
|
-
context,
|
|
888
|
-
selection.assignments.slice(0, index)
|
|
889
|
-
);
|
|
863
|
+
const priorContext = withAssignmentEvents(context, selection.assignments.slice(0, index));
|
|
890
864
|
const selfConflictCell = evaluateResourceForInterval({
|
|
891
865
|
resource,
|
|
892
866
|
interval,
|
|
893
867
|
context: priorContext
|
|
894
868
|
});
|
|
895
|
-
if (selfConflictCell.status !==
|
|
896
|
-
|
|
869
|
+
if (selfConflictCell.status !== 'available' && selfConflictCell.status !== 'limited') {
|
|
870
|
+
conflicts.push(...selfConflictCell.conflicts.map(conflict => ({
|
|
897
871
|
...conflict,
|
|
898
|
-
type:
|
|
899
|
-
message:
|
|
872
|
+
type: 'cart_overlap',
|
|
873
|
+
message: '本次选择中存在资源时间冲突'
|
|
900
874
|
})));
|
|
901
875
|
}
|
|
902
876
|
}
|
|
903
877
|
return {
|
|
904
|
-
ok:
|
|
905
|
-
conflicts
|
|
878
|
+
ok: conflicts.length === 0,
|
|
879
|
+
conflicts
|
|
906
880
|
};
|
|
907
881
|
}
|
|
908
882
|
const projection = queryPlannerAvailability(contextInput, {
|
|
@@ -913,21 +887,9 @@ function validateAssignments(contextInput, selection) {
|
|
|
913
887
|
resourceIds: selection.resourceIds,
|
|
914
888
|
mode: selection.mode
|
|
915
889
|
});
|
|
916
|
-
const conflicts = projection.cells.filter(
|
|
890
|
+
const conflicts = projection.cells.filter(cell => cell.status !== 'available' && cell.status !== 'limited').flatMap(cell => cell.conflicts);
|
|
917
891
|
return {
|
|
918
892
|
ok: conflicts.length === 0,
|
|
919
893
|
conflicts
|
|
920
894
|
};
|
|
921
|
-
}
|
|
922
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
923
|
-
0 && (module.exports = {
|
|
924
|
-
DEFAULT_RESOURCE_PLANNER_CONTEXT,
|
|
925
|
-
DEFAULT_RESOURCE_PLANNER_SELECTION,
|
|
926
|
-
autoAssignResources,
|
|
927
|
-
buildDemandIntervals,
|
|
928
|
-
evaluateResourceForInterval,
|
|
929
|
-
normalizePlannerContext,
|
|
930
|
-
queryPlannerAvailability,
|
|
931
|
-
resolveAssignableSlots,
|
|
932
|
-
validateAssignments
|
|
933
|
-
});
|
|
895
|
+
}
|