@pisell/pisellos 2.3.53 → 2.3.55

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 (149) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/Discount/index.d.ts +1 -0
  3. package/dist/modules/Discount/index.js +40 -11
  4. package/dist/modules/Discount/types.d.ts +1 -0
  5. package/dist/modules/Holder/index.d.ts +48 -0
  6. package/dist/modules/Holder/index.js +640 -0
  7. package/dist/modules/Holder/types.d.ts +123 -0
  8. package/dist/modules/Holder/types.js +1 -0
  9. package/dist/modules/Holder/utils.d.ts +13 -0
  10. package/dist/modules/Holder/utils.js +34 -0
  11. package/dist/modules/OpenData/index.js +2 -2
  12. package/dist/modules/Order/index.d.ts +4 -2
  13. package/dist/modules/Order/index.js +66 -34
  14. package/dist/modules/Order/types.d.ts +9 -0
  15. package/dist/modules/Order/utils.d.ts +10 -0
  16. package/dist/modules/Order/utils.js +35 -3
  17. package/dist/modules/Product/types.d.ts +1 -0
  18. package/dist/modules/ProductList/index.js +34 -15
  19. package/dist/modules/Quotation/index.d.ts +1 -1
  20. package/dist/modules/Quotation/index.js +2 -2
  21. package/dist/modules/ResourcePlanner/index.d.ts +24 -0
  22. package/dist/modules/ResourcePlanner/index.js +181 -0
  23. package/dist/modules/ResourcePlanner/planner.d.ts +14 -0
  24. package/dist/modules/ResourcePlanner/planner.js +1069 -0
  25. package/dist/modules/ResourcePlanner/types.d.ts +227 -0
  26. package/dist/modules/ResourcePlanner/types.js +1 -0
  27. package/dist/modules/Rules/index.js +47 -13
  28. package/dist/modules/SalesSummary/index.js +12 -13
  29. package/dist/modules/ScanOrderLogger/index.d.ts +2 -0
  30. package/dist/modules/ScanOrderLogger/index.js +15 -2
  31. package/dist/modules/ScanOrderLogger/providers/feishu.js +45 -27
  32. package/dist/modules/ScanOrderLogger/types.d.ts +1 -0
  33. package/dist/modules/Schedule/index.d.ts +3 -1
  34. package/dist/modules/Schedule/index.js +21 -16
  35. package/dist/modules/Summary/utils.js +38 -13
  36. package/dist/modules/index.d.ts +2 -0
  37. package/dist/modules/index.js +3 -1
  38. package/dist/plugins/window.d.ts +1 -0
  39. package/dist/server/index.d.ts +14 -0
  40. package/dist/server/index.js +907 -793
  41. package/dist/solution/BaseSales/index.d.ts +7 -0
  42. package/dist/solution/BaseSales/index.js +64 -42
  43. package/dist/solution/BaseSales/utils/cartPromotion.js +4 -1
  44. package/dist/solution/BookingByStep/index.d.ts +17 -2
  45. package/dist/solution/BookingByStep/index.js +294 -215
  46. package/dist/solution/BookingByStep/types.d.ts +2 -1
  47. package/dist/solution/BookingByStep/types.js +3 -1
  48. package/dist/solution/BookingByStep/utils/capacity.js +17 -1
  49. package/dist/solution/BookingByStep/utils/timeslots.d.ts +3 -1
  50. package/dist/solution/BookingByStep/utils/timeslots.js +19 -12
  51. package/dist/solution/BookingTicket/index.d.ts +14 -2
  52. package/dist/solution/BookingTicket/index.js +2 -2
  53. package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  54. package/dist/solution/BookingTicket/utils/scan/handleScan.js +10 -2
  55. package/dist/solution/ScanOrder/index.d.ts +26 -14
  56. package/dist/solution/ScanOrder/index.js +1142 -739
  57. package/dist/solution/ScanOrder/types.d.ts +21 -1
  58. package/dist/solution/ScanOrder/utils.d.ts +8 -0
  59. package/dist/solution/ScanOrder/utils.js +66 -25
  60. package/dist/solution/ShopDiscount/index.d.ts +1 -0
  61. package/dist/solution/ShopDiscount/index.js +125 -87
  62. package/dist/solution/UnifiedBookingSales/index.d.ts +179 -0
  63. package/dist/solution/UnifiedBookingSales/index.js +1892 -0
  64. package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
  65. package/dist/solution/UnifiedBookingSales/types.js +11 -0
  66. package/dist/solution/VenueBooking/index.d.ts +39 -11
  67. package/dist/solution/VenueBooking/index.js +1167 -841
  68. package/dist/solution/VenueBooking/types.d.ts +3 -0
  69. package/dist/solution/VenueBooking/utils/resource.js +1 -0
  70. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +4 -4
  71. package/dist/solution/VenueBooking/utils/slotMerge.js +10 -5
  72. package/dist/solution/index.d.ts +1 -0
  73. package/dist/solution/index.js +2 -1
  74. package/dist/types/index.d.ts +1 -0
  75. package/lib/model/strategy/adapter/promotion/index.js +51 -0
  76. package/lib/modules/Discount/index.d.ts +1 -0
  77. package/lib/modules/Discount/index.js +9 -0
  78. package/lib/modules/Discount/types.d.ts +1 -0
  79. package/lib/modules/Holder/index.d.ts +48 -0
  80. package/lib/modules/Holder/index.js +402 -0
  81. package/lib/modules/Holder/types.d.ts +123 -0
  82. package/lib/modules/Holder/types.js +17 -0
  83. package/lib/modules/Holder/utils.d.ts +13 -0
  84. package/lib/modules/Holder/utils.js +71 -0
  85. package/lib/modules/OpenData/index.js +1 -1
  86. package/lib/modules/Order/index.d.ts +4 -2
  87. package/lib/modules/Order/index.js +30 -13
  88. package/lib/modules/Order/types.d.ts +9 -0
  89. package/lib/modules/Order/utils.d.ts +10 -0
  90. package/lib/modules/Order/utils.js +36 -5
  91. package/lib/modules/Product/types.d.ts +1 -0
  92. package/lib/modules/ProductList/index.js +48 -31
  93. package/lib/modules/Quotation/index.d.ts +1 -1
  94. package/lib/modules/Quotation/index.js +2 -2
  95. package/lib/modules/ResourcePlanner/index.d.ts +24 -0
  96. package/lib/modules/ResourcePlanner/index.js +148 -0
  97. package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
  98. package/lib/modules/ResourcePlanner/planner.js +933 -0
  99. package/lib/modules/ResourcePlanner/types.d.ts +227 -0
  100. package/lib/modules/ResourcePlanner/types.js +17 -0
  101. package/lib/modules/Rules/index.js +33 -16
  102. package/lib/modules/SalesSummary/index.js +6 -7
  103. package/lib/modules/ScanOrderLogger/index.d.ts +2 -0
  104. package/lib/modules/ScanOrderLogger/index.js +13 -1
  105. package/lib/modules/ScanOrderLogger/providers/feishu.js +15 -6
  106. package/lib/modules/ScanOrderLogger/types.d.ts +1 -0
  107. package/lib/modules/Schedule/index.d.ts +3 -1
  108. package/lib/modules/Schedule/index.js +10 -8
  109. package/lib/modules/Summary/utils.js +21 -1
  110. package/lib/modules/index.d.ts +2 -0
  111. package/lib/modules/index.js +5 -1
  112. package/lib/plugins/window.d.ts +1 -0
  113. package/lib/server/index.d.ts +14 -0
  114. package/lib/server/index.js +72 -15
  115. package/lib/solution/BaseSales/index.d.ts +7 -0
  116. package/lib/solution/BaseSales/index.js +24 -8
  117. package/lib/solution/BaseSales/utils/cartPromotion.js +3 -0
  118. package/lib/solution/BookingByStep/index.d.ts +17 -2
  119. package/lib/solution/BookingByStep/index.js +60 -18
  120. package/lib/solution/BookingByStep/types.d.ts +2 -1
  121. package/lib/solution/BookingByStep/types.js +5 -0
  122. package/lib/solution/BookingByStep/utils/capacity.js +20 -1
  123. package/lib/solution/BookingByStep/utils/timeslots.d.ts +3 -1
  124. package/lib/solution/BookingByStep/utils/timeslots.js +19 -11
  125. package/lib/solution/BookingTicket/index.d.ts +14 -2
  126. package/lib/solution/BookingTicket/index.js +10 -2
  127. package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
  128. package/lib/solution/BookingTicket/utils/scan/handleScan.js +2 -2
  129. package/lib/solution/ScanOrder/index.d.ts +26 -14
  130. package/lib/solution/ScanOrder/index.js +449 -182
  131. package/lib/solution/ScanOrder/types.d.ts +21 -1
  132. package/lib/solution/ScanOrder/utils.d.ts +8 -0
  133. package/lib/solution/ScanOrder/utils.js +31 -0
  134. package/lib/solution/ShopDiscount/index.d.ts +1 -0
  135. package/lib/solution/ShopDiscount/index.js +14 -0
  136. package/lib/solution/UnifiedBookingSales/index.d.ts +179 -0
  137. package/lib/solution/UnifiedBookingSales/index.js +1085 -0
  138. package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
  139. package/lib/solution/UnifiedBookingSales/types.js +33 -0
  140. package/lib/solution/VenueBooking/index.d.ts +39 -11
  141. package/lib/solution/VenueBooking/index.js +322 -110
  142. package/lib/solution/VenueBooking/types.d.ts +3 -0
  143. package/lib/solution/VenueBooking/utils/resource.js +1 -0
  144. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +4 -4
  145. package/lib/solution/VenueBooking/utils/slotMerge.js +6 -4
  146. package/lib/solution/index.d.ts +1 -0
  147. package/lib/solution/index.js +3 -1
  148. package/lib/types/index.d.ts +1 -0
  149. package/package.json +1 -1
