201 lines
5.2 KiB
JavaScript
201 lines
5.2 KiB
JavaScript
const Price = require('../models/Price');
|
||
const { validateAndCleanPricesData } = require('../utils/validator');
|
||
|
||
/**
|
||
* 价格服务
|
||
* 处理价格相关的业务逻辑
|
||
*/
|
||
class PriceService {
|
||
/**
|
||
* 按地区查询价格
|
||
*/
|
||
static async getByRegion(region, date, page = 1, pageSize = 20) {
|
||
try {
|
||
const prices = await Price.getByRegion(region, date);
|
||
|
||
// 手动分页
|
||
const total = prices.length;
|
||
const startIndex = (page - 1) * pageSize;
|
||
const endIndex = startIndex + parseInt(pageSize, 10);
|
||
const paginatedData = prices.slice(startIndex, endIndex);
|
||
|
||
return {
|
||
success: true,
|
||
data: paginatedData,
|
||
pagination: {
|
||
page: parseInt(page, 10),
|
||
pageSize: parseInt(pageSize, 10),
|
||
total,
|
||
totalPages: Math.ceil(total / pageSize)
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 搜索价格数据
|
||
*/
|
||
static async search(filters) {
|
||
try {
|
||
// 解析分页参数
|
||
const page = parseInt(filters.page) || 1;
|
||
const pageSize = parseInt(filters.pageSize) || 20;
|
||
|
||
const searchFilters = {
|
||
material: filters.material,
|
||
specification: filters.specification,
|
||
startDate: filters.startDate,
|
||
endDate: filters.endDate,
|
||
region: filters.region,
|
||
page,
|
||
pageSize
|
||
};
|
||
|
||
// 获取数据
|
||
const data = await Price.search(searchFilters);
|
||
|
||
// 获取总数
|
||
const total = await Price.count({
|
||
material: filters.material,
|
||
specification: filters.specification,
|
||
startDate: filters.startDate,
|
||
endDate: filters.endDate,
|
||
region: filters.region
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
data,
|
||
pagination: {
|
||
page,
|
||
pageSize,
|
||
total,
|
||
totalPages: Math.ceil(total / pageSize)
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取价格统计
|
||
*/
|
||
static async getStats(filters) {
|
||
try {
|
||
const stats = await Price.getStats(filters);
|
||
|
||
// 计算趋势(如果提供了天数)
|
||
let trend = null;
|
||
let changeRate = null;
|
||
|
||
if (filters.days && stats.avgPrice) {
|
||
const previousStats = await Price.getStats({
|
||
region: filters.region,
|
||
material: filters.material,
|
||
days: parseInt(filters.days) * 2 // 获取双倍天数的范围
|
||
});
|
||
|
||
if (previousStats.avgPrice) {
|
||
const currentAvg = parseFloat(stats.avgPrice);
|
||
const previousAvg = parseFloat(previousStats.avgPrice);
|
||
const change = currentAvg - previousAvg;
|
||
changeRate = (change / previousAvg * 100).toFixed(2);
|
||
trend = change > 0 ? 'up' : change < 0 ? 'down' : 'stable';
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
count: stats.count,
|
||
avgPrice: stats.avgPrice ? parseFloat(parseFloat(stats.avgPrice).toFixed(2)) : null,
|
||
minPrice: stats.minPrice,
|
||
maxPrice: stats.maxPrice,
|
||
stdDev: stats.stdDev ? parseFloat(parseFloat(stats.stdDev).toFixed(2)) : null,
|
||
trend,
|
||
changeRate: changeRate ? `${changeRate > 0 ? '+' : ''}${changeRate}%` : null
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取价格趋势
|
||
*/
|
||
static async getTrend(filters) {
|
||
try {
|
||
const trend = await Price.getTrend(filters);
|
||
|
||
return {
|
||
success: true,
|
||
data: trend.map(item => ({
|
||
date: item.date,
|
||
avgPrice: item.avgPrice ? parseFloat(parseFloat(item.avgPrice).toFixed(2)) : null,
|
||
minPrice: item.minPrice,
|
||
maxPrice: item.maxPrice
|
||
})),
|
||
meta: {
|
||
total: trend.length,
|
||
filters: {
|
||
region: filters.region || null,
|
||
material: filters.material || null,
|
||
days: filters.days || null
|
||
}
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导入数据
|
||
*/
|
||
static async importData(prices) {
|
||
try {
|
||
if (!Array.isArray(prices) || prices.length === 0) {
|
||
throw new Error('无效的数据格式');
|
||
}
|
||
|
||
// 验证并清洗数据
|
||
const validation = validateAndCleanPricesData(prices);
|
||
|
||
if (validation.validCount === 0) {
|
||
return {
|
||
success: false,
|
||
message: '没有有效的数据可以导入',
|
||
data: {
|
||
total: validation.total,
|
||
imported: 0,
|
||
errors: validation.errors
|
||
}
|
||
};
|
||
}
|
||
|
||
// 批量插入有效数据
|
||
const result = await Price.batchInsert(validation.validData);
|
||
|
||
return {
|
||
success: true,
|
||
message: `成功导入 ${result} 条数据${validation.errorCount > 0 ? `,${validation.errorCount} 条数据因格式错误被跳过` : ''}`,
|
||
data: {
|
||
imported: result,
|
||
total: validation.total,
|
||
validCount: validation.validCount,
|
||
errorCount: validation.errorCount,
|
||
errors: validation.errorCount > 0 ? validation.errors.slice(0, 10) : undefined // 只返回前 10 个错误
|
||
}
|
||
};
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = PriceService;
|