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