@pisell/pisellos 2.1.129 → 2.1.130
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/Order/index.d.ts +4 -0
- package/dist/modules/Order/index.js +18 -1
- package/dist/modules/Order/types.d.ts +9 -1
- package/dist/modules/Order/utils.d.ts +7 -0
- package/dist/modules/Order/utils.js +27 -11
- package/dist/solution/ScanOrder/index.d.ts +27 -3
- package/dist/solution/ScanOrder/index.js +865 -481
- package/dist/solution/ScanOrder/types.d.ts +34 -24
- package/dist/solution/ScanOrder/types.js +5 -1
- package/dist/solution/ScanOrder/utils.d.ts +13 -1
- package/dist/solution/ScanOrder/utils.js +45 -6
- package/dist/solution/VenueBooking/index.d.ts +28 -5
- package/dist/solution/VenueBooking/index.js +428 -193
- package/dist/solution/VenueBooking/types.d.ts +23 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/dist/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/dist/solution/VenueBooking/utils/resource.js +57 -21
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +33 -12
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/timeSlot.js +259 -62
- package/lib/modules/Order/index.d.ts +4 -0
- package/lib/modules/Order/index.js +14 -1
- package/lib/modules/Order/types.d.ts +9 -1
- package/lib/modules/Order/utils.d.ts +7 -0
- package/lib/modules/Order/utils.js +22 -12
- package/lib/solution/ScanOrder/index.d.ts +27 -3
- package/lib/solution/ScanOrder/index.js +409 -114
- package/lib/solution/ScanOrder/types.d.ts +34 -24
- package/lib/solution/ScanOrder/utils.d.ts +13 -1
- package/lib/solution/ScanOrder/utils.js +37 -0
- package/lib/solution/VenueBooking/index.d.ts +28 -5
- package/lib/solution/VenueBooking/index.js +193 -45
- package/lib/solution/VenueBooking/types.d.ts +23 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/lib/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/lib/solution/VenueBooking/utils/resource.js +15 -4
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +29 -12
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/timeSlot.js +182 -43
- package/package.json +1 -1
|
@@ -36,14 +36,31 @@ export interface VenueTimeSlot {
|
|
|
36
36
|
resourceFormId: number | string;
|
|
37
37
|
capacity: number | null;
|
|
38
38
|
remainingCapacity: number | null;
|
|
39
|
+
/** per-product sub-row slot 上带的 productId;外层合并 slot 不写 */
|
|
40
|
+
productId?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface VenueResourceSubRow {
|
|
43
|
+
productId: number;
|
|
44
|
+
productTitle: string;
|
|
45
|
+
price: string;
|
|
46
|
+
slots: VenueTimeSlot[];
|
|
47
|
+
}
|
|
48
|
+
export interface VenueResourceCombinedInfo {
|
|
49
|
+
resourceIds: Array<number | string>;
|
|
39
50
|
}
|
|
40
51
|
export interface VenueResourceRow {
|
|
41
52
|
resourceId: number | string;
|
|
42
53
|
resourceFormId: number | string;
|
|
43
54
|
resourceName: string;
|
|
55
|
+
/** 单商品时取唯一 mapping.productId;多商品时取第一个 mapping(兼容字段) */
|
|
44
56
|
productId: number;
|
|
45
57
|
productTitle: string;
|
|
46
58
|
slots: VenueTimeSlot[];
|
|
59
|
+
/** 多商品共享同一场地时的子行;单商品不输出 */
|
|
60
|
+
products?: VenueResourceSubRow[];
|
|
61
|
+
hasMultipleProducts?: boolean;
|
|
62
|
+
/** 当前资源是 combined_resource.status===1 时输出 */
|
|
63
|
+
combinedResource?: VenueResourceCombinedInfo;
|
|
47
64
|
}
|
|
48
65
|
export interface VenueTimeSlotGrid {
|
|
49
66
|
date: string;
|
|
@@ -57,6 +74,10 @@ export interface VenueDateSummaryItem {
|
|
|
57
74
|
totalSlots: number;
|
|
58
75
|
availableSlots: number;
|
|
59
76
|
}
|
|
77
|
+
export interface VenueResourceCombinedRaw {
|
|
78
|
+
status: number;
|
|
79
|
+
resource_ids: Array<number | string>;
|
|
80
|
+
}
|
|
60
81
|
export interface VenueResourceRawData {
|
|
61
82
|
resourceId: number | string;
|
|
62
83
|
formId: number | string;
|
|
@@ -69,6 +90,7 @@ export interface VenueResourceRawData {
|
|
|
69
90
|
end_at: string;
|
|
70
91
|
[key: string]: any;
|
|
71
92
|
}>;
|
|
93
|
+
combined_resource?: VenueResourceCombinedRaw;
|
|
72
94
|
[key: string]: any;
|
|
73
95
|
}
|
|
74
96
|
export interface ResourceProductMapping {
|
|
@@ -81,6 +103,7 @@ export interface ResourceProductMapping {
|
|
|
81
103
|
}
|
|
82
104
|
export interface VenueSlotSelection {
|
|
83
105
|
resourceId: number | string;
|
|
106
|
+
productId: number;
|
|
84
107
|
startTime: string;
|
|
85
108
|
endTime: string;
|
|
86
109
|
price: string;
|
|
@@ -5,7 +5,7 @@ export declare function buildDateRangeSummary(params: {
|
|
|
5
5
|
endDate: string;
|
|
6
6
|
config: VenueBookingSlotConfig;
|
|
7
7
|
rawResources: VenueResourceRawData[];
|
|
8
|
-
resourceProductMap: Map<number | string, ResourceProductMapping>;
|
|
8
|
+
resourceProductMap: Map<number | string, ResourceProductMapping[]>;
|
|
9
9
|
quotationModule?: QuotationModule;
|
|
10
10
|
resolveConfig?: (date: string) => VenueBookingSlotConfig;
|
|
11
11
|
}): VenueDateSummaryItem[];
|
|
@@ -45,7 +45,7 @@ function buildDateRangeSummary(params) {
|
|
|
45
45
|
resolveConfig
|
|
46
46
|
} = params;
|
|
47
47
|
const result = [];
|
|
48
|
-
const productIds = quotationModule ? [...new Set([...resourceProductMap.values()].map((m) => m.productId))] : [];
|
|
48
|
+
const productIds = quotationModule ? [...new Set([...resourceProductMap.values()].flat().map((m) => m.productId))] : [];
|
|
49
49
|
let cursor = (0, import_dayjs.default)(startDate);
|
|
50
50
|
const end = (0, import_dayjs.default)(endDate);
|
|
51
51
|
while (!cursor.isAfter(end)) {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { ProductData } from '../../../modules/Product/types';
|
|
2
2
|
import type { ResourceProductMapping } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* 从商品列表里抽取所有 resource_id:
|
|
5
|
+
* - 商品绑定的 default_resource / optional_resource
|
|
6
|
+
* - 以及这些资源上声明的 combined_resource.resource_ids(若可从 product_resource 读取)
|
|
7
|
+
* 用于一次性拉到子资源数据,判定组合资源的可用性。
|
|
8
|
+
*/
|
|
3
9
|
export declare function extractResourceIds(products: ProductData[]): number[];
|
|
4
|
-
|
|
10
|
+
/**
|
|
11
|
+
* 每个 resourceId 可以被多个商品共享(同一场地关联多种运动商品)。
|
|
12
|
+
* Map 的 value 改为 ResourceProductMapping[],按商品出现顺序保留。
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildResourceProductMap(products: ProductData[]): Map<number | string, ResourceProductMapping[]>;
|
|
@@ -39,6 +39,13 @@ function extractResourceIds(products) {
|
|
|
39
39
|
if (typeof id === "number" && id > 0)
|
|
40
40
|
ids.add(id);
|
|
41
41
|
}
|
|
42
|
+
const combined = resource.combined_resource;
|
|
43
|
+
if (combined && combined.status === 1 && Array.isArray(combined.resource_ids)) {
|
|
44
|
+
for (const id of combined.resource_ids) {
|
|
45
|
+
if (typeof id === "number" && id > 0)
|
|
46
|
+
ids.add(id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
return Array.from(ids);
|
|
@@ -58,16 +65,20 @@ function buildResourceProductMap(products) {
|
|
|
58
65
|
...resource.optional_resource || []
|
|
59
66
|
];
|
|
60
67
|
for (const resId of allResourceIds) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
map.set(resId, {
|
|
68
|
+
const existing = map.get(resId);
|
|
69
|
+
const mapping = {
|
|
64
70
|
productId: product.id,
|
|
65
71
|
productTitle: product.title,
|
|
66
72
|
resourceName: resource.title,
|
|
67
73
|
formId: resource.id,
|
|
68
74
|
price: product.price || "0.00",
|
|
69
75
|
resourceType: resource.type || "single"
|
|
70
|
-
}
|
|
76
|
+
};
|
|
77
|
+
if (!existing) {
|
|
78
|
+
map.set(resId, [mapping]);
|
|
79
|
+
} else if (!existing.some((m) => m.productId === mapping.productId)) {
|
|
80
|
+
existing.push(mapping);
|
|
81
|
+
}
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
@@ -19,6 +19,11 @@ export interface BuildVenueBookingParams {
|
|
|
19
19
|
rawResource: VenueResourceRawData | undefined;
|
|
20
20
|
bookingUuid: string;
|
|
21
21
|
productUid: string;
|
|
22
|
+
/**
|
|
23
|
+
* 若当前 resource 是组合资源(combined_resource.status === 1),传入其子资源的原始数据,
|
|
24
|
+
* 将作为 resources[0].children 写入 booking,结构与父 resource 保持一致。
|
|
25
|
+
*/
|
|
26
|
+
childResources?: VenueResourceRawData[];
|
|
22
27
|
}
|
|
23
28
|
export declare function buildVenueBookingEntry(params: BuildVenueBookingParams): Record<string, any>;
|
|
24
29
|
/**
|
|
@@ -107,10 +107,32 @@ function buildPriceBreakdown(params) {
|
|
|
107
107
|
return entries;
|
|
108
108
|
}
|
|
109
109
|
function buildVenueBookingEntry(params) {
|
|
110
|
-
const { group, resourceId, mapping, rawResource, bookingUuid, productUid } = params;
|
|
110
|
+
const { group, resourceId, mapping, rawResource, bookingUuid, productUid, childResources } = params;
|
|
111
111
|
const startMoment = (0, import_dayjs.default)(group.startTime, "YYYY-MM-DD HH:mm");
|
|
112
112
|
const endMoment = (0, import_dayjs.default)(group.endTime, "YYYY-MM-DD HH:mm");
|
|
113
113
|
const duration = endMoment.diff(startMoment, "minute");
|
|
114
|
+
const resourceEntry = {
|
|
115
|
+
relation_type: "form",
|
|
116
|
+
like_status: "common",
|
|
117
|
+
id: resourceId,
|
|
118
|
+
main_field: mapping.resourceName,
|
|
119
|
+
form_id: (rawResource == null ? void 0 : rawResource.form_id) ?? mapping.formId,
|
|
120
|
+
relation_id: resourceId,
|
|
121
|
+
capacity: 1,
|
|
122
|
+
metadata: {}
|
|
123
|
+
};
|
|
124
|
+
if (childResources && childResources.length) {
|
|
125
|
+
resourceEntry.children = childResources.map((child) => ({
|
|
126
|
+
relation_type: "form",
|
|
127
|
+
like_status: "common",
|
|
128
|
+
id: child.resourceId,
|
|
129
|
+
main_field: child.main_field || "",
|
|
130
|
+
form_id: child.form_id ?? child.formId,
|
|
131
|
+
relation_id: child.resourceId,
|
|
132
|
+
capacity: child.capacity ?? 1,
|
|
133
|
+
metadata: {}
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
114
136
|
return {
|
|
115
137
|
schedule_event_id: null,
|
|
116
138
|
appointment_status: "new",
|
|
@@ -126,23 +148,15 @@ function buildVenueBookingEntry(params) {
|
|
|
126
148
|
is_all: false,
|
|
127
149
|
schedule_id: 0,
|
|
128
150
|
product_uid: productUid,
|
|
129
|
-
resources: [
|
|
130
|
-
relation_type: "form",
|
|
131
|
-
like_status: "common",
|
|
132
|
-
id: resourceId,
|
|
133
|
-
main_field: mapping.resourceName,
|
|
134
|
-
form_id: (rawResource == null ? void 0 : rawResource.form_id) ?? mapping.formId,
|
|
135
|
-
relation_id: resourceId,
|
|
136
|
-
capacity: 1,
|
|
137
|
-
metadata: {}
|
|
138
|
-
}],
|
|
151
|
+
resources: [resourceEntry],
|
|
139
152
|
relation_products: [],
|
|
140
153
|
relation_forms: [],
|
|
141
154
|
holder: null,
|
|
142
155
|
metadata: {
|
|
143
156
|
unique_identification_number: bookingUuid,
|
|
144
157
|
venue_booking: true,
|
|
145
|
-
resource_id: resourceId
|
|
158
|
+
resource_id: resourceId,
|
|
159
|
+
product_id: mapping.productId
|
|
146
160
|
}
|
|
147
161
|
};
|
|
148
162
|
}
|
|
@@ -155,6 +169,7 @@ function expandMergedSlotToIndividual(product, slotDurationMinutes) {
|
|
|
155
169
|
const endTime = meta.end_time;
|
|
156
170
|
if (!startTime || !endTime)
|
|
157
171
|
return [];
|
|
172
|
+
const productId = Number(product.product_id);
|
|
158
173
|
const breakdown = meta.price_breakdown;
|
|
159
174
|
const result = [];
|
|
160
175
|
let cursor = (0, import_dayjs.default)(startTime, "YYYY-MM-DD HH:mm");
|
|
@@ -175,6 +190,7 @@ function expandMergedSlotToIndividual(product, slotDurationMinutes) {
|
|
|
175
190
|
const price = priceMap.get(hm) ?? 0;
|
|
176
191
|
result.push({
|
|
177
192
|
resourceId,
|
|
193
|
+
productId,
|
|
178
194
|
startTime: cursor.format("YYYY-MM-DD HH:mm"),
|
|
179
195
|
endTime: slotEnd.format("YYYY-MM-DD HH:mm"),
|
|
180
196
|
price: new import_decimal.default(price).toFixed(2)
|
|
@@ -189,6 +205,7 @@ function expandMergedSlotToIndividual(product, slotDurationMinutes) {
|
|
|
189
205
|
const slotEnd = cursor.add(slotDurationMinutes, "minute");
|
|
190
206
|
result.push({
|
|
191
207
|
resourceId,
|
|
208
|
+
productId,
|
|
192
209
|
startTime: cursor.format("YYYY-MM-DD HH:mm"),
|
|
193
210
|
endTime: slotEnd.format("YYYY-MM-DD HH:mm"),
|
|
194
211
|
price: perSlotPrice
|
|
@@ -26,7 +26,7 @@ export declare function buildTimeSlotGrid(params: {
|
|
|
26
26
|
date: string;
|
|
27
27
|
config: VenueBookingSlotConfig;
|
|
28
28
|
rawResources: VenueResourceRawData[];
|
|
29
|
-
resourceProductMap: Map<number | string, ResourceProductMapping>;
|
|
29
|
+
resourceProductMap: Map<number | string, ResourceProductMapping[]>;
|
|
30
30
|
quotationPriceMap?: Map<string, string | null>;
|
|
31
31
|
}): VenueTimeSlotGrid;
|
|
32
32
|
export {};
|
|
@@ -38,6 +38,7 @@ __export(timeSlot_exports, {
|
|
|
38
38
|
});
|
|
39
39
|
module.exports = __toCommonJS(timeSlot_exports);
|
|
40
40
|
var import_dayjs = __toESM(require("dayjs"));
|
|
41
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
41
42
|
function isBusinessHoursCrossDay(config) {
|
|
42
43
|
const [startH, startM] = config.businessStartTime.split(":").map(Number);
|
|
43
44
|
const [endH, endM] = config.businessEndTime.split(":").map(Number);
|
|
@@ -122,64 +123,202 @@ function computeSlotStatus(params) {
|
|
|
122
123
|
return { status: "partially_occupied", remainingCapacity: remaining };
|
|
123
124
|
return { status: "available", remainingCapacity: remaining };
|
|
124
125
|
}
|
|
126
|
+
function mergeSubSlots(subSlots) {
|
|
127
|
+
const priority = {
|
|
128
|
+
available: 5,
|
|
129
|
+
partially_occupied: 4,
|
|
130
|
+
occupied: 3,
|
|
131
|
+
unavailable: 2,
|
|
132
|
+
past: 1
|
|
133
|
+
};
|
|
134
|
+
let best = subSlots[0];
|
|
135
|
+
for (const slot of subSlots) {
|
|
136
|
+
if ((priority[slot.status] ?? 0) > (priority[best.status] ?? 0))
|
|
137
|
+
best = slot;
|
|
138
|
+
}
|
|
139
|
+
let minPrice = null;
|
|
140
|
+
for (const slot of subSlots) {
|
|
141
|
+
if (slot.price == null)
|
|
142
|
+
continue;
|
|
143
|
+
const decimal = new import_decimal.default(slot.price || "0");
|
|
144
|
+
if (!minPrice || decimal.lt(minPrice))
|
|
145
|
+
minPrice = decimal;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
startTime: best.startTime,
|
|
149
|
+
endTime: best.endTime,
|
|
150
|
+
status: best.status,
|
|
151
|
+
price: minPrice ? minPrice.toFixed(2) : null,
|
|
152
|
+
resourceId: best.resourceId,
|
|
153
|
+
resourceFormId: best.resourceFormId,
|
|
154
|
+
capacity: best.capacity,
|
|
155
|
+
remainingCapacity: best.remainingCapacity
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function buildProductSlots(params) {
|
|
159
|
+
const {
|
|
160
|
+
date,
|
|
161
|
+
config,
|
|
162
|
+
resource,
|
|
163
|
+
mapping,
|
|
164
|
+
timeLabels,
|
|
165
|
+
timesForDate,
|
|
166
|
+
events,
|
|
167
|
+
resCapacity,
|
|
168
|
+
now,
|
|
169
|
+
crossDay,
|
|
170
|
+
quotationPriceMap,
|
|
171
|
+
childRawResources,
|
|
172
|
+
childTimesCache,
|
|
173
|
+
childEventsCache,
|
|
174
|
+
config_businessStartHour
|
|
175
|
+
} = params;
|
|
176
|
+
const slots = [];
|
|
177
|
+
for (const label of timeLabels) {
|
|
178
|
+
const [h] = label.split(":").map(Number);
|
|
179
|
+
const isNextDay = crossDay && h < config_businessStartHour;
|
|
180
|
+
const slotDate = isNextDay ? (0, import_dayjs.default)(date).add(1, "day").format("YYYY-MM-DD") : date;
|
|
181
|
+
const slotStart = (0, import_dayjs.default)(`${slotDate} ${label}`);
|
|
182
|
+
const slotEnd = slotStart.add(config.slotDurationMinutes, "minute");
|
|
183
|
+
let status;
|
|
184
|
+
let remainingCapacity = null;
|
|
185
|
+
if (slotStart.isBefore(now)) {
|
|
186
|
+
status = "past";
|
|
187
|
+
} else if (!isSlotWithinResourceTimes(slotStart, slotEnd, timesForDate)) {
|
|
188
|
+
status = "unavailable";
|
|
189
|
+
} else {
|
|
190
|
+
const result = computeSlotStatus({
|
|
191
|
+
slotStart,
|
|
192
|
+
slotEnd,
|
|
193
|
+
events,
|
|
194
|
+
resourceType: mapping.resourceType,
|
|
195
|
+
capacity: resCapacity
|
|
196
|
+
});
|
|
197
|
+
status = result.status;
|
|
198
|
+
remainingCapacity = result.remainingCapacity;
|
|
199
|
+
if (status !== "occupied" && childRawResources && childRawResources.length) {
|
|
200
|
+
for (let i = 0; i < childRawResources.length; i++) {
|
|
201
|
+
const child = childRawResources[i];
|
|
202
|
+
const childTimes = (childTimesCache == null ? void 0 : childTimesCache[i]) ?? getResourceTimesForDate(child, date, config);
|
|
203
|
+
const childEvents = (childEventsCache == null ? void 0 : childEventsCache[i]) ?? collectEventsForDate(child, date, config);
|
|
204
|
+
if (!isSlotWithinResourceTimes(slotStart, slotEnd, childTimes)) {
|
|
205
|
+
status = "unavailable";
|
|
206
|
+
remainingCapacity = null;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const childType = child.type || "single";
|
|
210
|
+
const childResult = computeSlotStatus({
|
|
211
|
+
slotStart,
|
|
212
|
+
slotEnd,
|
|
213
|
+
events: childEvents,
|
|
214
|
+
resourceType: childType,
|
|
215
|
+
capacity: child.capacity ?? 1
|
|
216
|
+
});
|
|
217
|
+
if (childResult.status === "occupied") {
|
|
218
|
+
status = "occupied";
|
|
219
|
+
remainingCapacity = 0;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (childResult.status === "partially_occupied" && status === "available") {
|
|
223
|
+
status = "partially_occupied";
|
|
224
|
+
remainingCapacity = Math.min(
|
|
225
|
+
remainingCapacity ?? childResult.remainingCapacity,
|
|
226
|
+
childResult.remainingCapacity
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const isBookable = status === "available" || status === "partially_occupied";
|
|
233
|
+
const slotStartStr = slotStart.format("YYYY-MM-DD HH:mm");
|
|
234
|
+
slots.push({
|
|
235
|
+
startTime: slotStartStr,
|
|
236
|
+
endTime: slotEnd.format("YYYY-MM-DD HH:mm"),
|
|
237
|
+
status,
|
|
238
|
+
price: isBookable ? (quotationPriceMap == null ? void 0 : quotationPriceMap.get(`${mapping.productId}:${slotStartStr}`)) ?? mapping.price : null,
|
|
239
|
+
resourceId: resource.resourceId,
|
|
240
|
+
resourceFormId: resource.formId,
|
|
241
|
+
capacity: status === "past" || status === "unavailable" ? null : resCapacity,
|
|
242
|
+
remainingCapacity,
|
|
243
|
+
productId: mapping.productId
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return slots;
|
|
247
|
+
}
|
|
125
248
|
function buildTimeSlotGrid(params) {
|
|
126
249
|
const { date, config, rawResources, resourceProductMap, quotationPriceMap } = params;
|
|
127
250
|
const timeLabels = generateTimeLabels(config);
|
|
128
251
|
const now = (0, import_dayjs.default)();
|
|
129
252
|
const crossDay = isBusinessHoursCrossDay(config);
|
|
253
|
+
const config_businessStartHour = Number(config.businessStartTime.split(":")[0]);
|
|
254
|
+
const rawResourceById = /* @__PURE__ */ new Map();
|
|
255
|
+
for (const item of rawResources)
|
|
256
|
+
rawResourceById.set(item.resourceId, item);
|
|
130
257
|
const resources = [];
|
|
131
258
|
for (const resource of rawResources) {
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
259
|
+
const mappings = resourceProductMap.get(resource.resourceId);
|
|
260
|
+
if (!mappings || !mappings.length)
|
|
134
261
|
continue;
|
|
135
262
|
const timesForDate = getResourceTimesForDate(resource, date, config);
|
|
136
263
|
const events = collectEventsForDate(resource, date, config);
|
|
137
264
|
const resCapacity = resource.capacity ?? 1;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
remainingCapacity = result.remainingCapacity;
|
|
161
|
-
}
|
|
162
|
-
const isBookable = status === "available" || status === "partially_occupied";
|
|
163
|
-
const slotStartStr = slotStart.format("YYYY-MM-DD HH:mm");
|
|
164
|
-
slots.push({
|
|
165
|
-
startTime: slotStartStr,
|
|
166
|
-
endTime: slotEnd.format("YYYY-MM-DD HH:mm"),
|
|
167
|
-
status,
|
|
168
|
-
price: isBookable ? (quotationPriceMap == null ? void 0 : quotationPriceMap.get(`${mapping.productId}:${slotStartStr}`)) ?? mapping.price : null,
|
|
169
|
-
resourceId: resource.resourceId,
|
|
170
|
-
resourceFormId: resource.formId,
|
|
171
|
-
capacity: status === "past" || status === "unavailable" ? null : resCapacity,
|
|
172
|
-
remainingCapacity
|
|
265
|
+
const combined = resource.combined_resource;
|
|
266
|
+
const isCombined = !!(combined && combined.status === 1 && Array.isArray(combined.resource_ids) && combined.resource_ids.length);
|
|
267
|
+
const childRawResources = isCombined ? combined.resource_ids.map((id) => rawResourceById.get(id)).filter((r) => !!r) : void 0;
|
|
268
|
+
const childTimesCache = childRawResources == null ? void 0 : childRawResources.map((child) => getResourceTimesForDate(child, date, config));
|
|
269
|
+
const childEventsCache = childRawResources == null ? void 0 : childRawResources.map((child) => collectEventsForDate(child, date, config));
|
|
270
|
+
const subRows = mappings.map((mapping) => {
|
|
271
|
+
const productSlots = buildProductSlots({
|
|
272
|
+
date,
|
|
273
|
+
config,
|
|
274
|
+
resource,
|
|
275
|
+
mapping,
|
|
276
|
+
timeLabels,
|
|
277
|
+
timesForDate,
|
|
278
|
+
events,
|
|
279
|
+
resCapacity,
|
|
280
|
+
now,
|
|
281
|
+
crossDay,
|
|
282
|
+
quotationPriceMap,
|
|
283
|
+
childRawResources,
|
|
284
|
+
childTimesCache,
|
|
285
|
+
childEventsCache,
|
|
286
|
+
config_businessStartHour
|
|
173
287
|
});
|
|
288
|
+
return {
|
|
289
|
+
productId: mapping.productId,
|
|
290
|
+
productTitle: mapping.productTitle,
|
|
291
|
+
price: mapping.price,
|
|
292
|
+
slots: productSlots
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
const hasMultipleProducts = subRows.length > 1;
|
|
296
|
+
const primary = mappings[0];
|
|
297
|
+
let outerSlots;
|
|
298
|
+
if (hasMultipleProducts) {
|
|
299
|
+
outerSlots = timeLabels.map((_label, slotIndex) => {
|
|
300
|
+
const group = subRows.map((row2) => row2.slots[slotIndex]);
|
|
301
|
+
return mergeSubSlots(group);
|
|
302
|
+
});
|
|
303
|
+
} else {
|
|
304
|
+
outerSlots = subRows[0].slots.map((slot) => ({ ...slot, productId: void 0 }));
|
|
174
305
|
}
|
|
175
|
-
|
|
306
|
+
const row = {
|
|
176
307
|
resourceId: resource.resourceId,
|
|
177
308
|
resourceFormId: resource.formId,
|
|
178
|
-
resourceName: resource.main_field ||
|
|
179
|
-
productId:
|
|
180
|
-
productTitle:
|
|
181
|
-
slots
|
|
182
|
-
}
|
|
309
|
+
resourceName: resource.main_field || primary.resourceName,
|
|
310
|
+
productId: primary.productId,
|
|
311
|
+
productTitle: primary.productTitle,
|
|
312
|
+
slots: outerSlots
|
|
313
|
+
};
|
|
314
|
+
if (hasMultipleProducts) {
|
|
315
|
+
row.products = subRows;
|
|
316
|
+
row.hasMultipleProducts = true;
|
|
317
|
+
}
|
|
318
|
+
if (isCombined && combined) {
|
|
319
|
+
row.combinedResource = { resourceIds: [...combined.resource_ids] };
|
|
320
|
+
}
|
|
321
|
+
resources.push(row);
|
|
183
322
|
}
|
|
184
323
|
const productOrder = /* @__PURE__ */ new Map();
|
|
185
324
|
for (const row of resources) {
|