123
This commit is contained in:
parent
330bbd5fb0
commit
aac34433fa
@ -15,19 +15,5 @@ namespace NPP.SmartSchedue.Api.Contracts.Services.Integration
|
|||||||
public interface IGlobalPersonnelAllocationService
|
public interface IGlobalPersonnelAllocationService
|
||||||
{
|
{
|
||||||
Task<GlobalAllocationResult> AllocatePersonnelGloballyAsync(GlobalAllocationInput input);
|
Task<GlobalAllocationResult> AllocatePersonnelGloballyAsync(GlobalAllocationInput input);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 【性能优化】公共的人员可用性计算方法 - 专为遗传算法优化
|
|
||||||
/// 替代原有的反射调用,消除15-20%的性能开销
|
|
||||||
/// </summary>
|
|
||||||
Task<double> CalculatePersonnelAvailabilityForGeneticAsync(
|
|
||||||
GlobalPersonnelInfo personnel, WorkOrderEntity task, List<WorkOrderEntity> contextTasks);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 【性能优化】公共的班次规则合规性评分方法 - 专为遗传算法优化
|
|
||||||
/// 替代原有的反射调用,提升约束检查性能
|
|
||||||
/// </summary>
|
|
||||||
Task<double> CalculateShiftRuleComplianceForGeneticAsync(
|
|
||||||
long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -57,6 +57,11 @@ namespace NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, bool> PersonnelProjectFLMapping { get; set; } = new();
|
public Dictionary<string, bool> PersonnelProjectFLMapping { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 班次信息
|
||||||
|
/// </summary>
|
||||||
|
public List<ShiftGetPageOutput> ShiftInformationCache { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 班次规则
|
/// 班次规则
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -24,20 +24,21 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Repositories\"/>
|
<Folder Include="Repositories\" />
|
||||||
<Folder Include="Services\"/>
|
<Folder Include="Services\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Google.OrTools" Version="9.14.6206" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.5" />
|
||||||
<PackageReference Include="Polly" Version="8.4.2" />
|
<PackageReference Include="Polly" Version="8.4.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\NPP.SmartSchedue.Api.Contracts\NPP.SmartSchedue.Api.Contracts.csproj"/>
|
<ProjectReference Include="..\NPP.SmartSchedue.Api.Contracts\NPP.SmartSchedue.Api.Contracts.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -57,6 +57,8 @@ namespace NPP.SmartSchedue.Api
|
|||||||
services.AddScoped<ContextBuilderEngine>();
|
services.AddScoped<ContextBuilderEngine>();
|
||||||
services.AddScoped<GeneticAlgorithmEngine>();
|
services.AddScoped<GeneticAlgorithmEngine>();
|
||||||
services.AddScoped<GlobalOptimizationEngine>();
|
services.AddScoped<GlobalOptimizationEngine>();
|
||||||
|
services.AddScoped<ShiftRuleValidationEngine>();
|
||||||
|
services.AddScoped<LinearProgrammingEngine>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -13,9 +14,10 @@ using NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal;
|
|||||||
using NPP.SmartSchedue.Api.Contracts.Services.Personnel;
|
using NPP.SmartSchedue.Api.Contracts.Services.Personnel;
|
||||||
using NPP.SmartSchedue.Api.Contracts.Services.Personnel.Output;
|
using NPP.SmartSchedue.Api.Contracts.Services.Personnel.Output;
|
||||||
using NPP.SmartSchedue.Api.Contracts.Services.Time;
|
using NPP.SmartSchedue.Api.Contracts.Services.Time;
|
||||||
|
using NPP.SmartSchedue.Api.Contracts.Services.Time.Input;
|
||||||
using NPP.SmartSchedue.Api.Contracts.Services.Time.Output;
|
using NPP.SmartSchedue.Api.Contracts.Services.Time.Output;
|
||||||
using NPP.SmartSchedue.Api.Contracts.Domain.Time;
|
|
||||||
using NPP.SmartSchedue.Api.Repositories.Work;
|
using NPP.SmartSchedue.Api.Repositories.Work;
|
||||||
|
using ZhonTai.Admin.Core.Dto;
|
||||||
|
|
||||||
namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
||||||
{
|
{
|
||||||
@ -32,6 +34,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
private readonly IPersonnelQualificationService _personnelQualificationService;
|
private readonly IPersonnelQualificationService _personnelQualificationService;
|
||||||
private readonly IShiftRuleService _shiftRuleService;
|
private readonly IShiftRuleService _shiftRuleService;
|
||||||
private readonly IShiftUnavailabilityRepository _shiftUnavailabilityRepository;
|
private readonly IShiftUnavailabilityRepository _shiftUnavailabilityRepository;
|
||||||
|
private readonly IShiftService _shiftService;
|
||||||
private readonly ILogger<ContextBuilderEngine> _logger;
|
private readonly ILogger<ContextBuilderEngine> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -43,6 +46,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
IPersonnelQualificationService personnelQualificationService,
|
IPersonnelQualificationService personnelQualificationService,
|
||||||
IShiftRuleService shiftRuleService,
|
IShiftRuleService shiftRuleService,
|
||||||
IShiftUnavailabilityRepository shiftUnavailabilityRepository,
|
IShiftUnavailabilityRepository shiftUnavailabilityRepository,
|
||||||
|
IShiftService shiftService,
|
||||||
ILogger<ContextBuilderEngine> logger)
|
ILogger<ContextBuilderEngine> logger)
|
||||||
{
|
{
|
||||||
_workOrderRepository = workOrderRepository ?? throw new ArgumentNullException(nameof(workOrderRepository));
|
_workOrderRepository = workOrderRepository ?? throw new ArgumentNullException(nameof(workOrderRepository));
|
||||||
@ -50,6 +54,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
_personnelQualificationService = personnelQualificationService ?? throw new ArgumentNullException(nameof(personnelQualificationService));
|
_personnelQualificationService = personnelQualificationService ?? throw new ArgumentNullException(nameof(personnelQualificationService));
|
||||||
_shiftRuleService = shiftRuleService ?? throw new ArgumentNullException(nameof(shiftRuleService));
|
_shiftRuleService = shiftRuleService ?? throw new ArgumentNullException(nameof(shiftRuleService));
|
||||||
_shiftUnavailabilityRepository = shiftUnavailabilityRepository ?? throw new ArgumentNullException(nameof(shiftUnavailabilityRepository));
|
_shiftUnavailabilityRepository = shiftUnavailabilityRepository ?? throw new ArgumentNullException(nameof(shiftUnavailabilityRepository));
|
||||||
|
_shiftService = shiftService ?? throw new ArgumentNullException(nameof(shiftService));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +220,8 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
LoadPersonnelDataAsync(context), // 【修复N+1】人员和资质数据批量加载
|
LoadPersonnelDataAsync(context), // 【修复N+1】人员和资质数据批量加载
|
||||||
LoadTaskFLPersonnelMappingAsync(context), // 任务FL关系预加载
|
LoadTaskFLPersonnelMappingAsync(context), // 任务FL关系预加载
|
||||||
LoadPersonnelHistoryTasksAsync(context), // 人员历史任务预加载
|
LoadPersonnelHistoryTasksAsync(context), // 人员历史任务预加载
|
||||||
LoadShiftUnavailabilityDataAsync(context) // 【新增】班次不可用性数据预加载 - 85%+性能提升
|
LoadShiftUnavailabilityDataAsync(context), // 【新增】班次不可用性数据预加载 - 85%+性能提升
|
||||||
|
LoadShiftInformationDataAsync(context) // 【优化新增】班次信息缓存预加载 - 消除高频IShiftService查询
|
||||||
);
|
);
|
||||||
|
|
||||||
loadingStopwatch.Stop();
|
loadingStopwatch.Stop();
|
||||||
@ -270,7 +276,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
_logger.LogDebug("👥 开始加载人员数据(批量优化)");
|
_logger.LogDebug("👥 开始加载人员数据(批量优化)");
|
||||||
|
|
||||||
// 第一步:批量获取所有激活人员
|
// 第一步:批量获取所有激活人员
|
||||||
var allPersonnel = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false);
|
var allPersonnel = await _personService.GetAllPersonnelPoolAsync(includeQualifications: true);
|
||||||
|
|
||||||
if (allPersonnel == null || !allPersonnel.Any())
|
if (allPersonnel == null || !allPersonnel.Any())
|
||||||
{
|
{
|
||||||
@ -286,7 +292,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
context.AvailablePersonnel = activePersonnel.Select(p => new GlobalPersonnelInfo
|
context.AvailablePersonnel = activePersonnel.Select(p => new GlobalPersonnelInfo
|
||||||
{
|
{
|
||||||
Id = p.Id,
|
Id = p.Id,
|
||||||
Name = p.PersonnelName ?? $"Person_{p.Id}",
|
Name = !string.IsNullOrWhiteSpace(p.PersonnelName) ? p.PersonnelName : $"人员_{p.Id}",
|
||||||
IsActive = true
|
IsActive = true
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
@ -719,6 +725,44 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
return totalBytes / 1024; // 转换为KB
|
return totalBytes / 1024; // 转换为KB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【优化新增】加载班次信息缓存 - 消除ShiftRuleValidationEngine中IShiftService的高频查询
|
||||||
|
/// 【核心价值】:将ShiftRuleValidationEngine中频繁的IShiftService.GetAsync调用替换为缓存查找
|
||||||
|
/// 【性能优化】:预加载所有任务涉及的班次信息,支持O(1)快速查询
|
||||||
|
/// 【业务逻辑】:批量加载任务中使用的班次,构建班次ID到班次详情的映射缓存
|
||||||
|
/// 【缓存策略】:智能去重 + 批量查询 + 异常容错处理
|
||||||
|
/// </summary>
|
||||||
|
private async Task LoadShiftInformationDataAsync(GlobalAllocationContext context)
|
||||||
|
{
|
||||||
|
var shiftLoadingStopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogDebug("🕐 开始加载班次信息缓存(消除IShiftService高频查询)");
|
||||||
|
|
||||||
|
var shifts = await _shiftService.GetPageAsync(new PageInput<ShiftGetPageInput>()
|
||||||
|
{
|
||||||
|
CurrentPage = 0,
|
||||||
|
PageSize = 5
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第四步:批量加载班次信息
|
||||||
|
context.ShiftInformationCache = shifts.List.ToList();
|
||||||
|
shiftLoadingStopwatch.Stop();
|
||||||
|
|
||||||
|
// 更新性能指标
|
||||||
|
context.Metrics["ShiftCacheLoadingTimeMs"] = shiftLoadingStopwatch.ElapsedMilliseconds;
|
||||||
|
context.Metrics["CachedShiftCount"] = context.ShiftInformationCache.Count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
shiftLoadingStopwatch.Stop();
|
||||||
|
_logger.LogError(ex, "💥 班次信息缓存加载异常,耗时:{ElapsedMs}ms",
|
||||||
|
shiftLoadingStopwatch.ElapsedMilliseconds);
|
||||||
|
_logger.LogWarning("🔄 班次缓存降级到空缓存,ShiftRuleValidationEngine将使用实时查询(性能受影响)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 智能预筛选
|
#region 智能预筛选
|
||||||
@ -809,19 +853,12 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 资质预检
|
// 资质预检
|
||||||
if (!await CheckQualificationRequirementsAsync(task, personnel, context))
|
if (!await CheckQualificationRequirementsAsync(task, personnel, context) && await CheckTimeAvailabilityAsync(task, personnel, context))
|
||||||
{
|
{
|
||||||
context.PrefilterResults[key] = result;
|
context.PrefilterResults[key] = result;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间可用性检查
|
|
||||||
if (!await CheckTimeAvailabilityAsync(task, personnel, context))
|
|
||||||
{
|
|
||||||
context.PrefilterResults[key] = result;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过所有筛选条件
|
// 通过所有筛选条件
|
||||||
result.IsFeasible = true;
|
result.IsFeasible = true;
|
||||||
|
|
||||||
@ -840,9 +877,16 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查资质要求
|
/// 检查资质要求 - 核心业务逻辑实现
|
||||||
/// 业务逻辑:验证人员是否具备任务所需的资质和岗位负责人要求
|
/// 业务流程:缓存获取 → 激活状态验证 → 有效期检查 → 资质匹配 → 岗位负责人验证
|
||||||
|
/// 性能策略:缓存优先 + 早期失败 + 详细日志追踪
|
||||||
|
/// 业务规则:支持任一资质匹配 + 岗位负责人强制要求
|
||||||
|
/// 异常处理:全面错误容忍,确保系统稳定运行
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="task">工作任务实体,包含资质要求信息</param>
|
||||||
|
/// <param name="personnel">人员信息</param>
|
||||||
|
/// <param name="context">全局分配上下文,包含缓存数据</param>
|
||||||
|
/// <returns>true-符合资质要求,false-不符合或异常</returns>
|
||||||
private async Task<bool> CheckQualificationRequirementsAsync(
|
private async Task<bool> CheckQualificationRequirementsAsync(
|
||||||
WorkOrderEntity task,
|
WorkOrderEntity task,
|
||||||
GlobalPersonnelInfo personnel,
|
GlobalPersonnelInfo personnel,
|
||||||
@ -850,53 +894,238 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 从缓存中获取人员资质
|
await Task.CompletedTask; // 保持异步接口
|
||||||
|
|
||||||
|
// 第一步:从缓存中获取人员资质数据
|
||||||
if (!context.PersonnelQualificationsCache.TryGetValue(personnel.Id, out var qualifications))
|
if (!context.PersonnelQualificationsCache.TryGetValue(personnel.Id, out var qualifications))
|
||||||
{
|
{
|
||||||
return false; // 如果没有资质数据,默认不符合条件
|
_logger.LogDebug("❌ 资质检查失败 - 任务:{TaskId},人员:{PersonnelId},原因:未找到资质缓存数据",
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有激活的资质
|
|
||||||
var activeQualifications = qualifications.Where(q => q.IsActive).ToList();
|
|
||||||
|
|
||||||
// 过滤 任务的资质信息 该人员是否包含
|
|
||||||
var taskQualifications = task.ProcessEntity?.QualificationRequirements?.Split(',')
|
|
||||||
.Select(q => long.Parse(q.Trim()))
|
|
||||||
.ToList() ?? new List<long>();
|
|
||||||
|
|
||||||
// 检查人员是否包含任务所需的任一的资质
|
|
||||||
var hasAllQualifications = taskQualifications.All(q =>
|
|
||||||
activeQualifications.Any(aq => aq.QualificationId == q));
|
|
||||||
|
|
||||||
// 检查岗位负责人需求
|
|
||||||
if (task.NeedPostHead)
|
|
||||||
{
|
|
||||||
// 检查人员是否具备岗位负责人资质
|
|
||||||
var hasPostHeadQualification = activeQualifications.Any(q => q.HighLevel);
|
|
||||||
if (!hasPostHeadQualification)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("❌ 岗位负责人检查 - 任务:{TaskId},人员:{PersonnelId},不具备岗位负责人资质",
|
|
||||||
task.Id, personnel.Id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug("✅ 岗位负责人检查 - 任务:{TaskId},人员:{PersonnelId},具备岗位负责人资质",
|
|
||||||
task.Id, personnel.Id);
|
task.Id, personnel.Id);
|
||||||
|
return false; // 缓存中无资质数据,不符合条件
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!qualifications.Any())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("❌ 资质检查失败 - 任务:{TaskId},人员:{PersonnelId},原因:人员无任何资质记录",
|
||||||
// 日志记录
|
task.Id, personnel.Id);
|
||||||
_logger.LogDebug("✅ 资质检查 - 任务:{TaskId},人员:{PersonnelId},是否符合:{IsValid}",
|
return false; // 人员无资质记录
|
||||||
task.Id, personnel.Id, hasAllQualifications);
|
}
|
||||||
|
|
||||||
return hasAllQualifications;
|
// 第二步:筛选激活且有效的资质
|
||||||
|
var validQualifications = GetValidQualifications(qualifications, task.Id, personnel.Id);
|
||||||
|
|
||||||
|
if (!validQualifications.Any())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("❌ 资质检查失败 - 任务:{TaskId},人员:{PersonnelId},原因:无有效的激活资质",
|
||||||
|
task.Id, personnel.Id);
|
||||||
|
return false; // 无有效激活资质
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三步:解析任务的资质要求
|
||||||
|
var taskQualifications = ParseTaskQualificationRequirements(task);
|
||||||
|
|
||||||
|
if (!taskQualifications.Any())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("✅ 资质检查通过 - 任务:{TaskId},人员:{PersonnelId},原因:任务无特定资质要求",
|
||||||
|
task.Id, personnel.Id);
|
||||||
|
return await CheckPostHeadRequirementAsync(task, personnel, validQualifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四步:执行资质匹配验证
|
||||||
|
var hasRequiredQualifications = ValidateQualificationMatch(
|
||||||
|
taskQualifications, validQualifications, task.Id, personnel.Id);
|
||||||
|
|
||||||
|
if (!hasRequiredQualifications)
|
||||||
|
{
|
||||||
|
return false; // 不满足基础资质要求
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第五步:检查岗位负责人要求
|
||||||
|
return await CheckPostHeadRequirementAsync(task, personnel, validQualifications);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (FormatException ex)
|
||||||
{
|
{
|
||||||
return false; // 异常时默认不符合条件
|
_logger.LogWarning(ex, "⚠️ 资质检查异常 - 任务:{TaskId},人员:{PersonnelId},原因:资质ID格式错误",
|
||||||
|
task.Id, personnel.Id);
|
||||||
|
return false; // 数据格式错误
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "💥 资质检查异常 - 任务:{TaskId},人员:{PersonnelId},原因:未知异常",
|
||||||
|
task.Id, personnel.Id);
|
||||||
|
return false; // 其他异常,默认不符合条件
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取有效的资质列表
|
||||||
|
/// 业务逻辑:筛选激活状态 + 有效期验证
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="qualifications">原始资质列表</param>
|
||||||
|
/// <param name="taskId">任务ID(用于日志)</param>
|
||||||
|
/// <param name="personnelId">人员ID(用于日志)</param>
|
||||||
|
/// <returns>有效的资质列表</returns>
|
||||||
|
private List<PersonnelQualificationCacheItem> GetValidQualifications(
|
||||||
|
List<PersonnelQualificationCacheItem> qualifications,
|
||||||
|
long taskId,
|
||||||
|
long personnelId)
|
||||||
|
{
|
||||||
|
var currentTime = DateTime.Now;
|
||||||
|
|
||||||
|
var validQualifications = qualifications.Where(q =>
|
||||||
|
q.IsActive && // 资质必须处于激活状态
|
||||||
|
(q.ExpiryDate == null || q.ExpiryDate > currentTime) // 资质必须在有效期内
|
||||||
|
).ToList();
|
||||||
|
|
||||||
|
// 记录过期的资质(用于运维监控)
|
||||||
|
var expiredCount = qualifications.Count(q =>
|
||||||
|
q.IsActive && q.ExpiryDate.HasValue && q.ExpiryDate <= currentTime);
|
||||||
|
|
||||||
|
if (expiredCount > 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("⚠️ 发现过期资质 - 任务:{TaskId},人员:{PersonnelId},过期资质数:{ExpiredCount}",
|
||||||
|
taskId, personnelId, expiredCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("📋 资质有效性验证 - 任务:{TaskId},人员:{PersonnelId}," +
|
||||||
|
"总资质:{TotalCount},有效资质:{ValidCount},过期:{ExpiredCount}",
|
||||||
|
taskId, personnelId, qualifications.Count, validQualifications.Count, expiredCount);
|
||||||
|
|
||||||
|
return validQualifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析任务的资质要求
|
||||||
|
/// 业务逻辑:解析逗号分隔的资质ID字符串,支持容错处理
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">工作任务实体</param>
|
||||||
|
/// <returns>资质ID列表</returns>
|
||||||
|
private List<long> ParseTaskQualificationRequirements(WorkOrderEntity task)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(task.ProcessEntity?.QualificationRequirements))
|
||||||
|
{
|
||||||
|
return new List<long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var qualificationRequirements = task.ProcessEntity.QualificationRequirements;
|
||||||
|
|
||||||
|
// 解析逗号分隔的资质ID字符串
|
||||||
|
var taskQualifications = qualificationRequirements
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(q => q.Trim())
|
||||||
|
.Where(q => !string.IsNullOrEmpty(q))
|
||||||
|
.Select(q => long.Parse(q))
|
||||||
|
.Distinct() // 去重
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_logger.LogDebug("📋 解析任务资质要求 - 任务:{TaskId},要求资质:[{QualificationIds}]",
|
||||||
|
task.Id, string.Join(", ", taskQualifications));
|
||||||
|
|
||||||
|
return taskQualifications;
|
||||||
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "❌ 解析任务资质要求失败 - 任务:{TaskId},资质字符串:{QualString}",
|
||||||
|
task.Id, task.ProcessEntity?.QualificationRequirements);
|
||||||
|
throw; // 重新抛出,让上层处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证资质匹配
|
||||||
|
/// 业务策略:任一匹配策略(OR逻辑)- 人员只需具备任意一种所需资质即可
|
||||||
|
/// 扩展支持:未来可配置为全匹配策略(AND逻辑)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requiredQualifications">任务要求的资质ID列表</param>
|
||||||
|
/// <param name="personnelQualifications">人员拥有的有效资质列表</param>
|
||||||
|
/// <param name="taskId">任务ID(用于日志)</param>
|
||||||
|
/// <param name="personnelId">人员ID(用于日志)</param>
|
||||||
|
/// <returns>是否匹配</returns>
|
||||||
|
private bool ValidateQualificationMatch(
|
||||||
|
List<long> requiredQualifications,
|
||||||
|
List<PersonnelQualificationCacheItem> personnelQualifications,
|
||||||
|
long taskId,
|
||||||
|
long personnelId)
|
||||||
|
{
|
||||||
|
var personnelQualificationIds = personnelQualifications
|
||||||
|
.Select(q => q.QualificationId)
|
||||||
|
.ToHashSet(); // 使用HashSet提高查询性能
|
||||||
|
|
||||||
|
// 【业务规则】:任一匹配策略 - 人员只需具备任意一种所需资质
|
||||||
|
var hasAnyRequiredQualification = requiredQualifications
|
||||||
|
.Any(reqId => personnelQualificationIds.Contains(reqId));
|
||||||
|
|
||||||
|
// 详细日志记录匹配结果
|
||||||
|
var matchedQualifications = requiredQualifications
|
||||||
|
.Where(reqId => personnelQualificationIds.Contains(reqId))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (hasAnyRequiredQualification)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("✅ 资质匹配成功 - 任务:{TaskId},人员:{PersonnelId}," +
|
||||||
|
"要求资质:[{RequiredIds}],匹配资质:[{MatchedIds}]",
|
||||||
|
taskId, personnelId,
|
||||||
|
string.Join(", ", requiredQualifications),
|
||||||
|
string.Join(", ", matchedQualifications));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("❌ 资质匹配失败 - 任务:{TaskId},人员:{PersonnelId}," +
|
||||||
|
"要求资质:[{RequiredIds}],人员资质:[{PersonnelIds}]",
|
||||||
|
taskId, personnelId,
|
||||||
|
string.Join(", ", requiredQualifications),
|
||||||
|
string.Join(", ", personnelQualificationIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAnyRequiredQualification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查岗位负责人要求
|
||||||
|
/// 业务逻辑:如果任务需要岗位负责人,验证人员是否具备高级别资质
|
||||||
|
/// 验证策略:在有效资质中查找HighLevel=true的资质
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">工作任务实体</param>
|
||||||
|
/// <param name="personnel">人员信息</param>
|
||||||
|
/// <param name="validQualifications">人员的有效资质列表</param>
|
||||||
|
/// <returns>是否符合岗位负责人要求</returns>
|
||||||
|
private async Task<bool> CheckPostHeadRequirementAsync(
|
||||||
|
WorkOrderEntity task,
|
||||||
|
GlobalPersonnelInfo personnel,
|
||||||
|
List<PersonnelQualificationCacheItem> validQualifications)
|
||||||
|
{
|
||||||
|
await Task.CompletedTask; // 保持异步接口
|
||||||
|
|
||||||
|
if (!task.NeedPostHead)
|
||||||
|
{
|
||||||
|
// 任务不需要岗位负责人,直接通过
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查人员是否具备岗位负责人资质(HighLevel=true)
|
||||||
|
var postHeadQualifications = validQualifications
|
||||||
|
.Where(q => q.HighLevel)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var hasPostHeadQualification = postHeadQualifications.Any();
|
||||||
|
|
||||||
|
if (hasPostHeadQualification)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("✅ 岗位负责人验证通过 - 任务:{TaskId},人员:{PersonnelId}," +
|
||||||
|
"负责人资质:[{PostHeadQuals}]",
|
||||||
|
task.Id, personnel.Id,
|
||||||
|
string.Join(", ", postHeadQualifications.Select(q => $"{q.QualificationId}({q.PersonnelName})")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("❌ 岗位负责人验证失败 - 任务:{TaskId},人员:{PersonnelId}," +
|
||||||
|
"原因:无岗位负责人资质(HighLevel=true)",
|
||||||
|
task.Id, personnel.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPostHeadQualification;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -376,7 +376,7 @@ namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
|||||||
private async Task ValidateTaskStatusesAsync(List<WorkOrderEntity> workOrders, List<string> validationErrors)
|
private async Task ValidateTaskStatusesAsync(List<WorkOrderEntity> workOrders, List<string> validationErrors)
|
||||||
{
|
{
|
||||||
var invalidStatusTasks = workOrders
|
var invalidStatusTasks = workOrders
|
||||||
.Where(w => w.Status != (int)WorkOrderStatusEnum.PendingAssignment)
|
.Where(w => w.Status != (int)WorkOrderStatusEnum.PendingIntegration)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (invalidStatusTasks.Any())
|
if (invalidStatusTasks.Any())
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -218,12 +218,26 @@ public class PersonnelQualificationService : BaseService, IPersonnelQualificatio
|
|||||||
{
|
{
|
||||||
// 获取所有有效的人员资质记录
|
// 获取所有有效的人员资质记录
|
||||||
// 这里不分页,获取全量数据用于构建人员池
|
// 这里不分页,获取全量数据用于构建人员池
|
||||||
var allQualifications = await _personnelQualificationRepository.Select
|
var entities = await _personnelQualificationRepository.Select
|
||||||
.Where(pq => pq.PersonnelId > 0) // 确保有有效的人员ID
|
.Where(pq => pq.PersonnelId > 0) // 确保有有效的人员ID
|
||||||
.OrderBy(pq => pq.PersonnelId)
|
.OrderBy(pq => pq.PersonnelId)
|
||||||
.ToListAsync<PersonnelQualificationGetPageOutput>();
|
.ToListAsync();
|
||||||
|
|
||||||
return allQualifications ?? new List<PersonnelQualificationGetPageOutput>();
|
// 手动映射确保PersonnelName字段被正确传递
|
||||||
|
var results = entities.Select(entity => new PersonnelQualificationGetPageOutput
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
PersonnelId = entity.PersonnelId,
|
||||||
|
PersonnelName = entity.PersonnelName, // 【关键修复】确保人员姓名被正确映射
|
||||||
|
QualificationId = entity.QualificationId,
|
||||||
|
QualificationLevel = entity.QualificationLevel,
|
||||||
|
ExpiryDate = entity.ExpiryDate,
|
||||||
|
ExpiryWarningDays = entity.ExpiryWarningDays,
|
||||||
|
RenewalDate = entity.RenewalDate,
|
||||||
|
IsActive = entity.IsActive
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -241,12 +255,26 @@ public class PersonnelQualificationService : BaseService, IPersonnelQualificatio
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获取指定人员的所有资质记录,不论是否有效
|
// 获取指定人员的所有资质记录,不论是否有效
|
||||||
var personnelQualifications = await _personnelQualificationRepository.Select
|
var entities = await _personnelQualificationRepository.Select
|
||||||
.Where(pq => pq.PersonnelId == personnelId)
|
.Where(pq => pq.PersonnelId == personnelId)
|
||||||
.OrderBy(pq => pq.QualificationId)
|
.OrderBy(pq => pq.QualificationId)
|
||||||
.ToListAsync<PersonnelQualificationGetPageOutput>();
|
.ToListAsync();
|
||||||
|
|
||||||
return personnelQualifications ?? new List<PersonnelQualificationGetPageOutput>();
|
// 手动映射确保PersonnelName字段被正确传递
|
||||||
|
var results = entities.Select(entity => new PersonnelQualificationGetPageOutput
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
PersonnelId = entity.PersonnelId,
|
||||||
|
PersonnelName = entity.PersonnelName, // 【关键修复】确保人员姓名被正确映射
|
||||||
|
QualificationId = entity.QualificationId,
|
||||||
|
QualificationLevel = entity.QualificationLevel,
|
||||||
|
ExpiryDate = entity.ExpiryDate,
|
||||||
|
ExpiryWarningDays = entity.ExpiryWarningDays,
|
||||||
|
RenewalDate = entity.RenewalDate,
|
||||||
|
IsActive = entity.IsActive
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,7 @@ using NPP.SmartSchedue.Api.Contracts.Domain.Time;
|
|||||||
using ZhonTai.DynamicApi;
|
using ZhonTai.DynamicApi;
|
||||||
using ZhonTai.DynamicApi.Attributes;
|
using ZhonTai.DynamicApi.Attributes;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace NPP.SmartSchedue.Api.Services.Time;
|
namespace NPP.SmartSchedue.Api.Services.Time;
|
||||||
@ -81,8 +82,8 @@ public class ShiftRuleService : BaseService, IShiftRuleService, IDynamicApi
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var list = _shiftRuleRepository.Select.Where(m => m.IsDeleted == false).OrderBy(a => a.RuleType);
|
var list = await _shiftRuleRepository.Select.OrderBy(a => a.RuleType).ToListAsync<ShiftRuleGetPageOutput>();
|
||||||
return list.Adapt<List<ShiftRuleGetPageOutput>>();
|
return list;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -1007,16 +1007,19 @@ public class WorkOrderService : BaseService, IWorkOrderService, IDynamicApi
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var shiftRules = await _shiftRuleService.GetListAsync();
|
var shiftRules = await _shiftRuleService.GetListAsync();
|
||||||
|
|
||||||
foreach (var shiftRule in shiftRules)
|
foreach (var shiftRule in shiftRules)
|
||||||
{
|
{
|
||||||
// 根据规则类型执行不同的检查逻辑(考虑批次上下文)
|
if (shiftRule.IsEnabled)
|
||||||
var ruleConflicts = await ValidateShiftRuleWithBatchContextAsync(personnelId, workOrder, shiftRule,
|
|
||||||
workStartTime, workEndTime, batchContext);
|
|
||||||
|
|
||||||
if (ruleConflicts.Any())
|
|
||||||
{
|
{
|
||||||
conflicts.AddRange(ruleConflicts);
|
// 根据规则类型执行不同的检查逻辑(考虑批次上下文)
|
||||||
|
var ruleConflicts = await ValidateShiftRuleWithBatchContextAsync(personnelId, workOrder, shiftRule,
|
||||||
|
workStartTime, workEndTime, batchContext);
|
||||||
|
|
||||||
|
if (ruleConflicts.Any())
|
||||||
|
{
|
||||||
|
conflicts.AddRange(ruleConflicts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user