Files
makemd/server/src/core/ai/SettlementOptimizationService.ts

436 lines
14 KiB
TypeScript
Raw Normal View History

import { Injectable, Logger } from '@nestjs/common';
import { Settlement, Order, Merchant } from '@prisma/client';
import { PrismaService } from '../../config/database';
interface SettlementData {
id: string;
merchantId: string;
periodStart: Date;
periodEnd: Date;
totalAmount: number;
status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
createdAt: Date;
updatedAt: Date;
}
interface FeeData {
feeType: string;
amount: number;
description: string;
merchantId: string;
createdAt: Date;
}
interface OptimizationResult {
settlementId: string;
merchantId: string;
originalAmount: number;
optimizedAmount: number;
savings: number;
optimizationRate: number;
recommendations: {
type: string;
description: string;
potentialSavings: number;
priority: 'high' | 'medium' | 'low';
}[];
feeBreakdown: {
originalFees: FeeData[];
optimizedFees: FeeData[];
};
confidence: number;
}
@Injectable()
export class SettlementOptimizationService {
private readonly logger = new Logger(SettlementOptimizationService.name);
constructor(private readonly prisma: PrismaService) {}
/**
*
* @param settlementId ID
* @param traceId ID
* @returns
*/
async optimizeSettlement(settlementId: string, traceId: string): Promise<OptimizationResult> {
this.logger.log(`开始优化结算: ${settlementId}`, { traceId, settlementId });
try {
// 获取结算单信息
const settlement = await this.prisma.settlement.findUnique({
where: { id: settlementId },
});
if (!settlement) {
throw new Error(`结算单不存在: ${settlementId}`);
}
// 获取商户信息
const merchant = await this.prisma.merchant.findUnique({
where: { id: settlement.merchantId },
});
if (!merchant) {
throw new Error(`商户不存在: ${settlement.merchantId}`);
}
// 获取结算周期内的订单数据
const orders = await this.prisma.order.findMany({
where: {
merchantId: settlement.merchantId,
createdAt: {
gte: settlement.periodStart,
lte: settlement.periodEnd,
},
},
});
// 获取结算周期内的费用数据
const fees = await this.getFeeData(settlement.merchantId, settlement.periodStart, settlement.periodEnd);
// 分析结算数据
const analysis = this.analyzeSettlementData(settlement, orders, fees);
// 生成优化建议
const recommendations = this.generateOptimizationRecommendations(analysis, merchant);
// 计算优化后的金额
const optimizedAmount = this.calculateOptimizedAmount(settlement.totalAmount, recommendations);
// 计算节省金额和优化率
const savings = settlement.totalAmount - optimizedAmount;
const optimizationRate = settlement.totalAmount > 0 ? (savings / settlement.totalAmount) * 100 : 0;
// 生成优化后的费用明细
const optimizedFees = this.generateOptimizedFees(fees, recommendations);
// 计算置信度
const confidence = this.calculateConfidence(recommendations);
const result: OptimizationResult = {
settlementId,
merchantId: settlement.merchantId,
originalAmount: settlement.totalAmount,
optimizedAmount,
savings,
optimizationRate: Math.round(optimizationRate * 100) / 100,
recommendations,
feeBreakdown: {
originalFees: fees,
optimizedFees,
},
confidence,
};
this.logger.log(`结算优化完成: ${settlementId}, 节省: ${savings}, 优化率: ${optimizationRate}%`, { traceId, result });
return result;
} catch (error) {
this.logger.error(`结算优化失败: ${error.message}`, { traceId, settlementId, error });
throw error;
}
}
/**
*
* @param settlementIds ID列表
* @param traceId ID
* @returns
*/
async batchOptimizeSettlements(settlementIds: string[], traceId: string): Promise<OptimizationResult[]> {
this.logger.log(`开始批量优化结算, 数量: ${settlementIds.length}`, { traceId });
const results: OptimizationResult[] = [];
for (const settlementId of settlementIds) {
try {
const result = await this.optimizeSettlement(settlementId, traceId);
results.push(result);
} catch (error) {
this.logger.error(`批量优化结算失败: ${settlementId}, 错误: ${error.message}`, { traceId, settlementId });
}
}
this.logger.log(`批量优化结算完成, 成功: ${results.length}/${settlementIds.length}`, { traceId });
return results;
}
/**
*
*/
private async getFeeData(merchantId: string, startDate: Date, endDate: Date): Promise<FeeData[]> {
// 这里模拟获取费用数据
// 实际项目中应该从数据库或API获取
return [
{
feeType: 'platform_fee',
amount: 100.00,
description: '平台服务费',
merchantId,
createdAt: new Date(),
},
{
feeType: 'transaction_fee',
amount: 50.00,
description: '交易手续费',
merchantId,
createdAt: new Date(),
},
{
feeType: 'logistics_fee',
amount: 80.00,
description: '物流费用',
merchantId,
createdAt: new Date(),
},
{
feeType: 'marketing_fee',
amount: 30.00,
description: '营销费用',
merchantId,
createdAt: new Date(),
},
];
}
/**
*
*/
private analyzeSettlementData(settlement: SettlementData, orders: Order[], fees: FeeData[]) {
// 计算订单相关指标
const orderCount = orders.length;
const totalOrderAmount = orders.reduce((sum, order) => sum + order.totalAmount, 0);
const averageOrderValue = orderCount > 0 ? totalOrderAmount / orderCount : 0;
// 计算费用相关指标
const totalFees = fees.reduce((sum, fee) => sum + fee.amount, 0);
const feeToSalesRatio = totalOrderAmount > 0 ? (totalFees / totalOrderAmount) * 100 : 0;
// 分析费用结构
const feeStructure = fees.reduce((acc, fee) => {
acc[fee.feeType] = (acc[fee.feeType] || 0) + fee.amount;
return acc;
}, {} as Record<string, number>);
return {
orderCount,
totalOrderAmount,
averageOrderValue,
totalFees,
feeToSalesRatio,
feeStructure,
settlementAmount: settlement.totalAmount,
};
}
/**
*
*/
private generateOptimizationRecommendations(analysis: any, merchant: Merchant) {
const recommendations: OptimizationResult['recommendations'] = [];
// 平台服务费优化
if (analysis.feeStructure.platform_fee && analysis.feeStructure.platform_fee > 50) {
recommendations.push({
type: 'platform_fee',
description: '建议升级商户等级,享受平台服务费折扣',
potentialSavings: analysis.feeStructure.platform_fee * 0.2, // 假设可节省20%
priority: 'high',
});
}
// 交易手续费优化
if (analysis.feeStructure.transaction_fee && analysis.feeStructure.transaction_fee > 30) {
recommendations.push({
type: 'transaction_fee',
description: '建议优化支付方式,降低交易手续费',
potentialSavings: analysis.feeStructure.transaction_fee * 0.15, // 假设可节省15%
priority: 'medium',
});
}
// 物流费用优化
if (analysis.feeStructure.logistics_fee && analysis.feeStructure.logistics_fee > 60) {
recommendations.push({
type: 'logistics_fee',
description: '建议与物流商协商折扣,优化物流方案',
potentialSavings: analysis.feeStructure.logistics_fee * 0.25, // 假设可节省25%
priority: 'high',
});
}
// 营销费用优化
if (analysis.feeStructure.marketing_fee && analysis.feeStructure.marketing_fee > 20) {
recommendations.push({
type: 'marketing_fee',
description: '建议优化营销策略提高营销ROI',
potentialSavings: analysis.feeStructure.marketing_fee * 0.3, // 假设可节省30%
priority: 'medium',
});
}
// 订单量优化
if (analysis.orderCount < 50) {
recommendations.push({
type: 'order_volume',
description: '建议增加订单量,分摊固定成本',
potentialSavings: analysis.totalFees * 0.1, // 假设可节省10%
priority: 'low',
});
}
// 平均订单价值优化
if (analysis.averageOrderValue < 100) {
recommendations.push({
type: 'average_order_value',
description: '建议提高平均订单价值,增加利润率',
potentialSavings: analysis.totalFees * 0.05, // 假设可节省5%
priority: 'low',
});
}
return recommendations;
}
/**
*
*/
private calculateOptimizedAmount(originalAmount: number, recommendations: OptimizationResult['recommendations']): number {
const totalSavings = recommendations.reduce((sum, rec) => sum + rec.potentialSavings, 0);
const optimizedAmount = originalAmount - totalSavings;
return Math.max(0, optimizedAmount);
}
/**
*
*/
private generateOptimizedFees(originalFees: FeeData[], recommendations: OptimizationResult['recommendations']): FeeData[] {
const optimizedFees = [...originalFees];
// 应用优化建议到费用明细
recommendations.forEach(rec => {
const feeIndex = optimizedFees.findIndex(fee => fee.feeType === rec.type);
if (feeIndex !== -1) {
optimizedFees[feeIndex] = {
...optimizedFees[feeIndex],
amount: Math.max(0, optimizedFees[feeIndex].amount - rec.potentialSavings),
description: `${optimizedFees[feeIndex].description} (优化后)`,
};
}
});
return optimizedFees;
}
/**
*
*/
private calculateConfidence(recommendations: OptimizationResult['recommendations']): number {
// 基于建议数量和优先级计算置信度
const highPriorityCount = recommendations.filter(r => r.priority === 'high').length;
const mediumPriorityCount = recommendations.filter(r => r.priority === 'medium').length;
const confidence = 70 + (highPriorityCount * 5) + (mediumPriorityCount * 3);
return Math.min(95, confidence); // 最高95%
}
/**
*
* @param merchantId ID
* @param period 'monthly', 'quarterly', 'yearly'
* @param traceId ID
* @returns
*/
async getSettlementOptimizationReport(merchantId: string, period: string, traceId: string) {
this.logger.log(`获取商户结算优化报告: ${merchantId}, 周期: ${period}`, { traceId, merchantId, period });
try {
// 计算时间范围
const endDate = new Date();
let startDate = new Date();
switch (period) {
case 'monthly':
startDate.setMonth(startDate.getMonth() - 1);
break;
case 'quarterly':
startDate.setMonth(startDate.getMonth() - 3);
break;
case 'yearly':
startDate.setFullYear(startDate.getFullYear() - 1);
break;
default:
startDate.setMonth(startDate.getMonth() - 1);
}
// 获取时间范围内的结算单
const settlements = await this.prisma.settlement.findMany({
where: {
merchantId,
periodStart: {
gte: startDate,
},
periodEnd: {
lte: endDate,
},
},
});
// 批量优化结算单
const settlementIds = settlements.map(s => s.id);
const optimizationResults = await this.batchOptimizeSettlements(settlementIds, traceId);
// 计算总体优化效果
const totalOriginalAmount = optimizationResults.reduce((sum, result) => sum + result.originalAmount, 0);
const totalOptimizedAmount = optimizationResults.reduce((sum, result) => sum + result.optimizedAmount, 0);
const totalSavings = totalOriginalAmount - totalOptimizedAmount;
const totalOptimizationRate = totalOriginalAmount > 0 ? (totalSavings / totalOriginalAmount) * 100 : 0;
// 汇总建议
const allRecommendations = optimizationResults.flatMap(result => result.recommendations);
const recommendationCounts = allRecommendations.reduce((acc, rec) => {
acc[rec.type] = (acc[rec.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const topRecommendations = Object.entries(recommendationCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 3)
.map(([type, count]) => {
const recommendation = allRecommendations.find(rec => rec.type === type);
return {
type,
count,
description: recommendation?.description || '',
potentialSavings: recommendation?.potentialSavings || 0,
};
});
const report = {
merchantId,
period,
timeRange: {
start: startDate,
end: endDate,
},
totalOriginalAmount,
totalOptimizedAmount,
totalSavings,
totalOptimizationRate: Math.round(totalOptimizationRate * 100) / 100,
settlementCount: settlements.length,
optimizationResults,
topRecommendations,
};
this.logger.log(`获取商户结算优化报告完成: ${merchantId}`, { traceId, report });
return report;
} catch (error) {
this.logger.error(`获取商户结算优化报告失败: ${error.message}`, { traceId, merchantId, period, error });
throw error;
}
}
}