fix: 代客下单修改
This commit is contained in:
@@ -60,8 +60,8 @@ export interface BackendCoupon {
|
||||
id?: number; // 自增主键(int64)
|
||||
shopId?: number; // 店铺ID(int64)
|
||||
syncId?: number; // 同步Id(int64)
|
||||
couponType?: number; // 优惠券类型:1-满减券,2-商品兑换券,3-折扣券,4-第二件半价券,5-消费送券,6-买一送一券,7-固定价格券,8-免配送费券
|
||||
title?: string; // 券名称
|
||||
type?: number; // 优惠券类型:1-满减券,2-商品兑换券,3-折扣券,4-第二件半价券,5-消费送券,6-买一送一券,7-固定价格券,8-免配送费券
|
||||
name?: string; // 券名称
|
||||
useShopType?: string; // 可用门店类型:only-仅本店;all-所有门店,custom-指定门店
|
||||
useShops?: string; // 可用门店(逗号分隔字符串,如"1,2,3")
|
||||
useType?: string; // 可使用类型:dine堂食/pickup自取/deliv配送/express快递
|
||||
@@ -228,6 +228,7 @@ export interface OrderExtraConfig {
|
||||
userPoints: number; // 用户当前积分(用于积分抵扣)
|
||||
isMember: boolean; // 用户是否会员(用于会员优惠)
|
||||
memberDiscountRate?: number; // 会员折扣率(如0.95=95折,无会员价时用)
|
||||
newUserDiscount?: number; // 新用户减免金额(元,默认0)
|
||||
}
|
||||
|
||||
/** 订单费用汇总(修改:补充商家减免类型和明细) */
|
||||
@@ -251,6 +252,9 @@ export interface OrderCostSummary {
|
||||
finalPayAmount: number; // 最终实付金额
|
||||
couponUsed?: Coupon; // 实际使用的优惠券
|
||||
pointUsed: number; // 实际使用的积分
|
||||
newUserDiscount: number; // 新用户减免金额(元,默认0)
|
||||
dinnerType?: 'dine-in' | 'take-out'; // 就餐类型(堂食/自取/配送/快递)
|
||||
|
||||
}
|
||||
|
||||
// ============================ 2. 基础工具函数(核心修正:所有商品ID匹配用product_id) ============================
|
||||
@@ -269,15 +273,15 @@ export function convertBackendCouponToToolCoupon(
|
||||
currentTime: Date = new Date()
|
||||
): Coupon | null {
|
||||
// 1. 基础校验:必选字段缺失直接返回null
|
||||
if (!backendCoupon.id || backendCoupon.type === undefined || !backendCoupon.title) {
|
||||
if (!backendCoupon.id || backendCoupon.type === undefined) {
|
||||
console.warn('优惠券必选字段缺失', backendCoupon);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 转换券类型:后端数字枚举 → 工具库字符串枚举
|
||||
const couponType = mapBackendCouponTypeToTool(backendCoupon.couponType);
|
||||
const couponType = mapBackendCouponTypeToTool(backendCoupon.type);
|
||||
if (!couponType) {
|
||||
console.warn(`不支持的优惠券类型:${backendCoupon.couponType}(券ID:${backendCoupon.id})`);
|
||||
console.warn(`不支持的优惠券类型:${backendCoupon.type}(券ID:${backendCoupon.id})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -286,16 +290,17 @@ export function convertBackendCouponToToolCoupon(
|
||||
? [] // 空字符串/undefined → 全部商品(按商品ID匹配)
|
||||
: backendCoupon.foods.split(',').map(id => id.trim()); // 逗号分隔 → 指定商品ID数组
|
||||
|
||||
const useType = backendCoupon?.useType?.split(',')?.map(v => v.replace(/[\[\]]/g, '').replace(/""/g, '"').replace(/["']/g, '')) || [];
|
||||
// 4. 计算基础公共字段(含多维度可用性校验)
|
||||
const baseCoupon: BaseCoupon = {
|
||||
id: backendCoupon.id,
|
||||
type: couponType,
|
||||
name: backendCoupon.title,
|
||||
name: backendCoupon.name || '',
|
||||
available: isCouponAvailable(backendCoupon, currentStoreId, dinnerType, currentTime),
|
||||
useShops: getApplicableStoreIds(backendCoupon, currentStoreId),
|
||||
discountShare: backendCoupon.discountShare === 1,
|
||||
vipPriceShare: backendCoupon.vipPriceShare === 1,
|
||||
useType: backendCoupon.useType ? backendCoupon.useType.split(',') : [],
|
||||
useType: useType,
|
||||
isValid: isCouponInValidPeriod(backendCoupon, currentTime),
|
||||
applicableProductIds: applicableProductIds,
|
||||
};
|
||||
@@ -365,7 +370,7 @@ function isCouponAvailable(
|
||||
currentTime: Date = new Date()
|
||||
): boolean {
|
||||
// 1. 状态校验:必须启用(status=1)
|
||||
if (backendCoupon.status !== 1) return false;
|
||||
if (backendCoupon.status === 0) return false;
|
||||
|
||||
// 3. 有效期校验:必须在有效期内
|
||||
if (!isCouponInValidPeriod(backendCoupon, currentTime)) return false;
|
||||
@@ -377,13 +382,13 @@ function isCouponAvailable(
|
||||
if (!isCouponInDailyTimeRange(backendCoupon, currentTime)) return false;
|
||||
|
||||
// 6. 每周周期校验:当前星期几需在可用周期内(useDays非空时生效)
|
||||
if (!isCouponInWeekDays(backendCoupon, currentTime)) return false;
|
||||
// if (!isCouponInWeekDays(backendCoupon, currentTime)) return false;
|
||||
|
||||
// 7. 门店匹配校验:当前门店需在适用门店范围内
|
||||
if (!isStoreMatch(backendCoupon, currentStoreId)) return false;
|
||||
// if (!isStoreMatch(backendCoupon, currentStoreId)) return false;
|
||||
|
||||
// 8. 就餐类型校验:当前就餐类型需在可用类型范围内
|
||||
if (!isDinnerTypeMatch(backendCoupon, dinnerType)) return false;
|
||||
// if (!isDinnerTypeMatch(backendCoupon, dinnerType)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1188,81 +1193,29 @@ export function calcCouponDeduction(
|
||||
usedCoupon?: Coupon;
|
||||
excludedProductIds: string[]; // 排除的商品ID列表(商品ID)
|
||||
} {
|
||||
// 1. 后端优惠券转工具库Coupon(过滤无效/不支持的券)
|
||||
const toolCoupons = backendCoupons
|
||||
.map(coupon => convertBackendCouponToToolCoupon(
|
||||
coupon,
|
||||
config.currentStoreId,
|
||||
config.dinnerType,
|
||||
config.currentTime
|
||||
))
|
||||
.filter(Boolean) as Coupon[];
|
||||
if (toolCoupons.length === 0) {
|
||||
return {
|
||||
deductionAmount: 0,
|
||||
productCouponDeduction: 0,
|
||||
fullCouponDeduction: 0,
|
||||
excludedProductIds: []
|
||||
};
|
||||
}
|
||||
console.log('toolCoupons', toolCoupons)
|
||||
|
||||
// 2. 优惠券互斥逻辑:兑换券与其他券互斥,优先选最优
|
||||
const exchangeCoupons = toolCoupons.filter(c => c.type === CouponType.EXCHANGE);
|
||||
const nonExchangeCoupons = toolCoupons.filter(c => c.type !== CouponType.EXCHANGE);
|
||||
const goodsCoupon = backendCoupons.filter(v => v.type == 2)
|
||||
const discountCoupon = backendCoupons.filter(v => v.type != 2)
|
||||
|
||||
// 3. 计算非兑换券最优抵扣(传递已抵扣商品ID,避免重复,统计细分字段)
|
||||
let nonExchangeResult: CouponResult = {
|
||||
deductionAmount: 0,
|
||||
deductionAmount: discountCoupon.reduce((prve, cur): number => {
|
||||
return prve + (cur.discountAmount || 0)
|
||||
}, 0),
|
||||
excludedProductIds: [],
|
||||
usedCoupon: undefined,
|
||||
productCouponDeduction: 0,
|
||||
fullCouponDeduction: 0
|
||||
};
|
||||
if (nonExchangeCoupons.length > 0) {
|
||||
nonExchangeResult = nonExchangeCoupons.reduce((best, coupon) => {
|
||||
const strategy = getCouponStrategy(coupon.type);
|
||||
const result = strategy.calculate(coupon, goodsList, {
|
||||
...config,
|
||||
excludedProductIds: best.excludedProductIds // 传递已排除的商品ID(商品ID)
|
||||
});
|
||||
const currentResult: CouponResult = {
|
||||
deductionAmount: result.deductionAmount,
|
||||
excludedProductIds: result.excludedProductIds,
|
||||
usedCoupon: coupon,
|
||||
// 按策略返回的字段赋值细分抵扣
|
||||
productCouponDeduction: result.productCouponDeduction || 0,
|
||||
fullCouponDeduction: result.fullCouponDeduction || 0
|
||||
};
|
||||
// 按总抵扣金额选择最优
|
||||
return new BigNumber(currentResult.deductionAmount).isGreaterThan(best.deductionAmount)
|
||||
? currentResult
|
||||
: best;
|
||||
}, nonExchangeResult);
|
||||
}
|
||||
|
||||
// 4. 计算兑换券抵扣(排除非兑换券已抵扣的商品ID,统计商品券细分)
|
||||
let exchangeResult: ExchangeCalculationResult = {
|
||||
deductionAmount: 0,
|
||||
deductionAmount: goodsCoupon.reduce((prve, cur): number => {
|
||||
return prve + (cur.discountAmount || 0)
|
||||
}, 0),
|
||||
excludedProductIds: [],
|
||||
productCouponDeduction: 0
|
||||
};
|
||||
if (exchangeCoupons.length > 0) {
|
||||
exchangeResult = exchangeCoupons.reduce((best, coupon) => {
|
||||
const strategy = getCouponStrategy(coupon.type);
|
||||
const result = strategy.calculate(coupon, goodsList, {
|
||||
...config,
|
||||
excludedProductIds: [...nonExchangeResult.excludedProductIds, ...best.excludedProductIds] // 合并排除的商品ID
|
||||
});
|
||||
return new BigNumber(result.deductionAmount).isGreaterThan(best.deductionAmount)
|
||||
? {
|
||||
deductionAmount: result.deductionAmount,
|
||||
excludedProductIds: result.excludedProductIds,
|
||||
productCouponDeduction: result.productCouponDeduction || 0 // 兑换券属于商品券
|
||||
}
|
||||
: best;
|
||||
}, exchangeResult);
|
||||
}
|
||||
|
||||
|
||||
// 5. 汇总结果:兑换券与非兑换券不可同时使用,取抵扣金额大的
|
||||
const exchangeBn = new BigNumber(exchangeResult.deductionAmount);
|
||||
@@ -1270,9 +1223,9 @@ export function calcCouponDeduction(
|
||||
const isExchangeBetter = exchangeBn.isGreaterThan(nonExchangeBn);
|
||||
|
||||
return {
|
||||
deductionAmount: truncateToTwoDecimals(isExchangeBetter ? exchangeResult.deductionAmount : nonExchangeResult.deductionAmount),
|
||||
productCouponDeduction: isExchangeBetter ? exchangeResult.productCouponDeduction : nonExchangeResult.productCouponDeduction,
|
||||
fullCouponDeduction: isExchangeBetter ? 0 : nonExchangeResult.fullCouponDeduction, // 兑换券与满减券互斥,满减券抵扣置0
|
||||
deductionAmount: exchangeBn.plus(nonExchangeBn).toNumber(),
|
||||
productCouponDeduction: exchangeResult.deductionAmount,
|
||||
fullCouponDeduction: nonExchangeResult.deductionAmount, // 兑换券与满减券互斥,满减券抵扣置0
|
||||
usedCoupon: isExchangeBetter ? undefined : nonExchangeResult.usedCoupon,
|
||||
excludedProductIds: isExchangeBetter ? exchangeResult.excludedProductIds : nonExchangeResult.excludedProductIds
|
||||
};
|
||||
@@ -1289,6 +1242,7 @@ export function calcTotalPackFee(
|
||||
goodsList: BaseCartItem[],
|
||||
dinnerType: 'dine-in' | 'take-out'
|
||||
): number {
|
||||
if (dinnerType !== 'take-out') return 0;
|
||||
let total = new BigNumber(0);
|
||||
|
||||
for (const goods of goodsList) {
|
||||
@@ -1298,7 +1252,7 @@ export function calcTotalPackFee(
|
||||
// 计算单个商品打包数量(外卖全打包,堂食按配置,称重商品≤1)
|
||||
let packNum = dinnerType === 'take-out'
|
||||
? availableNum
|
||||
: (goods.packNumber || 0);
|
||||
: (0);
|
||||
if (goods.product_type === GoodsType.WEIGHT) {
|
||||
packNum = Math.min(packNum, 1);
|
||||
}
|
||||
@@ -1415,8 +1369,11 @@ export function calculateOrderCostSummary(
|
||||
);
|
||||
|
||||
// 3. 其他费用计算(原有逻辑不变)
|
||||
// 新客立减
|
||||
const newUserDiscount = config.newUserDiscount || 0;
|
||||
const packFee = calcTotalPackFee(goodsList, dinnerType);
|
||||
const seatFee = calcSeatFee(config.seatFeeConfig);
|
||||
let seatFee = calcSeatFee(config.seatFeeConfig);
|
||||
seatFee = dinnerType === 'dine-in' ? seatFee : 0; // 外卖不收餐位费
|
||||
const additionalFee = Math.max(0, config.additionalFee);
|
||||
|
||||
// 4. 积分抵扣(原有逻辑不变,先于商家减免计算)
|
||||
@@ -1475,6 +1432,7 @@ export function calculateOrderCostSummary(
|
||||
const finalPayAmount = new BigNumber(goodsOriginalAmount) // 商品原价总和
|
||||
.minus(goodsDiscountAmount) // 减去商品折扣
|
||||
.minus(couponDeductionAmount) // 减去优惠券抵扣
|
||||
.minus(newUserDiscount) // 新客立减
|
||||
.minus(pointDeductionAmount) // 减去积分抵扣
|
||||
.minus(merchantReductionActualAmount) // 减去商家实际减免金额
|
||||
.plus(seatFee) // 加上餐位费(不参与减免)
|
||||
@@ -1503,7 +1461,9 @@ export function calculateOrderCostSummary(
|
||||
additionalFee,
|
||||
finalPayAmount: truncateToTwoDecimals(finalPayAmountNonNegative),
|
||||
couponUsed: usedCoupon,
|
||||
pointUsed: usedPoints
|
||||
pointUsed: usedPoints,
|
||||
newUserDiscount,
|
||||
dinnerType
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1522,6 +1482,7 @@ export const OrderPriceCalculator = {
|
||||
isWeightGoods,
|
||||
// 优惠券转换
|
||||
convertBackendCouponToToolCoupon,
|
||||
mapBackendCouponTypeToTool,
|
||||
// 商品价格计算
|
||||
calcSingleGoodsRealPrice,
|
||||
calcGoodsOriginalAmount,
|
||||
|
||||
Reference in New Issue
Block a user