import { BigNumber } from "bignumber.js"; import _ from "lodash"; export interface Goods { productId: string | number; // 商品ID(唯一标识商品,用于优惠券/活动匹配,必选) skuId: string | number; // 商品规格ID(唯一标识商品规格,如颜色/尺寸) id: string | number; // 购物车ID(唯一标识购物车中的条目,如购物车项主键) product_id: string | number; // 商品ID(唯一标识商品,用于优惠券/活动匹配,必选) salePrice: number; // 商品原价(元) number: number; // 商品数量 product_type: string; // 商品类型 is_temporary?: number; // 是否临时菜(默认false) is_gift?: number; // 是否赠菜(默认false) returnNum?: number; // 退货数量(历史订单用,默认0) memberPrice: number; // 商品会员价(元,优先级:商品会员价 > 会员折扣) discountSaleAmount?: number; // 商家改价后单价(元,优先级最高) packFee?: number; // 单份打包费(元,默认0) packNumber?: number; // 堂食打包数量(默认0) skuData?: { // SKU扩展数据(可选) id: string | number; // SKU ID(唯一标识商品规格,如颜色/尺寸) memberPrice?: number; // SKU会员价 salePrice?: number; // SKU原价 }; discount_sale_amount: number; // 商家改价后单价(元,优先级最高) [property: string]: any; } export interface User { isVip: number; // 是否会员 1是会员 [property: string]: any; } export interface ShopInfo { isMemberPrice: number; // 是否开启会员价 1是开启 [property: string]: any; } export interface ThresholdFood { id: string | number; // 商品ID [property: string]: any; } export interface UseFood { id: string | number; [property: string]: any; } export interface Coupon { id: string | number; use: boolean; type: number; thresholdFoods: ThresholdFood[]; useFoods: UseFood[]; noUseRestrictions?: string; discountShare: number; // 是否与折扣优惠同享 1是同享 vipPriceShare: number; // 是否与会员优惠同享 1是同享 otherCouponShare: number; // 是否与其他优惠券同享 1是同享 fullAmount: number; // 使用门槛金额 discountRate: number; // 折扣率(满减券:折扣金额/门槛金额,折扣券:折扣率) maxDiscountAmount: number; // 最大折扣金额(满减券:折扣金额,折扣券:折扣金额) discountNum: number; // 抵扣商品数量(商品券:抵扣商品数量,折扣券:0) useRule: string; // 使用规则(price_asc:按商品单价升序,price_desc:按商品单价降序) } export interface couponDiscount { discountPrice: number; hasDiscountGoodsArr: Goods[]; } export interface selCoupon extends Coupon { discount?: couponDiscount; } export interface couponCalcParams { canDikouGoodsArr: Goods[]; coupon: Coupon; user: User; shopInfo: ShopInfo; selCoupon: selCoupon[]; goodsOrderPrice: number; //商品订单总价 isMemberPrice: number; // 是否开启会员价 1是开启 limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined; } //限时折扣配置 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; } export interface CanDikouGoodsArrArgs { canDikouGoodsArr: Goods[]; selCoupon: selCoupon[]; user: User; shopInfo: ShopInfo; limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined; } /** * 返回商品单价 * @param goods 商品 * @param user 用户信息 * @param {Object} shopInfo */ export function returnGoodsPrice( goods: Goods, user: User, 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 * 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: Goods[]) { let map: { [key: string]: Goods[] } = {}; 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: Coupon) { const couponTypes = { 1: "满减券", 2: "商品券", 3: "折扣券", 4: "第二件半价券", 5: "消费送券", 6: "买一送一券", 7: "固定价格券", 8: "免配送费券", }; return couponTypes[coupon.type as keyof typeof 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: Goods[], 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)) { // 类型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: ShopInfo, user: User, goods: Goods) { if (goods.is_time_discount) { return false; } if (shopInfo.isMemberPrice != 1 || user.isVip != 1) { return false; } if (shopInfo.isMemberPrice == 1 && user.isVip == 1) { console.log('goods', goods); if (goods.memberPrice <= 0) { return false; }else{ return true; } } return false } /** * 返回可以计算抵扣金额的商品列表 */ function returnCanCalcGoodsList(canCalcGoodsArr: Goods[], coupon: Coupon, shopInfo: ShopInfo, user: User) { return canCalcGoodsArr.filter((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: 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); }); } console.log("canCalcGoodsArr",canCalcGoodsArr); canCalcGoodsArr = returnCanCalcGoodsList( canCalcGoodsArr, coupon, shopInfo, user ); fullAmount = canCalcGoodsArr.reduce((pre, cur) => { return ( pre + returnGoodsPrice(cur, user, shopInfo, limitTimeDiscount) * cur.num ); }, 0); // 是否全部商品可用 const isDikouAll = coupon.useFoods.length === 0; // 订单可用商品列表 let canUseGoodsArr: Goods[] = []; 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(coupon.type==2){ console.log("isDikouAll",isDikouAll); console.log("canCalcGoodsArr",canCalcGoodsArr); } // 没有符合条件的商品 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 店铺信息 */ export function calcDiscountGoodsArrPrice( discountGoodsArr: Goods[], discountNum: number, user: User, shopInfo: ShopInfo, limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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: Goods[], coupon: Coupon, user: User, goodsOrderPrice: number, selCoupon: selCoupon[], 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: Goods[], coupon: Coupon, user: User, goodsOrderPrice: number, selCoupon: selCoupon[], limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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 || 0) ); }, 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 店铺信息 */ export function returnCouponProductDiscount( canDikouGoodsArr: Goods[], coupon: Coupon, user: User, shopInfo: ShopInfo, limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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 店铺信息 */ function returnCouponBuyOneGiveOneDiscount( canDikouGoodsArr: Goods[], coupon: Coupon, user: User, shopInfo: ShopInfo, limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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: Goods[] = []; 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: Goods[], coupon: Coupon, user: User, shopInfo: ShopInfo, limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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: Goods[] = []; 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: Goods[], user: User, shopInfo: ShopInfo, limitTimeDiscount?: TimeLimitDiscountConfig | null | undefined ) { 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; } export const utils = { returnGoodsPrice, returnGoodsGroupMap, returnCoupType, returnCanDikouGoods, returnCanDikouGoodsArr, returnCouponCanUse, calcDiscountGoodsArrPrice, returnCouponDiscount, returnCouponProductDiscount, returnCouponZhekouDiscount, }; export default utils;