You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

420 lines
13 KiB

/**
* 价格计算工具类
* 根据当前地址、日期、宠物类型等因素计算价格,保留2位小数
*/
class PriceCalculator {
constructor(priceConfig) {
this.priceConfig = priceConfig || this.getDefaultPriceConfig();
}
// 获取默认价格配置
getDefaultPriceConfig() {
return {
basePrice: {
normal: 75,
holiday: 85,
weekend: 80
},
memberDiscount: {
'new': 0.95,
'regular': 0.9,
'silver': 0.88,
'gold': 0.85
},
preFamiliarize: {
price: 40,
holidayRate: 1.2
},
multiService: {
two: {
price: 45,
holidayRate: 1.1
},
three: {
price: 130,
holidayRate: 1.1
}
},
petExtra: {
largeDog: {
price: 40,
holidayRate: 1.1
},
mediumDog: {
price: 30,
holidayRate: 1.1
},
smallDog: {
price: 15,
holidayRate: 1.1
},
cat: {
price: 10,
holidayRate: 1.1
}
},
freeQuota: {
threshold: 30,
rules: [
{
type: 'cat',
count: 3,
freeAmount: 30,
description: '3只及以上猫免费30元'
},
{
type: 'smallDog',
count: 2,
freeAmount: 30,
description: '2只及以上小型犬免费30元'
},
{
type: 'mediumDog',
count: 1,
freeAmount: 30,
description: '1只及以上中型犬免费30元'
},
{
type: 'mixed',
count: 0,
freeAmount: 25,
description: '混合类型免费25元(1猫1小型犬)'
}
]
},
holidays: [
'2024-07-15', '2024-07-16', '2024-07-17',
'2024-10-01', '2024-10-02', '2024-10-03'
],
weekends: [0, 6],
customServices: {
priceConfig: {},
holidayRate: 1.1
},
cityConfig: {
priceRates: {
'beijing': 1.2,
'shanghai': 1.15,
'guangzhou': 1.1,
'shenzhen': 1.15,
'default': 1.0
}
}
};
}
// 保留2位小数
roundToTwoDecimals(value) {
return Math.round(value * 100) / 100;
}
// 根据地址获取城市代码
getCityCode(address) {
if (!address) return 'default';
const cityMapping = {
'北京': 'beijing',
'上海': 'shanghai',
'广州': 'guangzhou',
'深圳': 'shenzhen'
};
for (const [cityName, cityCode] of Object.entries(cityMapping)) {
if (address.includes(cityName)) {
return cityCode;
}
}
return 'default';
}
// 获取城市价格倍率
getCityPriceRate(address) {
const cityCode = this.getCityCode(address);
return this.priceConfig.cityConfig.priceRates[cityCode] || 1.0;
}
// 判断日期类型
getDatePriceType(dateString) {
const date = new Date(dateString);
const dayOfWeek = date.getDay();
if (this.priceConfig.holidays.includes(dateString)) {
return 'holiday';
}
if (this.priceConfig.weekends.includes(dayOfWeek)) {
return 'weekend';
}
return 'normal';
}
// 获取基础服务价格
getBasePriceByDate(dateString) {
const priceType = this.getDatePriceType(dateString);
return this.priceConfig.basePrice[priceType] || this.priceConfig.basePrice.normal;
}
// 获取宠物额外费用
getPetExtraCost(pet, dateString) {
const priceType = this.getDatePriceType(dateString);
let baseCost = 0;
let holidayRate = 1;
if (pet.petType === 'cat') {
baseCost = this.priceConfig.petExtra.cat.price;
holidayRate = this.priceConfig.petExtra.cat.holidayRate;
} else if (pet.petType === 'dog' && pet.bodyType.includes('小型')) {
baseCost = this.priceConfig.petExtra.smallDog.price;
holidayRate = this.priceConfig.petExtra.smallDog.holidayRate;
} else if (pet.petType === 'dog' && pet.bodyType.includes('中型')) {
baseCost = this.priceConfig.petExtra.mediumDog.price;
holidayRate = this.priceConfig.petExtra.mediumDog.holidayRate;
} else if (pet.petType === 'dog' && pet.bodyType.includes('大型')) {
baseCost = this.priceConfig.petExtra.largeDog.price;
holidayRate = this.priceConfig.petExtra.largeDog.holidayRate;
}
if (priceType === 'holiday') {
baseCost = baseCost * holidayRate;
}
return this.roundToTwoDecimals(baseCost);
}
// 获取多次服务费用
getMultiServiceCost(feedCount, dateString) {
const priceType = this.getDatePriceType(dateString);
let baseCost = 0;
let holidayRate = 1;
if (feedCount === 2) {
baseCost = this.priceConfig.multiService.two.price;
holidayRate = this.priceConfig.multiService.two.holidayRate;
} else if (feedCount === 3) {
baseCost = this.priceConfig.multiService.three.price;
holidayRate = this.priceConfig.multiService.three.holidayRate;
}
if (priceType === 'holiday') {
baseCost = baseCost * holidayRate;
}
return this.roundToTwoDecimals(baseCost);
}
// 获取提前熟悉费用
getPreFamiliarizeCost(dateString) {
const priceType = this.getDatePriceType(dateString);
let baseCost = this.priceConfig.preFamiliarize.price;
if (priceType === 'holiday') {
baseCost = baseCost * this.priceConfig.preFamiliarize.holidayRate;
}
return this.roundToTwoDecimals(baseCost);
}
// 计算套餐免费额度
calculateFreeQuota(pets) {
let totalPetCost = pets.reduce((acc, pet) => {
return acc + this.getPetExtraCost(pet, pet.serviceDate);
}, 0);
if (totalPetCost <= this.priceConfig.freeQuota.threshold) {
return 0;
}
let freeAmount = 0;
const petCounts = {
cat: pets.filter(pet => pet.petType === 'cat').length,
smallDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('小型')).length,
mediumDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('中型')).length,
largeDog: pets.filter(pet => pet.petType === 'dog' && pet.bodyType.includes('大型')).length
};
for (const rule of this.priceConfig.freeQuota.rules) {
if (rule.type === 'cat' && petCounts.cat >= rule.count) {
freeAmount += rule.freeAmount;
petCounts.cat -= rule.count;
} else if (rule.type === 'smallDog' && petCounts.smallDog >= rule.count) {
freeAmount += rule.freeAmount;
petCounts.smallDog -= rule.count;
} else if (rule.type === 'mediumDog' && petCounts.mediumDog >= rule.count) {
freeAmount += rule.freeAmount;
petCounts.mediumDog -= rule.count;
} else if (rule.type === 'mixed' && petCounts.cat >= 1 && petCounts.smallDog >= 1) {
freeAmount += rule.freeAmount;
petCounts.cat -= 1;
petCounts.smallDog -= 1;
}
}
return this.roundToTwoDecimals(freeAmount);
}
// 计算会员折扣
calculateMemberDiscount(originalPrice, memberLevel) {
const discount = this.priceConfig.memberDiscount[memberLevel] || 1;
const discountedPrice = originalPrice * discount;
return this.roundToTwoDecimals(discountedPrice);
}
// 计算定制服务费用
calculateCustomServiceCost(customServices, dateString) {
const priceType = this.getDatePriceType(dateString);
let totalCost = 0;
customServices.forEach(service => {
let servicePrice = service.price * service.quantity;
if (priceType === 'holiday') {
servicePrice = servicePrice * this.priceConfig.customServices.holidayRate;
}
totalCost += servicePrice;
});
return this.roundToTwoDecimals(totalCost);
}
// 计算单日总价
calculateDailyPrice(pets, dateString, feedCount = 1, customServices = []) {
// 明细数组
let priceDetails = [];
// 基础服务费用
let baseServiceCost = this.getBasePriceByDate(dateString);
priceDetails.push({
name: '专业喂养',
formula: `¥${baseServiceCost.toFixed(2)} x ${pets.length}`,
amount: this.roundToTwoDecimals(baseServiceCost * pets.length)
});
// 宠物额外费用
let petExtraCost = pets.reduce((acc, pet) => {
return acc + this.getPetExtraCost(pet, dateString);
}, 0);
if (petExtraCost > 0) {
// 细分每只宠物
pets.forEach(pet => {
const extra = this.getPetExtraCost(pet, dateString);
if (extra > 0) {
priceDetails.push({
name: '额外宠物费用',
formula: `${pet.petType === 'cat' ? '猫' : pet.bodyType}${pet.petType === 'dog' ? '犬' : ''} 1只 x ¥${extra.toFixed(2)}`,
amount: extra
});
}
});
}
// 多次服务费用
let multiServiceCost = this.getMultiServiceCost(feedCount, dateString);
if (multiServiceCost > 0) {
priceDetails.push({
name: '上门次数',
formula: `${feedCount === 2 ? '1天2次' : feedCount === 3 ? '1天3次' : '1天1次'} x ¥${multiServiceCost.toFixed(2)}`,
amount: multiServiceCost
});
}
// 定制服务费用
let customServiceCost = this.calculateCustomServiceCost(customServices, dateString);
if (customServiceCost > 0) {
customServices.forEach(service => {
if (service.quantity > 0) {
priceDetails.push({
name: '定制服务',
formula: `${service.name} | ¥${service.price} x ${service.quantity}`,
amount: this.roundToTwoDecimals(service.price * service.quantity)
});
}
});
}
// 计算免费额度
let freeQuota = this.calculateFreeQuota(pets);
if (freeQuota > 0) {
priceDetails.push({
name: '套餐免费额度',
formula: `${freeQuota.toFixed(2)}`,
amount: -freeQuota
});
}
// 总价(未折扣)
let totalOriginalPrice = baseServiceCost + petExtraCost + multiServiceCost + customServiceCost - freeQuota;
return {
baseServiceCost: this.roundToTwoDecimals(baseServiceCost),
petExtraCost: this.roundToTwoDecimals(petExtraCost),
multiServiceCost: this.roundToTwoDecimals(multiServiceCost),
customServiceCost: this.roundToTwoDecimals(customServiceCost),
freeQuota: this.roundToTwoDecimals(freeQuota),
totalOriginalPrice: this.roundToTwoDecimals(totalOriginalPrice),
priceType: this.getDatePriceType(dateString),
priceDetails // 新增明细
};
}
// 计算订单总价(含城市倍率和会员折扣)
calculateOrderTotal(orderData, userAddress, memberLevel = 'new') {
const cityRate = this.getCityPriceRate(userAddress);
// 计算每日费用
const dailyPrices = orderData.pets.map(pet => {
const dailyPrice = this.calculateDailyPrice(
[pet],
pet.serviceDate,
pet.feedCount,
pet.customServices || []
);
// 应用城市倍率
dailyPrice.totalWithCityRate = this.roundToTwoDecimals(dailyPrice.totalOriginalPrice * cityRate);
// 明细也加上城市倍率
dailyPrice.priceDetails = dailyPrice.priceDetails.map(detail => ({
...detail,
amount: this.roundToTwoDecimals(detail.amount * cityRate),
formula: cityRate !== 1.0 ? `${detail.formula} x 城市倍率${cityRate}` : detail.formula
}));
return dailyPrice;
});
// 汇总明细
let priceDetails = [];
dailyPrices.forEach((d, idx) => {
d.priceDetails.forEach(item => {
priceDetails.push({
...item,
date: orderData.pets[idx]?.serviceDate || ''
});
});
});
// 提前熟悉费用
let preFamiliarizeCost = 0;
if (orderData.needPreFamiliarize && orderData.needPreFamiliarize.length > 0) {
const firstServiceDate = orderData.pets[0]?.serviceDate;
if (firstServiceDate) {
preFamiliarizeCost = this.getPreFamiliarizeCost(firstServiceDate);
preFamiliarizeCost = this.roundToTwoDecimals(preFamiliarizeCost * cityRate);
priceDetails.push({
name: '提前熟悉',
formula: `¥${this.getPreFamiliarizeCost(firstServiceDate)} x 城市倍率${cityRate}`,
amount: preFamiliarizeCost
});
}
}
// 计算总价
let totalOriginalPrice = dailyPrices.reduce((acc, daily) => {
return acc + daily.totalWithCityRate;
}, 0) + preFamiliarizeCost;
// 应用会员折扣
const totalWithDiscount = this.calculateMemberDiscount(totalOriginalPrice, memberLevel);
return {
dailyPrices,
preFamiliarizeCost,
totalOriginalPrice: this.roundToTwoDecimals(totalOriginalPrice),
totalWithDiscount: this.roundToTwoDecimals(totalWithDiscount),
cityRate: this.roundToTwoDecimals(cityRate),
memberDiscount: this.roundToTwoDecimals(totalOriginalPrice - totalWithDiscount),
memberLevel,
priceDetails // 新增明细
};
}
}
export default PriceCalculator;