import { BigNumber } from "bignumber.js"; import _ from "lodash"; import { ShopInfo, couponCalcParams, BaseCartItem, TimeLimitDiscountConfig, CanDikouGoodsArrArgs, Coupon, ShopUserInfo, GoodsType, BackendCoupon, ExchangeCalculationResult, PointDeductionRule, OrderCostSummary, } from "./types"; /** * 返回商品单价 * @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) { //优先使用 if (goods.isTimeDiscount || goods.is_time_discount) { 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); if (canUseNum <= 0 || v.is_temporary || v.is_gift) { return false; } return true; }); // 过滤掉数量<=0的商品,赠菜,临时菜 return arr; } /** * 返回商品是否享用了会员价/会员折扣 * @param {*} goods */ function returnGoodsIsUseVipPrice( shopInfo: ShopInfo, user: ShopUserInfo, goods: BaseCartItem ) { 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; } else { return true; } } return false; } /** * 返回可以计算抵扣金额的商品列表 */ function returnCanCalcGoodsList( canCalcGoodsArr: BaseCartItem[], coupon: Coupon, shopInfo: ShopInfo, user: ShopUserInfo ) { 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: 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 ); console.log("canCalcGoodsArr"); console.log(canCalcGoodsArr); 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 = []; 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; 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 = []; //抵扣全部商品 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 = undefined; //符合买一送一条件的商品 const canUseGoods = canDikouGoodsArr.filter((v) => (v.num || 0) >= 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: 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 = undefined; //符合条件的商品 const canUseGoods = canDikouGoodsArr.filter((v) => (v.num || 0) >= 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: 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) => { return !v.is_temporary && !v.is_gift; }) .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;