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 { 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 { 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 { // 这里模拟获取费用数据 // 实际项目中应该从数据库或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); 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); 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; } } }