@pisell/pisellos 0.10.55 → 0.10.57
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/promotion/index.js +0 -9
- package/dist/modules/OpenData/index.js +2 -1
- package/dist/modules/Order/index.d.ts +1 -0
- package/dist/modules/Order/index.js +118 -59
- package/dist/modules/Order/utils/weighedProduct.d.ts +12 -0
- package/dist/modules/Order/utils/weighedProduct.js +94 -0
- package/dist/modules/Order/utils.d.ts +1 -1
- package/dist/modules/Order/utils.js +9 -6
- package/dist/modules/Payment/index.d.ts +0 -5
- package/dist/modules/Payment/index.js +35 -85
- package/dist/modules/ProductList/index.js +18 -5
- package/dist/modules/ResourcePlanner/planner.js +10 -8
- package/dist/modules/Rules/index.js +16 -14
- package/dist/modules/Rules/types.d.ts +2 -0
- package/dist/plugins/request.d.ts +4 -0
- package/dist/server/index.js +122 -69
- package/dist/server/modules/floor-plan/index.js +34 -3
- package/dist/server/modules/payment/index.d.ts +25 -6
- package/dist/server/modules/payment/index.js +660 -133
- package/dist/server/test.json +1 -1
- package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
- package/dist/shared/receipt/small-ticket.d.ts +11 -0
- package/dist/shared/receipt/small-ticket.js +430 -255
- package/dist/shared/receipt/templateVisibility.d.ts +23 -0
- package/dist/shared/receipt/templateVisibility.js +64 -0
- package/dist/solution/BaseSales/index.d.ts +4 -2
- package/dist/solution/BaseSales/index.js +1328 -1214
- package/dist/solution/BaseSales/types.d.ts +5 -1
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +40 -25
- package/dist/solution/BaseSales/utils.js +3 -2
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +4 -1
- package/dist/solution/BookingTicket/index.js +120 -80
- package/dist/solution/OrderInvoicePreview/index.d.ts +10 -0
- package/dist/solution/OrderInvoicePreview/index.js +87 -16
- package/dist/solution/RegisterAndLogin/config.js +54 -11
- package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
- package/dist/solution/RegisterAndLogin/index.js +1 -1
- package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/dist/solution/RegisterAndLogin/utils.js +36 -16
- package/dist/solution/Sales/index.d.ts +2 -0
- package/dist/solution/Sales/index.js +15 -1
- package/dist/solution/ScanOrder/utils.js +3 -2
- package/dist/solution/UnifiedBookingSales/index.js +12 -13
- package/dist/solution/VenueBooking/index.d.ts +15 -2
- package/dist/solution/VenueBooking/index.js +1062 -785
- package/dist/solution/VenueBooking/types.d.ts +2 -1
- package/dist/solution/VenueBooking/types.js +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
- package/lib/modules/OpenData/index.js +2 -1
- package/lib/modules/Order/index.d.ts +1 -0
- package/lib/modules/Order/index.js +71 -11
- package/lib/modules/Order/utils/weighedProduct.d.ts +12 -0
- package/lib/modules/Order/utils/weighedProduct.js +89 -0
- package/lib/modules/Order/utils.d.ts +1 -1
- package/lib/modules/Order/utils.js +5 -3
- package/lib/modules/Payment/index.d.ts +0 -5
- package/lib/modules/Payment/index.js +3 -28
- package/lib/modules/ProductList/index.js +14 -1
- package/lib/modules/ResourcePlanner/planner.js +10 -8
- package/lib/modules/Rules/index.js +16 -14
- package/lib/modules/Rules/types.d.ts +2 -0
- package/lib/plugins/request.d.ts +4 -0
- package/lib/server/index.js +72 -28
- package/lib/server/modules/floor-plan/index.js +29 -1
- package/lib/server/modules/payment/index.d.ts +25 -6
- package/lib/server/modules/payment/index.js +350 -78
- package/lib/server/test.json +1 -1
- package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
- package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
- package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
- package/lib/shared/receipt/small-ticket.d.ts +11 -0
- package/lib/shared/receipt/small-ticket.js +234 -30
- package/lib/shared/receipt/templateVisibility.d.ts +23 -0
- package/lib/shared/receipt/templateVisibility.js +63 -0
- package/lib/solution/BaseSales/index.d.ts +4 -2
- package/lib/solution/BaseSales/index.js +79 -18
- package/lib/solution/BaseSales/types.d.ts +5 -1
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +24 -9
- package/lib/solution/BaseSales/utils.js +2 -1
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +4 -1
- package/lib/solution/BookingTicket/index.js +14 -2
- package/lib/solution/OrderInvoicePreview/index.d.ts +10 -0
- package/lib/solution/OrderInvoicePreview/index.js +62 -10
- package/lib/solution/RegisterAndLogin/config.js +57 -11
- package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
- package/lib/solution/RegisterAndLogin/index.js +9 -1
- package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
- package/lib/solution/RegisterAndLogin/utils.js +24 -17
- package/lib/solution/Sales/index.d.ts +2 -0
- package/lib/solution/Sales/index.js +10 -1
- package/lib/solution/ScanOrder/utils.js +2 -1
- package/lib/solution/UnifiedBookingSales/index.js +5 -6
- package/lib/solution/VenueBooking/index.d.ts +15 -2
- package/lib/solution/VenueBooking/index.js +243 -96
- package/lib/solution/VenueBooking/types.d.ts +2 -1
- package/lib/solution/VenueBooking/types.js +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
- package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
- package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
- package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ var _BaseModule = require("../BaseModule");
|
|
|
15
15
|
var _types = require("./types");
|
|
16
16
|
var _utils = require("./utils");
|
|
17
17
|
var _utils2 = require("../Product/utils");
|
|
18
|
+
var _weighedProduct = require("./utils/weighedProduct");
|
|
18
19
|
var _pickupTiming = require("./pickupTiming");
|
|
19
20
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
20
21
|
var _availability = require("../Discount/availability");
|
|
@@ -567,9 +568,9 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
567
568
|
with_wallet_pass_holder: 1,
|
|
568
569
|
with_available_wallet_flag: 1,
|
|
569
570
|
with_available_wallet_pass_flag: 1,
|
|
570
|
-
with_virtual_currency: 1,
|
|
571
571
|
with_entitlement_pass: 1,
|
|
572
572
|
filter_order_wallet_pass_usage: 0,
|
|
573
|
+
with_virtual_currency: 1,
|
|
573
574
|
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
574
575
|
...(orderId ? {
|
|
575
576
|
order_id: orderId
|
|
@@ -838,6 +839,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
838
839
|
orderTotalAmount: Number(this.store.summary?.total_amount || 0)
|
|
839
840
|
}, {
|
|
840
841
|
reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids,
|
|
842
|
+
reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids,
|
|
841
843
|
preferredEntitlementPassIds: options?.preferredEntitlementPassIds
|
|
842
844
|
});
|
|
843
845
|
if (result?.productList) {
|
|
@@ -3938,7 +3940,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3938
3940
|
const product = uid !== undefined ? tempOrder.products.find(item => String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === String(uid)) : tempOrder.products[(0, _utils3.getProductIdentityIndex)(tempOrder.products, params)];
|
|
3939
3941
|
return !product || (0, _utils3.getSafeProductNum)(params.updates.num) > product.num;
|
|
3940
3942
|
}
|
|
3941
|
-
applyOrderProductUpdateToTempOrder(tempOrder, params) {
|
|
3943
|
+
applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids) {
|
|
3942
3944
|
const {
|
|
3943
3945
|
product_id,
|
|
3944
3946
|
product_variant_id,
|
|
@@ -4011,7 +4013,8 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4011
4013
|
const previousLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(targetProduct), targetProduct.product_bundle);
|
|
4012
4014
|
const nextLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(nextProduct), nextProduct.product_bundle);
|
|
4013
4015
|
const callerChangesVariant = Number(product_variant_id || 0) !== Number(targetProduct.product_variant_id || 0);
|
|
4014
|
-
const
|
|
4016
|
+
const callerChangesWeighing = (0, _weighedProduct.getWeighedProductPriceFingerprint)(targetProduct.product_sku) !== (0, _weighedProduct.getWeighedProductPriceFingerprint)(nextProduct.product_sku);
|
|
4017
|
+
const callerChangesLineConfiguration = callerChangesVariant || callerChangesWeighing || previousLineFingerprint !== nextLineFingerprint;
|
|
4015
4018
|
const isPersistedOrderLine = targetProduct.order_detail_id !== undefined && targetProduct.order_detail_id !== null;
|
|
4016
4019
|
const callerChangesDiscountList = Array.isArray(updates?.discount_list) && updates.discount_list.length > 0 && !isSameDiscountList(updates.discount_list, targetProduct.discount_list);
|
|
4017
4020
|
const callerUpdatesManualPrice = updates?.metadata?.price_override !== undefined || updates?.metadata?.is_manual_discount !== undefined || updates?.metadata?.product_discount_reason !== undefined || callerChangesDiscountList;
|
|
@@ -4027,6 +4030,23 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4027
4030
|
};
|
|
4028
4031
|
return hasChangedPrice(updates.selling_price, targetProduct.selling_price) || hasChangedPrice(updates.original_price, targetProduct.original_price);
|
|
4029
4032
|
})();
|
|
4033
|
+
const weighedSourcePrice = callerChangesWeighing ? (0, _weighedProduct.resolveWeighedSourcePrice)(nextProduct.product_sku) : undefined;
|
|
4034
|
+
const hasResolvedWeighedPrice = weighedSourcePrice !== undefined;
|
|
4035
|
+
if (hasResolvedWeighedPrice && !callerUpdatesSourcePrice) {
|
|
4036
|
+
const nextMainOriginal = new _decimal.default(weighedSourcePrice).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
|
|
4037
|
+
const previousMainOriginal = new _decimal.default(targetProduct.metadata?.main_product_original_price ?? targetProduct.original_price ?? 0);
|
|
4038
|
+
const previousMainSelling = new _decimal.default(targetProduct.metadata?.main_product_selling_price ?? targetProduct.selling_price ?? 0);
|
|
4039
|
+
// A manual final price remains fixed; other legacy discounts keep their amount
|
|
4040
|
+
// until Rules reapplies the currently selected wallet discount below.
|
|
4041
|
+
const nextMainSelling = isManualProductDiscountProduct(nextProduct) ? previousMainSelling : nextMainOriginal.minus(previousMainOriginal.minus(previousMainSelling));
|
|
4042
|
+
nextProduct.metadata = {
|
|
4043
|
+
...nextProduct.metadata,
|
|
4044
|
+
source_product_price: weighedSourcePrice,
|
|
4045
|
+
main_product_original_price: callerUpdatesMainOriginalPrice ? nextProduct.metadata.main_product_original_price : nextMainOriginal.toFixed(2),
|
|
4046
|
+
main_product_selling_price: callerUpdatesMainSellingPrice ? nextProduct.metadata.main_product_selling_price : _decimal.default.max(0, nextMainSelling).toFixed(2),
|
|
4047
|
+
price_schema_version: 2
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4030
4050
|
if (isPersistedOrderLine && !callerChangesLineConfiguration && !callerUpdatesManualPrice && !callerAllowsBookingReprice) {
|
|
4031
4051
|
nextProduct.selling_price = targetProduct.selling_price;
|
|
4032
4052
|
nextProduct.original_price = targetProduct.original_price;
|
|
@@ -4043,17 +4063,17 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4043
4063
|
// 新配置只传了 source 时,不能把未传的 main_product_* 从旧行继承下来。
|
|
4044
4064
|
// 以新 source + 新 option 重建券前价;券后价未显式提供时保留旧主商品折扣额。
|
|
4045
4065
|
const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price) || 0);
|
|
4046
|
-
const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price
|
|
4066
|
+
const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price ?? previousMainOriginal.toNumber()));
|
|
4047
4067
|
const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
|
|
4048
4068
|
const nextMainOriginal = new _decimal.default(Number(updates.metadata.source_product_price) || 0).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
|
|
4049
4069
|
nextProduct.metadata = {
|
|
4050
4070
|
...(nextProduct.metadata || {}),
|
|
4051
4071
|
source_product_price: String(updates.metadata.source_product_price),
|
|
4052
4072
|
main_product_original_price: callerUpdatesMainOriginalPrice ? String(updates.metadata.main_product_original_price) : nextMainOriginal.toDecimalPlaces(2).toFixed(2),
|
|
4053
|
-
main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
|
|
4073
|
+
main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : hasResolvedWeighedPrice && isManualProductDiscountProduct(nextProduct) ? previousMainSelling.toFixed(2) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
|
|
4054
4074
|
price_schema_version: 2
|
|
4055
4075
|
};
|
|
4056
|
-
} else if (callerChangesVariant && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
4076
|
+
} else if (callerChangesVariant && !hasResolvedWeighedPrice && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
4057
4077
|
// variant 已变化但 caller 没有显式报价时,旧 v2 metadata 不能继续充当权威价格源。
|
|
4058
4078
|
// 清理后由 normalize 优先读取 metadata.origin.variant 中新 variant 的目录价;
|
|
4059
4079
|
// 顶层 original/selling 的旧差额仍会作为 legacy discount 迁移到新价格。
|
|
@@ -4065,7 +4085,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4065
4085
|
delete nextProduct.metadata.main_product_selling_price;
|
|
4066
4086
|
delete nextProduct.metadata.main_product_original_price;
|
|
4067
4087
|
delete nextProduct.metadata.price_schema_version;
|
|
4068
|
-
} else if (callerChangesLineConfiguration && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
4088
|
+
} else if (callerChangesLineConfiguration && !hasResolvedWeighedPrice && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
4069
4089
|
// 仅 option / bundle 变化时,保留历史 source 和主商品折扣金额,
|
|
4070
4090
|
// 但必须让 original 与 selling 一起随新的 option 金额移动。
|
|
4071
4091
|
const previousOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(targetProduct));
|
|
@@ -4082,7 +4102,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4082
4102
|
main_product_selling_price: nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
|
|
4083
4103
|
price_schema_version: 2
|
|
4084
4104
|
};
|
|
4085
|
-
} else if (callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
4105
|
+
} else if (callerUpdatesTopPrice && !callerUpdatesMainMeta && !hasResolvedWeighedPrice) {
|
|
4086
4106
|
const existedMeta = nextProduct.metadata || {};
|
|
4087
4107
|
nextProduct.metadata = {
|
|
4088
4108
|
...existedMeta
|
|
@@ -4092,6 +4112,37 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4092
4112
|
delete nextProduct.metadata.main_product_original_price;
|
|
4093
4113
|
delete nextProduct.metadata.price_schema_version;
|
|
4094
4114
|
}
|
|
4115
|
+
if (callerChangesLineConfiguration && (0, _weighedProduct.resolveWeighedSourcePrice)(nextProduct.product_sku) !== undefined && nextProduct.metadata.main_product_original_price != null && nextProduct.metadata.main_product_selling_price != null && isManualProductDiscountProduct(nextProduct)) {
|
|
4116
|
+
const originalPrice = (0, _utils.composeLinePrice)({
|
|
4117
|
+
mainPrice: nextProduct.metadata.main_product_original_price,
|
|
4118
|
+
bundle: (0, _weighedProduct.withOriginalBundleOptionPrices)(nextProduct.product_bundle),
|
|
4119
|
+
useOriginalBundle: true
|
|
4120
|
+
});
|
|
4121
|
+
const sellingPrice = (0, _utils.composeLinePrice)({
|
|
4122
|
+
mainPrice: nextProduct.metadata.main_product_selling_price,
|
|
4123
|
+
bundle: nextProduct.product_bundle
|
|
4124
|
+
});
|
|
4125
|
+
const previousManualDiscount = (0, _manualProductDiscount.findManualProductDiscountItem)(nextProduct.discount_list);
|
|
4126
|
+
nextProduct.original_price = originalPrice;
|
|
4127
|
+
nextProduct.metadata = {
|
|
4128
|
+
...nextProduct.metadata,
|
|
4129
|
+
origin: {
|
|
4130
|
+
...nextProduct.metadata.origin,
|
|
4131
|
+
_extend: {
|
|
4132
|
+
...nextProduct.metadata.origin?._extend,
|
|
4133
|
+
origin_total: Number(originalPrice)
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
};
|
|
4137
|
+
nextProduct.discount_list = (0, _manualProductDiscount.mergeManualProductDiscountIntoList)(nextProduct.discount_list, (0, _manualProductDiscount.buildManualProductDiscountItem)({
|
|
4138
|
+
originTotal: Number(originalPrice),
|
|
4139
|
+
sellingTotal: Number(sellingPrice),
|
|
4140
|
+
quantity: nextProduct.num,
|
|
4141
|
+
message: previousManualDiscount?.discount.message,
|
|
4142
|
+
discountway: previousManualDiscount?.discount.discountway,
|
|
4143
|
+
discount_per: previousManualDiscount?.discount.discount_per
|
|
4144
|
+
}));
|
|
4145
|
+
}
|
|
4095
4146
|
// normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
|
|
4096
4147
|
// 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
|
|
4097
4148
|
let normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct)));
|
|
@@ -4129,6 +4180,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4129
4180
|
};
|
|
4130
4181
|
tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
|
|
4131
4182
|
}
|
|
4183
|
+
if (callerChangesWeighing && !isManualProductDiscountProduct(normalizedProduct)) {
|
|
4184
|
+
const uid = normalizedProduct.metadata?.unique_identification_number;
|
|
4185
|
+
if (uid) reapplyWeighedDiscountProductUids?.push(String(uid));
|
|
4186
|
+
}
|
|
4132
4187
|
tempOrder.products[productIndex] = normalizedProduct;
|
|
4133
4188
|
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
4134
4189
|
this.captureProductRuntime(tempOrder, updates, tempOrder.products[productIndex], tempOrder.products[productIndex].metadata?.unique_identification_number);
|
|
@@ -4139,9 +4194,11 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4139
4194
|
if (this.isRestrictedCartProductUpdate(tempOrder, params)) {
|
|
4140
4195
|
this.createCartIntentCommitGuard('update_product', tempOrder)();
|
|
4141
4196
|
}
|
|
4142
|
-
const
|
|
4197
|
+
const reapplyWeighedDiscountProductUids = [];
|
|
4198
|
+
const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
|
|
4143
4199
|
const promotionPayload = await this.evaluatePromotion();
|
|
4144
4200
|
this.applyDiscount({
|
|
4201
|
+
reapplyWeighedDiscountProductUids,
|
|
4145
4202
|
reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
|
|
4146
4203
|
});
|
|
4147
4204
|
this.sanitizeTempOrderProducts(tempOrder);
|
|
@@ -4163,8 +4220,9 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4163
4220
|
if (paramsList.some(params => this.isRestrictedCartProductUpdate(tempOrder, params))) {
|
|
4164
4221
|
this.createCartIntentCommitGuard('update_product', tempOrder)();
|
|
4165
4222
|
}
|
|
4223
|
+
const reapplyWeighedDiscountProductUids = [];
|
|
4166
4224
|
const reapplyBookingDiscountProductUids = paramsList.reduce((acc, params) => {
|
|
4167
|
-
const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
|
|
4225
|
+
const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
|
|
4168
4226
|
// 批量改预约时间时,延迟到最后一次折扣计算里统一恢复已选预约折扣卡。
|
|
4169
4227
|
if (params.allow_booking_discount_reapply && updatedProductUid) {
|
|
4170
4228
|
acc.push(String(updatedProductUid));
|
|
@@ -4172,6 +4230,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4172
4230
|
return acc;
|
|
4173
4231
|
}, []);
|
|
4174
4232
|
await this.finalizeAfterProductsMutation(tempOrder, {
|
|
4233
|
+
reapplyWeighedDiscountProductUids,
|
|
4175
4234
|
reapplyBookingDiscountProductUids: reapplyBookingDiscountProductUids.length ? reapplyBookingDiscountProductUids : undefined
|
|
4176
4235
|
});
|
|
4177
4236
|
this.logInfo('updateOrderProduct success', {
|
|
@@ -4307,7 +4366,8 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4307
4366
|
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
4308
4367
|
const promotionPayload = await this.evaluatePromotion();
|
|
4309
4368
|
this.applyDiscount({
|
|
4310
|
-
reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
|
|
4369
|
+
reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids,
|
|
4370
|
+
reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids
|
|
4311
4371
|
});
|
|
4312
4372
|
this.sanitizeTempOrderProducts(tempOrder);
|
|
4313
4373
|
await this.recalculateSummary({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Only pricing/measurement fields participate; display and option data do not. */
|
|
2
|
+
export declare function getWeighedProductPriceFingerprint(sku?: Record<string, any>): string;
|
|
3
|
+
/**
|
|
4
|
+
* A weighed line's source is the amount for its net weight, never its catalog unit price.
|
|
5
|
+
* Persisted SKU measurements and the full catalog snapshot take priority over runtime
|
|
6
|
+
* _extend.weighing, which can survive repricing with an obsolete total_amount.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveWeighedSourcePrice(sku?: Record<string, any>, weighing?: Record<string, any>): string | undefined;
|
|
9
|
+
/** Original bundle totals must use the nested options before manual distribution. */
|
|
10
|
+
export declare function withOriginalBundleOptionPrices(bundles?: Array<Record<string, any>>): {
|
|
11
|
+
option: any;
|
|
12
|
+
}[] | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getWeighedProductPriceFingerprint = getWeighedProductPriceFingerprint;
|
|
7
|
+
exports.resolveWeighedSourcePrice = resolveWeighedSourcePrice;
|
|
8
|
+
exports.withOriginalBundleOptionPrices = withOriginalBundleOptionPrices;
|
|
9
|
+
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
10
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
+
const UNIT_GRAMS = {
|
|
12
|
+
kg: '1000',
|
|
13
|
+
KILOGRAMS: '1000',
|
|
14
|
+
g: '1',
|
|
15
|
+
GRAMS: '1',
|
|
16
|
+
lb: '453.59237',
|
|
17
|
+
POUNDS: '453.59237',
|
|
18
|
+
oz: '28.349523125',
|
|
19
|
+
OUNCES: '28.349523125'
|
|
20
|
+
};
|
|
21
|
+
function nonNegative(value) {
|
|
22
|
+
if (value === null || value === undefined || value === '') return undefined;
|
|
23
|
+
try {
|
|
24
|
+
const result = new _decimal.default(value);
|
|
25
|
+
return result.isFinite() && result.gte(0) ? result : undefined;
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function numericKey(value) {
|
|
31
|
+
return nonNegative(value)?.toString();
|
|
32
|
+
}
|
|
33
|
+
function unitKey(value) {
|
|
34
|
+
return UNIT_GRAMS[String(value)] || (value == null ? undefined : String(value));
|
|
35
|
+
}
|
|
36
|
+
function snapshotKey(snapshot) {
|
|
37
|
+
return [numericKey(snapshot?.price), unitKey(snapshot?.unit), numericKey(snapshot?.unit_value ?? 1)];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Only pricing/measurement fields participate; display and option data do not. */
|
|
41
|
+
function getWeighedProductPriceFingerprint(sku) {
|
|
42
|
+
if (Number(sku?.open_sold_weight) !== 1) return 'unweighed';
|
|
43
|
+
return JSON.stringify([1, numericKey(sku?.net_weight), unitKey(sku?.unit), numericKey(sku?.unit_price), numericKey(sku?.unit_weight ?? 1), snapshotKey(sku?.unit_data_snapshot)]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A weighed line's source is the amount for its net weight, never its catalog unit price.
|
|
48
|
+
* Persisted SKU measurements and the full catalog snapshot take priority over runtime
|
|
49
|
+
* _extend.weighing, which can survive repricing with an obsolete total_amount.
|
|
50
|
+
*/
|
|
51
|
+
function resolveWeighedSourcePrice(sku, weighing) {
|
|
52
|
+
if (sku?.open_sold_weight != null && Number(sku.open_sold_weight) !== 1) return undefined;
|
|
53
|
+
if (Number(sku?.open_sold_weight) !== 1 && !weighing) return undefined;
|
|
54
|
+
const hasSkuMeasurement = sku?.net_weight !== undefined && sku?.net_weight !== null;
|
|
55
|
+
const measurement = hasSkuMeasurement ? sku : weighing;
|
|
56
|
+
const netWeight = nonNegative(measurement?.net_weight);
|
|
57
|
+
if (netWeight === undefined) return undefined;
|
|
58
|
+
const snapshot = hasSkuMeasurement ? sku?.unit_data_snapshot : weighing?.scalePriceProduct;
|
|
59
|
+
const snapshotPrice = nonNegative(snapshot?.price);
|
|
60
|
+
const snapshotUnits = nonNegative(snapshot?.unit_value ?? 1);
|
|
61
|
+
const sourceGrams = UNIT_GRAMS[String(snapshot?.unit)];
|
|
62
|
+
const targetGrams = UNIT_GRAMS[String(measurement?.unit)];
|
|
63
|
+
if (snapshotPrice && snapshotUnits?.gt(0) && sourceGrams && targetGrams) {
|
|
64
|
+
return snapshotPrice.mul(netWeight).mul(targetGrams).div(sourceGrams).div(snapshotUnits).toFixed(2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Legacy callers may only retain the native scale's amount. Do not let an old
|
|
68
|
+
// amount overwrite a changed SKU measurement, unit price, or pricing snapshot.
|
|
69
|
+
const runtimeAmount = nonNegative(weighing?.total_amount);
|
|
70
|
+
const runtimeMatchesSku = !hasSkuMeasurement || numericKey(weighing?.net_weight) === numericKey(sku?.net_weight) && unitKey(weighing?.unit) === unitKey(sku?.unit) && numericKey(weighing?.unit_price) === numericKey(sku?.unit_price) && numericKey(weighing?.unit_weight ?? 1) === numericKey(sku?.unit_weight ?? 1) && JSON.stringify(snapshotKey(weighing?.scalePriceProduct)) === JSON.stringify(snapshotKey(sku?.unit_data_snapshot));
|
|
71
|
+
if (runtimeAmount && runtimeMatchesSku) return runtimeAmount.toFixed(2);
|
|
72
|
+
|
|
73
|
+
// Older persisted orders have only a rounded per-scale-unit price.
|
|
74
|
+
const unitPrice = nonNegative(measurement?.unit_price);
|
|
75
|
+
const unitWeight = nonNegative(measurement?.unit_weight ?? 1);
|
|
76
|
+
if (!unitPrice || !unitWeight?.gt(0)) return undefined;
|
|
77
|
+
return netWeight.div(unitWeight).mul(unitPrice).toFixed(2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Original bundle totals must use the nested options before manual distribution. */
|
|
81
|
+
function withOriginalBundleOptionPrices(bundles) {
|
|
82
|
+
return bundles?.map(bundle => ({
|
|
83
|
+
...bundle,
|
|
84
|
+
option: bundle.option?.map(option => ({
|
|
85
|
+
...option,
|
|
86
|
+
add_price: option.original_price ?? option.add_price ?? option.price
|
|
87
|
+
}))
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Decimal from 'decimal.js';
|
|
2
|
-
import type { CartItem } from
|
|
2
|
+
import type { CartItem } from "../Cart";
|
|
3
3
|
import type { OrderProductDiscountItem, OrderMetaList, OrderProduct, OrderSubmitPayload, OrderSubmitProduct, OrderSummary, OrderTempOrder, PointCardSnapshotItem, VirtualCurrencySnapshotItem } from './types';
|
|
4
4
|
import type { RulesParamsHooks } from '../Rules/types';
|
|
5
5
|
import type { MapOrderPaymentsOptions, OrderPaymentData, OrderPaymentSource } from './types';
|
|
@@ -131,12 +131,13 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
|
|
|
131
131
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
132
132
|
var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
|
|
133
133
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
134
|
+
var _weighedProduct = require("./utils/weighedProduct");
|
|
135
|
+
var _manualProductDiscount = require("./utils/manualProductDiscount");
|
|
134
136
|
var _utils = require("../Holder/utils");
|
|
135
137
|
var _utils2 = require("../../solution/ScanOrder/utils");
|
|
136
138
|
var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
|
|
137
139
|
var _salesNotes = require("./salesNotes");
|
|
138
140
|
var _pickupTiming = require("./pickupTiming");
|
|
139
|
-
var _manualProductDiscount = require("./utils/manualProductDiscount");
|
|
140
141
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
141
142
|
_dayjs.default.extend(_customParseFormat.default);
|
|
142
143
|
const POINT_CARD_SNAPSHOT_TAG = 'point_card';
|
|
@@ -957,7 +958,7 @@ function createDefaultOrderRulesHooks(getBookings) {
|
|
|
957
958
|
});
|
|
958
959
|
const composedOriginalPrice = composeLinePrice({
|
|
959
960
|
mainPrice: nextMainOriginalPrice,
|
|
960
|
-
bundle: nextBundle,
|
|
961
|
+
bundle: Number(product.product_sku?.open_sold_weight) === 1 && (0, _manualProductDiscount.hasManualProductDiscountFlag)(metadataAny) ? (0, _weighedProduct.withOriginalBundleOptionPrices)(nextBundle) : nextBundle,
|
|
961
962
|
useOriginalBundle: true
|
|
962
963
|
});
|
|
963
964
|
const finalOriginalPrice = resolveManualOverrideLineOriginalPrice({
|
|
@@ -1058,6 +1059,7 @@ function ensureCartItemOriginHolder(cartItem, window) {
|
|
|
1058
1059
|
form_record: [formRecordId]
|
|
1059
1060
|
};
|
|
1060
1061
|
}
|
|
1062
|
+
;
|
|
1061
1063
|
const getAllDiscountList = cartItem => {
|
|
1062
1064
|
let discountList = cartItem?._origin?.product?.discount_list || [];
|
|
1063
1065
|
(cartItem?._origin?.product?.product_bundle || []).forEach(item => {
|
|
@@ -1538,7 +1540,7 @@ function buildSubmitPayload(params) {
|
|
|
1538
1540
|
shop_discount: tempOrder.shop_discount || '0.00',
|
|
1539
1541
|
surcharge_fee: tempOrder.surcharge_fee || '0.00',
|
|
1540
1542
|
note: tempOrder.note || '',
|
|
1541
|
-
delivery_type: tempOrder.delivery_type || '
|
|
1543
|
+
delivery_type: tempOrder.delivery_type || 'shop_service',
|
|
1542
1544
|
schedule_date: scheduleDate,
|
|
1543
1545
|
// created_at: tempOrder.created_at || formatDateTime(now), 和后端协定这个字段先不提交到接口
|
|
1544
1546
|
bookings: (tempOrder.bookings || []).filter(booking => (0, _pickupTiming.isFulfillmentTimingBooking)(booking) || Number(booking.parent_id) !== 0).map(booking => normalizeSubmitBooking(booking)),
|
|
@@ -171,11 +171,6 @@ export declare class PaymentModule extends BaseModule implements Module, Payment
|
|
|
171
171
|
* 获取钱包支付方式
|
|
172
172
|
*/
|
|
173
173
|
getWalletPaymentMethod(): Promise<PaymentMethod | null>;
|
|
174
|
-
/**
|
|
175
|
-
* 确保支付模块所需的数据库表已创建
|
|
176
|
-
*/
|
|
177
|
-
/** @deprecated */
|
|
178
|
-
private ensurePaymentTables;
|
|
179
174
|
/**
|
|
180
175
|
* 获取部分支付的订单
|
|
181
176
|
*/
|
|
@@ -160,10 +160,6 @@ class PaymentModule extends _BaseModule.BaseModule {
|
|
|
160
160
|
this.app = this.appPlugin.getApp();
|
|
161
161
|
this.dbManager = this.app.dbManager;
|
|
162
162
|
this.logger = this.app.logger;
|
|
163
|
-
|
|
164
|
-
// 确保支付模块所需的对象存储已创建
|
|
165
|
-
// await this.ensurePaymentTables();
|
|
166
|
-
|
|
167
163
|
this.registerNetworkHandlers();
|
|
168
164
|
|
|
169
165
|
// // 预连接数据库
|
|
@@ -1110,7 +1106,7 @@ class PaymentModule extends _BaseModule.BaseModule {
|
|
|
1110
1106
|
*/
|
|
1111
1107
|
async getCashPaymentMethod() {
|
|
1112
1108
|
try {
|
|
1113
|
-
const payMethods = await this.
|
|
1109
|
+
const payMethods = await this.getPayMethodListAsync();
|
|
1114
1110
|
return payMethods.find(method => method.code === _types.PaymentMethodType.Cash) || null;
|
|
1115
1111
|
} catch (error) {
|
|
1116
1112
|
console.error('[PaymentModule] 获取现金支付方式失败', error);
|
|
@@ -1123,7 +1119,7 @@ class PaymentModule extends _BaseModule.BaseModule {
|
|
|
1123
1119
|
*/
|
|
1124
1120
|
async getEftposPaymentMethod() {
|
|
1125
1121
|
try {
|
|
1126
|
-
const payMethods = await this.
|
|
1122
|
+
const payMethods = await this.getPayMethodListAsync();
|
|
1127
1123
|
return payMethods.find(method => method.code === _types.PaymentMethodType.Eftpos) || null;
|
|
1128
1124
|
} catch (error) {
|
|
1129
1125
|
console.error('[PaymentModule] 获取Eftpos支付方式失败', error);
|
|
@@ -1136,7 +1132,7 @@ class PaymentModule extends _BaseModule.BaseModule {
|
|
|
1136
1132
|
*/
|
|
1137
1133
|
async getWalletPaymentMethod() {
|
|
1138
1134
|
try {
|
|
1139
|
-
const payMethods = await this.
|
|
1135
|
+
const payMethods = await this.getPayMethodListAsync();
|
|
1140
1136
|
return payMethods.find(method => method.code === _types.PaymentMethodType.Wallet) || null;
|
|
1141
1137
|
} catch (error) {
|
|
1142
1138
|
console.error('[PaymentModule] 获取钱包支付方式失败', error);
|
|
@@ -1144,27 +1140,6 @@ class PaymentModule extends _BaseModule.BaseModule {
|
|
|
1144
1140
|
}
|
|
1145
1141
|
}
|
|
1146
1142
|
|
|
1147
|
-
/**
|
|
1148
|
-
* 确保支付模块所需的数据库表已创建
|
|
1149
|
-
*/
|
|
1150
|
-
/** @deprecated */
|
|
1151
|
-
async ensurePaymentTables() {
|
|
1152
|
-
try {
|
|
1153
|
-
// 尝试访问 pay_method 表,如果不存在会抛出错误
|
|
1154
|
-
await this.dbManager.getAll('pay_method');
|
|
1155
|
-
} catch (error) {
|
|
1156
|
-
console.warn('[PaymentModule] pay_method 表不存在,请在数据库配置中添加以下配置:');
|
|
1157
|
-
console.warn('{ name: "pay_method", keyPath: "id" }');
|
|
1158
|
-
}
|
|
1159
|
-
try {
|
|
1160
|
-
// 尝试访问 order 表,如果不存在会抛出错误
|
|
1161
|
-
await this.dbManager.getAll('order');
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
console.warn('[PaymentModule] order 表不存在,请在数据库配置中添加以下配置:');
|
|
1164
|
-
console.warn('{ name: "order", keyPath: "uuid" }');
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
1143
|
/**
|
|
1169
1144
|
* 获取部分支付的订单
|
|
1170
1145
|
*/
|
|
@@ -130,6 +130,19 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
130
130
|
};
|
|
131
131
|
const rawApplicationCode = this.otherParams?.channel;
|
|
132
132
|
const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
|
|
133
|
+
const configuredBusinessCode = this.otherParams?.businessCode ?? this.otherParams?.business_code;
|
|
134
|
+
// application_code 是后端商品筛选字段;智能定价评估器则只读取
|
|
135
|
+
// strategy_context 中的 channel / business_code。默认补齐两者,
|
|
136
|
+
// 但允许调用方显式传入的上下文覆盖默认值。
|
|
137
|
+
const resolvedStrategyContext = {
|
|
138
|
+
...(mappedApplicationCode ? {
|
|
139
|
+
channel: mappedApplicationCode
|
|
140
|
+
} : {}),
|
|
141
|
+
...(configuredBusinessCode ? {
|
|
142
|
+
business_code: configuredBusinessCode
|
|
143
|
+
} : {}),
|
|
144
|
+
...(strategy_context || {})
|
|
145
|
+
};
|
|
133
146
|
const queryPayload = {
|
|
134
147
|
...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
|
|
135
148
|
open_quotation: 1
|
|
@@ -162,7 +175,7 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
162
175
|
with_schedule,
|
|
163
176
|
schedule_datetime,
|
|
164
177
|
extension_type,
|
|
165
|
-
strategy_context
|
|
178
|
+
strategy_context: Object.keys(resolvedStrategyContext).length ? resolvedStrategyContext : undefined
|
|
166
179
|
};
|
|
167
180
|
const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
|
|
168
181
|
const originalCallback = typeof options?.callback === 'function' ? options.callback : undefined;
|
|
@@ -412,7 +412,7 @@ function normalizeOccupancy(occupancy, coverage, timezoneName, fixedOffsetMinute
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
function normalizeRequirementGroup(product, requirementGroup) {
|
|
415
|
-
const resourceType = 'single';
|
|
415
|
+
const resourceType = requirementGroup.resourceType === 'multiple' || requirementGroup.resourceType === 'capacity' ? requirementGroup.resourceType : 'single';
|
|
416
416
|
const required = requirementGroup.required !== false;
|
|
417
417
|
const min = required ? 1 : 0;
|
|
418
418
|
return {
|
|
@@ -457,7 +457,7 @@ function normalizeProducts(products) {
|
|
|
457
457
|
function normalizeResources(resources) {
|
|
458
458
|
return resources.map(resource => ({
|
|
459
459
|
...resource,
|
|
460
|
-
resourceType: 'single',
|
|
460
|
+
resourceType: resource.resourceType === 'multiple' || resource.resourceType === 'capacity' ? resource.resourceType : 'single',
|
|
461
461
|
capacity: Math.max(1, Number(resource.capacity || 1) || 1),
|
|
462
462
|
windows: (resource.windows || []).map(window => ({
|
|
463
463
|
...window
|
|
@@ -573,7 +573,8 @@ function buildCells(projection) {
|
|
|
573
573
|
occupancy
|
|
574
574
|
}) => occupancy) : [];
|
|
575
575
|
const usedCapacity = segmentOccupancies.reduce((sum, occupancy) => sum + Math.max(1, Number(occupancy.pax || 1) || 1), 0);
|
|
576
|
-
|
|
576
|
+
// 预约模式来自商品资源组;v2 资源记录可能不包含 type。
|
|
577
|
+
const remainingCapacity = requirementGroup.resourceType === 'single' && segmentOccupancies.length > 0 ? 0 : Math.max(0, resource.capacity - usedCapacity);
|
|
577
578
|
const requiredCapacity = resolveRequiredCapacity(requirementGroup, product);
|
|
578
579
|
const reasonCodes = Array.from(new Set(segmentOccupancies.flatMap(occupancy => {
|
|
579
580
|
const source = occupancy.source || 'remote';
|
|
@@ -607,7 +608,7 @@ function buildCells(projection) {
|
|
|
607
608
|
requirementFormId: requirementGroup.formId,
|
|
608
609
|
resourceId: resource.id,
|
|
609
610
|
resourceName: resource.name,
|
|
610
|
-
resourceType:
|
|
611
|
+
resourceType: requirementGroup.resourceType,
|
|
611
612
|
startAt: formatDateTime(start, timezoneName, fixedOffsetMinutes),
|
|
612
613
|
endAt: formatDateTime(end, timezoneName, fixedOffsetMinutes),
|
|
613
614
|
date: formatDate(start, timezoneName),
|
|
@@ -615,7 +616,7 @@ function buildCells(projection) {
|
|
|
615
616
|
endTime: formatTime(end, timezoneName),
|
|
616
617
|
timezone: projection.meta.timezone,
|
|
617
618
|
status,
|
|
618
|
-
//
|
|
619
|
+
// capacity 表示人数;single 被占用时不再贡献可用容量。
|
|
619
620
|
remainingCapacity,
|
|
620
621
|
maxCapacity: resource.capacity,
|
|
621
622
|
reasonCodes: Array.from(new Set(reasonCodes)),
|
|
@@ -1500,7 +1501,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1500
1501
|
resourceName: resource.name,
|
|
1501
1502
|
resourceFormId: resource.formId,
|
|
1502
1503
|
policyId: resource.raw?.policy_id,
|
|
1503
|
-
resourceType:
|
|
1504
|
+
resourceType: requirementGroup.resourceType,
|
|
1504
1505
|
status: isPast ? 'past' : 'unavailable',
|
|
1505
1506
|
maxPartySize: 0,
|
|
1506
1507
|
reasonCodes: isPast ? ['past'] : [...(isExcludedScheduleDate ? ['schedule_excluded_date'] : []), 'outside_resource_window']
|
|
@@ -1524,7 +1525,7 @@ function evaluateCandidate(projection, candidate, request) {
|
|
|
1524
1525
|
resourceName: resource.name,
|
|
1525
1526
|
resourceFormId: resource.formId,
|
|
1526
1527
|
policyId: resource.raw?.policy_id,
|
|
1527
|
-
resourceType:
|
|
1528
|
+
resourceType: requirementGroup.resourceType,
|
|
1528
1529
|
status,
|
|
1529
1530
|
remainingCapacity,
|
|
1530
1531
|
maxCapacity,
|
|
@@ -2008,7 +2009,8 @@ function validateAvailabilitySelection(projection, params, runtimeOptions = {})
|
|
|
2008
2009
|
const remainingCapacity = matchedCell?.remainingCapacity;
|
|
2009
2010
|
if (option && (option.status === 'available' || option.status === 'limited') && remainingCapacity !== undefined) {
|
|
2010
2011
|
const capacityPerBooking = normalizedPartySize ?? 1;
|
|
2011
|
-
const
|
|
2012
|
+
const capacityQuantity = Math.max(0, Math.floor(remainingCapacity / capacityPerBooking));
|
|
2013
|
+
const availableQuantity = matchedGroup.resourceType === 'single' ? Math.min(1, capacityQuantity) : capacityQuantity;
|
|
2012
2014
|
if (normalizedBookingCount > availableQuantity) {
|
|
2013
2015
|
conflicts.push({
|
|
2014
2016
|
code: 'resource_capacity_exceeded',
|
|
@@ -225,7 +225,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
225
225
|
// 识别出来是不是在编辑的界面里又新增了商品
|
|
226
226
|
// 这种情况下,如果有可用的优惠券,也会自动勾选上
|
|
227
227
|
const isEditModeAddNewProduct = productList.find(n => n.booking_id) && productList.find(n => !n.booking_id);
|
|
228
|
-
const
|
|
228
|
+
const reapplyWeighedDiscountProductUids = new Set((options?.reapplyWeighedDiscountProductUids || []).map(String));
|
|
229
|
+
const reapplyBookingDiscountProductUids = new Set([...(options?.reapplyBookingDiscountProductUids || []), ...reapplyWeighedDiscountProductUids].filter(uid => uid !== undefined && uid !== null && uid !== '').map(String));
|
|
229
230
|
const getOriginProductUid = originProduct => {
|
|
230
231
|
const uid = originProduct?.metadata?.unique_identification_number ?? originProduct?.unique_identification_number;
|
|
231
232
|
if (uid === undefined || uid === null || uid === '') return undefined;
|
|
@@ -235,8 +236,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
235
236
|
if (!selectedDiscount) return false;
|
|
236
237
|
const uid = getOriginProductUid(originProduct);
|
|
237
238
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
238
|
-
const discountType = selectedDiscount.tag || selectedDiscount.type
|
|
239
|
-
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
239
|
+
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
240
|
+
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
240
241
|
};
|
|
241
242
|
const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
|
|
242
243
|
if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount)) return undefined;
|
|
@@ -287,8 +288,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
287
288
|
};
|
|
288
289
|
}
|
|
289
290
|
const canUseEditModeDiscountForReapply = discount => {
|
|
290
|
-
const discountType = discount.tag || discount.type
|
|
291
|
-
return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
291
|
+
const discountType = discount.tag || discount.type;
|
|
292
|
+
return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
292
293
|
};
|
|
293
294
|
|
|
294
295
|
// 过滤掉手动取消false的优惠券,但仅用于应用优惠逻辑。
|
|
@@ -509,9 +510,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
509
510
|
addModeDiscount.forEach(discount => {
|
|
510
511
|
// 即使没有匹配的动态策略,服务端状态和次数限制也不能被覆盖为可用。
|
|
511
512
|
if (discount.reasonCode) return;
|
|
512
|
-
const discountType = discount.tag || discount.type
|
|
513
|
+
const discountType = discount.tag || discount.type;
|
|
513
514
|
// 对两类全额抵扣卡和折扣卡执行附加策略检查。
|
|
514
|
-
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
|
|
515
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '')) {
|
|
515
516
|
const entitlementQuota = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.resolveEntitlementQuota)(discount) : null;
|
|
516
517
|
if ((0, _entitlementPass.isEntitlementPass)(discount) && !entitlementQuota) {
|
|
517
518
|
discount.config = {
|
|
@@ -1079,7 +1080,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1079
1080
|
});
|
|
1080
1081
|
|
|
1081
1082
|
// 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
|
|
1082
|
-
|
|
1083
|
+
const isReweighedProduct = reapplyWeighedDiscountProductUids.has(getOriginProductUid(originProduct) || '');
|
|
1084
|
+
if (isPersistedProduct && hasExistingWalletDiscount && !isReweighedProduct) {
|
|
1083
1085
|
if (flatItem.type === 'main') {
|
|
1084
1086
|
processedProductsMap.set(product._id, [originProduct]);
|
|
1085
1087
|
}
|
|
@@ -1093,7 +1095,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1093
1095
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
1094
1096
|
if (this.isItemRewardProductDiscount(product)) return false;
|
|
1095
1097
|
// 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
|
|
1096
|
-
const discountType = discount.tag || discount.type
|
|
1098
|
+
const discountType = discount.tag || discount.type;
|
|
1097
1099
|
const currentOriginUid = getOriginProductUid(originProduct);
|
|
1098
1100
|
const isReapplyEditModeDiscountForProduct = discount.isEditMode && reapplyBookingDiscountProductUids.size > 0 && currentOriginUid && reapplyBookingDiscountProductUids.has(String(currentOriginUid)) && discount.isSelected === true;
|
|
1099
1101
|
if (discount.isEditMode && reapplyBookingDiscountProductUids.size > 0) {
|
|
@@ -1101,7 +1103,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1101
1103
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
1102
1104
|
if (!discount.isSelected) return false;
|
|
1103
1105
|
}
|
|
1104
|
-
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType) && !isReapplyEditModeDiscountForProduct) {
|
|
1106
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '') && !isReapplyEditModeDiscountForProduct) {
|
|
1105
1107
|
// 如果策略检查后没有config,说明不可用
|
|
1106
1108
|
if (discount.config === undefined || !discount?.config?.isAvailable) {
|
|
1107
1109
|
return false;
|
|
@@ -1172,8 +1174,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1172
1174
|
// 识别用户本次显式选择的卡券,供次卡和折扣卡各自的优先级使用。
|
|
1173
1175
|
// 目标仍必须在当前商品的 applicableDiscounts 中,因此不会绕过 Holder、时间或商品范围校验。
|
|
1174
1176
|
const explicitlySelectedDiscount = options?.isSelected === true && options.discountId !== undefined ? applicableDiscounts.find(discount => {
|
|
1175
|
-
const discountType = discount.tag || discount.type
|
|
1176
|
-
return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
1177
|
+
const discountType = discount.tag || discount.type;
|
|
1178
|
+
return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
1177
1179
|
}) : undefined;
|
|
1178
1180
|
// 折扣卡会复用于整条商品行;商品券必须继续按单张拆分,不能复用该分支。
|
|
1179
1181
|
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && !(0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined;
|
|
@@ -1182,8 +1184,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1182
1184
|
// applicableDiscounts 中,就应保留该行选择,避免被自动排序中力度更大的卡替换。
|
|
1183
1185
|
const appliedWalletDiscountIds = new Set((product.discount_list || []).filter(item => ['entitlement_pass', 'discount_card', 'product_discount_card'].includes(item?.tag || item?.type)).map(item => item?.discount?.resource_id ?? item?.resource_id ?? item?.id).filter(id => id !== undefined && id !== null).map(String));
|
|
1184
1186
|
const appliedDiscountCard = options?.scan ? undefined : applicableDiscounts.find(discount => {
|
|
1185
|
-
const discountType = discount.tag || discount.type
|
|
1186
|
-
return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType) && appliedWalletDiscountIds.has(String(discount.id));
|
|
1187
|
+
const discountType = discount.tag || discount.type;
|
|
1188
|
+
return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType ?? '') && appliedWalletDiscountIds.has(String(discount.id));
|
|
1187
1189
|
});
|
|
1188
1190
|
const appliedEntitlementPass = options?.scan || options?.preserveEntitlementSelection === false ? undefined : applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && discount.isSelected === true && appliedWalletDiscountIds.has(String(discount.id)));
|
|
1189
1191
|
|
|
@@ -35,6 +35,8 @@ export interface RulesCalcDiscountOptions {
|
|
|
35
35
|
preserveEntitlementSelection?: boolean;
|
|
36
36
|
/** Change time 重报价目标行:允许已选编辑态折扣卡重新应用到这些 booking 商品。 */
|
|
37
37
|
reapplyBookingDiscountProductUids?: string[];
|
|
38
|
+
/** Reweighed order lines only: reapply selected wallet discounts against their new amount. */
|
|
39
|
+
reapplyWeighedDiscountProductUids?: string[];
|
|
38
40
|
}
|
|
39
41
|
export interface RulesModuleAPI {
|
|
40
42
|
setRulesList: (rulesList: Rules[]) => Promise<void>;
|