paiban/NPP.SmartSchedue.Api/Services/Integration/TaskValidationService.cs
Asoka.Wang 21f044712c 1
2025-08-27 18:39:19 +08:00

677 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NPP.SmartSchedue.Api.Contracts;
using ZhonTai.Admin.Services;
using ZhonTai.DynamicApi;
using ZhonTai.DynamicApi.Attributes;
using NPP.SmartSchedue.Api.Contracts.Services.Integration;
using NPP.SmartSchedue.Api.Contracts.Services.Integration.Input;
using NPP.SmartSchedue.Api.Contracts.Services.Integration.Output;
using NPP.SmartSchedue.Api.Contracts.Core.Enums;
using NPP.SmartSchedue.Api.Contracts.Domain.Work;
using NPP.SmartSchedue.Api.Contracts.Domain.Equipment;
using NPP.SmartSchedue.Api.Contracts.Domain.Personnel;
using NPP.SmartSchedue.Api.Contracts.Domain.Time;
using NPP.SmartSchedue.Api.Repositories.Work;
namespace NPP.SmartSchedue.Api.Services.Integration
{
/// <summary>
/// 任务验证服务
/// 职责:专门负责任务数据完整性验证、业务规则检查和需求分析
/// 架构思考:集中处理所有验证逻辑,确保数据质量和业务规则一致性
/// </summary>
[DynamicApi(Area = "app")]
public class TaskValidationService : BaseService, ITaskValidationService
{
private readonly WorkOrderRepository _workOrderRepository;
private readonly IShiftRuleMappingRepository _shiftRuleMappingRepository;
private readonly IShiftRepository _shiftRepository;
private readonly IEquipmentRepository _equipmentRepository;
private readonly ILogger<TaskValidationService> _logger;
public TaskValidationService(
WorkOrderRepository workOrderRepository,
IShiftRuleMappingRepository shiftRuleMappingRepository,
IShiftRepository shiftRepository,
IEquipmentRepository equipmentRepository,
ILogger<TaskValidationService> logger)
{
_workOrderRepository = workOrderRepository;
_shiftRuleMappingRepository = shiftRuleMappingRepository ?? throw new ArgumentNullException(nameof(shiftRuleMappingRepository));
_shiftRepository = shiftRepository ?? throw new ArgumentNullException(nameof(shiftRepository));
_equipmentRepository = equipmentRepository ?? throw new ArgumentNullException(nameof(equipmentRepository));
_logger = logger;
}
/// <summary>
/// 分析任务需求
/// 深度业务思考按照文档1.1步骤进行完整的任务需求分析
/// </summary>
[HttpPost]
public async Task<TaskRequirementAnalysisResult> AnalyzeTaskRequirementsAsync(List<WorkOrderEntity> tasks)
{
var result = new TaskRequirementAnalysisResult
{
ProcessRequirements = new List<TaskProcessRequirement>(),
ResourceRequirements = new ResourceRequirement(),
TimeDistribution = new TimeDistribution(),
AnalysisSummary = ""
};
try
{
// 1.1.1 工序需求提取
var processReqs = ExtractProcessRequirements(tasks);
result.ProcessRequirements = processReqs.Select(p => new TaskProcessRequirement
{
TaskId = p.TaskId,
ProcessId = p.ProcessId,
RequiredQualifications = p.RequiredQualifications,
EstimatedDuration = p.EstimatedDuration,
EquipmentTypeId = p.EquipmentTypeId
}).ToList();
// 1.1.2 资源需求统计
result.ResourceRequirements = await StatisticalResourceRequirementsAsync(tasks);
// 生成分析摘要
result.AnalysisSummary = GenerateRequirementAnalysisSummary(result, tasks.Count);
_logger.LogInformation("任务需求分析完成: 总任务数={TaskCount}, 工序类型数={ProcessTypes}",
tasks.Count, result.ProcessRequirements.Select(p => p.ProcessId).Distinct().Count());
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "任务需求分析失败: {Message}", ex.Message);
result.AnalysisSummary = $"需求分析异常: {ex.Message}";
return result;
}
}
/// <summary>
/// 验证任务数据完整性
/// 深度业务思考按照文档1.2步骤进行全面的数据完整性检查
/// </summary>
[HttpPost]
public async Task<TaskDataValidationResult> ValidateTaskDataIntegrityAsync(List<WorkOrderEntity> tasks)
{
var result = new TaskDataValidationResult
{
IsValid = true,
ValidationIssues = new List<ValidationIssue>(),
BusinessRuleViolations = new List<BusinessRuleViolation>(),
ValidationSummary = ""
};
try
{
// 1.2.1 基础数据完整性检查
var dataIntegrityIssues = await ValidateBasicDataIntegrityAsync(tasks);
result.ValidationIssues.AddRange(dataIntegrityIssues);
// 1.2.2 业务规则预检查
var businessRuleViolations = await ValidateBusinessRulesAsync(tasks);
result.BusinessRuleViolations.AddRange(businessRuleViolations);
// 确定整体验证结果
result.IsValid = !result.ValidationIssues.Any() && !result.BusinessRuleViolations.Any();
result.ValidationSummary = GenerateValidationSummary(result, tasks.Count);
if (!result.IsValid)
{
_logger.LogWarning("任务数据验证发现问题: 完整性问题={IntegrityIssues}, 业务规则违反={BusinessRuleViolations}",
result.ValidationIssues.Count, result.BusinessRuleViolations.Count);
}
else
{
_logger.LogInformation("任务数据验证通过: 总任务数={TaskCount}", tasks.Count);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "任务数据验证失败: {Message}", ex.Message);
result.IsValid = false;
result.ValidationSummary = $"验证过程异常: {ex.Message}";
return result;
}
}
/// <summary>
/// 加载任务详细信息
/// 深度业务思考按照文档1.3步骤优化关联数据加载和结构化处理
/// </summary>
[HttpPost]
public async Task<TaskDetailLoadResult> LoadTaskDetailedInfoAsync(List<WorkOrderEntity> tasks)
{
var result = new TaskDetailLoadResult
{
TaskDetails = new List<TaskDetailInfo>(),
LoadedDataStatistics = new LoadedDataStatistics(),
OptimizationSummary = ""
};
try
{
var taskIds = tasks.Select(t => t.Id).ToList();
// 1.3.1 关联数据加载策略
var taskDetails = await LoadTaskAssociatedDataAsync(taskIds);
// 1.3.2 数据结构优化
var optimizedData = await OptimizeDataStructuresAsync(taskDetails);
result.TaskDetails = optimizedData.TaskDetails;
result.LoadedDataStatistics = optimizedData.Statistics;
result.OptimizationSummary = GenerateLoadingSummary(result, tasks.Count);
_logger.LogInformation("任务详细信息加载完成: 任务数={TaskCount}, 关联数据项={AssociatedDataCount}",
tasks.Count, result.LoadedDataStatistics.TotalAssociatedRecords);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "任务详细信息加载失败: {Message}", ex.Message);
result.OptimizationSummary = $"加载过程异常: {ex.Message}";
return result;
}
}
#region 1.1
/// <summary>
/// 内部工序需求结构
/// </summary>
private class InternalProcessRequirement
{
public long TaskId { get; set; }
public long ProcessId { get; set; }
public List<string> RequiredQualifications { get; set; } = new List<string>();
public TimeSpan? EstimatedDuration { get; set; }
public long? EquipmentTypeId { get; set; }
}
/// <summary>
/// 提取工序需求
/// 高级工程师修复:正确解析字符串格式的资质要求
/// ProcessEntity.QualificationRequirements 是逗号分隔的资质ID字符串
/// </summary>
private List<InternalProcessRequirement> ExtractProcessRequirements(List<WorkOrderEntity> tasks)
{
var result = new List<InternalProcessRequirement>(tasks.Count);
foreach (var task in tasks)
{
var requirement = new InternalProcessRequirement
{
TaskId = task.Id,
ProcessId = task.ProcessId,
RequiredQualifications = ParseQualificationIds(task.ProcessEntity?.QualificationRequirements),
EstimatedDuration = task.ProcessEntity?.EstimatedDuration,
EquipmentTypeId = task.ProcessEntity?.EquipmentTypeId
};
result.Add(requirement);
}
return result;
}
/// <summary>
/// 解析资质ID字符串
/// 高级工程师实现:严格的字符串解析和验证
/// </summary>
/// <param name="qualificationRequirements">逗号分隔的资质ID字符串如 "1,2,3"</param>
/// <returns>资质ID列表转换为字符串以保持兼容性</returns>
private List<string> ParseQualificationIds(string qualificationRequirements)
{
if (string.IsNullOrWhiteSpace(qualificationRequirements))
return new List<string>();
try
{
return qualificationRequirements
.Split(QualificationIdSeparators, StringSplitOptions.RemoveEmptyEntries)
.Select(id => id.Trim())
.Where(id => !string.IsNullOrWhiteSpace(id) && long.TryParse(id, out var numId) && numId > 0)
.ToList();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "解析工序资质要求字符串失败: {QualificationRequirements}", qualificationRequirements);
return new List<string>();
}
}
/// <summary>资质ID分隔符数组</summary>
private static readonly char[] QualificationIdSeparators = { ',', ';', '|' };
/// <summary>
/// 统计资源需求
/// 高级工程师修复:正确解析字符串格式的资质要求进行统计
/// 修复异步警告:改为同步方法,因为没有异步操作
/// </summary>
private Task<ResourceRequirement> StatisticalResourceRequirementsAsync(List<WorkOrderEntity> tasks)
{
var qualificationStats = new Dictionary<long, int>();
var equipmentTypeStats = new Dictionary<string, double>();
foreach (var task in tasks)
{
// 人员资质需求统计 - 修复从字符串解析资质ID
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.QualificationRequirements))
{
var qualificationIds = ParseQualificationIds(task.ProcessEntity.QualificationRequirements);
foreach (var qualificationIdStr in qualificationIds)
{
if (long.TryParse(qualificationIdStr, out var qualificationId))
{
qualificationStats[qualificationId] =
qualificationStats.GetValueOrDefault(qualificationId, 0) + 1;
}
}
}
// 设备类型需求统计
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.EquipmentType) )
{
var equipmentType = task.ProcessEntity.EquipmentType;
var duration = (double)(task.ProcessEntity?.TheoreticalDuration ?? (decimal)DefaultTaskDurationHours);
equipmentTypeStats[equipmentType] =
equipmentTypeStats.GetValueOrDefault(equipmentType, 0) + duration;
}
}
_logger.LogDebug("资源需求统计完成: 资质类型 {QualificationCount} 个,设备类型 {EquipmentCount} 个",
qualificationStats.Count, equipmentTypeStats.Count);
return Task.FromResult(new ResourceRequirement
{
QualificationRequirements = qualificationStats,
EquipmentTypeRequirements = equipmentTypeStats
});
}
/// <summary>默认任务持续时间(小时)</summary>
private const double DefaultTaskDurationHours = 8.0;
#endregion
#region 1.2
/// <summary>
/// 验证基础数据完整性
/// </summary>
private async Task<List<ValidationIssue>> ValidateBasicDataIntegrityAsync(List<WorkOrderEntity> tasks)
{
var validationIssues = new List<ValidationIssue>();
foreach (var task in tasks)
{
// 检查工序信息完整性
if (task.ProcessEntity == null)
{
validationIssues.Add(new ValidationIssue
{
TaskId = task.Id,
IssueType = ValidationIssueType.MissingProcess,
Description = "工序信息缺失",
Severity = ValidationSeverity.Error
});
}
// 检查班次信息
if (!task.ShiftId.HasValue || task.ShiftId == 0)
{
validationIssues.Add(new ValidationIssue
{
TaskId = task.Id,
IssueType = ValidationIssueType.MissingShift,
Description = "班次信息缺失",
Severity = ValidationSeverity.Error
});
}
// 检查项目编号
if (string.IsNullOrWhiteSpace(task.ProjectNumber))
{
validationIssues.Add(new ValidationIssue
{
TaskId = task.Id,
IssueType = ValidationIssueType.MissingProjectNumber,
Description = "项目编号缺失",
Severity = ValidationSeverity.Warning
});
}
}
return validationIssues;
}
/// <summary>
/// 验证业务规则
/// </summary>
private async Task<List<BusinessRuleViolation>> ValidateBusinessRulesAsync(List<WorkOrderEntity> tasks)
{
var violations = new List<BusinessRuleViolation>();
foreach (var task in tasks)
{
// 资质要求验证
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.QualificationRequirements))
{
var qualificationViolations = await ValidateQualificationRequirementsAsync(task);
violations.AddRange(qualificationViolations);
}
// 设备可用性预检
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.EquipmentType))
{
var equipmentViolations = await ValidateEquipmentAvailabilityAsync(task);
violations.AddRange(equipmentViolations);
}
}
return violations;
}
/// <summary>
/// 验证班次规则
/// </summary>
private async Task<List<BusinessRuleViolation>> ValidateShiftRulesAsync(WorkOrderEntity task)
{
var violations = new List<BusinessRuleViolation>();
// 暂时注释班次规则验证需要完善ShiftEntity导航属性
// if (task.ShiftEntity?.ShiftRules?.Any() == true)
// {
// foreach (var rule in task.ShiftEntity.ShiftRules)
// {
// // 检查任务时间是否符合班次规则
// if (rule.Type == ShiftRuleTypeEnum.WorkingHours)
// {
// var taskStartHour = task.PlannedStartTime.Hour;
// var taskEndHour = task.PlannedEndTime.Hour;
//
// if (taskStartHour < 6 || taskEndHour > 22) // 示例规则
// {
// violations.Add(new BusinessRuleViolation
// {
// TaskId = task.Id,
// RuleType = BusinessRuleType.ShiftRule,
// RuleDescription = $"任务时间超出班次工作时间范围",
// ViolationDetails = $"任务时间: {task.PlannedStartTime:HH:mm}-{task.PlannedEndTime:HH:mm}"
// });
// }
// }
// }
// }
return violations;
}
/// <summary>
/// 验证资质要求
/// </summary>
private async Task<List<BusinessRuleViolation>> ValidateQualificationRequirementsAsync(WorkOrderEntity task)
{
var violations = new List<BusinessRuleViolation>();
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.QualificationRequirements))
{
// 解析资质ID字符串并验证
var qualificationIds = ParseQualificationIds(task.ProcessEntity.QualificationRequirements);
foreach (var qualificationIdStr in qualificationIds)
{
if (!long.TryParse(qualificationIdStr, out var qualificationId) || qualificationId <= 0)
{
violations.Add(new BusinessRuleViolation
{
TaskId = task.Id,
RuleType = BusinessRuleType.QualificationRequirement,
RuleDescription = "无效的资质要求配置",
ViolationDetails = $"资质ID: {qualificationIdStr}"
});
}
}
}
return violations;
}
/// <summary>
/// 验证设备可用性
/// </summary>
private async Task<List<BusinessRuleViolation>> ValidateEquipmentAvailabilityAsync(WorkOrderEntity task)
{
var violations = new List<BusinessRuleViolation>();
if (!string.IsNullOrWhiteSpace(task.ProcessEntity?.EquipmentType))
{
// 使用 EquipmentRepository 检查指定设备类型是否有可用设备
var hasAvailableEquipment = await _equipmentRepository.HasAvailableEquipmentByTypeAsync(task.ProcessEntity.EquipmentType);
if (!hasAvailableEquipment)
{
violations.Add(new BusinessRuleViolation
{
TaskId = task.Id,
RuleType = BusinessRuleType.EquipmentAvailability,
RuleDescription = "所需设备类型暂无可用设备",
ViolationDetails = $"设备类型: {task.ProcessEntity.EquipmentType}"
});
}
}
return violations;
}
#endregion
#region 1.3
/// <summary>
/// 加载任务关联数据
/// 批量加载优化查询性能
/// </summary>
private async Task<List<TaskDetailInfo>> LoadTaskAssociatedDataAsync(List<long> taskIds)
{
var taskDetails = await _workOrderRepository.Select
.Where(w => taskIds.Contains(w.Id))
.IncludeMany(w => w.WorkOrderFLPersonnels)
.Include(w => w.ShiftEntity)
.Include(w => w.ProcessEntity)
.ToListAsync();
// 批量获取班次规则数据
var shiftIds = taskDetails.Where(t => t.ShiftId.HasValue).Select(t => t.ShiftId.Value).Distinct().ToList();
var shiftRulesMap = await _shiftRuleMappingRepository.GetBatchShiftRulesAsync(shiftIds);
return taskDetails.Select(task => new TaskDetailInfo
{
TaskId = task.Id,
TaskCode = task.Code,
ShiftRules = task.ShiftId.HasValue && shiftRulesMap.ContainsKey(task.ShiftId.Value)
? shiftRulesMap[task.ShiftId.Value]
: new List<ShiftRuleEntity>(),
QualificationRequirements = ConvertQualificationRequirements(task.ProcessEntity?.QualificationRequirements),
FLPersonnelRelations = task.WorkOrderFLPersonnels?.ToList() ?? new List<WorkOrderFLPersonnelEntity>()
}).ToList();
}
/// <summary>
/// 优化数据结构
/// </summary>
private async Task<(List<TaskDetailInfo> TaskDetails, LoadedDataStatistics Statistics)> OptimizeDataStructuresAsync(
List<TaskDetailInfo> taskDetails)
{
var statistics = new LoadedDataStatistics
{
TotalTasks = taskDetails.Count,
TotalAssociatedRecords = 0,
ShiftRulesCount = 0,
QualificationRequirementsCount = 0,
FLPersonnelRelationsCount = 0,
SpecifiedConstraintsCount = 0
};
// 构建快速查询索引
var shiftRuleIndex = new Dictionary<long, List<ShiftRuleEntity>>();
var qualificationIndex = new Dictionary<long, List<NPP.SmartSchedue.Api.Contracts.Services.Integration.Output.QualificationRequirement>>();
foreach (var detail in taskDetails)
{
// 班次规则解析和索引
if (detail.ShiftRules.Any())
{
shiftRuleIndex[detail.TaskId] = detail.ShiftRules;
statistics.ShiftRulesCount += detail.ShiftRules.Count;
}
// 资质要求映射和索引
if (detail.QualificationRequirements.Any())
{
qualificationIndex[detail.TaskId] = detail.QualificationRequirements;
statistics.QualificationRequirementsCount += detail.QualificationRequirements.Count;
}
// FL人员关系计数
if (detail.FLPersonnelRelations.Any())
{
statistics.FLPersonnelRelationsCount += detail.FLPersonnelRelations.Count;
}
// 指定约束计数(暂时不统计,因为没有指定约束)
statistics.SpecifiedConstraintsCount = 0;
}
statistics.TotalAssociatedRecords = statistics.ShiftRulesCount +
statistics.QualificationRequirementsCount +
statistics.FLPersonnelRelationsCount +
statistics.SpecifiedConstraintsCount;
return (taskDetails, statistics);
}
/// <summary>
/// 转换资质要求为输出格式
/// 深度业务思考:统一资质要求的数据格式,便于后续验证和分配使用
/// 高级工程师优化:
/// 1. 正确解析字符串格式的资质ID逗号分隔
/// 2. 严格的空值和异常处理
/// 3. 业务规则验证
/// 4. 性能优化
/// 5. 常量化硬编码值
/// </summary>
private List<QualificationRequirement> ConvertQualificationRequirements(
string qualificationRequirementsStr)
{
// 防御性编程:空字符串处理
if (string.IsNullOrWhiteSpace(qualificationRequirementsStr))
{
_logger.LogDebug("工序资质要求字符串为空,返回空结果");
return new List<QualificationRequirement>();
}
try
{
// 解析资质ID字符串
var qualificationIds = ParseQualificationIds(qualificationRequirementsStr);
if (!qualificationIds.Any())
{
_logger.LogDebug("工序资质要求字符串解析后为空: {QualificationRequirements}", qualificationRequirementsStr);
return new List<QualificationRequirement>();
}
var result = new List<QualificationRequirement>(qualificationIds.Count);
foreach (var qualificationIdStr in qualificationIds)
{
if (!long.TryParse(qualificationIdStr, out var qualificationId) || qualificationId <= 0)
{
_logger.LogWarning("发现无效的资质ID字符串: {QualificationId},已跳过", qualificationIdStr);
continue;
}
var qualification = new QualificationRequirement
{
QualificationId = qualificationId,
QualificationName = $"资质{qualificationId}", // 临时名称,实际应用中需要查询资质表
};
result.Add(qualification);
}
_logger.LogDebug("成功转换 {Count} 个工序资质要求", result.Count);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "转换工序资质要求时发生异常: {QualificationRequirements}", qualificationRequirementsStr);
return new List<QualificationRequirement>();
}
}
#region
/// <summary>默认资质等级</summary>
private const int DefaultQualificationLevel = 1;
#endregion
#endregion
#region
/// <summary>
/// 生成需求分析摘要
/// </summary>
private string GenerateRequirementAnalysisSummary(TaskRequirementAnalysisResult result, int totalTasks)
{
var processTypeCount = result.ProcessRequirements.Select(p => p.ProcessId).Distinct().Count();
var qualificationTypeCount = result.ResourceRequirements.QualificationRequirements.Count;
var equipmentTypeCount = result.ResourceRequirements.EquipmentTypeRequirements.Count;
return $"需求分析完成: 总任务{totalTasks}个, 涉及{processTypeCount}种工序, " +
$"{qualificationTypeCount}种资质需求, {equipmentTypeCount}种设备类型需求。";
}
/// <summary>
/// 生成验证摘要
/// </summary>
private string GenerateValidationSummary(TaskDataValidationResult result, int totalTasks)
{
if (result.IsValid)
{
return $"数据验证通过: 总任务{totalTasks}个, 数据完整性和业务规则均符合要求。";
}
else
{
return $"数据验证发现问题: 总任务{totalTasks}个, " +
$"完整性问题{result.ValidationIssues.Count}个, " +
$"业务规则违反{result.BusinessRuleViolations.Count}个。";
}
}
/// <summary>
/// 生成加载摘要
/// </summary>
private string GenerateLoadingSummary(TaskDetailLoadResult result, int totalTasks)
{
return $"详细信息加载完成: 任务{totalTasks}个, " +
$"关联数据{result.LoadedDataStatistics.TotalAssociatedRecords}条, " +
$"包含班次规则{result.LoadedDataStatistics.ShiftRulesCount}条, " +
$"资质要求{result.LoadedDataStatistics.QualificationRequirementsCount}条。";
}
#endregion
}
}