订单结算修改,增加限时折扣
This commit is contained in:
330
utils/goods.ts
330
utils/goods.ts
@@ -234,6 +234,40 @@ 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;
|
||||
}
|
||||
|
||||
/** 订单额外费用配置 */
|
||||
export interface OrderExtraConfig {
|
||||
// merchantReduction: number; // 商家减免金额(元,默认0)
|
||||
@@ -251,6 +285,7 @@ export interface OrderExtraConfig {
|
||||
currentDinnerType: "dine-in" | "take-out" | "take-away" | "post"; // 当前就餐类型(匹配useType)
|
||||
isFreeDine?: boolean; //是否霸王餐
|
||||
freeDineConfig?: FreeDineConfig;
|
||||
limitTimeDiscount?: TimeLimitDiscountConfig; //限时折扣
|
||||
}
|
||||
|
||||
/** 订单费用汇总(修改:补充商家减免类型和明细) */
|
||||
@@ -283,10 +318,12 @@ export interface OrderCostSummary {
|
||||
config: OrderExtraConfig; // 订单额外费用配置
|
||||
//满减活动
|
||||
fullReduction: {
|
||||
usedFullReductionActivityFullAmount: number; // 计算出的满减活动的门槛金额
|
||||
usedActivity?: FullReductionActivity; // 实际使用的满减活动
|
||||
usedThreshold?: FullReductionThreshold; // 实际使用的满减阈值(多门槛中选最优)
|
||||
actualAmount: number; // 满减实际减免金额(元)
|
||||
};
|
||||
vipDiscountAmount: number; //会员折扣减免金额
|
||||
// 订单原支付金额
|
||||
orderOriginFinalPayAmount: number; //订单原金额(包含打包费+餐位费)
|
||||
}
|
||||
@@ -399,6 +436,188 @@ 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(",");
|
||||
if (limitTimeDiscount.discountPriority == "limit-time") {
|
||||
if (
|
||||
limitTimeDiscount.foodType == 1 ||
|
||||
canUseFoods.includes("" + goods[idKey])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (limitTimeDiscount.discountPriority == "vip-price") {
|
||||
if (!useVipPrice) {
|
||||
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
|
||||
) {
|
||||
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) {
|
||||
//使用会员价
|
||||
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
|
||||
): 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;
|
||||
console.log("amount", amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选最优满减活动(对齐后端逻辑:状态→时间→周期→时段→就餐类型→排序→修改时间)
|
||||
* @param activities 后端返回的满减活动列表
|
||||
@@ -424,11 +643,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);
|
||||
@@ -665,11 +889,12 @@ 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;
|
||||
|
||||
//如果是增菜价格为0
|
||||
if (goods.is_gift) {
|
||||
@@ -680,6 +905,7 @@ export function calcSingleGoodsRealPrice(
|
||||
if (goods.discountSaleAmount && goods.discountSaleAmount > 0) {
|
||||
return truncateToTwoDecimals(goods.discountSaleAmount);
|
||||
}
|
||||
console.log("calcSingleGoodsRealPrice:goods", goods);
|
||||
|
||||
// 2. 优先级2:会员价(含会员折扣率,SKU会员价优先)
|
||||
const memberPrice = new BigNumber(
|
||||
@@ -687,12 +913,35 @@ 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();
|
||||
}
|
||||
//限时折扣优先或者会员价优先但是不是会员或者未开启会员价格时限时折扣优先
|
||||
if (
|
||||
activity.discountPriority == "limit-time" ||
|
||||
(activity.discountPriority == "vip-price" && !isMember)
|
||||
) {
|
||||
//限时折扣优先
|
||||
return truncateToTwoDecimals(
|
||||
new BigNumber(goods.salePrice)
|
||||
.multipliedBy(activity.discountRate / 100)
|
||||
.toNumber()
|
||||
);
|
||||
}
|
||||
if (activity.discountPriority == "vip-price" && isMember) {
|
||||
return memberPrice.toNumber();
|
||||
}
|
||||
|
||||
// 处理活动与会员的同享/不同享逻辑
|
||||
if (activity.vipPriceShare) {
|
||||
@@ -751,26 +1000,17 @@ 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 })
|
||||
);
|
||||
const realPrice = new BigNumber(calcSingleGoodsRealPrice(goods, config));
|
||||
total = total.plus(realPrice.multipliedBy(availableNum));
|
||||
}
|
||||
|
||||
@@ -967,14 +1207,13 @@ export function calcTotalPackFee(
|
||||
for (const goods of goodsList) {
|
||||
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
|
||||
if (dinnerType === "take-out") {
|
||||
packNum = availableNum;
|
||||
}
|
||||
if (goods.product_type === GoodsType.WEIGHT) {
|
||||
packNum = Math.min(packNum, 1);
|
||||
@@ -1066,7 +1305,12 @@ export function calculateOrderCostSummary(
|
||||
currentTime: Date = new Date()
|
||||
): OrderCostSummary {
|
||||
//是否使用霸王餐,霸王餐配置
|
||||
const { isFreeDine, freeDineConfig } = config;
|
||||
const {
|
||||
isFreeDine,
|
||||
freeDineConfig,
|
||||
limitTimeDiscount,
|
||||
fullReductionActivities,
|
||||
} = config;
|
||||
|
||||
// ------------------------------ 1. 基础费用计算 ------------------------------
|
||||
const goodsOriginalAmount = calcGoodsOriginalAmount(goodsList); // 商品原价总和
|
||||
@@ -1076,13 +1320,17 @@ export function calculateOrderCostSummary(
|
||||
{
|
||||
isMember: config.isMember,
|
||||
memberDiscountRate: config.memberDiscountRate,
|
||||
},
|
||||
activities
|
||||
limitTimeDiscount: config.limitTimeDiscount,
|
||||
}
|
||||
);
|
||||
const goodsDiscountAmount = calcGoodsDiscountAmount(
|
||||
goodsOriginalAmount,
|
||||
goodsRealAmount
|
||||
); // 商品折扣金额
|
||||
|
||||
//会员折扣
|
||||
let vipDiscountAmount = 0;
|
||||
|
||||
const newUserDiscount = config.newUserDiscount || 0; // 新客立减
|
||||
|
||||
// ------------------------------ 2. 满减活动计算(核心步骤) ------------------------------
|
||||
@@ -1104,7 +1352,14 @@ export function calculateOrderCostSummary(
|
||||
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)
|
||||
@@ -1114,10 +1369,19 @@ export function calculateOrderCostSummary(
|
||||
baseAfterNewUserDiscount > 0 ? baseAfterNewUserDiscount : 0;
|
||||
|
||||
// 2.3 选择最优满减阈值(多门槛场景)
|
||||
let usedFullReductionActivityFullAmount = 0;
|
||||
if (usedFullReductionActivity) {
|
||||
//计算当前满减活动的门槛金额
|
||||
usedFullReductionActivityFullAmount = calcFullReductionActivityFullAmount(
|
||||
goodsList,
|
||||
usedFullReductionActivity,
|
||||
config.limitTimeDiscount,
|
||||
config.isMember
|
||||
);
|
||||
|
||||
usedFullReductionThreshold = selectOptimalThreshold(
|
||||
usedFullReductionActivity.thresholds,
|
||||
baseAfterNewUserDiscount,
|
||||
usedFullReductionActivityFullAmount,
|
||||
goodsOriginalAmount,
|
||||
goodsRealAmount,
|
||||
usedFullReductionActivity.discountShare || 0 // 与限时折扣同享规则
|
||||
@@ -1182,27 +1446,23 @@ 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");
|
||||
pointDeductionAmount = 0;
|
||||
usedPoints = 0;
|
||||
}
|
||||
|
||||
//使用霸王餐
|
||||
if (isFreeDine && freeDineConfig && freeDineConfig.enable) {
|
||||
console.log("使用霸王餐");
|
||||
fullReductionAmount=0;
|
||||
fullReductionAmount = 0;
|
||||
//不与优惠券同享
|
||||
if (!freeDineConfig.withCoupon) {
|
||||
couponDeductionAmount = 0;
|
||||
@@ -1319,10 +1579,12 @@ export function calculateOrderCostSummary(
|
||||
scoreMaxMoney,
|
||||
// 满减活动明细(后端字段)
|
||||
fullReduction: {
|
||||
usedFullReductionActivityFullAmount: usedFullReductionActivityFullAmount,
|
||||
usedActivity: usedFullReductionActivity,
|
||||
usedThreshold: usedFullReductionThreshold,
|
||||
actualAmount: truncateToTwoDecimals(fullReductionAmount),
|
||||
},
|
||||
vipDiscountAmount: vipDiscountAmount, //会员折扣减免金额
|
||||
merchantReduction: {
|
||||
type: merchantReductionConfig.type,
|
||||
originalConfig: merchantReductionConfig,
|
||||
|
||||
Reference in New Issue
Block a user