@pisell/pisellos 2.3.71 → 2.3.73
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/modules/OpenData/types.d.ts +1 -0
- package/dist/modules/Order/utils.js +6 -1
- package/dist/modules/ResourcePlanner/index.d.ts +0 -22
- package/dist/modules/ResourcePlanner/index.js +1 -180
- package/dist/modules/ResourcePlanner/localClock.d.ts +33 -0
- package/dist/modules/ResourcePlanner/localClock.js +183 -0
- package/dist/modules/ResourcePlanner/planner.d.ts +5 -14
- package/dist/modules/ResourcePlanner/planner.js +2019 -936
- package/dist/modules/ResourcePlanner/types.d.ts +346 -187
- package/dist/modules/ResourcePlanner/types.js +33 -1
- package/dist/server/index.js +2 -1
- package/dist/solution/BookingTicket/index.d.ts +13 -1
- package/dist/solution/BookingTicket/index.js +4 -2
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
- package/dist/solution/BookingTicket/utils/addProductDecision.js +3 -2
- package/dist/solution/UnifiedBookingSales/index.d.ts +110 -73
- package/dist/solution/UnifiedBookingSales/index.js +3349 -971
- package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
- package/dist/solution/UnifiedBookingSales/remoteOccupancyCache.js +430 -0
- package/dist/solution/UnifiedBookingSales/types.d.ts +133 -76
- package/dist/solution/UnifiedBookingSales/types.js +4 -9
- package/lib/modules/OpenData/types.d.ts +1 -0
- package/lib/modules/Order/utils.js +5 -1
- package/lib/modules/ResourcePlanner/index.d.ts +0 -22
- package/lib/modules/ResourcePlanner/index.js +0 -123
- package/lib/modules/ResourcePlanner/localClock.d.ts +33 -0
- package/lib/modules/ResourcePlanner/localClock.js +270 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +5 -14
- package/lib/modules/ResourcePlanner/planner.js +1903 -798
- package/lib/modules/ResourcePlanner/types.d.ts +346 -187
- package/lib/modules/ResourcePlanner/types.js +19 -0
- package/lib/server/index.js +2 -1
- package/lib/solution/BookingTicket/index.d.ts +13 -1
- package/lib/solution/BookingTicket/index.js +2 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +2 -1
- package/lib/solution/BookingTicket/utils/addProductDecision.js +2 -0
- package/lib/solution/UnifiedBookingSales/index.d.ts +110 -73
- package/lib/solution/UnifiedBookingSales/index.js +2289 -374
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.d.ts +49 -0
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +376 -0
- package/lib/solution/UnifiedBookingSales/types.d.ts +133 -76
- package/lib/solution/UnifiedBookingSales/types.js +3 -2
- package/package.json +1 -1
|
@@ -30,25 +30,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/solution/UnifiedBookingSales/index.ts
|
|
31
31
|
var UnifiedBookingSales_exports = {};
|
|
32
32
|
__export(UnifiedBookingSales_exports, {
|
|
33
|
+
AvailabilityError: () => import_ResourcePlanner2.AvailabilityError,
|
|
33
34
|
UnifiedBookingSales: () => UnifiedBookingSales,
|
|
34
35
|
UnifiedBookingSalesImpl: () => UnifiedBookingSalesImpl,
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
buildAvailabilityLineIdentity: () => buildAvailabilityLineIdentity,
|
|
37
|
+
buildAvailabilityResourcesFromV2: () => buildAvailabilityResourcesFromV2,
|
|
38
|
+
buildBookingFromAvailabilityAssignment: () => buildBookingFromAvailabilityAssignment,
|
|
39
|
+
buildBookingFromAvailabilityAssignments: () => buildBookingFromAvailabilityAssignments,
|
|
37
40
|
buildPlannerDiscountResult: () => buildPlannerDiscountResult,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
collectPlannerResourceIds: () => collectPlannerResourceIds,
|
|
41
|
-
normalizeProductForPlanner: () => normalizeProductForPlanner
|
|
41
|
+
collectAvailabilityResourceIds: () => collectAvailabilityResourceIds,
|
|
42
|
+
normalizeProductForAvailability: () => normalizeProductForAvailability
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(UnifiedBookingSales_exports);
|
|
44
45
|
var import_dayjs = __toESM(require("dayjs"));
|
|
46
|
+
var import_lodash_es = require("lodash-es");
|
|
45
47
|
var import_modules = require("../../modules");
|
|
46
48
|
var import_getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
|
|
49
|
+
var import_ResourcePlanner = require("../../modules/ResourcePlanner");
|
|
47
50
|
var import_types = require("./types");
|
|
51
|
+
var import_remoteOccupancyCache = require("./remoteOccupancyCache");
|
|
48
52
|
var import_BookingTicket = require("../BookingTicket");
|
|
53
|
+
var import_localClock = require("../../modules/ResourcePlanner/localClock");
|
|
49
54
|
__reExport(UnifiedBookingSales_exports, require("./types"), module.exports);
|
|
50
|
-
var
|
|
55
|
+
var import_ResourcePlanner2 = require("../../modules/ResourcePlanner");
|
|
56
|
+
var OPEN_DATA_SECTION_CODES = ["sale", "reservation", "fulfillment", "menu", "workflow", "basic", "checkout", "availability"];
|
|
51
57
|
var OPEN_DATA_CACHE_TTL = 5 * 60 * 1e3;
|
|
58
|
+
function normalizePositiveInteger(value) {
|
|
59
|
+
const normalized = Number(value);
|
|
60
|
+
if (!Number.isInteger(normalized) || normalized <= 0)
|
|
61
|
+
return void 0;
|
|
62
|
+
return normalized;
|
|
63
|
+
}
|
|
52
64
|
function isRecord(value) {
|
|
53
65
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
54
66
|
}
|
|
@@ -166,88 +178,473 @@ function filterCustomerBoundDiscounts(discountList) {
|
|
|
166
178
|
return [];
|
|
167
179
|
return discountList.filter((discount) => !isCustomerBoundDiscount(discount));
|
|
168
180
|
}
|
|
181
|
+
var DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES = 30;
|
|
182
|
+
var DEFAULT_AVAILABILITY_BUSINESS_HOURS = {
|
|
183
|
+
startTime: "09:00",
|
|
184
|
+
endTime: "18:00"
|
|
185
|
+
};
|
|
186
|
+
var MAX_AVAILABILITY_RANGE_DAYS = 90;
|
|
187
|
+
var MAX_AVAILABILITY_CONTEXT_REGISTRY = 128;
|
|
188
|
+
var MAX_AVAILABILITY_PROJECTION_POOL = 16;
|
|
189
|
+
var MAX_REMOTE_OCCUPANCY_RESOURCE_DATE_CELLS = 4096;
|
|
190
|
+
function isValidClockTime(value) {
|
|
191
|
+
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
|
192
|
+
if (!match)
|
|
193
|
+
return false;
|
|
194
|
+
return Number(match[1]) < 24 && Number(match[2]) < 60;
|
|
195
|
+
}
|
|
196
|
+
function assertValidAvailabilityTimezone(timezoneName) {
|
|
197
|
+
if (!String(timezoneName || "").trim()) {
|
|
198
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "timezone label 不能为空", {
|
|
199
|
+
timezone: timezoneName
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function normalizeAvailabilityLocalDateTime(value, fixedOffsetMinutes) {
|
|
204
|
+
if (value == null || value === "")
|
|
205
|
+
return null;
|
|
206
|
+
try {
|
|
207
|
+
const localText = typeof (value == null ? void 0 : value.format) === "function" ? value.format("YYYY-MM-DDTHH:mm:ss") : String(value);
|
|
208
|
+
return (0, import_localClock.formatLocalDateTime)((0, import_localClock.parseLocalDateTime)(localText), fixedOffsetMinutes);
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function normalizeAvailabilityLocalDateTimeRange(params) {
|
|
214
|
+
const startText = normalizeAvailabilityLocalDateTime(params.startAt, params.fixedOffsetMinutes);
|
|
215
|
+
const endText = normalizeAvailabilityLocalDateTime(params.endAt, params.fixedOffsetMinutes);
|
|
216
|
+
if (!startText || !endText)
|
|
217
|
+
return null;
|
|
218
|
+
const startScalar = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(startText));
|
|
219
|
+
let endScalar = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(endText));
|
|
220
|
+
if (endScalar <= startScalar && params.rollOverEnd) {
|
|
221
|
+
endScalar = (0, import_localClock.addLocalCalendarDays)(endScalar, 1);
|
|
222
|
+
}
|
|
223
|
+
if (endScalar <= startScalar)
|
|
224
|
+
return null;
|
|
225
|
+
return {
|
|
226
|
+
startAt: startText,
|
|
227
|
+
endAt: (0, import_localClock.formatLocalDateTime)(endScalar, params.fixedOffsetMinutes),
|
|
228
|
+
startScalar,
|
|
229
|
+
endScalar
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function sameAvailabilityId(left, right) {
|
|
233
|
+
return left !== void 0 && left !== null && right !== void 0 && right !== null && String(left) === String(right);
|
|
234
|
+
}
|
|
235
|
+
function uniqueAvailabilityIds(values) {
|
|
236
|
+
const result = /* @__PURE__ */ new Map();
|
|
237
|
+
values.forEach((value) => {
|
|
238
|
+
const id = toNumberId(value);
|
|
239
|
+
if (id !== void 0)
|
|
240
|
+
result.set(String(id), id);
|
|
241
|
+
});
|
|
242
|
+
return Array.from(result.values());
|
|
243
|
+
}
|
|
244
|
+
function assertAvailabilityCatalogProductIds(productIds, context) {
|
|
245
|
+
if (productIds == null)
|
|
246
|
+
return;
|
|
247
|
+
if (!Array.isArray(productIds)) {
|
|
248
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", `${context} 必须是 number[]`, {
|
|
249
|
+
context,
|
|
250
|
+
productIds
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const invalidIds = productIds.filter((id) => typeof id !== "number" || !Number.isFinite(id));
|
|
254
|
+
if (invalidIds.length > 0) {
|
|
255
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", `${context} 只能包含有限数值商品 ID`, {
|
|
256
|
+
context,
|
|
257
|
+
invalidIds,
|
|
258
|
+
productIds
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function normalizeResourceType(_value) {
|
|
263
|
+
return "single";
|
|
264
|
+
}
|
|
169
265
|
function resolveProductKind(product) {
|
|
170
266
|
var _a, _b, _c;
|
|
171
|
-
if (product.duration)
|
|
267
|
+
if (product.duration || product.service_duration)
|
|
172
268
|
return "duration";
|
|
269
|
+
if (product.extension_type === "venue_slot")
|
|
270
|
+
return "venue_slot";
|
|
173
271
|
if (["session_product", "session_ticket"].includes(product.extension_type))
|
|
174
272
|
return "session";
|
|
175
|
-
if (((_a = product.cartDetailValue) == null ? void 0 : _a.session) || ((_c = (_b = product._extend) == null ? void 0 : _b.other) == null ? void 0 : _c.session))
|
|
273
|
+
if (((_a = product.cartDetailValue) == null ? void 0 : _a.session) || ((_c = (_b = product._extend) == null ? void 0 : _b.other) == null ? void 0 : _c.session) || product.session) {
|
|
176
274
|
return "session";
|
|
177
|
-
|
|
275
|
+
}
|
|
276
|
+
const scheduleIds = product["schedule.ids"] || product.schedule_ids;
|
|
277
|
+
if (Array.isArray(scheduleIds) && scheduleIds.length > 0)
|
|
178
278
|
return "session";
|
|
179
279
|
return "normal";
|
|
180
280
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
281
|
+
function expandScheduleDateRanges(values) {
|
|
282
|
+
if (!Array.isArray(values))
|
|
283
|
+
return [];
|
|
284
|
+
const dates = /* @__PURE__ */ new Set();
|
|
285
|
+
values.forEach((value) => {
|
|
286
|
+
if (typeof value === "string") {
|
|
287
|
+
try {
|
|
288
|
+
dates.add((0, import_localClock.formatLocalDate)((0, import_localClock.parseLocalDate)(value.slice(0, 10))));
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const startValue = (value == null ? void 0 : value.start) || (value == null ? void 0 : value.start_date) || (value == null ? void 0 : value.date);
|
|
294
|
+
const endValue = (value == null ? void 0 : value.end) || (value == null ? void 0 : value.end_date) || startValue;
|
|
295
|
+
let start;
|
|
296
|
+
let end;
|
|
297
|
+
try {
|
|
298
|
+
start = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(String(startValue).slice(0, 10)));
|
|
299
|
+
end = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(String(endValue).slice(0, 10)));
|
|
300
|
+
} catch {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (end < start)
|
|
304
|
+
return;
|
|
305
|
+
let cursor = start;
|
|
306
|
+
let guard = 0;
|
|
307
|
+
while (cursor <= end && guard < MAX_AVAILABILITY_RANGE_DAYS) {
|
|
308
|
+
dates.add((0, import_localClock.formatLocalDate)(cursor));
|
|
309
|
+
cursor = (0, import_localClock.addLocalCalendarDays)(cursor, 1);
|
|
310
|
+
guard += 1;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return Array.from(dates).sort();
|
|
195
314
|
}
|
|
196
|
-
function
|
|
315
|
+
function normalizeRequirementGroups(product) {
|
|
197
316
|
var _a;
|
|
198
317
|
const resources = (_a = product.product_resource) == null ? void 0 : _a.resources;
|
|
199
318
|
if (!Array.isArray(resources))
|
|
200
319
|
return [];
|
|
201
|
-
return resources.filter((resource) => (resource == null ? void 0 : resource.status) !== 0).map((resource) => {
|
|
202
|
-
var _a2, _b;
|
|
320
|
+
return resources.filter((resource) => (resource == null ? void 0 : resource.status) !== 0).map((resource, index) => {
|
|
321
|
+
var _a2, _b, _c;
|
|
322
|
+
const formId = toNumberId(resource.id ?? resource.form_id);
|
|
323
|
+
const required = resource.required ?? resource.status === 1;
|
|
324
|
+
const resourceType = normalizeResourceType(resource.type);
|
|
325
|
+
const combinedIds = ((_a2 = resource.combined_resource) == null ? void 0 : _a2.status) === 1 ? (_b = resource.combined_resource) == null ? void 0 : _b.resource_ids : [];
|
|
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
|
+
]);
|
|
331
|
+
const min = required ? 1 : 0;
|
|
332
|
+
const max = 1;
|
|
203
333
|
return {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
334
|
+
id: String(formId ?? resource.code ?? `requirement-${index}`),
|
|
335
|
+
formId,
|
|
336
|
+
title: pickLocalizedText(resource.title || resource.name || resource.main_field),
|
|
337
|
+
code: resource.code,
|
|
338
|
+
resourceType,
|
|
339
|
+
required: Boolean(required),
|
|
340
|
+
min,
|
|
341
|
+
max,
|
|
342
|
+
resourceIds,
|
|
343
|
+
capacityRequired: Math.max(1, Number(
|
|
344
|
+
resource.capacity_required ?? ((_c = product.capacity) == null ? void 0 : _c.default) ?? product.capacity ?? 1
|
|
345
|
+
) || 1),
|
|
346
|
+
raw: resource
|
|
212
347
|
};
|
|
213
348
|
});
|
|
214
349
|
}
|
|
215
|
-
function
|
|
350
|
+
function normalizeScheduleRepeatType(value) {
|
|
351
|
+
if (value === "daily" || value === 1 || value === "1")
|
|
352
|
+
return "daily";
|
|
353
|
+
if (value === "weekly" || value === 2 || value === "2")
|
|
354
|
+
return "weekly";
|
|
355
|
+
return "none";
|
|
356
|
+
}
|
|
357
|
+
function normalizeAbsoluteSessionRange(params) {
|
|
358
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
359
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
360
|
+
startAt: params.startAt,
|
|
361
|
+
endAt: params.endAt,
|
|
362
|
+
fixedOffsetMinutes,
|
|
363
|
+
rollOverEnd: true
|
|
364
|
+
});
|
|
365
|
+
if (!range)
|
|
366
|
+
return null;
|
|
367
|
+
return {
|
|
368
|
+
startAt: range.startAt,
|
|
369
|
+
endAt: range.endAt,
|
|
370
|
+
scheduleRefs: [{
|
|
371
|
+
scheduleId: params.scheduleId,
|
|
372
|
+
timeSlotId: params.timeSlotId,
|
|
373
|
+
source: params.source
|
|
374
|
+
}]
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function normalizeScheduleItem(schedule, fixedOffsetMinutes) {
|
|
216
378
|
var _a, _b;
|
|
379
|
+
const rules = [];
|
|
380
|
+
const ranges = [];
|
|
381
|
+
const scheduleId = toNumberId(schedule.id ?? schedule.schedule_id);
|
|
382
|
+
const repeatType = normalizeScheduleRepeatType(schedule.repeat_type);
|
|
383
|
+
const repeatRule = schedule.repeat_rule || {};
|
|
384
|
+
const startAt = schedule.start_at || schedule.start || schedule.start_time;
|
|
385
|
+
const endAt = schedule.end_at || schedule.end || schedule.end_time;
|
|
386
|
+
const startDate = startAt ? String(startAt).slice(0, 10) : void 0;
|
|
387
|
+
const configuredEndDate = ((_a = repeatRule.end) == null ? void 0 : _a.type) === "date" ? (_b = repeatRule.end) == null ? void 0 : _b.end_date : void 0;
|
|
388
|
+
const endDate = configuredEndDate ? String(configuredEndDate).slice(0, 10) : void 0;
|
|
389
|
+
const includedDates = expandScheduleDateRanges(repeatRule.included_date);
|
|
390
|
+
const excludedDates = expandScheduleDateRanges(repeatRule.excluded_date);
|
|
391
|
+
const addRule = (params) => {
|
|
392
|
+
const mode = repeatType === "weekly" ? "weekly" : "daily";
|
|
393
|
+
rules.push({
|
|
394
|
+
id: `${String(scheduleId ?? "schedule")}:${params.idSuffix}`,
|
|
395
|
+
mode,
|
|
396
|
+
startTime: params.startTime.slice(0, 5),
|
|
397
|
+
endTime: params.endTime.slice(0, 5),
|
|
398
|
+
startDate,
|
|
399
|
+
endDate,
|
|
400
|
+
frequency: Math.max(1, Number(repeatRule.frequency || 1)),
|
|
401
|
+
weekdays: repeatType === "weekly" ? (repeatRule.frequency_date || []).map(Number).filter(Number.isFinite) : void 0,
|
|
402
|
+
includeDates: includedDates,
|
|
403
|
+
excludeDates: excludedDates,
|
|
404
|
+
scheduleRef: {
|
|
405
|
+
scheduleId,
|
|
406
|
+
timeSlotId: params.timeSlotId,
|
|
407
|
+
source: `schedule_${schedule.type || "standard"}`
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
if (schedule.type === "designation") {
|
|
412
|
+
const designations = Array.isArray(schedule.designation) ? schedule.designation : schedule.designation ? [schedule.designation] : [];
|
|
413
|
+
designations.forEach((designation) => {
|
|
414
|
+
const range = normalizeAbsoluteSessionRange({
|
|
415
|
+
startAt: `${designation.start_date} ${designation.start_time}`,
|
|
416
|
+
endAt: `${designation.end_date || designation.start_date} ${designation.end_time}`,
|
|
417
|
+
scheduleId,
|
|
418
|
+
source: "schedule_designation",
|
|
419
|
+
fixedOffsetMinutes
|
|
420
|
+
});
|
|
421
|
+
if (range)
|
|
422
|
+
ranges.push(range);
|
|
423
|
+
});
|
|
424
|
+
return { rules, ranges };
|
|
425
|
+
}
|
|
426
|
+
if (schedule.type === "time-slots" && Array.isArray(schedule.time_slot)) {
|
|
427
|
+
schedule.time_slot.forEach((slot, index) => {
|
|
428
|
+
if (!slot.start_time || !slot.end_time)
|
|
429
|
+
return;
|
|
430
|
+
if (repeatType === "none" && startDate) {
|
|
431
|
+
const range = normalizeAbsoluteSessionRange({
|
|
432
|
+
startAt: `${startDate} ${slot.start_time}`,
|
|
433
|
+
endAt: `${startDate} ${slot.end_time}`,
|
|
434
|
+
scheduleId,
|
|
435
|
+
timeSlotId: toNumberId(slot.id),
|
|
436
|
+
source: "schedule_time_slot",
|
|
437
|
+
fixedOffsetMinutes
|
|
438
|
+
});
|
|
439
|
+
if (range)
|
|
440
|
+
ranges.push(range);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
addRule({
|
|
444
|
+
idSuffix: `slot-${String(slot.id ?? index)}`,
|
|
445
|
+
startTime: slot.start_time,
|
|
446
|
+
endTime: slot.end_time,
|
|
447
|
+
timeSlotId: toNumberId(slot.id)
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
return { rules, ranges };
|
|
451
|
+
}
|
|
452
|
+
if (startAt && endAt) {
|
|
453
|
+
if (repeatType === "none") {
|
|
454
|
+
const range = normalizeAbsoluteSessionRange({
|
|
455
|
+
startAt,
|
|
456
|
+
endAt,
|
|
457
|
+
scheduleId,
|
|
458
|
+
source: "schedule_standard",
|
|
459
|
+
fixedOffsetMinutes
|
|
460
|
+
});
|
|
461
|
+
if (range)
|
|
462
|
+
ranges.push(range);
|
|
463
|
+
} else {
|
|
464
|
+
try {
|
|
465
|
+
addRule({
|
|
466
|
+
idSuffix: "standard",
|
|
467
|
+
startTime: (0, import_localClock.formatLocalTime)(String(startAt), false),
|
|
468
|
+
endTime: (0, import_localClock.formatLocalTime)(String(endAt), false)
|
|
469
|
+
});
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return { rules, ranges };
|
|
475
|
+
}
|
|
476
|
+
function resolveProductScheduleItems(product, scheduleList) {
|
|
477
|
+
const scheduleMap = new Map(scheduleList.map((schedule) => [String(schedule.id), schedule]));
|
|
478
|
+
const rawSchedules = [product.schedules, product.schedule, product._schedule].flatMap((value) => Array.isArray(value) ? value : value ? [value] : []);
|
|
479
|
+
const explicitIds = uniqueAvailabilityIds([
|
|
480
|
+
...Array.isArray(product["schedule.ids"]) ? product["schedule.ids"] : [],
|
|
481
|
+
...Array.isArray(product.schedule_ids) ? product.schedule_ids : [],
|
|
482
|
+
...rawSchedules.filter((item) => typeof item !== "object")
|
|
483
|
+
]);
|
|
484
|
+
const result = /* @__PURE__ */ new Map();
|
|
485
|
+
rawSchedules.filter((item) => item && typeof item === "object").forEach((schedule) => {
|
|
486
|
+
result.set(String(schedule.id ?? JSON.stringify(schedule)), schedule);
|
|
487
|
+
});
|
|
488
|
+
explicitIds.forEach((id) => {
|
|
489
|
+
const schedule = scheduleMap.get(String(id));
|
|
490
|
+
if (schedule)
|
|
491
|
+
result.set(String(id), schedule);
|
|
492
|
+
});
|
|
493
|
+
return Array.from(result.values());
|
|
494
|
+
}
|
|
495
|
+
function normalizeProductForAvailability(product, scheduleList = [], fixedOffsetMinutes = (0, import_localClock.captureRuntimeOffsetMinutes)()) {
|
|
496
|
+
var _a, _b, _c, _d, _e;
|
|
217
497
|
const raw = product;
|
|
218
498
|
const kind = resolveProductKind(raw);
|
|
499
|
+
const sessionRules = [];
|
|
500
|
+
const sessionRanges = [];
|
|
501
|
+
const explicitSession = ((_a = raw.cartDetailValue) == null ? void 0 : _a.session) || ((_c = (_b = raw._extend) == null ? void 0 : _b.other) == null ? void 0 : _c.session) || raw.session;
|
|
502
|
+
const explicitRange = normalizeAbsoluteSessionRange({
|
|
503
|
+
startAt: explicitSession == null ? void 0 : explicitSession.start_at,
|
|
504
|
+
endAt: explicitSession == null ? void 0 : explicitSession.end_at,
|
|
505
|
+
scheduleId: toNumberId(explicitSession == null ? void 0 : explicitSession.schedule_id),
|
|
506
|
+
timeSlotId: toNumberId(explicitSession == null ? void 0 : explicitSession.time_slot_id),
|
|
507
|
+
source: "product_session",
|
|
508
|
+
fixedOffsetMinutes
|
|
509
|
+
});
|
|
510
|
+
if (explicitRange)
|
|
511
|
+
sessionRanges.push(explicitRange);
|
|
512
|
+
resolveProductScheduleItems(raw, scheduleList).forEach((schedule) => {
|
|
513
|
+
const normalized = normalizeScheduleItem(schedule, fixedOffsetMinutes);
|
|
514
|
+
sessionRules.push(...normalized.rules);
|
|
515
|
+
sessionRanges.push(...normalized.ranges);
|
|
516
|
+
});
|
|
219
517
|
return {
|
|
220
518
|
id: raw.id ?? raw.product_id,
|
|
221
519
|
title: pickLocalizedText(raw.title || raw.name || raw.product_title),
|
|
222
520
|
kind,
|
|
223
|
-
durationMinutes: Number(((
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
521
|
+
durationMinutes: kind === "duration" ? Math.max(1, Number(((_d = raw.duration) == null ? void 0 : _d.value) || raw.duration || raw.service_duration || 30) || 30) : void 0,
|
|
522
|
+
requirementGroups: normalizeRequirementGroups(raw),
|
|
523
|
+
sessionRules,
|
|
524
|
+
sessionRanges,
|
|
525
|
+
capacityRequired: Math.max(1, Number(((_e = raw.capacity) == null ? void 0 : _e.default) || raw.capacity || 1) || 1),
|
|
526
|
+
quantity: Math.max(1, Number(raw.num || raw.quantity || 1) || 1),
|
|
527
|
+
cutOffPolicy: normalizeAvailabilityCutOffPolicy(raw.cut_off_time),
|
|
229
528
|
raw
|
|
230
529
|
};
|
|
231
530
|
}
|
|
232
|
-
function
|
|
233
|
-
if (value
|
|
234
|
-
return
|
|
235
|
-
|
|
531
|
+
function normalizeAvailabilityCutOffNumber(value, context) {
|
|
532
|
+
if (value == null) {
|
|
533
|
+
return { reason: `${context} 缺失` };
|
|
534
|
+
}
|
|
535
|
+
if (typeof value === "string" && !value.trim()) {
|
|
536
|
+
return { reason: `${context} 缺失` };
|
|
537
|
+
}
|
|
538
|
+
const normalized = Number(value);
|
|
539
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
540
|
+
return { reason: `${context} 必须是非负有限数` };
|
|
541
|
+
}
|
|
542
|
+
return { value: normalized };
|
|
236
543
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
544
|
+
function normalizeAvailabilityCutOffInteger(value, context) {
|
|
545
|
+
const numeric = normalizeAvailabilityCutOffNumber(value, context);
|
|
546
|
+
if (numeric.reason)
|
|
547
|
+
return numeric;
|
|
548
|
+
if (!Number.isInteger(numeric.value)) {
|
|
549
|
+
return { reason: `${context} 必须是非负整数` };
|
|
550
|
+
}
|
|
551
|
+
return { value: numeric.value };
|
|
552
|
+
}
|
|
553
|
+
function invalidAvailabilityCutOffPolicy(reason) {
|
|
554
|
+
return { type: "invalid", reason };
|
|
555
|
+
}
|
|
556
|
+
function normalizeAvailabilityFutureDay(raw) {
|
|
557
|
+
if (!Object.prototype.hasOwnProperty.call(raw, "future_day"))
|
|
558
|
+
return {};
|
|
559
|
+
const futureDay = normalizeAvailabilityCutOffInteger(raw.future_day, "cut_off_time.future_day");
|
|
560
|
+
return futureDay.reason ? { reason: futureDay.reason } : { futureDay: futureDay.value };
|
|
561
|
+
}
|
|
562
|
+
function normalizeAvailabilityCutOffPolicy(raw) {
|
|
563
|
+
if (raw == null || raw === "")
|
|
564
|
+
return void 0;
|
|
565
|
+
if (!isRecord(raw)) {
|
|
566
|
+
return invalidAvailabilityCutOffPolicy("cut_off_time 必须是对象");
|
|
567
|
+
}
|
|
568
|
+
const futureDay = normalizeAvailabilityFutureDay(raw);
|
|
569
|
+
if (futureDay.reason) {
|
|
570
|
+
return invalidAvailabilityCutOffPolicy(futureDay.reason);
|
|
571
|
+
}
|
|
572
|
+
const type = String(raw.type ?? "").trim();
|
|
573
|
+
if (!type) {
|
|
574
|
+
return invalidAvailabilityCutOffPolicy("cut_off_time.type 缺失");
|
|
575
|
+
}
|
|
576
|
+
if (type === "ongoing") {
|
|
577
|
+
if (!isRecord(raw.ongoing)) {
|
|
578
|
+
return invalidAvailabilityCutOffPolicy("cut_off_time.ongoing 缺失");
|
|
579
|
+
}
|
|
580
|
+
const ongoingType = String(raw.ongoing.type ?? "").trim();
|
|
581
|
+
if (ongoingType === "before_end") {
|
|
582
|
+
return {
|
|
583
|
+
type: "ongoing",
|
|
584
|
+
mode: "before_end",
|
|
585
|
+
...futureDay.futureDay === void 0 ? {} : { futureDay: futureDay.futureDay }
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
if (ongoingType === "after_start") {
|
|
589
|
+
const graceMinutes = normalizeAvailabilityCutOffNumber(
|
|
590
|
+
raw.ongoing.unit,
|
|
591
|
+
"cut_off_time.ongoing.unit"
|
|
592
|
+
);
|
|
593
|
+
return graceMinutes.reason ? invalidAvailabilityCutOffPolicy(graceMinutes.reason) : {
|
|
594
|
+
type: "ongoing",
|
|
595
|
+
mode: "after_start",
|
|
596
|
+
graceMinutes: graceMinutes.value,
|
|
597
|
+
...futureDay.futureDay === void 0 ? {} : { futureDay: futureDay.futureDay }
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return invalidAvailabilityCutOffPolicy(`未知的 cut_off_time.ongoing.type: ${ongoingType}`);
|
|
601
|
+
}
|
|
602
|
+
if (type === "before_start") {
|
|
603
|
+
return {
|
|
604
|
+
type: "before_start",
|
|
605
|
+
...futureDay.futureDay === void 0 ? {} : { futureDay: futureDay.futureDay }
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (type === "advance") {
|
|
609
|
+
const unit = normalizeAvailabilityCutOffNumber(raw.unit, "cut_off_time.unit");
|
|
610
|
+
if (unit.reason)
|
|
611
|
+
return invalidAvailabilityCutOffPolicy(unit.reason);
|
|
612
|
+
const unitType = String(raw.unit_type ?? "").trim();
|
|
613
|
+
if (!["days", "hours", "minutes"].includes(unitType)) {
|
|
614
|
+
return invalidAvailabilityCutOffPolicy("cut_off_time.unit_type 必须是 days|hours|minutes");
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
type: "advance",
|
|
618
|
+
unit: unit.value,
|
|
619
|
+
unitType,
|
|
620
|
+
...futureDay.futureDay === void 0 ? {} : { futureDay: futureDay.futureDay }
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return invalidAvailabilityCutOffPolicy(`未知的 cut_off_time.type: ${type}`);
|
|
624
|
+
}
|
|
625
|
+
function normalizeResource(raw, fixedOffsetMinutes = (0, import_localClock.captureRuntimeOffsetMinutes)()) {
|
|
239
626
|
const id = raw.resourceId ?? raw.resource_id ?? raw.id;
|
|
240
627
|
if (id === void 0 || id === null || id === "")
|
|
241
628
|
return null;
|
|
242
|
-
const
|
|
629
|
+
const rawWindows = raw.windows || raw.times || [];
|
|
243
630
|
return {
|
|
244
631
|
id,
|
|
245
632
|
formId: raw.formId ?? raw.form_id ?? raw.resource_form_id,
|
|
246
633
|
name: raw.resourceName || raw.main_field || raw.name || raw.title,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
634
|
+
code: raw.code,
|
|
635
|
+
resourceType: normalizeResourceType(raw.type || raw.resourceType),
|
|
636
|
+
capacity: Math.max(1, Number(raw.capacity || raw.max_capacity || 1) || 1),
|
|
637
|
+
windows: (Array.isArray(rawWindows) ? rawWindows : []).flatMap((window) => {
|
|
638
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
639
|
+
startAt: window.startAt || window.start_at || window.start,
|
|
640
|
+
endAt: window.endAt || window.end_at || window.end,
|
|
641
|
+
fixedOffsetMinutes,
|
|
642
|
+
rollOverEnd: true
|
|
643
|
+
});
|
|
644
|
+
if (!range)
|
|
645
|
+
return [];
|
|
646
|
+
return [{ startAt: range.startAt, endAt: range.endAt, source: window.source, raw: window }];
|
|
647
|
+
}),
|
|
251
648
|
raw
|
|
252
649
|
};
|
|
253
650
|
}
|
|
@@ -255,97 +652,81 @@ function normalizeScheduleIds(raw) {
|
|
|
255
652
|
const ids = raw.schedule ?? raw.schedule_ids ?? raw.scheduleIds ?? [];
|
|
256
653
|
return (Array.isArray(ids) ? ids : [ids]).map((id) => toNumberId(id)).filter((id) => id !== void 0);
|
|
257
654
|
}
|
|
258
|
-
function collectRawScheduleIds(rawResources) {
|
|
259
|
-
const ids = /* @__PURE__ */ new Map();
|
|
260
|
-
(rawResources || []).forEach((resource) => {
|
|
261
|
-
normalizeScheduleIds(resource).forEach((id) => {
|
|
262
|
-
ids.set(String(id), id);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
return Array.from(ids.values());
|
|
266
|
-
}
|
|
267
|
-
function scheduleListHasIds(scheduleList, scheduleIds) {
|
|
268
|
-
if (!scheduleIds.length)
|
|
269
|
-
return true;
|
|
270
|
-
const existingIds = new Set((scheduleList || []).map((schedule) => String(schedule.id)));
|
|
271
|
-
return scheduleIds.every((id) => existingIds.has(String(id)));
|
|
272
|
-
}
|
|
273
655
|
function dateRangeDays(dateRange) {
|
|
274
|
-
if (!(dateRange == null ? void 0 : dateRange.
|
|
656
|
+
if (!(dateRange == null ? void 0 : dateRange.startDate) || !(dateRange == null ? void 0 : dateRange.endDate))
|
|
275
657
|
return [];
|
|
276
658
|
const dates = [];
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
if (
|
|
659
|
+
const startScalar = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(dateRange.startDate));
|
|
660
|
+
const endScalar = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(dateRange.endDate));
|
|
661
|
+
if (endScalar < startScalar)
|
|
280
662
|
return dates;
|
|
281
|
-
|
|
282
|
-
dates.push(
|
|
283
|
-
cursor = cursor.add(1, "day");
|
|
663
|
+
for (let cursor = startScalar; cursor <= endScalar; cursor = (0, import_localClock.addLocalCalendarDays)(cursor, 1)) {
|
|
664
|
+
dates.push((0, import_localClock.formatLocalDate)(cursor));
|
|
284
665
|
}
|
|
285
666
|
return dates;
|
|
286
667
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
668
|
+
function normalizeResourceOccupancy(rawEvent, resourceId, source = "remote", fixedOffsetMinutes = (0, import_localClock.captureRuntimeOffsetMinutes)()) {
|
|
669
|
+
var _a, _b;
|
|
670
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
671
|
+
startAt: rawEvent.start_at ?? rawEvent.startAt ?? rawEvent.start_time ?? rawEvent.startTime,
|
|
672
|
+
endAt: rawEvent.end_at ?? rawEvent.endAt ?? rawEvent.end_time ?? rawEvent.endTime,
|
|
673
|
+
fixedOffsetMinutes,
|
|
674
|
+
rollOverEnd: false
|
|
675
|
+
});
|
|
676
|
+
if (!range)
|
|
291
677
|
return null;
|
|
292
|
-
const rawSource = rawEvent.source;
|
|
293
|
-
const source = rawSource === "remote" || rawSource === "cart" || rawSource === "selection" ? rawSource : "remote";
|
|
294
678
|
return {
|
|
295
|
-
|
|
679
|
+
id: rawEvent.id == null ? void 0 : String(rawEvent.id),
|
|
296
680
|
resourceId,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
end_at: endAt,
|
|
681
|
+
startAt: range.startAt,
|
|
682
|
+
endAt: range.endAt,
|
|
300
683
|
pax: Number(rawEvent.pax ?? rawEvent.number ?? rawEvent.capacity ?? 1) || 1,
|
|
301
|
-
source
|
|
684
|
+
source: rawEvent.source === "cart" || rawEvent.source === "selection" ? rawEvent.source : source,
|
|
685
|
+
scheduleEventId: toNumberId(rawEvent.schedule_event_id ?? rawEvent.scheduleEventId),
|
|
686
|
+
serverEventId: toNumberId(rawEvent.schedule_event_id ?? rawEvent.server_event_id ?? rawEvent.id),
|
|
687
|
+
bookingUid: rawEvent.booking_uid ?? rawEvent.bookingUid ?? ((_a = rawEvent.metadata) == null ? void 0 : _a.unique_identification_number),
|
|
688
|
+
productUid: rawEvent.product_uid ?? rawEvent.productUid ?? ((_b = rawEvent.metadata) == null ? void 0 : _b.product_uid),
|
|
689
|
+
raw: rawEvent
|
|
302
690
|
};
|
|
303
691
|
}
|
|
304
|
-
function normalizeResourceV2(raw) {
|
|
305
|
-
const normalized = normalizeResource({
|
|
306
|
-
...raw,
|
|
307
|
-
times: []
|
|
308
|
-
});
|
|
309
|
-
if (!normalized)
|
|
310
|
-
return null;
|
|
311
|
-
return normalized;
|
|
312
|
-
}
|
|
313
692
|
function buildResourceWindowFallbackTimes(params) {
|
|
314
693
|
const { raw, dates, scheduleIds } = params;
|
|
315
|
-
const
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
694
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
695
|
+
const boundary = normalizeAvailabilityLocalDateTimeRange({
|
|
696
|
+
startAt: raw.start_time || raw.start_at || raw.startTime,
|
|
697
|
+
endAt: raw.end_time || raw.end_at || raw.endTime,
|
|
698
|
+
fixedOffsetMinutes,
|
|
699
|
+
rollOverEnd: true
|
|
700
|
+
});
|
|
701
|
+
if (!boundary)
|
|
322
702
|
return [];
|
|
323
|
-
}
|
|
324
703
|
return dates.flatMap((date) => {
|
|
325
|
-
const dayStart = (0,
|
|
326
|
-
const dayEnd = (0,
|
|
327
|
-
const start =
|
|
328
|
-
const end =
|
|
329
|
-
if (
|
|
704
|
+
const dayStart = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(`${date}T00:00:00`));
|
|
705
|
+
const dayEnd = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(`${date}T23:59:59`));
|
|
706
|
+
const start = Math.max(boundary.startScalar, dayStart);
|
|
707
|
+
const end = Math.min(boundary.endScalar, dayEnd);
|
|
708
|
+
if (end <= start)
|
|
330
709
|
return [];
|
|
331
710
|
return [{
|
|
332
|
-
start_at:
|
|
333
|
-
end_at:
|
|
711
|
+
start_at: (0, import_localClock.formatLocalDateTime)(start, fixedOffsetMinutes),
|
|
712
|
+
end_at: (0, import_localClock.formatLocalDateTime)(end, fixedOffsetMinutes),
|
|
334
713
|
schedule_ids: scheduleIds,
|
|
335
714
|
source: "resource_window_fallback"
|
|
336
715
|
}];
|
|
337
716
|
});
|
|
338
717
|
}
|
|
339
|
-
function
|
|
718
|
+
function buildAvailabilityResourcesFromV2(params) {
|
|
340
719
|
const scheduleMap = new Map(
|
|
341
720
|
(params.scheduleList || []).map((schedule) => [String(schedule.id), schedule])
|
|
342
721
|
);
|
|
343
722
|
const dates = dateRangeDays(params.dateRange);
|
|
344
|
-
const
|
|
723
|
+
const resourceOccupancies = [];
|
|
724
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
345
725
|
const resources = (params.rawResources || []).map((raw) => {
|
|
346
|
-
const resource =
|
|
726
|
+
const resource = normalizeResource(raw, fixedOffsetMinutes);
|
|
347
727
|
if (!resource)
|
|
348
728
|
return null;
|
|
729
|
+
const explicitWindows = [...resource.windows];
|
|
349
730
|
const scheduleIds = normalizeScheduleIds(raw);
|
|
350
731
|
const schedules = scheduleIds.map((id) => scheduleMap.get(String(id))).filter(Boolean);
|
|
351
732
|
const matchedScheduleIds = new Set(schedules.map((schedule) => String(schedule.id)));
|
|
@@ -353,35 +734,53 @@ function buildPlannerResourcesFromV2(params) {
|
|
|
353
734
|
if (missingScheduleIds.length > 0) {
|
|
354
735
|
resource.raw = {
|
|
355
736
|
...resource.raw || raw,
|
|
356
|
-
|
|
737
|
+
_availabilityMissingScheduleIds: missingScheduleIds
|
|
357
738
|
};
|
|
358
739
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
740
|
+
const materializedWindows = dates.flatMap((date) => schedules.flatMap((schedule) => (0, import_getDateIsInSchedule.getScheduleStartEndTimePoints)(date, [schedule]).flatMap((slot) => {
|
|
741
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
742
|
+
startAt: slot.start_at,
|
|
743
|
+
endAt: slot.end_at,
|
|
744
|
+
fixedOffsetMinutes,
|
|
745
|
+
rollOverEnd: true
|
|
746
|
+
});
|
|
747
|
+
if (!range)
|
|
748
|
+
return [];
|
|
749
|
+
return [{
|
|
750
|
+
startAt: range.startAt,
|
|
751
|
+
endAt: range.endAt,
|
|
752
|
+
source: `resource_schedule:${schedule.id}`,
|
|
753
|
+
raw: { scheduleId: schedule.id }
|
|
754
|
+
}];
|
|
363
755
|
})));
|
|
364
|
-
|
|
365
|
-
|
|
756
|
+
resource.windows = materializedWindows.length > 0 ? materializedWindows : explicitWindows;
|
|
757
|
+
if (resource.windows.length === 0 && missingScheduleIds.length > 0) {
|
|
758
|
+
resource.windows = buildResourceWindowFallbackTimes({
|
|
366
759
|
raw,
|
|
367
760
|
dates,
|
|
368
|
-
scheduleIds
|
|
369
|
-
|
|
761
|
+
scheduleIds,
|
|
762
|
+
fixedOffsetMinutes
|
|
763
|
+
}).map((window) => ({
|
|
764
|
+
startAt: window.start_at,
|
|
765
|
+
endAt: window.end_at,
|
|
766
|
+
source: window.source,
|
|
767
|
+
raw: window
|
|
768
|
+
}));
|
|
370
769
|
}
|
|
371
770
|
const eventList = Array.isArray(raw.event_list) ? raw.event_list : [];
|
|
372
771
|
eventList.forEach((event) => {
|
|
373
|
-
const
|
|
374
|
-
if (
|
|
375
|
-
|
|
772
|
+
const occupancy = normalizeResourceOccupancy(event, resource.id, "remote", fixedOffsetMinutes);
|
|
773
|
+
if (occupancy)
|
|
774
|
+
resourceOccupancies.push(occupancy);
|
|
376
775
|
});
|
|
377
776
|
return resource;
|
|
378
777
|
}).filter((resource) => resource !== null);
|
|
379
778
|
return {
|
|
380
779
|
resources,
|
|
381
|
-
|
|
780
|
+
resourceOccupancies
|
|
382
781
|
};
|
|
383
782
|
}
|
|
384
|
-
function
|
|
783
|
+
function collectAvailabilityResourceIds(products, explicitResourceIds = []) {
|
|
385
784
|
const ids = /* @__PURE__ */ new Map();
|
|
386
785
|
const add = (value) => {
|
|
387
786
|
if (value === void 0 || value === null || value === "")
|
|
@@ -391,76 +790,199 @@ function collectPlannerResourceIds(products, explicitResourceIds = []) {
|
|
|
391
790
|
explicitResourceIds.forEach(add);
|
|
392
791
|
products.forEach((product) => {
|
|
393
792
|
var _a;
|
|
394
|
-
(_a = product.
|
|
793
|
+
(_a = product.requirementGroups) == null ? void 0 : _a.forEach((requirement) => {
|
|
395
794
|
var _a2;
|
|
396
795
|
(_a2 = requirement.resourceIds) == null ? void 0 : _a2.forEach(add);
|
|
397
796
|
});
|
|
398
797
|
});
|
|
399
798
|
return Array.from(ids.values());
|
|
400
799
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
800
|
+
function getProductLineUid(product) {
|
|
801
|
+
var _a;
|
|
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
|
+
);
|
|
405
805
|
}
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
|
|
806
|
+
function buildAvailabilityLineIdentity(product) {
|
|
807
|
+
return {
|
|
808
|
+
product_id: product.product_id,
|
|
809
|
+
product_variant_id: product.product_variant_id ?? 0,
|
|
810
|
+
unique_identification_number: getProductLineUid(product),
|
|
811
|
+
product_sku: product.product_sku,
|
|
812
|
+
product_bundle: product.product_bundle
|
|
813
|
+
};
|
|
409
814
|
}
|
|
410
|
-
function
|
|
411
|
-
return
|
|
815
|
+
function buildBookingFromAvailabilityAssignment(params) {
|
|
816
|
+
return buildBookingFromAvailabilityAssignments({
|
|
817
|
+
product: params.product,
|
|
412
818
|
assignments: [params.assignment],
|
|
819
|
+
timezone: params.timezone,
|
|
820
|
+
fixedOffsetMinutes: params.fixedOffsetMinutes
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
function buildBookingFromAvailabilityAssignments(params) {
|
|
824
|
+
const normalizedAssignments = (params.assignments || []).map((assignment) => {
|
|
825
|
+
const resourceId = assignment.resourceId ?? assignment.resource_id;
|
|
826
|
+
const matchedGroup = (params.product.requirementGroups || []).find((group) => {
|
|
827
|
+
var _a;
|
|
828
|
+
return (_a = group.resourceIds) == null ? void 0 : _a.some((id) => sameAvailabilityId(id, resourceId));
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
productId: assignment.productId ?? assignment.product_id,
|
|
832
|
+
resourceId,
|
|
833
|
+
formId: assignment.formId ?? assignment.form_id ?? (matchedGroup == null ? void 0 : matchedGroup.formId),
|
|
834
|
+
startAt: assignment.startAt ?? assignment.start_at,
|
|
835
|
+
endAt: assignment.endAt ?? assignment.end_at,
|
|
836
|
+
capacityRequired: assignment.capacityRequired ?? assignment.capacity_required ?? (matchedGroup == null ? void 0 : matchedGroup.capacityRequired),
|
|
837
|
+
scheduleId: assignment.scheduleId ?? assignment.schedule_id,
|
|
838
|
+
timeSlotId: assignment.timeSlotId ?? assignment.time_slot_id
|
|
839
|
+
};
|
|
840
|
+
});
|
|
841
|
+
const first = normalizedAssignments[0];
|
|
842
|
+
const fixedOffsetMinutes = params.fixedOffsetMinutes ?? (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
843
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
844
|
+
startAt: first.startAt,
|
|
845
|
+
endAt: first.endAt,
|
|
846
|
+
fixedOffsetMinutes,
|
|
847
|
+
rollOverEnd: false
|
|
848
|
+
});
|
|
849
|
+
if (!range) {
|
|
850
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "assignment 时间范围非法", {
|
|
851
|
+
assignments: params.assignments
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
const candidate = {
|
|
855
|
+
key: `${String(first.productId)}:${range.startAt}:${range.endAt}`,
|
|
856
|
+
productId: first.productId,
|
|
857
|
+
source: "duration_window",
|
|
858
|
+
startAt: range.startAt,
|
|
859
|
+
endAt: range.endAt,
|
|
860
|
+
bookingStartAt: range.startAt,
|
|
861
|
+
bookingEndAt: range.endAt,
|
|
862
|
+
date: range.startAt.slice(0, 10),
|
|
863
|
+
startTime: (0, import_localClock.formatLocalTime)(range.startAt, false),
|
|
864
|
+
endTime: (0, import_localClock.formatLocalTime)(range.endAt, false),
|
|
865
|
+
timezone: String(params.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || "runtime"),
|
|
866
|
+
scheduleRefs: [],
|
|
867
|
+
primaryScheduleRef: void 0
|
|
868
|
+
};
|
|
869
|
+
return buildBookingFromAvailabilitySelection({
|
|
870
|
+
assignments: normalizedAssignments,
|
|
871
|
+
candidate,
|
|
413
872
|
product: params.product
|
|
414
873
|
});
|
|
415
874
|
}
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
875
|
+
function getBookingUid(booking) {
|
|
876
|
+
var _a;
|
|
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
|
+
);
|
|
880
|
+
}
|
|
881
|
+
function stableValue(value) {
|
|
882
|
+
if (Array.isArray(value))
|
|
883
|
+
return value.map(stableValue);
|
|
884
|
+
if (!value || typeof value !== "object")
|
|
885
|
+
return value;
|
|
886
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
887
|
+
if (value[key] !== void 0 && typeof value[key] !== "function") {
|
|
888
|
+
result[key] = stableValue(value[key]);
|
|
889
|
+
}
|
|
890
|
+
return result;
|
|
891
|
+
}, {});
|
|
892
|
+
}
|
|
893
|
+
function hashText(value) {
|
|
894
|
+
let hash = 2166136261;
|
|
895
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
896
|
+
hash ^= value.charCodeAt(index);
|
|
897
|
+
hash = Math.imul(hash, 16777619);
|
|
898
|
+
}
|
|
899
|
+
return (hash >>> 0).toString(36);
|
|
900
|
+
}
|
|
901
|
+
function resolveOrderRevision(tempOrder) {
|
|
902
|
+
if (!tempOrder)
|
|
903
|
+
return "order:none";
|
|
904
|
+
const products = (Array.isArray(tempOrder.products) ? tempOrder.products : []).map((product) => {
|
|
905
|
+
var _a;
|
|
906
|
+
return {
|
|
907
|
+
uid: getProductLineUid(product),
|
|
908
|
+
productId: product.product_id ?? product.id,
|
|
909
|
+
variantId: product.product_variant_id ?? 0,
|
|
910
|
+
num: product.num ?? product.quantity ?? 1,
|
|
911
|
+
bookingUid: product.booking_uid ?? ((_a = product.metadata) == null ? void 0 : _a.booking_uid),
|
|
912
|
+
productSku: product.product_sku,
|
|
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) => ({
|
|
917
|
+
uid: getBookingUid(booking),
|
|
918
|
+
scheduleEventId: booking.schedule_event_id,
|
|
919
|
+
productUid: booking.product_uid,
|
|
920
|
+
scheduleId: booking.schedule_id,
|
|
921
|
+
startDate: booking.start_date,
|
|
922
|
+
startTime: booking.start_time,
|
|
923
|
+
endDate: booking.end_date,
|
|
924
|
+
endTime: booking.end_time,
|
|
925
|
+
resources: booking.resources
|
|
926
|
+
})).sort((left, right) => left.uid.localeCompare(right.uid));
|
|
927
|
+
return `order:${hashText(JSON.stringify(stableValue({ products, bookings })))}`;
|
|
928
|
+
}
|
|
929
|
+
function buildBookingFromAvailabilitySelection(params) {
|
|
930
|
+
var _a, _b, _c, _d;
|
|
931
|
+
const {
|
|
932
|
+
assignments,
|
|
933
|
+
candidate,
|
|
934
|
+
product,
|
|
935
|
+
resources = [],
|
|
936
|
+
bookingUid
|
|
937
|
+
} = params;
|
|
938
|
+
const bookingStartAt = candidate.bookingStartAt || candidate.startAt;
|
|
939
|
+
const bookingEndAt = candidate.bookingEndAt || candidate.endAt;
|
|
940
|
+
const startScalar = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(bookingStartAt));
|
|
941
|
+
const endScalar = (0, import_localClock.localDateTimeToScalar)((0, import_localClock.parseLocalDateTime)(bookingEndAt));
|
|
942
|
+
const scheduleId = ((_a = candidate.primaryScheduleRef) == null ? void 0 : _a.scheduleId) ?? ((_b = assignments.find((assignment) => assignment.scheduleId != null)) == null ? void 0 : _b.scheduleId);
|
|
943
|
+
const timeSlotId = ((_c = candidate.primaryScheduleRef) == null ? void 0 : _c.timeSlotId) ?? ((_d = assignments.find((assignment) => assignment.timeSlotId != null)) == null ? void 0 : _d.timeSlotId);
|
|
944
|
+
if (product.kind === "session" && scheduleId == null) {
|
|
945
|
+
throw new import_ResourcePlanner.AvailabilityError("REVALIDATION_FAILED", "session 候选缺少真实 schedule identity", {
|
|
946
|
+
productId: product.id,
|
|
947
|
+
candidateKey: candidate.key,
|
|
948
|
+
scheduleRefs: candidate.scheduleRefs
|
|
949
|
+
});
|
|
950
|
+
}
|
|
421
951
|
return {
|
|
422
|
-
start_date:
|
|
423
|
-
start_time:
|
|
424
|
-
end_date:
|
|
425
|
-
end_time:
|
|
426
|
-
duration: Math.max(1,
|
|
952
|
+
start_date: bookingStartAt.slice(0, 10),
|
|
953
|
+
start_time: bookingStartAt.slice(11, 16),
|
|
954
|
+
end_date: bookingEndAt.slice(0, 10),
|
|
955
|
+
end_time: bookingEndAt.slice(11, 16),
|
|
956
|
+
duration: Math.max(1, Math.trunc((endScalar - startScalar) / (60 * 1e3))),
|
|
427
957
|
like_status: "common",
|
|
428
|
-
schedule_id: 0,
|
|
958
|
+
schedule_id: scheduleId ?? 0,
|
|
429
959
|
resources: assignments.map((assignment) => {
|
|
430
|
-
const
|
|
960
|
+
const matchedResource = resources.find((resource) => sameAvailabilityId(resource.id, assignment.resourceId));
|
|
961
|
+
const resourceName = String((matchedResource == null ? void 0 : matchedResource.name) || "").trim();
|
|
431
962
|
return {
|
|
432
963
|
id: assignment.resourceId,
|
|
433
964
|
resource_id: assignment.resourceId,
|
|
434
|
-
form_id:
|
|
965
|
+
form_id: assignment.formId,
|
|
435
966
|
relation_type: "form",
|
|
436
967
|
relation_id: assignment.resourceId,
|
|
437
968
|
like_status: "common",
|
|
438
|
-
capacity: assignment.capacityRequired ||
|
|
969
|
+
capacity: assignment.capacityRequired || 1,
|
|
970
|
+
...resourceName ? { metadata: { resource_name: resourceName } } : {}
|
|
439
971
|
};
|
|
440
972
|
}),
|
|
441
973
|
metadata: {
|
|
974
|
+
...bookingUid ? { unique_identification_number: bookingUid } : {},
|
|
442
975
|
unified_booking_sales: true,
|
|
443
|
-
|
|
976
|
+
availability_candidate_key: candidate.key,
|
|
977
|
+
availability_source: candidate.source,
|
|
978
|
+
availability_slice_start_at: candidate.startAt,
|
|
979
|
+
availability_slice_end_at: candidate.endAt,
|
|
980
|
+
availability_schedule_refs: candidate.scheduleRefs,
|
|
981
|
+
...timeSlotId == null ? {} : { time_slot_id: timeSlotId },
|
|
982
|
+
product_kind: product.kind
|
|
444
983
|
}
|
|
445
984
|
};
|
|
446
985
|
}
|
|
447
|
-
function buildAssignmentGroupKey(assignment) {
|
|
448
|
-
return [
|
|
449
|
-
assignment.productId,
|
|
450
|
-
assignment.start_at,
|
|
451
|
-
assignment.end_at
|
|
452
|
-
].join(":");
|
|
453
|
-
}
|
|
454
|
-
function buildPlannerLineIdentity(product) {
|
|
455
|
-
var _a;
|
|
456
|
-
return {
|
|
457
|
-
product_id: product.product_id,
|
|
458
|
-
product_variant_id: product.product_variant_id ?? 0,
|
|
459
|
-
unique_identification_number: ((_a = product.metadata) == null ? void 0 : _a.unique_identification_number) || product.unique_identification_number,
|
|
460
|
-
product_sku: product.product_sku,
|
|
461
|
-
product_bundle: product.product_bundle
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
986
|
var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
465
987
|
constructor() {
|
|
466
988
|
super(...arguments);
|
|
@@ -471,12 +993,334 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
471
993
|
this.openDataTarget = null;
|
|
472
994
|
this.loadOpenDataInFlight = null;
|
|
473
995
|
this.loadOpenDataInFlightTarget = null;
|
|
996
|
+
this.availabilityRuntimeOffsetMinutes = null;
|
|
997
|
+
this.availabilityScheduleCatalog = null;
|
|
998
|
+
this.availabilityProjectionSequence = 0;
|
|
999
|
+
this.availabilityContextSequence = 0;
|
|
1000
|
+
this.availabilityLifecycleGeneration = 0;
|
|
1001
|
+
this.availabilityContextRegistry = /* @__PURE__ */ new Map();
|
|
1002
|
+
this.availabilityProjectionPool = /* @__PURE__ */ new Map();
|
|
1003
|
+
this.availabilityPrepareInFlight = /* @__PURE__ */ new Map();
|
|
1004
|
+
this.availabilityProjectionRefreshInFlight = /* @__PURE__ */ new Map();
|
|
1005
|
+
this.availabilityRemoteOccupancyCache = new import_remoteOccupancyCache.AvailabilityRemoteOccupancyCache(
|
|
1006
|
+
MAX_REMOTE_OCCUPANCY_RESOURCE_DATE_CELLS
|
|
1007
|
+
);
|
|
1008
|
+
this.availabilityRemoteResourceQueryInFlight = /* @__PURE__ */ new Map();
|
|
1009
|
+
this.availabilityRemoteResourceQuerySequence = 0;
|
|
1010
|
+
this.availabilityRemoteResourceEpoch = 0;
|
|
1011
|
+
this.availabilityCommitLocks = /* @__PURE__ */ new Set();
|
|
1012
|
+
}
|
|
1013
|
+
async initialize(core, options = {}) {
|
|
1014
|
+
var _a, _b, _c, _d;
|
|
1015
|
+
this.availabilityScheduleCatalog = null;
|
|
1016
|
+
this.openDataTarget = null;
|
|
1017
|
+
this.availabilityRuntimeOffsetMinutes = (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
1018
|
+
this.clearAvailabilityRemoteResourceState();
|
|
1019
|
+
await super.initialize(core, options);
|
|
1020
|
+
const openDataBusinessCode = String(
|
|
1021
|
+
((_a = this.otherParams) == null ? void 0 : _a.businessCode) ?? ((_b = this.otherParams) == null ? void 0 : _b.business_code) ?? ""
|
|
1022
|
+
).trim();
|
|
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) {
|
|
1025
|
+
this.openDataTarget = `${openDataBusinessCode}+${openDataChannel}`;
|
|
1026
|
+
}
|
|
1027
|
+
this.captureAvailabilityScheduleCatalog();
|
|
474
1028
|
}
|
|
475
1029
|
getSubmitOrderSalesChannel() {
|
|
476
1030
|
var _a;
|
|
477
1031
|
const channel = String(((_a = this.otherParams) == null ? void 0 : _a.channel) ?? "").trim();
|
|
478
1032
|
return channel || super.getSubmitOrderSalesChannel();
|
|
479
1033
|
}
|
|
1034
|
+
createAvailabilityFacadeError(code, message, details) {
|
|
1035
|
+
return new import_ResourcePlanner.AvailabilityError(code, message, details);
|
|
1036
|
+
}
|
|
1037
|
+
getAvailabilityScopeDescriptor(otherParams = this.otherParams || {}) {
|
|
1038
|
+
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 ?? "")
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
normalizeAvailabilityIdList(values) {
|
|
1047
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
1048
|
+
return void 0;
|
|
1049
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
1050
|
+
values.forEach((value) => {
|
|
1051
|
+
uniqueMap.set(String(value), value);
|
|
1052
|
+
});
|
|
1053
|
+
return Array.from(uniqueMap.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([, value]) => value);
|
|
1054
|
+
}
|
|
1055
|
+
normalizeAvailabilityStringList(values) {
|
|
1056
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
1057
|
+
return void 0;
|
|
1058
|
+
return Array.from(new Set(values.map(String))).sort((left, right) => left.localeCompare(right));
|
|
1059
|
+
}
|
|
1060
|
+
normalizeAvailabilityPrepareDescriptor(params) {
|
|
1061
|
+
var _a;
|
|
1062
|
+
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
|
+
);
|
|
1066
|
+
assertValidAvailabilityTimezone(timezone);
|
|
1067
|
+
const dateRange = this.normalizeAvailabilityDateRange(params, timezone);
|
|
1068
|
+
const slotStepMinutes = params.slotStepMinutes || DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES;
|
|
1069
|
+
const businessHours = params.businessHours || DEFAULT_AVAILABILITY_BUSINESS_HOURS;
|
|
1070
|
+
const mode = params.mode || "appointment";
|
|
1071
|
+
if (!isValidClockTime(businessHours.startTime) || !isValidClockTime(businessHours.endTime)) {
|
|
1072
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "businessHours 必须使用 HH:mm", {
|
|
1073
|
+
businessHours
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return this.cloneAvailabilityPrepareParams({
|
|
1077
|
+
...params,
|
|
1078
|
+
productIds: this.normalizeAvailabilityIdList(params.productIds),
|
|
1079
|
+
resourceIds: this.normalizeAvailabilityIdList(params.resourceIds),
|
|
1080
|
+
editingProductLineUids: this.normalizeAvailabilityStringList(params.editingProductLineUids),
|
|
1081
|
+
dateRange,
|
|
1082
|
+
timezone,
|
|
1083
|
+
slotStepMinutes,
|
|
1084
|
+
businessHours: { ...businessHours },
|
|
1085
|
+
_availabilityOperatingHours: params._availabilityOperatingHours ? {
|
|
1086
|
+
source: params._availabilityOperatingHours.source,
|
|
1087
|
+
scheduleIds: [...params._availabilityOperatingHours.scheduleIds]
|
|
1088
|
+
} : void 0,
|
|
1089
|
+
mode
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
async resolveAvailabilityPrepareDefaults(params) {
|
|
1093
|
+
var _a, _b, _c, _d;
|
|
1094
|
+
const needsOperatingHours = params.businessHours === void 0;
|
|
1095
|
+
const needsTimeSlice = params.slotStepMinutes === void 0;
|
|
1096
|
+
if (!needsOperatingHours && !needsTimeSlice) {
|
|
1097
|
+
return {
|
|
1098
|
+
...params,
|
|
1099
|
+
_availabilityOperatingHours: {
|
|
1100
|
+
source: "prepare_business_hours",
|
|
1101
|
+
scheduleIds: []
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
const businessCode = String(
|
|
1106
|
+
((_a = this.otherParams) == null ? void 0 : _a.businessCode) ?? ((_b = this.otherParams) == null ? void 0 : _b.business_code) ?? ""
|
|
1107
|
+
).trim();
|
|
1108
|
+
const channel = String(((_c = this.otherParams) == null ? void 0 : _c.channel) ?? "").trim();
|
|
1109
|
+
const currentTarget = businessCode && channel ? `${businessCode}+${channel}` : null;
|
|
1110
|
+
let openData = !currentTarget || this.openDataTarget === currentTarget ? (_d = this.otherParams) == null ? void 0 : _d.openData : null;
|
|
1111
|
+
if (!openData && this.store.openData) {
|
|
1112
|
+
try {
|
|
1113
|
+
openData = await this.getOpenData();
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
if (needsOperatingHours) {
|
|
1116
|
+
throw this.createAvailabilityFacadeError(
|
|
1117
|
+
"OPERATING_HOURS_RESOLVE_FAILED",
|
|
1118
|
+
"无法读取店铺营业时间配置,请重试",
|
|
1119
|
+
{
|
|
1120
|
+
businessCode,
|
|
1121
|
+
channel,
|
|
1122
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
console.warn("[UnifiedBookingSales] OpenData 时间切片配置加载失败,回退为默认值", error);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
const operatingScheduleIds = this.normalizeAvailabilityIdList(
|
|
1130
|
+
Array.isArray(openData == null ? void 0 : openData["availability.operating_hours"]) ? openData == null ? void 0 : openData["availability.operating_hours"] : void 0
|
|
1131
|
+
) || [];
|
|
1132
|
+
const configuredTimeSlice = needsTimeSlice ? normalizePositiveInteger(openData == null ? void 0 : openData["reservation.time_slice"]) : void 0;
|
|
1133
|
+
return {
|
|
1134
|
+
...params,
|
|
1135
|
+
...configuredTimeSlice ? { slotStepMinutes: configuredTimeSlice } : {},
|
|
1136
|
+
_availabilityOperatingHours: needsOperatingHours ? {
|
|
1137
|
+
source: operatingScheduleIds.length > 0 ? "open_data_schedule" : "default_business_hours",
|
|
1138
|
+
scheduleIds: operatingScheduleIds
|
|
1139
|
+
} : {
|
|
1140
|
+
source: "prepare_business_hours",
|
|
1141
|
+
scheduleIds: []
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
buildAvailabilityPrepareKey(params) {
|
|
1146
|
+
const normalizedParams = this.normalizeAvailabilityPrepareDescriptor(params);
|
|
1147
|
+
return JSON.stringify(stableValue({
|
|
1148
|
+
scope: this.getAvailabilityScopeDescriptor(),
|
|
1149
|
+
prepare: normalizedParams
|
|
1150
|
+
}));
|
|
1151
|
+
}
|
|
1152
|
+
clearAvailabilityRemoteResourceState() {
|
|
1153
|
+
this.availabilityRemoteResourceEpoch += 1;
|
|
1154
|
+
this.availabilityRemoteOccupancyCache.clear();
|
|
1155
|
+
this.availabilityRemoteResourceQueryInFlight.clear();
|
|
1156
|
+
this.availabilityRemoteResourceQuerySequence = 0;
|
|
1157
|
+
}
|
|
1158
|
+
buildAvailabilityRemoteResourceScopeKey(timezoneName) {
|
|
1159
|
+
return JSON.stringify(stableValue({
|
|
1160
|
+
scope: this.getAvailabilityScopeDescriptor(),
|
|
1161
|
+
timezone: timezoneName
|
|
1162
|
+
}));
|
|
1163
|
+
}
|
|
1164
|
+
buildAvailabilityRemoteResourceQueryKey(params) {
|
|
1165
|
+
return JSON.stringify(stableValue({
|
|
1166
|
+
scope: this.getAvailabilityScopeDescriptor(),
|
|
1167
|
+
timezone: params.timezone,
|
|
1168
|
+
dateRange: params.dateRange,
|
|
1169
|
+
resourceIds: this.normalizeAvailabilityIdList(params.resourceIds) || []
|
|
1170
|
+
}));
|
|
1171
|
+
}
|
|
1172
|
+
async setOtherParams(params, { cover = false } = {}) {
|
|
1173
|
+
var _a;
|
|
1174
|
+
const previousScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1175
|
+
const explicitlySetsOpenData = Object.prototype.hasOwnProperty.call(params, "openData");
|
|
1176
|
+
await super.setOtherParams(params, { cover });
|
|
1177
|
+
const nextScope = this.getAvailabilityScopeDescriptor(this.otherParams || {});
|
|
1178
|
+
if (explicitlySetsOpenData) {
|
|
1179
|
+
this.openDataTarget = ((_a = this.otherParams) == null ? void 0 : _a.openData) && nextScope.businessCode && nextScope.channel ? `${nextScope.businessCode}+${nextScope.channel}` : null;
|
|
1180
|
+
}
|
|
1181
|
+
const shouldResetAvailabilityState = previousScope.cacheId !== nextScope.cacheId || previousScope.businessCode !== nextScope.businessCode || previousScope.channel !== nextScope.channel || previousScope.platform !== nextScope.platform || previousScope.businessTimezone !== nextScope.businessTimezone;
|
|
1182
|
+
if (shouldResetAvailabilityState) {
|
|
1183
|
+
await this.resetAvailabilityProjectionState("set_other_params_reset");
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async destroy() {
|
|
1187
|
+
this.availabilityScheduleCatalog = null;
|
|
1188
|
+
this.availabilityRuntimeOffsetMinutes = null;
|
|
1189
|
+
await this.resetAvailabilityProjectionState("destroy");
|
|
1190
|
+
await super.destroy();
|
|
1191
|
+
}
|
|
1192
|
+
createAvailabilityContextId() {
|
|
1193
|
+
this.availabilityContextSequence += 1;
|
|
1194
|
+
return `${this.name}:availability:${Date.now()}:${this.availabilityContextSequence}`;
|
|
1195
|
+
}
|
|
1196
|
+
buildAvailabilityProjectionKey(contextId, version) {
|
|
1197
|
+
return `${contextId}:${version}`;
|
|
1198
|
+
}
|
|
1199
|
+
getAvailabilityRuntimeOffsetMinutes() {
|
|
1200
|
+
if (this.availabilityRuntimeOffsetMinutes === null) {
|
|
1201
|
+
this.availabilityRuntimeOffsetMinutes = (0, import_localClock.captureRuntimeOffsetMinutes)();
|
|
1202
|
+
}
|
|
1203
|
+
return this.availabilityRuntimeOffsetMinutes;
|
|
1204
|
+
}
|
|
1205
|
+
getAvailabilityRuntimeDateTime(value = /* @__PURE__ */ new Date()) {
|
|
1206
|
+
return (0, import_localClock.formatLocalDateTime)((0, import_localClock.localDateToScalar)(value), this.getAvailabilityRuntimeOffsetMinutes());
|
|
1207
|
+
}
|
|
1208
|
+
cloneAvailabilityPrepareParams(params) {
|
|
1209
|
+
var _a, _b;
|
|
1210
|
+
return (0, import_lodash_es.cloneDeep)({
|
|
1211
|
+
...params,
|
|
1212
|
+
productIds: params.productIds ? [...params.productIds] : void 0,
|
|
1213
|
+
resourceIds: params.resourceIds ? [...params.resourceIds] : void 0,
|
|
1214
|
+
rawProducts: params.rawProducts ? (0, import_lodash_es.cloneDeep)(params.rawProducts) : void 0,
|
|
1215
|
+
rawResources: params.rawResources ? (0, import_lodash_es.cloneDeep)(params.rawResources) : void 0,
|
|
1216
|
+
rawOccupancies: params.rawOccupancies ? (0, import_lodash_es.cloneDeep)(params.rawOccupancies) : void 0,
|
|
1217
|
+
editingProductLineUids: params.editingProductLineUids ? [...params.editingProductLineUids] : void 0,
|
|
1218
|
+
dateRange: params.dateRange ? { ...params.dateRange } : void 0,
|
|
1219
|
+
businessHours: params.businessHours ? { ...params.businessHours } : void 0,
|
|
1220
|
+
_availabilityOperatingHours: params._availabilityOperatingHours ? {
|
|
1221
|
+
source: params._availabilityOperatingHours.source,
|
|
1222
|
+
scheduleIds: [...params._availabilityOperatingHours.scheduleIds]
|
|
1223
|
+
} : void 0,
|
|
1224
|
+
catalogScope: ((_a = params.catalogScope) == null ? void 0 : _a.type) === "schedule" ? { ...params.catalogScope } : ((_b = params.catalogScope) == null ? void 0 : _b.type) === "productList" ? { type: "productList", params: { ...params.catalogScope.params } } : void 0
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
getAvailabilityDurationCandidateAnchorAt(projection) {
|
|
1228
|
+
const value = projection == null ? void 0 : projection.meta.durationCandidateAnchorAt;
|
|
1229
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1230
|
+
}
|
|
1231
|
+
createAvailabilityContextRef(context) {
|
|
1232
|
+
return {
|
|
1233
|
+
contextId: context.contextId
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
async emitAvailabilityDiagnostic(payload) {
|
|
1237
|
+
var _a, _b, _c;
|
|
1238
|
+
await ((_c = (_b = (_a = this.core) == null ? void 0 : _a.effects) == null ? void 0 : _b.emit) == null ? void 0 : _c.call(
|
|
1239
|
+
_b,
|
|
1240
|
+
`${this.name}:${import_types.UnifiedBookingSalesHooks.onAvailabilityProjectionDiagnostic}`,
|
|
1241
|
+
{
|
|
1242
|
+
...payload,
|
|
1243
|
+
registrySize: this.availabilityContextRegistry.size,
|
|
1244
|
+
projectionPoolSize: this.availabilityProjectionPool.size
|
|
1245
|
+
}
|
|
1246
|
+
));
|
|
1247
|
+
}
|
|
1248
|
+
touchAvailabilityContext(context) {
|
|
1249
|
+
context.lastAccessedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1250
|
+
this.availabilityContextRegistry.delete(context.contextId);
|
|
1251
|
+
this.availabilityContextRegistry.set(context.contextId, context);
|
|
1252
|
+
}
|
|
1253
|
+
touchAvailabilityProjection(entry) {
|
|
1254
|
+
this.availabilityProjectionPool.delete(entry.projectionKey);
|
|
1255
|
+
this.availabilityProjectionPool.set(entry.projectionKey, entry);
|
|
1256
|
+
return entry;
|
|
1257
|
+
}
|
|
1258
|
+
removeAvailabilityProjection(projectionKey) {
|
|
1259
|
+
if (!projectionKey)
|
|
1260
|
+
return;
|
|
1261
|
+
this.availabilityProjectionPool.delete(projectionKey);
|
|
1262
|
+
}
|
|
1263
|
+
removeAvailabilityContext(contextId) {
|
|
1264
|
+
const context = this.availabilityContextRegistry.get(contextId);
|
|
1265
|
+
if (!context)
|
|
1266
|
+
return;
|
|
1267
|
+
this.removeAvailabilityProjection(context.activeProjectionKey);
|
|
1268
|
+
this.availabilityProjectionRefreshInFlight.delete(contextId);
|
|
1269
|
+
this.availabilityCommitLocks.delete(contextId);
|
|
1270
|
+
this.availabilityContextRegistry.delete(contextId);
|
|
1271
|
+
}
|
|
1272
|
+
evictAvailabilityProjectionPool() {
|
|
1273
|
+
while (this.availabilityProjectionPool.size > MAX_AVAILABILITY_PROJECTION_POOL) {
|
|
1274
|
+
const oldestEntry = this.availabilityProjectionPool.entries().next().value;
|
|
1275
|
+
if (!oldestEntry)
|
|
1276
|
+
break;
|
|
1277
|
+
const [projectionKey, projectionEntry] = oldestEntry;
|
|
1278
|
+
this.availabilityProjectionPool.delete(projectionKey);
|
|
1279
|
+
const context = this.availabilityContextRegistry.get(projectionEntry.contextId);
|
|
1280
|
+
if ((context == null ? void 0 : context.activeProjectionKey) === projectionKey) {
|
|
1281
|
+
context.activeProjectionKey = null;
|
|
1282
|
+
context.refreshNonce += 1;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
evictAvailabilityContextRegistry() {
|
|
1287
|
+
while (this.availabilityContextRegistry.size > MAX_AVAILABILITY_CONTEXT_REGISTRY) {
|
|
1288
|
+
const oldestContext = this.availabilityContextRegistry.entries().next().value;
|
|
1289
|
+
if (!oldestContext)
|
|
1290
|
+
break;
|
|
1291
|
+
this.removeAvailabilityContext(oldestContext[0]);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
invalidateAvailabilityContextsAfterCommit() {
|
|
1295
|
+
this.availabilityLifecycleGeneration += 1;
|
|
1296
|
+
const nextOrderRevision = this.getCurrentOrderRevision();
|
|
1297
|
+
this.availabilityProjectionPool.clear();
|
|
1298
|
+
this.availabilityPrepareInFlight.clear();
|
|
1299
|
+
this.availabilityProjectionRefreshInFlight.clear();
|
|
1300
|
+
this.availabilityContextRegistry.forEach((context) => {
|
|
1301
|
+
context.activeVersion += 1;
|
|
1302
|
+
context.activeProjectionKey = null;
|
|
1303
|
+
context.lastKnownOrderRevision = nextOrderRevision;
|
|
1304
|
+
context.refreshNonce += 1;
|
|
1305
|
+
context.lifecycleGeneration = this.availabilityLifecycleGeneration;
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
async resetAvailabilityProjectionState(action) {
|
|
1309
|
+
this.availabilityLifecycleGeneration += 1;
|
|
1310
|
+
this.availabilityContextRegistry.clear();
|
|
1311
|
+
this.availabilityProjectionPool.clear();
|
|
1312
|
+
this.availabilityPrepareInFlight.clear();
|
|
1313
|
+
this.availabilityProjectionRefreshInFlight.clear();
|
|
1314
|
+
this.availabilityCommitLocks.clear();
|
|
1315
|
+
this.clearAvailabilityRemoteResourceState();
|
|
1316
|
+
await this.emitAvailabilityDiagnostic({
|
|
1317
|
+
action,
|
|
1318
|
+
contextId: null,
|
|
1319
|
+
contextVersion: null,
|
|
1320
|
+
reason: action,
|
|
1321
|
+
projection: null
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
480
1324
|
isSessionStoragePersistEnabled() {
|
|
481
1325
|
var _a;
|
|
482
1326
|
return Boolean(
|
|
@@ -545,12 +1389,9 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
545
1389
|
return 1;
|
|
546
1390
|
}
|
|
547
1391
|
getRegisteredModuleNames() {
|
|
548
|
-
return [...super.getRegisteredModuleNames(), "
|
|
1392
|
+
return [...super.getRegisteredModuleNames(), "openData"];
|
|
549
1393
|
}
|
|
550
1394
|
createSubModule(moduleName) {
|
|
551
|
-
if (moduleName === "resourcePlanner") {
|
|
552
|
-
return new import_modules.ResourcePlannerModule(`${this.name}_resourcePlanner`);
|
|
553
|
-
}
|
|
554
1395
|
if (moduleName === "openData") {
|
|
555
1396
|
return new import_modules.OpenDataModule(`${this.name}_openData`);
|
|
556
1397
|
}
|
|
@@ -896,22 +1737,16 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
896
1737
|
preferredDiscountFound: preferredDiscountId === void 0 || preferredDiscountId === null ? void 0 : Boolean(preferredDiscount)
|
|
897
1738
|
});
|
|
898
1739
|
}
|
|
899
|
-
/** ResourcePlanner is a required child module; fail early instead of producing partial availability. */
|
|
900
|
-
getPlanner() {
|
|
901
|
-
if (!this.store.resourcePlanner) {
|
|
902
|
-
throw new Error("[UnifiedBookingSales] resourcePlanner 模块未初始化");
|
|
903
|
-
}
|
|
904
|
-
return this.store.resourcePlanner;
|
|
905
|
-
}
|
|
906
1740
|
buildUnifiedOrderProduct(sourceProduct, ownershipKey, product) {
|
|
1741
|
+
const normalizedProductRaw = (product == null ? void 0 : product.raw) || {};
|
|
907
1742
|
const orderProduct = this.transformBaseProductToOrderProduct({
|
|
908
1743
|
payload: {
|
|
909
1744
|
...sourceProduct,
|
|
910
1745
|
id: sourceProduct.id ?? (product == null ? void 0 : product.id),
|
|
911
1746
|
product_id: sourceProduct.product_id ?? sourceProduct.id ?? (product == null ? void 0 : product.id),
|
|
912
1747
|
quantity: sourceProduct.quantity ?? sourceProduct.num ?? (product == null ? void 0 : product.quantity) ?? 1,
|
|
913
|
-
price: sourceProduct.price ??
|
|
914
|
-
selling_price: sourceProduct.selling_price ?? sourceProduct.price ??
|
|
1748
|
+
price: sourceProduct.price ?? normalizedProductRaw.price,
|
|
1749
|
+
selling_price: sourceProduct.selling_price ?? sourceProduct.price ?? normalizedProductRaw.selling_price ?? normalizedProductRaw.price,
|
|
915
1750
|
metadata: {
|
|
916
1751
|
...sourceProduct.metadata || {},
|
|
917
1752
|
[ownershipKey]: true
|
|
@@ -935,7 +1770,7 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
935
1770
|
return ((_a2 = product == null ? void 0 : product.metadata) == null ? void 0 : _a2[ownershipKey]) === true;
|
|
936
1771
|
});
|
|
937
1772
|
if (existingLines.length > 0) {
|
|
938
|
-
await this.removeProductsFromOrder(existingLines.map(
|
|
1773
|
+
await this.removeProductsFromOrder(existingLines.map(buildAvailabilityLineIdentity));
|
|
939
1774
|
}
|
|
940
1775
|
}
|
|
941
1776
|
async stageProducts(params, ownershipKey) {
|
|
@@ -966,15 +1801,8 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
966
1801
|
return this.getTempOrder();
|
|
967
1802
|
}
|
|
968
1803
|
/**
|
|
969
|
-
* Adds
|
|
970
|
-
*
|
|
971
|
-
*/
|
|
972
|
-
async stagePlannerProducts(params) {
|
|
973
|
-
return this.stageProducts(params, "unified_booking_sales");
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Adds ordinary retail products without asking ResourcePlanner for booking facts.
|
|
977
|
-
* Its staged lines have separate ownership so planner and retail skins can coexist safely.
|
|
1804
|
+
* Adds ordinary retail products without asking ResourcePlanner for booking facts.
|
|
1805
|
+
* Its staged lines have separate ownership so planner and retail skins can coexist safely.
|
|
978
1806
|
*/
|
|
979
1807
|
async stageRetailProducts(params) {
|
|
980
1808
|
return this.stageProducts(params, "unified_booking_sales_retail");
|
|
@@ -1021,57 +1849,200 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1021
1849
|
}
|
|
1022
1850
|
return this.addProductToOrder(orderProduct);
|
|
1023
1851
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1852
|
+
normalizeAvailabilityDateRange(params, timezone) {
|
|
1853
|
+
var _a, _b;
|
|
1854
|
+
const today = (0, import_localClock.formatLocalDate)((0, import_localClock.localDateToScalar)(/* @__PURE__ */ new Date()));
|
|
1855
|
+
const startDate = ((_a = params.dateRange) == null ? void 0 : _a.startDate) || today;
|
|
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));
|
|
1857
|
+
let startScalar;
|
|
1858
|
+
let endScalar;
|
|
1859
|
+
try {
|
|
1860
|
+
startScalar = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(startDate));
|
|
1861
|
+
endScalar = (0, import_localClock.localDateToScalarStart)((0, import_localClock.parseLocalDate)(endDate));
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_DATE_RANGE", "日期范围必须是有效的 YYYY-MM-DD 且结束日期不早于开始日期", {
|
|
1864
|
+
dateRange: { startDate, endDate },
|
|
1865
|
+
timezone,
|
|
1866
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1037
1867
|
});
|
|
1038
1868
|
}
|
|
1039
|
-
|
|
1869
|
+
if (endScalar < startScalar) {
|
|
1870
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_DATE_RANGE", "日期范围必须是有效的 YYYY-MM-DD 且结束日期不早于开始日期", {
|
|
1871
|
+
dateRange: { startDate, endDate },
|
|
1872
|
+
timezone
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
const inclusiveDays = Math.trunc((endScalar - startScalar) / (24 * 60 * 60 * 1e3)) + 1;
|
|
1876
|
+
if (inclusiveDays > MAX_AVAILABILITY_RANGE_DAYS) {
|
|
1877
|
+
throw new import_ResourcePlanner.AvailabilityError("RANGE_TOO_LARGE", "日期范围最多包含 90 个自然日", {
|
|
1878
|
+
dateRange: { startDate, endDate },
|
|
1879
|
+
inclusiveDays
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
return { startDate, endDate };
|
|
1040
1883
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
const
|
|
1047
|
-
if (!
|
|
1048
|
-
|
|
1884
|
+
assertAvailabilityPrepareParams(params) {
|
|
1885
|
+
var _a, _b;
|
|
1886
|
+
assertAvailabilityCatalogProductIds(params.productIds, "prepare.productIds");
|
|
1887
|
+
const hasProductAnchor = Array.isArray(params.productIds) && params.productIds.length > 0;
|
|
1888
|
+
const hasResourceAnchor = Array.isArray(params.resourceIds) && params.resourceIds.length > 0;
|
|
1889
|
+
const hasDateAnchor = Boolean(((_a = params.dateRange) == null ? void 0 : _a.startDate) || ((_b = params.dateRange) == null ? void 0 : _b.endDate));
|
|
1890
|
+
if (!hasProductAnchor && !hasResourceAnchor && !hasDateAnchor) {
|
|
1891
|
+
throw new import_ResourcePlanner.AvailabilityError("MISSING_ANCHOR", "prepare 至少需要 productIds、resourceIds 或 dateRange 之一", {
|
|
1892
|
+
params
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
if (!hasProductAnchor && !params.catalogScope) {
|
|
1896
|
+
throw new import_ResourcePlanner.AvailabilityError("MISSING_CATALOG_SCOPE", "缺少 productIds 时必须提供 catalogScope", {
|
|
1897
|
+
params
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
if (params.slotStepMinutes !== void 0) {
|
|
1901
|
+
const step = Number(params.slotStepMinutes);
|
|
1902
|
+
if (!Number.isInteger(step) || step <= 0) {
|
|
1903
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "slotStepMinutes 必须是正整数", {
|
|
1904
|
+
slotStepMinutes: params.slotStepMinutes
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1049
1907
|
}
|
|
1050
|
-
return ((_f = (_e = this.store.schedule).getScheduleList) == null ? void 0 : _f.call(_e)) || [];
|
|
1051
1908
|
}
|
|
1052
|
-
|
|
1053
|
-
* v2 resources reference schedule ids rather than expanded work times. Refresh once when the
|
|
1054
|
-
* cached schedule catalog cannot materialize all referenced ids.
|
|
1055
|
-
*/
|
|
1056
|
-
async ensurePlannerSchedulesForResources(rawResources) {
|
|
1909
|
+
captureAvailabilityScheduleCatalog() {
|
|
1057
1910
|
var _a, _b;
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1911
|
+
if (!this.store.schedule) {
|
|
1912
|
+
throw new Error("[UnifiedBookingSales] schedule 模块未初始化");
|
|
1913
|
+
}
|
|
1914
|
+
this.availabilityScheduleCatalog = (0, import_lodash_es.cloneDeep)(
|
|
1915
|
+
((_b = (_a = this.store.schedule).getScheduleList) == null ? void 0 : _b.call(_a)) || []
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
/** Returns the solution-lifetime schedule snapshot captured after initialization. */
|
|
1919
|
+
getAvailabilityScheduleCatalog() {
|
|
1920
|
+
if (this.availabilityScheduleCatalog === null) {
|
|
1921
|
+
throw new Error("[UnifiedBookingSales] schedule 快照未初始化");
|
|
1063
1922
|
}
|
|
1064
|
-
return
|
|
1923
|
+
return this.availabilityScheduleCatalog;
|
|
1924
|
+
}
|
|
1925
|
+
buildAvailabilityOperatingHoursSnapshot(params) {
|
|
1926
|
+
const descriptor = params.prepare._availabilityOperatingHours || {
|
|
1927
|
+
source: params.prepare.businessHours ? "prepare_business_hours" : "default_business_hours",
|
|
1928
|
+
scheduleIds: []
|
|
1929
|
+
};
|
|
1930
|
+
if (descriptor.source !== "open_data_schedule") {
|
|
1931
|
+
const windows2 = dateRangeDays(params.dateRange).flatMap((date) => {
|
|
1932
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
1933
|
+
startAt: `${date}T${params.businessHours.startTime}:00`,
|
|
1934
|
+
endAt: `${date}T${params.businessHours.endTime}:00`,
|
|
1935
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
1936
|
+
rollOverEnd: true
|
|
1937
|
+
});
|
|
1938
|
+
return range ? [{
|
|
1939
|
+
startAt: range.startAt,
|
|
1940
|
+
endAt: range.endAt,
|
|
1941
|
+
source: descriptor.source
|
|
1942
|
+
}] : [];
|
|
1943
|
+
});
|
|
1944
|
+
return {
|
|
1945
|
+
source: descriptor.source,
|
|
1946
|
+
scheduleIds: [],
|
|
1947
|
+
missingScheduleIds: [],
|
|
1948
|
+
windows: windows2
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
const scheduleMap = new Map(
|
|
1952
|
+
params.scheduleList.map((schedule) => [String(schedule.id), schedule])
|
|
1953
|
+
);
|
|
1954
|
+
const schedules = descriptor.scheduleIds.map((scheduleId) => scheduleMap.get(String(scheduleId))).filter((schedule) => Boolean(schedule));
|
|
1955
|
+
const matchedScheduleIds = new Set(schedules.map((schedule) => String(schedule.id)));
|
|
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) => {
|
|
1960
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
1961
|
+
startAt: slot.start_at,
|
|
1962
|
+
endAt: slot.end_at,
|
|
1963
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
1964
|
+
rollOverEnd: true
|
|
1965
|
+
});
|
|
1966
|
+
return range ? [{
|
|
1967
|
+
startAt: range.startAt,
|
|
1968
|
+
endAt: range.endAt,
|
|
1969
|
+
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(",")}`
|
|
1970
|
+
}] : [];
|
|
1971
|
+
}));
|
|
1972
|
+
return {
|
|
1973
|
+
source: descriptor.source,
|
|
1974
|
+
scheduleIds: [...descriptor.scheduleIds],
|
|
1975
|
+
missingScheduleIds,
|
|
1976
|
+
windows
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
async loadAvailabilityProducts(params) {
|
|
1980
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1981
|
+
const { prepare, dateRange } = params;
|
|
1982
|
+
const explicitProductIds = prepare.productIds || [];
|
|
1983
|
+
let rawProducts = [];
|
|
1984
|
+
if (Array.isArray(prepare.rawProducts)) {
|
|
1985
|
+
rawProducts = prepare.rawProducts;
|
|
1986
|
+
this.unifiedProductCatalog = [...rawProducts];
|
|
1987
|
+
} else if (explicitProductIds.length > 0) {
|
|
1988
|
+
rawProducts = await this.loadProducts({
|
|
1989
|
+
product_ids: explicitProductIds,
|
|
1990
|
+
schedule_date: dateRange.startDate
|
|
1991
|
+
});
|
|
1992
|
+
} else if (((_a = prepare.catalogScope) == null ? void 0 : _a.type) === "schedule") {
|
|
1993
|
+
await this.loadScheduleAvailableDate({
|
|
1994
|
+
startDate: dateRange.startDate,
|
|
1995
|
+
endDate: dateRange.endDate,
|
|
1996
|
+
custom_page_id: Number(prepare.catalogScope.customPageId) || void 0,
|
|
1997
|
+
channel: prepare.catalogScope.channel
|
|
1998
|
+
});
|
|
1999
|
+
const dateSet = new Set(dateRangeDays(dateRange));
|
|
2000
|
+
const scheduleProductIds = (((_c = (_b = this.store.schedule).getAvailabilityScheduleDateList) == null ? void 0 : _c.call(_b)) || []).filter((item) => dateSet.has(item.date)).flatMap((item) => item.product_ids || []);
|
|
2001
|
+
const productIds = uniqueAvailabilityIds([
|
|
2002
|
+
...scheduleProductIds,
|
|
2003
|
+
...((_e = (_d = this.store.schedule).getOtherProductsIds) == null ? void 0 : _e.call(_d)) || []
|
|
2004
|
+
]);
|
|
2005
|
+
if (productIds.length > 0) {
|
|
2006
|
+
assertAvailabilityCatalogProductIds(productIds, "prepare.catalogScope.scheduleProductIds");
|
|
2007
|
+
rawProducts = await this.loadProducts({
|
|
2008
|
+
product_ids: productIds,
|
|
2009
|
+
schedule_date: dateRange.startDate
|
|
2010
|
+
});
|
|
2011
|
+
} else {
|
|
2012
|
+
this.unifiedProductCatalog = [];
|
|
2013
|
+
}
|
|
2014
|
+
} else if (((_f = prepare.catalogScope) == null ? void 0 : _f.type) === "productList") {
|
|
2015
|
+
rawProducts = await this.loadProducts({
|
|
2016
|
+
...prepare.catalogScope.params,
|
|
2017
|
+
schedule_date: prepare.catalogScope.params.schedule_date || dateRange.startDate
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
const normalized = rawProducts.map((product) => normalizeProductForAvailability(
|
|
2021
|
+
product,
|
|
2022
|
+
params.scheduleList,
|
|
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
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
const productIdSet = explicitProductIds.length ? new Set(explicitProductIds.map(String)) : null;
|
|
2035
|
+
return productIdSet ? normalized.filter((product) => productIdSet.has(String(product.id))) : normalized;
|
|
1065
2036
|
}
|
|
1066
2037
|
/**
|
|
1067
2038
|
* Fetches resource metadata plus flat occupied events. Work windows are intentionally derived
|
|
1068
|
-
* locally in
|
|
2039
|
+
* locally in buildAvailabilityResourcesFromV2 from /schedule, rather than requested from the legacy
|
|
1069
2040
|
* resource dates endpoint.
|
|
1070
2041
|
*/
|
|
1071
|
-
async
|
|
2042
|
+
async fetchAvailabilityResourcesV2(params) {
|
|
1072
2043
|
const response = await this.request.get("/schedule/resource/list/v2", {
|
|
1073
|
-
start_date: params.dateRange.
|
|
1074
|
-
end_date: params.dateRange.
|
|
2044
|
+
start_date: params.dateRange.startDate,
|
|
2045
|
+
end_date: params.dateRange.endDate,
|
|
1075
2046
|
resource_ids: params.resourceIds,
|
|
1076
2047
|
front_end_cache_id: false
|
|
1077
2048
|
}, {
|
|
@@ -1084,190 +2055,1134 @@ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
|
|
|
1084
2055
|
return data.list;
|
|
1085
2056
|
return [];
|
|
1086
2057
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
2058
|
+
async fetchAndPublishAvailabilityResourcesV2(params) {
|
|
2059
|
+
const normalizedResourceIds = this.normalizeAvailabilityIdList(params.resourceIds) || [];
|
|
2060
|
+
if (normalizedResourceIds.length === 0)
|
|
2061
|
+
return [];
|
|
2062
|
+
const scopeKey = this.buildAvailabilityRemoteResourceScopeKey(params.timezone);
|
|
2063
|
+
const cacheParams = {
|
|
2064
|
+
scopeKey,
|
|
2065
|
+
resourceIds: normalizedResourceIds,
|
|
2066
|
+
dateRange: params.dateRange
|
|
2067
|
+
};
|
|
2068
|
+
const cachedRawResources = this.availabilityRemoteOccupancyCache.read(cacheParams);
|
|
2069
|
+
if (cachedRawResources) {
|
|
2070
|
+
if (params.useCacheOnHit) {
|
|
2071
|
+
void this.refreshAvailabilityRemoteResourceQuery({
|
|
2072
|
+
timezone: params.timezone,
|
|
2073
|
+
dateRange: params.dateRange,
|
|
2074
|
+
resourceIds: normalizedResourceIds
|
|
2075
|
+
}).catch((error) => {
|
|
2076
|
+
console.warn("[UnifiedBookingSales] Remote occupancy background refresh failed", error);
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
return cachedRawResources;
|
|
2080
|
+
}
|
|
2081
|
+
return await this.refreshAvailabilityRemoteResourceQuery({
|
|
2082
|
+
timezone: params.timezone,
|
|
2083
|
+
dateRange: params.dateRange,
|
|
2084
|
+
resourceIds: normalizedResourceIds
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
async refreshAvailabilityRemoteResourceQuery(params) {
|
|
2088
|
+
const normalizedResourceIds = this.normalizeAvailabilityIdList(params.resourceIds) || [];
|
|
2089
|
+
if (normalizedResourceIds.length === 0)
|
|
2090
|
+
return [];
|
|
2091
|
+
const queryKey = this.buildAvailabilityRemoteResourceQueryKey({
|
|
2092
|
+
timezone: params.timezone,
|
|
2093
|
+
dateRange: params.dateRange,
|
|
2094
|
+
resourceIds: normalizedResourceIds
|
|
2095
|
+
});
|
|
2096
|
+
const epoch = this.availabilityRemoteResourceEpoch;
|
|
2097
|
+
const existing = this.availabilityRemoteResourceQueryInFlight.get(queryKey);
|
|
2098
|
+
if ((existing == null ? void 0 : existing.epoch) === epoch) {
|
|
2099
|
+
return await existing.promise;
|
|
2100
|
+
}
|
|
2101
|
+
this.availabilityRemoteResourceQuerySequence += 1;
|
|
2102
|
+
const writeVersion = this.availabilityRemoteResourceQuerySequence;
|
|
2103
|
+
const promise = this.fetchAvailabilityResourcesV2({
|
|
2104
|
+
dateRange: params.dateRange,
|
|
2105
|
+
resourceIds: normalizedResourceIds
|
|
2106
|
+
});
|
|
2107
|
+
this.availabilityRemoteResourceQueryInFlight.set(queryKey, {
|
|
2108
|
+
epoch,
|
|
2109
|
+
promise
|
|
2110
|
+
});
|
|
2111
|
+
try {
|
|
2112
|
+
const rawResources = await promise;
|
|
2113
|
+
if (epoch === this.availabilityRemoteResourceEpoch) {
|
|
2114
|
+
this.availabilityRemoteOccupancyCache.publish({
|
|
2115
|
+
scopeKey: this.buildAvailabilityRemoteResourceScopeKey(params.timezone),
|
|
2116
|
+
timezoneName: params.timezone,
|
|
2117
|
+
resourceIds: normalizedResourceIds,
|
|
2118
|
+
dateRange: params.dateRange,
|
|
2119
|
+
rawResources,
|
|
2120
|
+
version: writeVersion
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
return rawResources;
|
|
2124
|
+
} finally {
|
|
2125
|
+
const current = this.availabilityRemoteResourceQueryInFlight.get(queryKey);
|
|
2126
|
+
if ((current == null ? void 0 : current.promise) === promise) {
|
|
2127
|
+
this.availabilityRemoteResourceQueryInFlight.delete(queryKey);
|
|
2128
|
+
}
|
|
1098
2129
|
}
|
|
1099
|
-
|
|
2130
|
+
}
|
|
2131
|
+
async loadAvailabilityResources(params) {
|
|
2132
|
+
const resourceIds = collectAvailabilityResourceIds(params.products, params.resourceIds || []);
|
|
2133
|
+
if (resourceIds.length === 0) {
|
|
1100
2134
|
return {
|
|
1101
|
-
resources:
|
|
1102
|
-
|
|
2135
|
+
resources: [],
|
|
2136
|
+
resourceOccupancies: []
|
|
1103
2137
|
};
|
|
1104
2138
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
2139
|
+
if (Array.isArray(params.rawResources)) {
|
|
2140
|
+
return buildAvailabilityResourcesFromV2({
|
|
2141
|
+
rawResources: params.rawResources,
|
|
2142
|
+
scheduleList: params.scheduleList,
|
|
2143
|
+
dateRange: params.dateRange,
|
|
2144
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes()
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
if (params.fetchPolicy === "cache_only") {
|
|
2148
|
+
const cachedRawResources = this.availabilityRemoteOccupancyCache.read({
|
|
2149
|
+
scopeKey: this.buildAvailabilityRemoteResourceScopeKey(params.timezone),
|
|
2150
|
+
resourceIds,
|
|
2151
|
+
dateRange: params.dateRange
|
|
2152
|
+
});
|
|
2153
|
+
if (cachedRawResources) {
|
|
2154
|
+
return buildAvailabilityResourcesFromV2({
|
|
2155
|
+
rawResources: cachedRawResources,
|
|
2156
|
+
scheduleList: params.scheduleList,
|
|
2157
|
+
dateRange: params.dateRange,
|
|
2158
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes()
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
return params.fallbackResources ? (0, import_lodash_es.cloneDeep)(params.fallbackResources) : {
|
|
1108
2162
|
resources: [],
|
|
1109
|
-
|
|
2163
|
+
resourceOccupancies: []
|
|
1110
2164
|
};
|
|
1111
2165
|
}
|
|
1112
|
-
const rawResources = await this.
|
|
2166
|
+
const rawResources = await this.fetchAndPublishAvailabilityResourcesV2({
|
|
2167
|
+
timezone: params.timezone,
|
|
1113
2168
|
dateRange: params.dateRange,
|
|
1114
|
-
resourceIds
|
|
2169
|
+
resourceIds,
|
|
2170
|
+
useCacheOnHit: true
|
|
1115
2171
|
});
|
|
1116
|
-
|
|
1117
|
-
const materialized = buildPlannerResourcesFromV2({
|
|
2172
|
+
return buildAvailabilityResourcesFromV2({
|
|
1118
2173
|
rawResources,
|
|
1119
|
-
scheduleList,
|
|
1120
|
-
dateRange: params.dateRange
|
|
2174
|
+
scheduleList: params.scheduleList,
|
|
2175
|
+
dateRange: params.dateRange,
|
|
2176
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes()
|
|
1121
2177
|
});
|
|
1122
|
-
return {
|
|
1123
|
-
resources: materialized.resources,
|
|
1124
|
-
externalEvents: [
|
|
1125
|
-
...((_f = params.context) == null ? void 0 : _f.externalEvents) || [],
|
|
1126
|
-
...materialized.externalEvents
|
|
1127
|
-
]
|
|
1128
|
-
};
|
|
1129
2178
|
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
2179
|
+
resolveEditingOccupancyExclusions(tempOrder, productLineUids = []) {
|
|
2180
|
+
const productUids = /* @__PURE__ */ new Set();
|
|
2181
|
+
const bookingUids = /* @__PURE__ */ new Set();
|
|
2182
|
+
const scheduleEventIds = /* @__PURE__ */ new Set();
|
|
2183
|
+
if (!productLineUids.length)
|
|
2184
|
+
return { productUids, bookingUids, scheduleEventIds };
|
|
2185
|
+
const products = Array.isArray(tempOrder == null ? void 0 : tempOrder.products) ? tempOrder.products : [];
|
|
2186
|
+
const bookings = Array.isArray(tempOrder == null ? void 0 : tempOrder.bookings) ? tempOrder.bookings : [];
|
|
2187
|
+
productLineUids.forEach((requestedUid) => {
|
|
2188
|
+
var _a;
|
|
2189
|
+
const product = products.find((line) => getProductLineUid(line) === String(requestedUid));
|
|
2190
|
+
if (!product) {
|
|
2191
|
+
throw new import_ResourcePlanner.AvailabilityError(
|
|
2192
|
+
"SELF_EXCLUSION_RESOLVE_FAILED",
|
|
2193
|
+
"无法解析编辑商品行,不能安全排除自身占用",
|
|
2194
|
+
{ productLineUid: requestedUid }
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
const productUid = getProductLineUid(product);
|
|
2198
|
+
const linkedBookingUid = String(product.booking_uid ?? ((_a = product.metadata) == null ? void 0 : _a.booking_uid) ?? "");
|
|
2199
|
+
const linkedBookings = bookings.filter((booking) => linkedBookingUid && getBookingUid(booking) === linkedBookingUid || productUid && String(booking.product_uid ?? "") === productUid);
|
|
2200
|
+
if (!productUid || !linkedBookings.length) {
|
|
2201
|
+
throw new import_ResourcePlanner.AvailabilityError(
|
|
2202
|
+
"SELF_EXCLUSION_RESOLVE_FAILED",
|
|
2203
|
+
"编辑商品行缺少可验证的 booking 关联",
|
|
2204
|
+
{ productLineUid: requestedUid, linkedBookingUid }
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
productUids.add(productUid);
|
|
2208
|
+
linkedBookings.forEach((booking) => {
|
|
2209
|
+
const bookingUid = getBookingUid(booking);
|
|
2210
|
+
if (bookingUid)
|
|
2211
|
+
bookingUids.add(bookingUid);
|
|
2212
|
+
if (booking.schedule_event_id != null) {
|
|
2213
|
+
scheduleEventIds.add(String(booking.schedule_event_id));
|
|
2214
|
+
}
|
|
2215
|
+
});
|
|
1151
2216
|
});
|
|
1152
|
-
|
|
1153
|
-
return this.getPlanner().getSnapshot();
|
|
2217
|
+
return { productUids, bookingUids, scheduleEventIds };
|
|
1154
2218
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
2219
|
+
occupancyIsExcluded(occupancy, exclusions) {
|
|
2220
|
+
if (occupancy.scheduleEventId != null) {
|
|
2221
|
+
return exclusions.scheduleEventIds.has(String(occupancy.scheduleEventId));
|
|
2222
|
+
}
|
|
2223
|
+
if (occupancy.bookingUid != null) {
|
|
2224
|
+
return exclusions.bookingUids.has(String(occupancy.bookingUid));
|
|
2225
|
+
}
|
|
2226
|
+
if (occupancy.productUid != null) {
|
|
2227
|
+
return exclusions.productUids.has(String(occupancy.productUid));
|
|
2228
|
+
}
|
|
2229
|
+
return false;
|
|
1158
2230
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
return
|
|
2231
|
+
collectCartResourceOccupancies(tempOrder, exclusions) {
|
|
2232
|
+
const bookings = Array.isArray(tempOrder == null ? void 0 : tempOrder.bookings) ? tempOrder.bookings : [];
|
|
2233
|
+
return bookings.flatMap((booking) => {
|
|
2234
|
+
var _a;
|
|
2235
|
+
const bookingUid = getBookingUid(booking);
|
|
2236
|
+
const productUid = String(booking.product_uid ?? ((_a = booking.metadata) == null ? void 0 : _a.product_uid) ?? "");
|
|
2237
|
+
const scheduleEventId = toNumberId(booking.schedule_event_id);
|
|
2238
|
+
const startAt = booking.start_at || `${booking.start_date || ""} ${booking.start_time || ""}`.trim();
|
|
2239
|
+
const endAt = booking.end_at || `${booking.end_date || booking.start_date || ""} ${booking.end_time || ""}`.trim();
|
|
2240
|
+
const resources = Array.isArray(booking.resources) ? booking.resources : [];
|
|
2241
|
+
return resources.flatMap((resource) => {
|
|
2242
|
+
const resourceId = resource.resource_id ?? resource.relation_id ?? resource.id;
|
|
2243
|
+
if (resourceId == null || !startAt || !endAt)
|
|
2244
|
+
return [];
|
|
2245
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
2246
|
+
startAt,
|
|
2247
|
+
endAt,
|
|
2248
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
2249
|
+
rollOverEnd: false
|
|
2250
|
+
});
|
|
2251
|
+
if (!range)
|
|
2252
|
+
return [];
|
|
2253
|
+
const occupancy = {
|
|
2254
|
+
resourceId,
|
|
2255
|
+
startAt: range.startAt,
|
|
2256
|
+
endAt: range.endAt,
|
|
2257
|
+
pax: Math.max(1, Number(resource.capacity ?? booking.capacity ?? 1) || 1),
|
|
2258
|
+
source: "cart",
|
|
2259
|
+
scheduleEventId,
|
|
2260
|
+
bookingUid: bookingUid || void 0,
|
|
2261
|
+
productUid: productUid || void 0,
|
|
2262
|
+
raw: { booking, resource }
|
|
2263
|
+
};
|
|
2264
|
+
return this.occupancyIsExcluded(occupancy, exclusions) ? [] : [occupancy];
|
|
2265
|
+
});
|
|
2266
|
+
});
|
|
1162
2267
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return this.getPlanner().setSelectionPatch(patch);
|
|
2268
|
+
getCurrentOrderRevision() {
|
|
2269
|
+
return resolveOrderRevision(this.getTempOrder());
|
|
1166
2270
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
2271
|
+
getAvailabilityContextEntry(contextId) {
|
|
2272
|
+
const context = this.availabilityContextRegistry.get(contextId);
|
|
2273
|
+
if (!context) {
|
|
2274
|
+
throw this.createAvailabilityFacadeError(
|
|
2275
|
+
"AVAILABILITY_CONTEXT_NOT_FOUND",
|
|
2276
|
+
"availability context 不存在或已失效,请重新 prepare",
|
|
2277
|
+
{
|
|
2278
|
+
contextId,
|
|
2279
|
+
lifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2280
|
+
}
|
|
2281
|
+
);
|
|
1175
2282
|
}
|
|
1176
|
-
|
|
2283
|
+
this.touchAvailabilityContext(context);
|
|
2284
|
+
return context;
|
|
1177
2285
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
2286
|
+
getAvailabilityProjectionByKey(projectionKey) {
|
|
2287
|
+
var _a;
|
|
2288
|
+
if (!projectionKey)
|
|
2289
|
+
return null;
|
|
2290
|
+
return ((_a = this.availabilityProjectionPool.get(projectionKey)) == null ? void 0 : _a.projection) || null;
|
|
2291
|
+
}
|
|
2292
|
+
getActiveAvailabilityProjectionEntry(context) {
|
|
2293
|
+
if (!context.activeProjectionKey)
|
|
2294
|
+
return null;
|
|
2295
|
+
const entry = this.availabilityProjectionPool.get(context.activeProjectionKey);
|
|
2296
|
+
if (!entry)
|
|
2297
|
+
return null;
|
|
2298
|
+
return this.touchAvailabilityProjection(entry);
|
|
1181
2299
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
2300
|
+
decorateAvailabilityCandidate(contextId, contextVersion, candidate) {
|
|
2301
|
+
return {
|
|
2302
|
+
...candidate,
|
|
2303
|
+
availabilityContextId: contextId,
|
|
2304
|
+
availabilityContextVersion: contextVersion
|
|
2305
|
+
};
|
|
1185
2306
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
2307
|
+
assertAvailabilityCandidateFreshness(context, candidate) {
|
|
2308
|
+
if (candidate.availabilityContextId !== context.contextId) {
|
|
2309
|
+
throw this.createAvailabilityFacadeError(
|
|
2310
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2311
|
+
"候选与 availability context 不匹配,请重新获取候选",
|
|
2312
|
+
{
|
|
2313
|
+
candidateContextId: candidate.availabilityContextId,
|
|
2314
|
+
contextId: context.contextId
|
|
2315
|
+
}
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
const currentContextVersion = context.activeVersion;
|
|
2319
|
+
if (candidate.availabilityContextVersion !== currentContextVersion) {
|
|
2320
|
+
throw this.createAvailabilityFacadeError(
|
|
2321
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2322
|
+
"候选已过期,请重新获取最新时间段",
|
|
2323
|
+
{
|
|
2324
|
+
contextId: context.contextId,
|
|
2325
|
+
candidateContextVersion: candidate.availabilityContextVersion,
|
|
2326
|
+
currentContextVersion,
|
|
2327
|
+
snapshotId: context.lastSnapshotId
|
|
2328
|
+
}
|
|
2329
|
+
);
|
|
1193
2330
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
2331
|
+
}
|
|
2332
|
+
assertContextualQueryFreshness(context, request) {
|
|
2333
|
+
if (request.target === "time") {
|
|
2334
|
+
request.candidates.forEach((candidate) => {
|
|
2335
|
+
this.assertAvailabilityCandidateFreshness(context, candidate);
|
|
2336
|
+
});
|
|
2337
|
+
return;
|
|
1196
2338
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
2339
|
+
if (request.target === "resource") {
|
|
2340
|
+
request.selectedRanges.forEach((candidate) => {
|
|
2341
|
+
this.assertAvailabilityCandidateFreshness(context, candidate);
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
storeAvailabilityProjectionEntry(params) {
|
|
2346
|
+
const previousContext = this.availabilityContextRegistry.get(params.contextId);
|
|
2347
|
+
const contextVersion = previousContext ? previousContext.activeVersion + (params.incrementVersion ? 1 : 0) : 1;
|
|
2348
|
+
const projectionKey = this.buildAvailabilityProjectionKey(params.contextId, contextVersion);
|
|
2349
|
+
const entry = {
|
|
2350
|
+
contextId: params.contextId,
|
|
2351
|
+
contextVersion,
|
|
2352
|
+
projectionKey,
|
|
2353
|
+
projection: params.projection,
|
|
2354
|
+
orderRevision: params.orderRevision,
|
|
2355
|
+
preparedAt: params.projection.meta.preparedAt,
|
|
2356
|
+
snapshotId: params.projection.meta.snapshotId
|
|
2357
|
+
};
|
|
2358
|
+
if ((previousContext == null ? void 0 : previousContext.activeProjectionKey) && previousContext.activeProjectionKey !== projectionKey) {
|
|
2359
|
+
this.removeAvailabilityProjection(previousContext.activeProjectionKey);
|
|
2360
|
+
}
|
|
2361
|
+
this.availabilityProjectionPool.set(projectionKey, entry);
|
|
2362
|
+
this.evictAvailabilityProjectionPool();
|
|
2363
|
+
const nextContext = {
|
|
2364
|
+
contextId: params.contextId,
|
|
2365
|
+
prepareParams: this.cloneAvailabilityPrepareParams(params.prepareParams),
|
|
2366
|
+
prepareKey: params.prepareKey,
|
|
2367
|
+
activeVersion: contextVersion,
|
|
2368
|
+
activeProjectionKey: projectionKey,
|
|
2369
|
+
createdAt: (previousContext == null ? void 0 : previousContext.createdAt) ?? params.projection.meta.preparedAt,
|
|
2370
|
+
lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2371
|
+
lastKnownOrderRevision: params.orderRevision,
|
|
2372
|
+
lastPreparedAt: params.projection.meta.preparedAt,
|
|
2373
|
+
lastSnapshotId: params.projection.meta.snapshotId,
|
|
2374
|
+
durationCandidateAnchorAt: (previousContext == null ? void 0 : previousContext.durationCandidateAnchorAt) ?? this.getAvailabilityDurationCandidateAnchorAt(params.projection),
|
|
2375
|
+
refreshNonce: (previousContext == null ? void 0 : previousContext.refreshNonce) ?? 0,
|
|
2376
|
+
lifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2377
|
+
};
|
|
2378
|
+
this.touchAvailabilityContext(nextContext);
|
|
2379
|
+
this.evictAvailabilityContextRegistry();
|
|
2380
|
+
return this.touchAvailabilityProjection(entry);
|
|
2381
|
+
}
|
|
2382
|
+
async buildAvailabilityProjectionSnapshot(params, options = {}) {
|
|
2383
|
+
const normalizedPrepareParams = this.normalizeAvailabilityPrepareDescriptor(params);
|
|
2384
|
+
const timezoneLabel = String(normalizedPrepareParams.timezone);
|
|
2385
|
+
const dateRange = normalizedPrepareParams.dateRange;
|
|
2386
|
+
const slotStepMinutes = normalizedPrepareParams.slotStepMinutes || DEFAULT_AVAILABILITY_SLOT_STEP_MINUTES;
|
|
2387
|
+
const businessHours = normalizedPrepareParams.businessHours || DEFAULT_AVAILABILITY_BUSINESS_HOURS;
|
|
2388
|
+
const mode = normalizedPrepareParams.mode || "appointment";
|
|
2389
|
+
const scheduleList = this.getAvailabilityScheduleCatalog();
|
|
2390
|
+
const operatingHours = this.buildAvailabilityOperatingHoursSnapshot({
|
|
2391
|
+
prepare: normalizedPrepareParams,
|
|
2392
|
+
dateRange,
|
|
2393
|
+
scheduleList,
|
|
2394
|
+
businessHours
|
|
2395
|
+
});
|
|
2396
|
+
const products = await this.loadAvailabilityProducts({
|
|
2397
|
+
prepare: normalizedPrepareParams,
|
|
2398
|
+
dateRange,
|
|
2399
|
+
scheduleList
|
|
2400
|
+
});
|
|
2401
|
+
const loadedResources = await this.loadAvailabilityResources({
|
|
2402
|
+
timezone: timezoneLabel,
|
|
2403
|
+
dateRange,
|
|
2404
|
+
resourceIds: normalizedPrepareParams.resourceIds,
|
|
2405
|
+
products,
|
|
2406
|
+
rawResources: normalizedPrepareParams.rawResources,
|
|
2407
|
+
scheduleList,
|
|
2408
|
+
fetchPolicy: options.resourceFetchPolicy || "default",
|
|
2409
|
+
fallbackResources: options.fallbackResources
|
|
2410
|
+
});
|
|
2411
|
+
const tempOrder = this.getTempOrder();
|
|
2412
|
+
const exclusions = this.resolveEditingOccupancyExclusions(
|
|
2413
|
+
tempOrder,
|
|
2414
|
+
normalizedPrepareParams.editingProductLineUids
|
|
2415
|
+
);
|
|
2416
|
+
const injectedRawOccupancies = (normalizedPrepareParams.rawOccupancies || []).flatMap((occupancy) => {
|
|
2417
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
2418
|
+
startAt: occupancy.startAt,
|
|
2419
|
+
endAt: occupancy.endAt,
|
|
2420
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
2421
|
+
rollOverEnd: false
|
|
2422
|
+
});
|
|
2423
|
+
if (!range)
|
|
2424
|
+
return [];
|
|
2425
|
+
return [{
|
|
2426
|
+
...occupancy,
|
|
2427
|
+
startAt: range.startAt,
|
|
2428
|
+
endAt: range.endAt
|
|
2429
|
+
}];
|
|
2430
|
+
});
|
|
2431
|
+
const remoteOccupancies = [
|
|
2432
|
+
...loadedResources.resourceOccupancies,
|
|
2433
|
+
...loadedResources.includesInjectedRawOccupancies ? [] : injectedRawOccupancies
|
|
2434
|
+
].filter((occupancy) => !this.occupancyIsExcluded(occupancy, exclusions));
|
|
2435
|
+
const cartOccupancies = this.collectCartResourceOccupancies(tempOrder, exclusions);
|
|
2436
|
+
const preparedAt = this.getAvailabilityRuntimeDateTime();
|
|
2437
|
+
const durationCandidateAnchorAt = options.durationCandidateAnchorAt || preparedAt;
|
|
2438
|
+
this.availabilityProjectionSequence += 1;
|
|
2439
|
+
const orderRevision = this.getCurrentOrderRevision();
|
|
2440
|
+
const projection = (0, import_ResourcePlanner.createAvailabilityProjection)({
|
|
2441
|
+
products,
|
|
2442
|
+
resources: loadedResources.resources,
|
|
2443
|
+
resourceOccupancies: [...remoteOccupancies, ...cartOccupancies],
|
|
2444
|
+
mode,
|
|
2445
|
+
timezone: timezoneLabel,
|
|
2446
|
+
coverage: dateRange,
|
|
2447
|
+
slotStepMinutes,
|
|
2448
|
+
businessHours,
|
|
2449
|
+
operatingHours,
|
|
2450
|
+
preparedAt,
|
|
2451
|
+
now: preparedAt,
|
|
2452
|
+
orderRevision,
|
|
2453
|
+
snapshotId: `${this.name}:${Date.now()}:${this.availabilityProjectionSequence}`,
|
|
2454
|
+
anchors: {
|
|
2455
|
+
productIds: normalizedPrepareParams.productIds,
|
|
2456
|
+
resourceIds: normalizedPrepareParams.resourceIds,
|
|
2457
|
+
dateRange
|
|
2458
|
+
},
|
|
2459
|
+
prepareParams: normalizedPrepareParams,
|
|
2460
|
+
durationCandidateAnchorAt
|
|
2461
|
+
});
|
|
2462
|
+
return {
|
|
2463
|
+
projection,
|
|
2464
|
+
prepareParams: normalizedPrepareParams,
|
|
2465
|
+
orderRevision
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
async getSharedPreparedAvailabilitySnapshot(params) {
|
|
2469
|
+
const lifecycleGeneration = this.availabilityLifecycleGeneration;
|
|
2470
|
+
const normalizedParams = this.normalizeAvailabilityPrepareDescriptor(params);
|
|
2471
|
+
const hasRawInputs = params.rawProducts !== void 0 || params.rawResources !== void 0 || params.rawOccupancies !== void 0;
|
|
2472
|
+
const prepareKey = hasRawInputs ? JSON.stringify(stableValue({
|
|
2473
|
+
scope: this.getAvailabilityScopeDescriptor(),
|
|
2474
|
+
prepare: {
|
|
2475
|
+
...normalizedParams,
|
|
2476
|
+
rawProducts: void 0,
|
|
2477
|
+
rawResources: void 0,
|
|
2478
|
+
rawOccupancies: void 0
|
|
2479
|
+
},
|
|
2480
|
+
rawInputs: {
|
|
2481
|
+
products: params.rawProducts !== void 0,
|
|
2482
|
+
resources: params.rawResources !== void 0,
|
|
2483
|
+
occupancies: params.rawOccupancies !== void 0
|
|
2484
|
+
}
|
|
2485
|
+
})) : this.buildAvailabilityPrepareKey(normalizedParams);
|
|
2486
|
+
if (hasRawInputs) {
|
|
2487
|
+
const snapshot = await this.buildAvailabilityProjectionSnapshot(normalizedParams);
|
|
2488
|
+
if (lifecycleGeneration !== this.availabilityLifecycleGeneration) {
|
|
2489
|
+
throw this.createAvailabilityFacadeError(
|
|
2490
|
+
"AVAILABILITY_CONTEXT_NOT_FOUND",
|
|
2491
|
+
"prepare 期间业务作用域或实例生命周期已变化,请重新 prepare",
|
|
2492
|
+
{
|
|
2493
|
+
prepareKey,
|
|
2494
|
+
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2495
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2496
|
+
}
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
return { prepareKey, snapshot };
|
|
2500
|
+
}
|
|
2501
|
+
const existing = this.availabilityPrepareInFlight.get(prepareKey);
|
|
2502
|
+
const promise = (existing == null ? void 0 : existing.lifecycleGeneration) === lifecycleGeneration ? existing.promise : this.buildAvailabilityProjectionSnapshot(normalizedParams);
|
|
2503
|
+
if (!existing || existing.lifecycleGeneration !== lifecycleGeneration) {
|
|
2504
|
+
this.availabilityPrepareInFlight.set(prepareKey, {
|
|
2505
|
+
lifecycleGeneration,
|
|
2506
|
+
prepareKey,
|
|
2507
|
+
promise
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
try {
|
|
2511
|
+
const snapshot = await promise;
|
|
2512
|
+
if (lifecycleGeneration !== this.availabilityLifecycleGeneration) {
|
|
2513
|
+
throw this.createAvailabilityFacadeError(
|
|
2514
|
+
"AVAILABILITY_CONTEXT_NOT_FOUND",
|
|
2515
|
+
"prepare 期间业务作用域或实例生命周期已变化,请重新 prepare",
|
|
2516
|
+
{
|
|
2517
|
+
prepareKey,
|
|
2518
|
+
expectedLifecycleGeneration: lifecycleGeneration,
|
|
2519
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
return { prepareKey, snapshot };
|
|
2524
|
+
} finally {
|
|
2525
|
+
const current = this.availabilityPrepareInFlight.get(prepareKey);
|
|
2526
|
+
if ((current == null ? void 0 : current.promise) === promise) {
|
|
2527
|
+
this.availabilityPrepareInFlight.delete(prepareKey);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
async refreshAvailabilityContextProjection(params) {
|
|
2532
|
+
const existingInFlight = this.availabilityProjectionRefreshInFlight.get(params.contextId);
|
|
2533
|
+
if (existingInFlight)
|
|
2534
|
+
return await existingInFlight;
|
|
2535
|
+
const context = this.getAvailabilityContextEntry(params.contextId);
|
|
2536
|
+
const expectedVersion = context.activeVersion;
|
|
2537
|
+
const expectedRefreshNonce = context.refreshNonce;
|
|
2538
|
+
const expectedLifecycleGeneration = this.availabilityLifecycleGeneration;
|
|
2539
|
+
const refreshPromise = (async () => {
|
|
2540
|
+
const snapshot = await this.buildAvailabilityProjectionSnapshot(
|
|
2541
|
+
this.cloneAvailabilityPrepareParams(context.prepareParams),
|
|
2542
|
+
{
|
|
2543
|
+
durationCandidateAnchorAt: context.durationCandidateAnchorAt
|
|
1209
2544
|
}
|
|
1210
|
-
|
|
2545
|
+
);
|
|
2546
|
+
const currentContext = this.availabilityContextRegistry.get(params.contextId);
|
|
2547
|
+
if (!currentContext)
|
|
2548
|
+
return null;
|
|
2549
|
+
if (expectedLifecycleGeneration !== this.availabilityLifecycleGeneration || currentContext.activeVersion !== expectedVersion || currentContext.refreshNonce !== expectedRefreshNonce || currentContext.lifecycleGeneration !== expectedLifecycleGeneration) {
|
|
2550
|
+
return null;
|
|
2551
|
+
}
|
|
2552
|
+
const projectionEntry = this.storeAvailabilityProjectionEntry({
|
|
2553
|
+
contextId: currentContext.contextId,
|
|
2554
|
+
prepareParams: snapshot.prepareParams,
|
|
2555
|
+
prepareKey: currentContext.prepareKey,
|
|
2556
|
+
projection: snapshot.projection,
|
|
2557
|
+
orderRevision: snapshot.orderRevision,
|
|
2558
|
+
incrementVersion: true
|
|
2559
|
+
});
|
|
2560
|
+
await this.emitAvailabilityDiagnostic({
|
|
2561
|
+
action: "refresh",
|
|
2562
|
+
contextId: currentContext.contextId,
|
|
2563
|
+
contextVersion: projectionEntry.contextVersion,
|
|
2564
|
+
projection: projectionEntry.projection,
|
|
2565
|
+
snapshotId: projectionEntry.snapshotId,
|
|
2566
|
+
orderRevision: projectionEntry.orderRevision,
|
|
2567
|
+
previousOrderRevision: params.previousOrderRevision,
|
|
2568
|
+
reason: params.reason
|
|
2569
|
+
});
|
|
2570
|
+
return projectionEntry;
|
|
2571
|
+
})();
|
|
2572
|
+
this.availabilityProjectionRefreshInFlight.set(params.contextId, refreshPromise);
|
|
2573
|
+
try {
|
|
2574
|
+
return await refreshPromise;
|
|
2575
|
+
} finally {
|
|
2576
|
+
const current = this.availabilityProjectionRefreshInFlight.get(params.contextId);
|
|
2577
|
+
if (current === refreshPromise) {
|
|
2578
|
+
this.availabilityProjectionRefreshInFlight.delete(params.contextId);
|
|
2579
|
+
}
|
|
1211
2580
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
2581
|
+
}
|
|
2582
|
+
async ensureAvailabilityContextProjection(contextId, reason) {
|
|
2583
|
+
let retries = 0;
|
|
2584
|
+
while (retries < 3) {
|
|
2585
|
+
const context = this.getAvailabilityContextEntry(contextId);
|
|
2586
|
+
const currentOrderRevision = this.getCurrentOrderRevision();
|
|
2587
|
+
const projectionEntry = this.getActiveAvailabilityProjectionEntry(context);
|
|
2588
|
+
if (projectionEntry && projectionEntry.orderRevision === currentOrderRevision) {
|
|
2589
|
+
return { context, projectionEntry, refreshed: false };
|
|
2590
|
+
}
|
|
2591
|
+
const refreshedProjectionEntry = await this.refreshAvailabilityContextProjection({
|
|
2592
|
+
contextId,
|
|
2593
|
+
reason: projectionEntry ? reason : `${reason}:projection_cache_miss`,
|
|
2594
|
+
previousOrderRevision: context.lastKnownOrderRevision
|
|
2595
|
+
});
|
|
2596
|
+
if (!refreshedProjectionEntry) {
|
|
2597
|
+
retries += 1;
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
if (refreshedProjectionEntry.orderRevision !== this.getCurrentOrderRevision()) {
|
|
2601
|
+
retries += 1;
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
1214
2604
|
return {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
2605
|
+
context: this.getAvailabilityContextEntry(contextId),
|
|
2606
|
+
projectionEntry: refreshedProjectionEntry,
|
|
2607
|
+
refreshed: true
|
|
1218
2608
|
};
|
|
1219
2609
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
2610
|
+
throw new import_ResourcePlanner.AvailabilityError("STALE_PROJECTION", "availability projection 刷新期间购物车持续变化,请重新 prepare", {
|
|
2611
|
+
contextId,
|
|
2612
|
+
reason
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
async tryAcquireAvailabilityCommitLock(contextId) {
|
|
2616
|
+
if (this.availabilityCommitLocks.has(contextId)) {
|
|
2617
|
+
const waitingContext = this.availabilityContextRegistry.get(contextId);
|
|
2618
|
+
await this.emitAvailabilityDiagnostic({
|
|
2619
|
+
action: "commit_lock_wait",
|
|
2620
|
+
contextId,
|
|
2621
|
+
contextVersion: (waitingContext == null ? void 0 : waitingContext.activeVersion) ?? null,
|
|
2622
|
+
projection: this.getAvailabilityProjectionByKey(waitingContext == null ? void 0 : waitingContext.activeProjectionKey),
|
|
2623
|
+
snapshotId: waitingContext == null ? void 0 : waitingContext.lastSnapshotId,
|
|
2624
|
+
orderRevision: waitingContext == null ? void 0 : waitingContext.lastKnownOrderRevision,
|
|
2625
|
+
reason: "AVAILABILITY_COMMIT_IN_PROGRESS"
|
|
2626
|
+
});
|
|
2627
|
+
throw this.createAvailabilityFacadeError(
|
|
2628
|
+
"AVAILABILITY_COMMIT_IN_PROGRESS",
|
|
2629
|
+
"同一 availability context 已有 commit 正在进行中",
|
|
2630
|
+
{ contextId }
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
this.availabilityCommitLocks.add(contextId);
|
|
2634
|
+
const currentContext = this.availabilityContextRegistry.get(contextId);
|
|
2635
|
+
await this.emitAvailabilityDiagnostic({
|
|
2636
|
+
action: "commit_lock_acquired",
|
|
2637
|
+
contextId,
|
|
2638
|
+
contextVersion: (currentContext == null ? void 0 : currentContext.activeVersion) ?? null,
|
|
2639
|
+
projection: this.getAvailabilityProjectionByKey(currentContext == null ? void 0 : currentContext.activeProjectionKey),
|
|
2640
|
+
snapshotId: currentContext == null ? void 0 : currentContext.lastSnapshotId,
|
|
2641
|
+
orderRevision: currentContext == null ? void 0 : currentContext.lastKnownOrderRevision,
|
|
2642
|
+
reason: "commit_start"
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
async releaseAvailabilityCommitLock(contextId) {
|
|
2646
|
+
this.availabilityCommitLocks.delete(contextId);
|
|
2647
|
+
const releasedContext = this.availabilityContextRegistry.get(contextId);
|
|
2648
|
+
await this.emitAvailabilityDiagnostic({
|
|
2649
|
+
action: "commit_lock_released",
|
|
2650
|
+
contextId,
|
|
2651
|
+
contextVersion: (releasedContext == null ? void 0 : releasedContext.activeVersion) ?? null,
|
|
2652
|
+
projection: this.getAvailabilityProjectionByKey(releasedContext == null ? void 0 : releasedContext.activeProjectionKey),
|
|
2653
|
+
snapshotId: releasedContext == null ? void 0 : releasedContext.lastSnapshotId,
|
|
2654
|
+
orderRevision: releasedContext == null ? void 0 : releasedContext.lastKnownOrderRevision,
|
|
2655
|
+
reason: "commit_end"
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
/** IO boundary: loads catalog, schedules, resources, occupancies, and registers one context. */
|
|
2659
|
+
async prepareAvailabilityProjection(params) {
|
|
2660
|
+
this.assertAvailabilityPrepareParams(params);
|
|
2661
|
+
const paramsWithDefaults = await this.resolveAvailabilityPrepareDefaults(params);
|
|
2662
|
+
const { prepareKey, snapshot } = await this.getSharedPreparedAvailabilitySnapshot(paramsWithDefaults);
|
|
2663
|
+
const contextId = this.createAvailabilityContextId();
|
|
2664
|
+
const projectionEntry = this.storeAvailabilityProjectionEntry({
|
|
2665
|
+
contextId,
|
|
2666
|
+
prepareParams: snapshot.prepareParams,
|
|
2667
|
+
prepareKey,
|
|
2668
|
+
projection: snapshot.projection,
|
|
2669
|
+
orderRevision: snapshot.orderRevision,
|
|
2670
|
+
incrementVersion: false
|
|
2671
|
+
});
|
|
2672
|
+
const context = this.getAvailabilityContextEntry(contextId);
|
|
2673
|
+
const handle = this.createAvailabilityContextRef(context);
|
|
2674
|
+
const payload = {
|
|
2675
|
+
context: handle,
|
|
2676
|
+
contextVersion: context.activeVersion,
|
|
2677
|
+
projection: projectionEntry.projection,
|
|
2678
|
+
refreshed: false,
|
|
2679
|
+
reason: "prepare"
|
|
2680
|
+
};
|
|
2681
|
+
await this.emitAvailabilityDiagnostic({
|
|
2682
|
+
action: "prepare",
|
|
2683
|
+
contextId,
|
|
2684
|
+
contextVersion: context.activeVersion,
|
|
2685
|
+
projection: projectionEntry.projection,
|
|
2686
|
+
snapshotId: context.lastSnapshotId,
|
|
2687
|
+
orderRevision: context.lastKnownOrderRevision,
|
|
2688
|
+
reason: "prepare"
|
|
2689
|
+
});
|
|
2690
|
+
await this.core.effects.emit(
|
|
2691
|
+
`${this.name}:${import_types.UnifiedBookingSalesHooks.onAvailabilityProjectionPrepared}`,
|
|
2692
|
+
payload
|
|
2693
|
+
);
|
|
2694
|
+
return handle;
|
|
2695
|
+
}
|
|
2696
|
+
/** Pure configuration calculation over the current context projection. */
|
|
2697
|
+
async getProductTimeRanges(contextId, request = {}) {
|
|
2698
|
+
const { context, projectionEntry } = await this.ensureAvailabilityContextProjection(
|
|
2699
|
+
contextId,
|
|
2700
|
+
"get_product_time_ranges"
|
|
1222
2701
|
);
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
2702
|
+
const contextVersion = context.activeVersion;
|
|
2703
|
+
return (0, import_ResourcePlanner.getProductTimeRanges)(projectionEntry.projection, request).map((group) => ({
|
|
2704
|
+
...group,
|
|
2705
|
+
ranges: group.ranges.map((candidate) => this.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate))
|
|
2706
|
+
}));
|
|
2707
|
+
}
|
|
2708
|
+
/** Thin facade: auto-refresh on cart drift, then delegate to the pure projection query. */
|
|
2709
|
+
async queryBookingAvailability(contextId, request) {
|
|
2710
|
+
const { context, projectionEntry, refreshed } = await this.ensureAvailabilityContextProjection(
|
|
2711
|
+
contextId,
|
|
2712
|
+
"query_booking_availability"
|
|
2713
|
+
);
|
|
2714
|
+
this.assertContextualQueryFreshness(context, request);
|
|
2715
|
+
const result = (0, import_ResourcePlanner.queryAvailabilityProjection)(
|
|
2716
|
+
projectionEntry.projection,
|
|
2717
|
+
request
|
|
2718
|
+
);
|
|
2719
|
+
const contextVersion = context.activeVersion;
|
|
2720
|
+
const contextualResult = result.target === "time" ? {
|
|
2721
|
+
...result,
|
|
2722
|
+
data: result.data.map((group) => ({
|
|
2723
|
+
...group,
|
|
2724
|
+
times: group.times.map((item) => ({
|
|
2725
|
+
...item,
|
|
2726
|
+
availabilityContextId: context.contextId,
|
|
2727
|
+
availabilityContextVersion: contextVersion
|
|
2728
|
+
}))
|
|
2729
|
+
}))
|
|
2730
|
+
} : result.target === "resource" ? {
|
|
2731
|
+
...result,
|
|
2732
|
+
data: result.data.map((group) => ({
|
|
2733
|
+
...group,
|
|
2734
|
+
ranges: group.ranges.map((rangeGroup) => ({
|
|
2735
|
+
...rangeGroup,
|
|
2736
|
+
range: {
|
|
2737
|
+
...rangeGroup.range,
|
|
2738
|
+
availabilityContextId: context.contextId,
|
|
2739
|
+
availabilityContextVersion: contextVersion
|
|
2740
|
+
}
|
|
2741
|
+
}))
|
|
2742
|
+
}))
|
|
2743
|
+
} : result;
|
|
2744
|
+
return {
|
|
2745
|
+
...contextualResult,
|
|
2746
|
+
contextId: context.contextId,
|
|
2747
|
+
contextVersion,
|
|
2748
|
+
refreshed
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
releaseAvailabilityContext(contextId) {
|
|
2752
|
+
var _a;
|
|
2753
|
+
const context = this.availabilityContextRegistry.get(contextId);
|
|
2754
|
+
if (!context)
|
|
2755
|
+
return;
|
|
2756
|
+
this.removeAvailabilityContext(contextId);
|
|
2757
|
+
void this.emitAvailabilityDiagnostic({
|
|
2758
|
+
action: "release",
|
|
2759
|
+
contextId,
|
|
2760
|
+
contextVersion: context.activeVersion,
|
|
2761
|
+
projection: ((_a = this.getActiveAvailabilityProjectionEntry(context)) == null ? void 0 : _a.projection) || null,
|
|
2762
|
+
snapshotId: context.lastSnapshotId,
|
|
2763
|
+
orderRevision: context.lastKnownOrderRevision,
|
|
2764
|
+
reason: "explicit_release"
|
|
1228
2765
|
});
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
product
|
|
2766
|
+
}
|
|
2767
|
+
assertAvailabilityCommitWriteCAS(params) {
|
|
2768
|
+
const context = this.availabilityContextRegistry.get(params.contextId);
|
|
2769
|
+
if (!context) {
|
|
2770
|
+
throw this.createAvailabilityFacadeError(
|
|
2771
|
+
"AVAILABILITY_CONTEXT_NOT_FOUND",
|
|
2772
|
+
"availability context 不存在或已失效,请重新 prepare",
|
|
2773
|
+
{ contextId: params.contextId }
|
|
1238
2774
|
);
|
|
1239
|
-
if (!productInput)
|
|
1240
|
-
continue;
|
|
1241
|
-
await this.addProductToOrder(productInput, buildBookingFromAssignments({
|
|
1242
|
-
assignments: groupAssignments,
|
|
1243
|
-
product
|
|
1244
|
-
}));
|
|
1245
2775
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
2776
|
+
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
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2779
|
+
"availability context 已变化,当前选择已失效,请重新获取候选",
|
|
2780
|
+
{
|
|
2781
|
+
contextId: params.contextId,
|
|
2782
|
+
expectedVersion: params.expectedVersion,
|
|
2783
|
+
currentVersion: context.activeVersion,
|
|
2784
|
+
expectedRefreshNonce: params.expectedRefreshNonce,
|
|
2785
|
+
currentRefreshNonce: context.refreshNonce,
|
|
2786
|
+
expectedLifecycleGeneration: params.expectedLifecycleGeneration,
|
|
2787
|
+
currentLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
2788
|
+
expectedOrderRevision: params.expectedOrderRevision,
|
|
2789
|
+
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2790
|
+
}
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
return context;
|
|
2794
|
+
}
|
|
2795
|
+
findProductLineByUid(productLineUid) {
|
|
2796
|
+
var _a;
|
|
2797
|
+
return (((_a = this.getTempOrder()) == null ? void 0 : _a.products) || []).find((product) => getProductLineUid(product) === String(productLineUid));
|
|
2798
|
+
}
|
|
2799
|
+
findLinkedBooking(product) {
|
|
2800
|
+
var _a, _b;
|
|
2801
|
+
const productUid = getProductLineUid(product);
|
|
2802
|
+
const bookingUid = String(product.booking_uid ?? ((_a = product.metadata) == null ? void 0 : _a.booking_uid) ?? "");
|
|
2803
|
+
return (((_b = this.getTempOrder()) == null ? void 0 : _b.bookings) || []).find((booking) => bookingUid && getBookingUid(booking) === bookingUid || productUid && String(booking.product_uid ?? "") === productUid);
|
|
2804
|
+
}
|
|
2805
|
+
/** Revalidates against the latest landed occupancy cache, then mutates exactly one product line. */
|
|
2806
|
+
async commitAvailabilitySelection(contextId, params) {
|
|
2807
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2808
|
+
const normalizedContextId = String(contextId || "").trim();
|
|
2809
|
+
if (!normalizedContextId) {
|
|
2810
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "commitAvailabilitySelection 缺少 contextId", {
|
|
2811
|
+
candidate: params.candidate
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
await this.tryAcquireAvailabilityCommitLock(normalizedContextId);
|
|
2815
|
+
try {
|
|
2816
|
+
const bookingCount = Number(params.bookingCount ?? 1);
|
|
2817
|
+
if (!Number.isInteger(bookingCount) || bookingCount <= 0) {
|
|
2818
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_RESERVATION_QUANTITY", "bookingCount 必须是正整数", {
|
|
2819
|
+
bookingCount: params.bookingCount
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
if (params.mode === "edit" && bookingCount !== 1) {
|
|
2823
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_RESERVATION_QUANTITY", "编辑预约商品时 bookingCount 必须为 1", {
|
|
2824
|
+
bookingCount
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
const orderProductQuantity = ((_a = params.orderProduct) == null ? void 0 : _a.num) ?? ((_b = params.orderProduct) == null ? void 0 : _b.quantity) ?? ((_d = (_c = params.orderProduct) == null ? void 0 : _c._extend) == null ? void 0 : _d.quantity);
|
|
2828
|
+
if (orderProductQuantity !== void 0 && orderProductQuantity !== null && Number(orderProductQuantity) !== bookingCount) {
|
|
2829
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_RESERVATION_QUANTITY", "orderProduct 数量与 bookingCount 不一致", {
|
|
2830
|
+
bookingCount,
|
|
2831
|
+
orderProductQuantity
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
const partySize = Number(params.partySize);
|
|
2835
|
+
if (!Number.isInteger(partySize) || partySize <= 0) {
|
|
2836
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_RESERVATION_QUANTITY", "partySize 必须是正整数", {
|
|
2837
|
+
partySize: params.partySize
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
const {
|
|
2841
|
+
context,
|
|
2842
|
+
projectionEntry,
|
|
2843
|
+
refreshed
|
|
2844
|
+
} = await this.ensureAvailabilityContextProjection(
|
|
2845
|
+
normalizedContextId,
|
|
2846
|
+
"commit_precheck"
|
|
2847
|
+
);
|
|
2848
|
+
if (refreshed) {
|
|
2849
|
+
throw this.createAvailabilityFacadeError(
|
|
2850
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2851
|
+
"availability context 已刷新,请重新获取候选",
|
|
2852
|
+
{
|
|
2853
|
+
contextId: normalizedContextId,
|
|
2854
|
+
currentContextVersion: context.activeVersion,
|
|
2855
|
+
candidateContextVersion: params.candidate.availabilityContextVersion
|
|
2856
|
+
}
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
const boundEditingUids = context.prepareParams.editingProductLineUids || [];
|
|
2860
|
+
if (params.mode === "edit") {
|
|
2861
|
+
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
|
+
{
|
|
2877
|
+
contextId: normalizedContextId,
|
|
2878
|
+
editingProductLineUids: boundEditingUids
|
|
2879
|
+
}
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
if (projectionEntry.contextVersion !== context.activeVersion) {
|
|
2883
|
+
throw this.createAvailabilityFacadeError(
|
|
2884
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2885
|
+
"availability context 已刷新,请重新获取候选",
|
|
2886
|
+
{
|
|
2887
|
+
contextId: normalizedContextId,
|
|
2888
|
+
currentContextVersion: context.activeVersion,
|
|
2889
|
+
projectionContextVersion: projectionEntry.contextVersion
|
|
2890
|
+
}
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
if (projectionEntry.orderRevision !== this.getCurrentOrderRevision()) {
|
|
2894
|
+
throw this.createAvailabilityFacadeError(
|
|
2895
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2896
|
+
"购物车已变化,请重新获取候选",
|
|
2897
|
+
{
|
|
2898
|
+
contextId: normalizedContextId,
|
|
2899
|
+
currentContextVersion: context.activeVersion,
|
|
2900
|
+
projectionOrderRevision: projectionEntry.orderRevision,
|
|
2901
|
+
currentOrderRevision: this.getCurrentOrderRevision()
|
|
2902
|
+
}
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
if (projectionEntry.contextVersion !== params.candidate.availabilityContextVersion) {
|
|
2906
|
+
throw this.createAvailabilityFacadeError(
|
|
2907
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2908
|
+
"候选已过期,请重新获取最新时间段",
|
|
2909
|
+
{
|
|
2910
|
+
contextId: normalizedContextId,
|
|
2911
|
+
candidateContextVersion: params.candidate.availabilityContextVersion,
|
|
2912
|
+
currentContextVersion: context.activeVersion
|
|
2913
|
+
}
|
|
2914
|
+
);
|
|
2915
|
+
}
|
|
2916
|
+
this.assertAvailabilityCandidateFreshness(context, params.candidate);
|
|
2917
|
+
const orderProductId = ((_e = params.orderProduct) == null ? void 0 : _e.product_id) ?? ((_f = params.orderProduct) == null ? void 0 : _f.id);
|
|
2918
|
+
if (!sameAvailabilityId(orderProductId, params.candidate.productId)) {
|
|
2919
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "orderProduct 与 candidate 商品不一致", {
|
|
2920
|
+
orderProductId,
|
|
2921
|
+
candidateProductId: params.candidate.productId
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
if (params.mode === "edit" && !params.productLineUid) {
|
|
2925
|
+
throw new import_ResourcePlanner.AvailabilityError("PRODUCT_LINE_NOT_FOUND", "edit 模式必须提供 productLineUid");
|
|
2926
|
+
}
|
|
2927
|
+
const editingLine = params.mode === "edit" ? this.findProductLineByUid(String(params.productLineUid)) : void 0;
|
|
2928
|
+
if (params.mode === "edit" && !editingLine) {
|
|
2929
|
+
throw new import_ResourcePlanner.AvailabilityError("PRODUCT_LINE_NOT_FOUND", "找不到要编辑的预约商品行", {
|
|
2930
|
+
productLineUid: params.productLineUid
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
if (editingLine && !sameAvailabilityId(editingLine.product_id ?? editingLine.id, params.candidate.productId)) {
|
|
2934
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "编辑商品行与 candidate 商品不一致", {
|
|
2935
|
+
productLineUid: params.productLineUid,
|
|
2936
|
+
productLineProductId: editingLine.product_id ?? editingLine.id,
|
|
2937
|
+
candidateProductId: params.candidate.productId
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
if (!Array.isArray(params.requirementSelections)) {
|
|
2941
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "requirementSelections 必须是数组", {
|
|
2942
|
+
requirementSelections: params.requirementSelections
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
const baseProjection = projectionEntry.projection;
|
|
2946
|
+
let commitWriteGuard = {
|
|
2947
|
+
contextId: normalizedContextId,
|
|
2948
|
+
expectedVersion: context.activeVersion,
|
|
2949
|
+
expectedRefreshNonce: context.refreshNonce,
|
|
2950
|
+
expectedLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
2951
|
+
expectedOrderRevision: String(baseProjection.meta.orderRevision ?? "")
|
|
2952
|
+
};
|
|
2953
|
+
const prepareParams = baseProjection.meta.prepareParams;
|
|
2954
|
+
if (!prepareParams) {
|
|
2955
|
+
throw new import_ResourcePlanner.AvailabilityError("REVALIDATION_FAILED", "projection 缺少 prepareParams,无法执行缓存重校验");
|
|
2956
|
+
}
|
|
2957
|
+
const fallbackResources = {
|
|
2958
|
+
resources: (0, import_lodash_es.cloneDeep)([...baseProjection.resources || []]),
|
|
2959
|
+
resourceOccupancies: (0, import_lodash_es.cloneDeep)(
|
|
2960
|
+
[...baseProjection.resourceOccupancies || []].filter((occupancy) => occupancy.source !== "cart")
|
|
2961
|
+
),
|
|
2962
|
+
includesInjectedRawOccupancies: true
|
|
2963
|
+
};
|
|
2964
|
+
const commitSnapshot = await this.buildAvailabilityProjectionSnapshot({
|
|
2965
|
+
...this.cloneAvailabilityPrepareParams(prepareParams),
|
|
2966
|
+
editingProductLineUids: params.mode === "edit" && params.productLineUid ? [params.productLineUid] : []
|
|
2967
|
+
}, {
|
|
2968
|
+
resourceFetchPolicy: "cache_only",
|
|
2969
|
+
fallbackResources,
|
|
2970
|
+
durationCandidateAnchorAt: this.getAvailabilityDurationCandidateAnchorAt(baseProjection) || context.durationCandidateAnchorAt
|
|
2971
|
+
});
|
|
2972
|
+
const refreshedProjection = commitSnapshot.projection;
|
|
2973
|
+
if (String(refreshedProjection.meta.orderRevision ?? "") !== String(baseProjection.meta.orderRevision ?? "")) {
|
|
2974
|
+
await this.refreshAvailabilityContextProjection({
|
|
2975
|
+
contextId: normalizedContextId,
|
|
2976
|
+
reason: "commit_order_revision_changed",
|
|
2977
|
+
previousOrderRevision: String(baseProjection.meta.orderRevision ?? "")
|
|
2978
|
+
});
|
|
2979
|
+
const latestContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
2980
|
+
throw this.createAvailabilityFacadeError(
|
|
2981
|
+
"AVAILABILITY_SELECTION_STALE",
|
|
2982
|
+
"最终缓存重校验期间购物车已变化,请重新获取候选",
|
|
2983
|
+
{
|
|
2984
|
+
contextId: normalizedContextId,
|
|
2985
|
+
candidateContextVersion: params.candidate.availabilityContextVersion,
|
|
2986
|
+
currentContextVersion: latestContext.activeVersion,
|
|
2987
|
+
projectionOrderRevision: baseProjection.meta.orderRevision,
|
|
2988
|
+
refreshedOrderRevision: refreshedProjection.meta.orderRevision,
|
|
2989
|
+
snapshotId: baseProjection.meta.snapshotId,
|
|
2990
|
+
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
2991
|
+
}
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
2995
|
+
const refreshedRanges = (0, import_ResourcePlanner.getProductTimeRanges)(refreshedProjection, {
|
|
2996
|
+
productIds: [params.candidate.productId],
|
|
2997
|
+
dateRange: {
|
|
2998
|
+
startDate: params.candidate.startAt.slice(0, 10),
|
|
2999
|
+
endDate: params.candidate.startAt.slice(0, 10)
|
|
3000
|
+
}
|
|
3001
|
+
}).flatMap((group) => group.ranges);
|
|
3002
|
+
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
|
+
if (!refreshedCandidateBase) {
|
|
3004
|
+
throw new import_ResourcePlanner.AvailabilityError("REVALIDATION_FAILED", "缓存重校验后候选时间已不在商品配置中", {
|
|
3005
|
+
contextId: normalizedContextId,
|
|
3006
|
+
candidate: params.candidate,
|
|
3007
|
+
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
const validation = (0, import_ResourcePlanner.validateAvailabilitySelection)(refreshedProjection, {
|
|
3011
|
+
candidate: refreshedCandidateBase,
|
|
3012
|
+
requirementSelections: params.requirementSelections,
|
|
3013
|
+
partySize,
|
|
3014
|
+
bookingCount
|
|
3015
|
+
});
|
|
3016
|
+
if (!validation.ok) {
|
|
3017
|
+
const capacityConflict = validation.conflicts.find((conflict) => conflict.code === "resource_capacity_exceeded");
|
|
3018
|
+
throw new import_ResourcePlanner.AvailabilityError(
|
|
3019
|
+
"REVALIDATION_FAILED",
|
|
3020
|
+
(capacityConflict == null ? void 0 : capacityConflict.message) || "缓存重校验后资源已不可提交",
|
|
3021
|
+
{
|
|
3022
|
+
validation,
|
|
3023
|
+
refreshedSnapshotId: refreshedProjection.meta.snapshotId
|
|
3024
|
+
}
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
const revalidationEntry = this.storeAvailabilityProjectionEntry({
|
|
3028
|
+
contextId: normalizedContextId,
|
|
3029
|
+
prepareParams: commitSnapshot.prepareParams,
|
|
3030
|
+
prepareKey: context.prepareKey,
|
|
3031
|
+
projection: refreshedProjection,
|
|
3032
|
+
orderRevision: commitSnapshot.orderRevision,
|
|
3033
|
+
incrementVersion: true
|
|
3034
|
+
});
|
|
3035
|
+
const revalidatedContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
3036
|
+
await this.emitAvailabilityDiagnostic({
|
|
3037
|
+
action: "refresh",
|
|
3038
|
+
contextId: normalizedContextId,
|
|
3039
|
+
contextVersion: revalidatedContext.activeVersion,
|
|
3040
|
+
projection: refreshedProjection,
|
|
3041
|
+
snapshotId: revalidationEntry.snapshotId,
|
|
3042
|
+
orderRevision: revalidationEntry.orderRevision,
|
|
3043
|
+
previousOrderRevision: baseProjection.meta.orderRevision,
|
|
3044
|
+
reason: "commit_cached_occupancy_revalidation"
|
|
3045
|
+
});
|
|
3046
|
+
commitWriteGuard = {
|
|
3047
|
+
contextId: normalizedContextId,
|
|
3048
|
+
expectedVersion: revalidatedContext.activeVersion,
|
|
3049
|
+
expectedRefreshNonce: revalidatedContext.refreshNonce,
|
|
3050
|
+
expectedLifecycleGeneration: this.availabilityLifecycleGeneration,
|
|
3051
|
+
expectedOrderRevision: revalidationEntry.orderRevision
|
|
3052
|
+
};
|
|
3053
|
+
const refreshedCandidate = this.decorateAvailabilityCandidate(
|
|
3054
|
+
normalizedContextId,
|
|
3055
|
+
revalidatedContext.activeVersion,
|
|
3056
|
+
refreshedCandidateBase
|
|
3057
|
+
);
|
|
3058
|
+
const product = refreshedProjection.products.find((item) => sameAvailabilityId(item.id, refreshedCandidate.productId));
|
|
3059
|
+
if (!product) {
|
|
3060
|
+
throw new import_ResourcePlanner.AvailabilityError("REVALIDATION_FAILED", "缓存重校验后商品不在 projection 中", {
|
|
3061
|
+
productId: refreshedCandidate.productId
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
const existingBooking = editingLine ? this.findLinkedBooking(editingLine) : void 0;
|
|
3065
|
+
const existingBookingUid = existingBooking ? getBookingUid(existingBooking) : "";
|
|
3066
|
+
if (params.mode === "edit" && !existingBookingUid) {
|
|
3067
|
+
throw new import_ResourcePlanner.AvailabilityError("PRODUCT_LINE_NOT_FOUND", "编辑商品行缺少关联 booking", {
|
|
3068
|
+
productLineUid: params.productLineUid
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
this.assertAvailabilityCommitWriteCAS(commitWriteGuard);
|
|
3072
|
+
const booking = buildBookingFromAvailabilitySelection({
|
|
3073
|
+
assignments: validation.assignments,
|
|
3074
|
+
candidate: refreshedCandidate,
|
|
3075
|
+
product,
|
|
3076
|
+
resources: refreshedProjection.resources,
|
|
3077
|
+
bookingUid: existingBookingUid || void 0
|
|
3078
|
+
});
|
|
3079
|
+
const transformed = this.buildUnifiedOrderProduct({
|
|
3080
|
+
...params.orderProduct,
|
|
3081
|
+
num: bookingCount,
|
|
3082
|
+
quantity: bookingCount
|
|
3083
|
+
}, "unified_booking_sales", product);
|
|
3084
|
+
if (!transformed) {
|
|
3085
|
+
throw new import_ResourcePlanner.AvailabilityError("INVALID_TARGET_FILTER", "orderProduct 无法转换为订单行", {
|
|
3086
|
+
orderProduct: params.orderProduct
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
transformed.num = bookingCount;
|
|
3090
|
+
transformed.metadata = {
|
|
3091
|
+
...transformed.metadata || {},
|
|
3092
|
+
unified_booking_sales: true,
|
|
3093
|
+
availability_candidate_key: refreshedCandidate.key,
|
|
3094
|
+
availability_context_id: normalizedContextId,
|
|
3095
|
+
availability_context_version: refreshedCandidate.availabilityContextVersion
|
|
3096
|
+
};
|
|
3097
|
+
let productLineUid = "";
|
|
3098
|
+
let bookingUid = "";
|
|
3099
|
+
let productLineUids = [];
|
|
3100
|
+
let bookingUids = [];
|
|
3101
|
+
if (params.mode === "edit" && editingLine && params.productLineUid) {
|
|
3102
|
+
transformed.product_sku = transformed.product_sku ?? editingLine.product_sku;
|
|
3103
|
+
transformed.product_bundle = transformed.product_bundle ?? editingLine.product_bundle;
|
|
3104
|
+
transformed.booking_uid = existingBookingUid;
|
|
3105
|
+
transformed.metadata = {
|
|
3106
|
+
...editingLine.metadata || {},
|
|
3107
|
+
...transformed.metadata || {},
|
|
3108
|
+
unique_identification_number: params.productLineUid,
|
|
3109
|
+
booking_uid: existingBookingUid
|
|
3110
|
+
};
|
|
3111
|
+
await this.updateOrderProduct({
|
|
3112
|
+
product_id: transformed.product_id ?? editingLine.product_id,
|
|
3113
|
+
product_variant_id: transformed.product_variant_id ?? editingLine.product_variant_id ?? 0,
|
|
3114
|
+
unique_identification_number: params.productLineUid,
|
|
3115
|
+
updates: transformed,
|
|
3116
|
+
booking
|
|
3117
|
+
});
|
|
3118
|
+
productLineUid = params.productLineUid;
|
|
3119
|
+
bookingUid = existingBookingUid;
|
|
3120
|
+
productLineUids = [productLineUid];
|
|
3121
|
+
bookingUids = [bookingUid];
|
|
3122
|
+
} else {
|
|
3123
|
+
const beforeProductLineUids = new Set(
|
|
3124
|
+
(((_g = this.getTempOrder()) == null ? void 0 : _g.products) || []).map((line) => getProductLineUid(line)).filter(Boolean)
|
|
3125
|
+
);
|
|
3126
|
+
const beforeBookingUids = new Set(
|
|
3127
|
+
(((_h = this.getTempOrder()) == null ? void 0 : _h.bookings) || []).map((item) => getBookingUid(item)).filter(Boolean)
|
|
3128
|
+
);
|
|
3129
|
+
const products = await this.addProductToOrder(transformed, booking);
|
|
3130
|
+
const addedLines = products.filter((line) => {
|
|
3131
|
+
var _a2;
|
|
3132
|
+
return sameAvailabilityId(line.product_id ?? line.id, refreshedCandidate.productId) && ((_a2 = line.metadata) == null ? void 0 : _a2.availability_candidate_key) === refreshedCandidate.key && !beforeProductLineUids.has(getProductLineUid(line));
|
|
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);
|
|
3140
|
+
if (bookingUids.length === 0) {
|
|
3141
|
+
bookingUids = addedLines.map((line) => {
|
|
3142
|
+
var _a2;
|
|
3143
|
+
return String(
|
|
3144
|
+
line.booking_uid ?? ((_a2 = line.metadata) == null ? void 0 : _a2.booking_uid) ?? ""
|
|
3145
|
+
);
|
|
3146
|
+
}).filter(Boolean);
|
|
3147
|
+
}
|
|
3148
|
+
productLineUid = productLineUids[productLineUids.length - 1] || "";
|
|
3149
|
+
bookingUid = bookingUids[bookingUids.length - 1] || "";
|
|
3150
|
+
}
|
|
3151
|
+
this.invalidateAvailabilityContextsAfterCommit();
|
|
3152
|
+
const nextContext = this.getAvailabilityContextEntry(normalizedContextId);
|
|
3153
|
+
const result = {
|
|
3154
|
+
contextId: normalizedContextId,
|
|
3155
|
+
contextVersion: nextContext.activeVersion,
|
|
3156
|
+
tempOrder: this.getTempOrder(),
|
|
3157
|
+
validation,
|
|
3158
|
+
candidate: refreshedCandidate,
|
|
3159
|
+
productLineUid,
|
|
3160
|
+
bookingUid,
|
|
3161
|
+
productLineUids,
|
|
3162
|
+
bookingUids
|
|
3163
|
+
};
|
|
3164
|
+
await this.core.effects.emit(
|
|
3165
|
+
`${this.name}:${import_types.UnifiedBookingSalesHooks.onAvailabilitySelectionCommitted}`,
|
|
3166
|
+
result
|
|
3167
|
+
);
|
|
3168
|
+
return result;
|
|
3169
|
+
} finally {
|
|
3170
|
+
await this.releaseAvailabilityCommitLock(normalizedContextId);
|
|
1249
3171
|
}
|
|
1250
|
-
const result = {
|
|
1251
|
-
tempOrder: this.getTempOrder(),
|
|
1252
|
-
assignments,
|
|
1253
|
-
validation,
|
|
1254
|
-
submitResult
|
|
1255
|
-
};
|
|
1256
|
-
await this.core.effects.emit(`${this.name}:${import_types.UnifiedBookingSalesHooks.onPlannerCommitted}`, result);
|
|
1257
|
-
return result;
|
|
1258
3172
|
}
|
|
1259
3173
|
};
|
|
1260
3174
|
var UnifiedBookingSales = UnifiedBookingSalesImpl;
|
|
1261
3175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1262
3176
|
0 && (module.exports = {
|
|
3177
|
+
AvailabilityError,
|
|
1263
3178
|
UnifiedBookingSales,
|
|
1264
3179
|
UnifiedBookingSalesImpl,
|
|
1265
|
-
|
|
1266
|
-
|
|
3180
|
+
buildAvailabilityLineIdentity,
|
|
3181
|
+
buildAvailabilityResourcesFromV2,
|
|
3182
|
+
buildBookingFromAvailabilityAssignment,
|
|
3183
|
+
buildBookingFromAvailabilityAssignments,
|
|
1267
3184
|
buildPlannerDiscountResult,
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
collectPlannerResourceIds,
|
|
1271
|
-
normalizeProductForPlanner,
|
|
3185
|
+
collectAvailabilityResourceIds,
|
|
3186
|
+
normalizeProductForAvailability,
|
|
1272
3187
|
...require("./types")
|
|
1273
3188
|
});
|