@@ -0,0 +1,1085 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/solution/UnifiedBookingSales/index.ts
31
+ var UnifiedBookingSales_exports = {};
32
+ __export(UnifiedBookingSales_exports, {
33
+ UnifiedBookingSales: () => UnifiedBookingSales,
34
+ UnifiedBookingSalesImpl: () => UnifiedBookingSalesImpl,
35
+ buildBookingFromAssignment: () => buildBookingFromAssignment,
36
+ buildBookingFromAssignments: () => buildBookingFromAssignments,
37
+ buildPlannerDiscountResult: () => buildPlannerDiscountResult,
38
+ buildPlannerLineIdentity: () => buildPlannerLineIdentity,
39
+ buildPlannerResourcesFromV2: () => buildPlannerResourcesFromV2,
40
+ collectPlannerResourceIds: () => collectPlannerResourceIds,
41
+ normalizeProductForPlanner: () => normalizeProductForPlanner
42
+ });
43
+ module.exports = __toCommonJS(UnifiedBookingSales_exports);
44
+ var import_dayjs = __toESM(require("dayjs"));
45
+ var import_modules = require("../../modules");
46
+ var import_getDateIsInSchedule = require("../../modules/Schedule/getDateIsInSchedule");
47
+ var import_types = require("./types");
48
+ var import_BookingTicket = require("../BookingTicket");
49
+ __reExport(UnifiedBookingSales_exports, require("./types"), module.exports);
50
+ var OPEN_DATA_SECTION_CODES = ["sale", "reservation", "fulfillment", "menu", "workflow"];
51
+ var OPEN_DATA_CACHE_TTL = 5 * 60 * 1e3;
52
+ function pickLocalizedText(value) {
53
+ if (!value)
54
+ return void 0;
55
+ if (typeof value === "string")
56
+ return value;
57
+ if (typeof value === "object") {
58
+ return value.en || value.zh || value.default || Object.values(value)[0];
59
+ }
60
+ return String(value);
61
+ }
62
+ function resolvePlannerDiscountId(discount) {
63
+ var _a;
64
+ return ((_a = discount.discount) == null ? void 0 : _a.resource_id) ?? discount.resource_id ?? discount.id;
65
+ }
66
+ function resolvePlannerDiscountTitle(discount) {
67
+ var _a;
68
+ return pickLocalizedText(
69
+ discount.name || discount.format_title || ((_a = discount.discount) == null ? void 0 : _a.title) || discount.title
70
+ );
71
+ }
72
+ function isPlannerDiscountAvailable(discount) {
73
+ var _a;
74
+ return discount.isDisabled !== true && discount.isAvailable !== false && ((_a = discount.config) == null ? void 0 : _a.isAvailable) !== false;
75
+ }
76
+ function buildPlannerDiscountResult(params) {
77
+ const {
78
+ customerId,
79
+ discountList,
80
+ products,
81
+ summaryBefore,
82
+ summaryAfter,
83
+ status,
84
+ preferredDiscountId,
85
+ preferredDiscountFound
86
+ } = params;
87
+ const items = (discountList || []).reduce((result, discount) => {
88
+ const id = resolvePlannerDiscountId(discount);
89
+ if (id !== void 0 && id !== null) {
90
+ result.push({
91
+ id,
92
+ name: resolvePlannerDiscountTitle(discount),
93
+ type: discount.tag || discount.type,
94
+ available: isPlannerDiscountAvailable(discount),
95
+ selected: discount.isSelected === true,
96
+ savedAmount: Number(discount.savedAmount || 0) || void 0,
97
+ unavailableReason: discount.unavailableReason || discount.reason
98
+ });
99
+ }
100
+ return result;
101
+ }, []);
102
+ const discountNameById = new Map(items.map((item) => [String(item.id), item.name]));
103
+ const applications = [];
104
+ (products || []).forEach((product) => {
105
+ const productDiscounts = [
106
+ ...Array.isArray(product.discount_list) ? product.discount_list : [],
107
+ ...Array.isArray(product.product_bundle) ? product.product_bundle.flatMap((bundle) => (bundle == null ? void 0 : bundle.discount_list) || []) : []
108
+ ];
109
+ productDiscounts.forEach((discount) => {
110
+ var _a;
111
+ const type = discount.tag || discount.type;
112
+ if (!["good_pass", "discount_card", "product_discount_card"].includes(type))
113
+ return;
114
+ const discountId = resolvePlannerDiscountId(discount);
115
+ applications.push({
116
+ discountId,
117
+ discountName: discountId === void 0 ? void 0 : discountNameById.get(String(discountId)),
118
+ discountType: type,
119
+ productId: product.product_id ?? product.id,
120
+ productTitle: pickLocalizedText(product.title || product.name || product.product_title),
121
+ productUid: ((_a = product.metadata) == null ? void 0 : _a.unique_identification_number) || product.unique_identification_number,
122
+ amount: Number(discount.amount || 0)
123
+ });
124
+ });
125
+ });
126
+ return {
127
+ status,
128
+ customerId,
129
+ preferredDiscountFound,
130
+ preferredDiscountApplied: preferredDiscountId === void 0 || preferredDiscountId === null ? void 0 : items.some((item) => String(item.id) === String(preferredDiscountId) && item.selected) && applications.some((item) => String(item.discountId) === String(preferredDiscountId)),
131
+ availableDiscounts: items.filter((item) => item.available),
132
+ selectedDiscounts: items.filter((item) => item.selected),
133
+ applications,
134
+ summaryBefore,
135
+ summaryAfter
136
+ };
137
+ }
138
+ function toNumberId(value) {
139
+ if (value === void 0 || value === null || value === "")
140
+ return void 0;
141
+ const parsed = Number(value);
142
+ return Number.isFinite(parsed) ? parsed : String(value);
143
+ }
144
+ function resolveProductKind(product) {
145
+ var _a, _b, _c;
146
+ if (product.duration)
147
+ return "duration";
148
+ if (["session_product", "session_ticket"].includes(product.extension_type))
149
+ return "session";
150
+ if (((_a = product.cartDetailValue) == null ? void 0 : _a.session) || ((_c = (_b = product._extend) == null ? void 0 : _b.other) == null ? void 0 : _c.session))
151
+ return "session";
152
+ if (Array.isArray(product["schedule.ids"]) && product["schedule.ids"].length > 0)
153
+ return "session";
154
+ return "normal";
155
+ }
156
+ function normalizeSchedule(product) {
157
+ var _a, _b, _c;
158
+ const session = ((_a = product.cartDetailValue) == null ? void 0 : _a.session) || ((_c = (_b = product._extend) == null ? void 0 : _b.other) == null ? void 0 : _c.session) || product.session;
159
+ if ((session == null ? void 0 : session.start_at) && (session == null ? void 0 : session.end_at)) {
160
+ return [{ start_at: session.start_at, end_at: session.end_at }];
161
+ }
162
+ const schedules = product.schedules || product.schedule || product._schedule;
163
+ if (Array.isArray(schedules)) {
164
+ return schedules.map((item) => ({
165
+ start_at: item.start_at || item.start || item.start_time,
166
+ end_at: item.end_at || item.end || item.end_time
167
+ })).filter((item) => item.start_at && item.end_at);
168
+ }
169
+ return [];
170
+ }
171
+ function normalizeRequirements(product) {
172
+ var _a;
173
+ const resources = (_a = product.product_resource) == null ? void 0 : _a.resources;
174
+ if (!Array.isArray(resources))
175
+ return [];
176
+ return resources.filter((resource) => (resource == null ? void 0 : resource.status) !== 0).map((resource) => {
177
+ var _a2, _b;
178
+ return {
179
+ formId: toNumberId(resource.id),
180
+ resourceIds: [
181
+ ...Array.isArray(resource.default_resource) ? resource.default_resource : [],
182
+ ...Array.isArray(resource.optional_resource) ? resource.optional_resource : []
183
+ ].map((id) => toNumberId(id)).filter((id) => id !== void 0),
184
+ resourceType: resource.type || ((_a2 = resource.select_type) == null ? void 0 : _a2.type) || "single",
185
+ required: resource.status === 1,
186
+ capacityRequired: Number(((_b = product.capacity) == null ? void 0 : _b.default) || product.capacity || 1) || 1
187
+ };
188
+ });
189
+ }
190
+ function normalizeProductForPlanner(product) {
191
+ var _a, _b;
192
+ const raw = product;
193
+ const kind = resolveProductKind(raw);
194
+ return {
195
+ id: raw.id ?? raw.product_id,
196
+ title: pickLocalizedText(raw.title || raw.name || raw.product_title),
197
+ kind,
198
+ durationMinutes: Number(((_a = raw.duration) == null ? void 0 : _a.value) || raw.duration || raw.service_duration || 30) || 30,
199
+ schedule: normalizeSchedule(raw),
200
+ resourceRequirements: normalizeRequirements(raw),
201
+ capacityRequired: Number(((_b = raw.capacity) == null ? void 0 : _b.default) || raw.capacity || 1) || 1,
202
+ quantity: Number(raw.num || raw.quantity || 1) || 1,
203
+ price: raw.price ?? raw.selling_price ?? raw.base_price,
204
+ raw
205
+ };
206
+ }
207
+ function normalizeResourceType(value) {
208
+ if (value === "multiple" || value === "capacity")
209
+ return value;
210
+ return "single";
211
+ }
212
+ function normalizeResource(raw) {
213
+ var _a, _b;
214
+ const id = raw.resourceId ?? raw.resource_id ?? raw.id;
215
+ if (id === void 0 || id === null || id === "")
216
+ return null;
217
+ const combinedResourceIds = (_a = raw.combined_resource) == null ? void 0 : _a.resource_ids;
218
+ return {
219
+ id,
220
+ formId: raw.formId ?? raw.form_id ?? raw.resource_form_id,
221
+ name: raw.resourceName || raw.main_field || raw.name || raw.title,
222
+ resourceType: normalizeResourceType(raw.type || raw.resourceType || ((_b = raw.select_type) == null ? void 0 : _b.type)),
223
+ capacity: Number(raw.capacity || raw.max_capacity || 1) || 1,
224
+ times: Array.isArray(raw.times) ? raw.times : [],
225
+ childResourceIds: Array.isArray(combinedResourceIds) ? combinedResourceIds : void 0,
226
+ raw
227
+ };
228
+ }
229
+ function normalizeScheduleIds(raw) {
230
+ const ids = raw.schedule ?? raw.schedule_ids ?? raw.scheduleIds ?? [];
231
+ return (Array.isArray(ids) ? ids : [ids]).map((id) => toNumberId(id)).filter((id) => id !== void 0);
232
+ }
233
+ function collectRawScheduleIds(rawResources) {
234
+ const ids = /* @__PURE__ */ new Map();
235
+ (rawResources || []).forEach((resource) => {
236
+ normalizeScheduleIds(resource).forEach((id) => {
237
+ ids.set(String(id), id);
238
+ });
239
+ });
240
+ return Array.from(ids.values());
241
+ }
242
+ function scheduleListHasIds(scheduleList, scheduleIds) {
243
+ if (!scheduleIds.length)
244
+ return true;
245
+ const existingIds = new Set((scheduleList || []).map((schedule) => String(schedule.id)));
246
+ return scheduleIds.every((id) => existingIds.has(String(id)));
247
+ }
248
+ function dateRangeDays(dateRange) {
249
+ if (!(dateRange == null ? void 0 : dateRange.start) || !(dateRange == null ? void 0 : dateRange.end))
250
+ return [];
251
+ const dates = [];
252
+ let cursor = (0, import_dayjs.default)(dateRange.start);
253
+ const end = (0, import_dayjs.default)(dateRange.end);
254
+ if (!cursor.isValid() || !end.isValid() || end.isBefore(cursor, "day"))
255
+ return dates;
256
+ while (cursor.isSame(end, "day") || cursor.isBefore(end, "day")) {
257
+ dates.push(cursor.format("YYYY-MM-DD"));
258
+ cursor = cursor.add(1, "day");
259
+ }
260
+ return dates;
261
+ }
262
+ function normalizePlannerEvent(rawEvent, resourceId) {
263
+ const startAt = rawEvent.start_at ?? rawEvent.startAt ?? rawEvent.start_time ?? rawEvent.startTime;
264
+ const endAt = rawEvent.end_at ?? rawEvent.endAt ?? rawEvent.end_time ?? rawEvent.endTime;
265
+ if (!startAt || !endAt)
266
+ return null;
267
+ const rawSource = rawEvent.source;
268
+ const source = rawSource === "remote" || rawSource === "cart" || rawSource === "selection" ? rawSource : "remote";
269
+ return {
270
+ ...rawEvent,
271
+ resourceId,
272
+ resource_id: rawEvent.resource_id ?? rawEvent.resourceId ?? resourceId,
273
+ start_at: startAt,
274
+ end_at: endAt,
275
+ pax: Number(rawEvent.pax ?? rawEvent.number ?? rawEvent.capacity ?? 1) || 1,
276
+ source
277
+ };
278
+ }
279
+ function normalizeResourceV2(raw) {
280
+ const normalized = normalizeResource({
281
+ ...raw,
282
+ times: []
283
+ });
284
+ if (!normalized)
285
+ return null;
286
+ return normalized;
287
+ }
288
+ function buildResourceWindowFallbackTimes(params) {
289
+ const { raw, dates, scheduleIds } = params;
290
+ const rawStart = raw.start_time || raw.start_at || raw.startTime;
291
+ const rawEnd = raw.end_time || raw.end_at || raw.endTime;
292
+ if (!rawStart || !rawEnd)
293
+ return [];
294
+ const startBoundary = (0, import_dayjs.default)(rawStart);
295
+ const endBoundary = (0, import_dayjs.default)(rawEnd);
296
+ if (!startBoundary.isValid() || !endBoundary.isValid() || !endBoundary.isAfter(startBoundary)) {
297
+ return [];
298
+ }
299
+ return dates.flatMap((date) => {
300
+ const dayStart = (0, import_dayjs.default)(`${date} 00:00:00`);
301
+ const dayEnd = (0, import_dayjs.default)(`${date} 23:59:59`);
302
+ const start = startBoundary.isAfter(dayStart) ? startBoundary : dayStart;
303
+ const end = endBoundary.isBefore(dayEnd) ? endBoundary : dayEnd;
304
+ if (!end.isAfter(start))
305
+ return [];
306
+ return [{
307
+ start_at: start.format("YYYY-MM-DD HH:mm:ss"),
308
+ end_at: end.format("YYYY-MM-DD HH:mm:ss"),
309
+ schedule_ids: scheduleIds,
310
+ source: "resource_window_fallback"
311
+ }];
312
+ });
313
+ }
314
+ function buildPlannerResourcesFromV2(params) {
315
+ const scheduleMap = new Map(
316
+ (params.scheduleList || []).map((schedule) => [String(schedule.id), schedule])
317
+ );
318
+ const dates = dateRangeDays(params.dateRange);
319
+ const externalEvents = [];
320
+ const resources = (params.rawResources || []).map((raw) => {
321
+ const resource = normalizeResourceV2(raw);
322
+ if (!resource)
323
+ return null;
324
+ const scheduleIds = normalizeScheduleIds(raw);
325
+ const schedules = scheduleIds.map((id) => scheduleMap.get(String(id))).filter(Boolean);
326
+ const matchedScheduleIds = new Set(schedules.map((schedule) => String(schedule.id)));
327
+ const missingScheduleIds = scheduleIds.filter((id) => !matchedScheduleIds.has(String(id)));
328
+ if (missingScheduleIds.length > 0) {
329
+ resource.raw = {
330
+ ...resource.raw || raw,
331
+ _plannerMissingScheduleIds: missingScheduleIds
332
+ };
333
+ }
334
+ resource.times = dates.flatMap((date) => (0, import_getDateIsInSchedule.getScheduleStartEndTimePoints)(date, schedules).map((slot) => ({
335
+ start_at: (0, import_dayjs.default)(slot.start_at).format("YYYY-MM-DD HH:mm:ss"),
336
+ end_at: (0, import_dayjs.default)(slot.end_at).format("YYYY-MM-DD HH:mm:ss"),
337
+ schedule_ids: scheduleIds
338
+ })));
339
+ if (resource.times.length === 0 && missingScheduleIds.length > 0) {
340
+ resource.times = buildResourceWindowFallbackTimes({
341
+ raw,
342
+ dates,
343
+ scheduleIds
344
+ });
345
+ }
346
+ const eventList = Array.isArray(raw.event_list) ? raw.event_list : [];
347
+ eventList.forEach((event) => {
348
+ const normalizedEvent = normalizePlannerEvent(event, resource.id);
349
+ if (normalizedEvent)
350
+ externalEvents.push(normalizedEvent);
351
+ });
352
+ return resource;
353
+ }).filter((resource) => resource !== null);
354
+ return {
355
+ resources,
356
+ externalEvents
357
+ };
358
+ }
359
+ function collectPlannerResourceIds(products, explicitResourceIds = []) {
360
+ const ids = /* @__PURE__ */ new Map();
361
+ const add = (value) => {
362
+ if (value === void 0 || value === null || value === "")
363
+ return;
364
+ ids.set(String(value), value);
365
+ };
366
+ explicitResourceIds.forEach(add);
367
+ products.forEach((product) => {
368
+ var _a;
369
+ (_a = product.resourceRequirements) == null ? void 0 : _a.forEach((requirement) => {
370
+ var _a2;
371
+ (_a2 = requirement.resourceIds) == null ? void 0 : _a2.forEach(add);
372
+ });
373
+ });
374
+ return Array.from(ids.values());
375
+ }
376
+ function samePlannerId(left, right) {
377
+ if (left === void 0 || right === void 0)
378
+ return false;
379
+ return String(left) === String(right);
380
+ }
381
+ function findRequirementForAssignment(product, assignment) {
382
+ const requirements = product.resourceRequirements || [];
383
+ return requirements.find((requirement) => (requirement.resourceIds || []).some((id) => samePlannerId(id, assignment.resourceId))) || (requirements.length === 1 ? requirements[0] : void 0) || requirements.find((requirement) => requirement.required) || requirements[0];
384
+ }
385
+ function buildBookingFromAssignment(params) {
386
+ return buildBookingFromAssignments({
387
+ assignments: [params.assignment],
388
+ product: params.product
389
+ });
390
+ }
391
+ function buildBookingFromAssignments(params) {
392
+ const { assignments, product } = params;
393
+ const firstAssignment = assignments[0];
394
+ const start = (0, import_dayjs.default)(firstAssignment.start_at);
395
+ const end = (0, import_dayjs.default)(firstAssignment.end_at);
396
+ return {
397
+ start_date: start.format("YYYY-MM-DD"),
398
+ start_time: start.format("HH:mm"),
399
+ end_date: end.format("YYYY-MM-DD"),
400
+ end_time: end.format("HH:mm"),
401
+ duration: Math.max(1, end.diff(start, "minute")),
402
+ like_status: "common",
403
+ schedule_id: 0,
404
+ resources: assignments.map((assignment) => {
405
+ const requirement = findRequirementForAssignment(product, assignment);
406
+ return {
407
+ id: assignment.resourceId,
408
+ resource_id: assignment.resourceId,
409
+ form_id: requirement == null ? void 0 : requirement.formId,
410
+ relation_type: "form",
411
+ relation_id: assignment.resourceId,
412
+ like_status: "common",
413
+ capacity: assignment.capacityRequired || product.capacityRequired || 1
414
+ };
415
+ }),
416
+ metadata: {
417
+ unified_booking_sales: true,
418
+ planner_source: product.kind
419
+ }
420
+ };
421
+ }
422
+ function buildAssignmentGroupKey(assignment) {
423
+ return [
424
+ assignment.productId,
425
+ assignment.start_at,
426
+ assignment.end_at
427
+ ].join(":");
428
+ }
429
+ function buildPlannerLineIdentity(product) {
430
+ var _a;
431
+ return {
432
+ product_id: product.product_id,
433
+ product_variant_id: product.product_variant_id ?? 0,
434
+ unique_identification_number: ((_a = product.metadata) == null ? void 0 : _a.unique_identification_number) || product.unique_identification_number,
435
+ product_sku: product.product_sku,
436
+ product_bundle: product.product_bundle
437
+ };
438
+ }
439
+ var UnifiedBookingSalesImpl = class extends import_BookingTicket.BookingTicket {
440
+ constructor() {
441
+ super(...arguments);
442
+ this.defaultName = "unifiedBookingSales";
443
+ this.defaultVersion = "1.0.0";
444
+ this.store = {};
445
+ this.unifiedProductCatalog = [];
446
+ this.openDataTarget = null;
447
+ this.loadOpenDataInFlight = null;
448
+ this.loadOpenDataInFlightTarget = null;
449
+ }
450
+ /**
451
+ * Kiosk 不能把已收款订单留在本地等待重试:默认直接等待云端 checkout。
452
+ * 兼容仍需要 WebPOS 待同步模式的入口可显式传 checkoutSyncTaskEnabled: true。
453
+ */
454
+ shouldUseCheckoutSyncTask() {
455
+ var _a;
456
+ const configured = (_a = this.otherParams) == null ? void 0 : _a.checkoutSyncTaskEnabled;
457
+ return typeof configured === "boolean" ? configured : false;
458
+ }
459
+ /** UnifiedBookingSales 的真实结账默认请求收据数据,供云端确认后再本地打印。 */
460
+ getDefaultCheckoutSmallTicketDataFlag() {
461
+ return 1;
462
+ }
463
+ getRegisteredModuleNames() {
464
+ return [...super.getRegisteredModuleNames(), "resourcePlanner", "openData"];
465
+ }
466
+ createSubModule(moduleName) {
467
+ if (moduleName === "resourcePlanner") {
468
+ return new import_modules.ResourcePlannerModule(`${this.name}_resourcePlanner`);
469
+ }
470
+ if (moduleName === "openData") {
471
+ return new import_modules.OpenDataModule(`${this.name}_openData`);
472
+ }
473
+ return super.createSubModule(moduleName);
474
+ }
475
+ /**
476
+ * Loads OpenData without borrowing BookingTicket. Cache ownership is tied to the exact
477
+ * business/channel target so a reused solution instance cannot leak another entry's menus.
478
+ */
479
+ async loadOpenData(params) {
480
+ if (!this.store.openData) {
481
+ throw new Error("[UnifiedBookingSales] openData 模块未初始化");
482
+ }
483
+ const businessCode = String(params.businessCode || "").trim();
484
+ const channel = String(params.channel || "").trim();
485
+ if (!businessCode || !channel) {
486
+ throw new Error("[UnifiedBookingSales] OpenData 需要 businessCode 与 channel");
487
+ }
488
+ const target = `${businessCode}+${channel}`;
489
+ const cachedData = this.store.openData.getOpenData();
490
+ const lastFetchedAt = this.store.openData.getLastFetchedAt();
491
+ if (!params.force && this.openDataTarget === target && cachedData && lastFetchedAt && Date.now() - lastFetchedAt < OPEN_DATA_CACHE_TTL) {
492
+ return cachedData;
493
+ }
494
+ if (this.loadOpenDataInFlight) {
495
+ if (this.loadOpenDataInFlightTarget === target) {
496
+ return await this.loadOpenDataInFlight;
497
+ }
498
+ await this.loadOpenDataInFlight;
499
+ return this.loadOpenData(params);
500
+ }
501
+ this.loadOpenDataInFlightTarget = target;
502
+ this.loadOpenDataInFlight = this.store.openData.fetchOpenData({
503
+ scope: "board",
504
+ target,
505
+ section_code: [...OPEN_DATA_SECTION_CODES]
506
+ });
507
+ try {
508
+ const openData = await this.loadOpenDataInFlight;
509
+ this.openDataTarget = target;
510
+ this.otherParams.openData = openData;
511
+ this.otherParams.businessCode = businessCode;
512
+ this.otherParams.channel = channel;
513
+ return openData;
514
+ } finally {
515
+ this.loadOpenDataInFlight = null;
516
+ this.loadOpenDataInFlightTarget = null;
517
+ }
518
+ }
519
+ async getOpenData() {
520
+ var _a, _b, _c;
521
+ const businessCode = String(((_a = this.otherParams) == null ? void 0 : _a.businessCode) || "").trim();
522
+ const channel = String(((_b = this.otherParams) == null ? void 0 : _b.channel) || "").trim();
523
+ if (!businessCode || !channel)
524
+ return null;
525
+ const target = `${businessCode}+${channel}`;
526
+ const cachedData = ((_c = this.store.openData) == null ? void 0 : _c.getOpenData()) || null;
527
+ if (cachedData && this.openDataTarget === target)
528
+ return cachedData;
529
+ return this.loadOpenData({ businessCode, channel });
530
+ }
531
+ /**
532
+ * Loads the product catalog used by both UI and Planner. Schedule data is requested with each
533
+ * product because session products carry their fixed intervals there.
534
+ */
535
+ async loadProducts(params = {}, options) {
536
+ if (!this.store.products)
537
+ throw new Error("[UnifiedBookingSales] products 模块未初始化");
538
+ const schedule_date = params.schedule_date || (0, import_dayjs.default)().format("YYYY-MM-DD");
539
+ const schedule_datetime = params.schedule_datetime || `${schedule_date} ${(0, import_dayjs.default)().format("HH:mm:ss")}`;
540
+ const result = await this.store.products.loadProducts({
541
+ with_count: ["bundleGroup", "optionGroup"],
542
+ with_schedule: 1,
543
+ ...params,
544
+ schedule_date,
545
+ schedule_datetime,
546
+ cacheId: this.cacheId
547
+ }, options);
548
+ this.unifiedProductCatalog = Array.isArray(result) ? [...result] : [];
549
+ await this.core.effects.emit(`${this.name}:onProductsLoaded`, result);
550
+ return result;
551
+ }
552
+ /** Date-first entry point. It delegates protocol details and cache ownership to ScheduleModule. */
553
+ async loadScheduleAvailableDate(params) {
554
+ if (!this.store.schedule)
555
+ throw new Error("[UnifiedBookingSales] schedule 模块未初始化");
556
+ return this.store.schedule.loadScheduleAvailableDate(params);
557
+ }
558
+ /**
559
+ * Implements the date-first product pool: the chosen date's product ids plus globally sellable
560
+ * other_product_ids. If a batch product query is unexpectedly empty, retry one id at a time so
561
+ * a partial backend incompatibility does not hide the entire date's catalog.
562
+ */
563
+ async loadProductsByScheduleDate(params) {
564
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
565
+ const {
566
+ date,
567
+ custom_page_id,
568
+ channel,
569
+ product_ids,
570
+ ...productLoadParams
571
+ } = params;
572
+ if (!this.store.schedule)
573
+ throw new Error("[UnifiedBookingSales] schedule 模块未初始化");
574
+ const cachedDates = ((_b = (_a = this.store.schedule).getAvailabilityScheduleDateList) == null ? void 0 : _b.call(_a)) || [];
575
+ const hasCachedDate = cachedDates.some((item) => item.date === date);
576
+ const hasOtherProducts = (((_d = (_c = this.store.schedule).getOtherProductsIds) == null ? void 0 : _d.call(_c)) || []).length > 0;
577
+ if (!hasCachedDate && !hasOtherProducts) {
578
+ await this.loadScheduleAvailableDate({
579
+ startDate: date,
580
+ endDate: date,
581
+ custom_page_id,
582
+ channel
583
+ });
584
+ }
585
+ const scheduleDate = (_g = (_f = (_e = this.store.schedule).getAvailabilityScheduleDateList) == null ? void 0 : _f.call(_e)) == null ? void 0 : _g.find((item) => item.date === date);
586
+ const productIdFilter = (product_ids == null ? void 0 : product_ids.length) ? new Set(product_ids.map((id) => String(id))) : null;
587
+ const availableProductIds = [
588
+ ...(scheduleDate == null ? void 0 : scheduleDate.product_ids) || [],
589
+ ...((_i = (_h = this.store.schedule).getOtherProductsIds) == null ? void 0 : _i.call(_h)) || []
590
+ ].filter((id, index, self) => self.indexOf(id) === index).filter((id) => !productIdFilter || productIdFilter.has(String(id)));
591
+ if (!availableProductIds.length) {
592
+ this.unifiedProductCatalog = [];
593
+ await this.core.effects.emit(`${this.name}:onProductsLoaded`, []);
594
+ return [];
595
+ }
596
+ const products = await this.loadProducts({
597
+ ...productLoadParams,
598
+ product_ids: availableProductIds,
599
+ schedule_date: date
600
+ });
601
+ if (products.length || availableProductIds.length <= 1)
602
+ return products;
603
+ const fallbackProducts = [];
604
+ for (const productId of availableProductIds) {
605
+ const result = await this.loadProducts({
606
+ ...productLoadParams,
607
+ product_ids: [productId],
608
+ schedule_date: date
609
+ });
610
+ result.forEach((product) => {
611
+ const id = (product == null ? void 0 : product.id) ?? (product == null ? void 0 : product.product_id);
612
+ if (!fallbackProducts.some((item) => String((item == null ? void 0 : item.id) ?? (item == null ? void 0 : item.product_id)) === String(id))) {
613
+ fallbackProducts.push(product);
614
+ }
615
+ });
616
+ }
617
+ this.unifiedProductCatalog = [...fallbackProducts];
618
+ await this.core.effects.emit(`${this.name}:onProductsLoaded`, fallbackProducts);
619
+ return fallbackProducts;
620
+ }
621
+ /** Returns a copy so UI consumers cannot accidentally mutate the solution's product cache. */
622
+ getProductCatalog() {
623
+ return { products: [...this.unifiedProductCatalog] };
624
+ }
625
+ /**
626
+ * Read-only compatibility view for salesSdk skins. It reconnects a booking to its product but
627
+ * must not be used to mutate tempOrder; all writes go through BaseSales / planner APIs.
628
+ */
629
+ // getCart(): BookingTicketCartView {
630
+ // const tempOrder = this.getTempOrder();
631
+ // const salesDetail = this.getSalesOrder();
632
+ // const items = tempOrder?.products || [];
633
+ // const bookings = salesDetail?.bookings || tempOrder?.bookings || [];
634
+ // return {
635
+ // items,
636
+ // bookings: bookings.map((booking: any) => {
637
+ // const productUid = booking?.product_uid;
638
+ // const product = productUid
639
+ // ? items.find((item: any) => {
640
+ // const uid = item?.metadata?.unique_identification_number || item?.unique_identification_number;
641
+ // return String(uid || '') === String(productUid);
642
+ // }) || null
643
+ // : null;
644
+ // return {
645
+ // ...booking,
646
+ // _extend: {
647
+ // ...(booking?._extend || {}),
648
+ // product,
649
+ // addTimeProduct: [],
650
+ // canAddTime: false,
651
+ // },
652
+ // };
653
+ // }),
654
+ // };
655
+ // }
656
+ /**
657
+ * Loads the current customer's product coupons and discount cards after planner lines exist,
658
+ * then optionally lets the existing Rules engine select the best valid combination. This is a
659
+ * diagnostic boundary for new booking skins: no pricing logic is duplicated in the planner.
660
+ */
661
+ async applyPlannerDiscounts(params = {}) {
662
+ const tempOrder = this.getTempOrder();
663
+ const requestedCustomerId = params.customerId ?? (tempOrder == null ? void 0 : tempOrder.customer_id);
664
+ const parsedCustomerId = Number(requestedCustomerId);
665
+ const customerId = Number.isFinite(parsedCustomerId) && parsedCustomerId > 1 ? parsedCustomerId : null;
666
+ const summaryBefore = await this.getSummary();
667
+ if (!customerId) {
668
+ return buildPlannerDiscountResult({
669
+ customerId: null,
670
+ discountList: this.getDiscountList(),
671
+ products: (tempOrder == null ? void 0 : tempOrder.products) || [],
672
+ summaryBefore,
673
+ summaryAfter: summaryBefore,
674
+ status: "no_customer"
675
+ });
676
+ }
677
+ await this.loadDiscountConfig({ customerId, action: "create" });
678
+ const preferredDiscountId = params.preferredDiscountId;
679
+ const preferredDiscount = preferredDiscountId === void 0 || preferredDiscountId === null ? void 0 : this.getDiscountList().find((discount) => String(resolvePlannerDiscountId(discount) ?? "") === String(preferredDiscountId));
680
+ if (preferredDiscount) {
681
+ const preferredId = resolvePlannerDiscountId(preferredDiscount);
682
+ const selectedOtherDiscounts = this.getDiscountList().filter((discount) => discount.isSelected === true && String(resolvePlannerDiscountId(discount) ?? "") !== String(preferredId));
683
+ for (const discount of selectedOtherDiscounts) {
684
+ const discountId = Number(resolvePlannerDiscountId(discount));
685
+ if (Number.isFinite(discountId)) {
686
+ await this.setDiscountSelected({ discountId, isSelected: false });
687
+ }
688
+ }
689
+ await this.setDiscountSelected({
690
+ discountId: Number(preferredId),
691
+ isSelected: true
692
+ });
693
+ } else if (preferredDiscountId === void 0 || preferredDiscountId === null) {
694
+ if (params.applyBestDiscount !== false)
695
+ await this.bestDiscount();
696
+ } else if (params.applyBestDiscount !== false) {
697
+ await this.bestDiscount();
698
+ }
699
+ const nextTempOrder = this.getTempOrder();
700
+ return buildPlannerDiscountResult({
701
+ customerId,
702
+ discountList: this.getDiscountList(),
703
+ products: (nextTempOrder == null ? void 0 : nextTempOrder.products) || [],
704
+ summaryBefore,
705
+ summaryAfter: await this.getSummary(),
706
+ status: params.applyBestDiscount === false && !preferredDiscount ? "loaded" : "applied",
707
+ preferredDiscountId,
708
+ preferredDiscountFound: preferredDiscountId === void 0 || preferredDiscountId === null ? void 0 : Boolean(preferredDiscount)
709
+ });
710
+ }
711
+ /** ResourcePlanner is a required child module; fail early instead of producing partial availability. */
712
+ getPlanner() {
713
+ if (!this.store.resourcePlanner) {
714
+ throw new Error("[UnifiedBookingSales] resourcePlanner 模块未初始化");
715
+ }
716
+ return this.store.resourcePlanner;
717
+ }
718
+ buildUnifiedOrderProduct(sourceProduct, ownershipKey, product) {
719
+ const orderProduct = this.transformBaseProductToOrderProduct({
720
+ payload: {
721
+ ...sourceProduct,
722
+ id: sourceProduct.id ?? (product == null ? void 0 : product.id),
723
+ product_id: sourceProduct.product_id ?? sourceProduct.id ?? (product == null ? void 0 : product.id),
724
+ quantity: sourceProduct.quantity ?? sourceProduct.num ?? (product == null ? void 0 : product.quantity) ?? 1,
725
+ price: sourceProduct.price ?? (product == null ? void 0 : product.price),
726
+ selling_price: sourceProduct.selling_price ?? sourceProduct.price ?? (product == null ? void 0 : product.price),
727
+ metadata: {
728
+ ...sourceProduct.metadata || {},
729
+ [ownershipKey]: true
730
+ }
731
+ },
732
+ sourceProduct
733
+ });
734
+ if (orderProduct && typeof orderProduct === "object") {
735
+ orderProduct.metadata = {
736
+ ...orderProduct.metadata || {},
737
+ [ownershipKey]: true
738
+ };
739
+ }
740
+ return orderProduct;
741
+ }
742
+ /** Replacing a staged selection is idempotent and never removes unrelated cart lines. */
743
+ async removeExistingStagedLines(ownershipKey) {
744
+ var _a;
745
+ const existingLines = (((_a = this.getTempOrder()) == null ? void 0 : _a.products) || []).filter((product) => {
746
+ var _a2;
747
+ return ((_a2 = product == null ? void 0 : product.metadata) == null ? void 0 : _a2[ownershipKey]) === true;
748
+ });
749
+ if (existingLines.length > 0) {
750
+ await this.removeProductsFromOrder(existingLines.map(buildPlannerLineIdentity));
751
+ }
752
+ }
753
+ async stageProducts(params, ownershipKey) {
754
+ var _a;
755
+ const productIds = params.productIds || [];
756
+ if (!productIds.length)
757
+ return this.getTempOrder();
758
+ if ((_a = params.rawProducts) == null ? void 0 : _a.length) {
759
+ this.unifiedProductCatalog = params.rawProducts;
760
+ } else {
761
+ const existingIds = new Set((this.unifiedProductCatalog || []).map((product) => String(product.id ?? product.product_id)));
762
+ const missingIds = productIds.filter((id) => !existingIds.has(String(id)));
763
+ if (missingIds.length || params.productLoadParams) {
764
+ await this.loadProducts({
765
+ ...params.productLoadParams || {},
766
+ product_ids: productIds.map(Number).filter(Number.isFinite)
767
+ });
768
+ }
769
+ }
770
+ await this.removeExistingStagedLines(ownershipKey);
771
+ const productIdSet = new Set(productIds.map((id) => String(id)));
772
+ const selectedProducts = (this.unifiedProductCatalog || []).filter((product) => productIdSet.has(String(product.id ?? product.product_id)));
773
+ for (const sourceProduct of selectedProducts) {
774
+ const productInput = this.buildUnifiedOrderProduct(sourceProduct, ownershipKey);
775
+ if (productInput)
776
+ await this.addProductToOrder(productInput);
777
+ }
778
+ return this.getTempOrder();
779
+ }
780
+ /**
781
+ * Adds the current product choice to an otherwise clean planner cart without creating bookings.
782
+ * This separates "what the customer wants" from the later time/resource confirmation step.
783
+ */
784
+ async stagePlannerProducts(params) {
785
+ return this.stageProducts(params, "unified_booking_sales");
786
+ }
787
+ /**
788
+ * Adds ordinary retail products without asking ResourcePlanner for booking facts.
789
+ * Its staged lines have separate ownership so planner and retail skins can coexist safely.
790
+ */
791
+ async stageRetailProducts(params) {
792
+ return this.stageProducts(params, "unified_booking_sales_retail");
793
+ }
794
+ /**
795
+ * Adds one retail line without replacing the cart's existing retail selection.
796
+ * This is intentionally separate from stageRetailProducts(), whose contract is
797
+ * "replace the staged selection" for demo and wizard-like callers.
798
+ */
799
+ async addRetailProduct(params) {
800
+ var _a, _b;
801
+ const quantity = Math.floor(Number(params.quantity ?? 1));
802
+ if (!Number.isFinite(quantity) || quantity <= 0) {
803
+ throw new Error("[UnifiedBookingSales] retail 商品数量必须大于 0");
804
+ }
805
+ const productId = params.productId ?? ((_a = params.product) == null ? void 0 : _a.id) ?? ((_b = params.product) == null ? void 0 : _b.product_id);
806
+ let sourceProduct = params.product;
807
+ if (!sourceProduct && productId !== void 0 && productId !== null) {
808
+ sourceProduct = this.unifiedProductCatalog.find(
809
+ (product) => String(product.id ?? product.product_id) === String(productId)
810
+ );
811
+ }
812
+ if (!sourceProduct && productId !== void 0 && productId !== null) {
813
+ const products = await this.loadProducts({
814
+ ...params.productLoadParams || {},
815
+ product_ids: [Number(productId)].filter(Number.isFinite)
816
+ });
817
+ sourceProduct = products.find(
818
+ (product) => String(product.id ?? product.product_id) === String(productId)
819
+ );
820
+ }
821
+ if (!sourceProduct) {
822
+ throw new Error(`[UnifiedBookingSales] 未找到零售商品 ${String(productId ?? "")}`.trim());
823
+ }
824
+ const orderProduct = this.buildUnifiedOrderProduct(
825
+ {
826
+ ...sourceProduct,
827
+ quantity
828
+ },
829
+ "unified_booking_sales_retail"
830
+ );
831
+ if (!orderProduct) {
832
+ throw new Error("[UnifiedBookingSales] 无法生成零售商品订单行");
833
+ }
834
+ return this.addProductToOrder(orderProduct);
835
+ }
836
+ /** Normalizes caller-provided products or loads the catalog before any resource request is made. */
837
+ async loadPlannerProducts(params) {
838
+ var _a, _b, _c, _d, _e, _f;
839
+ if ((_b = (_a = params.context) == null ? void 0 : _a.products) == null ? void 0 : _b.length)
840
+ return params.context.products;
841
+ if ((_c = params.rawProducts) == null ? void 0 : _c.length)
842
+ return params.rawProducts.map(normalizeProductForPlanner);
843
+ const productIds = params.productIds || ((_d = params.productLoadParams) == null ? void 0 : _d.product_ids) || [];
844
+ if (productIds.length > 0 || params.productLoadParams) {
845
+ await this.loadProducts({
846
+ ...params.date ? { schedule_date: params.date } : {},
847
+ ...params.productLoadParams || {},
848
+ product_ids: productIds.length ? productIds.map(Number).filter(Number.isFinite) : (_e = params.productLoadParams) == null ? void 0 : _e.product_ids
849
+ });
850
+ }
851
+ return (((_f = this.getProductCatalog) == null ? void 0 : _f.call(this).products) || []).map(normalizeProductForPlanner);
852
+ }
853
+ /** Loads the reusable store-level schedule catalog on demand. */
854
+ async ensurePlannerSchedulesLoaded() {
855
+ var _a, _b, _c, _d, _e, _f;
856
+ if (!this.store.schedule)
857
+ throw new Error("[UnifiedBookingSales] schedule 模块未初始化");
858
+ const scheduleList = ((_b = (_a = this.store.schedule).getScheduleList) == null ? void 0 : _b.call(_a)) || [];
859
+ if (!((_d = (_c = this.store.schedule).isScheduleListLoaded) == null ? void 0 : _d.call(_c)) || scheduleList.length === 0) {
860
+ await this.store.schedule.loadAllSchedule();
861
+ }
862
+ return ((_f = (_e = this.store.schedule).getScheduleList) == null ? void 0 : _f.call(_e)) || [];
863
+ }
864
+ /**
865
+ * v2 resources reference schedule ids rather than expanded work times. Refresh once when the
866
+ * cached schedule catalog cannot materialize all referenced ids.
867
+ */
868
+ async ensurePlannerSchedulesForResources(rawResources) {
869
+ var _a, _b;
870
+ let scheduleList = await this.ensurePlannerSchedulesLoaded();
871
+ const scheduleIds = collectRawScheduleIds(rawResources);
872
+ if (!scheduleListHasIds(scheduleList, scheduleIds)) {
873
+ await this.store.schedule.loadAllSchedule({ useCache: false });
874
+ scheduleList = ((_b = (_a = this.store.schedule).getScheduleList) == null ? void 0 : _b.call(_a)) || [];
875
+ }
876
+ return scheduleList;
877
+ }
878
+ /**
879
+ * Fetches resource metadata plus flat occupied events. Work windows are intentionally derived
880
+ * locally in buildPlannerResourcesFromV2 from /schedule, rather than requested from the legacy
881
+ * resource dates endpoint.
882
+ */
883
+ async fetchPlannerResourcesV2(params) {
884
+ const response = await this.request.get("/schedule/resource/list/v2", {
885
+ start_date: params.dateRange.start,
886
+ end_date: params.dateRange.end,
887
+ resource_ids: params.resourceIds,
888
+ front_end_cache_id: false
889
+ }, {
890
+ useCache: false
891
+ });
892
+ const data = response == null ? void 0 : response.data;
893
+ if (Array.isArray(data))
894
+ return data;
895
+ if (Array.isArray(data == null ? void 0 : data.list))
896
+ return data.list;
897
+ return [];
898
+ }
899
+ /**
900
+ * Resolves the Planner context in precedence order: explicit normalized context, test raw data,
901
+ * then the v2 resource API inferred from selected product requirements and any URL lock.
902
+ */
903
+ async loadPlannerResources(params, products = []) {
904
+ var _a, _b, _c, _d, _e, _f;
905
+ if ((_b = (_a = params.context) == null ? void 0 : _a.resources) == null ? void 0 : _b.length) {
906
+ return {
907
+ resources: params.context.resources,
908
+ externalEvents: params.context.externalEvents || []
909
+ };
910
+ }
911
+ if ((_c = params.rawResources) == null ? void 0 : _c.length) {
912
+ return {
913
+ resources: params.rawResources.map(normalizeResource).filter((resource) => resource !== null),
914
+ externalEvents: ((_d = params.context) == null ? void 0 : _d.externalEvents) || []
915
+ };
916
+ }
917
+ const resourceIds = collectPlannerResourceIds(products, params.resourceIds || []);
918
+ if (!params.useResourceDateApi || !params.dateRange || resourceIds.length === 0) {
919
+ return {
920
+ resources: [],
921
+ externalEvents: ((_e = params.context) == null ? void 0 : _e.externalEvents) || []
922
+ };
923
+ }
924
+ const rawResources = await this.fetchPlannerResourcesV2({
925
+ dateRange: params.dateRange,
926
+ resourceIds
927
+ });
928
+ const scheduleList = await this.ensurePlannerSchedulesForResources(rawResources);
929
+ const materialized = buildPlannerResourcesFromV2({
930
+ rawResources,
931
+ scheduleList,
932
+ dateRange: params.dateRange
933
+ });
934
+ return {
935
+ resources: materialized.resources,
936
+ externalEvents: [
937
+ ...((_f = params.context) == null ? void 0 : _f.externalEvents) || [],
938
+ ...materialized.externalEvents
939
+ ]
940
+ };
941
+ }
942
+ /**
943
+ * Boundary between IO and pure planning. Once this returns, all later availability/slot calls
944
+ * operate on the ResourcePlanner snapshot and do not issue resource requests themselves.
945
+ */
946
+ async loadPlannerContext(params = {}) {
947
+ const products = await this.loadPlannerProducts(params);
948
+ const plannerResources = await this.loadPlannerResources(params, products);
949
+ const context = {
950
+ ...params.context || {},
951
+ products,
952
+ resources: plannerResources.resources,
953
+ externalEvents: plannerResources.externalEvents
954
+ };
955
+ const snapshot = this.getPlanner().setContext(context);
956
+ this.getPlanner().setSelectionPatch({
957
+ mode: params.mode,
958
+ productIds: params.productIds || [],
959
+ date: params.date,
960
+ startTime: params.startTime,
961
+ endTime: params.endTime,
962
+ resourceIds: params.resourceIds || []
963
+ });
964
+ await this.core.effects.emit(`${this.name}:${import_types.UnifiedBookingSalesHooks.onPlannerContextLoaded}`, snapshot);
965
+ return this.getPlanner().getSnapshot();
966
+ }
967
+ /** Returns cells, date summaries and a derived grid; cells are continuous ranges, not UI slots. */
968
+ async queryPlannerAvailability(query = {}) {
969
+ return this.getPlanner().queryAvailability(query);
970
+ }
971
+ /** Turns compatible availability ranges into user-selectable intervals and ready-to-commit assignments. */
972
+ resolveAssignableSlots(query = {}) {
973
+ return this.getPlanner().resolveAssignableSlots(query);
974
+ }
975
+ /** Stores a UI choice only. It is deliberately side-effect free with respect to tempOrder. */
976
+ setPlannerSelection(patch) {
977
+ return this.getPlanner().setSelectionPatch(patch);
978
+ }
979
+ /**
980
+ * Legacy-friendly automatic assignment for already fixed intervals. Duration products without a
981
+ * start time intentionally remain unresolved; callers offering a picker should use slots.
982
+ */
983
+ async autoAssignPlanner(query = {}) {
984
+ const result = this.getPlanner().autoAssign(query);
985
+ if (result.conflicts.length) {
986
+ console.warn("[UnifiedBookingSales] autoAssignPlanner 存在未分配项", result.conflicts);
987
+ }
988
+ return this.getPlanner().getSnapshot();
989
+ }
990
+ /** Rechecks remote events, capacity and conflicts among the current selection before committing. */
991
+ validatePlannerSelection() {
992
+ return this.getPlanner().validateSelection();
993
+ }
994
+ /** Read-only snapshot for UI diagnostics and progressive flow steps. */
995
+ getPlannerSnapshot() {
996
+ return this.getPlanner().getSnapshot();
997
+ }
998
+ /**
999
+ * Validates then writes assignments to tempOrder. Real order submission is opt-in so UI callers
1000
+ * can safely preview the cart; pass submitAfterCommit only after an explicit confirmation.
1001
+ */
1002
+ async commitPlannerSelection(params = {}) {
1003
+ if (params.selection) {
1004
+ this.setPlannerSelection(params.selection);
1005
+ }
1006
+ if (params.assignments) {
1007
+ this.setPlannerSelection({ assignments: params.assignments });
1008
+ }
1009
+ const snapshot = this.getPlannerSnapshot();
1010
+ const assignments = snapshot.selection.assignments || [];
1011
+ if (assignments.length === 0) {
1012
+ return {
1013
+ tempOrder: this.getTempOrder(),
1014
+ assignments,
1015
+ validation: {
1016
+ ok: false,
1017
+ conflicts: [{
1018
+ type: "missing_assignment",
1019
+ message: "没有可提交的 planner assignment,请先执行 autoAssignPlanner 或传入 assignments"
1020
+ }]
1021
+ }
1022
+ };
1023
+ }
1024
+ const validation = this.validatePlannerSelection();
1025
+ if (!validation.ok) {
1026
+ return {
1027
+ tempOrder: this.getTempOrder(),
1028
+ assignments,
1029
+ validation
1030
+ };
1031
+ }
1032
+ const productsById = new Map(
1033
+ snapshot.context.products.map((product) => [String(product.id), product])
1034
+ );
1035
+ await this.removeExistingStagedLines("unified_booking_sales");
1036
+ const assignmentGroups = /* @__PURE__ */ new Map();
1037
+ assignments.forEach((assignment) => {
1038
+ const key = buildAssignmentGroupKey(assignment);
1039
+ assignmentGroups.set(key, [...assignmentGroups.get(key) || [], assignment]);
1040
+ });
1041
+ for (const groupAssignments of assignmentGroups.values()) {
1042
+ const product = productsById.get(String(groupAssignments[0].productId));
1043
+ if (!product)
1044
+ continue;
1045
+ const sourceProduct = product.raw || { id: product.id, title: product.title, price: product.price };
1046
+ const productInput = this.buildUnifiedOrderProduct(
1047
+ sourceProduct,
1048
+ "unified_booking_sales",
1049
+ product
1050
+ );
1051
+ if (!productInput)
1052
+ continue;
1053
+ await this.addProductToOrder(productInput, buildBookingFromAssignments({
1054
+ assignments: groupAssignments,
1055
+ product
1056
+ }));
1057
+ }
1058
+ let submitResult;
1059
+ if (params.submitAfterCommit) {
1060
+ submitResult = await this.submitSalesOrder();
1061
+ }
1062
+ const result = {
1063
+ tempOrder: this.getTempOrder(),
1064
+ assignments,
1065
+ validation,
1066
+ submitResult
1067
+ };
1068
+ await this.core.effects.emit(`${this.name}:${import_types.UnifiedBookingSalesHooks.onPlannerCommitted}`, result);
1069
+ return result;
1070
+ }
1071
+ };
1072
+ var UnifiedBookingSales = UnifiedBookingSalesImpl;
1073
+ // Annotate the CommonJS export names for ESM import in node:
1074
+ 0 && (module.exports = {
1075
+ UnifiedBookingSales,
1076
+ UnifiedBookingSalesImpl,
1077
+ buildBookingFromAssignment,
1078
+ buildBookingFromAssignments,
1079
+ buildPlannerDiscountResult,
1080
+ buildPlannerLineIdentity,
1081
+ buildPlannerResourcesFromV2,
1082
+ collectPlannerResourceIds,
1083
+ normalizeProductForPlanner,
1084
+ ...require("./types")
1085
+ });