优化更新

This commit is contained in:
gyq
2025-11-11 11:04:26 +08:00
parent 2432c53a73
commit 636fa4e033
32 changed files with 2280 additions and 704 deletions

View File

@@ -5,19 +5,45 @@ import _ from "lodash";
* 返回商品单价
* @param goods 商品
* @param user 用户信息
* @param {Object} shopInfo
* @param {Object} shopInfo 店铺信息
* @param {boolean} limitTimeDiscount - 限时折扣
*/
export function returnGoodsPrice(goods, user, shopInfo) {
export function returnGoodsPrice(goods, user, shopInfo, limitTimeDiscount) {
if (!goods) {
return 0;
}
//是否可以使用会员价
const canUseVipPrice =
user && user.isVip && user.isMemberPrice && goods.memberPrice * 1 > 0;
// 商家改价
if (goods.discount_sale_amount * 1 > 0) {
return goods.discount_sale_amount;
}
if (shopInfo && !shopInfo.isMemberPrice) {
return goods.salePrice;
}
if (user.isVip && goods.memberPrice * 1 <= goods.salePrice * 1 && goods.memberPrice * 1 > 0) {
// 限时折扣
if (limitTimeDiscount && limitTimeDiscount.id) {
const canUseFoods = limitTimeDiscount.foods.split(",");
const canUseLimit =
limitTimeDiscount.foodType == 1 ||
canUseFoods.includes(`${goods.productId}`);
if (canUseLimit && limitTimeDiscount.discountPriority == "limit-time") {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
if (canUseLimit && limitTimeDiscount.discountPriority == "vip-price") {
if (canUseVipPrice) {
return goods.memberPrice;
} else {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
}
}
if (canUseVipPrice) {
return goods.memberPrice;
}
return goods.salePrice;
@@ -63,7 +89,9 @@ export function returnCoupType(coupon) {
* @param selCoupon 已选择的优惠券列表
* @param user 用户信息
*/
export function returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user) {
export function returnCanDikouGoodsArr(args) {
const { canDikouGoodsArr, selCoupon, user, shopInfo, limitTimeDiscount } =
args;
const types = [2, 4, 6];
// 收集已抵扣商品并关联对应的优惠券类型
const goodsCouponGoods = selCoupon
@@ -92,11 +120,60 @@ export function returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user) {
}
return v;
})
.filter((v) => v.num > 0); // 过滤掉数量<=0的商品
.filter((v) => {
const canUseNum = v.num - (v.returnNum || 0);
if (canUseNum <= 0 || v.is_temporary == 1 || v.is_gift == 1) {
return false;
}
return true;
}); // 过滤掉数量<=0的商品,赠菜,临时菜
return arr;
}
/**
* 返回商品是否享用了会员价/会员折扣
* @param {*} goods
*/
function returnGoodsIsUseVipPrice(shopInfo, user, goods) {
if (goods.is_time_discount) {
return false;
}
if (shopInfo.isMemberPrice != 1 || user.isVip != 1) {
return false;
}
if (shopInfo.isMemberPrice == 1 && user.isVip == 1) {
if (goods.memberPrice <= 0) {
return false;
}
return true;
}
}
/**
* 返回可以计算抵扣金额的商品列表
*/
function returnCanCalcGoodsList(canCalcGoodsArr, coupon, shopInfo, user) {
return canCalcGoodsArr.filter((goods) => {
console.log("goods");
console.log(goods);
if (
!coupon.discountShare &&
(goods.is_time_discount || goods.isTimeDiscount)
) {
return false;
}
if (
!coupon.vipPriceShare &&
returnGoodsIsUseVipPrice(shopInfo, user, goods)
) {
return false;
}
return true;
});
}
/**
* 判断优惠券是否可使用,并返回不可用原因
*
@@ -111,10 +188,20 @@ export function returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user) {
* @param {Object} args.user - 用户信息对象
* @param {Object} args.selCoupon - 已经选择的优惠券信息对象
* @param {Object} args.shopInfo
* @param {boolean} args.limitTimeDiscount - 限时折扣
* @returns {Object} - { canUse: boolean, reason: string } 可用状态及不可用原因
*/
export function returnCouponCanUse(args) {
let { canDikouGoodsArr, coupon, goodsOrderPrice, user, selCoupon, shopInfo } = args;
let {
canDikouGoodsArr,
coupon,
goodsOrderPrice,
user,
selCoupon,
shopInfo,
isMemberPrice,
limitTimeDiscount,
} = args;
// 优惠券未启用
if (!coupon.use) {
return {
@@ -122,10 +209,27 @@ export function returnCouponCanUse(args) {
reason: coupon.noUseRestrictions || "不在可用时间段内",
};
}
if (
limitTimeDiscount &&
limitTimeDiscount.id &&
limitTimeDiscount.foodType == 1 &&
!coupon.discountShare
) {
return {
canUse: false,
reason: coupon.noUseRestrictions || "不可与限时折扣同享",
};
}
// 计算门槛金额
let fullAmount = goodsOrderPrice;
canDikouGoodsArr = returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user, shopInfo);
canDikouGoodsArr = returnCanDikouGoodsArr({
canDikouGoodsArr,
selCoupon,
user,
shopInfo,
limitTimeDiscount,
});
//优惠券指定门槛商品列表
let canCalcGoodsArr = [...canDikouGoodsArr];
//部分商品参与门槛计算
@@ -133,10 +237,20 @@ export function returnCouponCanUse(args) {
canCalcGoodsArr = canDikouGoodsArr.filter((v) => {
return coupon.thresholdFoods.find((food) => food.id == v.productId);
});
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return pre + returnGoodsPrice(cur, user, shopInfo) * cur.num;
}, 0);
}
canCalcGoodsArr = returnCanCalcGoodsList(
canCalcGoodsArr,
coupon,
shopInfo,
user
);
console.log("canCalcGoodsArr");
console.log(canCalcGoodsArr);
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return (
pre + returnGoodsPrice(cur, user, shopInfo, limitTimeDiscount) * cur.num
);
}, 0);
// 是否全部商品可用
const isDikouAll = coupon.useFoods.length === 0;
@@ -147,12 +261,12 @@ export function returnCouponCanUse(args) {
return coupon.useFoods.find((food) => food.id == v.productId);
});
}
if (user.isVip && !coupon.vipPriceShare) {
return {
canUse: false,
reason: "非会员可用",
};
}
// if (user.isVip && !coupon.vipPriceShare) {
// return {
// canUse: false,
// reason: "非会员可用",
// };
// }
if (selCoupon.length > 0 && !selCoupon[0].otherCouponShare) {
return {
canUse: false,
@@ -183,13 +297,6 @@ export function returnCouponCanUse(args) {
}
// 商品兑换券,第二件半价和买一送一判断是否有可用商品
if ([2, 4, 5].includes(coupon.type)) {
if (coupon.type == 2 && fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
// 没有符合条件的商品
if (isDikouAll && canDikouGoodsArr.length === 0) {
return {
@@ -203,6 +310,20 @@ export function returnCouponCanUse(args) {
reason: "没有符合条件的商品",
};
}
if (coupon.type == 2) {
if (canCalcGoodsArr.length <= 0) {
return {
canUse: false,
reason: "没有符合计算门槛条件的商品",
};
}
if (fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
}
//商品兑换券是否达到门槛金额
if (coupon.type == 2 && goodsOrderPrice < coupon.fullAmount) {
@@ -258,11 +379,19 @@ export function returnCouponCanUse(args) {
* @param discountNum 抵扣数量
* @param user 用户信息
* @param {Object} shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo) {
export function calcDiscountGoodsArrPrice(
discountGoodsArr,
discountNum,
user,
shopInfo,
limitTimeDiscount
) {
let hasCountNum = 0;
let discountPrice = 0;
let hasDiscountGoodsArr = [];
for (let i = 0; i < discountGoodsArr.length; i++) {
if (hasCountNum >= discountNum) {
break;
@@ -270,7 +399,14 @@ export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, s
const goods = discountGoodsArr[i];
const shengyuNum = discountNum - hasCountNum;
const num = Math.min(goods.num, shengyuNum);
discountPrice += returnGoodsPrice(goods, user, shopInfo) * num;
const realPrice = returnGoodsPrice(
goods,
user,
shopInfo,
limitTimeDiscount
);
discountPrice += realPrice * num;
hasCountNum += num;
hasDiscountGoodsArr.push({
@@ -293,22 +429,62 @@ export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, s
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCouponDiscount(arr, coupon, user, goodsOrderPrice, selCoupon, shopInfo) {
arr = returnCanDikouGoods(arr, user, shopInfo);
const canDikouGoodsArr = returnCanDikouGoodsArr(arr, selCoupon, user);
export function returnCouponDiscount(
arr,
coupon,
user,
goodsOrderPrice,
selCoupon,
shopInfo,
limitTimeDiscount
) {
arr = returnCanDikouGoods(arr, user, shopInfo,limitTimeDiscount);
const canDikouGoodsArr = returnCanDikouGoodsArr({
canDikouGoodsArr: arr,
selCoupon,
user,
shopInfo,
limitTimeDiscount,
});
if (coupon.type == 2) {
return returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo);
return returnCouponProductDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
}
if (coupon.type == 6) {
const result = returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo);
const result = returnCouponBuyOneGiveOneDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
return result;
}
if (coupon.type == 4) {
return returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo);
return returnSecoendDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
}
if (coupon.type == 3) {
return returnCouponZhekouDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice, selCoupon);
return returnCouponZhekouDiscount(
canDikouGoodsArr,
coupon,
user,
goodsOrderPrice,
selCoupon,
limitTimeDiscount
);
}
}
@@ -319,6 +495,7 @@ export function returnCouponDiscount(arr, coupon, user, goodsOrderPrice, selCoup
* @param user 用户信息
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
* @param limitTimeDiscount 限时折扣
*
*/
export function returnCouponZhekouDiscount(
@@ -326,7 +503,8 @@ export function returnCouponZhekouDiscount(
coupon,
user,
goodsOrderPrice,
selCoupon
selCoupon,
limitTimeDiscount
) {
const { discountRate, maxDiscountAmount } = coupon;
@@ -334,14 +512,20 @@ export function returnCouponZhekouDiscount(
const goodsCouponDiscount = selCoupon
.filter((v) => v.type == 2)
.reduce((prve, cur) => {
return new BigNumber(prve).plus(new BigNumber(cur.discount.discountPrice));
return new BigNumber(prve).plus(
new BigNumber(cur.discount.discountPrice)
);
}, new BigNumber(0));
// 将商品订单价格转换为BigNumber并减去优惠券折扣
const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(goodsCouponDiscount);
const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(
goodsCouponDiscount
);
// 计算优惠比例:(100 - 折扣率) / 100
const discountAmountRatio = new BigNumber(100).minus(discountRate).dividedBy(100);
const discountAmountRatio = new BigNumber(100)
.minus(discountRate)
.dividedBy(100);
// 计算折扣金额:调整后的商品订单金额 × 优惠比例
let discountPrice = adjustedGoodsOrderPrice
@@ -351,7 +535,8 @@ export function returnCouponZhekouDiscount(
// 应用最大折扣金额限制
if (maxDiscountAmount !== 0) {
discountPrice = discountPrice >= maxDiscountAmount ? maxDiscountAmount : discountPrice;
discountPrice =
discountPrice >= maxDiscountAmount ? maxDiscountAmount : discountPrice;
}
return {
@@ -366,11 +551,20 @@ export function returnCouponZhekouDiscount(
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
export function returnCouponProductDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, discountNum, useRule } = coupon;
//抵扣商品数组
let discountGoodsArr = [];
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
@@ -389,7 +583,15 @@ export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shop
discountGoodsArr = discountSelGoodsArr.slice(0, discountNum);
}
}
const result = calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo);
const result = calcDiscountGoodsArrPrice(
discountGoodsArr,
discountNum,
user,
shopInfo,
limitTimeDiscount
);
return result;
}
@@ -399,8 +601,15 @@ export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shop
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
function returnCouponBuyOneGiveOneDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
@@ -415,7 +624,9 @@ function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopI
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId));
const canUseGoods1 = canUseGoods.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
@@ -424,9 +635,13 @@ function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopI
}
let discountPrice = 0;
let hasDiscountGoodsArr = [];
console.log("returnCouponBuyOneGiveOneDiscount:discountGoods", discountGoods);
if (discountGoods) {
discountPrice = returnGoodsPrice(discountGoods, user, shopInfo);
discountPrice = returnGoodsPrice(
discountGoods,
user,
shopInfo,
limitTimeDiscount
);
hasDiscountGoodsArr = [discountGoods];
}
return {
@@ -441,8 +656,15 @@ function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopI
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
function returnSecoendDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
@@ -457,7 +679,9 @@ function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId));
const canUseGoods1 = canUseGoods.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
@@ -467,12 +691,20 @@ function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
let discountPrice = 0;
let hasDiscountGoodsArr = [];
if (discountGoods) {
discountPrice = returnGoodsPrice(discountGoods, user, shopInfo);
discountPrice = returnGoodsPrice(
discountGoods,
user,
shopInfo,
limitTimeDiscount
);
hasDiscountGoodsArr = [discountGoods];
}
//返回半价价格
return {
discountPrice: discountPrice <= 0 ? 0 : new BigNumber(discountPrice).dividedBy(2).toNumber(),
discountPrice:
discountPrice <= 0
? 0
: new BigNumber(discountPrice).dividedBy(2).toNumber(),
hasDiscountGoodsArr,
};
}
@@ -482,8 +714,9 @@ function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) {
* @param arr 商品列表
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCanDikouGoods(arr, user, shopInfo) {
export function returnCanDikouGoods(arr, user, shopInfo, limitTimeDiscount) {
const result = arr
.filter((v) => {
return v.is_temporary != 1 && v.is_gift != 1;
@@ -492,7 +725,10 @@ export function returnCanDikouGoods(arr, user, shopInfo) {
return v.num > 0;
})
.sort((a, b) => {
return returnGoodsPrice(b, user, shopInfo) - returnGoodsPrice(a, user, shopInfo);
return (
returnGoodsPrice(b, user, shopInfo, limitTimeDiscount) -
returnGoodsPrice(a, user, shopInfo, limitTimeDiscount)
);
});
return result;
}

734
src/utils/goods-utils.js Normal file
View File

@@ -0,0 +1,734 @@
import { BigNumber } from "bignumber.js";
import _ from "lodash";
/**
* 返回商品单价
* @param goods 商品
* @param user 用户信息
* @param {Object} shopInfo 店铺信息
* @param {boolean} limitTimeDiscount - 限时折扣
*/
export function returnGoodsPrice(goods, user, shopInfo, limitTimeDiscount) {
if (!goods) {
return 0;
}
//是否可以使用会员价
const canUseVipPrice =
user && user.isVip && user.isMemberPrice && goods.memberPrice * 1 > 0;
// 商家改价
if (goods.discount_sale_amount * 1 > 0) {
return goods.salePrice;
}
// 限时折扣
if (limitTimeDiscount && limitTimeDiscount.id) {
const canUseFoods = limitTimeDiscount.foods.split(",");
const canUseLimit =
limitTimeDiscount.foodType == 1 ||
canUseFoods.includes(`${goods.productId}`);
if (canUseLimit && limitTimeDiscount.discountPriority == "limit-time") {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
if (canUseLimit && limitTimeDiscount.discountPriority == "vip-price") {
if (canUseVipPrice) {
return goods.memberPrice;
} else {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
}
}
if (canUseVipPrice) {
return goods.memberPrice;
}
return goods.salePrice;
}
/**
* 返回商品分组
* @param arr 商品列表
*/
export function returnGoodsGroupMap(arr) {
let map = {};
arr.forEach((v) => {
const key = v.productId + "_" + v.skuId;
if (!map[key]) {
map[key] = [];
}
map[key].push(v);
});
return map;
}
/**
* 优惠券类型1-满减券2-商品兑换券3-折扣券4-第二件半价券5-消费送券6-买一送一券7-固定价格券8-免配送费券
* @param coupon
*/
export function returnCoupType(coupon) {
const couponTypes = {
1: "满减券",
2: "商品券",
3: "折扣券",
4: "第二件半价券",
5: "消费送券",
6: "买一送一券",
7: "固定价格券",
8: "免配送费券",
};
return couponTypes[coupon.type] || "未知类型";
}
/**
* 返回商品券抵扣后的商品列表
* @param canDikouGoodsArr 可抵扣商品列表
* @param selCoupon 已选择的优惠券列表
* @param user 用户信息
*/
export function returnCanDikouGoodsArr(args) {
const { canDikouGoodsArr, selCoupon, user, shopInfo, limitTimeDiscount } =
args;
const types = [2, 4, 6];
// 收集已抵扣商品并关联对应的优惠券类型
const goodsCouponGoods = selCoupon
.filter((v) => types.includes(v.type))
.reduce((prev, cur) => {
// 给每个抵扣商品添加所属优惠券类型
const goodsWithType = cur.discount.hasDiscountGoodsArr.map((goods) => ({
...goods,
couponType: cur.type, // 记录该商品是被哪种类型的优惠券抵扣的
}));
prev.push(...goodsWithType);
return prev;
}, []);
const arr = _.cloneDeep(canDikouGoodsArr)
.map((v) => {
const findCart = goodsCouponGoods.find((carts) => carts.id == v.id);
if (findCart) {
// 根据优惠券类型判断扣减数量
if ([4, 6].includes(findCart.couponType)) {
// 类型4第二件半价或6买一送一数量减2
v.num -= 2;
} else {
// 其他类型如类型2商品券按原逻辑扣减对应数量
v.num -= findCart.num;
}
}
return v;
})
.filter((v) => {
const canUseNum = v.num - (v.returnNum || 0);
if (canUseNum <= 0 || v.is_temporary == 1 || v.is_gift == 1) {
return false;
}
return true;
}); // 过滤掉数量<=0的商品,赠菜,临时菜
return arr;
}
/**
* 返回商品是否享用了会员价/会员折扣
* @param {*} goods
*/
function returnGoodsIsUseVipPrice(shopInfo, user, goods) {
if (goods.is_time_discount) {
return false;
}
if (shopInfo.isMemberPrice != 1 || user.isVip != 1) {
return false;
}
if (shopInfo.isMemberPrice == 1 && user.isVip == 1) {
if (goods.memberPrice <= 0) {
return false;
}
return true;
}
}
/**
* 返回可以计算抵扣金额的商品列表
*/
function returnCanCalcGoodsList(canCalcGoodsArr, coupon, shopInfo, user) {
return canCalcGoodsArr.filter((goods) => {
console.log("goods");
console.log(goods);
if (
!coupon.discountShare &&
(goods.is_time_discount || goods.isTimeDiscount)
) {
return false;
}
if (
!coupon.vipPriceShare &&
returnGoodsIsUseVipPrice(shopInfo, user, goods)
) {
return false;
}
return true;
});
}
/**
* 判断优惠券是否可使用,并返回不可用原因
*
* @param {Object} args - 函数参数集合
* @param {Array} args.canDikouGoodsArr - 可参与抵扣的商品列表
* @param {Object} args.coupon - 优惠券信息对象
* @param {boolean} args.coupon.use - 优惠券是否启用
* @param {Array} args.coupon.useFoods - 优惠券适用的商品ID列表
* @param {number} args.coupon.fullAmount - 优惠券使用门槛金额
* @param {number} args.coupon.type - 优惠券类型
* @param {number} args.goodsOrderPrice - 订单中所有商品的总金额
* @param {Object} args.user - 用户信息对象
* @param {Object} args.selCoupon - 已经选择的优惠券信息对象
* @param {Object} args.shopInfo
* @param {boolean} args.limitTimeDiscount - 限时折扣
* @returns {Object} - { canUse: boolean, reason: string } 可用状态及不可用原因
*/
export function returnCouponCanUse(args) {
let {
canDikouGoodsArr,
coupon,
goodsOrderPrice,
user,
selCoupon,
shopInfo,
isMemberPrice,
limitTimeDiscount,
} = args;
// 优惠券未启用
if (!coupon.use) {
return {
canUse: false,
reason: coupon.noUseRestrictions || "不在可用时间段内",
};
}
if (
limitTimeDiscount &&
limitTimeDiscount.id &&
limitTimeDiscount.foodType == 1 &&
!coupon.discountShare
) {
return {
canUse: false,
reason: coupon.noUseRestrictions || "不可与限时折扣同享",
};
}
// 计算门槛金额
let fullAmount = goodsOrderPrice;
canDikouGoodsArr = returnCanDikouGoodsArr({
canDikouGoodsArr,
selCoupon,
user,
shopInfo,
limitTimeDiscount,
});
//优惠券指定门槛商品列表
let canCalcGoodsArr = [...canDikouGoodsArr];
//部分商品参与门槛计算
if (coupon.thresholdFoods.length) {
canCalcGoodsArr = canDikouGoodsArr.filter((v) => {
return coupon.thresholdFoods.find((food) => food.id == v.productId);
});
}
canCalcGoodsArr = returnCanCalcGoodsList(
canCalcGoodsArr,
coupon,
shopInfo,
user
);
console.log("canCalcGoodsArr");
console.log(canCalcGoodsArr);
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return (
pre + returnGoodsPrice(cur, user, shopInfo, limitTimeDiscount) * cur.num
);
}, 0);
// 是否全部商品可用
const isDikouAll = coupon.useFoods.length === 0;
// 订单可用商品列表
let canUseGoodsArr = [];
if (!isDikouAll) {
canUseGoodsArr = canDikouGoodsArr.filter((v) => {
return coupon.useFoods.find((food) => food.id == v.productId);
});
}
// if (user.isVip && !coupon.vipPriceShare) {
// return {
// canUse: false,
// reason: "非会员可用",
// };
// }
if (selCoupon.length > 0 && !selCoupon[0].otherCouponShare) {
return {
canUse: false,
reason: "当前选中的券不可与其他券同享",
};
}
if (selCoupon.length > 0 && !coupon.otherCouponShare) {
return {
canUse: false,
reason: "当前选中的券不可与其他券同享",
};
}
// 满减券和折扣券计算门槛金额是否满足
if ([1, 3].includes(coupon.type)) {
if (canCalcGoodsArr.length <= 0) {
return {
canUse: false,
reason: "没有可参与计算门槛的商品",
};
}
// 不满足门槛金额
if (fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
// 商品兑换券,第二件半价和买一送一判断是否有可用商品
if ([2, 4, 5].includes(coupon.type)) {
// 没有符合条件的商品
if (isDikouAll && canDikouGoodsArr.length === 0) {
return {
canUse: false,
reason: "没有符合条件的商品",
};
}
if (!isDikouAll && canUseGoodsArr.length === 0) {
return {
canUse: false,
reason: "没有符合条件的商品",
};
}
if (coupon.type == 2) {
if (canCalcGoodsArr.length <= 0) {
return {
canUse: false,
reason: "没有符合计算门槛条件的商品",
};
}
if (fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
}
//商品兑换券是否达到门槛金额
if (coupon.type == 2 && goodsOrderPrice < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
// 买一送一券特殊验证
if (coupon.type === 6) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => v.num >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => v.num >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 第二件半价券特殊验证
if (coupon.type === 4) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => v.num >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => v.num >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 所有条件都满足
return {
canUse: true,
reason: "",
};
}
/**
* 计算抵扣商品金额
* @param discountGoodsArr 可抵扣商品列表
* @param discountNum 抵扣数量
* @param user 用户信息
* @param {Object} shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function calcDiscountGoodsArrPrice(
discountGoodsArr,
discountNum,
user,
shopInfo,
limitTimeDiscount
) {
let hasCountNum = 0;
let discountPrice = 0;
let hasDiscountGoodsArr = [];
for (let i = 0; i < discountGoodsArr.length; i++) {
if (hasCountNum >= discountNum) {
break;
}
const goods = discountGoodsArr[i];
const shengyuNum = discountNum - hasCountNum;
const num = Math.min(goods.num, shengyuNum);
const realPrice = returnGoodsPrice(
goods,
user,
shopInfo,
limitTimeDiscount
);
discountPrice += realPrice * num;
hasCountNum += num;
hasDiscountGoodsArr.push({
...goods,
num,
});
}
return {
discountPrice,
hasDiscountGoodsArr,
};
}
/**
* 计算优惠券抵扣金额
* @param arr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCouponDiscount(
arr,
coupon,
user,
goodsOrderPrice,
selCoupon,
shopInfo,
limitTimeDiscount
) {
arr = returnCanDikouGoods(arr, user, shopInfo,limitTimeDiscount);
const canDikouGoodsArr = returnCanDikouGoodsArr({
canDikouGoodsArr: arr,
selCoupon,
user,
shopInfo,
limitTimeDiscount,
});
if (coupon.type == 2) {
return returnCouponProductDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
}
if (coupon.type == 6) {
const result = returnCouponBuyOneGiveOneDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
return result;
}
if (coupon.type == 4) {
return returnSecoendDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
);
}
if (coupon.type == 3) {
return returnCouponZhekouDiscount(
canDikouGoodsArr,
coupon,
user,
goodsOrderPrice,
selCoupon,
limitTimeDiscount
);
}
}
/**
* 折扣券抵扣金额
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param goodsOrderPrice 商品订单金额
* @param selCoupon 已选择的优惠券列表
* @param limitTimeDiscount 限时折扣
*
*/
export function returnCouponZhekouDiscount(
canDikouGoodsArr,
coupon,
user,
goodsOrderPrice,
selCoupon,
limitTimeDiscount
) {
const { discountRate, maxDiscountAmount } = coupon;
// 计算商品优惠券折扣总和使用BigNumber避免精度问题
const goodsCouponDiscount = selCoupon
.filter((v) => v.type == 2)
.reduce((prve, cur) => {
return new BigNumber(prve).plus(
new BigNumber(cur.discount.discountPrice)
);
}, new BigNumber(0));
// 将商品订单价格转换为BigNumber并减去优惠券折扣
const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(
goodsCouponDiscount
);
// 计算优惠比例:(100 - 折扣率) / 100
const discountAmountRatio = new BigNumber(100)
.minus(discountRate)
.dividedBy(100);
// 计算折扣金额:调整后的商品订单金额 × 优惠比例
let discountPrice = adjustedGoodsOrderPrice
.times(discountAmountRatio)
.decimalPlaces(2, BigNumber.ROUND_FLOOR)
.toNumber();
// 应用最大折扣金额限制
if (maxDiscountAmount !== 0) {
discountPrice =
discountPrice >= maxDiscountAmount ? maxDiscountAmount : discountPrice;
}
return {
discountPrice, // 折扣抵扣金额(即优惠的金额)
hasDiscountGoodsArr: [],
};
}
/**
* 商品券抵扣金额
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCouponProductDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, discountNum, useRule } = coupon;
//抵扣商品数组
let discountGoodsArr = [];
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoodsArr = canDikouGoodsArr.slice(discountNum * -1).reverse();
} else {
discountGoodsArr = canDikouGoodsArr.slice(0, discountNum);
}
} else {
//抵扣选中商品
const discountSelGoodsArr = canDikouGoodsArr.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoodsArr = discountSelGoodsArr.slice(discountNum * -1).reverse();
} else {
discountGoodsArr = discountSelGoodsArr.slice(0, discountNum);
}
}
const result = calcDiscountGoodsArrPrice(
discountGoodsArr,
discountNum,
user,
shopInfo,
limitTimeDiscount
);
return result;
}
// 返回买一送一券抵扣详情
/**
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
function returnCouponBuyOneGiveOneDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
//符合买一送一条件的商品
const canUseGoods = canDikouGoodsArr.filter((v) => v.num >= 2);
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoods = canUseGoods[canUseGoods.length - 1];
} else {
discountGoods = canUseGoods[0];
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
discountGoods = canUseGoods1[0];
}
}
let discountPrice = 0;
let hasDiscountGoodsArr = [];
if (discountGoods) {
discountPrice = returnGoodsPrice(
discountGoods,
user,
shopInfo,
limitTimeDiscount
);
hasDiscountGoodsArr = [discountGoods];
}
return {
discountPrice: discountPrice <= 0 ? 0 : discountPrice,
hasDiscountGoodsArr,
};
}
/**
* 返回第二件半价券抵扣详情
* @param canDikouGoodsArr 可抵扣商品列表
* @param coupon 优惠券
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
function returnSecoendDiscount(
canDikouGoodsArr,
coupon,
user,
shopInfo,
limitTimeDiscount
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods = undefined;
//符合条件的商品
const canUseGoods = canDikouGoodsArr.filter((v) => v.num >= 2);
//抵扣全部商品
if (useFoods.length === 0) {
if (useRule == "price_asc") {
discountGoods = canUseGoods[canUseGoods.length - 1];
} else {
discountGoods = canUseGoods[0];
}
} else {
//符合抵扣条件的商品
const canUseGoods1 = canUseGoods.filter((v) =>
useFoods.find((food) => food.id == v.productId)
);
if (useRule == "price_asc") {
discountGoods = canUseGoods1[canUseGoods1.length - 1];
} else {
discountGoods = canUseGoods1[0];
}
}
let discountPrice = 0;
let hasDiscountGoodsArr = [];
if (discountGoods) {
discountPrice = returnGoodsPrice(
discountGoods,
user,
shopInfo,
limitTimeDiscount
);
hasDiscountGoodsArr = [discountGoods];
}
//返回半价价格
return {
discountPrice:
discountPrice <= 0
? 0
: new BigNumber(discountPrice).dividedBy(2).toNumber(),
hasDiscountGoodsArr,
};
}
/**
* 返回可以抵扣优惠券的商品列表,过滤掉赠品、临时商品,价格从高到低排序
* @param arr 商品列表
* @param user 用户信息
* @param shopInfo 店铺信息
* @param limitTimeDiscount 限时折扣
*/
export function returnCanDikouGoods(arr, user, shopInfo, limitTimeDiscount) {
const result = arr
.filter((v) => {
return v.is_temporary != 1 && v.is_gift != 1;
})
.filter((v) => {
return v.num > 0;
})
.sort((a, b) => {
return (
returnGoodsPrice(b, user, shopInfo, limitTimeDiscount) -
returnGoodsPrice(a, user, shopInfo, limitTimeDiscount)
);
});
return result;
}

View File

@@ -234,6 +234,46 @@ export interface FreeDineConfig {
useShopType?: string; //all 全部 part部分
shopIdList?: number[]; //可用门店id
}
//限时折扣配置
export interface TimeLimitDiscountConfig {
/**
* 折扣优先级 limit-time/vip-price
*/
discountPriority: string;
/**
* 折扣% 范围1-99
*/
discountRate: number;
/**
* 参与商品
*/
foods: string;
/**
* 参与商品 1全部 2部分
*/
foodType: number;
/**
* 自增主键
*/
id: number;
/**
* 店铺ID
*/
shopId: number;
/**
* 可使用类型:堂食 dine-in 外带 take-out 外卖 take-away 配送 post
*/
useType: string;
[property: string]: any;
}
//用户信息
interface ShopUserInfo {
isVip: number | null; //是否会员
discount: number | null; //用户折扣
isMemberPrice: number | null; //会员折扣与会员价是否同时使用
}
/** 订单额外费用配置 */
export interface OrderExtraConfig {
// merchantReduction: number; // 商家减免金额默认0
@@ -251,6 +291,8 @@ export interface OrderExtraConfig {
currentDinnerType: "dine-in" | "take-out" | "take-away" | "post"; // 当前就餐类型匹配useType
isFreeDine?: boolean; //是否霸王餐
freeDineConfig?: FreeDineConfig;
limitTimeDiscount?: TimeLimitDiscountConfig; //限时折扣
shopUserInfo: ShopUserInfo; // 用户信息
}
/** 订单费用汇总(修改:补充商家减免类型和明细) */
@@ -283,10 +325,12 @@ export interface OrderCostSummary {
config: OrderExtraConfig; // 订单额外费用配置
//满减活动
fullReduction: {
usedFullReductionActivityFullAmount: number; // 计算出的满减活动的门槛金额
usedActivity?: FullReductionActivity; // 实际使用的满减活动
usedThreshold?: FullReductionThreshold; // 实际使用的满减阈值(多门槛中选最优)
actualAmount: number; // 满减实际减免金额(元)
};
vipDiscountAmount: number; //会员折扣减免金额
// 订单原支付金额
orderOriginFinalPayAmount: number; //订单原金额(包含打包费+餐位费)
}
@@ -399,6 +443,195 @@ function isDinnerTypeMatch(
//满减活动的就餐类型和当前券类型字段值不一样暂时返回true
return true;
}
//判断商品是否可以使用限时折扣
export function returnCanUseLimitTimeDiscount(
goods: BaseCartItem,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined,
useVipPrice: boolean,
idKey = "product_id"
) {
if (!limitTimeDiscount || !limitTimeDiscount.id) {
return false;
}
const canUseFoods = (limitTimeDiscount.foods || "").split(",");
const goodsCanUse =
limitTimeDiscount.foodType == 1 || canUseFoods.includes("" + goods[idKey as keyof BaseCartItem]);
if (!goodsCanUse) {
return false;
}
if (limitTimeDiscount.discountPriority == "limit-time") {
return true;
}
if (limitTimeDiscount.discountPriority == "vip-price") {
if (!useVipPrice) {
return true;
}
if (useVipPrice && goods.hasOwnProperty("memberPrice")) {
if (goods.memberPrice && goods.memberPrice * 1 <= 0) {
return true;
}
}
}
return false;
}
function returnMemberPrice(useVipPrice: boolean, goods: BaseCartItem) {
if (useVipPrice) {
return goods.memberPrice || goods.salePrice;
} else {
return goods.salePrice;
}
}
/**
* 返回商品限时折扣价格
*/
function returnLimitPrice(
goods: BaseCartItem,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined,
useVipPrice: boolean
) {
if (!limitTimeDiscount) {
return 0;
}
const discountRate = new BigNumber(limitTimeDiscount.discountRate).dividedBy(
100
);
const canuseLimit = returnCanUseLimitTimeDiscount(
goods,
limitTimeDiscount,
useVipPrice
);
if (canuseLimit) {
//可以使用限时折扣
if (limitTimeDiscount.discountPriority == "limit-time") {
//限时价优先
const result = BigNumber(goods.salePrice)
.times(discountRate)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
return result;
}
if (limitTimeDiscount.discountPriority == "vip-price") {
//会员价优先
if (useVipPrice && goods.memberPrice && goods.memberPrice * 1 > 0) {
//使用会员价
return returnMemberPrice(useVipPrice, goods);
} else {
//不使用会员价
const result = BigNumber(goods.salePrice)
.times(discountRate)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
return result;
}
}
} else {
//不可以使用限时折扣
//会员价优先
if (useVipPrice) {
//使用会员价
return returnMemberPrice(useVipPrice, goods);
} else {
return goods.salePrice;
}
}
}
/**
* 计算商品计算门槛时的金额
*/
export function returnCalcPrice(
goods: BaseCartItem,
fullReductionActivitie: FullReductionActivity | undefined,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined,
useVipPrice: boolean,
idKey = "product_id"
) {
if (goods.discountSaleAmount && goods.discountSaleAmount * 1 > 0) {
return goods.salePrice;
}
//限时折扣和满减活动都有
if (fullReductionActivitie && limitTimeDiscount) {
if (
fullReductionActivitie.discountShare == 1 &&
fullReductionActivitie.vipPriceShare == 1
) {
//与限时折扣同享,与会员价不同享
return returnLimitPrice(goods, limitTimeDiscount, useVipPrice);
}
if (
fullReductionActivitie.discountShare != 1 &&
fullReductionActivitie.vipPriceShare == 1
) {
//与限时折扣不同享,与会员价同享
return returnMemberPrice(useVipPrice, goods);
}
if (fullReductionActivitie.vipPriceShare != 1) {
//与会员价不同享
return goods.salePrice;
}
return goods.salePrice;
}
//只有满减活动
if (fullReductionActivitie) {
if (fullReductionActivitie.vipPriceShare == 1) {
return returnMemberPrice(useVipPrice, goods);
} else {
return goods.salePrice;
}
}
//只有限时折扣
if (limitTimeDiscount) {
return returnLimitPrice(goods, limitTimeDiscount, useVipPrice);
}
if (useVipPrice) {
return returnMemberPrice(useVipPrice, goods);
}
return goods.salePrice;
}
/**
* 计算满减活动门槛
*/
export function calcFullReductionActivityFullAmount(
goodsList: BaseCartItem[],
fullReductionActivitie: FullReductionActivity | undefined,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined,
useVipPrice: boolean,
seatFee: number,
packFee: number
): number {
if (!fullReductionActivitie) {
return 0;
}
let amount = 0;
for (let goods of goodsList) {
const availableNum = Math.max(0, goods.number - (goods.returnNum || 0));
if (goods.is_temporary || goods.is_gift || availableNum <= 0) {
//临时菜,赠菜,数量<=0的商品不计算
continue;
}
const calcPrice = returnCalcPrice(
goods,
fullReductionActivitie,
limitTimeDiscount,
useVipPrice,
"product_id"
);
if (calcPrice !== undefined) {
amount += calcPrice * availableNum;
}
}
return amount + seatFee + packFee;
console.log("amount", amount);
}
/**
* 筛选最优满减活动(对齐后端逻辑:状态→时间→周期→时段→就餐类型→排序→修改时间)
* @param activities 后端返回的满减活动列表
@@ -424,11 +657,16 @@ export function filterOptimalFullReductionActivity(
activity.thresholds?.length // 至少有一个满减阈值
);
});
console.log("baseEligible", baseEligible);
if (!baseEligible.length) return undefined;
// 第二步:时间筛选(有效期内+周期内+时段内)
const timeEligible = baseEligible.filter((activity) => {
// 1. 校验有效期validStartTime ~ validEndTime
if (activity.useTimeType == "all") {
return true;
}
if (!activity.validStartTime || !activity.validEndTime) return false;
const startDate = new Date(activity.validStartTime);
const endDate = new Date(activity.validEndTime);
@@ -520,9 +758,7 @@ export function calcMemberPrice(
if (!isMember) return truncateToTwoDecimals(goods.salePrice);
// 优先级SKU会员价 > 商品会员价 > 商品原价(无会员价时用会员折扣)
const basePrice =
goods.skuData?.memberPrice ?? goods.memberPrice ?? goods.salePrice;
const basePrice = goods.memberPrice || goods.salePrice;
// 仅当无SKU会员价、无商品会员价时才应用会员折扣率
if (memberDiscountRate && !goods.skuData?.memberPrice && !goods.memberPrice) {
return truncateToTwoDecimals(
@@ -565,8 +801,8 @@ export function filterThresholdGoods(
return applicableProductIds.length === 0
? baseEligibleGoods
: baseEligibleGoods.filter((goods) =>
applicableProductIds.includes(String(goods.product_id))
); // 核心修正用商品ID匹配
applicableProductIds.includes(String(goods.product_id))
); // 核心修正用商品ID匹配
}
/**
@@ -665,11 +901,13 @@ export function calcCouponThresholdAmount(
*/
export function calcSingleGoodsRealPrice(
goods: BaseCartItem,
config: Pick<OrderExtraConfig, "isMember" | "memberDiscountRate"> & {
activity?: ActivityConfig; // 商品参与的营销活动如限时折扣按商品ID匹配
}
config: Pick<
OrderExtraConfig,
"isMember" | "memberDiscountRate" | "limitTimeDiscount"
>
): number {
const { isMember, memberDiscountRate, activity } = config;
const { isMember, memberDiscountRate, limitTimeDiscount: activity } = config;
console.log("activity", activity);
//如果是增菜价格为0
if (goods.is_gift) {
@@ -687,12 +925,38 @@ export function calcSingleGoodsRealPrice(
);
// 3. 优先级3营销活动折扣如限时折扣需按商品ID匹配活动
const isActivityApplicable = activity
? (activity.applicableProductIds || []).includes(String(goods.product_id)) // 核心修正用商品ID匹配活动
: false;
let isActivityApplicable = false;
if (activity) {
if (activity.foodType == 1) {
isActivityApplicable = true;
} else {
const canUseGoods = activity.foods?.split(",") || [];
if (canUseGoods.find((v) => v == String(goods.product_id))) {
isActivityApplicable = true;
}
}
}
if (!activity || !isActivityApplicable) {
return memberPrice.toNumber();
}
console.log("isMember", isMember);
//限时折扣优先或者会员价优先但是不是会员或者未开启会员价格时限时折扣优先
if (
activity.discountPriority == "limit-time" ||
(activity.discountPriority == "vip-price" && !isMember) ||
(activity.discountPriority == "vip-price" && isMember && !goods.memberPrice)
) {
//限时折扣优先
return truncateToTwoDecimals(
new BigNumber(goods.salePrice)
.times(activity.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber()
);
}
if (activity.discountPriority == "vip-price" && isMember) {
return memberPrice.toNumber();
}
// 处理活动与会员的同享/不同享逻辑
if (activity.vipPriceShare) {
@@ -751,26 +1015,18 @@ export function calcGoodsOriginalAmount(goodsList: BaseCartItem[]): number {
*/
export function calcGoodsRealAmount(
goodsList: BaseCartItem[],
config: Pick<OrderExtraConfig, "isMember" | "memberDiscountRate">,
activities: ActivityConfig[] = []
config: Pick<
OrderExtraConfig,
"isMember" | "memberDiscountRate" | "limitTimeDiscount"
>
): number {
let total = new BigNumber(0);
for (const goods of goodsList) {
const availableNum = Math.max(0, goods.number - (goods.returnNum || 0));
if (availableNum <= 0) continue;
// 匹配商品参与的营销活动按商品ID匹配优先商品自身配置
const activity =
goods.activityInfo ??
activities.find(
(act) =>
(act.applicableProductIds || []).includes(String(goods.product_id)) // 核心修正用商品ID匹配活动
);
const realPrice = new BigNumber(
calcSingleGoodsRealPrice(goods, { ...config, activity })
);
console.log("goods", goods);
const realPrice = new BigNumber(calcSingleGoodsRealPrice(goods, config));
total = total.plus(realPrice.multipliedBy(availableNum));
}
@@ -818,27 +1074,18 @@ export function selectOptimalThreshold(
const validThresholds = thresholds.filter((threshold) => {
const fullAmount = new BigNumber(threshold.fullAmount || 0);
const discountAmount = new BigNumber(threshold.discountAmount || 0);
console.log("fullAmount", fullAmount);
console.log("discountAmount", discountAmount);
return (
fullAmount.isLessThanOrEqualTo(thresholdBase) &&
discountAmount.isGreaterThan(0)
);
});
console.log("validThresholds", validThresholds);
if (!validThresholds.length) return undefined;
// 第三步选择最优阈值优先级1.满金额最小 → 2.减免金额最大)
// const sortValidThresholds = validThresholds.sort((a, b) => {
// const aFull = new BigNumber(a.fullAmount || 0);
// const bFull = new BigNumber(b.fullAmount || 0);
// const aDiscount = new BigNumber(a.discountAmount || 0);
// const bDiscount = new BigNumber(b.discountAmount || 0);
// // 先比满金额越小越优先满1减10 比 满100减20 更优)
// if (!aFull.isEqualTo(bFull)) {
// return aFull.comparedTo(bFull) || 0; // Ensure a number is always returned
// }
// // 再比减免金额:越大越优先
// return bDiscount.comparedTo(aDiscount) || 0; // Ensure a number is always returned
// })
// 找到抵扣金额最大的门槛项
const maxDiscountThreshold = validThresholds.reduce(
(maxItem, currentItem) => {
@@ -981,13 +1228,12 @@ export function calcTotalPackFee(
const packNumber = goods.packNumber ? goods.packNumber * 1 : 0;
let availableNum = Math.max(0, goods.number - (goods.returnNum || 0));
if (availableNum === 0) continue;
// 计算单个商品打包数量外卖全打包堂食按配置称重商品≤1
let packNum = Math.min(availableNum, packNumber);
if (dinnerType === "take-out") {
packNum = availableNum
packNum = availableNum;
}
if (goods.product_type === GoodsType.WEIGHT) {
packNum = Math.min(packNum, 1);
@@ -1043,8 +1289,8 @@ export function calcPointDeduction(
)
? maxDeductByPoints
: new BigNumber(rule.maxDeductionAmount || Infinity).isLessThan(maxLimitBn)
? maxDeductByPoints
: maxLimitBn;
? maxDeductByPoints
: maxLimitBn;
// 实际使用积分 = 抵扣金额 * 积分兑换比例
const usedPoints = maxDeductAmount.multipliedBy(pointsPerYuanBn);
@@ -1057,6 +1303,24 @@ export function calcPointDeduction(
};
}
function calcVipDiscountAmount(
goodsRealAmount: number,
shopUserInfo: ShopUserInfo
): number {
if (!shopUserInfo.isVip || shopUserInfo.discount === 0) return 0;
if (shopUserInfo.isVip == 1 && shopUserInfo.isMemberPrice != 1) {
return 0;
}
console.log("goodsRealAmount", goodsRealAmount);
console.log("discount", (100 - (shopUserInfo.discount || 0)) / 100);
return truncateToTwoDecimals(
new BigNumber(goodsRealAmount)
.times((100 - (shopUserInfo.discount || 0)) / 100)
.decimalPlaces(2, BigNumber.ROUND_DOWN)
.toNumber()
);
}
// ============================ 6. 订单总费用汇总与实付金额计算(核心入口,补充细分字段) ============================
/**
* 计算订单所有费用子项并汇总(核心入口函数)
@@ -1079,7 +1343,14 @@ export function calculateOrderCostSummary(
currentTime: Date = new Date()
): OrderCostSummary {
//是否使用霸王餐,霸王餐配置
const { isFreeDine, freeDineConfig } = config;
const {
isFreeDine,
freeDineConfig,
limitTimeDiscount,
fullReductionActivities,
shopUserInfo,
} = config;
console.log("shopUserInfo", shopUserInfo);
// ------------------------------ 1. 基础费用计算 ------------------------------
const goodsOriginalAmount = calcGoodsOriginalAmount(goodsList); // 商品原价总和
@@ -1089,19 +1360,35 @@ export function calculateOrderCostSummary(
{
isMember: config.isMember,
memberDiscountRate: config.memberDiscountRate,
},
activities
limitTimeDiscount: config.limitTimeDiscount,
}
);
const goodsDiscountAmount = calcGoodsDiscountAmount(
goodsOriginalAmount,
goodsRealAmount
); // 商品折扣金额
const newUserDiscount = config.newUserDiscount || 0; // 新客立减
// 其他费用计算(原有逻辑不变) ------------------------------
const packFee = calcTotalPackFee(goodsList, dinnerType); // 打包费
let seatFee = calcSeatFee(config.seatFeeConfig); // 餐位费
seatFee = dinnerType === "dine-in" ? seatFee : 0; // 外卖不收餐位费
const additionalFee = Math.max(0, config.additionalFee); // 附加费
// ------------------------------ 2. 满减活动计算(核心步骤) ------------------------------
let usedFullReductionActivity: FullReductionActivity | undefined;
let usedFullReductionThreshold: FullReductionThreshold | undefined;
let fullReductionAmount = 0;
let usedFullReductionActivityFullAmount = calcFullReductionActivityFullAmount(
goodsList,
usedFullReductionActivity,
config.limitTimeDiscount,
config.isMember,
seatFee,
packFee
);
// 2.1 筛选最优满减活动(后端活动列表、当前店铺、就餐类型、时间)
usedFullReductionActivity = filterOptimalFullReductionActivity(
config.fullReductionActivities,
@@ -1110,14 +1397,15 @@ export function calculateOrderCostSummary(
currentTime
);
// 其他费用计算(原有逻辑不变) ------------------------------
const packFee = calcTotalPackFee(goodsList, dinnerType); // 打包费
let seatFee = calcSeatFee(config.seatFeeConfig); // 餐位费
seatFee = dinnerType === "dine-in" ? seatFee : 0; // 外卖不收餐位费
const additionalFee = Math.max(0, config.additionalFee); // 附加费
// 2.2 计算满减基数(先扣新客立减)
let baseAfterNewUserDiscount = new BigNumber(goodsRealAmount)
let baseAfterNewUserDiscount = new BigNumber(
limitTimeDiscount &&
limitTimeDiscount.id &&
usedFullReductionActivity &&
!usedFullReductionActivity.discountShare
? goodsRealAmount
: goodsRealAmount
)
.minus(newUserDiscount)
.plus(packFee)
.plus(seatFee)
@@ -1128,9 +1416,19 @@ export function calculateOrderCostSummary(
// 2.3 选择最优满减阈值(多门槛场景)
if (usedFullReductionActivity) {
//计算当前满减活动的门槛金额
usedFullReductionActivityFullAmount = calcFullReductionActivityFullAmount(
goodsList,
usedFullReductionActivity,
config.limitTimeDiscount,
config.isMember,
seatFee,
packFee
);
usedFullReductionThreshold = selectOptimalThreshold(
usedFullReductionActivity.thresholds,
baseAfterNewUserDiscount,
usedFullReductionActivityFullAmount,
goodsOriginalAmount,
goodsRealAmount,
usedFullReductionActivity.discountShare || 0 // 与限时折扣同享规则
@@ -1143,6 +1441,7 @@ export function calculateOrderCostSummary(
usedFullReductionThreshold
);
}
// ------------------------------ 3. 优惠券抵扣(适配满减同享规则) ------------------------------
let couponDeductionAmount = 0;
let productCouponDeduction = 0;
@@ -1164,6 +1463,7 @@ export function calculateOrderCostSummary(
currentTime,
}
);
console.log("couponResult", couponResult);
couponDeductionAmount = couponResult.deductionAmount;
productCouponDeduction = couponResult.productCouponDeduction;
fullCouponDeduction = couponResult.fullCouponDeduction;
@@ -1171,7 +1471,10 @@ export function calculateOrderCostSummary(
excludedProductIds = couponResult.excludedProductIds;
// 若满减与优惠券同享couponShare=1才计算优惠券否则优惠券抵扣为0
if (usedFullReductionActivity && !usedFullReductionActivity.couponShare) {
if (
usedFullReductionThreshold &&
(!usedFullReductionActivity || !usedFullReductionActivity.couponShare)
) {
couponDeductionAmount = 0;
productCouponDeduction = 0;
fullCouponDeduction = 0;
@@ -1195,26 +1498,26 @@ export function calculateOrderCostSummary(
maxPointDeductionLimit =
maxPointDeductionLimit > 0 ? maxPointDeductionLimit : 0;
const pointResult = calcPointDeduction(
config.userPoints,
config.pointDeductionRule,
maxPointDeductionLimit
);
console.log("积分抵扣结果:", pointResult);
pointDeductionAmount = pointResult.deductionAmount;
usedPoints = pointResult.usedPoints;
// 若满减与积分不同享pointsShare=1积分抵扣为0
if (usedFullReductionActivity && !usedFullReductionActivity.pointsShare) {
console.log("满减与积分不同享:积分抵扣为0");
if (
usedFullReductionThreshold &&
(!usedFullReductionActivity || !usedFullReductionActivity.pointsShare)
) {
pointDeductionAmount = 0;
usedPoints = 0;
}
//使用霸王餐
if (isFreeDine && freeDineConfig && freeDineConfig.enable) {
console.log("使用霸王餐");
fullReductionAmount = 0;
//不与优惠券同享
if (!freeDineConfig.withCoupon) {
couponDeductionAmount = 0;
@@ -1242,13 +1545,13 @@ export function calculateOrderCostSummary(
.plus(packFee)
.isGreaterThan(0)
? new BigNumber(goodsRealAmount)
.minus(newUserDiscount)
.minus(fullReductionAmount)
.minus(couponDeductionAmount)
.minus(pointDeductionAmount)
.plus(seatFee)
.plus(packFee)
.toNumber()
.minus(newUserDiscount)
.minus(fullReductionAmount)
.minus(couponDeductionAmount)
.minus(pointDeductionAmount)
.plus(seatFee)
.plus(packFee)
.toNumber()
: 0;
switch (merchantReductionConfig.type) {
@@ -1271,9 +1574,22 @@ export function calculateOrderCostSummary(
truncateToTwoDecimals(merchantReductionActualAmount)
);
// 会员折扣减免
const vipDiscountAmount = calcVipDiscountAmount(
new BigNumber(goodsRealAmount)
.minus(couponDeductionAmount)
.plus(packFee)
.plus(seatFee)
.minus(newUserDiscount)
.minus(fullReductionAmount)
.toNumber(),
shopUserInfo
);
console.log("vipDiscountAmount", vipDiscountAmount);
// ------------------------------ 6. 最终实付金额计算 ------------------------------
const finalPayAmountBn = new BigNumber(goodsRealAmount)
.minus(newUserDiscount)
.minus(vipDiscountAmount)
.minus(fullReductionAmount)
.minus(couponDeductionAmount)
.minus(pointDeductionAmount)
@@ -1301,6 +1617,7 @@ export function calculateOrderCostSummary(
.plus(couponDeductionAmount)
.plus(pointDeductionAmount)
.plus(merchantReductionActualAmount)
.plus(vipDiscountAmount)
.toNumber()
);
//积分可抵扣最大金额 最终支付金额+积分抵扣-商家减免
@@ -1331,10 +1648,12 @@ export function calculateOrderCostSummary(
scoreMaxMoney,
// 满减活动明细(后端字段)
fullReduction: {
usedFullReductionActivityFullAmount: usedFullReductionActivityFullAmount,
usedActivity: usedFullReductionActivity,
usedThreshold: usedFullReductionThreshold,
actualAmount: truncateToTwoDecimals(fullReductionAmount),
},
vipDiscountAmount: vipDiscountAmount, //会员折扣减免金额
merchantReduction: {
type: merchantReductionConfig.type,
originalConfig: merchantReductionConfig,

183
src/utils/order-utils.js Normal file
View File

@@ -0,0 +1,183 @@
import BigNumber from "bignumber.js";
//判断商品是否可以使用限时折扣
export function canUseLimitTimeDiscount(
goods,
limitTimeDiscountRes,
shopInfo,
shopUserInfo,
idKey = "id"
) {
shopInfo = shopInfo || {};
shopUserInfo = shopUserInfo || {};
if (!limitTimeDiscountRes || !limitTimeDiscountRes.id) {
return false;
}
const canUseFoods = (limitTimeDiscountRes.foods || "").split(",");
const goodsCanUse =
limitTimeDiscountRes.foodType == 1 ||
canUseFoods.includes(`${goods[idKey]}`);
if (!goodsCanUse) {
return false;
}
if (limitTimeDiscountRes.discountPriority == "limit-time") {
return true;
}
if (limitTimeDiscountRes.discountPriority == "vip-price") {
if (shopUserInfo.isVip != 1 || shopUserInfo.isMemberPrice != 1) {
return true;
}
if (
shopUserInfo.isVip == 1 &&
shopUserInfo.isMemberPrice == 1 &&
goods.memberPrice * 1 <= 0
) {
return true;
}
}
return false;
}
/**
* 返回商品显示价格
* @params {*} args 参数对象
* @params {*} args.goods 商品对象
* @params {*} args.shopInfo 店铺信息
* @params {*} args.limitTimeDiscountRes 限时折扣信息
* @params {*} args.shopUserInfo 店铺用户信息
* @returns
*/
export function returnPrice(args) {
let {
goods,
shopInfo,
limitTimeDiscountRes,
shopUserInfo,
idKey = "product_id",
} = args;
console.log('返回商品显示价格.args===', args);
limitTimeDiscountRes = limitTimeDiscountRes || { foods: '', foodType: 2 }
const canUseFoods = (limitTimeDiscountRes.foods || "").split(",");
// console.log('返回商品显示价格.goods===', goods);
const includesGoods =
limitTimeDiscountRes.foodType == 1 ||
canUseFoods.includes("" + goods[idKey]);
console.log('includesGoods===', includesGoods);
shopInfo = shopInfo || {};
shopUserInfo = shopUserInfo || {};
if (shopUserInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1 && shopInfo.isMemberPrice == 1) {
const memberPrice = goods.memberPrice || goods.salePrice;
//是会员而且启用会员价
if (limitTimeDiscountRes) {
//使用限时折扣
//限时折扣优先
if (limitTimeDiscountRes.discountPriority == "limit-time") {
if (includesGoods) {
return returnLimitPrice({
price: goods.salePrice,
limitTimeDiscountRes,
});
} else {
return memberPrice;
}
}
if (
limitTimeDiscountRes.discountPriority == "vip-price" &&
includesGoods
) {
if (goods.memberPrice * 1 > 0) {
//会员优先
return memberPrice;
} else {
const price = returnLimitPrice({
price: goods.salePrice,
limitTimeDiscountRes,
goods: goods,
});
return price;
}
} else {
return memberPrice;
}
} else {
//是会员没有限时折扣
return memberPrice;
}
} else {
// console.log('不是会员或者没有启用会员价',goods,limitTimeDiscountRes);
//不是会员或者没有启用会员价
if (limitTimeDiscountRes && limitTimeDiscountRes.id && includesGoods) {
const price = returnLimitPrice({
price: goods.salePrice,
limitTimeDiscountRes,
goods: goods,
});
return price;
} else {
return goods.salePrice;
}
}
}
/**
* 返回限时折扣价格
* @params {*} args 参数对象
* @params {*} args.limitTimeDiscountRes 限时折扣信息
* @params {*} args.price 商品价格
* @param {*} args.goods 商品对象
* @returns
*/
export function returnLimitPrice(args) {
console.log('返回限时折扣价格===', args);
const { limitTimeDiscountRes, price, goods } = args;
const discountRate = new BigNumber(
limitTimeDiscountRes.discountRate
).dividedBy(100);
const result = BigNumber(price)
.times(discountRate)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
return result;
}
/**
* 判断是否返回会员价
* @param {*} args 参数对象
* @param {*} args.shopInfo 店铺信息
* @param {*} args.shopUserInfo 店铺用户信息
* @returns
*/
export function canReturnMemberPrice(args) {
const { shopInfo, shopUserInfo } = args;
if (shopUserInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1) {
return true;
} else {
return false;
}
}
/**
* 返回会员价格
* @param {*} goods
* @returns
*/
export function returnMemberPrice(goods) {
return goods.memberPrice || goods.salePrice;
}