1055 lines
48 KiB
C#
1055 lines
48 KiB
C#
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
|
||
}
|
||
} |