分销问题修复,订单问题修复

This commit is contained in:
2025-10-30 17:11:18 +08:00
parent 6283d168e9
commit 09fc28de06
29 changed files with 2652 additions and 533 deletions

View File

@@ -12,41 +12,41 @@ export function returnGoodsPrice(goods, user, shopInfo, limitTimeDiscount) {
if (!goods) {
return 0;
}
//是否可以使用会员价
const canUseVipPrice =
user && user.isVip && user.isMemberPrice && goods.memberPrice * 1 > 0;
// 商家改价
if (goods.discount_sale_amount * 1 > 0) {
return goods.salePrice;
}
// 限时折扣
if (limitTimeDiscount && limitTimeDiscount.id) {
if (
limitTimeDiscount.foodType == 1 &&
limitTimeDiscount.discountPriority == "limit-time"
) {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
const canUseFoods = limitTimeDiscount.foods.split(",");
if (
limitTimeDiscount.foodType == 2 &&
limitTimeDiscount.discountPriority == "limit-time" &&
canUseFoods.includes(`${goods.productId}`)
) {
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" &&
!canUseVipPrice
) {
if (canUseVipPrice) {
return goods.memberPrice;
} else {
return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber();
}
}
}
if (shopInfo && !shopInfo.isMemberPrice) {
return goods.salePrice;
}
if (
user.isVip &&
goods.memberPrice * 1 <= goods.salePrice * 1 &&
goods.memberPrice * 1 > 0
) {
if (canUseVipPrice) {
return goods.memberPrice;
}
return goods.salePrice;
@@ -124,12 +124,12 @@ export function returnCanDikouGoodsArr(args) {
return v;
})
.filter((v) => {
const canUseNum=v.num-(v.returnNum||0)
if(canUseNum <= 0||v.is_temporary==1||v.is_gift==1){
return false
const canUseNum = v.num - (v.returnNum || 0);
if (canUseNum <= 0 || v.is_temporary == 1 || v.is_gift == 1) {
return false;
}
return true
return true;
}); // 过滤掉数量<=0的商品,赠菜,临时菜
return arr;
@@ -139,20 +139,19 @@ export function returnCanDikouGoodsArr(args) {
* 返回商品是否享用了会员价/会员折扣
* @param {*} goods
*/
function returnGoodsIsUseVipPrice(shopInfo,user,goods) {
if(goods.is_time_discount){
return false
function returnGoodsIsUseVipPrice(shopInfo, user, goods) {
if (goods.is_time_discount) {
return false;
}
if(shopInfo.isMemberPrice!=1||user.isVip!=1){
return false
if (shopInfo.isMemberPrice != 1 || user.isVip != 1) {
return false;
}
if(shopInfo.isMemberPrice==1&&user.isVip==1){
if(goods.memberPrice<=0){
return false
if (shopInfo.isMemberPrice == 1 && user.isVip == 1) {
if (goods.memberPrice <= 0) {
return false;
}
return true
return true;
}
}
/**
@@ -160,10 +159,15 @@ function returnGoodsIsUseVipPrice(shopInfo,user,goods) {
*/
function returnCanCalcGoodsList(canCalcGoodsArr, coupon, shopInfo, user) {
return canCalcGoodsArr.filter((goods) => {
if (!coupon.discountShare && goods.is_time_discount) {
console.log("goods");
console.log(goods);
if (!coupon.discountShare && (goods.is_time_discount||goods.isTimeDiscount)) {
return false;
}
if(!coupon.vipPriceShare&& returnGoodsIsUseVipPrice(shopInfo,user,goods)){
if (
!coupon.vipPriceShare &&
returnGoodsIsUseVipPrice(shopInfo, user, goods)
) {
return false;
}
return true;
@@ -240,6 +244,8 @@ export function returnCouponCanUse(args) {
shopInfo,
user
);
console.log("canCalcGoodsArr");
console.log(canCalcGoodsArr);
fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return (
pre + returnGoodsPrice(cur, user, shopInfo, limitTimeDiscount) * cur.num
@@ -291,8 +297,8 @@ export function returnCouponCanUse(args) {
}
// 商品兑换券,第二件半价和买一送一判断是否有可用商品
if ([2, 4, 5].includes(coupon.type)) {
// 没有符合条件的商品
if (isDikouAll && canDikouGoodsArr.length === 0) {
// 没有符合条件的商品
if (isDikouAll && canDikouGoodsArr.length === 0) {
return {
canUse: false,
reason: "没有符合条件的商品",
@@ -304,23 +310,20 @@ export function returnCouponCanUse(args) {
reason: "没有符合条件的商品",
};
}
if (coupon.type == 2 ) {
if(canCalcGoodsArr.length<=0){
if (coupon.type == 2) {
if (canCalcGoodsArr.length <= 0) {
return {
canUse: false,
reason: "没有符合计算门槛条件的商品",
};
}
if(fullAmount < coupon.fullAmount){
if (fullAmount < coupon.fullAmount) {
return {
canUse: false,
reason: `${coupon.fullAmount}元可用,当前可参与金额${fullAmount}`,
};
}
}
}
//商品兑换券是否达到门槛金额
if (coupon.type == 2 && goodsOrderPrice < coupon.fullAmount) {

View File

@@ -455,18 +455,23 @@ export function returnCanUseLimitTimeDiscount(
return false;
}
const canUseFoods = (limitTimeDiscount.foods || "").split(",");
const goodsCanUse =
limitTimeDiscount.foodType == 1 || canUseFoods.includes("" + goods[idKey]);
if (!goodsCanUse) {
return false;
}
if (limitTimeDiscount.discountPriority == "limit-time") {
if (
limitTimeDiscount.foodType == 1 ||
canUseFoods.includes("" + goods[idKey])
) {
return true;
}
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;
@@ -512,7 +517,7 @@ function returnLimitPrice(
}
if (limitTimeDiscount.discountPriority == "vip-price") {
//会员价优先
if (useVipPrice) {
if (useVipPrice && goods.memberPrice && goods.memberPrice * 1 > 0) {
//使用会员价
return returnMemberPrice(useVipPrice, goods);
} else {
@@ -598,7 +603,9 @@ export function calcFullReductionActivityFullAmount(
goodsList: BaseCartItem[],
fullReductionActivitie: FullReductionActivity | undefined,
limitTimeDiscount: TimeLimitDiscountConfig | null | undefined,
useVipPrice: boolean
useVipPrice: boolean,
seatFee: number,
packFee: number
): number {
if (!fullReductionActivitie) {
return 0;
@@ -621,7 +628,7 @@ export function calcFullReductionActivityFullAmount(
amount += calcPrice * availableNum;
}
}
return amount;
return amount + seatFee + packFee;
console.log("amount", amount);
}
@@ -902,6 +909,7 @@ export function calcSingleGoodsRealPrice(
>
): number {
const { isMember, memberDiscountRate, limitTimeDiscount: activity } = config;
console.log("activity", activity);
//如果是增菜价格为0
if (goods.is_gift) {
@@ -912,7 +920,6 @@ 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(
@@ -934,15 +941,18 @@ export function calcSingleGoodsRealPrice(
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) ||
(activity.discountPriority == "vip-price" && isMember && !goods.memberPrice)
) {
//限时折扣优先
return truncateToTwoDecimals(
new BigNumber(goods.salePrice)
.multipliedBy(activity.discountRate / 100)
.times(activity.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber()
);
}
@@ -1017,6 +1027,7 @@ export function calcGoodsRealAmount(
for (const goods of goodsList) {
const availableNum = Math.max(0, goods.number - (goods.returnNum || 0));
if (availableNum <= 0) continue;
console.log("goods", goods);
const realPrice = new BigNumber(calcSingleGoodsRealPrice(goods, config));
total = total.plus(realPrice.multipliedBy(availableNum));
}
@@ -1065,11 +1076,15 @@ 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;
@@ -1298,9 +1313,11 @@ function calcVipDiscountAmount(
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)
.multipliedBy((100 - (shopUserInfo.discount || 0)) / 100)
.times((100 - (shopUserInfo.discount || 0)) / 100)
.decimalPlaces(2, BigNumber.ROUND_UP)
.toNumber()
);
@@ -1348,26 +1365,32 @@ export function calculateOrderCostSummary(
limitTimeDiscount: config.limitTimeDiscount,
}
);
// 会员折扣减免
const vipDiscountAmount = calcVipDiscountAmount(
goodsRealAmount,
shopUserInfo
); // 会员折扣减免金额
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,
@@ -1376,12 +1399,6 @@ 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(
limitTimeDiscount &&
@@ -1400,14 +1417,15 @@ export function calculateOrderCostSummary(
baseAfterNewUserDiscount > 0 ? baseAfterNewUserDiscount : 0;
// 2.3 选择最优满减阈值(多门槛场景)
let usedFullReductionActivityFullAmount = 0;
if (usedFullReductionActivity) {
//计算当前满减活动的门槛金额
usedFullReductionActivityFullAmount = calcFullReductionActivityFullAmount(
goodsList,
usedFullReductionActivity,
config.limitTimeDiscount,
config.isMember
config.isMember,
seatFee,
packFee
);
usedFullReductionThreshold = selectOptimalThreshold(
@@ -1425,6 +1443,7 @@ export function calculateOrderCostSummary(
usedFullReductionThreshold
);
}
// ------------------------------ 3. 优惠券抵扣(适配满减同享规则) ------------------------------
let couponDeductionAmount = 0;
let productCouponDeduction = 0;
@@ -1446,6 +1465,7 @@ export function calculateOrderCostSummary(
currentTime,
}
);
console.log("couponResult", couponResult);
couponDeductionAmount = couponResult.deductionAmount;
productCouponDeduction = couponResult.productCouponDeduction;
fullCouponDeduction = couponResult.fullCouponDeduction;
@@ -1453,7 +1473,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;
@@ -1486,7 +1509,10 @@ export function calculateOrderCostSummary(
pointDeductionAmount = pointResult.deductionAmount;
usedPoints = pointResult.usedPoints;
// 若满减与积分不同享pointsShare=1积分抵扣为0
if (usedFullReductionActivity && !usedFullReductionActivity.pointsShare) {
if (
usedFullReductionThreshold &&
(!usedFullReductionActivity || !usedFullReductionActivity.pointsShare)
) {
pointDeductionAmount = 0;
usedPoints = 0;
}
@@ -1550,6 +1576,17 @@ export function calculateOrderCostSummary(
truncateToTwoDecimals(merchantReductionActualAmount)
);
// 会员折扣减免
const vipDiscountAmount = calcVipDiscountAmount(
new BigNumber(goodsRealAmount)
.minus(couponDeductionAmount)
.plus(packFee)
.plus(seatFee)
.minus(newUserDiscount)
.minus(fullReductionAmount)
.toNumber(),
shopUserInfo
); // 会员折扣减免金额
// ------------------------------ 6. 最终实付金额计算 ------------------------------
const finalPayAmountBn = new BigNumber(goodsRealAmount)
.minus(newUserDiscount)

View File

@@ -8,26 +8,32 @@ export function canUseLimitTimeDiscount(
shopUserInfo,
idKey = "id"
) {
shopInfo=shopInfo||{}
shopUserInfo=shopUserInfo||{}
shopInfo = shopInfo || {};
shopUserInfo = shopUserInfo || {};
if (!limitTimeDiscountRes || !limitTimeDiscountRes.id) {
return false;
}
const canUseFoods = (limitTimeDiscountRes.foods || "").split(",");
const goodsCanUse =
limitTimeDiscountRes.foodType == 1 ||
canUseFoods.includes("" + goods[idKey]);
if (!goodsCanUse) {
return false;
}
if (limitTimeDiscountRes.discountPriority == "limit-time") {
if (
limitTimeDiscountRes.foodType == 1 ||
canUseFoods.includes("" + goods[idKey])
) {
return true;
}
if (limitTimeDiscountRes.discountPriority == "vip-price") {
if (shopUserInfo.isVip != 1 || shopUserInfo.isMemberPrice != 1) {
return true;
}
}
if (
limitTimeDiscountRes.discountPriority == "vip-price"
) {
if(shopUserInfo.isVip != 1 || shopInfo.isMemberPrice != 1){
if (
shopUserInfo.isVip == 1 &&
shopUserInfo.isMemberPrice == 1 &&
goods.memberPrice * 1 <= 0
) {
return true;
}
}
@@ -53,9 +59,10 @@ export function returnPrice(args) {
idKey = "product_id",
} = args;
const canUseFoods = (limitTimeDiscountRes.foods || "").split(",");
shopInfo=shopInfo||{}
shopUserInfo=shopUserInfo||{}
if (shopInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1) {
shopInfo = shopInfo || {};
shopUserInfo = shopUserInfo || {};
if (shopUserInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1) {
const memberPrice = goods.memberPrice || goods.salePrice;
//是会员而且启用会员价
@@ -76,8 +83,18 @@ export function returnPrice(args) {
}
}
if (limitTimeDiscountRes.discountPriority == "vip-price") {
//会员优先
return memberPrice;
if (goods.memberPrice * 1 > 0) {
//会员优先
return memberPrice;
} else {
const price = returnLimitPrice({
price: goods.salePrice,
limitTimeDiscountRes,
goods: goods,
});
return price;
}
}
} else {
//是会员没有限时折扣
@@ -135,7 +152,7 @@ export function returnLimitPrice(args) {
*/
export function canReturnMemberPrice(args) {
const { shopInfo, shopUserInfo } = args;
if (shopInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1) {
if (shopUserInfo.isMemberPrice == 1 && shopUserInfo.isVip == 1) {
return true;
} else {
return false;

143
utils/util.js Normal file
View File

@@ -0,0 +1,143 @@
/**
* 手机号脱敏隐藏中间4位11位手机号通用
* @param {string} phone - 原始手机号可带非数字字符如138-1234-5678
* @returns {string} 脱敏后手机号
*/
export function desensitizePhone(phone) {
// 1. 提取纯数字(过滤非数字字符)
const purePhone = (phone || "").replace(/[^\d]/g, "");
// 2. 边界判断非11位手机号返回原字符串或自定义提示
if (purePhone.length !== 11) {
console.warn("手机号格式不正确需11位纯数字");
return phone; // 或返回 ''、'手机号格式错误' 等
}
// 3. 脱敏前3位 + **** + 后4位
return purePhone.replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3");
}
/**
* 姓名合法性校验
* @param {string} name - 待校验的姓名
* @returns {Object} 校验结果:{ valid: boolean, msg: string }
*/
export function validateName(name) {
// 1. 空值校验
if (!name || name.trim() === '') {
return { valid: false, msg: '姓名不能为空' };
}
const pureName = name.trim();
// 2. 长度校验2-6位含少数民族中间点
if (pureName.length < 2 || pureName.length > 6) {
return { valid: false, msg: '姓名长度应为2-6位' };
}
// 3. 正则校验:仅允许中文、少数民族中间点(·),且中间点不能在开头/结尾
// 中文范围:[\u4e00-\u9fa5],中间点:[\u00b7]Unicode 标准中间点,非小数点)
const nameReg = /^[\u4e00-\u9fa5]+([\u00b7][\u4e00-\u9fa5]+)*$/;
if (!nameReg.test(pureName)) {
return {
valid: false,
msg: '姓名仅支持中文和少数民族中间点(·),且不能包含数字、字母或特殊符号'
};
}
// 4. 额外限制:中间点不能连续(如“李··四”)
if (/[\u00b7]{2,}/.test(pureName)) {
return { valid: false, msg: '姓名中的中间点(·)不能连续' };
}
// 校验通过
return { valid: true, msg: '姓名格式合法' };
}
/**
* 身份证号码合法性校验支持18位/15位
* @param {string} idCard - 待校验的身份证号
* @returns {Object} 校验结果:{ valid: boolean, msg: string, info?: Object }
* info 可选返回:{ birthDate: string, gender: string }(出生日期、性别)
*/
export function validateIdCard(idCard) {
// 1. 空值校验
if (!idCard || idCard.trim() === '') {
return { valid: false, msg: '身份证号码不能为空' };
}
const pureIdCard = idCard.trim().toUpperCase(); // 统一转为大写处理X
// 2. 格式校验18位或15位
const id18Reg = /^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([12]\d)|(3[01]))\d{3}([0-9]|X)$/;
const id15Reg = /^[1-9]\d{5}\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([12]\d)|(3[01]))\d{3}$/;
if (!id18Reg.test(pureIdCard) && !id15Reg.test(pureIdCard)) {
return {
valid: false,
msg: '身份证号码格式错误需18位最后一位可含X或15位纯数字'
};
}
// 3. 提取出生日期并校验合法性
let birthDateStr, birthDate;
if (pureIdCard.length === 18) {
// 18位第7-14位为出生日期YYYYMMDD
birthDateStr = pureIdCard.slice(6, 14);
birthDate = new Date(`${birthDateStr.slice(0,4)}-${birthDateStr.slice(4,6)}-${birthDateStr.slice(6,8)}`);
} else {
// 15位第7-12位为出生日期YYMMDD补全为YYYYMMDD19xx或20xx默认19xx
const year = `19${pureIdCard.slice(6, 8)}`;
const month = pureIdCard.slice(8, 10);
const day = pureIdCard.slice(10, 12);
birthDateStr = `${year}${month}${day}`;
birthDate = new Date(`${year}-${month}-${day}`);
}
// 校验出生日期有效性如20230230 → 日期对象会是Invalid Date
if (
isNaN(birthDate.getTime()) ||
birthDateStr.slice(0,4) !== birthDate.getFullYear().toString() ||
birthDateStr.slice(4,6) !== (birthDate.getMonth() + 1).toString().padStart(2, '0') ||
birthDateStr.slice(6,8) !== birthDate.getDate().toString().padStart(2, '0')
) {
return { valid: false, msg: '身份证中的出生日期无效' };
}
// 4. 18位身份证额外校验校验码合法性加权算法
if (pureIdCard.length === 18) {
// 加权因子
const weightFactors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 校验码对应值0-10 → 10对应X
const checkCodeMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
// 计算前17位与加权因子的乘积和
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(pureIdCard[i]) * weightFactors[i];
}
// 计算预期校验码
const expectedCheckCode = checkCodeMap[sum % 11];
// 对比实际校验码(最后一位)
if (pureIdCard[17] !== expectedCheckCode) {
return { valid: false, msg: '身份证校验码错误,可能是无效身份证' };
}
}
// 5. 可选提取性别18位第17位15位第15位奇数=男,偶数=女)
let gender = '';
if (pureIdCard.length === 18) {
const genderCode = parseInt(pureIdCard[16]);
gender = genderCode % 2 === 1 ? '男' : '女';
} else {
const genderCode = parseInt(pureIdCard[14]);
gender = genderCode % 2 === 1 ? '男' : '女';
}
// 校验通过,返回额外信息(出生日期、性别)
return {
valid: true,
msg: '身份证号码合法',
info: {
birthDate: `${birthDate.getFullYear()}-${(birthDate.getMonth() + 1).toString().padStart(2, '0')}-${birthDate.getDate().toString().padStart(2, '0')}`,
gender: gender
}
};
}