@pisell/pisellos 0.0.479 → 0.0.481

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 (147) hide show
  1. package/dist/core/index.d.ts +3 -2
  2. package/dist/core/index.js +7 -0
  3. package/dist/effects/index.d.ts +2 -2
  4. package/dist/effects/index.js +34 -81
  5. package/dist/model/strategy/adapter/promotion/evaluator.js +99 -26
  6. package/dist/model/strategy/adapter/walletPass/type.d.ts +9 -2
  7. package/dist/model/strategy/adapter/walletPass/utils.d.ts +6 -6
  8. package/dist/model/strategy/adapter/walletPass/utils.js +111 -72
  9. package/dist/modules/Customer/index.js +1 -1
  10. package/dist/modules/Discount/index.d.ts +6 -2
  11. package/dist/modules/Discount/index.js +14 -8
  12. package/dist/modules/Order/index.d.ts +1 -1
  13. package/dist/modules/Order/index.js +18 -13
  14. package/dist/modules/Payment/index.d.ts +4 -0
  15. package/dist/modules/Payment/index.js +774 -649
  16. package/dist/modules/Payment/walletpass.js +44 -17
  17. package/dist/modules/Product/index.d.ts +1 -1
  18. package/dist/modules/Product/types.d.ts +2 -0
  19. package/dist/modules/ProductList/index.d.ts +3 -0
  20. package/dist/modules/ProductList/index.js +9 -7
  21. package/dist/modules/Rules/index.d.ts +2 -2
  22. package/dist/modules/Rules/index.js +37 -31
  23. package/dist/modules/Rules/types.d.ts +2 -2
  24. package/dist/modules/Schedule/index.d.ts +9 -0
  25. package/dist/modules/Schedule/index.js +15 -2
  26. package/dist/plugins/app-types/app/app.d.ts +1 -0
  27. package/dist/plugins/request.d.ts +2 -0
  28. package/dist/server/index.d.ts +107 -2
  29. package/dist/server/index.js +1507 -279
  30. package/dist/server/modules/index.d.ts +6 -0
  31. package/dist/server/modules/index.js +7 -0
  32. package/dist/server/modules/menu/index.d.ts +19 -0
  33. package/dist/server/modules/menu/index.js +221 -71
  34. package/dist/server/modules/order/index.d.ts +87 -0
  35. package/dist/server/modules/order/index.js +916 -0
  36. package/dist/server/modules/order/types.d.ts +530 -0
  37. package/dist/server/modules/order/types.js +141 -0
  38. package/dist/server/modules/order/utils/filterBookings.d.ts +6 -0
  39. package/dist/server/modules/order/utils/filterBookings.js +350 -0
  40. package/dist/server/modules/order/utils/filterOrders.d.ts +15 -0
  41. package/dist/server/modules/order/utils/filterOrders.js +226 -0
  42. package/dist/server/modules/products/index.d.ts +117 -5
  43. package/dist/server/modules/products/index.js +1450 -240
  44. package/dist/server/modules/products/types.d.ts +25 -1
  45. package/dist/server/modules/products/types.js +3 -0
  46. package/dist/server/modules/resource/index.d.ts +86 -0
  47. package/dist/server/modules/resource/index.js +1128 -0
  48. package/dist/server/modules/resource/types.d.ts +121 -0
  49. package/dist/server/modules/resource/types.js +47 -0
  50. package/dist/server/modules/schedule/index.d.ts +19 -0
  51. package/dist/server/modules/schedule/index.js +229 -68
  52. package/dist/server/utils/product.d.ts +5 -0
  53. package/dist/server/utils/product.js +71 -31
  54. package/dist/solution/BookingTicket/index.d.ts +10 -2
  55. package/dist/solution/BookingTicket/index.js +41 -28
  56. package/dist/solution/BookingTicket/utils/scan/index.js +1 -1
  57. package/dist/solution/Checkout/index.d.ts +1 -0
  58. package/dist/solution/Checkout/index.js +286 -188
  59. package/dist/solution/Checkout/utils/index.d.ts +2 -1
  60. package/dist/solution/Checkout/utils/index.js +6 -4
  61. package/dist/solution/RegisterAndLogin/config.js +340 -1
  62. package/dist/solution/Sales/index.d.ts +96 -0
  63. package/dist/solution/Sales/index.js +566 -0
  64. package/dist/solution/Sales/types.d.ts +67 -0
  65. package/dist/solution/Sales/types.js +26 -0
  66. package/dist/solution/ShopDiscount/index.d.ts +1 -0
  67. package/dist/solution/ShopDiscount/index.js +35 -22
  68. package/dist/solution/ShopDiscount/types.d.ts +6 -0
  69. package/dist/solution/ShopDiscount/utils.d.ts +9 -0
  70. package/dist/solution/ShopDiscount/utils.js +21 -27
  71. package/dist/solution/index.d.ts +2 -1
  72. package/dist/solution/index.js +2 -1
  73. package/dist/types/index.d.ts +5 -0
  74. package/lib/core/index.d.ts +3 -2
  75. package/lib/core/index.js +4 -0
  76. package/lib/effects/index.d.ts +2 -2
  77. package/lib/effects/index.js +22 -31
  78. package/lib/model/strategy/adapter/promotion/evaluator.js +57 -8
  79. package/lib/model/strategy/adapter/walletPass/type.d.ts +9 -2
  80. package/lib/model/strategy/adapter/walletPass/utils.d.ts +6 -6
  81. package/lib/model/strategy/adapter/walletPass/utils.js +115 -48
  82. package/lib/modules/Customer/index.js +1 -1
  83. package/lib/modules/Discount/index.d.ts +6 -2
  84. package/lib/modules/Discount/index.js +3 -1
  85. package/lib/modules/Order/index.d.ts +1 -1
  86. package/lib/modules/Order/index.js +20 -18
  87. package/lib/modules/Payment/index.d.ts +4 -0
  88. package/lib/modules/Payment/index.js +134 -66
  89. package/lib/modules/Payment/walletpass.js +23 -4
  90. package/lib/modules/Product/index.d.ts +1 -1
  91. package/lib/modules/Product/types.d.ts +2 -0
  92. package/lib/modules/ProductList/index.d.ts +3 -0
  93. package/lib/modules/ProductList/index.js +2 -2
  94. package/lib/modules/Rules/index.d.ts +2 -2
  95. package/lib/modules/Rules/index.js +69 -73
  96. package/lib/modules/Rules/types.d.ts +2 -2
  97. package/lib/modules/Schedule/index.d.ts +9 -0
  98. package/lib/modules/Schedule/index.js +11 -0
  99. package/lib/plugins/app-types/app/app.d.ts +1 -0
  100. package/lib/plugins/request.d.ts +2 -0
  101. package/lib/server/index.d.ts +107 -2
  102. package/lib/server/index.js +773 -51
  103. package/lib/server/modules/index.d.ts +6 -0
  104. package/lib/server/modules/index.js +16 -2
  105. package/lib/server/modules/menu/index.d.ts +19 -0
  106. package/lib/server/modules/menu/index.js +121 -2
  107. package/lib/server/modules/order/index.d.ts +87 -0
  108. package/lib/server/modules/order/index.js +543 -0
  109. package/lib/server/modules/order/types.d.ts +530 -0
  110. package/lib/server/modules/order/types.js +34 -0
  111. package/lib/server/modules/order/utils/filterBookings.d.ts +6 -0
  112. package/lib/server/modules/order/utils/filterBookings.js +320 -0
  113. package/lib/server/modules/order/utils/filterOrders.d.ts +15 -0
  114. package/lib/server/modules/order/utils/filterOrders.js +197 -0
  115. package/lib/server/modules/products/index.d.ts +117 -5
  116. package/lib/server/modules/products/index.js +799 -62
  117. package/lib/server/modules/products/types.d.ts +25 -1
  118. package/lib/server/modules/products/types.js +1 -0
  119. package/lib/server/modules/resource/index.d.ts +86 -0
  120. package/lib/server/modules/resource/index.js +557 -0
  121. package/lib/server/modules/resource/types.d.ts +121 -0
  122. package/lib/server/modules/resource/types.js +35 -0
  123. package/lib/server/modules/schedule/index.d.ts +19 -0
  124. package/lib/server/modules/schedule/index.js +141 -12
  125. package/lib/server/utils/product.d.ts +5 -0
  126. package/lib/server/utils/product.js +56 -27
  127. package/lib/solution/BookingTicket/index.d.ts +10 -2
  128. package/lib/solution/BookingTicket/index.js +10 -2
  129. package/lib/solution/BookingTicket/utils/scan/index.js +0 -1
  130. package/lib/solution/Checkout/index.d.ts +1 -0
  131. package/lib/solution/Checkout/index.js +399 -331
  132. package/lib/solution/Checkout/utils/index.d.ts +2 -1
  133. package/lib/solution/Checkout/utils/index.js +6 -4
  134. package/lib/solution/RegisterAndLogin/config.js +266 -1
  135. package/lib/solution/Sales/index.d.ts +96 -0
  136. package/lib/solution/Sales/index.js +416 -0
  137. package/lib/solution/Sales/types.d.ts +67 -0
  138. package/lib/solution/Sales/types.js +35 -0
  139. package/lib/solution/ShopDiscount/index.d.ts +1 -0
  140. package/lib/solution/ShopDiscount/index.js +14 -6
  141. package/lib/solution/ShopDiscount/types.d.ts +6 -0
  142. package/lib/solution/ShopDiscount/utils.d.ts +9 -0
  143. package/lib/solution/ShopDiscount/utils.js +6 -10
  144. package/lib/solution/index.d.ts +2 -1
  145. package/lib/solution/index.js +4 -2
  146. package/lib/types/index.d.ts +5 -0
  147. package/package.json +1 -1
