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

1055 lines
48 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 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
{
/// <summary>
/// 设备分配服务
/// 职责:专门负责智能设备分配算法的实现和执行
/// 架构思考:专注于设备分配领域,处理设备冲突、利用率优化等复杂逻辑
/// </summary>
[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<EquipmentAllocationService> _logger;
public EquipmentAllocationService(
WorkOrderRepository workOrderRepository,
EquipmentRepository equipmentRepository,
IEquipmentMaintenanceRepository equipmentMaintenanceRepository,
IEquipmentCalibrationRepository equipmentCalibrationRepository,
ILogger<EquipmentAllocationService> logger)
{
_workOrderRepository = workOrderRepository;
_equipmentRepository = equipmentRepository;
_equipmentMaintenanceRepository = equipmentMaintenanceRepository;
_equipmentCalibrationRepository = equipmentCalibrationRepository;
_logger = logger;
}
/// <summary>
/// 智能设备分配核心方法
/// 深度业务思考按照文档3.1-3.3步骤实现完整的设备分配算法
/// 架构优化:支持两种输入模式,避免重复数据库查询,提升性能
/// </summary>
[HttpPost]
public async Task<EquipmentAllocationResult> AllocateEquipmentSmartlyAsync(EquipmentAllocationInput input)
{
var result = new EquipmentAllocationResult
{
SuccessfulMatches = new List<TaskEquipmentMatch>(),
FailedAllocations = new List<FailedEquipmentAllocation>(),
OverallUtilizationRate = 0,
AllocationSummary = ""
};
try
{
// 0. 输入验证和任务获取 - 深度架构优化
List<WorkOrderEntity> 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<long, long?>();
var allEquipmentPools = new Dictionary<long, EquipmentEntity>(); // 收集所有设备信息
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
/// <summary>
/// 获取带有工序信息的任务列表
/// </summary>
private async Task<List<WorkOrderEntity>> GetTasksWithProcessInfoAsync(List<long> taskIds)
{
return await _workOrderRepository.Select
.Where(t => taskIds.Contains(t.Id))
.Where(t => t.Status == (int)WorkOrderStatusEnum.PendingIntegration)
.Include(t => t.ProcessEntity)
.ToListAsync();
}
/// <summary>
/// 识别需要设备分配的任务
/// </summary>
private List<WorkOrderEntity> IdentifyTasksRequiringEquipment(List<WorkOrderEntity> tasks)
{
return tasks.Where(t =>
!string.IsNullOrWhiteSpace(t.ProcessEntity?.EquipmentType))
.ToList();
}
/// <summary>
/// 按设备类型统计需求
/// 深度业务思考:基于用户纠正的错误进行修复
/// 1. 过滤条件改为检查ProcessEntity.EquipmentType字段
/// 2. 持续时间使用task的EstimatedHours字段
/// 3. 时间计算基于WorkOrderDate + 班次的开始/结束时间
/// </summary>
private Dictionary<string, List<EquipmentRequirement>> GroupByEquipmentType(List<WorkOrderEntity> 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
/// <summary>
/// 获取指定类型的所有可用设备
/// 深度业务思考:修改为根据设备类型字符串查询设备
/// </summary>
private async Task<List<EquipmentEntity>> GetAvailableEquipmentByTypeAsync(string equipmentType)
{
return await _equipmentRepository.Select
.Where(e => e.Status == 0) // 正常状态
.Where(e => e.EquipmentType == equipmentType) // 根据设备类型字符串过滤
.ToListAsync();
}
/// <summary>
/// 设备可用性分析
/// 深度业务逻辑:全面检测设备在指定时间段内的可用性
/// 包含维护冲突、校验冲突、任务占用冲突等多维度检测
/// </summary>
private async Task<List<EquipmentEntity>> FilterEquipmentByAvailabilityAsync(
List<EquipmentEntity> equipment, List<EquipmentRequirement> requirements, List<WorkOrderEntity> allTasks)
{
var validEquipment = new List<EquipmentEntity>();
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;
}
/// <summary>
/// 基础设备可用性检查
/// 深度业务逻辑:检查设备的基础状态和可用性条件
/// </summary>
private async Task<bool> 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;
}
/// <summary>
/// 检查设备维护计划冲突
/// 深度业务逻辑:基于设备状态和维护计划表的全面冲突检测
/// 完整实现:检查设备当前维护状态、计划维护任务、进行中维护等多个维度
/// </summary>
private async Task<bool> 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;
}
}
/// <summary>
/// 检查设备校验计划冲突
/// 深度业务逻辑基于EquipmentCalibrationRepository进行全面的校验冲突检测
/// 架构优化使用专门的校验仓储提供5个维度的校验冲突检查
/// </summary>
private async Task<bool> 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;
}
}
/// <summary>
/// 检查设备任务占用冲突
/// 深度业务逻辑优化基于WorkOrderDate + 班次时间构造准确的时间范围进行冲突检测
/// 核心改进:修复时间范围构造逻辑,支持跨天班次处理
/// </summary>
private async Task<bool> HasTaskConflictAsync(long equipmentId, DateTime startTime, DateTime endTime,
List<WorkOrderEntity> 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;
}
}
/// <summary>
/// 构造基于WorkOrderDate和班次时间的准确时间范围
/// 深度业务逻辑:解决跨天班次的时间计算问题
/// </summary>
/// <param name="workOrder">工作任务实体需包含ShiftEntity导航属性</param>
/// <returns>构造后的开始和结束时间</returns>
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
/// <summary>
/// 检查时间是否在指定范围内
/// </summary>
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;
}
/// <summary>
/// 检查两个时间段是否重叠
/// 深度算法:精确的时间重叠检测
/// </summary>
private bool IsTimeOverlapping(DateTime start1, DateTime end1, DateTime start2, DateTime end2)
{
return start1 <= end2 && end1 >= start2;
}
/// <summary>
/// 获取设备缓冲时间(分钟)
/// 深度业务逻辑:基于设备类型返回所需的缓冲时间
/// </summary>
private int GetEquipmentBufferTime(long equipmentId)
{
// 简化实现,实际可以从设备配置表或业务规则中获取
// 不同设备类型的缓冲时间要求:
// - 生产设备30分钟清洁、预热
// - 检测设备15分钟校准、预热
// - 实验室设备10分钟准备、清洁
// - 办公设备0分钟无缓冲要求
return 15; // 默认15分钟缓冲时间
}
/// <summary>
/// 检查月度维护窗口
/// </summary>
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);
}
/// <summary>
/// 检查周度维护窗口
/// </summary>
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;
}
/// <summary>
/// 检查双周维护窗口
/// </summary>
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
/// <summary>
/// 执行设备分配策略
/// </summary>
private async Task<Dictionary<long, long?>> ExecuteEquipmentAllocationStrategyAsync(
List<EquipmentEntity> availableEquipment, List<EquipmentRequirement> requirements, EquipmentAllocationInput input)
{
var allocations = new Dictionary<long, long?>();
// 按优先级和开始时间排序
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;
}
/// <summary>
/// 基于利用率均衡的设备分配
/// </summary>
private async Task<long?> SelectEquipmentByUtilizationAsync(
List<EquipmentEntity> availableEquipment, EquipmentRequirement requirement)
{
if (!availableEquipment.Any())
return null;
var equipmentUtilizations = new Dictionary<long, double>();
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
/// <summary>
/// 生成设备分配结果汇总
/// </summary>
private EquipmentAllocationResult GenerateEquipmentAllocationResult(
Dictionary<long, long?> allocations, List<WorkOrderEntity> tasks, Dictionary<long, EquipmentEntity> equipmentPools)
{
var result = new EquipmentAllocationResult
{
SuccessfulMatches = new List<TaskEquipmentMatch>(),
FailedAllocations = new List<FailedEquipmentAllocation>(),
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<string> { "检查设备维护计划", "考虑调整任务时间" }
});
}
}
// 计算整体利用率
result.OverallUtilizationRate = (decimal)(successfulAllocations.Count * 100.0 / Math.Max(1, allocations.Count));
result.AllocationSummary = $"设备分配完成: 成功 {successfulAllocations.Count},失败 {failedAllocations.Count}";
return result;
}
#endregion
#region ()
/// <summary>
/// 计算设备综合利用率
/// 深度业务思考:基于多维度数据源的准确利用率计算
/// 架构升级:整合实时状态、历史数据、计划任务和设备维护影响
/// </summary>
/// <param name="equipmentId">设备ID</param>
/// <param name="startDate">开始日期</param>
/// <param name="endDate">结束日期</param>
/// <returns>设备综合利用率0-100</returns>
private async Task<double> 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;
}
}
/// <summary>
/// 计算设备总可用时间
/// 深度业务逻辑:扣除维护时间、校验时间、故障时间等不可用时段
/// </summary>
private async Task<double> 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);
}
/// <summary>
/// 计算设备已使用时间
/// 深度业务逻辑基于WorkOrderDate + 班次时间的准确计算,整合多种任务状态
/// </summary>
private async Task<double> 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;
}
/// <summary>
/// 计算设备维护占用时间
/// 深度业务逻辑:统计指定时间范围内的维护活动占用时间
/// </summary>
private async Task<double> 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;
}
}
/// <summary>
/// 计算设备校验占用时间
/// 深度业务逻辑:统计指定时间范围内的校验活动占用时间
/// </summary>
private async Task<double> 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
}
}