代客下单修改

This commit is contained in:
2025-11-18 18:39:52 +08:00
parent e40b4fe8cc
commit 08e76b8897
39 changed files with 12932 additions and 4425 deletions

852
lib/coupon.ts Normal file
View File

@@ -0,0 +1,852 @@
import { BigNumber } from "bignumber.js";
import _ from "lodash";
import {
ShopInfo,
couponCalcParams,
BaseCartItem,
TimeLimitDiscountConfig,
CanDikouGoodsArrArgs,
Coupon,
ShopUserInfo,
GoodsType,
BackendCoupon,
ExchangeCalculationResult,
PointDeductionRule,
OrderCostSummary,
} from "./types";
import { getCompatibleFieldValue } from "./utils";
/**
* 返回商品单价
* @param goods 商品
* @param user 用户信息
* @param {Object} shopInfo
*/
export function returnGoodsPrice(
goods: BaseCartItem,
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined
) {
if (!goods) {
return 0;
}
//是否可以使用会员价
const canUseVipPrice =
user &&
user.isVip &&
user.isMemberPrice &&
goods.memberPrice * 1 > 0 &&
shopInfo &&
shopInfo.isMemberPrice;
// 商家改价
if (goods.discount_sale_amount && goods.discount_sale_amount * 1 > 0) {
return goods.salePrice;
}
// 限时折扣
if (limitTimeDiscount && limitTimeDiscount.id) {
//优先使用
// 兼容 isTimeDiscount/is_time_discount这里顺便处理该字段的命名兼容
const isTimeDiscount = getCompatibleFieldValue(
goods,
"isTimeDiscount",
"is_time_discount"
);
if (isTimeDiscount) {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
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: BaseCartItem[]) {
let map: { [key: string]: BaseCartItem[] } = {};
arr.forEach((v) => {
const key = v.productId + "_" + v.skuId;
if (!map[key]) {
map[key] = [];
}
map[key].push(v);
});
return map;
}
interface CouponTypes {
1: "满减券";
2: "商品券";
3: "折扣券";
4: "第二件半价券";
5: "消费送券";
6: "买一送一券";
7: "固定价格券";
8: "免配送费券";
}
/**
* 优惠券类型1-满减券2-商品兑换券3-折扣券4-第二件半价券5-消费送券6-买一送一券7-固定价格券8-免配送费券
* @param coupon
*/
export function returnCoupType(coupon: Coupon) {
const couponTypes: CouponTypes = {
1: "满减券",
2: "商品券",
3: "折扣券",
4: "第二件半价券",
5: "消费送券",
6: "买一送一券",
7: "固定价格券",
8: "免配送费券",
};
return couponTypes[coupon.type as keyof CouponTypes] || "未知类型";
}
/**
* 返回商品券抵扣后的商品列表
* @param canDikouGoodsArr 可抵扣商品列表
* @param selCoupon 已选择的优惠券列表
* @param user 用户信息
*/
export function returnCanDikouGoodsArr(args: CanDikouGoodsArrArgs) {
const { canDikouGoodsArr, selCoupon, user, shopInfo, limitTimeDiscount } =
args;
const types = [2, 4, 6];
// 收集已抵扣商品并关联对应的优惠券类型
const goodsCouponGoods = selCoupon
.filter((v) => types.includes(v.type))
.reduce((prev: BaseCartItem[], cur) => {
// 给每个抵扣商品添加所属优惠券类型
if (cur && cur.discount) {
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 ?? 0)) {
// 类型4第二件半价或6买一送一数量减2
if (v.num) {
v.num -= 2;
}
} else {
// 其他类型如类型2商品券按原逻辑扣减对应数量
if (v.num) {
v.num -= findCart.num ?? 0;
}
}
}
return v;
})
.filter((v) => {
const canUseNum = (v.num ?? 0) - (v.returnNum || 0);
// 兼容 is_temporary/isTemporary 和 is_gift/isGift
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
if (canUseNum <= 0 || isTemporary || isGift) {
return false;
}
return true;
}); // 过滤掉数量<=0的商品,赠菜,临时菜
return arr;
}
/**
* 返回商品是否享用了会员价/会员折扣
* @param {*} goods
*/
function returnGoodsIsUseVipPrice(
shopInfo: ShopInfo,
user: ShopUserInfo,
goods: BaseCartItem
) {
// 兼容 isTimeDiscount/is_time_discount
const isTimeDiscount = getCompatibleFieldValue(
goods,
"isTimeDiscount",
"is_time_discount"
);
if (isTimeDiscount) {
return false;
}
if (shopInfo.isMemberPrice != 1 || user.isVip != 1) {
return false;
}
if (shopInfo.isMemberPrice == 1 && user.isVip == 1) {
if (goods.memberPrice <= 0) {
return false;
} else {
return true;
}
}
return false;
}
/**
* 返回可以计算抵扣金额的商品列表
*/
function returnCanCalcGoodsList(
canCalcGoodsArr: BaseCartItem[],
coupon: Coupon,
shopInfo: ShopInfo,
user: ShopUserInfo
) {
return canCalcGoodsArr.filter((goods) => {
// 兼容 isTimeDiscount/is_time_discount
const isTimeDiscount = getCompatibleFieldValue(
goods,
"isTimeDiscount",
"is_time_discount"
);
if (!coupon.discountShare && 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: couponCalcParams) {
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(args);
//优惠券指定门槛商品列表
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
);
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return (
pre +
returnGoodsPrice(cur, user, shopInfo, limitTimeDiscount) * (cur.num || 0)
);
}, 0);
// 是否全部商品可用
const isDikouAll = coupon.useFoods.length === 0;
// 订单可用商品列表
let canUseGoodsArr: BaseCartItem[] = [];
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 || 0)) {
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 || 0)) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
}
//商品兑换券是否达到门槛金额
if (coupon.type == 2 && goodsOrderPrice < (coupon.fullAmount || 0)) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
// 买一送一券特殊验证
if (coupon.type === 6) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => (v.num || 0) >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => (v.num || 0) >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 第二件半价券特殊验证
if (coupon.type === 4) {
let canUse = false;
if (isDikouAll) {
canUse = canDikouGoodsArr.some((v) => (v.num || 0) >= 2);
} else if (canUseGoodsArr.length > 0) {
canUse = canUseGoodsArr.some((v) => (v.num || 0) >= 2);
}
if (!canUse) {
return {
canUse: false,
reason: "需要购买至少2件相同的商品才能使用",
};
}
}
// 所有条件都满足
return {
canUse: true,
reason: "",
};
}
/**
* 计算抵扣商品金额
* @param discountGoodsArr 可抵扣商品列表
* @param discountNum 抵扣数量
* @param user 用户信息
* @param {Object} shopInfo 店铺信息
*/
export function calcDiscountGoodsArrPrice(
discountGoodsArr: BaseCartItem[],
discountNum: number,
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
let hasCountNum = 0;
let discountPrice = 0;
let hasDiscountGoodsArr:BaseCartItem[] = [];
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 || 0, shengyuNum);
const realPrice = returnGoodsPrice(
goods,
user,
shopInfo,
limitTimeDiscount
);
discountPrice += realPrice * num;
hasCountNum += num;
if(goods){
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: BaseCartItem[],
coupon: Coupon,
user: ShopUserInfo,
goodsOrderPrice: number,
selCoupon: Coupon[],
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
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: BaseCartItem[],
coupon: Coupon,
user: ShopUserInfo,
goodsOrderPrice: number,
selCoupon: Coupon[],
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
let { discountRate, maxDiscountAmount } = coupon;
maxDiscountAmount = maxDiscountAmount || 0;
// 计算商品优惠券折扣总和使用BigNumber避免精度问题
const goodsCouponDiscount = selCoupon
.filter((v) => v.type == 2)
.reduce((prve, cur) => {
return new BigNumber(prve).plus(
new BigNumber(cur?.discount?.discountPrice || 0)
);
}, new BigNumber(0));
// 将商品订单价格转换为BigNumber并减去优惠券折扣
const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(
goodsCouponDiscount
);
// 计算优惠比例:(100 - 折扣率) / 100
const discountAmountRatio = new BigNumber(100)
.minus(discountRate || 0)
.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 店铺信息
*/
export function returnCouponProductDiscount(
canDikouGoodsArr: BaseCartItem[],
coupon: Coupon,
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
let { useFoods, discountNum, useRule } = coupon;
discountNum = discountNum || 0;
//抵扣商品数组
let discountGoodsArr:BaseCartItem[] = [];
//抵扣全部商品
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 店铺信息
*/
function returnCouponBuyOneGiveOneDiscount(
canDikouGoodsArr: BaseCartItem[],
coupon: Coupon,
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods:BaseCartItem | undefined = undefined;
//符合买一送一条件的商品(数量>=2 + 非临时/非赠品)
const canUseGoods = canDikouGoodsArr.filter((v) => {
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return (v.num || 0) >= 2 && !isTemporary && !isGift;
});
//抵扣全部商品
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: BaseCartItem[] = [];
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 店铺信息
*/
function returnSecoendDiscount(
canDikouGoodsArr: BaseCartItem[],
coupon: Coupon,
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
const { useFoods, useRule } = coupon;
//抵扣商品
let discountGoods:BaseCartItem | undefined = undefined;
//符合条件的商品(数量>=2 + 非临时/非赠品)
const canUseGoods = canDikouGoodsArr.filter((v) => {
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return (v.num || 0) >= 2 && !isTemporary && !isGift;
});
//抵扣全部商品
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: BaseCartItem[] = [];
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: BaseCartItem[],
user: ShopUserInfo,
shopInfo: ShopInfo,
limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined
) {
const result = arr
.filter((v) => {
// 兼容 is_temporary/isTemporary 和 is_gift/isGift
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return !isTemporary && !isGift;
})
.filter((v) => {
return (v.num || 0) > 0;
})
.sort((a, b) => {
return (
returnGoodsPrice(b, user, shopInfo, limitTimeDiscount) -
returnGoodsPrice(a, user, shopInfo, limitTimeDiscount)
);
});
return result;
}
export const utils = {
returnGoodsPrice,
returnGoodsGroupMap,
returnCoupType,
returnCanDikouGoods,
returnCanDikouGoodsArr,
returnCouponCanUse,
calcDiscountGoodsArrPrice,
returnCouponDiscount,
returnCouponProductDiscount,
returnCouponZhekouDiscount,
};
export default utils;