@@ -0,0 +1,416 @@
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/Sales/index.ts
31
+ var Sales_exports = {};
32
+ __export(Sales_exports, {
33
+ Sales: () => SalesImpl,
34
+ SalesImpl: () => SalesImpl,
35
+ default: () => Sales_default
36
+ });
37
+ module.exports = __toCommonJS(Sales_exports);
38
+ var import_dayjs = __toESM(require("dayjs"));
39
+ var import_BaseModule = require("../../modules/BaseModule");
40
+ var import_types = require("./types");
41
+ __reExport(Sales_exports, require("./types"), module.exports);
42
+ var SalesImpl = class extends import_BaseModule.BaseModule {
43
+ constructor(name, version) {
44
+ super(name, version);
45
+ this.defaultName = "sales";
46
+ this.defaultVersion = "1.0.0";
47
+ this.defaultTimelineBucketMinutes = 15;
48
+ this.isSolution = true;
49
+ this.options = {};
50
+ this.otherParams = {};
51
+ }
52
+ async initialize(core, options = {}) {
53
+ this.core = core;
54
+ this.options = options;
55
+ this.otherParams = options.otherParams || {};
56
+ const appPlugin = this.core.getPlugin("app");
57
+ this.request = appPlugin == null ? void 0 : appPlugin.getApp().request;
58
+ if (!this.request) {
59
+ throw new Error("Sales 解决方案需要 request 插件支持");
60
+ }
61
+ this.store = {
62
+ currentDay: (0, import_dayjs.default)().startOf("day"),
63
+ reservationList: []
64
+ };
65
+ await this.core.effects.emit(`${this.name}:onInited`, {});
66
+ }
67
+ // -------------------------------------------------------------------------
68
+ // SalesModuleAPI 实现
69
+ // -------------------------------------------------------------------------
70
+ /** 获取当前 store 快照 */
71
+ getState() {
72
+ return { ...this.store };
73
+ }
74
+ /** 获取当前选择日期 */
75
+ getCurrentDay() {
76
+ return this.store.currentDay;
77
+ }
78
+ /** 设置当前选择日期,会自动归一到当天 00:00:00 */
79
+ setCurrentDay(currentDay) {
80
+ const normalized = (0, import_dayjs.default)(currentDay).startOf("day");
81
+ this.store.currentDay = normalized;
82
+ this.core.effects.emit(import_types.SalesHooks.onCurrentDayChanged, { currentDay: normalized });
83
+ return normalized;
84
+ }
85
+ /** 获取当前预约列表 */
86
+ getReservationList() {
87
+ return [...this.store.reservationList];
88
+ }
89
+ /** 覆盖设置当前预约列表 */
90
+ setReservationList(reservationList) {
91
+ this.store.reservationList = reservationList;
92
+ this.core.effects.emit(import_types.SalesHooks.onReservationListChanged, { reservationList });
93
+ return reservationList;
94
+ }
95
+ /** 重置预约列表为空 */
96
+ resetReservationList() {
97
+ this.store.reservationList = [];
98
+ this.core.effects.emit(import_types.SalesHooks.onReservationListChanged, { reservationList: [] });
99
+ return [];
100
+ }
101
+ // -------------------------------------------------------------------------
102
+ // 生命周期
103
+ // -------------------------------------------------------------------------
104
+ async destroy() {
105
+ await this.core.effects.emit(`${this.name}:onDestroy`, {});
106
+ super.destroy();
107
+ }
108
+ /**
109
+ * 时间轴粒度(分钟)
110
+ * - 默认 15 分钟
111
+ * - 可通过 registerModule 时的 options.otherParams.timelineBucketMinutes 覆盖
112
+ */
113
+ getTimelineBucketMinutes() {
114
+ var _a;
115
+ const dynamicBucket = Number((_a = this.otherParams) == null ? void 0 : _a.timelineBucketMinutes);
116
+ if (!Number.isFinite(dynamicBucket) || dynamicBucket <= 0)
117
+ return this.defaultTimelineBucketMinutes;
118
+ return Math.floor(dynamicBucket);
119
+ }
120
+ /**
121
+ * 解析营业日统计窗口:
122
+ * - 当 operating_day_boundary.type === 'start_time'
123
+ * 使用「今天 + boundary.time」到「明天 + boundary.time」
124
+ * - 否则回退自然日(00:00 到次日 00:00)
125
+ */
126
+ getTimelineRangeByOperatingBoundary() {
127
+ var _a, _b, _c, _d, _e, _f, _g;
128
+ const today = (0, import_dayjs.default)();
129
+ const defaultStart = today.startOf("day");
130
+ const defaultEndExclusive = defaultStart.add(1, "day");
131
+ const appPlugin = this.core.getPlugin("app");
132
+ const coreData = (_f = (_d = (_c = (_b = (_a = appPlugin == null ? void 0 : appPlugin.getApp()) == null ? void 0 : _a.data) == null ? void 0 : _b.store) == null ? void 0 : _c.getStore) == null ? void 0 : (_e = _d.call(_c)).getDataByModel) == null ? void 0 : _f.call(_e, "core");
133
+ const operatingDayBoundary = (_g = coreData == null ? void 0 : coreData.core) == null ? void 0 : _g.operating_day_boundary;
134
+ if ((operatingDayBoundary == null ? void 0 : operatingDayBoundary.type) !== "start_time") {
135
+ return { startAt: defaultStart, endExclusiveAt: defaultEndExclusive };
136
+ }
137
+ const boundaryTime = String(operatingDayBoundary.time || "").trim();
138
+ const startAt = (0, import_dayjs.default)(`${today.format("YYYY-MM-DD")} ${boundaryTime}`);
139
+ if (!startAt.isValid()) {
140
+ return { startAt: defaultStart, endExclusiveAt: defaultEndExclusive };
141
+ }
142
+ return { startAt, endExclusiveAt: startAt.add(1, "day") };
143
+ }
144
+ /**
145
+ * 统计时间轴每个时间片的预约数量。
146
+ * 算法使用差分数组,复杂度 O(n + s):
147
+ * - n = booking 数量
148
+ * - s = 时间片数量
149
+ */
150
+ getTimelineHighlights(bookingList = [], startDateTime, endDateTime) {
151
+ const bucketMinutes = this.getTimelineBucketMinutes();
152
+ const bucketMs = bucketMinutes * 60 * 1e3;
153
+ const customStartAt = startDateTime ? (0, import_dayjs.default)(startDateTime) : (0, import_dayjs.default)("");
154
+ const customEndExclusiveAt = endDateTime ? (0, import_dayjs.default)(endDateTime) : (0, import_dayjs.default)("");
155
+ const hasValidCustomRange = customStartAt.isValid() && customEndExclusiveAt.isValid();
156
+ const { startAt, endExclusiveAt } = hasValidCustomRange ? { startAt: customStartAt, endExclusiveAt: customEndExclusiveAt } : this.getTimelineRangeByOperatingBoundary();
157
+ const startAtMs = startAt.valueOf();
158
+ const endExclusiveAtMs = endExclusiveAt.valueOf();
159
+ if (!startAt.isValid() || !endExclusiveAt.isValid() || endExclusiveAtMs <= startAtMs)
160
+ return [];
161
+ const slotCount = Math.ceil((endExclusiveAtMs - startAtMs) / bucketMs);
162
+ if (slotCount <= 0)
163
+ return [];
164
+ const diff = new Int32Array(slotCount + 1);
165
+ for (let i = 0; i < bookingList.length; i++) {
166
+ const booking = bookingList[i];
167
+ const bookingStartAt = this.toBookingDateTime(booking.start_date, booking.start_time);
168
+ const bookingEndAt = this.toBookingDateTime(booking.end_date, booking.end_time);
169
+ if (!bookingStartAt.isValid() || !bookingEndAt.isValid())
170
+ continue;
171
+ const overlapStartMs = Math.max(bookingStartAt.valueOf(), startAtMs);
172
+ const overlapEndExclusiveMs = Math.min(bookingEndAt.valueOf() + 1, endExclusiveAtMs);
173
+ if (overlapStartMs >= overlapEndExclusiveMs)
174
+ continue;
175
+ const startIdx = Math.max(0, Math.ceil((overlapStartMs - startAtMs) / bucketMs));
176
+ const endIdx = Math.min(
177
+ slotCount - 1,
178
+ Math.floor((overlapEndExclusiveMs - 1 - startAtMs) / bucketMs)
179
+ );
180
+ if (startIdx > endIdx)
181
+ continue;
182
+ diff[startIdx] += 1;
183
+ diff[endIdx + 1] -= 1;
184
+ }
185
+ const timeline = [];
186
+ let runningCount = 0;
187
+ for (let i = 0; i < slotCount; i++) {
188
+ runningCount += diff[i];
189
+ timeline.push(runningCount);
190
+ }
191
+ return timeline;
192
+ }
193
+ /** dayjs 未启用插件时,手动封装 >= 判断 */
194
+ isSameOrAfter(left, right) {
195
+ return left.isAfter(right) || left.isSame(right);
196
+ }
197
+ /** dayjs 未启用插件时,手动封装 <= 判断 */
198
+ isSameOrBefore(left, right) {
199
+ return left.isBefore(right) || left.isSame(right);
200
+ }
201
+ /**
202
+ * 将 booking 的日期/时间字段统一组装为 dayjs
203
+ * - date + time 都有:按完整时间解析
204
+ * - 只有一个字段:按单字段解析(兼容历史数据)
205
+ * - 都没有:返回 invalid
206
+ */
207
+ toBookingDateTime(date, time) {
208
+ if (!date && !time)
209
+ return (0, import_dayjs.default)("");
210
+ if (date && time)
211
+ return (0, import_dayjs.default)(`${date} ${time}`);
212
+ return (0, import_dayjs.default)(date || time);
213
+ }
214
+ /**
215
+ * 预约状态映射(面向 UI 的统一状态)
216
+ * 说明:locked 逻辑后续会补业务规则,这里先保留分支占位。
217
+ */
218
+ getBookingStatus(appointmentStatus) {
219
+ if (appointmentStatus === "started")
220
+ return "occupied";
221
+ if (appointmentStatus === "new" || appointmentStatus === "confirmed" || appointmentStatus === "arrived") {
222
+ return "reserved";
223
+ }
224
+ if (appointmentStatus === "locked")
225
+ return "locked";
226
+ return void 0;
227
+ }
228
+ getResourceId(resource) {
229
+ return resource.id ?? resource.form_record_id ?? "";
230
+ }
231
+ /**
232
+ * 资源下 bookings 的返回裁剪策略:
233
+ * 1) 优先返回当前时刻正在进行中的预约(允许并发返回多条)
234
+ * 2) 若无 active,返回已超时且仍占用中的预约(允许并发多条)
235
+ * 3) 若仍无,返回最近一组未来预约(同 start_time 的并发预约全部返回)
236
+ * 4) 最后兜底返回一条(避免资源有历史预约时完全无反馈)
237
+ */
238
+ pickBookingsForCurrentPoint(current, bookings) {
239
+ if (bookings.length === 0)
240
+ return [];
241
+ const currentDayStart = current.startOf("day");
242
+ const currentDayEnd = current.endOf("day");
243
+ const dayScopedBookings = bookings.filter((booking) => {
244
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
245
+ const endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
246
+ if (!startAt.isValid() || !endAt.isValid())
247
+ return false;
248
+ return this.isSameOrBefore(startAt, currentDayEnd) && this.isSameOrAfter(endAt, currentDayStart);
249
+ });
250
+ if (dayScopedBookings.length === 0)
251
+ return [];
252
+ const appendNextStartGroupIfNeeded = (selectedBookings) => {
253
+ if (selectedBookings.length === 0)
254
+ return selectedBookings;
255
+ const shouldAppendNextGroup = selectedBookings.some(
256
+ (booking) => booking.status === "reserved" || booking.status === "occupied"
257
+ );
258
+ if (!shouldAppendNextGroup)
259
+ return selectedBookings;
260
+ const selectedStartValues = new Set(
261
+ selectedBookings.map((booking) => this.toBookingDateTime(booking.start_date, booking.start_time)).filter((startAt) => startAt.isValid()).map((startAt) => startAt.valueOf())
262
+ );
263
+ const futureCandidates = dayScopedBookings.filter((booking) => {
264
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
265
+ if (!startAt.isValid())
266
+ return false;
267
+ return startAt.isAfter(current) && !selectedStartValues.has(startAt.valueOf());
268
+ });
269
+ if (futureCandidates.length === 0)
270
+ return selectedBookings;
271
+ const nextStartAt = this.toBookingDateTime(
272
+ futureCandidates[0].start_date,
273
+ futureCandidates[0].start_time
274
+ ).valueOf();
275
+ const nextStartGroup = futureCandidates.filter((booking) => {
276
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
277
+ return startAt.isValid() && startAt.valueOf() === nextStartAt;
278
+ });
279
+ if (nextStartGroup.length === 0)
280
+ return selectedBookings;
281
+ return [...selectedBookings, ...nextStartGroup];
282
+ };
283
+ const activeBookings = dayScopedBookings.filter((booking) => {
284
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
285
+ const endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
286
+ if (!startAt.isValid() || !endAt.isValid())
287
+ return false;
288
+ return this.isSameOrAfter(current, startAt) && this.isSameOrBefore(current, endAt);
289
+ });
290
+ if (activeBookings.length > 0)
291
+ return appendNextStartGroupIfNeeded(activeBookings);
292
+ const timeoutOccupied = dayScopedBookings.filter(
293
+ (booking) => booking.status === "occupied" && booking.isTimeout
294
+ );
295
+ if (timeoutOccupied.length > 0)
296
+ return appendNextStartGroupIfNeeded(timeoutOccupied);
297
+ const upcoming = dayScopedBookings.filter((booking) => {
298
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
299
+ return startAt.isValid() && startAt.isAfter(current) && startAt.isSame(current, "day");
300
+ });
301
+ if (upcoming.length > 0) {
302
+ const firstStartAt = this.toBookingDateTime(upcoming[0].start_date, upcoming[0].start_time).valueOf();
303
+ const upcomingStartGroup = upcoming.filter((booking) => {
304
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
305
+ return startAt.valueOf() === firstStartAt;
306
+ });
307
+ return appendNextStartGroupIfNeeded(upcomingStartGroup);
308
+ }
309
+ const fallback = dayScopedBookings[dayScopedBookings.length - 1];
310
+ return fallback ? [fallback] : [];
311
+ }
312
+ /**
313
+ * 标准化单条 booking:
314
+ * - 过滤终态(rejected/cancelled/completed)
315
+ * - 注入 status / isTimeout / reserved_status
316
+ */
317
+ normalizeMatchedBooking(current, booking) {
318
+ const appointmentStatus = String(booking.appointment_status ?? booking.status ?? "");
319
+ if (appointmentStatus === "rejected" || appointmentStatus === "cancelled" || appointmentStatus === "completed") {
320
+ return null;
321
+ }
322
+ const bookingStatus = this.getBookingStatus(appointmentStatus);
323
+ const endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
324
+ const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
325
+ const isTimeout = bookingStatus === "occupied" && endAt.isValid() && current.isAfter(endAt);
326
+ const timeoutTime = isTimeout ? current.diff(endAt, "minute") : void 0;
327
+ const progressPercent = (() => {
328
+ if (bookingStatus !== "occupied")
329
+ return 0;
330
+ if (!startAt.isValid() || !endAt.isValid())
331
+ return 0;
332
+ const totalMinutes = endAt.diff(startAt, "minute");
333
+ if (totalMinutes <= 0)
334
+ return 0;
335
+ const elapsedMinutes = current.diff(startAt, "minute");
336
+ if (elapsedMinutes <= 0)
337
+ return 0;
338
+ if (elapsedMinutes >= totalMinutes)
339
+ return 100;
340
+ return Math.floor(elapsedMinutes / totalMinutes * 100);
341
+ })();
342
+ let reservedStatus;
343
+ let lateTime;
344
+ let remainingReserveTime;
345
+ if (bookingStatus === "reserved" && startAt.isValid()) {
346
+ if (current.isBefore(startAt)) {
347
+ reservedStatus = "not_arrived";
348
+ remainingReserveTime = startAt.diff(current, "minute");
349
+ } else {
350
+ reservedStatus = "late";
351
+ lateTime = Math.max(current.diff(startAt, "minute"), 0);
352
+ }
353
+ }
354
+ return {
355
+ ...booking,
356
+ status: bookingStatus,
357
+ isTimeout,
358
+ timeoutTime,
359
+ progressPercent,
360
+ lateTime,
361
+ reserved_status: reservedStatus,
362
+ remainingReserveTime
363
+ };
364
+ }
365
+ async getResourceBookingList(currentTime, bookingList = []) {
366
+ var _a;
367
+ const current = (0, import_dayjs.default)(currentTime);
368
+ if (!current.isValid())
369
+ return [];
370
+ const resourceResponse = await this.request.get(
371
+ "/shop/form/resource/page",
372
+ { skip: 1, num: 999 },
373
+ // @ts-ignore
374
+ { osServer: true, isShopApi: true }
375
+ );
376
+ const resourceList = ((_a = resourceResponse == null ? void 0 : resourceResponse.data) == null ? void 0 : _a.list) ?? [];
377
+ if (!Array.isArray(resourceList) || resourceList.length === 0)
378
+ return [];
379
+ const normalizedBookings = bookingList.map((booking) => this.normalizeMatchedBooking(current, booking)).filter((booking) => Boolean(booking)).sort((left, right) => {
380
+ const leftStartAt = this.toBookingDateTime(left.start_date, left.start_time).valueOf();
381
+ const rightStartAt = this.toBookingDateTime(right.start_date, right.start_time).valueOf();
382
+ return leftStartAt - rightStartAt;
383
+ });
384
+ const bookingMap = /* @__PURE__ */ new Map();
385
+ normalizedBookings.forEach((booking) => {
386
+ if (!Array.isArray(booking.resources))
387
+ return;
388
+ booking.resources.forEach((resource) => {
389
+ const relationId = resource == null ? void 0 : resource.relation_id;
390
+ if (relationId === void 0 || relationId === null)
391
+ return;
392
+ const key = String(relationId);
393
+ const list = bookingMap.get(key) || [];
394
+ list.push(booking);
395
+ bookingMap.set(key, list);
396
+ });
397
+ });
398
+ return resourceList.map((resource) => {
399
+ const resourceId = this.getResourceId(resource);
400
+ const matchedBookings = bookingMap.get(String(resourceId)) || [];
401
+ const bookings = this.pickBookingsForCurrentPoint(current, matchedBookings);
402
+ return {
403
+ ...resource,
404
+ resource_id: resourceId,
405
+ bookings
406
+ };
407
+ });
408
+ }
409
+ };
410
+ var Sales_default = SalesImpl;
411
+ // Annotate the CommonJS export names for ESM import in node:
412
+ 0 && (module.exports = {
413
+ Sales,
414
+ SalesImpl,
415
+ ...require("./types")
416
+ });
@@ -0,0 +1,67 @@
1
+ import dayjs from 'dayjs';
2
+ import type { OrderData } from '../../server/modules/order/types';
3
+ import type { BookingData } from '../../server/modules/order/types';
4
+ import type { ResourceData, ResourceId } from '../../server/modules/resource/types';
5
+ export declare enum SalesHooks {
6
+ onInited = "sales:onInited",
7
+ onDestroy = "sales:onDestroy",
8
+ onCurrentDayChanged = "sales:onCurrentDayChanged",
9
+ onReservationListChanged = "sales:onReservationListChanged"
10
+ }
11
+ /**
12
+ * 预订记录类型(暂用 OrderData 占位)
13
+ *
14
+ * TODO(Rolo): 等领导提供 reservationList 的目标结构后,
15
+ * 将 Server/Order 原始数据转换为真正的 Reservation DTO,
16
+ * 当前不要直接把 OrderData 当作最终 UI 数据结构。
17
+ */
18
+ export type Reservation = OrderData;
19
+ export type SalesBookingStatus = 'reserved' | 'occupied' | 'locked' | undefined;
20
+ export type SalesReservedStatus = 'not_arrived' | 'late' | undefined;
21
+ export interface SalesResourceMatchedBooking extends Omit<BookingData, 'status'> {
22
+ status: SalesBookingStatus;
23
+ isTimeout: boolean;
24
+ timeoutTime?: number;
25
+ progressPercent?: number;
26
+ lateTime?: number;
27
+ remainingReserveTime?: number;
28
+ reserved_status: SalesReservedStatus;
29
+ }
30
+ export interface SalesResourceBookingItem extends Omit<ResourceData, 'bookings'> {
31
+ resource_id: ResourceId;
32
+ bookings: SalesResourceMatchedBooking[];
33
+ }
34
+ export type SalesTimelineHighlights = number[];
35
+ /**
36
+ * Sales 解决方案状态
37
+ *
38
+ * - currentDay: 当前选择的日期,默认当天 00:00:00
39
+ * - reservationList: currentDay 对应的预约列表(数据来源于 Server/Order,转换逻辑待补)
40
+ */
41
+ export interface SalesState {
42
+ /** 当前选择的日期,默认当天 00:00:00 */
43
+ currentDay: dayjs.Dayjs;
44
+ /** 当前日期对应的预约列表 */
45
+ reservationList: Reservation[];
46
+ }
47
+ /**
48
+ * Sales 解决方案最小 API 面
49
+ */
50
+ export interface SalesModuleAPI {
51
+ /** 获取当前 store 快照 */
52
+ getState: () => SalesState;
53
+ /** 获取当前选择日期 */
54
+ getCurrentDay: () => dayjs.Dayjs;
55
+ /** 设置当前选择日期,会自动归一到当天 00:00:00 */
56
+ setCurrentDay: (currentDay: dayjs.ConfigType) => dayjs.Dayjs;
57
+ /** 获取当前预约列表 */
58
+ getReservationList: () => Reservation[];
59
+ /** 覆盖设置当前预约列表 */
60
+ setReservationList: (reservationList: Reservation[]) => Reservation[];
61
+ /** 重置预约列表为空 */
62
+ resetReservationList: () => Reservation[];
63
+ /** 获取资源维度的预约占用列表 */
64
+ getResourceBookingList: (currentTime: string, bookingList: BookingData[]) => Promise<SalesResourceBookingItem[]>;
65
+ /** 获取时间轴每个时间片的预约数量 */
66
+ getTimelineHighlights: (bookingList: BookingData[], startDateTime?: string, endDateTime?: string) => SalesTimelineHighlights;
67
+ }
@@ -0,0 +1,35 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/solution/Sales/types.ts
20
+ var types_exports = {};
21
+ __export(types_exports, {
22
+ SalesHooks: () => SalesHooks
23
+ });
24
+ module.exports = __toCommonJS(types_exports);
25
+ var SalesHooks = /* @__PURE__ */ ((SalesHooks2) => {
26
+ SalesHooks2["onInited"] = "sales:onInited";
27
+ SalesHooks2["onDestroy"] = "sales:onDestroy";
28
+ SalesHooks2["onCurrentDayChanged"] = "sales:onCurrentDayChanged";
29
+ SalesHooks2["onReservationListChanged"] = "sales:onReservationListChanged";
30
+ return SalesHooks2;
31
+ })(SalesHooks || {});
32
+ // Annotate the CommonJS export names for ESM import in node:
33
+ 0 && (module.exports = {
34
+ SalesHooks
35
+ });
@@ -30,6 +30,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
30
30
  type?: 'form' | 'customer';
