@pisell/pisellos 2.1.128 → 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.
Files changed (46) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/Order/index.d.ts +4 -0
  3. package/dist/modules/Order/index.js +18 -1
  4. package/dist/modules/Order/types.d.ts +9 -1
  5. package/dist/modules/Order/utils.d.ts +7 -0
  6. package/dist/modules/Order/utils.js +27 -11
  7. package/dist/solution/ScanOrder/index.d.ts +27 -3
  8. package/dist/solution/ScanOrder/index.js +865 -481
  9. package/dist/solution/ScanOrder/types.d.ts +34 -24
  10. package/dist/solution/ScanOrder/types.js +5 -1
  11. package/dist/solution/ScanOrder/utils.d.ts +13 -1
  12. package/dist/solution/ScanOrder/utils.js +45 -6
  13. package/dist/solution/VenueBooking/index.d.ts +28 -5
  14. package/dist/solution/VenueBooking/index.js +463 -227
  15. package/dist/solution/VenueBooking/types.d.ts +23 -0
  16. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
  17. package/dist/solution/VenueBooking/utils/dateSummary.js +1 -1
  18. package/dist/solution/VenueBooking/utils/resource.d.ts +11 -1
  19. package/dist/solution/VenueBooking/utils/resource.js +57 -21
  20. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
  21. package/dist/solution/VenueBooking/utils/slotMerge.js +33 -12
  22. package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
  23. package/dist/solution/VenueBooking/utils/timeSlot.js +259 -62
  24. package/lib/model/strategy/adapter/promotion/index.js +49 -0
  25. package/lib/modules/Order/index.d.ts +4 -0
  26. package/lib/modules/Order/index.js +14 -1
  27. package/lib/modules/Order/types.d.ts +9 -1
  28. package/lib/modules/Order/utils.d.ts +7 -0
  29. package/lib/modules/Order/utils.js +22 -12
  30. package/lib/solution/ScanOrder/index.d.ts +27 -3
  31. package/lib/solution/ScanOrder/index.js +409 -114
  32. package/lib/solution/ScanOrder/types.d.ts +34 -24
  33. package/lib/solution/ScanOrder/utils.d.ts +13 -1
  34. package/lib/solution/ScanOrder/utils.js +37 -0
  35. package/lib/solution/VenueBooking/index.d.ts +28 -5
  36. package/lib/solution/VenueBooking/index.js +203 -58
  37. package/lib/solution/VenueBooking/types.d.ts +23 -0
  38. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
  39. package/lib/solution/VenueBooking/utils/dateSummary.js +1 -1
  40. package/lib/solution/VenueBooking/utils/resource.d.ts +11 -1
  41. package/lib/solution/VenueBooking/utils/resource.js +15 -4
  42. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
  43. package/lib/solution/VenueBooking/utils/slotMerge.js +29 -12
  44. package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
  45. package/lib/solution/VenueBooking/utils/timeSlot.js +182 -43
  46. package/package.json +1 -1
@@ -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 mapping = resourceProductMap.get(resource.resourceId);
133
- if (!mapping)
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 slots = [];
139
- for (const label of timeLabels) {
140
- const [h, m] = label.split(":").map(Number);
141
- const isNextDay = crossDay && h < Number(config.businessStartTime.split(":")[0]);
142
- const slotDate = isNextDay ? (0, import_dayjs.default)(date).add(1, "day").format("YYYY-MM-DD") : date;
143
- const slotStart = (0, import_dayjs.default)(`${slotDate} ${label}`);
144
- const slotEnd = slotStart.add(config.slotDurationMinutes, "minute");
145
- let status;
146
- let remainingCapacity = null;
147
- if (slotStart.isBefore(now)) {
148
- status = "past";
149
- } else if (!isSlotWithinResourceTimes(slotStart, slotEnd, timesForDate)) {
150
- status = "unavailable";
151
- } else {
152
- const result = computeSlotStatus({
153
- slotStart,
154
- slotEnd,
155
- events,
156
- resourceType: mapping.resourceType,
157
- capacity: resCapacity
158
- });
159
- status = result.status;
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
- resources.push({
306
+ const row = {
176
307
  resourceId: resource.resourceId,
177
308
  resourceFormId: resource.formId,
178
- resourceName: resource.main_field || mapping.resourceName,
179
- productId: mapping.productId,
180
- productTitle: mapping.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) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.1.128",
4
+ "version": "2.1.130",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",