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

1285 lines
55 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 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
{
/// <summary>
/// 任务整合前自检服务
/// </summary>
[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));
}
/// <summary>
/// 加载待整合的任务列表,支持多项目号筛选,按项目号分组展示
/// </summary>
[HttpPost]
public async Task<TaskIntegrationListOutput> 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);
}
}
/// <summary>
/// 对勾选的项目和任务进行整合前自检
/// </summary>
[HttpPost]
public async Task<TaskIntegrationPreCheckResult> 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<ProjectResourceSufficiencyResult>();
// 如果选择了项目号,按项目进行检查
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<string> { $"自检执行失败: {ex.Message}" }
};
}
}
/// <summary>
/// 获取项目任务数量与可用人员数量对比(按日和周)
/// </summary>
[HttpPost]
public async Task<ProjectResourceComparisonResult> 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<ProjectResourceComparison>();
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
/// <summary>
/// 检查项目资源充足性
/// </summary>
private async Task<ProjectResourceSufficiencyResult> 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);
}
}
/// <summary>
/// 分析日资源需求
/// </summary>
private async Task<List<DailyResourceDemand>> AnalyzeDailyResourceDemandAsync(
List<WorkOrderEntity> projectTasks,
bool checkPersonnel,
bool checkEquipment)
{
var dailyDemands = new List<DailyResourceDemand>();
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;
}
/// <summary>
/// 分析周资源需求
/// </summary>
private async Task<List<WeeklyResourceDemand>> AnalyzeWeeklyResourceDemandAsync(
List<WorkOrderEntity> projectTasks,
bool checkPersonnel,
bool checkEquipment)
{
var weeklyDemands = new List<WeeklyResourceDemand>();
// 按周分组任务
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;
}
/// <summary>
/// 分析项目资源对比
/// </summary>
private async Task<ProjectResourceComparison> 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;
}
/// <summary>
/// 生成资源建议
/// </summary>
private List<string> GenerateResourceRecommendations(ProjectResourceComparison comparison)
{
var recommendations = new List<string>();
if (comparison.ResourceStatus == "不足")
{
recommendations.Add("建议增加人员配置或调整任务优先级");
recommendations.Add("考虑延长项目周期以平衡资源负载");
}
else if (comparison.ResourceStatus == "紧张")
{
recommendations.Add("建议优化任务分配,避免资源冲突");
recommendations.Add("密切关注人员工作负荷,防止过度疲劳");
}
else
{
recommendations.Add("资源配置良好,可以按计划执行");
}
return recommendations;
}
/// <summary>
/// 计算资源紧张度评分
/// </summary>
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));
}
/// <summary>
/// 获取指定日期可用人员数量
/// 综合考虑班次安排、人员资质、请假状态、工作量限制等因素
/// </summary>
private async Task<int> GetAvailablePersonnelCountAsync(DateTime date)
{
try
{
// 1. 获取该日期的所有班次
var shifts = await _workOrderRepository.Orm.Select<ShiftEntity>()
.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);
var workEndTime = shift.EndTime < shift.StartTime ?
date.Date.AddDays(1).Add(shift.EndTime) :
date.Date.Add(shift.EndTime);
// 调用人员服务获取该班次可用人员
// 传入所有资质ID以获取具备任何资质的人员总数
var availablePersonnel = await _personService.GetAvailablePersonnelAsync(
allQualificationIds, // 传入所有资质ID
shift.Id,
workStartTime);
// 4. 进一步过滤:检查人员工作量限制
var filteredPersonnel = new List<long>();
foreach (var personnel in availablePersonnel)
{
// 检查该人员在指定日期是否已超过工作量限制
var isWithinWorkLimit = await CheckPersonnelWorkLimitAsync(
personnel.PersonnelId,
date);
if (isWithinWorkLimit)
{
filteredPersonnel.Add(personnel.PersonnelId);
}
}
totalAvailableCount += filteredPersonnel.Count;
}
return totalAvailableCount;
}
catch (Exception)
{
return 5; // 保守的最小人员数量
}
}
/// <summary>
/// 获取指定日期可用设备数量
/// 综合考虑设备状态、维护计划、校验计划、使用情况等因素
/// 通过设备Repository直接查询数据库获取真实数据
/// </summary>
private async Task<int> 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;
// 4. 获取故障设备数量(基于设备状态)
var faultEquipmentCount = await GetFaultEquipmentCountAsync(date);
// 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);
}
}
/// <summary>
/// 获取指定周可用人员数量
/// 基于每日可用人员数量,计算一周内的平均可用人员数量
/// </summary>
private async Task<int> GetAvailablePersonnelCountForWeekAsync(DateTime weekStart, DateTime weekEnd)
{
try
{
var dailyCounts = new List<int>();
// 遍历一周内每一天,计算每日可用人员数量
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; // 保守估计的周平均人员数量
}
}
/// <summary>
/// 获取指定周可用设备数量
/// 基于每日可用设备数量,计算一周内的平均可用设备数量
/// </summary>
private async Task<int> GetAvailableEquipmentCountForWeekAsync(DateTime weekStart, DateTime weekEnd)
{
try
{
var dailyCounts = new List<int>();
// 遍历一周内每一天,计算每日可用设备数量
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; // 保守估计的周平均设备数量
}
}
/// <summary>
/// 获取人员可用容量
/// 计算指定时间范围内的人员总工作容量(人天)
/// </summary>
private async Task<int> 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小时 * 天数
}
}
/// <summary>
/// 获取设备可用容量
/// 计算指定时间范围内的设备总工作容量(设备时)
/// </summary>
private async Task<int> 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小时 * 天数
}
}
/// <summary>
/// 计算所需人员数量(基于业务规则的智能估算)
/// </summary>
private int CalculateRequiredPersonnelCount(List<WorkOrderEntity> 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人员考虑需要额外的FLFirstLine人员支持
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;
}
/// <summary>
/// 计算所需设备数量(基于任务特性的智能估算)
/// </summary>
private int CalculateRequiredEquipmentCount(List<WorkOrderEntity> 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;
}
/// <summary>
/// 计算单日所需人员数量(基于业务规则的智能估算)
/// </summary>
private int CalculateRequiredPersonnelCountForDay(List<WorkOrderEntity> 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 = dayTasks
.Where(t => t.Complexity >= businessRules.HighComplexityThreshold)
.Count() * businessRules.ComplexityAdjustmentFactor;
// 3. 紧急度调整:紧急任务可能需要备用人员或监督人员
var urgencyAdjustment = dayTasks
.Where(t => t.Urgency >= businessRules.HighUrgencyThreshold)
.Count() * businessRules.UrgencyAdjustmentFactor;
// 4. 班次覆盖:不同班次需要独立人员配置,无法跨班次共享
var shiftCount = dayTasks.Select(t => t.ShiftId).Distinct().Count();
var shiftMultiplier = Math.Max(1, shiftCount * businessRules.ShiftMultiplierFactor);
// 5. FL人员支持需要额外的FLFirstLine人员提供技术支持
var flRequirement = Math.Ceiling(dayTasks.Count / (double)businessRules.TasksPerFlPersonnel);
// 6. 工序专业化:不同工序可能需要专业技能人员
var processCount = dayTasks.Select(t => t.ProcessCode).Distinct().Count();
var processSpecializationFactor = processCount > 1 ? businessRules.MultiProcessFactor : 1.0;
// 综合计算每日人员需求
var totalDailyRequirement = (baseRequirement + complexityAdjustment + urgencyAdjustment)
* shiftMultiplier * processSpecializationFactor + flRequirement;
return (int)Math.Ceiling(totalDailyRequirement);
}
/// <summary>
/// 计算单日所需设备数量(基于任务特性的智能估算)
/// </summary>
private int CalculateRequiredEquipmentCountForDay(List<WorkOrderEntity> 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}个,请检查数据是否正确");
}
// 1. 基础需求:大多数任务需要专用设备
var baseEquipmentNeed = dayTasks.Count;
// 2. 班次并发:同一班次内设备不能共享,需要独立设备
var shiftGroups = dayTasks.GroupBy(t => t.ShiftId);
var concurrentEquipmentNeed = shiftGroups.Sum(shiftGroup => shiftGroup.Count());
// 3. 工序类型:不同工序可能需要专用设备,无法通用
var processTypes = dayTasks.Select(t => t.ProcessCode).Distinct().Count();
var processMultiplier = Math.Max(1, processTypes * 0.6); // 工序多样性系数
// 4. 复杂度设备需求:高复杂度任务可能需要精密设备或多台设备协作
var complexEquipmentAdjustment = dayTasks
.Where(t => t.Complexity >= 8)
.Count() * 0.3; // 高复杂度任务额外设备需求
// 5. 备用设备:考虑设备故障风险,需要一定比例的备用设备
var backupRatio = 0.15; // 15%的备用设备
// 6. 设备利用率优化:同一工序的设备在不同时间段可能有一定程度的共享
var utilizationOptimization = processTypes <= 2 ? 0.9 : 1.0; // 工序少时可以适度优化
var totalEquipmentNeed = (concurrentEquipmentNeed + complexEquipmentAdjustment)
* processMultiplier * utilizationOptimization * (1 + backupRatio);
return (int)Math.Ceiling(totalEquipmentNeed);
}
/// <summary>
/// 获取周开始日期(周一)
/// </summary>
private DateTime GetWeekStartDate(DateTime date)
{
var daysFromMonday = (int)date.DayOfWeek - (int)DayOfWeek.Monday;
if (daysFromMonday < 0) daysFromMonday += 7;
return date.AddDays(-daysFromMonday);
}
/// <summary>
/// 检查人员在指定日期是否在工作量限制范围内
/// </summary>
private async Task<bool> CheckPersonnelWorkLimitAsync(long personnelId, DateTime date)
{
try
{
// 获取该人员的工作量限制设置
var workLimits = await _workOrderRepository.Orm.Select<PersonnelWorkLimitEntity>()
.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;
}
}
/// <summary>
/// 获取指定日期故障设备数量
/// 通过设备Repository直接查询数据库获取故障设备信息
/// </summary>
private async Task<int> 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台故障设备
}
}
/// <summary>
/// 计算人员截至指定日期的连续工作天数
/// </summary>
private async Task<int> 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避免阻塞流程
}
}
/// <summary>
/// EAM服务不可用时的设备数量降级策略
/// 基于历史数据和业务经验的保守估计
/// </summary>
private Task<int> 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); // 最保守的估计
}
}
/// <summary>
/// 获取人员计算规则配置
/// 提供可配置的业务规则参数,避免硬编码
/// </summary>
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
}
/// <summary>
/// 人员计算规则配置
/// </summary>
public class PersonnelCalculationRules
{
/// <summary>
/// 高复杂度阈值
/// </summary>
public int HighComplexityThreshold { get; set; }
/// <summary>
/// 复杂度调整系数
/// </summary>
public double ComplexityAdjustmentFactor { get; set; }
/// <summary>
/// 高紧急度阈值
/// </summary>
public int HighUrgencyThreshold { get; set; }
/// <summary>
/// 紧急度调整系数
/// </summary>
public double UrgencyAdjustmentFactor { get; set; }
/// <summary>
/// 班次系数
/// </summary>
public double ShiftMultiplierFactor { get; set; }
/// <summary>
/// 每个FL人员支持的任务数
/// </summary>
public int TasksPerFlPersonnel { get; set; }
/// <summary>
/// 多工序系数
/// </summary>
public double MultiProcessFactor { get; set; }
}
}