优化更新

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

@@ -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,