@pisell/pisellos 2.2.97 → 2.2.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/walletPass/type.d.ts +2 -2
- package/dist/model/strategy/adapter/walletPass/utils.js +70 -57
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/server/index.d.ts +30 -0
- package/dist/server/index.js +660 -330
- package/dist/server/modules/floor-plan/index.d.ts +39 -0
- package/dist/server/modules/floor-plan/index.js +595 -0
- package/dist/server/modules/floor-plan/types.d.ts +43 -0
- package/dist/server/modules/floor-plan/types.js +13 -0
- package/dist/server/modules/index.d.ts +3 -0
- package/dist/server/modules/index.js +4 -0
- package/dist/server/modules/order/types.d.ts +13 -1
- package/dist/server/modules/order/types.js +2 -1
- package/dist/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/dist/server/modules/order/utils/filterBookings.js +64 -4
- package/dist/server/modules/resource/index.d.ts +0 -5
- package/dist/server/modules/resource/index.js +186 -269
- package/dist/server/types.d.ts +2 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/Sales/index.d.ts +2 -1
- package/dist/solution/Sales/index.js +23 -10
- package/dist/solution/Sales/types.d.ts +1 -1
- package/lib/model/strategy/adapter/walletPass/type.d.ts +2 -2
- package/lib/model/strategy/adapter/walletPass/utils.js +58 -51
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/server/index.d.ts +30 -0
- package/lib/server/index.js +202 -9
- package/lib/server/modules/floor-plan/index.d.ts +39 -0
- package/lib/server/modules/floor-plan/index.js +327 -0
- package/lib/server/modules/floor-plan/types.d.ts +43 -0
- package/lib/server/modules/floor-plan/types.js +34 -0
- package/lib/server/modules/index.d.ts +3 -0
- package/lib/server/modules/index.js +6 -0
- package/lib/server/modules/order/types.d.ts +13 -1
- package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/lib/server/modules/order/utils/filterBookings.js +69 -3
- package/lib/server/modules/resource/index.d.ts +0 -5
- package/lib/server/modules/resource/index.js +60 -73
- package/lib/server/types.d.ts +2 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/Sales/index.d.ts +2 -1
- package/lib/solution/Sales/index.js +11 -4
- package/lib/solution/Sales/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/server/types.d.ts
CHANGED
|
@@ -344,7 +344,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
344
344
|
};
|
|
345
345
|
setOtherData(key: string, value: any): void;
|
|
346
346
|
getOtherData(key: string): any;
|
|
347
|
-
getProductTypeById(id: number): Promise<"
|
|
347
|
+
getProductTypeById(id: number): Promise<"normal" | "duration" | "session">;
|
|
348
348
|
/**
|
|
349
349
|
* 提供给 UI 的方法,减轻 UI 层的计算压力,UI 层只需要传递 cartItemId 和 resourceCode 即返回对应的 renderList
|
|
350
350
|
*
|
|
@@ -87,10 +87,11 @@ export declare class SalesImpl extends BaseModule implements Module, SalesModule
|
|
|
87
87
|
/**
|
|
88
88
|
* 标准化单条 booking:
|
|
89
89
|
* - 过滤终态(rejected/cancelled/completed)
|
|
90
|
+
* - 当 deviceTime 早于 currentTime 时,过滤 end_time 早于 currentTime 的历史数据
|
|
90
91
|
* - 注入 status / isTimeout / reserved_status
|
|
91
92
|
*/
|
|
92
93
|
private normalizeMatchedBooking;
|
|
93
|
-
getResourceBookingList(currentTime: string, bookingList?: BookingData[]): Promise<SalesResourceBookingItem[]>;
|
|
94
|
+
getResourceBookingList(currentTime: string, bookingList?: BookingData[], deviceTime?: string): Promise<SalesResourceBookingItem[]>;
|
|
94
95
|
}
|
|
95
96
|
export { SalesImpl as Sales };
|
|
96
97
|
export default SalesImpl;
|
|
@@ -423,18 +423,21 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
423
423
|
/**
|
|
424
424
|
* 标准化单条 booking:
|
|
425
425
|
* - 过滤终态(rejected/cancelled/completed)
|
|
426
|
+
* - 当 deviceTime 早于 currentTime 时,过滤 end_time 早于 currentTime 的历史数据
|
|
426
427
|
* - 注入 status / isTimeout / reserved_status
|
|
427
428
|
*/
|
|
428
429
|
}, {
|
|
429
430
|
key: "normalizeMatchedBooking",
|
|
430
|
-
value: function normalizeMatchedBooking(current, booking) {
|
|
431
|
+
value: function normalizeMatchedBooking(current, deviceCurrent, booking) {
|
|
431
432
|
var _ref3, _booking$appointment_;
|
|
432
433
|
var appointmentStatus = String((_ref3 = (_booking$appointment_ = booking.appointment_status) !== null && _booking$appointment_ !== void 0 ? _booking$appointment_ : booking.status) !== null && _ref3 !== void 0 ? _ref3 : '');
|
|
433
434
|
if (appointmentStatus === 'rejected' || appointmentStatus === 'cancelled' || appointmentStatus === 'completed') {
|
|
434
435
|
return null;
|
|
435
436
|
}
|
|
436
|
-
var bookingStatus = this.getBookingStatus(appointmentStatus);
|
|
437
437
|
var endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
|
|
438
|
+
var shouldFilterHistoryByCurrent = deviceCurrent.isBefore(current);
|
|
439
|
+
if (shouldFilterHistoryByCurrent && endAt.isValid() && endAt.isBefore(current)) return null;
|
|
440
|
+
var bookingStatus = this.getBookingStatus(appointmentStatus);
|
|
438
441
|
var startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
|
|
439
442
|
var isTimeout = bookingStatus === 'occupied' && endAt.isValid() && current.isAfter(endAt);
|
|
440
443
|
var timeoutTime = isTimeout ? current.diff(endAt, 'minute') : undefined;
|
|
@@ -478,7 +481,9 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
478
481
|
_resourceResponse$dat2,
|
|
479
482
|
_this3 = this;
|
|
480
483
|
var bookingList,
|
|
484
|
+
deviceTime,
|
|
481
485
|
current,
|
|
486
|
+
deviceCurrent,
|
|
482
487
|
resourceResponse,
|
|
483
488
|
resourceList,
|
|
484
489
|
normalizedBookings,
|
|
@@ -488,15 +493,23 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
488
493
|
while (1) switch (_context3.prev = _context3.next) {
|
|
489
494
|
case 0:
|
|
490
495
|
bookingList = _args3.length > 1 && _args3[1] !== undefined ? _args3[1] : [];
|
|
496
|
+
deviceTime = _args3.length > 2 && _args3[2] !== undefined ? _args3[2] : currentTime;
|
|
491
497
|
// currentTime 约定为完整 datetime;非法值直接返回空,避免误判
|
|
492
498
|
current = dayjs(currentTime);
|
|
493
499
|
if (current.isValid()) {
|
|
494
|
-
_context3.next =
|
|
500
|
+
_context3.next = 5;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
return _context3.abrupt("return", []);
|
|
504
|
+
case 5:
|
|
505
|
+
deviceCurrent = dayjs(deviceTime);
|
|
506
|
+
if (deviceCurrent.isValid()) {
|
|
507
|
+
_context3.next = 8;
|
|
495
508
|
break;
|
|
496
509
|
}
|
|
497
510
|
return _context3.abrupt("return", []);
|
|
498
|
-
case
|
|
499
|
-
_context3.next =
|
|
511
|
+
case 8:
|
|
512
|
+
_context3.next = 10;
|
|
500
513
|
return this.request.get('/shop/form/resource/page', {
|
|
501
514
|
skip: 1,
|
|
502
515
|
num: 999
|
|
@@ -506,18 +519,18 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
506
519
|
osServer: true,
|
|
507
520
|
isShopApi: true
|
|
508
521
|
});
|
|
509
|
-
case
|
|
522
|
+
case 10:
|
|
510
523
|
resourceResponse = _context3.sent;
|
|
511
524
|
resourceList = (_resourceResponse$dat = resourceResponse === null || resourceResponse === void 0 || (_resourceResponse$dat2 = resourceResponse.data) === null || _resourceResponse$dat2 === void 0 ? void 0 : _resourceResponse$dat2.list) !== null && _resourceResponse$dat !== void 0 ? _resourceResponse$dat : [];
|
|
512
525
|
if (!(!Array.isArray(resourceList) || resourceList.length === 0)) {
|
|
513
|
-
_context3.next =
|
|
526
|
+
_context3.next = 14;
|
|
514
527
|
break;
|
|
515
528
|
}
|
|
516
529
|
return _context3.abrupt("return", []);
|
|
517
|
-
case
|
|
530
|
+
case 14:
|
|
518
531
|
// 全局先做 booking 标准化 + start_time 升序,后续映射直接复用
|
|
519
532
|
normalizedBookings = bookingList.map(function (booking) {
|
|
520
|
-
return _this3.normalizeMatchedBooking(current, booking);
|
|
533
|
+
return _this3.normalizeMatchedBooking(current, deviceCurrent, booking);
|
|
521
534
|
}).filter(function (booking) {
|
|
522
535
|
return Boolean(booking);
|
|
523
536
|
}).sort(function (left, right) {
|
|
@@ -548,7 +561,7 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
548
561
|
bookings: bookings
|
|
549
562
|
});
|
|
550
563
|
}));
|
|
551
|
-
case
|
|
564
|
+
case 18:
|
|
552
565
|
case "end":
|
|
553
566
|
return _context3.stop();
|
|
554
567
|
}
|
|
@@ -61,7 +61,7 @@ export interface SalesModuleAPI {
|
|
|
61
61
|
/** 重置预约列表为空 */
|
|
62
62
|
resetReservationList: () => Reservation[];
|
|
63
63
|
/** 获取资源维度的预约占用列表 */
|
|
64
|
-
getResourceBookingList: (currentTime: string, bookingList: BookingData[]) => Promise<SalesResourceBookingItem[]>;
|
|
64
|
+
getResourceBookingList: (currentTime: string, bookingList: BookingData[], deviceTime?: string) => Promise<SalesResourceBookingItem[]>;
|
|
65
65
|
/** 获取时间轴每个时间片的预约数量 */
|
|
66
66
|
getTimelineHighlights: (bookingList: BookingData[], startDateTime?: string, endDateTime?: string) => SalesTimelineHighlights;
|
|
67
67
|
}
|
|
@@ -41,7 +41,7 @@ export interface Voucher {
|
|
|
41
41
|
allowCrossProduct: boolean;
|
|
42
42
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
43
43
|
applicableProductLimit: number;
|
|
44
|
-
/** 单商品可用卡券上限(同一 Wallet Pass
|
|
44
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品的每个 unit 最多抵扣次数,总上限 = maxPassesPerItem × quantity)。默认为 0,表示不限制。 */
|
|
45
45
|
maxPassesPerItem: number;
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -129,7 +129,7 @@ export interface EvaluatorInput {
|
|
|
129
129
|
allowCrossProduct: boolean;
|
|
130
130
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
131
131
|
applicableProductLimit: number;
|
|
132
|
-
/** 单商品可用卡券上限(同一 Wallet Pass
|
|
132
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品的每个 unit 最多抵扣次数,总上限 = maxPassesPerItem × quantity)。默认为 0,表示不限制。 */
|
|
133
133
|
maxPassesPerItem: number;
|
|
134
134
|
}>[];
|
|
135
135
|
}
|
|
@@ -82,6 +82,9 @@ var getApplicableProducts = (voucher, productsData) => {
|
|
|
82
82
|
}
|
|
83
83
|
return productsData.filter((p) => applicableProductIds.includes(p.product_id));
|
|
84
84
|
};
|
|
85
|
+
var getTotalQuantityByProductId = (allProducts, productId) => {
|
|
86
|
+
return allProducts.filter((p) => p.product_id === productId).reduce((sum, p) => sum + getProductQuantity(p), 0);
|
|
87
|
+
};
|
|
85
88
|
function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
86
89
|
console.log(products, "products123");
|
|
87
90
|
const productsCopy = expandProductsWithBundleItems(products, true);
|
|
@@ -90,18 +93,18 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
90
93
|
var _a;
|
|
91
94
|
return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
|
|
92
95
|
};
|
|
93
|
-
const incrementItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
|
|
96
|
+
const incrementItemPassUsage = (usageMap, walletPassProductId, orderItemProductId, count = 1) => {
|
|
94
97
|
if (!usageMap.has(walletPassProductId)) {
|
|
95
98
|
usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
96
99
|
}
|
|
97
100
|
const innerMap = usageMap.get(walletPassProductId);
|
|
98
|
-
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) +
|
|
101
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + count);
|
|
99
102
|
};
|
|
100
|
-
const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
|
|
103
|
+
const filterByMaxPassesPerItem = (products2, allProducts, usageMap, walletPassProductId, maxPassesPerItem) => {
|
|
101
104
|
if (maxPassesPerItem <= 0)
|
|
102
105
|
return products2;
|
|
103
106
|
return products2.filter(
|
|
104
|
-
(p) => getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem
|
|
107
|
+
(p) => getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem * getTotalQuantityByProductId(allProducts, p.product_id)
|
|
105
108
|
);
|
|
106
109
|
};
|
|
107
110
|
const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
|
|
@@ -116,7 +119,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
116
119
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
117
120
|
let applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
|
|
118
121
|
if (itemPassUsage) {
|
|
119
|
-
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
|
|
122
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, productsData, itemPassUsage, voucher.product_id, maxPassesPerItem);
|
|
120
123
|
}
|
|
121
124
|
if (applicableProducts.length === 0) {
|
|
122
125
|
return new import_decimal.default(0);
|
|
@@ -146,14 +149,16 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
146
149
|
}
|
|
147
150
|
} else {
|
|
148
151
|
const maxProduct = applicableProducts.reduce(
|
|
149
|
-
(max, p) => p[
|
|
150
|
-
);
|
|
151
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
152
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
153
|
-
finalApplicableAmount = import_decimal.default.min(
|
|
154
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
155
|
-
maxProduct[amountField]
|
|
152
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
156
153
|
);
|
|
154
|
+
if (maxPassesPerItem > 0) {
|
|
155
|
+
finalApplicableAmount = import_decimal.default.min(
|
|
156
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
157
|
+
maxProduct[amountField]
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
finalApplicableAmount = maxProduct[amountField];
|
|
161
|
+
}
|
|
157
162
|
}
|
|
158
163
|
return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
|
|
159
164
|
};
|
|
@@ -180,7 +185,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
180
185
|
if (maxPassesPerItem > 0 && itemPassUsage) {
|
|
181
186
|
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
|
|
182
187
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
183
|
-
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem);
|
|
188
|
+
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem * getTotalQuantityByProductId(productsData, p.product_id));
|
|
184
189
|
if (availableAfterPassLimit.length === 0) {
|
|
185
190
|
return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
|
|
186
191
|
}
|
|
@@ -230,7 +235,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
230
235
|
voucher,
|
|
231
236
|
productsForRecommendation
|
|
232
237
|
).filter((p) => p[amountField].greaterThan(0));
|
|
233
|
-
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
|
|
238
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, productsForRecommendation, itemPassUsageMap, product_id, maxPassesPerItem);
|
|
234
239
|
if (applicableProducts.length === 0)
|
|
235
240
|
return false;
|
|
236
241
|
const usageAmount = typeof voucher.edit_current_amount === "number" ? voucher.edit_current_amount : getRecommendedAmount(voucher);
|
|
@@ -263,14 +268,16 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
263
268
|
}
|
|
264
269
|
} else {
|
|
265
270
|
const maxProduct = applicableProducts.reduce(
|
|
266
|
-
(max, p) => p[
|
|
267
|
-
);
|
|
268
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
269
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
270
|
-
calculatedAvailableMaxAmount = import_decimal.default.min(
|
|
271
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
272
|
-
maxProduct[amountField]
|
|
271
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
273
272
|
);
|
|
273
|
+
if (maxPassesPerItem > 0) {
|
|
274
|
+
calculatedAvailableMaxAmount = import_decimal.default.min(
|
|
275
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
276
|
+
maxProduct[amountField]
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
calculatedAvailableMaxAmount = maxProduct[amountField];
|
|
280
|
+
}
|
|
274
281
|
}
|
|
275
282
|
const availableMaxAmount = import_decimal.default.min(
|
|
276
283
|
baseAmount,
|
|
@@ -314,14 +321,15 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
314
321
|
}
|
|
315
322
|
} else {
|
|
316
323
|
const targetProduct = applicableProducts.reduce(
|
|
317
|
-
(max, p) => p[
|
|
318
|
-
);
|
|
319
|
-
const currentAvailableQty = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
320
|
-
const availableQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
321
|
-
const maxDeductForProduct = import_decimal.default.min(
|
|
322
|
-
targetProduct[unitPriceField].times(availableQty),
|
|
323
|
-
targetProduct[amountField]
|
|
324
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
324
325
|
);
|
|
326
|
+
let maxDeductForProduct = targetProduct[amountField];
|
|
327
|
+
if (maxPassesPerItem > 0) {
|
|
328
|
+
maxDeductForProduct = import_decimal.default.min(
|
|
329
|
+
targetProduct[unitPriceField].times(maxPassesPerItem),
|
|
330
|
+
targetProduct[amountField]
|
|
331
|
+
);
|
|
332
|
+
}
|
|
325
333
|
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
|
|
326
334
|
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
327
335
|
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
|
|
@@ -331,9 +339,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
331
339
|
parent_product_id: targetProduct.parent_product_id || null,
|
|
332
340
|
is_bundle_item: targetProduct.is_bundle_item || false,
|
|
333
341
|
deductAmount: actualDeductAmount.toNumber(),
|
|
334
|
-
// 转换为数字
|
|
335
342
|
deductQuantity: actualDeductQty
|
|
336
|
-
// 抵扣涉及的数量(用于记录)
|
|
337
343
|
});
|
|
338
344
|
}
|
|
339
345
|
const totalDeducted = maxDeduction.minus(deductionLeft);
|
|
@@ -341,7 +347,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
341
347
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
342
348
|
usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
|
|
343
349
|
deductionDetails.forEach((detail) => {
|
|
344
|
-
incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
|
|
350
|
+
incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id, detail.deductQuantity);
|
|
345
351
|
});
|
|
346
352
|
recommendedVouchers.push({
|
|
347
353
|
...voucher,
|
|
@@ -389,18 +395,18 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
389
395
|
var _a;
|
|
390
396
|
return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
|
|
391
397
|
};
|
|
392
|
-
const incrementItemPassUsage = (walletPassProductId, orderItemProductId) => {
|
|
398
|
+
const incrementItemPassUsage = (walletPassProductId, orderItemProductId, count = 1) => {
|
|
393
399
|
if (!itemPassUsageMap.has(walletPassProductId)) {
|
|
394
400
|
itemPassUsageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
395
401
|
}
|
|
396
402
|
const innerMap = itemPassUsageMap.get(walletPassProductId);
|
|
397
|
-
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) +
|
|
403
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + count);
|
|
398
404
|
};
|
|
399
405
|
const filterByMaxPassesPerItem = (products2, walletPassProductId, maxPassesPerItem) => {
|
|
400
406
|
if (maxPassesPerItem <= 0)
|
|
401
407
|
return products2;
|
|
402
408
|
return products2.filter(
|
|
403
|
-
(p) => getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem
|
|
409
|
+
(p) => getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem * getTotalQuantityByProductId(productsForCalc, p.product_id)
|
|
404
410
|
);
|
|
405
411
|
};
|
|
406
412
|
selectedVouchers.forEach((selectedVoucher) => {
|
|
@@ -463,14 +469,15 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
463
469
|
}
|
|
464
470
|
} else {
|
|
465
471
|
const targetProduct = applicableProducts.reduce(
|
|
466
|
-
(max, p) => p[
|
|
467
|
-
);
|
|
468
|
-
const currentAvailableQty = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
469
|
-
const availableQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
470
|
-
const maxDeductForProduct = import_decimal.default.min(
|
|
471
|
-
targetProduct[unitPriceField].times(availableQty),
|
|
472
|
-
targetProduct[amountField]
|
|
472
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
473
473
|
);
|
|
474
|
+
let maxDeductForProduct = targetProduct[amountField];
|
|
475
|
+
if (maxPassesPerItem > 0) {
|
|
476
|
+
maxDeductForProduct = import_decimal.default.min(
|
|
477
|
+
targetProduct[unitPriceField].times(maxPassesPerItem),
|
|
478
|
+
targetProduct[amountField]
|
|
479
|
+
);
|
|
480
|
+
}
|
|
474
481
|
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
|
|
475
482
|
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
476
483
|
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
|
|
@@ -480,15 +487,13 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
480
487
|
parent_product_id: targetProduct.parent_product_id || null,
|
|
481
488
|
is_bundle_item: targetProduct.is_bundle_item || false,
|
|
482
489
|
deductAmount: actualDeductAmount.toNumber(),
|
|
483
|
-
// 转换为数字
|
|
484
490
|
deductQuantity: actualDeductQty
|
|
485
|
-
// 抵扣涉及的数量(用于记录)
|
|
486
491
|
});
|
|
487
492
|
}
|
|
488
493
|
const totalDeducted = maxDeduction.minus(deductionLeft);
|
|
489
494
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
490
495
|
deductionDetails.forEach((detail) => {
|
|
491
|
-
incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
|
|
496
|
+
incrementItemPassUsage(selectedVoucher.product_id, detail.product_id, detail.deductQuantity);
|
|
492
497
|
});
|
|
493
498
|
selectedWithDetails.push({
|
|
494
499
|
...selectedVoucher,
|
|
@@ -572,14 +577,16 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
572
577
|
}
|
|
573
578
|
} else {
|
|
574
579
|
const maxProduct = applicableProducts.reduce(
|
|
575
|
-
(max, p) => p[
|
|
576
|
-
);
|
|
577
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
578
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
579
|
-
calculatedMaxAmount = import_decimal.default.min(
|
|
580
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
581
|
-
maxProduct[amountField]
|
|
580
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
582
581
|
);
|
|
582
|
+
if (maxPassesPerItem > 0) {
|
|
583
|
+
calculatedMaxAmount = import_decimal.default.min(
|
|
584
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
585
|
+
maxProduct[amountField]
|
|
586
|
+
);
|
|
587
|
+
} else {
|
|
588
|
+
calculatedMaxAmount = maxProduct[amountField];
|
|
589
|
+
}
|
|
583
590
|
}
|
|
584
591
|
calculatedMaxAmount = import_decimal.default.min(
|
|
585
592
|
baseAmount,
|
|
@@ -23,7 +23,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
23
23
|
*/
|
|
24
24
|
private logError;
|
|
25
25
|
createOrder(params: CommitOrderParams['query']): {
|
|
26
|
-
type: "
|
|
26
|
+
type: "virtual" | "appointment_booking";
|
|
27
27
|
platform: string;
|
|
28
28
|
sales_channel: string;
|
|
29
29
|
order_sales_channel: string;
|
package/lib/server/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { MenuModule } from './modules/menu';
|
|
|
3
3
|
import { QuotationModule } from './modules/quotation';
|
|
4
4
|
import { ScheduleModuleEx } from './modules/schedule';
|
|
5
5
|
import { ResourceModule } from './modules/resource';
|
|
6
|
+
import { FloorPlanModule } from './modules/floor-plan';
|
|
6
7
|
import { PisellCore, ServerModuleConfig, InitializeServerOptions } from '../types';
|
|
7
8
|
import type { RouteHandler, HttpMethod, RouteDefinition, Router, ModuleRegistryConfig, RequestSetting } from './types';
|
|
8
9
|
import { OrderModule } from './modules/order';
|
|
@@ -21,10 +22,14 @@ declare class Server {
|
|
|
21
22
|
schedule?: ScheduleModuleEx;
|
|
22
23
|
resource?: ResourceModule;
|
|
23
24
|
order?: OrderModule;
|
|
25
|
+
floor_plan?: FloorPlanModule;
|
|
26
|
+
/** GET 前缀路由(最长前缀优先匹配) */
|
|
27
|
+
private prefixRouterGet;
|
|
24
28
|
router: Router;
|
|
25
29
|
private productQuerySubscribers;
|
|
26
30
|
private orderQuerySubscribers;
|
|
27
31
|
private bookingQuerySubscribers;
|
|
32
|
+
private floorPlanQuerySubscribers;
|
|
28
33
|
private moduleRegistry;
|
|
29
34
|
constructor(core: PisellCore);
|
|
30
35
|
/**
|
|
@@ -32,6 +37,10 @@ declare class Server {
|
|
|
32
37
|
* @param routes 路由定义数组
|
|
33
38
|
*/
|
|
34
39
|
private registerRoutes;
|
|
40
|
+
/**
|
|
41
|
+
* 注册前缀路由(仅 GET)。匹配规则:path === prefix 或 path 以 prefix + '/' 开头;最长前缀优先。
|
|
42
|
+
*/
|
|
43
|
+
private registerPrefixRoutes;
|
|
35
44
|
/**
|
|
36
45
|
* 注册单个模块并自动注册其路由
|
|
37
46
|
* @param module 模块实例
|
|
@@ -184,6 +193,27 @@ declare class Server {
|
|
|
184
193
|
* 转发到资源模块去
|
|
185
194
|
*/
|
|
186
195
|
private handleResourceList;
|
|
196
|
+
/**
|
|
197
|
+
* 从 url 或路由 path 解析 pathname(不含 query,去掉末尾 /)
|
|
198
|
+
*/
|
|
199
|
+
private parseRequestPath;
|
|
200
|
+
/**
|
|
201
|
+
* 解析平面图 GET 路径为查询上下文
|
|
202
|
+
*/
|
|
203
|
+
private resolveFloorPlanQueryContext;
|
|
204
|
+
/**
|
|
205
|
+
* GET /shop/schedule/floor-plan* 前缀路由:读本地 store;支持 subscriberId + callback 订阅更新
|
|
206
|
+
*/
|
|
207
|
+
private handleFloorPlanGet;
|
|
208
|
+
private computeFloorPlanQueryResult;
|
|
209
|
+
/**
|
|
210
|
+
* 平面图数据变更后向所有 GET 订阅者推送最新结果
|
|
211
|
+
*/
|
|
212
|
+
private recomputeAndNotifyFloorPlanQuery;
|
|
213
|
+
/**
|
|
214
|
+
* 取消平面图 GET 订阅(也可走 GET .../unsubscribe + subscriberId)
|
|
215
|
+
*/
|
|
216
|
+
removeFloorPlanQuerySubscriber(subscriberId?: string): void;
|
|
187
217
|
/**
|
|
188
218
|
* 取消预约列表查询订阅(HTTP 路由入口)
|
|
189
219
|
*/
|