- 将服务文件按功能分类到core、ai、analytics、security等目录 - 修复logger导入路径问题,统一使用相对路径 - 更新相关文件的导入路径引用 - 添加新的批量操作组件导出文件 - 修复dashboard页面中的类型错误 - 添加dotenv依赖到package.json
218 lines
5.8 KiB
TypeScript
218 lines
5.8 KiB
TypeScript
/**
|
|
* [BE-CTL-019] 物流决策AI控制器
|
|
* 提供运费审核和物流延误的AI介入RESTful API接口
|
|
* AI注意: 所有物流AI决策必须通过此控制器
|
|
*/
|
|
|
|
import { Request, Response } from 'express';
|
|
import {
|
|
makeAIDecision,
|
|
DecisionType,
|
|
DecisionResult,
|
|
} from '../middleware/AIDecisionMiddleware';
|
|
import { logger } from '../../utils/logger';
|
|
|
|
export class LogisticsDecisionController {
|
|
/**
|
|
* POST /api/v1/logistics/ai-decision
|
|
* 生成物流AI决策
|
|
*/
|
|
static async makeDecision(req: Request, res: Response) {
|
|
try {
|
|
const { tenantId, operatorId, orderId, operationType, customParams } = req.body;
|
|
|
|
if (!tenantId || !operatorId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'MISSING_REQUIRED_FIELDS',
|
|
message: '缺少必要字段',
|
|
});
|
|
}
|
|
|
|
const aiDecision = makeAIDecision({
|
|
tenantId,
|
|
operatorId,
|
|
businessType: DecisionType.FREIGHT,
|
|
operationType: operationType || 'FREIGHT_AUDIT',
|
|
targetIds: orderId ? [orderId] : [],
|
|
customParams,
|
|
});
|
|
|
|
logger.info(`[LogisticsDecisionController] AI decision: ${aiDecision.requiredAction}`);
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: aiDecision,
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogisticsDecisionController] makeDecision error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'DECISION_ERROR',
|
|
message: '物流AI决策失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/logistics/freight/audit
|
|
* 审核运费
|
|
*/
|
|
static async auditFreight(req: Request, res: Response) {
|
|
try {
|
|
const { tenantId, orderId, expectedFreight, actualFreight, customParams } = req.body;
|
|
|
|
if (!tenantId || !orderId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'MISSING_REQUIRED_FIELDS',
|
|
message: '缺少必要字段',
|
|
});
|
|
}
|
|
|
|
const diff = actualFreight - expectedFreight;
|
|
const diffPercent = expectedFreight > 0 ? Math.abs(diff / expectedFreight) * 100 : 0;
|
|
|
|
const aiDecision = makeAIDecision({
|
|
tenantId,
|
|
operatorId: 'system',
|
|
businessType: DecisionType.FREIGHT,
|
|
operationType: 'FREIGHT_AUDIT',
|
|
targetIds: [orderId],
|
|
customParams: {
|
|
...customParams,
|
|
expectedFreight,
|
|
actualFreight,
|
|
diff,
|
|
diffPercent,
|
|
isAbnormal: diffPercent > 15,
|
|
},
|
|
});
|
|
|
|
if (aiDecision.requiredAction === DecisionResult.HUMAN_ONLY) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'AI_NOT_ALLOWED',
|
|
message: '此运费审核仅支持人工处理',
|
|
aiDecision,
|
|
});
|
|
}
|
|
|
|
const action = diffPercent <= 5 ? 'AUTO_PASS' :
|
|
diffPercent <= 15 ? 'NEED_REVIEW' : 'NEED_HUMAN_REVIEW';
|
|
|
|
logger.info(`[LogisticsDecisionController] Freight audit: ${orderId}, action: ${action}`);
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
orderId,
|
|
expectedFreight,
|
|
actualFreight,
|
|
diff,
|
|
diffPercent,
|
|
action,
|
|
aiDecision,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogisticsDecisionController] auditFreight error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'AUDIT_ERROR',
|
|
message: '运费审核失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/logistics/delay/warn
|
|
* 物流延误预警
|
|
*/
|
|
static async warnDelay(req: Request, res: Response) {
|
|
try {
|
|
const { tenantId, orderId, expectedDeliveryDate, currentStatus, delayRisk } = req.body;
|
|
|
|
if (!tenantId || !orderId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'MISSING_REQUIRED_FIELDS',
|
|
message: '缺少必要字段',
|
|
});
|
|
}
|
|
|
|
const aiDecision = makeAIDecision({
|
|
tenantId,
|
|
operatorId: 'system',
|
|
businessType: DecisionType.FREIGHT,
|
|
operationType: 'DELAY_WARNING',
|
|
targetIds: [orderId],
|
|
customParams: {
|
|
expectedDeliveryDate,
|
|
currentStatus,
|
|
delayRisk,
|
|
},
|
|
});
|
|
|
|
logger.info(`[LogisticsDecisionController] Delay warning: ${orderId}, risk: ${delayRisk}`);
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
orderId,
|
|
expectedDeliveryDate,
|
|
currentStatus,
|
|
delayRisk,
|
|
aiDecision,
|
|
recommendedAction: delayRisk > 0.7 ? 'NOTIFY_BUYER' : 'MONITOR',
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogisticsDecisionController] warnDelay error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'WARN_ERROR',
|
|
message: '延误预警失败',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/logistics/trace/:orderId
|
|
* 追踪物流
|
|
*/
|
|
static async traceLogistics(req: Request, res: Response) {
|
|
try {
|
|
const { orderId } = req.params;
|
|
|
|
if (!orderId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'MISSING_ORDER_ID',
|
|
message: '缺少订单ID',
|
|
});
|
|
}
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
orderId,
|
|
status: 'IN_TRANSIT',
|
|
events: [
|
|
{ timestamp: new Date(), location: 'Warehouse', status: 'SHIPPED' },
|
|
{ timestamp: new Date(), location: 'Distribution Center', status: 'IN_TRANSIT' },
|
|
],
|
|
estimatedDelivery: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error('[LogisticsDecisionController] traceLogistics error:', error);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'TRACE_ERROR',
|
|
message: '物流追踪失败',
|
|
});
|
|
}
|
|
}
|
|
}
|