31
31
  [key: string]: any;
32
32
  }): void;
33
+ setOrderId(id?: number): void;
33
34
  calcDiscount(productList: Record<string, any>[], options?: SetDiscountSelectedParams): {
34
35
  productList: Record<string, any>[];
35
36
  discountList: Discount[];
@@ -57,7 +57,8 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
57
57
  filteredDiscountList: [],
58
58
  orderTotalAmount: 0,
59
59
  holders: [],
60
- bookingSubject: void 0
60
+ bookingSubject: void 0,
61
+ orderId: void 0
61
62
  };
62
63
  }
63
64
  // =========== 生命周期方法 ===========
@@ -205,6 +206,9 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
205
206
  setBookingSubject(bookingSubject) {
206
207
  this.store.bookingSubject = bookingSubject;
207
208
  }
209
+ setOrderId(id) {
210
+ this.store.orderId = id;
211
+ }
208
212
  // 计算优惠券
209
213
  calcDiscount(productList, options) {
210
214
  var _a;
@@ -247,6 +251,8 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
247
251
  selectionMap.set(operation.discountId, operation.isSelected);
248
252
  });
249
253
  const newDiscountList = discountList.map((discount) => {
254
+ if (discount.isEditMode)
255
+ return discount;
250
256
  if (selectionMap.has(discount.id)) {
251
257
  const targetIsSelected = selectionMap.get(discount.id);
252
258
  return {
@@ -282,7 +288,7 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
282
288
  async scanCode(code, customerId) {
283
289
  var _a, _b, _c;
284
290
  try {
285
- const resultDiscountList = await ((_a = this.store.discount) == null ? void 0 : _a.batchSearch(code, customerId)) || [];
291
+ const resultDiscountList = await ((_a = this.store.discount) == null ? void 0 : _a.batchSearch(code, { customerId, orderId: this.store.orderId })) || [];
286
292
  const rulesModule = this.store.rules;
287
293
  if (!rulesModule) {
288
294
  return {
@@ -325,9 +331,9 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
325
331
  productList: this.store.productList || [],
326
332
  oldDiscountList: this.getDiscountList(),
327
333
  newDiscountList: withScanList,
328
- orderTotalAmount: this.store.orderTotalAmount || 0,
329
334
  holders: this.store.holders || [],
330
- isFormSubject: ((_b = this.store.bookingSubject) == null ? void 0 : _b.type) === "form"
335
+ isFormSubject: ((_b = this.store.bookingSubject) == null ? void 0 : _b.type) === "form",
336
+ orderTotalAmount: this.store.orderTotalAmount || 0
331
337
  }) || {
332
338
  isAvailable: false,
333
339
  productList: this.store.productList || [],
@@ -517,14 +523,16 @@ var ShopDiscountImpl = class extends import_BaseModule.BaseModule {
517
523
  async loadPrepareConfig(params) {
518
524
  var _a, _b, _c, _d, _e;
519
525
  try {
526
+ const orderId = this.store.orderId;
520
527
  const customerId = params.customerId || ((_a = this.getCustomer()) == null ? void 0 : _a.id);
521
528
  const goodPassList = await ((_b = this.store.discount) == null ? void 0 : _b.loadPrepareConfig({
522
529
  customer_id: customerId,
523
- action: "create",
530
+ action: orderId ? "update" : "create",
524
531
  with_good_pass: 1,
525
532
  with_discount_card: 1,
526
533
  with_wallet_pass_holder: 1,
527
- request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
534
+ request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
535
+ ...orderId ? { order_id: orderId } : {}
528
536
  }));
529
537
  const scanDiscount = (_c = this.getDiscountList()) == null ? void 0 : _c.filter(
530
538
  (item) => item.isScan
@@ -36,6 +36,7 @@ export interface ShopDiscountState {
36
36
  type?: 'form' | 'customer';
37
37
  [key: string]: any;
38
38
  };
39
+ orderId?: number;
39
40
  }
40
41
  export interface SetDiscountSelectedParams {
41
42
  discountId: number;
@@ -44,4 +45,9 @@ export interface SetDiscountSelectedParams {
44
45
  discountId: number;
45
46
  isSelected: boolean;
46
47
  }[];
48
+ bookingSubject?: {
49
+ type?: 'form' | 'customer';
50
+ [key: string]: any;
51
+ };
52
+ orderId?: number;
47
53
  }
@@ -2,6 +2,15 @@ import { Discount } from "../../modules/Discount/types";
2
2
  export declare const uniqueById: <T>(arr: T[], key?: string) => T[];
3
3
  export declare const isNormalProductByDurationSchedule: (item: any) => boolean;
4
4
  export declare const isAllNormalProduct: (items: any[]) => boolean;
5
+ /**
6
+ * 获取折扣金额 基于折扣卡类型计算
7
+ * 商品券:直接返回商品价格
8
+ * 折扣卡:根据折扣卡类型计算 固定金额:直接返回折扣卡金额 百分比:根据折扣卡金额计算
9
+ * @param discount
10
+ * @param total
11
+ * @param price
12
+ * @returns
13
+ */
5
14
  /**
6
15
  * 获取折扣金额 基于折扣卡类型计算
7
16
  * 商品券:直接返回商品价格
@@ -56,18 +56,14 @@ var isAllNormalProduct = (items) => {
56
56
  };
57
57
  var getDiscountAmount = (discount, total, price) => {
58
58
  var _a;
59
- let discountedPrice = 0;
60
59
  if (discount.tag === "good_pass") {
61
- discountedPrice = new import_decimal.default(price).minus(new import_decimal.default(price || 0)).toNumber();
62
- } else {
63
- const isFixedAmount = ((_a = discount == null ? void 0 : discount.metadata) == null ? void 0 : _a.discount_card_type) === "fixed_amount";
64
- if (isFixedAmount) {
65
- discountedPrice = Math.max(new import_decimal.default(price).minus(new import_decimal.default((discount.amount ?? discount.par_value) || 0)).toNumber(), 0);
66
- } else {
67
- discountedPrice = new import_decimal.default(100).minus(discount.par_value || 0).div(100).mul(new import_decimal.default(price)).toNumber();
68
- }
60
+ return new import_decimal.default(price).minus(new import_decimal.default(price || 0)).toNumber();
61
+ }
62
+ const isFixedAmount = ((_a = discount == null ? void 0 : discount.metadata) == null ? void 0 : _a.discount_card_type) === "fixed_amount";
63
+ if (isFixedAmount) {
64
+ return Math.max(new import_decimal.default(price).minus(new import_decimal.default((discount.amount ?? discount.par_value) || 0)).toNumber(), 0);
69
65
  }
70
- return discountedPrice;
66
+ return new import_decimal.default(100).minus(discount.par_value || 0).div(100).mul(new import_decimal.default(price)).toNumber();
71
67
  };
72
68
  var getDiscountListAmountTotal = (discount) => {
73
69
  return discount.reduce((acc, cur) => {
@@ -2,5 +2,6 @@ export * from './BuyTickets';
2
2
  export * from './BookingByStep';
3
3
  export * from './BookingTicket';
4
4
  export * from './ShopDiscount';
5
- export * from './Checkout';
6
5
  export * from './RegisterAndLogin';
6
+ export * from './Checkout';
7
+ export * from './Sales';