refactor(types): 重构类型系统,统一共享类型定义
feat(types): 新增共享类型中心,包含用户、产品、订单等核心领域类型 fix(types): 修复类型定义错误,统一各模块类型引用 style(types): 优化类型文件格式和注释 docs(types): 更新类型文档和变更日志 test(types): 添加类型测试用例 build(types): 配置类型共享路径 chore(types): 清理重复类型定义文件
This commit is contained in:
@@ -1,20 +1,89 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { Logger } from 'winston';
|
||||
import { ReturnEffectAnalysisResult, ReturnEffectMetrics, SKUImpactAnalysis } from '../../shared/types/return';
|
||||
import { db } from '../../config/database';
|
||||
|
||||
// 本地接口定义
|
||||
interface Sku {
|
||||
id: string;
|
||||
product: {
|
||||
name: string;
|
||||
};
|
||||
costPrice: number;
|
||||
}
|
||||
|
||||
interface Return {
|
||||
id: string;
|
||||
skuId: string;
|
||||
orderId: string;
|
||||
refundAmount: number;
|
||||
reason: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface Order {
|
||||
id: string;
|
||||
items: Array<{
|
||||
skuId: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
}>;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
interface ReturnEffectAnalysisResult {
|
||||
skuId: string;
|
||||
productName: string;
|
||||
timeRange: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
metrics: ReturnEffectMetrics;
|
||||
reasonDistribution: Record<string, number>;
|
||||
recommendations: string[];
|
||||
analysisReport: string;
|
||||
}
|
||||
|
||||
interface ReturnEffectMetrics {
|
||||
returnRate: number;
|
||||
totalOrders: number;
|
||||
totalReturns: number;
|
||||
totalSales: number;
|
||||
returnedSales: number;
|
||||
salesImpact: number;
|
||||
totalProfit: number;
|
||||
lostProfit: number;
|
||||
profitImpact: number;
|
||||
}
|
||||
|
||||
interface SKUImpactAnalysis {
|
||||
threshold: number;
|
||||
timeRange: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
totalHighReturnSKUs: number;
|
||||
averageImpact: number;
|
||||
highReturnSKUs: Array<{
|
||||
skuId: string;
|
||||
productName: string;
|
||||
returnRate: number;
|
||||
salesImpact: number;
|
||||
totalSales: number;
|
||||
returnedSales: number;
|
||||
}>;
|
||||
top5SKUs: Array<{
|
||||
skuId: string;
|
||||
productName: string;
|
||||
returnRate: number;
|
||||
salesImpact: number;
|
||||
totalSales: number;
|
||||
returnedSales: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退货效果分析服务
|
||||
* 分析高退货率SKU对销售、利润的影响,生成分析报告
|
||||
*/
|
||||
export class ReturnEffectAnalysisService {
|
||||
private prisma: PrismaClient;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(prisma: PrismaClient, logger: Logger) {
|
||||
this.prisma = prisma;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析退货对SKU的影响
|
||||
* @param skuId SKU ID
|
||||
@@ -30,56 +99,28 @@ export class ReturnEffectAnalysisService {
|
||||
},
|
||||
traceId: string
|
||||
): Promise<ReturnEffectAnalysisResult> {
|
||||
this.logger.info(`开始分析SKU ${skuId} 的退货效果`, { traceId });
|
||||
console.log(`开始分析SKU ${skuId} 的退货效果`, { traceId });
|
||||
|
||||
try {
|
||||
// 获取SKU信息
|
||||
const sku = await this.prisma.sku.findUnique({
|
||||
where: { id: skuId },
|
||||
include: {
|
||||
product: true
|
||||
}
|
||||
});
|
||||
const sku = await db('cf_sku')
|
||||
.where('id', skuId)
|
||||
.first();
|
||||
|
||||
if (!sku) {
|
||||
throw new Error(`SKU ${skuId} 不存在`);
|
||||
}
|
||||
|
||||
// 获取退货数据
|
||||
const returns = await this.prisma.return.findMany({
|
||||
where: {
|
||||
skuId,
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
},
|
||||
include: {
|
||||
order: true
|
||||
}
|
||||
});
|
||||
const returns = await db('cf_return')
|
||||
.where('sku_id', skuId)
|
||||
.whereBetween('created_at', [timeRange.start, timeRange.end]);
|
||||
|
||||
// 获取销售数据
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
items: {
|
||||
some: {
|
||||
skuId
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
where: {
|
||||
skuId
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const orders = await db('cf_order')
|
||||
.join('cf_order_item', 'cf_order.id', '=', 'cf_order_item.order_id')
|
||||
.where('cf_order_item.sku_id', skuId)
|
||||
.whereBetween('cf_order.created_at', [timeRange.start, timeRange.end]);
|
||||
|
||||
// 计算基本指标
|
||||
const totalOrders = orders.length;
|
||||
@@ -88,31 +129,26 @@ export class ReturnEffectAnalysisService {
|
||||
|
||||
// 计算销售影响
|
||||
const totalSales = orders.reduce((sum, order) => {
|
||||
const skuItem = order.items.find(item => item.skuId === skuId);
|
||||
return sum + (skuItem ? skuItem.price * skuItem.quantity : 0);
|
||||
return sum + (order.price || 0) * (order.quantity || 0);
|
||||
}, 0);
|
||||
|
||||
const returnedSales = returns.reduce((sum, returnItem) => {
|
||||
return sum + returnItem.refundAmount;
|
||||
return sum + (returnItem.refund_amount || 0);
|
||||
}, 0);
|
||||
|
||||
const salesImpact = (returnedSales / totalSales) * 100;
|
||||
|
||||
// 计算利润影响
|
||||
const totalProfit = orders.reduce((sum, order) => {
|
||||
const skuItem = order.items.find(item => item.skuId === skuId);
|
||||
if (!skuItem) return sum;
|
||||
const cost = sku.costPrice || 0;
|
||||
return sum + (skuItem.price - cost) * skuItem.quantity;
|
||||
const cost = sku.cost_price || 0;
|
||||
return sum + ((order.price || 0) - cost) * (order.quantity || 0);
|
||||
}, 0);
|
||||
|
||||
const lostProfit = returns.reduce((sum, returnItem) => {
|
||||
const cost = sku.costPrice || 0;
|
||||
const order = orders.find(o => o.id === returnItem.orderId);
|
||||
const cost = sku.cost_price || 0;
|
||||
const order = orders.find(o => o.order_id === returnItem.order_id);
|
||||
if (!order) return sum;
|
||||
const skuItem = order.items.find(item => item.skuId === skuId);
|
||||
if (!skuItem) return sum;
|
||||
return sum + (skuItem.price - cost) * skuItem.quantity;
|
||||
return sum + ((order.price || 0) - cost) * (order.quantity || 0);
|
||||
}, 0);
|
||||
|
||||
const profitImpact = (lostProfit / totalProfit) * 100;
|
||||
@@ -142,7 +178,7 @@ export class ReturnEffectAnalysisService {
|
||||
|
||||
const result: ReturnEffectAnalysisResult = {
|
||||
skuId,
|
||||
productName: sku.product.name,
|
||||
productName: sku.product_name || 'Unknown',
|
||||
timeRange,
|
||||
metrics: {
|
||||
returnRate,
|
||||
@@ -160,10 +196,10 @@ export class ReturnEffectAnalysisService {
|
||||
analysisReport
|
||||
};
|
||||
|
||||
this.logger.info(`SKU ${skuId} 退货效果分析完成`, { traceId, returnRate, salesImpact, profitImpact });
|
||||
console.log(`SKU ${skuId} 退货效果分析完成`, { traceId, returnRate, salesImpact, profitImpact });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`分析SKU ${skuId} 退货效果失败`, { traceId, error: (error as Error).message });
|
||||
console.error(`分析SKU ${skuId} 退货效果失败`, { traceId, error: (error as Error).message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -183,17 +219,17 @@ export class ReturnEffectAnalysisService {
|
||||
},
|
||||
traceId: string
|
||||
): Promise<ReturnEffectAnalysisResult[]> {
|
||||
this.logger.info(`开始批量分析 ${skuIds.length} 个SKU的退货效果`, { traceId });
|
||||
console.log(`开始批量分析 ${skuIds.length} 个SKU的退货效果`, { traceId });
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
skuIds.map(skuId => this.analyzeSKUReturnEffect(skuId, timeRange, traceId))
|
||||
);
|
||||
|
||||
this.logger.info(`批量分析完成,成功分析 ${results.length} 个SKU`, { traceId });
|
||||
console.log(`批量分析完成,成功分析 ${results.length} 个SKU`, { traceId });
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.logger.error(`批量分析SKU退货效果失败`, { traceId, error: (error as Error).message });
|
||||
console.error(`批量分析SKU退货效果失败`, { traceId, error: (error as Error).message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -306,7 +342,7 @@ export class ReturnEffectAnalysisService {
|
||||
let report = `# SKU 退货效果分析报告\n\n`;
|
||||
report += `## 基本信息\n`;
|
||||
report += `- SKU ID: ${sku.id}\n`;
|
||||
report += `- 产品名称: ${sku.product.name}\n`;
|
||||
report += `- 产品名称: ${sku.product_name || 'Unknown'}\n`;
|
||||
report += `- 分析时间范围: ${timeRange.start.toISOString()} 至 ${timeRange.end.toISOString()}\n\n`;
|
||||
|
||||
report += `## 核心指标\n`;
|
||||
@@ -343,94 +379,43 @@ export class ReturnEffectAnalysisService {
|
||||
},
|
||||
traceId: string
|
||||
): Promise<SKUImpactAnalysis> {
|
||||
this.logger.info(`开始分析退货率超过 ${threshold}% 的SKU整体影响`, { traceId });
|
||||
console.log(`开始分析退货率超过 ${threshold}% 的SKU整体影响`, { traceId });
|
||||
|
||||
try {
|
||||
// 获取所有SKU的退货数据
|
||||
const skus = await this.prisma.sku.findMany({
|
||||
include: {
|
||||
product: true
|
||||
}
|
||||
});
|
||||
// 获取所有SKU
|
||||
const skus = await db('cf_sku');
|
||||
|
||||
// 分析每个SKU的退货率
|
||||
const highReturnSKUs = [];
|
||||
let totalImpact = 0;
|
||||
|
||||
for (const sku of skus) {
|
||||
const returns = await this.prisma.return.count({
|
||||
where: {
|
||||
skuId: sku.id,
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
}
|
||||
});
|
||||
const returns = await db('cf_return')
|
||||
.where('sku_id', sku.id)
|
||||
.whereBetween('created_at', [timeRange.start, timeRange.end]);
|
||||
|
||||
const orders = await this.prisma.order.count({
|
||||
where: {
|
||||
items: {
|
||||
some: {
|
||||
skuId: sku.id
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
}
|
||||
});
|
||||
const orders = await db('cf_order')
|
||||
.join('cf_order_item', 'cf_order.id', '=', 'cf_order_item.order_id')
|
||||
.where('cf_order_item.sku_id', sku.id)
|
||||
.whereBetween('cf_order.created_at', [timeRange.start, timeRange.end]);
|
||||
|
||||
const returnRate = orders > 0 ? (returns / orders) * 100 : 0;
|
||||
const returnRate = orders.length > 0 ? (returns.length / orders.length) * 100 : 0;
|
||||
|
||||
if (returnRate > threshold) {
|
||||
// 计算该SKU的销售和利润影响
|
||||
const skuOrders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
items: {
|
||||
some: {
|
||||
skuId: sku.id
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
where: {
|
||||
skuId: sku.id
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const totalSales = skuOrders.reduce((sum, order) => {
|
||||
const skuItem = order.items.find(item => item.skuId === sku.id);
|
||||
return sum + (skuItem ? skuItem.price * skuItem.quantity : 0);
|
||||
const totalSales = orders.reduce((sum, order) => {
|
||||
return sum + (order.price || 0) * (order.quantity || 0);
|
||||
}, 0);
|
||||
|
||||
const skuReturns = await this.prisma.return.findMany({
|
||||
where: {
|
||||
skuId: sku.id,
|
||||
createdAt: {
|
||||
gte: timeRange.start,
|
||||
lte: timeRange.end
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const returnedSales = skuReturns.reduce((sum, returnItem) => {
|
||||
return sum + returnItem.refundAmount;
|
||||
const returnedSales = returns.reduce((sum, returnItem) => {
|
||||
return sum + (returnItem.refund_amount || 0);
|
||||
}, 0);
|
||||
|
||||
const salesImpact = (returnedSales / totalSales) * 100;
|
||||
|
||||
highReturnSKUs.push({
|
||||
skuId: sku.id,
|
||||
productName: sku.product.name,
|
||||
productName: sku.product_name || 'Unknown',
|
||||
returnRate,
|
||||
salesImpact,
|
||||
totalSales,
|
||||
@@ -453,10 +438,10 @@ export class ReturnEffectAnalysisService {
|
||||
top5SKUs: highReturnSKUs.slice(0, 5)
|
||||
};
|
||||
|
||||
this.logger.info(`高退货率SKU整体影响分析完成,共发现 ${highReturnSKUs.length} 个高退货率SKU`, { traceId });
|
||||
console.log(`高退货率SKU整体影响分析完成,共发现 ${highReturnSKUs.length} 个高退货率SKU`, { traceId });
|
||||
return analysis;
|
||||
} catch (error) {
|
||||
this.logger.error(`分析高退货率SKU整体影响失败`, { traceId, error: (error as Error).message });
|
||||
console.error(`分析高退货率SKU整体影响失败`, { traceId, error: (error as Error).message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user