更新计算方法,添加新图标

This commit is contained in:
2025-10-20 17:19:36 +08:00
parent dc0cd2076c
commit a2e1300626
3 changed files with 235 additions and 118 deletions

View File

@@ -223,6 +223,17 @@ export interface MerchantReductionConfig {
fixedAmount?: number; // 固定减免金额(元,仅 FIXED_AMOUNT 生效≥0
discountRate?: number; // 折扣率(%,仅 DISCOUNT_RATE 生效0-100如 90 代表 9 折)
}
/**商家霸王餐配置 */
export interface FreeDineConfig {
enable: boolean; //是否开启
rechargeThreshold: number; //订单满多少元可以使用
rechargeTimes: number; //充值多少倍免单
withCoupon: boolean; //与优惠券同享
withPoints: boolean; //与积分同享
useType?: string[]; //使用类型 dine-in店内 takeout 自取 post快递takeaway外卖
useShopType?: string; //all 全部 part部分
shopIdList?: number[]; //可用门店id
}
/** 订单额外费用配置 */
export interface OrderExtraConfig {
// merchantReduction: number; // 商家减免金额默认0
@@ -238,14 +249,15 @@ export interface OrderExtraConfig {
newUserDiscount?: number; // 新用户减免金额默认0
fullReductionActivities: FullReductionActivity[]; // 当前店铺的满减活动列表(后端返回结构)
currentDinnerType: "dine-in" | "take-out" | "take-away" | "post"; // 当前就餐类型匹配useType
isFreeDine?: boolean; //是否霸王餐
freeDineConfig?: FreeDineConfig;
}
/** 订单费用汇总(修改:补充商家减免类型和明细) */
export interface OrderCostSummary {
// 商品总件数
goodsTotal: number;
totalDiscountAmount: number,
totalDiscountAmount: number;
goodsRealAmount: number; // 商品真实原价总和
goodsOriginalAmount: number; // 商品原价总和
goodsDiscountAmount: number; // 商品折扣金额
@@ -275,9 +287,10 @@ export interface OrderCostSummary {
usedThreshold?: FullReductionThreshold; // 实际使用的满减阈值(多门槛中选最优)
actualAmount: number; // 满减实际减免金额(元)
};
// 订单原支付金额
orderOriginFinalPayAmount: number; //订单原金额(包含打包费+餐位费)
}
/** 满减活动阈值单条满减规则满X减Y- 对应 MkDiscountThresholdInsertGroupDefaultGroup */
export interface FullReductionThreshold {
activityId?: number; // 关联满减活动ID
@@ -308,25 +321,15 @@ export interface FullReductionActivity {
isDel?: boolean; // 是否删除0=否1=是后端字段isDel默认false
}
// ============================ 扩展:订单配置与费用汇总(加入后端满减类型) ============================
/** 扩展订单额外配置:使用后端满减活动类型 */
export interface OrderExtraConfig {
// ... 原有字段不变 ...
fullReductionActivities: FullReductionActivity[]; // 当前店铺的满减活动列表(后端返回结构)
currentDinnerType: "dine-in" | "take-out" | "take-away" | "post"; // 当前就餐类型匹配useType
}
// 辅助枚举星期映射用于useDays校验
const WEEKDAY_MAP = {
"周一": 1,
"周二": 2,
"周三": 3,
"周四": 4,
"周五": 5,
"周六": 6,
"周日": 0 // JS中getDay()返回0=周日
周一: 1,
周二: 2,
周三: 3,
周四: 4,
周五: 5,
周六: 6,
周日: 0, // JS中getDay()返回0=周日
};
/**
@@ -335,7 +338,10 @@ const WEEKDAY_MAP = {
* @param currentTime 当前时间
* @returns 是否在时段内
*/
function isInDailyTimeRange(activity: FullReductionActivity, currentTime: Date): boolean {
function isInDailyTimeRange(
activity: FullReductionActivity,
currentTime: Date
): boolean {
// 全时段无需校验
if (activity.useTimeType === "all") return true;
// 无时段配置则不通过
@@ -365,11 +371,16 @@ function isInDailyTimeRange(activity: FullReductionActivity, currentTime: Date):
* @param currentTime 当前时间
* @returns 是否在周期内
*/
function isInWeeklyCycle(activity: FullReductionActivity, currentTime: Date): boolean {
function isInWeeklyCycle(
activity: FullReductionActivity,
currentTime: Date
): boolean {
// 无周期配置则不通过
if (!activity.useDays) return false;
const currentWeekday = currentTime.getDay(); // 0=周日1=周一...6=周六
const allowedWeekdays = activity.useDays.split(",").map(day => WEEKDAY_MAP[day as keyof typeof WEEKDAY_MAP]);
const allowedWeekdays = activity.useDays
.split(",")
.map((day) => WEEKDAY_MAP[day as keyof typeof WEEKDAY_MAP]);
return allowedWeekdays.includes(currentWeekday);
}
@@ -379,11 +390,14 @@ function isInWeeklyCycle(activity: FullReductionActivity, currentTime: Date): bo
* @param currentDinnerType 当前就餐类型
* @returns 是否匹配
*/
function isDinnerTypeMatch(activity: FullReductionActivity, currentDinnerType: string): boolean {
function isDinnerTypeMatch(
activity: FullReductionActivity,
currentDinnerType: string
): boolean {
if (!activity.useType) return false;
const allowedTypes = activity.useType.split(",");
//满减活动的就餐类型和当前券类型字段值不一样暂时返回true
return true
return true;
}
/**
* 筛选最优满减活动(对齐后端逻辑:状态→时间→周期→时段→就餐类型→排序→修改时间)
@@ -400,22 +414,20 @@ export function filterOptimalFullReductionActivity(
currentTime: Date = new Date()
): FullReductionActivity | undefined {
if (!activities || !activities.length) return undefined;
console.log("原始满减活动列表:", activities);
// 第一步:基础筛选(未删除+当前店铺+活动进行中+就餐类型匹配)
const baseEligible = activities.filter(activity => {
const baseEligible = activities.filter((activity) => {
return (
activity.isDel !== true && // 未删除
activity.shopId === currentShopId && // 当前店铺
activity.status === 2 && // 状态=2进行中
// activity.shopId === currentShopId && // 当前店铺
// activity.status === 2 && // 状态=2进行中
isDinnerTypeMatch(activity, currentDinnerType) && // 就餐类型匹配
activity.thresholds?.length // 至少有一个满减阈值
);
});
if (!baseEligible.length) return undefined;
// 第二步:时间筛选(有效期内+周期内+时段内)
const timeEligible = baseEligible.filter(activity => {
const timeEligible = baseEligible.filter((activity) => {
// 1. 校验有效期validStartTime ~ validEndTime
if (!activity.validStartTime || !activity.validEndTime) return false;
const startDate = new Date(activity.validStartTime);
@@ -485,6 +497,13 @@ export function isTemporaryGoods(goods: BaseCartItem): boolean {
export function isGiftGoods(goods: BaseCartItem): boolean {
return !!goods.is_gift;
}
/**
* 判断可用类型是否可用
*/
export function useTypeCanUse(useType: string[]) {
const arr = ["all", "dine-in", "take-out", "take-away", "post"];
return useType.some((item) => arr.includes(item));
}
/**
* 计算单个商品的会员价优先级SKU会员价 > 商品会员价 > 会员折扣率)
@@ -713,11 +732,8 @@ export function calcGoodsOriginalAmount(goodsList: BaseCartItem[]): number {
basePrice = new BigNumber(goods?.discountSaleAmount ?? 0);
} else if (goods.is_gift) {
basePrice = new BigNumber(0);
}
else {
basePrice = new BigNumber(
goods.skuData?.salePrice ?? goods.salePrice
); // SKU原价优先
} else {
basePrice = new BigNumber(goods.skuData?.salePrice ?? goods.salePrice); // SKU原价优先
}
total = total.plus(basePrice.multipliedBy(availableNum));
@@ -796,33 +812,47 @@ export function selectOptimalThreshold(
if (!thresholds.length) return undefined;
// 第一步确定满减门槛基数根据discountShare规则
const thresholdBase = discountShare === 1
? new BigNumber(goodsRealAmount) // 与限时折扣同享→用折扣后金额
: new BigNumber(goodsOriginalAmount); // 不同享→用原价
const thresholdBase = baseAmount;
// 第二步:筛选「满金额≤基数」且「减免金额>0」的有效阈值
const validThresholds = thresholds.filter(threshold => {
const validThresholds = thresholds.filter((threshold) => {
const fullAmount = new BigNumber(threshold.fullAmount || 0);
const discountAmount = new BigNumber(threshold.discountAmount || 0);
return fullAmount.isLessThanOrEqualTo(thresholdBase) && discountAmount.isGreaterThan(0);
return (
fullAmount.isLessThanOrEqualTo(thresholdBase) &&
discountAmount.isGreaterThan(0)
);
});
if (!validThresholds.length) return undefined;
// 第三步选择最优阈值优先级1.满金额最小 → 2.减免金额最大)
return 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);
// 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
})[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) => {
// 处理空值默认抵扣金额为0
const maxDiscount = new BigNumber(maxItem?.discountAmount || 0);
const currentDiscount = new BigNumber(currentItem?.discountAmount || 0);
// 比较当前项和已存最大项的抵扣金额,保留更大的
return currentDiscount.gt(maxDiscount) ? currentItem : maxItem;
},
validThresholds[0] || null
); // 初始值为数组第一项若数组为空则返回null
console.log("maxDiscountThreshold", maxDiscountThreshold);
return maxDiscountThreshold;
}
/**
@@ -835,7 +865,7 @@ export function selectOptimalThreshold(
export function calcFullReductionAmount(
baseAmount: number,
optimalActivity?: FullReductionActivity,
optimalThreshold?: FullReductionThreshold,
optimalThreshold?: FullReductionThreshold
): number {
if (!optimalActivity || !optimalThreshold) return 0;
@@ -854,7 +884,6 @@ export function calcFullReductionAmount(
return truncateToTwoDecimals(actualReduction.toNumber());
}
// ------------------------------ 策略辅助函数 ------------------------------
/**
* 根据优惠券useShops列表判断门店是否匹配适配BaseCoupon的useShops字段
@@ -869,8 +898,6 @@ function isStoreMatchByList(
return useShops.includes(currentStoreId);
}
/**
* 计算优惠券抵扣金额处理互斥逻辑选择最优优惠券按商品ID排除新增细分统计
* @param backendCoupons 后端优惠券列表
@@ -953,12 +980,15 @@ 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));
availableNum = Math.min(availableNum, packNumber);
if (availableNum === 0) continue;
// 计算单个商品打包数量外卖全打包堂食按配置称重商品≤1
let packNum = availableNum;
let packNum = Math.min(availableNum, packNumber);
if (dinnerType === "take-out") {
packNum = availableNum
}
if (goods.product_type === GoodsType.WEIGHT) {
packNum = Math.min(packNum, 1);
}
@@ -997,6 +1027,7 @@ export function calcPointDeduction(
deductionAmount: number;
usedPoints: number;
} {
console.log("calcPointDeduction", userPoints, rule, maxDeductionLimit);
if (rule.pointsPerYuan <= 0 || userPoints <= 0) {
return { deductionAmount: 0, usedPoints: 0 };
}
@@ -1047,14 +1078,24 @@ export function calculateOrderCostSummary(
cartOrder: Record<string, number> = {},
currentTime: Date = new Date()
): OrderCostSummary {
//是否使用霸王餐,霸王餐配置
const { isFreeDine, freeDineConfig } = config;
// ------------------------------ 1. 基础费用计算 ------------------------------
const goodsOriginalAmount = calcGoodsOriginalAmount(goodsList); // 商品原价总和
const goodsRealAmount = calcGoodsRealAmount( // 商品折扣后总和
const goodsRealAmount = calcGoodsRealAmount(
// 商品折扣后总和
goodsList,
{ isMember: config.isMember, memberDiscountRate: config.memberDiscountRate },
{
isMember: config.isMember,
memberDiscountRate: config.memberDiscountRate,
},
activities
);
const goodsDiscountAmount = calcGoodsDiscountAmount(goodsOriginalAmount, goodsRealAmount); // 商品折扣金额
const goodsDiscountAmount = calcGoodsDiscountAmount(
goodsOriginalAmount,
goodsRealAmount
); // 商品折扣金额
const newUserDiscount = config.newUserDiscount || 0; // 新客立减
// ------------------------------ 2. 满减活动计算(核心步骤) ------------------------------
@@ -1069,12 +1110,21 @@ 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 计算满减基数(先扣新客立减)
const baseAfterNewUserDiscount = new BigNumber(goodsRealAmount)
let baseAfterNewUserDiscount = new BigNumber(goodsRealAmount)
.minus(newUserDiscount)
.isGreaterThan(0)
? new BigNumber(goodsRealAmount).minus(newUserDiscount).toNumber()
: 0;
.plus(packFee)
.plus(seatFee)
.plus(additionalFee)
.toNumber();
baseAfterNewUserDiscount =
baseAfterNewUserDiscount > 0 ? baseAfterNewUserDiscount : 0;
// 2.3 选择最优满减阈值(多门槛场景)
if (usedFullReductionActivity) {
@@ -1090,10 +1140,9 @@ export function calculateOrderCostSummary(
fullReductionAmount = calcFullReductionAmount(
baseAfterNewUserDiscount,
usedFullReductionActivity,
usedFullReductionThreshold,
usedFullReductionThreshold
);
}
// ------------------------------ 3. 优惠券抵扣(适配满减同享规则) ------------------------------
let couponDeductionAmount = 0;
let productCouponDeduction = 0;
@@ -1101,57 +1150,85 @@ export function calculateOrderCostSummary(
let usedCoupon: Coupon | undefined;
let excludedProductIds: string[] = [];
const couponResult = calcCouponDeduction(
// 原有优惠券计算函数
backendCoupons,
goodsList,
{
currentStoreId: config.currentStoreId,
isMember: config.isMember,
memberDiscountRate: config.memberDiscountRate,
activities,
cartOrder,
dinnerType,
currentTime,
}
);
couponDeductionAmount = couponResult.deductionAmount;
productCouponDeduction = couponResult.productCouponDeduction;
fullCouponDeduction = couponResult.fullCouponDeduction;
usedCoupon = couponResult.usedCoupon;
excludedProductIds = couponResult.excludedProductIds;
// 若满减与优惠券同享couponShare=1才计算优惠券否则优惠券抵扣为0
if (usedFullReductionActivity?.couponShare === 1) {
const couponResult = calcCouponDeduction( // 原有优惠券计算函数
backendCoupons,
goodsList,
{
currentStoreId: config.currentStoreId,
isMember: config.isMember,
memberDiscountRate: config.memberDiscountRate,
activities,
cartOrder,
dinnerType,
currentTime
}
);
couponDeductionAmount = couponResult.deductionAmount;
productCouponDeduction = couponResult.productCouponDeduction;
fullCouponDeduction = couponResult.fullCouponDeduction;
usedCoupon = couponResult.usedCoupon;
excludedProductIds = couponResult.excludedProductIds;
if (usedFullReductionActivity && !usedFullReductionActivity.couponShare) {
couponDeductionAmount = 0;
productCouponDeduction = 0;
fullCouponDeduction = 0;
usedCoupon = undefined;
excludedProductIds = [];
}
// ------------------------------ 4. 积分抵扣(适配满减同享规则) ------------------------------
let pointDeductionAmount = 0;
let usedPoints = 0;
// 计算积分抵扣基数(商品折扣后 - 新客立减 - 满减 - 优惠券)
const maxPointDeductionLimit = new BigNumber(goodsRealAmount)
// 计算积分抵扣基数(商品折扣后 - 新客立减 - 满减 - 优惠券 + 餐位费 + 打包费 + 附加费
let maxPointDeductionLimit = new BigNumber(goodsRealAmount)
.minus(newUserDiscount)
.minus(fullReductionAmount)
.minus(couponDeductionAmount)
.isGreaterThan(0)
? new BigNumber(goodsRealAmount).minus(newUserDiscount).minus(fullReductionAmount).minus(couponDeductionAmount).toNumber()
: 0;
.plus(seatFee)
.plus(packFee)
.plus(additionalFee)
.toNumber();
maxPointDeductionLimit =
maxPointDeductionLimit > 0 ? maxPointDeductionLimit : 0;
// 若满减与积分同享pointsShare=1才计算积分否则积分抵扣为0
if ((usedFullReductionActivity?.pointsShare === 1) && maxPointDeductionLimit > 0) {
const pointResult = calcPointDeduction(
config.userPoints,
config.pointDeductionRule,
maxPointDeductionLimit
);
pointDeductionAmount = pointResult.deductionAmount;
usedPoints = pointResult.usedPoints;
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;
}
// ------------------------------ 5. 其他费用计算(原有逻辑不变) ------------------------------
const packFee = calcTotalPackFee(goodsList, dinnerType); // 打包费
let seatFee = calcSeatFee(config.seatFeeConfig); // 餐位费
seatFee = dinnerType === "dine-in" ? seatFee : 0; // 外卖不收餐位费
const additionalFee = Math.max(0, config.additionalFee); // 附加费
//使用霸王餐
if (isFreeDine && freeDineConfig && freeDineConfig.enable) {
console.log("使用霸王餐");
//不与优惠券同享
if (!freeDineConfig.withCoupon) {
couponDeductionAmount = 0;
productCouponDeduction = 0;
fullCouponDeduction = 0;
usedCoupon = undefined;
excludedProductIds = [];
}
//不与积分同享
if (!freeDineConfig.withPoints) {
pointDeductionAmount = 0;
usedPoints = 0;
}
}
// 商家减免计算(原有逻辑不变)
const merchantReductionConfig = config.merchantReduction;
@@ -1164,7 +1241,14 @@ export function calculateOrderCostSummary(
.plus(seatFee)
.plus(packFee)
.isGreaterThan(0)
? new BigNumber(goodsRealAmount).minus(newUserDiscount).minus(fullReductionAmount).minus(couponDeductionAmount).minus(pointDeductionAmount).plus(seatFee).plus(packFee).toNumber()
? new BigNumber(goodsRealAmount)
.minus(newUserDiscount)
.minus(fullReductionAmount)
.minus(couponDeductionAmount)
.minus(pointDeductionAmount)
.plus(seatFee)
.plus(packFee)
.toNumber()
: 0;
switch (merchantReductionConfig.type) {
@@ -1175,11 +1259,17 @@ export function calculateOrderCostSummary(
);
break;
case MerchantReductionType.DISCOUNT_RATE:
const validRate = Math.max(0, Math.min(100, merchantReductionConfig.discountRate || 0)) / 100;
merchantReductionActualAmount = maxMerchantReductionLimit * (1 - validRate);
const validRate =
Math.max(0, Math.min(100, merchantReductionConfig.discountRate || 0)) /
100;
merchantReductionActualAmount =
maxMerchantReductionLimit * (1 - validRate);
break;
}
merchantReductionActualAmount = Math.max(0, truncateToTwoDecimals(merchantReductionActualAmount));
merchantReductionActualAmount = Math.max(
0,
truncateToTwoDecimals(merchantReductionActualAmount)
);
// ------------------------------ 6. 最终实付金额计算 ------------------------------
const finalPayAmountBn = new BigNumber(goodsRealAmount)
@@ -1191,7 +1281,17 @@ export function calculateOrderCostSummary(
.plus(seatFee)
.plus(packFee)
.plus(additionalFee);
const finalPayAmount = Math.max(0, truncateToTwoDecimals(finalPayAmountBn.toNumber()));
let finalPayAmount = Math.max(
0,
truncateToTwoDecimals(finalPayAmountBn.toNumber())
);
// ------------------------------ 使用霸王餐计算 ------------------------------
let orderOriginFinalPayAmount = finalPayAmount;
if (isFreeDine && freeDineConfig && freeDineConfig.enable) {
finalPayAmount = BigNumber(finalPayAmount)
.times(freeDineConfig.rechargeTimes)
.toNumber();
}
// ------------------------------ 7. 总优惠金额计算 ------------------------------
const totalDiscountAmount = truncateToTwoDecimals(
@@ -1207,12 +1307,14 @@ export function calculateOrderCostSummary(
const scoreMaxMoney = new BigNumber(finalPayAmount)
.plus(pointDeductionAmount)
.minus(merchantReductionActualAmount)
.toNumber()
.toNumber();
// ------------------------------ 8. 返回完整结果 ------------------------------
return {
goodsTotal: goodsList.reduce((sum, g) => sum + Math.max(0, g.number - (g.returnNum || 0)), 0),
goodsTotal: goodsList.reduce(
(sum, g) => sum + Math.max(0, g.number - (g.returnNum || 0)),
0
),
goodsRealAmount,
goodsOriginalAmount,
goodsDiscountAmount,
@@ -1223,18 +1325,20 @@ export function calculateOrderCostSummary(
seatFee,
packFee,
totalDiscountAmount,
//最终支付原金额
orderOriginFinalPayAmount,
//积分最大可抵扣金额
scoreMaxMoney,
// 满减活动明细(后端字段)
fullReduction: {
usedActivity: usedFullReductionActivity,
usedThreshold: usedFullReductionThreshold,
actualAmount: truncateToTwoDecimals(fullReductionAmount)
actualAmount: truncateToTwoDecimals(fullReductionAmount),
},
merchantReduction: {
type: merchantReductionConfig.type,
originalConfig: merchantReductionConfig,
actualAmount: merchantReductionActualAmount
actualAmount: merchantReductionActualAmount,
},
additionalFee,
finalPayAmount,
@@ -1242,7 +1346,7 @@ export function calculateOrderCostSummary(
pointUsed: usedPoints,
newUserDiscount,
dinnerType,
config
config,
};
}