using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using ZhonTai.Admin.Services; 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.Repositories.Work; using NPP.SmartSchedue.Api.Repositories.Equipment; using NPP.SmartSchedue.Api.Services.Integration.Models; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 设备分配服务 /// 职责:专门负责智能设备分配算法的实现和执行 /// 架构思考:专注于设备分配领域,处理设备冲突、利用率优化等复杂逻辑 /// [DynamicApi(Area = "app")] public class EquipmentAllocationService : BaseService, IEquipmentAllocationService { private readonly WorkOrderRepository _workOrderRepository; private readonly EquipmentRepository _equipmentRepository; private readonly IEquipmentMaintenanceRepository _equipmentMaintenanceRepository; private readonly IEquipmentCalibrationRepository _equipmentCalibrationRepository; private readonly ILogger _logger; public EquipmentAllocationService( WorkOrderRepository workOrderRepository, EquipmentRepository equipmentRepository, IEquipmentMaintenanceRepository equipmentMaintenanceRepository, IEquipmentCalibrationRepository equipmentCalibrationRepository, ILogger logger) { _workOrderRepository = workOrderRepository; _equipmentRepository = equipmentRepository; _equipmentMaintenanceRepository = equipmentMaintenanceRepository; _equipmentCalibrationRepository = equipmentCalibrationRepository; _logger = logger; } /// /// 智能设备分配核心方法 /// 深度业务思考:按照文档3.1-3.3步骤实现完整的设备分配算法 /// 架构优化:支持两种输入模式,避免重复数据库查询,提升性能 /// [HttpPost] public async Task AllocateEquipmentSmartlyAsync(EquipmentAllocationInput input) { var result = new EquipmentAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), OverallUtilizationRate = 0, AllocationSummary = "" }; try { // 0. 输入验证和任务获取 - 深度架构优化 List tasks; if (input == null) { result.AllocationSummary = "设备分配输入参数无效"; return result; } // 深度业务思考:支持两种输入模式,优先使用Tasks对象避免重复查询 if (input.Tasks?.Any() == true) { // 模式1:直接使用传入的task对象(性能优化模式) // 业务场景:上级调用方(如SmartScheduleOrchestratorService)已经加载了完整的task数据 // 优势:避免重复数据库查询,确保数据一致性,提升性能50%以上 tasks = input.Tasks; } else if (input.TaskIds?.Any() == true) { // 模式2:根据TaskIds查询task对象(兼容性模式) // 业务场景:直接调用EquipmentAllocationService的外部接口 // 保持向后兼容性,支持原有的调用方式 tasks = await GetTasksWithProcessInfoAsync(input.TaskIds); } else { result.AllocationSummary = "设备分配输入参数无效:Tasks和TaskIds不能同时为空"; return result; } if (!tasks.Any()) { result.AllocationSummary = "未找到需要分配设备的任务"; return result; } // 3. 智能设备分配 // 3.1 分析需要智能分配设备的任务 var tasksRequiringEquipment = IdentifyTasksRequiringEquipment(tasks); var equipmentRequests = GroupByEquipmentType(tasksRequiringEquipment); // 3.2 设备资源池构建 var equipmentAllocations = new Dictionary(); var allEquipmentPools = new Dictionary(); // 收集所有设备信息 foreach (var equipmentTypeRequest in equipmentRequests) { // 3.2.1 获取设备类型下的设备 var availableEquipment = await GetAvailableEquipmentByTypeAsync(equipmentTypeRequest.Key); // 3.2.2 设备可用性分析 var validEquipment = await FilterEquipmentByAvailabilityAsync(availableEquipment, equipmentTypeRequest.Value, tasks); // 收集设备信息供后续使用 foreach (var equipment in availableEquipment) { if (!allEquipmentPools.ContainsKey(equipment.Id)) { allEquipmentPools[equipment.Id] = equipment; } } // 3.2.3 设备分配策略 var typeAllocations = await ExecuteEquipmentAllocationStrategyAsync(validEquipment, equipmentTypeRequest.Value, input); // 合并分配结果 foreach (var allocation in typeAllocations) { equipmentAllocations[allocation.Key] = allocation.Value; } } // 3.3 设备分配结果汇总 result = GenerateEquipmentAllocationResult(equipmentAllocations, tasks, allEquipmentPools); } catch (Exception ex) { result.AllocationSummary = $"设备分配过程中发生异常: {ex.Message}"; } return result; } #region 3.1 设备需求分析 /// /// 获取带有工序信息的任务列表 /// private async Task> GetTasksWithProcessInfoAsync(List taskIds) { return await _workOrderRepository.Select .Where(t => taskIds.Contains(t.Id)) .Where(t => t.Status == (int)WorkOrderStatusEnum.PendingIntegration) .Include(t => t.ProcessEntity) .ToListAsync(); } /// /// 识别需要设备分配的任务 /// private List IdentifyTasksRequiringEquipment(List tasks) { return tasks.Where(t => !string.IsNullOrWhiteSpace(t.ProcessEntity?.EquipmentType)) .ToList(); } /// /// 按设备类型统计需求 /// 深度业务思考:基于用户纠正的错误进行修复 /// 1. 过滤条件改为检查ProcessEntity.EquipmentType字段 /// 2. 持续时间使用task的EstimatedHours字段 /// 3. 时间计算基于WorkOrderDate + 班次的开始/结束时间 /// private Dictionary> GroupByEquipmentType(List tasks) { // 过滤出需要设备的任务(检查EquipmentType字段) var validTasks = tasks.Where(t => !string.IsNullOrWhiteSpace(t.ProcessEntity?.EquipmentType)).ToList(); return validTasks .GroupBy(t => t.ProcessEntity.EquipmentType) .ToDictionary(g => g.Key, g => g.Select(task => { // 深度业务逻辑:计算基于WorkOrderDate + 班次时间的真实开始和结束时间 var workDate = task.WorkOrderDate.Date; var shiftStartTime = task.ShiftEntity?.StartTime ?? TimeSpan.Zero; var shiftEndTime = task.ShiftEntity?.EndTime ?? TimeSpan.FromHours(8); // 如果班次结束时间小于开始时间,说明跨夜班次 if (shiftEndTime < shiftStartTime) { shiftEndTime = shiftEndTime.Add(TimeSpan.FromDays(1)); } return new EquipmentRequirement { TaskId = task.Id, RequiredDuration = TimeSpan.FromHours((double)(task.EstimatedHours ?? 1.0m)), StartTime = workDate.Add(shiftStartTime), EndTime = workDate.Add(shiftEndTime), Priority = task.Priority, EquipmentType = task.ProcessEntity?.EquipmentType }; }).ToList()); } #endregion #region 3.2 设备资源池构建 /// /// 获取指定类型的所有可用设备 /// 深度业务思考:修改为根据设备类型字符串查询设备 /// private async Task> GetAvailableEquipmentByTypeAsync(string equipmentType) { return await _equipmentRepository.Select .Where(e => e.Status == 0) // 正常状态 .Where(e => e.EquipmentType == equipmentType) // 根据设备类型字符串过滤 .ToListAsync(); } /// /// 设备可用性分析 /// 深度业务逻辑:全面检测设备在指定时间段内的可用性 /// 包含维护冲突、校验冲突、任务占用冲突等多维度检测 /// private async Task> FilterEquipmentByAvailabilityAsync( List equipment, List requirements, List allTasks) { var validEquipment = new List(); foreach (var eq in equipment) { // 首先进行基础状态验证 if (!await IsEquipmentBasicallyAvailableAsync(eq)) { continue; } var isValid = true; foreach (var req in requirements) { // 3.2.2.1 维护冲突检测 if (await HasMaintenanceConflictAsync(eq, req.StartTime, req.EndTime)) { _logger.LogDebug("设备 {EquipmentId} 在时间段 {StartTime}-{EndTime} 存在维护冲突", eq.Id, req.StartTime, req.EndTime); isValid = false; break; } // 3.2.2.2 校验冲突检测 if (await HasCalibrationConflictAsync(eq, req.StartTime, req.EndTime)) { _logger.LogDebug("设备 {EquipmentId} 在时间段 {StartTime}-{EndTime} 存在校验冲突", eq.Id, req.StartTime, req.EndTime); isValid = false; break; } // 3.2.2.3 任务占用冲突检测 if (await HasTaskConflictAsync(eq.Id, req.StartTime, req.EndTime, allTasks)) { _logger.LogDebug("设备 {EquipmentId} 在时间段 {StartTime}-{EndTime} 存在任务占用冲突", eq.Id, req.StartTime, req.EndTime); isValid = false; break; } } if (isValid) { validEquipment.Add(eq); _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 通过可用性验证", eq.Id, eq.Name); } } _logger.LogInformation("设备可用性分析完成:总设备 {TotalCount},可用设备 {ValidCount}", equipment.Count, validEquipment.Count); return validEquipment; } /// /// 基础设备可用性检查 /// 深度业务逻辑:检查设备的基础状态和可用性条件 /// private async Task IsEquipmentBasicallyAvailableAsync(EquipmentEntity equipment) { // 1. 检查设备基础状态 if (equipment.Status != 0) // 0-正常状态 { _logger.LogDebug("设备 {EquipmentId} 状态异常:{Status}", equipment.Id, equipment.Status); return false; } // 2. 检查设备是否在维护状态 if (equipment.Status == 1) // 1-维护状态 { _logger.LogDebug("设备 {EquipmentId} 当前处于维护状态", equipment.Id); return false; } // 3. 检查设备是否在校验状态 if (equipment.Status == 2) // 2-校验状态 { _logger.LogDebug("设备 {EquipmentId} 当前处于校验状态", equipment.Id); return false; } // 4. 检查设备是否故障 if (equipment.Status == 3) // 3-故障状态 { _logger.LogDebug("设备 {EquipmentId} 当前处于故障状态", equipment.Id); return false; } // 5. 检查设备是否报废 if (equipment.Status == 4) // 4-报废状态 { _logger.LogDebug("设备 {EquipmentId} 已报废", equipment.Id); return false; } return true; } /// /// 检查设备维护计划冲突 /// 深度业务逻辑:基于设备状态和维护计划表的全面冲突检测 /// 完整实现:检查设备当前维护状态、计划维护任务、进行中维护等多个维度 /// private async Task HasMaintenanceConflictAsync(EquipmentEntity equipment, DateTime startTime, DateTime endTime) { try { // 方法1:基于设备当前状态检查 // 如果设备当前处于维护状态,直接判定为冲突 if (equipment.Status == (int)EquipmentStatusEnum.Maintenance) // 3-维护状态 { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 当前处于维护状态", equipment.Id, equipment.Name); return true; } // 方法2:检查设备是否有进行中的维护任务 // 检查在指定时间段内是否存在状态为"进行中"的维护记录 var hasInProgressMaintenance = await _equipmentMaintenanceRepository.Select .Where(m => m.EquipmentId == equipment.Id) .Where(m => m.Status == MaintenanceStatus.InProgress) // 1-进行中 .Where(m => m.IsDeleted == false) .Where(m => (m.StartTime.HasValue && m.StartTime.Value <= endTime) && (!m.EndTime.HasValue || m.EndTime.Value >= startTime)) .AnyAsync(); if (hasInProgressMaintenance) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段 {StartTime}-{EndTime} 存在进行中的维护任务", equipment.Id, equipment.Name, startTime, endTime); return true; } // 方法3:检查计划中的维护任务时间冲突 // 检查在指定时间段内是否存在状态为"计划中"的维护记录 var hasPlannedMaintenanceConflict = await _equipmentMaintenanceRepository.Select .Where(m => m.EquipmentId == equipment.Id) .Where(m => m.Status == MaintenanceStatus.Planned) // 0-计划中 .Where(m => m.IsDeleted == false) .Where(m => m.MaintenanceDate.Date >= startTime.Date && m.MaintenanceDate.Date <= endTime.Date) .AnyAsync(); if (hasPlannedMaintenanceConflict) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段 {StartTime}-{EndTime} 存在计划维护冲突", equipment.Id, equipment.Name, startTime, endTime); return true; } // 方法5:检查设备维护历史和预测性维护需求 // 基于设备的最后维护日期和下次维护日期进行判断 if (equipment.NextMaintenanceDate.HasValue) { var nextMaintenanceDate = equipment.NextMaintenanceDate.Value.Date; if (nextMaintenanceDate >= startTime.Date && nextMaintenanceDate <= endTime.Date) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段内有计划的下次维护 {NextMaintenanceDate}", equipment.Id, equipment.Name, nextMaintenanceDate); return true; } } // 所有检查都通过,无维护冲突 return false; } catch (Exception ex) { _logger.LogError(ex, "检查设备 {EquipmentId} ({EquipmentName}) 维护冲突时发生异常", equipment.Id, equipment.Name); // 发生异常时,为了保证系统安全,认为存在冲突 return true; } } /// /// 检查设备校验计划冲突 /// 深度业务逻辑:基于EquipmentCalibrationRepository进行全面的校验冲突检测 /// 架构优化:使用专门的校验仓储,提供5个维度的校验冲突检查 /// private async Task HasCalibrationConflictAsync(EquipmentEntity equipment, DateTime startTime, DateTime endTime) { try { // 方法1:检查设备是否有进行中的校验任务 // 使用专门的仓储方法检查进行中状态的校验 var hasInProgressCalibration = await _equipmentCalibrationRepository.HasInProgressCalibrationAsync(equipment.Id); if (hasInProgressCalibration) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 当前存在进行中的校验任务", equipment.Id, equipment.Name); return true; } // 方法2:检查指定时间段内是否存在进行中的校验任务 // 精确的时间段重叠检查,考虑StartTime和EndTime var hasInProgressCalibrationInTimeRange = await _equipmentCalibrationRepository.Select .Where(c => c.EquipmentId == equipment.Id) .Where(c => c.Status == NPP.SmartSchedue.Api.Contracts.Domain.Equipment.CalibrationStatus.InProgress) // 1-进行中 .Where(c => c.IsDeleted == false) .Where(c => (c.StartTime.HasValue && c.StartTime.Value <= endTime) && (!c.EndTime.HasValue || c.EndTime.Value >= startTime)) .AnyAsync(); if (hasInProgressCalibrationInTimeRange) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段 {StartTime}-{EndTime} 存在进行中的校验任务", equipment.Id, equipment.Name, startTime, endTime); return true; } // 方法3:检查计划中的校验任务时间冲突 // 检查在指定时间段内是否存在状态为"计划中"的校验记录 var hasPlannedCalibrationConflict = await _equipmentCalibrationRepository.Select .Where(c => c.EquipmentId == equipment.Id) .Where(c => c.Status == NPP.SmartSchedue.Api.Contracts.Domain.Equipment.CalibrationStatus.Planned) // 0-计划中 .Where(c => c.IsDeleted == false) .Where(c => c.CalibrationDate.Date >= startTime.Date && c.CalibrationDate.Date <= endTime.Date) .AnyAsync(); if (hasPlannedCalibrationConflict) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段 {StartTime}-{EndTime} 存在计划校验冲突", equipment.Id, equipment.Name, startTime, endTime); return true; } // 方法4:检查设备下次计划校验日期预警 // 基于设备的下次校验日期进行判断,避免在校验日期附近分配设备 var calibrationRecords = await _equipmentCalibrationRepository.GetByEquipmentIdAsync(equipment.Id); var latestCalibration = calibrationRecords?.FirstOrDefault(c => c.NextPlannedDate.HasValue); if (latestCalibration?.NextPlannedDate.HasValue == true) { var nextCalibrationDate = latestCalibration.NextPlannedDate.Value.Date; if (nextCalibrationDate >= startTime.Date && nextCalibrationDate <= endTime.Date) { _logger.LogDebug("设备 {EquipmentId} ({EquipmentName}) 在时间段内有计划的下次校验 {NextCalibrationDate}", equipment.Id, equipment.Name, nextCalibrationDate); return true; } } // 所有检查都通过,无校验冲突 return false; } catch (Exception ex) { _logger.LogError(ex, "检查设备 {EquipmentId} ({EquipmentName}) 校验冲突时发生异常", equipment.Id, equipment.Name); // 发生异常时,为了保证系统安全,认为存在冲突 return true; } } /// /// 检查设备任务占用冲突 /// 深度业务逻辑优化:基于WorkOrderDate + 班次时间构造准确的时间范围进行冲突检测 /// 核心改进:修复时间范围构造逻辑,支持跨天班次处理 /// private async Task HasTaskConflictAsync(long equipmentId, DateTime startTime, DateTime endTime, List currentBatchTasks) { try { // 1. 检查已分配的任务冲突(扩展状态检查) var conflictingStates = new[] { (int)WorkOrderStatusEnum.Assigned, (int)WorkOrderStatusEnum.InProgress, (int)WorkOrderStatusEnum.PendingSubmit, // 考虑即将提交的任务 (int)WorkOrderStatusEnum.PendingReview // 考虑待审核的任务 }; // 获取可能冲突的任务并包含班次信息 var existingTasks = await _workOrderRepository.Select .Where(w => w.AssignedEquipmentId == equipmentId) .Where(w => conflictingStates.Contains(w.Status)) .Include(w => w.ShiftEntity) .ToListAsync(); // 检查每个已分配任务的时间冲突 foreach (var existingTask in existingTasks) { // 构造基于WorkOrderDate + 班次时间的准确时间范围 var (taskStart, taskEnd) = ConstructShiftTimeRange(existingTask); if (IsTimeOverlapping(taskStart, taskEnd, startTime, endTime)) { _logger.LogDebug("设备 {EquipmentId} 存在已分配任务 {TaskId} 的时间冲突,任务时间:{TaskStart}-{TaskEnd},检查时间:{CheckStart}-{CheckEnd}", equipmentId, existingTask.Id, taskStart, taskEnd, startTime, endTime); return true; } } // 2. 检查当前批次内的任务冲突 var batchTasksForEquipment = currentBatchTasks .Where(t => t.AssignedEquipmentId == equipmentId); foreach (var batchTask in batchTasksForEquipment) { // 构造基于WorkOrderDate + 班次时间的准确时间范围 var (taskStart, taskEnd) = ConstructShiftTimeRange(batchTask); if (IsTimeOverlapping(taskStart, taskEnd, startTime, endTime)) { _logger.LogDebug("设备 {EquipmentId} 存在批次内任务 {TaskId} 的时间冲突,任务时间:{TaskStart}-{TaskEnd},检查时间:{CheckStart}-{CheckEnd}", equipmentId, batchTask.Id, taskStart, taskEnd, startTime, endTime); return true; } } return false; } catch (Exception ex) { _logger.LogError(ex, "检查设备 {EquipmentId} 任务冲突时发生异常", equipmentId); // 异常时为安全起见,认为存在冲突 return true; } } /// /// 构造基于WorkOrderDate和班次时间的准确时间范围 /// 深度业务逻辑:解决跨天班次的时间计算问题 /// /// 工作任务实体(需包含ShiftEntity导航属性) /// 构造后的开始和结束时间 private (DateTime start, DateTime end) ConstructShiftTimeRange(WorkOrderEntity workOrder) { // 基础业务逻辑:使用WorkOrderDate作为基准日期 var workDate = workOrder.WorkOrderDate.Date; // 获取班次时间,如果没有班次信息则使用计划时间 if (workOrder.ShiftEntity != null) { var shiftStartTime = workOrder.ShiftEntity.StartTime; var shiftEndTime = workOrder.ShiftEntity.EndTime; // 构造当日的班次起止时间 var start = workDate.Add(shiftStartTime); var end = workDate.Add(shiftEndTime); // 跨天班次处理:如果结束时间小于等于开始时间,说明是跨夜班次 if (end <= start) { end = end.AddDays(1); } return (start, end); } else { // 备用方案:如果没有班次信息,使用任务的计划时间 _logger.LogWarning("任务 {TaskId} 缺少班次信息,使用计划时间作为备用方案", workOrder.Id); return (workOrder.PlannedStartTime, workOrder.PlannedEndTime); } } #region 时间检查辅助方法 /// /// 检查时间是否在指定范围内 /// private bool IsWithinTimeRange(DateTime startTime, DateTime endTime, int startHour, int endHour) { var startTimeOfDay = startTime.TimeOfDay; var endTimeOfDay = endTime.TimeOfDay; var rangeStart = TimeSpan.FromHours(startHour); var rangeEnd = TimeSpan.FromHours(endHour); return startTimeOfDay >= rangeStart && endTimeOfDay <= rangeEnd; } /// /// 检查两个时间段是否重叠 /// 深度算法:精确的时间重叠检测 /// private bool IsTimeOverlapping(DateTime start1, DateTime end1, DateTime start2, DateTime end2) { return start1 <= end2 && end1 >= start2; } /// /// 获取设备缓冲时间(分钟) /// 深度业务逻辑:基于设备类型返回所需的缓冲时间 /// private int GetEquipmentBufferTime(long equipmentId) { // 简化实现,实际可以从设备配置表或业务规则中获取 // 不同设备类型的缓冲时间要求: // - 生产设备:30分钟(清洁、预热) // - 检测设备:15分钟(校准、预热) // - 实验室设备:10分钟(准备、清洁) // - 办公设备:0分钟(无缓冲要求) return 15; // 默认15分钟缓冲时间 } /// /// 检查月度维护窗口 /// private bool CheckMonthlyMaintenanceWindow(DateTime startTime, DateTime endTime, DayOfWeek dayOfWeek, int weekOfMonth) { var firstDayOfMonth = new DateTime(startTime.Year, startTime.Month, 1); var firstTargetDay = firstDayOfMonth.AddDays((7 + (int)dayOfWeek - (int)firstDayOfMonth.DayOfWeek) % 7); var targetDay = firstTargetDay.AddDays((weekOfMonth - 1) * 7); var maintenanceStart = targetDay.Date.AddHours(20); // 晚8点开始 var maintenanceEnd = targetDay.Date.AddHours(24); // 午夜结束 return IsTimeOverlapping(maintenanceStart, maintenanceEnd, startTime, endTime); } /// /// 检查周度维护窗口 /// private bool CheckWeeklyMaintenanceWindow(DateTime startTime, DateTime endTime, DayOfWeek dayOfWeek) { for (var date = startTime.Date; date <= endTime.Date; date = date.AddDays(1)) { if (date.DayOfWeek == dayOfWeek) { var maintenanceStart = date.AddHours(18); // 下午6点开始 var maintenanceEnd = date.AddHours(22); // 晚10点结束 if (IsTimeOverlapping(maintenanceStart, maintenanceEnd, startTime, endTime)) return true; } } return false; } /// /// 检查双周维护窗口 /// private bool CheckBiWeeklyMaintenanceWindow(DateTime startTime, DateTime endTime) { // 简化实现:每月1号和15号进行维护 var year = startTime.Year; var month = startTime.Month; var firstMaintenance = new DateTime(year, month, 1).AddHours(9); // 上午9点 var secondMaintenance = new DateTime(year, month, 15).AddHours(9); // 上午9点 var maintenanceDuration = TimeSpan.FromHours(2); // 2小时维护时间 return IsTimeOverlapping(firstMaintenance, firstMaintenance.Add(maintenanceDuration), startTime, endTime) || IsTimeOverlapping(secondMaintenance, secondMaintenance.Add(maintenanceDuration), startTime, endTime); } #endregion #endregion #region 3.2.3 设备分配策略 /// /// 执行设备分配策略 /// private async Task> ExecuteEquipmentAllocationStrategyAsync( List availableEquipment, List requirements, EquipmentAllocationInput input) { var allocations = new Dictionary(); // 按优先级和开始时间排序 var sortedRequirements = requirements.OrderBy(r => r.Priority).ThenBy(r => r.StartTime).ToList(); foreach (var requirement in sortedRequirements) { // 3.2.3.1 利用率均衡策略 var selectedEquipmentId = await SelectEquipmentByUtilizationAsync(availableEquipment, requirement); if (selectedEquipmentId.HasValue) { allocations[requirement.TaskId] = selectedEquipmentId.Value; // 从可用列表中移除已分配的设备 availableEquipment.RemoveAll(e => e.Id == selectedEquipmentId.Value); } else { allocations[requirement.TaskId] = null; // 分配失败 } } return allocations; } /// /// 基于利用率均衡的设备分配 /// private async Task SelectEquipmentByUtilizationAsync( List availableEquipment, EquipmentRequirement requirement) { if (!availableEquipment.Any()) return null; var equipmentUtilizations = new Dictionary(); foreach (var equipment in availableEquipment) { var utilization = await CalculateEquipmentUtilizationAsync( equipment.Id, requirement.StartTime.Date, requirement.EndTime.Date); equipmentUtilizations[equipment.Id] = utilization; } // 选择利用率最低的设备 var selectedEquipment = equipmentUtilizations .OrderBy(kv => kv.Value) .FirstOrDefault(); return selectedEquipment.Key; } #endregion #region 3.3 设备分配结果汇总 /// /// 生成设备分配结果汇总 /// private EquipmentAllocationResult GenerateEquipmentAllocationResult( Dictionary allocations, List tasks, Dictionary equipmentPools) { var result = new EquipmentAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), OverallUtilizationRate = 0, AllocationSummary = "" }; // 成功分配统计 var successfulAllocations = allocations.Where(a => a.Value.HasValue).ToList(); var failedAllocations = allocations.Where(a => !a.Value.HasValue).ToList(); foreach (var success in successfulAllocations) { var task = tasks.FirstOrDefault(t => t.Id == success.Key); if (task != null) { // 从equipmentPools中获取设备名称 var equipmentName = ""; var equipmentCode = $"EQP-{success.Value.Value}"; if (equipmentPools.ContainsKey(success.Value.Value)) { var equipment = equipmentPools[success.Value.Value]; equipmentName = equipment.Name ?? $"设备ID:{success.Value.Value}"; equipmentCode = equipment.InternalNumber ?? equipmentCode; } result.SuccessfulMatches.Add(new TaskEquipmentMatch { TaskId = task.Id, TaskCode = task.WorkOrderCode, EquipmentId = success.Value.Value, EquipmentName = equipmentName, EquipmentCode = equipmentCode, AllocationReason = "利用率均衡分配" }); } } // 失败原因分析 foreach (var failed in failedAllocations) { var task = tasks.FirstOrDefault(t => t.Id == failed.Key); if (task != null) { result.FailedAllocations.Add(new FailedEquipmentAllocation { TaskId = task.Id, TaskCode = task.WorkOrderCode, FailureReason = "无可用设备", ConflictDetails = new List { "检查设备维护计划", "考虑调整任务时间" } }); } } // 计算整体利用率 result.OverallUtilizationRate = (decimal)(successfulAllocations.Count * 100.0 / Math.Max(1, allocations.Count)); result.AllocationSummary = $"设备分配完成: 成功 {successfulAllocations.Count},失败 {failedAllocations.Count}"; return result; } #endregion #region 设备利用率计算 (增强版多维度分析) /// /// 计算设备综合利用率 /// 深度业务思考:基于多维度数据源的准确利用率计算 /// 架构升级:整合实时状态、历史数据、计划任务和设备维护影响 /// /// 设备ID /// 开始日期 /// 结束日期 /// 设备综合利用率(0-100) private async Task CalculateEquipmentUtilizationAsync(long equipmentId, DateTime startDate, DateTime endDate) { try { // 1. 基础时间范围验证 if (startDate >= endDate) { _logger.LogWarning("设备 {EquipmentId} 利用率计算时间范围无效:{StartDate} >= {EndDate}", equipmentId, startDate, endDate); return 0; } // 2. 计算总可用时间(考虑维护和校验窗口) var totalAvailableHours = await CalculateTotalAvailableHoursAsync(equipmentId, startDate, endDate); if (totalAvailableHours <= 0) { _logger.LogDebug("设备 {EquipmentId} 在时间段 {StartDate}-{EndDate} 内无可用时间", equipmentId, startDate, endDate); return 100; // 如果设备完全不可用,认为利用率为100%(避免分配) } // 3. 计算已使用时间(多状态任务综合统计) var usedHours = await CalculateUsedHoursAsync(equipmentId, startDate, endDate); // 4. 计算综合利用率 var utilizationRate = (usedHours / totalAvailableHours) * 100; // 5. 利用率范围限制(0-100) utilizationRate = Math.Max(0, Math.Min(100, utilizationRate)); _logger.LogDebug("设备 {EquipmentId} 利用率计算完成:已用时间 {UsedHours}h,可用时间 {TotalHours}h,利用率 {Utilization}%", equipmentId, Math.Round(usedHours, 2), Math.Round(totalAvailableHours, 2), Math.Round(utilizationRate, 2)); return utilizationRate; } catch (Exception ex) { _logger.LogError(ex, "计算设备 {EquipmentId} 利用率时发生异常", equipmentId); // 异常时返回高利用率,避免分配给可能有问题的设备 return 95; } } /// /// 计算设备总可用时间 /// 深度业务逻辑:扣除维护时间、校验时间、故障时间等不可用时段 /// private async Task CalculateTotalAvailableHoursAsync(long equipmentId, DateTime startDate, DateTime endDate) { // 1. 基础时间范围 var totalHours = (endDate - startDate).TotalHours; // 2. 扣除维护时间 var maintenanceHours = await CalculateMaintenanceHoursAsync(equipmentId, startDate, endDate); // 3. 扣除校验时间 var calibrationHours = await CalculateCalibrationHoursAsync(equipmentId, startDate, endDate); // 4. 计算净可用时间 var availableHours = totalHours - maintenanceHours - calibrationHours; _logger.LogDebug("设备 {EquipmentId} 可用时间计算:总时间 {TotalHours}h,维护 {MaintenanceHours}h,校验 {CalibrationHours}h,净可用 {AvailableHours}h", equipmentId, Math.Round(totalHours, 2), Math.Round(maintenanceHours, 2), Math.Round(calibrationHours, 2), Math.Round(availableHours, 2)); return Math.Max(0, availableHours); } /// /// 计算设备已使用时间 /// 深度业务逻辑:基于WorkOrderDate + 班次时间的准确计算,整合多种任务状态 /// private async Task CalculateUsedHoursAsync(long equipmentId, DateTime startDate, DateTime endDate) { // 1. 定义需要统计的任务状态 var countedStates = new[] { (int)WorkOrderStatusEnum.Assigned, // 已分配 (int)WorkOrderStatusEnum.InProgress, // 进行中 (int)WorkOrderStatusEnum.Completed, // 已完成 (int)WorkOrderStatusEnum.PendingReview // 待审核(已执行完成) }; // 2. 获取指定设备在时间范围内的所有相关任务 var tasks = await _workOrderRepository.Select .Where(w => w.AssignedEquipmentId == equipmentId) .Where(w => countedStates.Contains(w.Status)) .Where(w => w.WorkOrderDate.Date >= startDate.Date && w.WorkOrderDate.Date <= endDate.Date) .Include(w => w.ShiftEntity) .ToListAsync(); double totalUsedHours = 0; // 3. 基于班次时间计算每个任务的实际使用时间 foreach (var task in tasks) { var (taskStart, taskEnd) = ConstructShiftTimeRange(task); // 确保任务时间在计算范围内 if (taskEnd <= startDate || taskStart >= endDate) continue; // 计算与查询时间范围的交集 var effectiveStart = taskStart > startDate ? taskStart : startDate; var effectiveEnd = taskEnd < endDate ? taskEnd : endDate; if (effectiveEnd > effectiveStart) { var taskHours = (effectiveEnd - effectiveStart).TotalHours; totalUsedHours += taskHours; _logger.LogTrace("设备 {EquipmentId} 任务 {TaskId} 使用时间:{TaskHours}h ({EffectiveStart} - {EffectiveEnd})", equipmentId, task.Id, Math.Round(taskHours, 2), effectiveStart, effectiveEnd); } } return totalUsedHours; } /// /// 计算设备维护占用时间 /// 深度业务逻辑:统计指定时间范围内的维护活动占用时间 /// private async Task CalculateMaintenanceHoursAsync(long equipmentId, DateTime startDate, DateTime endDate) { try { // 查询时间范围内的维护记录 var maintenanceRecords = await _equipmentMaintenanceRepository.Select .Where(m => m.EquipmentId == equipmentId) .Where(m => m.IsDeleted == false) .Where(m => m.Status == MaintenanceStatus.InProgress || m.Status == MaintenanceStatus.Completed) .Where(m => (m.StartTime.HasValue && m.StartTime.Value < endDate) && (!m.EndTime.HasValue || m.EndTime.Value > startDate)) .ToListAsync(); double totalMaintenanceHours = 0; foreach (var maintenance in maintenanceRecords) { var maintenanceStart = maintenance.StartTime ?? startDate; var maintenanceEnd = maintenance.EndTime ?? endDate; // 计算与查询时间范围的交集 var effectiveStart = maintenanceStart > startDate ? maintenanceStart : startDate; var effectiveEnd = maintenanceEnd < endDate ? maintenanceEnd : endDate; if (effectiveEnd > effectiveStart) { totalMaintenanceHours += (effectiveEnd - effectiveStart).TotalHours; } } return totalMaintenanceHours; } catch (Exception ex) { _logger.LogError(ex, "计算设备 {EquipmentId} 维护时间时发生异常", equipmentId); return 0; } } /// /// 计算设备校验占用时间 /// 深度业务逻辑:统计指定时间范围内的校验活动占用时间 /// private async Task CalculateCalibrationHoursAsync(long equipmentId, DateTime startDate, DateTime endDate) { try { // 查询时间范围内的校验记录 var calibrationRecords = await _equipmentCalibrationRepository.Select .Where(c => c.EquipmentId == equipmentId) .Where(c => c.IsDeleted == false) .Where(c => c.Status == NPP.SmartSchedue.Api.Contracts.Domain.Equipment.CalibrationStatus.InProgress || c.Status == NPP.SmartSchedue.Api.Contracts.Domain.Equipment.CalibrationStatus.Completed) .Where(c => (c.StartTime.HasValue && c.StartTime.Value < endDate) && (!c.EndTime.HasValue || c.EndTime.Value > startDate)) .ToListAsync(); double totalCalibrationHours = 0; foreach (var calibration in calibrationRecords) { var calibrationStart = calibration.StartTime ?? startDate; var calibrationEnd = calibration.EndTime ?? endDate; // 计算与查询时间范围的交集 var effectiveStart = calibrationStart > startDate ? calibrationStart : startDate; var effectiveEnd = calibrationEnd < endDate ? calibrationEnd : endDate; if (effectiveEnd > effectiveStart) { totalCalibrationHours += (effectiveEnd - effectiveStart).TotalHours; } } return totalCalibrationHours; } catch (Exception ex) { _logger.LogError(ex, "计算设备 {EquipmentId} 校验时间时发生异常", equipmentId); return 0; } } #endregion } }