using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using ZhonTai.Admin.Core.Dto; using ZhonTai.Admin.Services; 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.Services.Work; using NPP.SmartSchedue.Api.Contracts.Services.Personnel; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Contracts.Domain.Time; using NPP.SmartSchedue.Api.Contracts.Domain.Personnel; using NPP.SmartSchedue.Api.Contracts.Domain.Equipment; using NPP.SmartSchedue.Api.Repositories.Work; using NPP.SmartSchedue.Api.Contracts; using ZhonTai.DynamicApi; using ZhonTai.DynamicApi.Attributes; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 任务整合前自检服务 /// [DynamicApi(Area = "app")] public class TaskIntegrationPreCheckService : BaseService, ITaskIntegrationPreCheckService, IDynamicApi { private readonly WorkOrderRepository _workOrderRepository; private readonly IPersonService _personService; private readonly IWorkOrderService _workOrderService; private readonly IEquipmentRepository _equipmentRepository; private readonly IEquipmentMaintenanceRepository _equipmentMaintenanceRepository; private readonly IEquipmentCalibrationRepository _equipmentCalibrationRepository; private readonly IQualificationRepository _qualificationRepository; public TaskIntegrationPreCheckService( WorkOrderRepository workOrderRepository, IPersonService personService, IWorkOrderService workOrderService, IEquipmentRepository equipmentRepository, IEquipmentMaintenanceRepository equipmentMaintenanceRepository, IEquipmentCalibrationRepository equipmentCalibrationRepository, IQualificationRepository qualificationRepository) { _workOrderRepository = workOrderRepository ?? throw new ArgumentNullException(nameof(workOrderRepository)); _personService = personService ?? throw new ArgumentNullException(nameof(personService)); _workOrderService = workOrderService ?? throw new ArgumentNullException(nameof(workOrderService)); _equipmentRepository = equipmentRepository ?? throw new ArgumentNullException(nameof(equipmentRepository)); _equipmentMaintenanceRepository = equipmentMaintenanceRepository ?? throw new ArgumentNullException(nameof(equipmentMaintenanceRepository)); _equipmentCalibrationRepository = equipmentCalibrationRepository ?? throw new ArgumentNullException(nameof(equipmentCalibrationRepository)); _qualificationRepository = qualificationRepository ?? throw new ArgumentNullException(nameof(qualificationRepository)); } /// /// 加载待整合的任务列表,支持多项目号筛选,按项目号分组展示 /// [HttpPost] public async Task LoadPendingIntegrationTasksAsync(LoadPendingTasksInput input) { try { // 业务参数验证 if (input == null) throw new ArgumentNullException(nameof(input), "加载任务输入参数不能为空"); // 日期范围合理性验证 if (input.StartDate.HasValue && input.EndDate.HasValue && input.StartDate > input.EndDate) throw new ArgumentException("开始日期不能晚于结束日期", nameof(input.StartDate)); // 时间范围过大保护(防止性能问题) if (input.StartDate.HasValue && input.EndDate.HasValue) { var timeSpan = input.EndDate.Value - input.StartDate.Value; if (timeSpan.TotalDays > 365) throw new ArgumentException("查询时间范围不能超过365天", nameof(input.StartDate)); } // 构建查询条件 var query = _workOrderRepository.Select .Where(wo => wo.Status == (int)input.Status); // 项目号筛选 if (input.ProjectNumbers?.Any() == true) { query = query.Where(wo => input.ProjectNumbers.Contains(wo.ProjectNumber)); } // 日期范围筛选 if (input.StartDate.HasValue) { query = query.Where(wo => wo.WorkOrderDate >= input.StartDate.Value); } if (input.EndDate.HasValue) { query = query.Where(wo => wo.WorkOrderDate <= input.EndDate.Value); } // 工序筛选 if (input.ProcessCodes?.Any() == true) { query = query.Where(wo => input.ProcessCodes.Contains(wo.ProcessCode)); } // 班次筛选 if (input.ShiftIds?.Any() == true) { query = query.Where(wo => wo.ShiftId.HasValue && input.ShiftIds.Contains(wo.ShiftId.Value)); } // 执行查询获取任务列表 var workOrders = await query .OrderBy(wo => wo.ProjectNumber) .OrderBy(wo => wo.WorkOrderDate) .OrderBy(wo => wo.Priority) .ToListAsync(); // 数据完整性检查 if (workOrders.Any(wo => string.IsNullOrEmpty(wo.ProjectNumber))) { throw new InvalidOperationException("发现项目号为空的任务,数据不完整,无法进行分组"); } // 按项目号分组 var projectGroups = workOrders .GroupBy(wo => wo.ProjectNumber) .Select(g => new ProjectTaskGroup { ProjectNumber = g.Key, ProjectName = g.Key, // 可以扩展从项目管理模块获取项目名称 ProjectCategory = g.First().ProjectCategory ?? "未分类", TaskCount = g.Count(), EstimatedTotalHours = g.Sum(wo => wo.EstimatedHours ?? 0), EarliestStartDate = g.Min(wo => wo.WorkOrderDate), LatestEndDate = g.Max(wo => wo.WorkOrderDate), Tasks = g.Select(wo => new WorkOrderSummary { Id = wo.Id, WorkOrderCode = wo.WorkOrderCode ?? $"WO_{wo.Id}", WorkOrderDate = wo.WorkOrderDate, ShiftId = wo.ShiftId ?? 0, ShiftName = wo.ShiftName ?? "未知班次", ShiftCode = wo.ShiftCode ?? "UNKNOWN", ProcessId = wo.ProcessId, ProcessName = wo.ProcessName ?? "未知工序", ProcessCode = wo.ProcessCode ?? "UNKNOWN", EstimatedHours = Math.Max(0, wo.EstimatedHours ?? 0), // 确保工时不为负数 Status = wo.Status, Priority = Math.Max(0, wo.Priority), // 确保优先级不为负数 UrgencyLevel = Math.Max(0, wo.Urgency), // 确保紧急度不为负数 ComplexityLevel = Math.Max(0, wo.Complexity), // 确保复杂度不为负数 HasAssignedPersonnel = wo.AssignedPersonnelId.HasValue, HasAssignedEquipment = wo.AssignedEquipmentId.HasValue }).ToList() }) .OrderBy(pg => pg.ProjectNumber) // 确保项目分组有序 .ToList(); return new TaskIntegrationListOutput { ProjectTaskGroups = projectGroups, TotalTaskCount = workOrders.Count, TotalProjectCount = projectGroups.Count }; } catch (Exception ex) { throw new Exception($"加载待整合任务列表失败: {ex.Message}", ex); } } /// /// 对勾选的项目和任务进行整合前自检 /// [HttpPost] public async Task ExecutePreCheckAsync(TaskIntegrationPreCheckInput input) { try { // 输入参数完整性验证 if (input == null) throw new ArgumentNullException(nameof(input), "自检输入参数不能为空"); // 检查是否有选择项目或任务 if ((input.SelectedProjectNumbers?.Any() != true) && (input.SelectedTaskIds?.Any() != true)) throw new ArgumentException("必须至少选择一个项目或任务进行自检", nameof(input)); // 检查时间范围合理性 if (input.CheckStartDate > input.CheckEndDate) throw new ArgumentException("检查开始时间不能晚于结束时间", nameof(input.CheckStartDate)); // 时间范围过大保护 var timeSpan = input.CheckEndDate - input.CheckStartDate; if (timeSpan.TotalDays > 90) throw new ArgumentException("检查时间范围不能超过90天", nameof(input.CheckStartDate)); var result = new TaskIntegrationPreCheckResult { CheckedProjectCount = input.SelectedProjectNumbers?.Count ?? 0, CheckedTaskCount = input.SelectedTaskIds?.Count ?? 0, CheckTime = DateTime.Now }; var projectResourceResults = new List(); // 如果选择了项目号,按项目进行检查 if (input.SelectedProjectNumbers?.Any() == true) { foreach (var projectNumber in input.SelectedProjectNumbers) { var projectResult = await CheckProjectResourceSufficiencyAsync( projectNumber, input.CheckStartDate, input.CheckEndDate, input.CheckPersonnelSufficiency, input.CheckEquipmentSufficiency); projectResourceResults.Add(projectResult); } } // 如果选择了具体任务,按任务所属项目进行检查 if (input.SelectedTaskIds?.Any() == true) { var selectedTasks = await _workOrderRepository.Select .Where(wo => input.SelectedTaskIds.Contains(wo.Id)) .ToListAsync(); var taskProjectGroups = selectedTasks.GroupBy(t => t.ProjectNumber); foreach (var group in taskProjectGroups) { if (!projectResourceResults.Any(pr => pr.ProjectNumber == group.Key)) { var projectResult = await CheckProjectResourceSufficiencyAsync( group.Key, input.CheckStartDate, input.CheckEndDate, input.CheckPersonnelSufficiency, input.CheckEquipmentSufficiency); projectResourceResults.Add(projectResult); } } } result.ProjectResourceResults = projectResourceResults; // 判断整体检查是否通过 result.IsCheckPassed = projectResourceResults.All(pr => pr.IsPersonnelSufficient && pr.IsEquipmentSufficient); // 生成检查摘要 var passedProjects = projectResourceResults.Count(pr => pr.IsPersonnelSufficient && pr.IsEquipmentSufficient); var totalProjects = projectResourceResults.Count; result.CheckSummary = $"检查完成。项目总数: {totalProjects},通过: {passedProjects},未通过: {totalProjects - passedProjects}"; // 收集警告和错误信息 foreach (var projectResult in projectResourceResults) { if (!projectResult.IsPersonnelSufficient) { result.Warnings.Add($"项目 {projectResult.ProjectNumber} 人员资源不足"); } if (!projectResult.IsEquipmentSufficient) { result.Warnings.Add($"项目 {projectResult.ProjectNumber} 设备资源不足"); } } return result; } catch (Exception ex) { return new TaskIntegrationPreCheckResult { IsCheckPassed = false, CheckTime = DateTime.Now, Errors = new List { $"自检执行失败: {ex.Message}" } }; } } /// /// 获取项目任务数量与可用人员数量对比(按日和周) /// [HttpPost] public async Task GetProjectResourceComparisonAsync(ProjectResourceComparisonInput input) { try { // 输入参数验证 if (input == null) throw new ArgumentNullException(nameof(input), "项目资源对比输入参数不能为空"); if (input.ProjectNumbers?.Any() != true) throw new ArgumentException("必须至少指定一个项目号进行资源对比", nameof(input.ProjectNumbers)); if (input.StartDate > input.EndDate) throw new ArgumentException("开始日期不能晚于结束日期", nameof(input.StartDate)); // 项目数量限制保护 if (input.ProjectNumbers.Count > 50) throw new ArgumentException("单次对比的项目数量不能超过50个", nameof(input.ProjectNumbers)); var result = new ProjectResourceComparisonResult { ComparisonDimension = input.Dimension.ToString(), StartDate = input.StartDate, EndDate = input.EndDate }; var projectComparisons = new List(); foreach (var projectNumber in input.ProjectNumbers) { var comparison = await AnalyzeProjectResourceComparisonAsync( projectNumber, input.StartDate, input.EndDate, input.Dimension == ComparisonDimension.Daily, input.Dimension == ComparisonDimension.Weekly); projectComparisons.Add(comparison); } result.ProjectComparisons = projectComparisons; // 计算总体资源充足性 result.OverallSufficiency = projectComparisons.All(pc => pc.ResourceStatus == "充足"); // 计算资源紧张度评分 var totalTensionScore = projectComparisons.Sum(pc => CalculateResourceTensionScore(pc)); result.ResourceTensionScore = projectComparisons.Any() ? (int)(totalTensionScore / projectComparisons.Count) : 0; return result; } catch (Exception ex) { throw new Exception($"获取项目资源对比失败: {ex.Message}", ex); } } #region 私有方法 /// /// 检查项目资源充足性 /// private async Task CheckProjectResourceSufficiencyAsync( string projectNumber, DateTime startDate, DateTime endDate, bool checkPersonnel, bool checkEquipment) { // 参数验证 if (string.IsNullOrWhiteSpace(projectNumber)) throw new ArgumentException("项目号不能为空", nameof(projectNumber)); if (startDate > endDate) throw new ArgumentException("开始日期不能晚于结束日期", nameof(startDate)); var result = new ProjectResourceSufficiencyResult { ProjectNumber = projectNumber, ProjectName = projectNumber, // 可以从项目管理模块获取项目名称 IsPersonnelSufficient = true, IsEquipmentSufficient = true, ResourceAnalysis = new ProjectResourceAnalysis() }; try { // 获取项目在指定时间范围内的任务 var projectTasks = await _workOrderRepository.Select .Where(wo => wo.ProjectNumber == projectNumber) .Where(wo => wo.WorkOrderDate >= startDate && wo.WorkOrderDate <= endDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingIntegration) .ToListAsync(); if (!projectTasks.Any()) { return result; } // 按日分析资源需求 var dailyDemands = await AnalyzeDailyResourceDemandAsync(projectTasks, checkPersonnel, checkEquipment); result.ResourceAnalysis.DailyDemands = dailyDemands; // 按周分析资源需求 var weeklyDemands = await AnalyzeWeeklyResourceDemandAsync(projectTasks, checkPersonnel, checkEquipment); result.ResourceAnalysis.WeeklyDemands = weeklyDemands; // 判断人员资源充足性 if (checkPersonnel) { result.IsPersonnelSufficient = dailyDemands.All(d => d.IsSufficient) && weeklyDemands.All(w => w.IsSufficient); } // 判断设备资源充足性 if (checkEquipment) { result.IsEquipmentSufficient = dailyDemands.All(d => d.AvailableEquipmentCount >= d.RequiredEquipmentCount) && weeklyDemands.All(w => w.AvailableEquipmentCount >= w.RequiredEquipmentCount); } return result; } catch (Exception ex) { result.IsPersonnelSufficient = false; result.IsEquipmentSufficient = false; throw new Exception($"检查项目 {projectNumber} 资源充足性失败: {ex.Message}", ex); } } /// /// 分析日资源需求 /// private async Task> AnalyzeDailyResourceDemandAsync( List projectTasks, bool checkPersonnel, bool checkEquipment) { var dailyDemands = new List(); var taskGroups = projectTasks.GroupBy(t => t.WorkOrderDate.Date); foreach (var group in taskGroups) { var date = group.Key; var tasksOnDate = group.ToList(); var demand = new DailyResourceDemand { Date = date, RequiredPersonnelCount = CalculateRequiredPersonnelCountForDay(tasksOnDate), RequiredEquipmentCount = CalculateRequiredEquipmentCountForDay(tasksOnDate), EstimatedWorkHours = tasksOnDate.Sum(t => t.EstimatedHours ?? 0) }; if (checkPersonnel) { // 获取该日期可用人员数量(这里需要调用人员服务) demand.AvailablePersonnelCount = await GetAvailablePersonnelCountAsync(date); } if (checkEquipment) { // 获取该日期可用设备数量(这里需要调用设备服务) demand.AvailableEquipmentCount = await GetAvailableEquipmentCountAsync(date); } dailyDemands.Add(demand); } return dailyDemands; } /// /// 分析周资源需求 /// private async Task> AnalyzeWeeklyResourceDemandAsync( List projectTasks, bool checkPersonnel, bool checkEquipment) { var weeklyDemands = new List(); // 按周分组任务 var weekGroups = projectTasks .GroupBy(t => GetWeekStartDate(t.WorkOrderDate.Date)) .OrderBy(g => g.Key); foreach (var group in weekGroups) { var weekStart = group.Key; var weekEnd = weekStart.AddDays(6); var tasksInWeek = group.ToList(); var demand = new WeeklyResourceDemand { WeekStartDate = weekStart, WeekEndDate = weekEnd, RequiredPersonnelCount = CalculateRequiredPersonnelCount(tasksInWeek), RequiredEquipmentCount = CalculateRequiredEquipmentCount(tasksInWeek), EstimatedWorkHours = tasksInWeek.Sum(t => t.EstimatedHours ?? 0) }; if (checkPersonnel) { demand.AvailablePersonnelCount = await GetAvailablePersonnelCountForWeekAsync(weekStart, weekEnd); } if (checkEquipment) { demand.AvailableEquipmentCount = await GetAvailableEquipmentCountForWeekAsync(weekStart, weekEnd); } weeklyDemands.Add(demand); } return weeklyDemands; } /// /// 分析项目资源对比 /// private async Task AnalyzeProjectResourceComparisonAsync( string projectNumber, DateTime startDate, DateTime endDate, bool compareByDay, bool compareByWeek) { var projectTasks = await _workOrderRepository.Select .Where(wo => wo.ProjectNumber == projectNumber) .Where(wo => wo.WorkOrderDate >= startDate && wo.WorkOrderDate <= endDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingIntegration) .ToListAsync(); var comparison = new ProjectResourceComparison { ProjectNumber = projectNumber, ProjectName = projectNumber, TotalTaskCount = projectTasks.Count, TotalEstimatedHours = projectTasks.Sum(t => t.EstimatedHours ?? 0) }; if (projectTasks.Any()) { // 计算人员需求峰值 if (compareByDay) { var dailyTaskCounts = projectTasks .GroupBy(t => t.WorkOrderDate.Date) .Select(g => g.Count()); comparison.PeakPersonnelDemand = dailyTaskCounts.Max(); } // 获取可用人员和设备容量 comparison.AvailablePersonnelCapacity = await GetAvailablePersonnelCapacityAsync(startDate, endDate); comparison.AvailableEquipmentCapacity = await GetAvailableEquipmentCapacityAsync(startDate, endDate); // 判断资源状态 var personnelRatio = comparison.AvailablePersonnelCapacity > 0 ? (double)comparison.PeakPersonnelDemand / comparison.AvailablePersonnelCapacity : 1.0; if (personnelRatio <= 0.7) comparison.ResourceStatus = "充足"; else if (personnelRatio <= 0.9) comparison.ResourceStatus = "紧张"; else comparison.ResourceStatus = "不足"; // 生成建议 comparison.Recommendations = GenerateResourceRecommendations(comparison); } return comparison; } /// /// 生成资源建议 /// private List GenerateResourceRecommendations(ProjectResourceComparison comparison) { var recommendations = new List(); if (comparison.ResourceStatus == "不足") { recommendations.Add("建议增加人员配置或调整任务优先级"); recommendations.Add("考虑延长项目周期以平衡资源负载"); } else if (comparison.ResourceStatus == "紧张") { recommendations.Add("建议优化任务分配,避免资源冲突"); recommendations.Add("密切关注人员工作负荷,防止过度疲劳"); } else { recommendations.Add("资源配置良好,可以按计划执行"); } return recommendations; } /// /// 计算资源紧张度评分 /// private int CalculateResourceTensionScore(ProjectResourceComparison comparison) { if (comparison.AvailablePersonnelCapacity == 0) return 100; var ratio = (double)comparison.PeakPersonnelDemand / comparison.AvailablePersonnelCapacity; return Math.Min(100, (int)(ratio * 100)); } /// /// 获取指定日期可用人员数量 /// 综合考虑班次安排、人员资质、请假状态、工作量限制等因素 /// private async Task GetAvailablePersonnelCountAsync(DateTime date) { try { // 1. 获取该日期的所有班次 var shifts = await _workOrderRepository.Orm.Select() .Where(s => s.IsDeleted == false) .ToListAsync(); if (!shifts.Any()) { return 0; // 没有班次安排 } // 2. 获取所有资质ID,用于统计全部人员数量 var allQualifications = await _qualificationRepository.Select .Where(q => q.IsDeleted == false) .ToListAsync(); var allQualificationIds = allQualifications.Any() ? string.Join(",", allQualifications.Select(q => q.Id)) : string.Empty; // 如果没有资质定义,传空字符串 int totalAvailableCount = 0; // 3. 按班次计算可用人员数量 foreach (var shift in shifts) { // 构造该班次的实际工作时间段 var workStartTime = date.Date.Add(shift.StartTime); // 调用人员服务获取该班次可用人员 // 传入所有资质ID,以获取具备任何资质的人员总数 var availablePersonnel = await _personService.GetAvailablePersonnelAsync( allQualificationIds, // 传入所有资质ID shift.Id, workStartTime); totalAvailableCount += availablePersonnel.Count; } return totalAvailableCount; } catch (Exception) { return 5; // 保守的最小人员数量 } } /// /// 获取指定日期可用设备数量 /// 综合考虑设备状态、维护计划、校验计划、使用情况等因素 /// 通过设备Repository直接查询数据库获取真实数据 /// private async Task GetAvailableEquipmentCountAsync(DateTime date) { try { // 1. 获取该日期所有可用的设备(状态为正常的设备) var availableEquipmentList = await _equipmentRepository.GetAvailableEquipmentAsync(date); if (availableEquipmentList == null || !availableEquipmentList.Any()) { return 0; // 没有可用设备 } var baseEquipmentCount = availableEquipmentList.Count; // 2. 获取当日需要维护的设备ID列表 var maintenanceEquipmentIds = await _equipmentMaintenanceRepository.GetMaintenanceEquipmentIdsAsync(date); var maintenanceEquipmentCount = maintenanceEquipmentIds?.Count ?? 0; // 3. 获取当日需要校验的设备ID列表 var calibrationEquipmentIds = await _equipmentCalibrationRepository.GetCalibrationEquipmentIdsAsync(date); var calibrationEquipmentCount = calibrationEquipmentIds?.Count ?? 0; // 5. 计算最终可用设备数量 // 业务逻辑:基础可用设备数 - 维护设备数 - 校验设备数 - 故障设备数 // 注意:避免重复扣减,因为故障设备已经在基础可用设备统计中被排除 var finalAvailableCount = baseEquipmentCount - maintenanceEquipmentCount - calibrationEquipmentCount; // 6. 数据合理性检查:确保返回值不为负数 var result = Math.Max(0, finalAvailableCount); // 7. 业务验证:如果计算结果异常,进行日志记录和保护性处理 if (result == 0 && baseEquipmentCount > 0) { // 所有设备都被占用的情况,可能需要人工干预 // TODO: 记录警告日志,便于运维人员关注 return 1; // 返回最小可用数量,避免完全阻塞 } return result; } catch (Exception) { // 异常处理:记录详细错误信息,返回保守估计值 // TODO: 集成日志系统记录异常详情 // 降级策略:当数据库查询异常时,返回基于历史数据的保守估计 return await GetFallbackEquipmentCountAsync(date); } } /// /// 获取指定周可用人员数量 /// 基于每日可用人员数量,计算一周内的平均可用人员数量 /// private async Task GetAvailablePersonnelCountForWeekAsync(DateTime weekStart, DateTime weekEnd) { try { var dailyCounts = new List(); // 遍历一周内每一天,计算每日可用人员数量 for (var date = weekStart; date <= weekEnd; date = date.AddDays(1)) { var dailyCount = await GetAvailablePersonnelCountAsync(date); dailyCounts.Add(dailyCount); } // 返回一周内的平均可用人员数量 return dailyCounts.Any() ? (int)Math.Ceiling(dailyCounts.Average()) : 0; } catch (Exception) { return 30; // 保守估计的周平均人员数量 } } /// /// 获取指定周可用设备数量 /// 基于每日可用设备数量,计算一周内的平均可用设备数量 /// private async Task GetAvailableEquipmentCountForWeekAsync(DateTime weekStart, DateTime weekEnd) { try { var dailyCounts = new List(); // 遍历一周内每一天,计算每日可用设备数量 for (var date = weekStart; date <= weekEnd; date = date.AddDays(1)) { var dailyCount = await GetAvailableEquipmentCountAsync(date); dailyCounts.Add(dailyCount); } // 返回一周内的平均可用设备数量 return dailyCounts.Any() ? (int)Math.Ceiling(dailyCounts.Average()) : 0; } catch (Exception) { return 20; // 保守估计的周平均设备数量 } } /// /// 获取人员可用容量 /// 计算指定时间范围内的人员总工作容量(人天) /// private async Task GetAvailablePersonnelCapacityAsync(DateTime startDate, DateTime endDate) { try { var totalCapacity = 0; // 按日计算人员容量,累加得到总容量 for (var date = startDate; date <= endDate; date = date.AddDays(1)) { var dailyPersonnelCount = await GetAvailablePersonnelCountAsync(date); // 假设每人每天工作8小时,将人员数量转换为工作容量 var dailyCapacity = dailyPersonnelCount * 8; // 8小时/人/天 totalCapacity += dailyCapacity; } // 返回总工作容量(工时) return totalCapacity; } catch (Exception) { // 异常时返回基于天数的保守估计 var days = (endDate - startDate).Days + 1; return days * 5 * 8; // 5人 * 8小时 * 天数 } } /// /// 获取设备可用容量 /// 计算指定时间范围内的设备总工作容量(设备时) /// private async Task GetAvailableEquipmentCapacityAsync(DateTime startDate, DateTime endDate) { try { var totalCapacity = 0; // 按日计算设备容量,累加得到总容量 for (var date = startDate; date <= endDate; date = date.AddDays(1)) { var dailyEquipmentCount = await GetAvailableEquipmentCountAsync(date); // 假设每台设备每天可运行16小时(两班制) var dailyCapacity = dailyEquipmentCount * 16; // 16小时/设备/天 totalCapacity += dailyCapacity; } // 返回总设备容量(设备时) return totalCapacity; } catch (Exception) { // 异常时返回基于天数的保守估计 var days = (endDate - startDate).Days + 1; return days * 3 * 16; // 3台设备 * 16小时 * 天数 } } /// /// 计算所需人员数量(基于业务规则的智能估算) /// private int CalculateRequiredPersonnelCount(List tasks) { if (!tasks.Any()) return 0; // 按日期分组,计算每日最大并发人员需求 var dailyRequirements = tasks .GroupBy(t => t.WorkOrderDate.Date) .Select(dayGroup => { var dayTasks = dayGroup.ToList(); // 1. 基础需求:每个任务至少需要1个人员 var baseRequirement = dayTasks.Count; // 2. 复杂度调整:高复杂度任务可能需要额外人员 var complexityAdjustment = dayTasks .Where(t => t.Complexity >= 8) // 高复杂度任务(优先级>=8) .Count() * 0.5; // 每个高复杂度任务增加0.5个人员需求 // 3. 紧急度调整:紧急任务可能需要备用人员 var urgencyAdjustment = dayTasks .Where(t => t.Urgency >= 8) // 高紧急度任务 .Count() * 0.3; // 每个紧急任务增加0.3个人员需求 // 4. 班次覆盖:不同班次需要独立人员配置 var shiftCount = dayTasks.Select(t => t.ShiftId).Distinct().Count(); var shiftMultiplier = Math.Max(1, shiftCount * 0.8); // 多班次系数 // 5. FL人员考虑:需要额外的FL(FirstLine)人员支持 var flRequirement = Math.Ceiling(dayTasks.Count / 5.0); // 每5个任务配1个FL人员 // 综合计算每日人员需求 var totalDailyRequirement = (baseRequirement + complexityAdjustment + urgencyAdjustment) * shiftMultiplier + flRequirement; return (int)Math.Ceiling(totalDailyRequirement); }) .ToList(); // 返回一周内的最大日需求量(峰值需求) return dailyRequirements.Any() ? dailyRequirements.Max() : 0; } /// /// 计算所需设备数量(基于任务特性的智能估算) /// private int CalculateRequiredEquipmentCount(List tasks) { if (!tasks.Any()) return 0; // 按日期分组,计算每日最大设备需求 var dailyEquipmentRequirements = tasks .GroupBy(t => t.WorkOrderDate.Date) .Select(dayGroup => { var dayTasks = dayGroup.ToList(); // 1. 基础需求:大多数任务需要专用设备 var baseEquipmentNeed = dayTasks.Count; // 2. 设备共享优化:某些设备可以在不同时间段共享 var shiftGroups = dayTasks.GroupBy(t => t.ShiftId); var optimizedCount = 0; foreach (var shiftGroup in shiftGroups) { // 同一班次内设备不能共享,需要独立设备 optimizedCount += shiftGroup.Count(); } // 3. 设备类型考虑:不同工序可能需要专用设备 var processTypes = dayTasks.Select(t => t.ProcessCode).Distinct().Count(); var processMultiplier = Math.Max(1, processTypes * 0.6); // 工序多样性系数 // 4. 备用设备:考虑设备故障风险 var backupRatio = 0.1; // 10%的备用设备 var totalEquipmentNeed = optimizedCount * processMultiplier * (1 + backupRatio); return (int)Math.Ceiling(totalEquipmentNeed); }) .ToList(); // 返回一周内的最大日设备需求量 return dailyEquipmentRequirements.Any() ? dailyEquipmentRequirements.Max() : 0; } /// /// 计算单日所需人员数量(基于业务规则的智能估算) /// private int CalculateRequiredPersonnelCountForDay(List dayTasks) { // 边界情况处理 if (dayTasks == null || !dayTasks.Any()) return 0; // 数据完整性验证 var invalidTasks = dayTasks.Where(t => t.Id <= 0 || (t.EstimatedHours.HasValue && t.EstimatedHours.Value < 0)).ToList(); if (invalidTasks.Any()) { throw new InvalidOperationException($"发现无效任务数据:任务ID或工时为负数,任务数量: {invalidTasks.Count}"); } // 单日任务数量合理性检查 if (dayTasks.Count > 500) { throw new InvalidOperationException($"单日任务数量异常:{dayTasks.Count}个,请检查数据是否正确"); } // 1. 基础需求:每个任务至少需要1个人员 var baseRequirement = dayTasks.Count; // 业务规则配置化处理,避免硬编码 var businessRules = GetPersonnelCalculationRules(); // 2. 复杂度调整:高复杂度任务可能需要额外人员协助 var complexityAdjustment = 0; // 3. 紧急度调整:紧急任务可能需要备用人员或监督人员 var urgencyAdjustment = 0; // 4. 班次覆盖:不同班次需要独立人员配置,无法跨班次共享 var shiftCount = dayTasks.Select(t => t.ShiftId).Distinct().Count(); var shiftMultiplier = Math.Max(1, shiftCount * businessRules.ShiftMultiplierFactor); // 5. FL人员支持:需要额外的FL(FirstLine)人员提供技术支持 var flRequirement = 0; // 6. 工序专业化:不同工序可能需要专业技能人员 var processSpecializationFactor = 0; // 综合计算每日人员需求 var totalDailyRequirement = (baseRequirement + complexityAdjustment + urgencyAdjustment) * shiftMultiplier * processSpecializationFactor + flRequirement; return (int)Math.Ceiling(totalDailyRequirement); } /// /// 计算单日所需设备数量(基于任务特性的智能估算) /// private int CalculateRequiredEquipmentCountForDay(List dayTasks) { // 边界情况处理 if (dayTasks == null || !dayTasks.Any()) return 0; // 数据完整性验证 var invalidTasks = dayTasks.Where(t => t.Id <= 0 || string.IsNullOrEmpty(t.ProcessCode)).ToList(); if (invalidTasks.Any()) { throw new InvalidOperationException($"发现无效任务数据:任务ID无效或工序代码为空,任务数量: {invalidTasks.Count}"); } // 单日任务数量合理性检查 if (dayTasks.Count > 500) { throw new InvalidOperationException($"单日任务数量异常:{dayTasks.Count}个,请检查数据是否正确"); } // 检查任务的工序指定了设备的型号的任务需要设备 decimal totalEquipmentNeed = dayTasks.Select(a => !string.IsNullOrWhiteSpace(a.ProcessEntity.EquipmentType)).Count();; return (int)Math.Ceiling(totalEquipmentNeed); } /// /// 获取周开始日期(周一) /// private DateTime GetWeekStartDate(DateTime date) { var daysFromMonday = (int)date.DayOfWeek - (int)DayOfWeek.Monday; if (daysFromMonday < 0) daysFromMonday += 7; return date.AddDays(-daysFromMonday); } /// /// 检查人员在指定日期是否在工作量限制范围内 /// private async Task CheckPersonnelWorkLimitAsync(long personnelId, DateTime date) { try { // 获取该人员的工作量限制设置 var workLimits = await _workOrderRepository.Orm.Select() .Where(pwl => pwl.PersonnelId == personnelId) .Where(pwl => pwl.IsDeleted == false) .ToListAsync(); if (!workLimits.Any()) { return true; // 没有工作量限制,默认可用 } // 获取该人员在指定日期已分配的工作任务 var assignedTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == date.Date) .Where(wo => wo.Status != (int)WorkOrderStatusEnum.Completed) .ToListAsync(); // 计算当日已分配的工作小时数 var assignedHours = assignedTasks.Sum(t => t.EstimatedHours ?? 0); // 检查是否超过工作量限制 foreach (var limit in workLimits) { // 检查每周最大班次限制 if (limit.MaxShiftsPerWeek.HasValue) { var weekStart = GetWeekStartDate(date); var weekEnd = weekStart.AddDays(6); var weeklyTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate >= weekStart && wo.WorkOrderDate <= weekEnd) .Where(wo => wo.Status != (int)WorkOrderStatusEnum.Completed) .CountAsync(); if (weeklyTasks >= limit.MaxShiftsPerWeek.Value) { return false; // 超过每周班次限制 } } // 检查连续工作日限制 if (limit.MaxContinuousWorkDays.HasValue) { var continuousDays = await CalculateContinuousWorkDaysAsync(personnelId, date); if (continuousDays >= limit.MaxContinuousWorkDays.Value) { return false; // 超过连续工作日限制 } } } return true; // 在工作量限制范围内 } catch (Exception) { // 异常时保守处理,返回可用状态 return true; } } /// /// 获取指定日期故障设备数量 /// 通过设备Repository直接查询数据库获取故障设备信息 /// private async Task GetFaultEquipmentCountAsync(DateTime date) { try { // 直接查询状态为故障的设备数量 var faultEquipmentList = await _equipmentRepository.GetByStatusAsync(EquipmentStatus.Fault); if (faultEquipmentList == null) { return 0; } // 返回故障设备数量 var faultCount = faultEquipmentList.Count; return Math.Max(0, faultCount); } catch (Exception) { return 1; // 保守估计,假设有1台故障设备 } } /// /// 计算人员截至指定日期的连续工作天数 /// private async Task CalculateContinuousWorkDaysAsync(long personnelId, DateTime targetDate) { try { var continuousDays = 0; var currentDate = targetDate; // 向前查找连续工作日,最多查找14天避免性能问题 for (int i = 0; i < 14; i++) { var hasTaskOnDate = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate.Date) .Where(wo => wo.Status != (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (hasTaskOnDate) { continuousDays++; currentDate = currentDate.AddDays(-1); } else { break; // 遇到休息日,中断连续工作 } } return continuousDays; } catch (Exception) { return 0; // 异常时返回0,避免阻塞流程 } } /// /// EAM服务不可用时的设备数量降级策略 /// 基于历史数据和业务经验的保守估计 /// private Task GetFallbackEquipmentCountAsync(DateTime date) { try { // 降级策略:基于历史平均值或业务规则的估算 // 1. 可以基于历史同期数据 var historicalAverage = 8; // 假设历史平均可用设备数量 // 2. 考虑季节性因素 var seasonalFactor = date.Month switch { 12 or 1 or 2 => 0.8, // 冬季维护较多 6 or 7 or 8 => 0.9, // 夏季使用频繁 _ => 1.0 // 其他月份正常 }; // 3. 考虑工作日因素 var weekdayFactor = date.DayOfWeek switch { DayOfWeek.Saturday or DayOfWeek.Sunday => 0.7, // 周末可用度较低 _ => 1.0 }; var estimatedCount = (int)(historicalAverage * seasonalFactor * weekdayFactor); return Task.FromResult(Math.Max(1, estimatedCount)); // 至少保证1台设备可用 } catch (Exception) { return Task.FromResult(3); // 最保守的估计 } } /// /// 获取人员计算规则配置 /// 提供可配置的业务规则参数,避免硬编码 /// private PersonnelCalculationRules GetPersonnelCalculationRules() { return new PersonnelCalculationRules { HighComplexityThreshold = 8, // 高复杂度阈值 ComplexityAdjustmentFactor = 0.5, // 复杂度调整系数 HighUrgencyThreshold = 8, // 高紧急度阈值 UrgencyAdjustmentFactor = 0.3, // 紧急度调整系数 ShiftMultiplierFactor = 0.8, // 班次系数 TasksPerFlPersonnel = 5, // 每个FL人员支持的任务数 MultiProcessFactor = 1.2 // 多工序系数 }; } #endregion } /// /// 人员计算规则配置 /// public class PersonnelCalculationRules { /// /// 高复杂度阈值 /// public int HighComplexityThreshold { get; set; } /// /// 复杂度调整系数 /// public double ComplexityAdjustmentFactor { get; set; } /// /// 高紧急度阈值 /// public int HighUrgencyThreshold { get; set; } /// /// 紧急度调整系数 /// public double UrgencyAdjustmentFactor { get; set; } /// /// 班次系数 /// public double ShiftMultiplierFactor { get; set; } /// /// 每个FL人员支持的任务数 /// public int TasksPerFlPersonnel { get; set; } /// /// 多工序系数 /// public double MultiProcessFactor { get; set; } } }