1091 lines
54 KiB
C#
1091 lines
54 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.Extensions.Logging;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal;
|
||
using NPP.SmartSchedue.Api.Contracts.Domain.Work;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Time.Output;
|
||
|
||
namespace NPP.SmartSchedue.Api.Services.Integration.Algorithms
|
||
{
|
||
/// <summary>
|
||
/// 班次规则校验引擎
|
||
/// 【业务用途】:专门负责班次规则的批次感知验证,支持遗传算法和常规任务分配的规则检查
|
||
/// 【核心功能】:整合9种班次规则验证,支持历史数据+批次上下文的双重验证模式
|
||
/// 【架构设计】:借鉴WorkOrderService中ValidateShiftRuleWithBatchContextAsync的实现方式
|
||
/// 【性能优化】:基于规则类型的优先级验证,缓存优化,批量验证支持
|
||
/// </summary>
|
||
public class ShiftRuleValidationEngine
|
||
{
|
||
private readonly ILogger<ShiftRuleValidationEngine> _logger;
|
||
|
||
/// <summary>
|
||
/// 【缓存优化】班次规则验证结果缓存
|
||
/// Key: PersonnelId_TaskId_RuleType, Value: ValidationResult
|
||
/// </summary>
|
||
private readonly Dictionary<string, ShiftRuleValidationResult> _validationCache = new();
|
||
|
||
/// <summary>
|
||
/// 【性能监控】验证统计信息
|
||
/// </summary>
|
||
private readonly Dictionary<string, int> _validationStats = new();
|
||
|
||
public ShiftRuleValidationEngine(
|
||
ILogger<ShiftRuleValidationEngine> logger)
|
||
{
|
||
_logger = logger;
|
||
_logger.LogInformation("班次规则校验引擎初始化完成,支持9种规则类型的批次感知验证,使用缓存替代班次服务");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【核心验证方法】增强版批次感知班次规则验证
|
||
/// 【业务逻辑】:整合9种班次规则的完整验证,支持历史数据+批次上下文的双重验证模式
|
||
/// 【验证策略】:优先验证批次上下文冲突,然后结合历史数据进行完整性检查
|
||
/// 【性能优化】:基于规则类型的优先级验证,快速识别高风险冲突
|
||
/// 【深度思考】:该方法是遗传算法适应度计算的关键组件,必须确保验证的准确性和性能
|
||
/// </summary>
|
||
/// <param name="personnelId">验证的人员ID</param>
|
||
/// <param name="currentTask">当前待验证的任务</param>
|
||
/// <param name="batchContext">批次上下文(不包含当前任务的其他相关任务)</param>
|
||
/// <returns>发现的班次规则冲突列表</returns>
|
||
public async Task<List<string>> ValidateShiftRulesWithEnhancedBatchContextAsync(
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
var validationStartTime = DateTime.Now;
|
||
|
||
try
|
||
{
|
||
// 【前置检查】验证输入参数有效性
|
||
if (!currentTask.ShiftId.HasValue)
|
||
{
|
||
_logger.LogWarning("【批次感知验证跳过】任务{TaskCode}缺少班次ID", currentTask.WorkOrderCode);
|
||
return conflicts;
|
||
}
|
||
|
||
var currentTaskHash = $"{personnelId}_{currentTask.Id}_{currentTask.ShiftId}";
|
||
|
||
_logger.LogDebug("【增强版班次规则验证开始】人员{PersonnelId}任务{TaskCode}({Date})班次{ShiftId},批次上下文{BatchCount}个任务",
|
||
personnelId, currentTask.WorkOrderCode, currentTask.WorkOrderDate.ToString("yyyy-MM-dd"),
|
||
currentTask.ShiftId, batchContext.Count);
|
||
|
||
// 【规则验证主循环】遍历所有启用的班次规则进行验证
|
||
var validationTasks = new List<Task<List<string>>>();
|
||
var ruleValidationResults = new Dictionary<string, List<string>>();
|
||
|
||
// 【并行验证优化】:对独立的规则进行并行验证以提升性能
|
||
foreach (var ruleType in GetEnabledShiftRuleTypes(context))
|
||
{
|
||
var validationTask = ValidateSpecificShiftRuleAsync(
|
||
ruleType, personnelId, currentTask, batchContext, context);
|
||
validationTasks.Add(validationTask);
|
||
}
|
||
|
||
// 等待所有规则验证完成
|
||
var allRuleResults = await Task.WhenAll(validationTasks);
|
||
|
||
// 【结果整合】合并所有规则的验证结果
|
||
for (int i = 0; i < allRuleResults.Length; i++)
|
||
{
|
||
var ruleType = GetEnabledShiftRuleTypes(context).ToList()[i];
|
||
var ruleConflicts = allRuleResults[i];
|
||
|
||
if (ruleConflicts.Any())
|
||
{
|
||
conflicts.AddRange(ruleConflicts);
|
||
ruleValidationResults[ruleType] = ruleConflicts;
|
||
}
|
||
}
|
||
|
||
// 【性能统计】记录验证性能和结果统计
|
||
var validationDuration = DateTime.Now - validationStartTime;
|
||
UpdateValidationStatistics(currentTaskHash, conflicts.Count, validationDuration);
|
||
|
||
_logger.LogInformation("【增强版班次规则验证完成】人员{PersonnelId}任务{TaskCode}共检查{RuleCount}种规则,发现{ConflictCount}个冲突,耗时{Duration}ms",
|
||
personnelId, currentTask.WorkOrderCode, GetEnabledShiftRuleTypes(context).Count(),
|
||
conflicts.Count, validationDuration.TotalMilliseconds);
|
||
|
||
// 【详细冲突日志】记录前5个具体冲突详情
|
||
LogConflictDetails(personnelId, currentTask.WorkOrderCode, conflicts, ruleValidationResults);
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【增强版班次规则验证异常】人员{PersonnelId}任务{TaskCode}验证失败",
|
||
personnelId, currentTask.WorkOrderCode);
|
||
|
||
// 【异常处理】异常情况下返回通用冲突信息,确保系统稳定性
|
||
return new List<string> { $"人员{personnelId}任务{currentTask.WorkOrderCode}班次规则验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则验证核心】验证特定班次规则类型
|
||
/// 【架构设计】:采用策略模式,根据规则类型分发到具体的验证方法
|
||
/// 【业务覆盖】:支持规则3-9的完整验证覆盖
|
||
/// </summary>
|
||
/// <param name="ruleType">规则类型标识</param>
|
||
/// <param name="personnelId">人员ID</param>
|
||
/// <param name="currentTask">当前任务</param>
|
||
/// <param name="batchContext">批次上下文</param>
|
||
/// <returns>该规则类型的冲突列表</returns>
|
||
private async Task<List<string>> ValidateSpecificShiftRuleAsync(
|
||
string ruleType, long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
try
|
||
{
|
||
// 【缓存检查】首先检查缓存中是否有该规则的验证结果
|
||
var cacheKey = $"{personnelId}_{currentTask.Id}_{ruleType}";
|
||
if (_validationCache.TryGetValue(cacheKey, out var cachedResult))
|
||
{
|
||
_logger.LogTrace("【缓存命中】规则{RuleType}验证结果来自缓存", ruleType);
|
||
return cachedResult.Conflicts;
|
||
}
|
||
|
||
List<string> conflicts = new List<string>();
|
||
|
||
// 【规则分发】根据规则类型分发到具体的验证逻辑
|
||
switch (ruleType)
|
||
{
|
||
case "3": // 一/二班不连续
|
||
conflicts = await ValidateShiftSequenceRuleWithBatchContextAsync(
|
||
"一/二班不连续规则", personnelId, currentTask, "一", "二", batchContext, context);
|
||
break;
|
||
|
||
case "4": // 二/三班不连续
|
||
conflicts = await ValidateShiftSequenceRuleWithBatchContextAsync(
|
||
"二/三班不连续规则", personnelId, currentTask, "二", "三", batchContext, context);
|
||
break;
|
||
|
||
case "5": // 不能这周日/下周六连
|
||
conflicts = await ValidateSundayToSaturdayRuleWithBatchContextAsync(
|
||
"周日/周六连续规则", personnelId, currentTask, batchContext, context);
|
||
break;
|
||
|
||
case "6": // 不能本周六/本周日连
|
||
conflicts = await ValidateSaturdayToSundayRuleWithBatchContextAsync(
|
||
"周六/周日连续规则", personnelId, currentTask, batchContext, context);
|
||
break;
|
||
|
||
case "7": // 不能连续7天排班
|
||
conflicts = await ValidateMaxConsecutiveDaysRuleWithBatchContextAsync(
|
||
"连续7天排班限制规则", personnelId, currentTask, 7, batchContext, context);
|
||
break;
|
||
|
||
case "8": // 三班后一天不排班
|
||
conflicts = await ValidateRestAfterShiftRuleWithBatchContextAsync(
|
||
"三班后休息规则", personnelId, currentTask, "三", batchContext, context);
|
||
break;
|
||
|
||
case "9": // 二班后一天不排班
|
||
conflicts = await ValidateRestAfterShiftRuleWithBatchContextAsync(
|
||
"二班后休息规则", personnelId, currentTask, "二", batchContext, context);
|
||
break;
|
||
|
||
case "12": // 当天最多排一个班次
|
||
conflicts = await ValidatePersonShiftRuleWithBatchContextAsync(
|
||
"一人当天只排一个班次", personnelId, currentTask, "二", batchContext, context);
|
||
break;
|
||
|
||
default:
|
||
// 【扩展性设计】未知规则类型记录警告但不阻止任务
|
||
_logger.LogWarning("【未知规则类型】规则类型{RuleType}暂未实现验证逻辑", ruleType);
|
||
break;
|
||
}
|
||
|
||
// 【缓存更新】将验证结果存入缓存
|
||
var validationResult = new ShiftRuleValidationResult
|
||
{
|
||
RuleType = ruleType,
|
||
PersonnelId = personnelId,
|
||
TaskId = currentTask.Id,
|
||
IsValid = !conflicts.Any(),
|
||
Conflicts = conflicts,
|
||
ValidatedAt = DateTime.Now
|
||
};
|
||
|
||
// 【内存管理】限制缓存大小,避免内存泄漏
|
||
if (_validationCache.Count > 1000)
|
||
{
|
||
ClearOldCacheEntries();
|
||
}
|
||
|
||
_validationCache[cacheKey] = validationResult;
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【规则验证异常】规则类型{RuleType}验证异常", ruleType);
|
||
return new List<string> { $"规则{ruleType}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则实现3&4】班次连续性规则验证(一/二班不连续,二/三班不连续)
|
||
/// 【业务逻辑】:同一人员同一天不能有冲突的班次安排
|
||
/// 【深度思考】:需要综合考虑批次内任务和数据库历史任务,确保无遗漏
|
||
/// 【验证范围】:批次内同日冲突 + 数据库历史同日冲突
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateShiftSequenceRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask,
|
||
string firstShiftName, string secondShiftName, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
|
||
try
|
||
{
|
||
// 【班次信息获取】获取当前任务的班次信息
|
||
var currentShift = context.ShiftInformationCache.Where(a => a.Id == currentTask.ShiftId).FirstOrDefault();
|
||
if (currentShift == null)
|
||
{
|
||
_logger.LogWarning("【班次规则】任务{TaskCode}的班次{ShiftId}信息不存在",
|
||
currentTask.WorkOrderCode, currentTask.ShiftId);
|
||
return conflicts;
|
||
}
|
||
|
||
var currentDate = currentTask.WorkOrderDate.Date;
|
||
|
||
// 【班次类型判断】检查当前班次是否为规则关注的班次之一
|
||
bool currentIsFirstShift = currentShift.Name?.Contains(firstShiftName) == true;
|
||
bool currentIsSecondShift = currentShift.Name?.Contains(secondShiftName) == true;
|
||
|
||
if (!currentIsFirstShift && !currentIsSecondShift)
|
||
{
|
||
// 当前班次不是规则关注的班次,无需验证
|
||
return conflicts;
|
||
}
|
||
|
||
_logger.LogDebug("【班次连续性检查】人员{PersonnelId}任务{TaskCode}当前为{ShiftType},检查与{OtherShiftType}的冲突",
|
||
personnelId, currentTask.WorkOrderCode,
|
||
currentIsFirstShift ? firstShiftName : secondShiftName,
|
||
currentIsFirstShift ? secondShiftName : firstShiftName);
|
||
|
||
// 【批次内冲突检查】检查批次内同一天是否有冲突班次
|
||
await ValidateBatchContextShiftConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, currentDate, currentIsFirstShift, currentIsSecondShift,
|
||
firstShiftName, secondShiftName, context);
|
||
|
||
// 【历史数据冲突检查】检查数据库中同一天是否有冲突班次
|
||
await ValidateHistoricalDataShiftConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
currentDate, currentIsFirstShift, currentIsSecondShift,
|
||
firstShiftName, secondShiftName, context);
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【班次连续性规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【批次内冲突检查】验证批次上下文中的班次冲突
|
||
/// </summary>
|
||
private async Task ValidateBatchContextShiftConflicts(
|
||
List<string> conflicts, string ruleName, long personnelId, WorkOrderEntity currentTask,
|
||
List<WorkOrderEntity> batchContext, DateTime currentDate,
|
||
bool currentIsFirstShift, bool currentIsSecondShift,
|
||
string firstShiftName, string secondShiftName, GlobalAllocationContext context)
|
||
{
|
||
// 获取批次内同一天同一人员的其他任务
|
||
var batchSameDayTasks = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == currentDate)
|
||
.ToList();
|
||
|
||
foreach (var batchTask in batchSameDayTasks)
|
||
{
|
||
if (batchTask.ShiftId.HasValue)
|
||
{
|
||
var batchShift = context.ShiftInformationCache.Where(a => a.Id == batchTask.ShiftId.Value)
|
||
.FirstOrDefault();
|
||
if (batchShift == null) continue;
|
||
|
||
bool batchIsFirstShift = batchShift.Name?.Contains(firstShiftName) == true;
|
||
bool batchIsSecondShift = batchShift.Name?.Contains(secondShiftName) == true;
|
||
|
||
// 【冲突检测】检查班次冲突:当前是一班且批次内有二班,或当前是二班且批次内有一班
|
||
if ((currentIsFirstShift && batchIsSecondShift) || (currentIsSecondShift && batchIsFirstShift))
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: 同一天内不能同时安排{firstShiftName}班和{secondShiftName}班 " +
|
||
$"(与批次内任务 '{batchTask.WorkOrderCode}' {batchShift.Name} 冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
|
||
_logger.LogError("【批次内班次冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【历史数据冲突检查】验证历史数据中的班次冲突
|
||
/// </summary>
|
||
private async Task ValidateHistoricalDataShiftConflicts(
|
||
List<string> conflicts, string ruleName, long personnelId, WorkOrderEntity currentTask,
|
||
DateTime currentDate, bool currentIsFirstShift, bool currentIsSecondShift,
|
||
string firstShiftName, string secondShiftName, GlobalAllocationContext context)
|
||
{
|
||
// 【历史数据查询优化】:使用上下文中的任务数据而非直接数据库查询
|
||
var historicalSameDayTasks = context.Tasks?.Where(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.Id != currentTask.Id &&
|
||
wo.WorkOrderDate.Date == currentDate).ToList() ?? new List<WorkOrderEntity>();
|
||
|
||
foreach (var dbTask in historicalSameDayTasks)
|
||
{
|
||
if (dbTask.ShiftId.HasValue)
|
||
{
|
||
var dbShift = context.ShiftInformationCache.Where(a => a.Id == dbTask.ShiftId.Value)
|
||
.FirstOrDefault();
|
||
if (dbShift == null) continue;
|
||
|
||
bool dbIsFirstShift = dbShift.Name?.Contains(firstShiftName) == true;
|
||
bool dbIsSecondShift = dbShift.Name?.Contains(secondShiftName) == true;
|
||
|
||
// 【历史冲突检测】检查与历史数据的班次冲突
|
||
if ((currentIsFirstShift && dbIsSecondShift) || (currentIsSecondShift && dbIsFirstShift))
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: 同一天内不能同时安排{firstShiftName}班和{secondShiftName}班 " +
|
||
$"(与历史任务 '{dbTask.WorkOrderCode}' {dbShift.Name} 冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
|
||
_logger.LogError("【历史数据班次冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则实现5】周日/下周六连续规则验证
|
||
/// 【业务逻辑】:防止跨周连续工作,检查周日->下周六的7天跨度
|
||
/// 【深度思考】:批次内可能同时包含这两个时间点的任务,需要双向检查
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateSundayToSaturdayRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
await Task.CompletedTask;
|
||
|
||
try
|
||
{
|
||
var currentDate = currentTask.WorkOrderDate.Date;
|
||
|
||
// 【情况1】当前任务是周日,检查下周六是否有排班
|
||
if (currentDate.DayOfWeek == DayOfWeek.Sunday)
|
||
{
|
||
var nextSaturday = currentDate.AddDays(6); // 周日 + 6天 = 下周六
|
||
await CheckNextSaturdayConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, nextSaturday, context);
|
||
}
|
||
|
||
// 【情况2】当前任务是周六,检查上周日是否有排班
|
||
if (currentDate.DayOfWeek == DayOfWeek.Saturday)
|
||
{
|
||
var lastSunday = currentDate.AddDays(-6); // 周六 - 6天 = 上周日
|
||
await CheckLastSundayConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, lastSunday, context);
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【周日/周六连续规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则实现6】周六/周日连续规则验证
|
||
/// 【业务逻辑】:防止周末连续工作,批次内可能同时包含同一人员的周六和周日任务
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateSaturdayToSundayRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
await Task.CompletedTask;
|
||
|
||
try
|
||
{
|
||
var currentDate = currentTask.WorkOrderDate.Date;
|
||
|
||
// 【情况1】当前是周六,检查周日是否有排班
|
||
if (currentDate.DayOfWeek == DayOfWeek.Saturday)
|
||
{
|
||
var sunday = currentDate.AddDays(1);
|
||
await CheckAdjacentDayConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, sunday, "周六", "周日", context);
|
||
}
|
||
|
||
// 【情况2】当前是周日,检查周六是否有排班
|
||
if (currentDate.DayOfWeek == DayOfWeek.Sunday)
|
||
{
|
||
var saturday = currentDate.AddDays(-1);
|
||
await CheckAdjacentDayConflicts(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, saturday, "周日", "周六", context);
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【周六/周日连续规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则实现7】最大连续天数规则验证
|
||
/// 【业务逻辑】:最严格的连续工作限制,基于批次+历史数据计算真实的连续工作天数序列
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateMaxConsecutiveDaysRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask, int maxDays,
|
||
List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
|
||
try
|
||
{
|
||
// 【连续天数计算】使用批次感知的连续工作天数计算
|
||
var consecutiveDays = await CalculateConsecutiveWorkDaysWithBatchContextAsync(
|
||
personnelId, currentTask.WorkOrderDate, batchContext, context);
|
||
|
||
if (consecutiveDays >= maxDays)
|
||
{
|
||
// 【相关任务信息】获取相关的批次内任务信息用于详细说明
|
||
var relatedBatchTasks = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => Math.Abs((wo.WorkOrderDate.Date - currentTask.WorkOrderDate.Date).Days) <= maxDays)
|
||
.Select(wo => wo.WorkOrderCode)
|
||
.ToList();
|
||
|
||
var batchTasksInfo = relatedBatchTasks.Any() ?
|
||
$"(涉及批次内任务: {string.Join(", ", relatedBatchTasks)})" : "";
|
||
|
||
var conflictMessage = $"违反{ruleName}: 连续工作 {consecutiveDays} 天超过限制 {maxDays} 天 {batchTasksInfo}";
|
||
conflicts.Add(conflictMessage);
|
||
|
||
_logger.LogError("【连续天数规则冲突】{Conflict}", conflictMessage);
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【连续天数规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【规则实现8&9】班次后休息规则验证(二班后休息,三班后休息)
|
||
/// 【业务逻辑】:指定班次后次日必须休息,需要双向检查前一天和后一天
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateRestAfterShiftRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask, string shiftName,
|
||
List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
|
||
try
|
||
{
|
||
var currentDate = currentTask.WorkOrderDate.Date;
|
||
|
||
// 【双向检查1】检查前一天是否是指定班次
|
||
await CheckPreviousDayRestRule(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, shiftName, currentDate, context);
|
||
|
||
// 【双向检查2】检查当前是指定班次时后一天是否安排了工作
|
||
await CheckNextDayRestRule(conflicts, ruleName, personnelId, currentTask,
|
||
batchContext, shiftName, currentDate, context);
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【班次后休息规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
private async Task<List<string>> ValidatePersonShiftRuleWithBatchContextAsync(
|
||
string ruleName, long personnelId, WorkOrderEntity currentTask, string shiftName,
|
||
List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
var conflicts = new List<string>();
|
||
|
||
try
|
||
{
|
||
var currentDate = currentTask.WorkOrderDate.Date;
|
||
|
||
var batchNextTask = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == currentDate)
|
||
.Count();
|
||
|
||
if (batchNextTask > 1)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内安排冲突】{Conflict}", conflictMessage);
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【班次后休息规则验证异常】规则{RuleName}", ruleName);
|
||
return new List<string> { $"{ruleName}验证异常: {ex.Message}" };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查前一天的休息规则
|
||
/// </summary>
|
||
private async Task CheckPreviousDayRestRule(List<string> conflicts, string ruleName,
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext,
|
||
string shiftName, DateTime currentDate, GlobalAllocationContext context)
|
||
{
|
||
var previousDay = currentDate.AddDays(-1);
|
||
|
||
// 【批次内检查】先检查批次内是否有前一天的指定班次任务
|
||
var batchPreviousTask = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == previousDay)
|
||
.FirstOrDefault();
|
||
|
||
if (batchPreviousTask?.ShiftId.HasValue == true)
|
||
{
|
||
var batchPreviousShift = context.ShiftInformationCache.Where(a => a.Id == batchPreviousTask.ShiftId.Value)
|
||
.FirstOrDefault();
|
||
if (batchPreviousShift?.Name?.Contains(shiftName) == true)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: 批次内任务 '{batchPreviousTask.WorkOrderCode}' " +
|
||
$"{shiftName}班后一天不能安排工作";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内休息规则冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 【历史数据检查】检查历史数据中前一天是否有指定班次
|
||
await CheckHistoricalPreviousDayTask(conflicts, ruleName, personnelId, previousDay, shiftName, context);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查后一天的休息规则
|
||
/// </summary>
|
||
private async Task CheckNextDayRestRule(List<string> conflicts, string ruleName,
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext,
|
||
string shiftName, DateTime currentDate, GlobalAllocationContext context)
|
||
{
|
||
// 【当前任务检查】检查当前任务是否为指定班次
|
||
var currentShift = context.ShiftInformationCache.Where(a => a.Id == (currentTask.ShiftId ?? 0))
|
||
.FirstOrDefault();
|
||
if (currentShift?.Name?.Contains(shiftName) == true)
|
||
{
|
||
var nextDay = currentDate.AddDays(1);
|
||
|
||
// 【批次内后一天检查】检查批次内是否有后一天的任务
|
||
var batchNextTask = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == nextDay)
|
||
.Any();
|
||
|
||
if (batchNextTask)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: {shiftName}班后一天不能安排批次内任务";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内次日安排冲突】{Conflict}", conflictMessage);
|
||
}
|
||
else
|
||
{
|
||
// 【历史数据后一天检查】检查历史数据中是否有后一天的任务
|
||
await CheckHistoricalNextDayTask(conflicts, ruleName, personnelId, nextDay, shiftName, context);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【缓存管理】清理过期的缓存条目
|
||
/// </summary>
|
||
private void ClearOldCacheEntries()
|
||
{
|
||
var cutoffTime = DateTime.Now.AddMinutes(-30); // 清理30分钟前的缓存
|
||
var keysToRemove = _validationCache
|
||
.Where(kvp => kvp.Value.ValidatedAt < cutoffTime)
|
||
.Select(kvp => kvp.Key)
|
||
.Take(200) // 每次最多清理200个
|
||
.ToList();
|
||
|
||
foreach (var key in keysToRemove)
|
||
{
|
||
_validationCache.Remove(key);
|
||
}
|
||
|
||
_logger.LogDebug("【缓存清理】清理了{Count}个过期的验证缓存项", keysToRemove.Count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【统计更新】更新验证统计信息
|
||
/// </summary>
|
||
private void UpdateValidationStatistics(string taskHash, int conflictCount, TimeSpan duration)
|
||
{
|
||
var statsKey = conflictCount > 0 ? "ValidationWithConflicts" : "ValidationPassed";
|
||
_validationStats[statsKey] = _validationStats.GetValueOrDefault(statsKey, 0) + 1;
|
||
_validationStats["TotalValidationTime"] = _validationStats.GetValueOrDefault("TotalValidationTime", 0) + (int)duration.TotalMilliseconds;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【日志记录】记录冲突详情
|
||
/// </summary>
|
||
private void LogConflictDetails(long personnelId, string taskCode, List<string> allConflicts, Dictionary<string, List<string>> ruleResults)
|
||
{
|
||
if (allConflicts.Any())
|
||
{
|
||
_logger.LogError("【冲突汇总】人员{PersonnelId}任务{TaskCode}发现{TotalConflicts}个冲突",
|
||
personnelId, taskCode, allConflicts.Count);
|
||
|
||
// 按规则类型分组显示冲突
|
||
foreach (var rule in ruleResults)
|
||
{
|
||
_logger.LogError("【规则{RuleType}冲突】{ConflictCount}个: {Conflicts}",
|
||
rule.Key, rule.Value.Count, string.Join("; ", rule.Value.Take(2)));
|
||
}
|
||
|
||
// 显示前5个具体冲突
|
||
for (int i = 0; i < Math.Min(5, allConflicts.Count); i++)
|
||
{
|
||
_logger.LogError("【冲突详情{Index}】{Conflict}", i + 1, allConflicts[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【配置方法】获取启用的班次规则类型列表
|
||
/// </summary>
|
||
private IEnumerable<string> GetEnabledShiftRuleTypes(GlobalAllocationContext context)
|
||
{
|
||
return context.ShiftRulesMapping.OrderBy(a => a.RuleType).Where(a=> a.IsEnabled == true).Select(a => a.RuleType).ToArray();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查下周六冲突 - 周日/下周六连续规则的核心逻辑
|
||
/// </summary>
|
||
private async Task CheckNextSaturdayConflicts(List<string> conflicts, string ruleName,
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, DateTime nextSaturday, GlobalAllocationContext context)
|
||
{
|
||
// 【批次内检查】先检查批次内是否有下周六的任务
|
||
var batchNextSaturday = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == nextSaturday)
|
||
.FirstOrDefault();
|
||
|
||
if (batchNextSaturday != null)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: 不能在这周日和下周六安排工作 " +
|
||
$"(与批次内任务 '{batchNextSaturday.WorkOrderCode}' 冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内跨周冲突】{Conflict}", conflictMessage);
|
||
}
|
||
else
|
||
{
|
||
// 【历史数据检查】检查历史数据中下周六是否有任务
|
||
var dbNextSaturday = context.Tasks?.Any(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.WorkOrderDate.Date == nextSaturday) == true;
|
||
|
||
if (dbNextSaturday)
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: 不能在这周日和下周六安排工作 (与历史数据任务冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【历史数据跨周冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查上周日冲突 - 周日/下周六连续规则的反向逻辑
|
||
/// </summary>
|
||
private async Task CheckLastSundayConflicts(List<string> conflicts, string ruleName,
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext, DateTime lastSunday, GlobalAllocationContext context)
|
||
{
|
||
// 【批次内检查】先检查批次内是否有上周日的任务
|
||
var batchLastSunday = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == lastSunday)
|
||
.FirstOrDefault();
|
||
|
||
if (batchLastSunday != null)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: 不能在上周日和这周六安排工作 " +
|
||
$"(与批次内任务 '{batchLastSunday.WorkOrderCode}' 冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内反向跨周冲突】{Conflict}", conflictMessage);
|
||
}
|
||
else
|
||
{
|
||
// 【历史数据检查】检查历史数据中上周日是否有任务
|
||
var dbLastSunday = context.Tasks?.Any(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.WorkOrderDate.Date == lastSunday) == true;
|
||
|
||
if (dbLastSunday)
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: 不能在上周日和这周六安排工作 (与历史数据任务冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【历史数据反向跨周冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查相邻日期冲突 - 周六/周日连续规则的通用逻辑
|
||
/// </summary>
|
||
private async Task CheckAdjacentDayConflicts(List<string> conflicts, string ruleName,
|
||
long personnelId, WorkOrderEntity currentTask, List<WorkOrderEntity> batchContext,
|
||
DateTime adjacentDay, string currentDayName, string adjacentDayName, GlobalAllocationContext context)
|
||
{
|
||
// 【批次内相邻日检查】先检查批次内相邻日期任务
|
||
var batchAdjacentTask = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.Where(wo => wo.WorkOrderDate.Date == adjacentDay)
|
||
.FirstOrDefault();
|
||
|
||
if (batchAdjacentTask != null)
|
||
{
|
||
var conflictMessage = $"违反批次内{ruleName}: 不能在{currentDayName}和{adjacentDayName}连续安排工作 " +
|
||
$"(与批次内任务 '{batchAdjacentTask.WorkOrderCode}' 冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【批次内相邻日冲突】{Conflict}", conflictMessage);
|
||
}
|
||
else
|
||
{
|
||
// 【历史数据相邻日检查】检查历史数据中相邻日期是否有任务
|
||
var dbAdjacentTask = context.Tasks?.Any(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.WorkOrderDate.Date == adjacentDay) == true;
|
||
|
||
if (dbAdjacentTask)
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: 不能在{currentDayName}和{adjacentDayName}连续安排工作 (与历史数据任务冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【历史数据相邻日冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查历史数据前一天任务 - 班次后休息规则的历史数据检查
|
||
/// </summary>
|
||
private async Task CheckHistoricalPreviousDayTask(List<string> conflicts, string ruleName,
|
||
long personnelId, DateTime previousDay, string shiftName, GlobalAllocationContext context)
|
||
{
|
||
var dbPreviousTask = context.Tasks?.FirstOrDefault(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.WorkOrderDate.Date == previousDay);
|
||
|
||
if (dbPreviousTask?.ShiftId.HasValue == true)
|
||
{
|
||
var dbPreviousShift = context.ShiftInformationCache.Where(a => a.Id == dbPreviousTask.ShiftId.Value)
|
||
.FirstOrDefault();
|
||
if (dbPreviousShift?.Name?.Contains(shiftName) == true)
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: 历史数据任务 '{dbPreviousTask.WorkOrderCode}' " +
|
||
$"{shiftName}班后一天不能安排工作";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【历史数据前日班次冲突】{Conflict}", conflictMessage);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查历史数据后一天任务 - 班次后休息规则的历史数据检查
|
||
/// </summary>
|
||
private async Task CheckHistoricalNextDayTask(List<string> conflicts, string ruleName,
|
||
long personnelId, DateTime nextDay, string shiftName, GlobalAllocationContext context)
|
||
{
|
||
var dbNextTask = context.Tasks?.Any(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
wo.WorkOrderDate.Date == nextDay) == true;
|
||
|
||
if (dbNextTask)
|
||
{
|
||
var conflictMessage = $"违反{ruleName}: {shiftName}班后一天不能安排工作(与历史数据任务冲突)";
|
||
conflicts.Add(conflictMessage);
|
||
_logger.LogError("【历史数据次日安排冲突】{Conflict}", conflictMessage);
|
||
}
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【核心算法】计算批次感知的连续工作天数 - 最大连续天数规则的核心实现
|
||
/// 【业务逻辑】:整合批次内任务和历史数据,计算真实的连续工作天数
|
||
/// 【深度思考】:需要处理批次内任务的时序关系,确保连续性计算的准确性
|
||
/// </summary>
|
||
private async Task<int> CalculateConsecutiveWorkDaysWithBatchContextAsync(
|
||
long personnelId, DateTime targetDate, List<WorkOrderEntity> batchContext, GlobalAllocationContext context)
|
||
{
|
||
await Task.CompletedTask;
|
||
|
||
try
|
||
{
|
||
var consecutiveDays = 1; // 包含目标日期
|
||
|
||
// 【批次任务整合】获取批次内该人员的任务,按日期排序
|
||
var personnelBatchTasks = batchContext
|
||
.Where(wo => wo.AssignedPersonnelId == personnelId)
|
||
.OrderBy(wo => wo.WorkOrderDate.Date)
|
||
.ToList();
|
||
|
||
// 【历史任务整合】获取历史上下文中该人员的任务
|
||
var personnelHistoryTasks = context.Tasks?.Where(wo =>
|
||
wo.AssignedPersonnelId == personnelId &&
|
||
!personnelBatchTasks.Any(bt => bt.Id == wo.Id)) // 避免重复
|
||
.ToList() ?? new List<WorkOrderEntity>();
|
||
|
||
// 【时序数据构建】创建包含批次任务和历史任务的完整工作日期集合
|
||
var allWorkDates = new HashSet<DateTime>();
|
||
|
||
// 添加批次内任务日期
|
||
foreach (var task in personnelBatchTasks)
|
||
{
|
||
allWorkDates.Add(task.WorkOrderDate.Date);
|
||
}
|
||
|
||
// 添加历史任务日期
|
||
foreach (var task in personnelHistoryTasks)
|
||
{
|
||
allWorkDates.Add(task.WorkOrderDate.Date);
|
||
}
|
||
|
||
// 包含当前目标日期
|
||
allWorkDates.Add(targetDate.Date);
|
||
|
||
// 【连续性计算】从目标日期往前检查连续工作天数
|
||
var currentDate = targetDate.Date.AddDays(-1); // 从目标日期前一天开始往前检查
|
||
|
||
while (true)
|
||
{
|
||
// 【工作日检查】检查当前日期是否有工作安排
|
||
bool hasWork = allWorkDates.Contains(currentDate);
|
||
|
||
if (!hasWork)
|
||
{
|
||
break; // 没有工作任务,中断连续天数
|
||
}
|
||
|
||
// 【班次不可用性检查】检查是否有班次不可用情况 - 深度实现
|
||
var isUnavailable = CheckPersonnelShiftUnavailability(personnelId, currentDate, context);
|
||
|
||
_logger.LogTrace("【班次不可用性检查】人员{PersonnelId}在{Date:yyyy-MM-dd}的可用状态:{IsAvailable}",
|
||
personnelId, currentDate, !isUnavailable);
|
||
|
||
if (isUnavailable)
|
||
{
|
||
break; // 班次不可用,中断连续天数
|
||
}
|
||
|
||
consecutiveDays++;
|
||
currentDate = currentDate.AddDays(-1);
|
||
|
||
// 【安全限制】避免无限循环,最多检查60天
|
||
if (consecutiveDays > 60)
|
||
{
|
||
_logger.LogWarning("【连续天数计算】人员{PersonnelId}连续工作天数计算超过60天限制", personnelId);
|
||
break;
|
||
}
|
||
}
|
||
|
||
_logger.LogDebug("【连续天数计算完成】人员{PersonnelId}目标日期{TargetDate:yyyy-MM-dd}连续工作{ConsecutiveDays}天",
|
||
personnelId, targetDate, consecutiveDays);
|
||
|
||
return consecutiveDays;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【连续天数计算异常】人员{PersonnelId}目标日期{TargetDate}", personnelId, targetDate);
|
||
return 1; // 异常时返回最小值,避免过度限制
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【深度实现】检查人员在指定日期的班次不可用性
|
||
/// 【业务思考】:整合GlobalAllocationContext中的班次不可用性缓存,支持高性能查询
|
||
/// 【数据来源】:DateShiftUnavailablePersonnel + PersonnelUnavailabilityIndex + UnavailabilityDetails
|
||
/// 【查询策略】:优先使用PersonnelUnavailabilityIndex进行O(1)查询,再检查详细信息
|
||
/// 【约束类型】:区分硬约束(请假、医疗)和软约束(个人意愿、培训)
|
||
/// </summary>
|
||
/// <param name="personnelId">人员ID</param>
|
||
/// <param name="checkDate">检查日期</param>
|
||
/// <param name="context">全局分配上下文</param>
|
||
/// <returns>是否不可用(true表示不可用,false表示可用)</returns>
|
||
private bool CheckPersonnelShiftUnavailability(long personnelId, DateTime checkDate, GlobalAllocationContext context)
|
||
{
|
||
try
|
||
{
|
||
var targetDate = checkDate.Date;
|
||
|
||
// 【查询策略1】优先使用PersonnelUnavailabilityIndex进行O(1)查询
|
||
if (context.PersonnelUnavailabilityIndex.TryGetValue(personnelId, out var unavailabilityDates))
|
||
{
|
||
// 检查该人员在目标日期是否有不可用记录
|
||
if (unavailabilityDates.TryGetValue(targetDate, out var unavailableShifts))
|
||
{
|
||
// 只要有任意班次不可用,就认为该日不可用(保守策略)
|
||
if (unavailableShifts.Any())
|
||
{
|
||
_logger.LogDebug("【班次不可用性命中】人员{PersonnelId}在{Date:yyyy-MM-dd}有{ShiftCount}个班次不可用",
|
||
personnelId, checkDate, unavailableShifts.Count);
|
||
|
||
// 【详细信息查询】获取不可用的具体原因和约束类型
|
||
var hardConstraintExists = CheckForHardConstraints(personnelId, targetDate, unavailableShifts, context);
|
||
|
||
if (hardConstraintExists)
|
||
{
|
||
_logger.LogDebug("【硬约束检测】人员{PersonnelId}在{Date:yyyy-MM-dd}存在硬约束不可用情况", personnelId, checkDate);
|
||
return true; // 硬约束不可用
|
||
}
|
||
else
|
||
{
|
||
_logger.LogTrace("【软约束检测】人员{PersonnelId}在{Date:yyyy-MM-dd}仅有软约束不可用,可协调", personnelId, checkDate);
|
||
return false; // 软约束可协调,认为可用
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 【查询策略2】后备查询:使用DateShiftUnavailablePersonnel验证
|
||
var fallbackUnavailable = CheckDateShiftUnavailabilityFallback(personnelId, targetDate, context);
|
||
if (fallbackUnavailable)
|
||
{
|
||
_logger.LogDebug("【后备查询命中】人员{PersonnelId}在{Date:yyyy-MM-dd}通过后备查询发现不可用", personnelId, checkDate);
|
||
return true;
|
||
}
|
||
|
||
// 【可用结论】所有查询都未发现不可用情况
|
||
_logger.LogTrace("【班次可用性验证通过】人员{PersonnelId}在{Date:yyyy-MM-dd}可用", personnelId, checkDate);
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【班次不可用性检查异常】人员{PersonnelId}日期{CheckDate:yyyy-MM-dd}", personnelId, checkDate);
|
||
|
||
// 【异常处理】异常情况下保守处理,假设可用(避免过度限制)
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】检查硬约束是否存在
|
||
/// 【业务逻辑】:通过UnavailabilityDetails查询具体的不可用原因,区分硬约束和软约束
|
||
/// 【约束类型】:硬约束(请假、医疗)必须遵守,软约束(个人意愿、培训)可协调
|
||
/// </summary>
|
||
private bool CheckForHardConstraints(long personnelId, DateTime targetDate, HashSet<long> unavailableShifts, GlobalAllocationContext context)
|
||
{
|
||
try
|
||
{
|
||
// 遍历所有不可用的班次,检查是否存在硬约束
|
||
foreach (var shiftId in unavailableShifts)
|
||
{
|
||
var detailKey = $"{targetDate:yyyyMMdd}_{shiftId}_{personnelId}";
|
||
|
||
if (context.UnavailabilityDetails.TryGetValue(detailKey, out var unavailabilityDetail))
|
||
{
|
||
// 检查是否为硬约束类型
|
||
if (unavailabilityDetail.IsHardConstraint)
|
||
{
|
||
_logger.LogDebug("【硬约束发现】人员{PersonnelId}日期{Date:yyyy-MM-dd}班次{ShiftId}:{ReasonType}({ConstraintScore})",
|
||
personnelId, targetDate, shiftId, unavailabilityDetail.ReasonType, unavailabilityDetail.ConstraintScore);
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogTrace("【软约束检测】人员{PersonnelId}日期{Date:yyyy-MM-dd}班次{ShiftId}:{ReasonType}(可协调)",
|
||
personnelId, targetDate, shiftId, unavailabilityDetail.ReasonType);
|
||
}
|
||
}
|
||
}
|
||
|
||
return false; // 未发现硬约束
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【硬约束检查异常】人员{PersonnelId}日期{Date:yyyy-MM-dd}", personnelId, targetDate);
|
||
return true; // 异常情况下保守处理,认为存在硬约束
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【辅助方法】后备查询:使用DateShiftUnavailablePersonnel验证
|
||
/// 【查询策略】:当PersonnelUnavailabilityIndex缺失时的后备验证机制
|
||
/// 【性能考虑】:虽然性能略低于索引查询,但确保数据一致性
|
||
/// </summary>
|
||
private bool CheckDateShiftUnavailabilityFallback(long personnelId, DateTime targetDate, GlobalAllocationContext context)
|
||
{
|
||
try
|
||
{
|
||
// 获取该日期所有可能的班次ID(这里简化处理,实际应该从系统配置获取)
|
||
// 为了演示,我们检查一些常见的班次ID模式
|
||
var commonShiftIds = new[] { 1L, 2L, 3L }; // 一班、二班、三班
|
||
|
||
foreach (var shiftId in commonShiftIds)
|
||
{
|
||
var dateShiftKey = $"{targetDate:yyyyMMdd}_{shiftId}";
|
||
|
||
if (context.DateShiftUnavailablePersonnel.TryGetValue(dateShiftKey, out var unavailablePersonnels))
|
||
{
|
||
if (unavailablePersonnels.Contains(personnelId))
|
||
{
|
||
_logger.LogTrace("【后备查询命中】人员{PersonnelId}在{Date:yyyy-MM-dd}班次{ShiftId}不可用",
|
||
personnelId, targetDate, shiftId);
|
||
|
||
// 进一步检查是否为硬约束
|
||
var detailKey = $"{dateShiftKey}_{personnelId}";
|
||
if (context.UnavailabilityDetails.TryGetValue(detailKey, out var detail))
|
||
{
|
||
return detail.IsHardConstraint;
|
||
}
|
||
else
|
||
{
|
||
return true; // 无详细信息时保守处理
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false; // 后备查询未发现不可用情况
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "【后备查询异常】人员{PersonnelId}日期{Date:yyyy-MM-dd}", personnelId, targetDate);
|
||
return false; // 异常情况下假设可用,避免过度限制
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 【验证结果数据结构】班次规则验证结果
|
||
/// </summary>
|
||
private class ShiftRuleValidationResult
|
||
{
|
||
public string RuleType { get; set; } = string.Empty;
|
||
public long PersonnelId { get; set; }
|
||
public long TaskId { get; set; }
|
||
public bool IsValid { get; set; }
|
||
public List<string> Conflicts { get; set; } = new();
|
||
public DateTime ValidatedAt { get; set; }
|
||
}
|
||
}
|
||
} |