using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using ZhonTai.Admin.Core.Dto; using ZhonTai.Admin.Services; using NPP.SmartSchedue.Api.Contracts.Services.Work; using NPP.SmartSchedue.Api.Contracts.Services.Work.Input; using NPP.SmartSchedue.Api.Contracts.Services.Work.Output; using NPP.SmartSchedue.Api.Repositories.Work; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Services.Time; using NPP.SmartSchedue.Api.Contracts.Services.Time.Output; using NPP.SmartSchedue.Api.Contracts.Services.Personnel; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal; using ZhonTai.DynamicApi; using ZhonTai.DynamicApi.Attributes; namespace NPP.SmartSchedue.Api.Services.Work; /// /// 工作任务服务 /// [DynamicApi(Area = "app")] public class WorkOrderService : BaseService, IWorkOrderService, IDynamicApi { private readonly WorkOrderRepository _workOrderRepository; private readonly WorkOrderFLPersonnelRepository _workOrderFlPersonnelRepository; private readonly IEmployeeLeaveService _employeeLeaveService; private readonly IPersonnelWorkLimitService _personnelWorkLimitService; private readonly IShiftService _shiftService; private readonly IShiftRuleMappingService _shiftRuleMappingService; private readonly IShiftRuleService _shiftRuleService; private readonly ILogger _logger; public WorkOrderService( WorkOrderRepository workOrderRepository, WorkOrderFLPersonnelRepository workOrderFlPersonnelRepository, IEmployeeLeaveService employeeLeaveService, IPersonnelWorkLimitService personnelWorkLimitService, IShiftService shiftService, IShiftRuleMappingService shiftRuleMappingService, IShiftRuleService shiftRuleService, ILogger logger) { _workOrderRepository = workOrderRepository; _workOrderFlPersonnelRepository = workOrderFlPersonnelRepository; _employeeLeaveService = employeeLeaveService; _personnelWorkLimitService = personnelWorkLimitService; _shiftService = shiftService; _shiftRuleMappingService = shiftRuleMappingService; _shiftRuleService = shiftRuleService; _logger = logger; } /// /// 查询 /// /// /// public async Task GetAsync(long id) { var output = await _workOrderRepository.Select .WhereDynamic(id) .ToOneAsync(); if (output != null) { // 获取关联的FL人员信息 var flPersonnels = await _workOrderFlPersonnelRepository.Select .Where(a => a.WorkOrderId == id) .ToListAsync(); output.FLPersonnels = flPersonnels.Select(fp => new FLPersonnelInfo { PersonnelId = fp.FLPersonnelId, PersonnelName = fp.FLPersonnelName }).ToList(); } return output; } /// /// 查询分页 /// /// /// [HttpPost] public async Task> GetPageAsync(PageInput input) { var list = await _workOrderRepository.Select .WhereIf(!string.IsNullOrEmpty(input.Filter?.ProjectNumber), a => a.ProjectNumber.Contains(input.Filter.ProjectNumber)) .WhereIf(!string.IsNullOrEmpty(input.Filter?.WorkOrderCode), a => a.WorkOrderCode.Contains(input.Filter.WorkOrderCode)) .WhereIf(input.Filter?.Status.HasValue == true, a => a.Status == (int)(WorkOrderStatusEnum)input.Filter.Status.Value) .WhereIf(input.Filter?.StartDate.HasValue == true, a => a.WorkOrderDate >= input.Filter.StartDate.Value) .WhereIf(input.Filter?.EndDate.HasValue == true, a => a.WorkOrderDate <= input.Filter.EndDate.Value) .Count(out var total) .OrderByDescending(a => a.Id) .Page(input.CurrentPage, input.PageSize) .ToListAsync(); // 为分页结果添加FL人员信息 if (list?.Any() == true) { var workOrderIds = list.Select(x => x.Id).ToList(); var flPersonnelsList = await _workOrderFlPersonnelRepository.Select .Where(a => workOrderIds.Contains(a.WorkOrderId)) .ToListAsync(); var flPersonnelsDict = flPersonnelsList .GroupBy(fp => fp.WorkOrderId) .ToDictionary( g => g.Key, g => g.Select(fp => new FLPersonnelInfo { PersonnelId = fp.FLPersonnelId, PersonnelName = fp.FLPersonnelName }).ToList() ); foreach (var item in list) { if (flPersonnelsDict.TryGetValue(item.Id, out var flPersonnels)) { item.FLPersonnels = flPersonnels; } else { item.FLPersonnels = new List(); } } } var data = new PageOutput() { List = list, Total = total }; return data; } /// /// 添加 /// /// /// public async Task AddAsync(WorkOrderAddInput input) { var entity = Mapper.Map(input); // 生成任务代码:项目号_班次code_工序code if (string.IsNullOrEmpty(entity.WorkOrderCode)) { entity.WorkOrderCode = $"{entity.ProjectNumber}_{entity.ShiftCode}_{entity.ProcessCode}"; } // 设置默认状态 if (entity.Status == 0) { entity.Status = (int)WorkOrderStatusEnum.PendingSubmit; } // 获取任务班次信息 var shift = await _shiftService.GetAsync(entity.ShiftId ?? 0); if (shift != null) { // 计算实际工作时间段 var workDate = entity.WorkOrderDate.Date; // 取日期部分 entity.PlannedStartTime = workDate.Add(shift.StartTime); entity.PlannedEndTime = workDate.Add(shift.EndTime); // 处理跨天班次(如夜班:22:00-06:00) if (shift.EndTime < shift.StartTime) { entity.PlannedEndTime = entity.PlannedEndTime.AddDays(1); } } // 设置默认预计工时 if (!entity.EstimatedHours.HasValue) { entity.EstimatedHours = 8; // 默认8小时 } var result = await _workOrderRepository.InsertAsync(entity); // 添加FL人员关联 if (input.FLPersonnels?.Any() == true) { await AddWorkOrderFLPersonnelsAsync(result.Id, input.FLPersonnels); } return result.Id; } /// /// 批量添加 /// /// /// [HttpPost] public async Task BatchAddAsync(BatchWorkOrderAddInput input) { var result = new BatchWorkOrderAddOutput { TotalCount = input.WorkOrders?.Count ?? 0 }; if (input.WorkOrders?.Any() != true) { result.IsSuccess = false; result.Message = "工作任务列表不能为空"; return result; } var successIds = new List(); var failedItems = new List(); // 验证重复任务(使用项目号+班次+工序组合) if (input.ValidateConflicts) { var workOrderKeys = input.WorkOrders .Where(w => !string.IsNullOrEmpty(w.ProjectNumber) && !string.IsNullOrEmpty(w.ShiftCode) && !string.IsNullOrEmpty(w.ProcessCode)) .Select(w => $"{w.ProjectNumber}_{w.ShiftCode}_{w.ProcessCode}") .ToList(); if (workOrderKeys.Any()) { var existingCodes = await _workOrderRepository.Select .Where(a => workOrderKeys.Contains(a.WorkOrderCode)) .ToListAsync(a => a.WorkOrderCode); if (existingCodes.Any() && !input.SkipDuplicates) { result.IsSuccess = false; result.Message = $"存在重复的任务组合: {string.Join(", ", existingCodes)}"; return result; } } } // 开始批量处理 for (int i = 0; i < input.WorkOrders.Count; i++) { var workOrder = input.WorkOrders[i]; try { // 检查是否跳过重复项 if (input.SkipDuplicates && !string.IsNullOrEmpty(workOrder.ProjectNumber) && !string.IsNullOrEmpty(workOrder.ShiftCode) && !string.IsNullOrEmpty(workOrder.ProcessCode)) { var workOrderCode = $"{workOrder.ProjectNumber}_{workOrder.ShiftCode}_{workOrder.ProcessCode}"; var exists = await _workOrderRepository.Select .AnyAsync(a => a.WorkOrderCode == workOrderCode); if (exists) { failedItems.Add(new BatchWorkOrderError { Index = i, WorkOrderCode = workOrderCode, ErrorMessage = "任务代码已存在,已跳过", ErrorDetail = "重复的任务代码" }); continue; } } // 添加单个任务 var workOrderId = await AddSingleWorkOrderAsync(workOrder); successIds.Add(workOrderId); } catch (Exception ex) { failedItems.Add(new BatchWorkOrderError { Index = i, WorkOrderCode = $"{workOrder.ProjectNumber}_{workOrder.ShiftCode}_{workOrder.ProcessCode}" ?? $"第{i + 1}个任务", ErrorMessage = ex.Message, ErrorDetail = ex.ToString() }); // 如果启用失败回滚,则回滚已添加的任务 if (input.RollbackOnFailure) { foreach (var successId in successIds) { try { await DeleteAsync(successId); } catch { // 回滚失败,记录日志但不影响主流程 } } result.IsSuccess = false; result.Message = $"批量添加失败,已回滚所有操作。错误: {ex.Message}"; return result; } } } // 设置结果 result.SuccessIds = successIds; result.FailedItems = failedItems; result.SuccessCount = successIds.Count; result.FailedCount = failedItems.Count; result.IsSuccess = result.SuccessCount > 0; result.Message = $"批量添加完成。成功: {result.SuccessCount},失败: {result.FailedCount}"; return result; } /// /// 修改 /// /// /// public async Task UpdateAsync(WorkOrderUpdateInput input) { var entity = await _workOrderRepository.GetAsync(input.Id); Mapper.Map(input, entity); ConvertWorkOrderShift(entity); await _workOrderRepository.UpdateAsync(entity); // 更新FL人员关联 await UpdateWorkOrderFLPersonnelsAsync(input.Id, input.FLPersonnels ?? new List()); } /// /// 删除 /// /// /// public async Task DeleteAsync(long id) { // 删除FL人员关联 await _workOrderFlPersonnelRepository.DeleteAsync(a => a.WorkOrderId == id); // 删除工作任务 await _workOrderRepository.DeleteAsync(id); } /// /// 软删除 /// /// /// public async Task SoftDeleteAsync(long id) { await _workOrderRepository.SoftDeleteAsync(id); } /// /// 批量软删除 /// /// /// public async Task BatchSoftDeleteAsync(long[] ids) { await _workOrderRepository.SoftDeleteAsync(ids); } /// /// 更新任务状态 /// /// /// /// public async Task UpdateStatusAsync(long id, WorkOrderStatusEnum status) { var entity = await _workOrderRepository.GetAsync(id); if (entity != null) { entity.Status = (int)status; await _workOrderRepository.UpdateAsync(entity); } } /// /// 开始任务 /// /// /// public async Task StartAsync(long id) { var entity = await _workOrderRepository.GetAsync(id); if (entity != null) { entity.Status = (int)WorkOrderStatusEnum.InProgress; entity.ActualStartTime = DateTime.Now; await _workOrderRepository.UpdateAsync(entity); } } /// /// 完成任务 /// /// /// /// public async Task CompleteAsync(long id, decimal? actualWorkHours = null) { var entity = await _workOrderRepository.GetAsync(id); if (entity != null) { entity.Status = (int)WorkOrderStatusEnum.Completed; entity.ActualEndTime = DateTime.Now; if (actualWorkHours.HasValue) { entity.ActualWorkHours = actualWorkHours.Value; } else if (entity.ActualStartTime.HasValue) { // 自动计算工时 var timeSpan = DateTime.Now - entity.ActualStartTime.Value; entity.ActualWorkHours = (decimal)timeSpan.TotalHours; } await _workOrderRepository.UpdateAsync(entity); } } #region 私有方法 /// /// 添加单个工作任务(内部方法) /// /// /// private async Task AddSingleWorkOrderAsync(WorkOrderAddInput input) { var entity = Mapper.Map(input); // 生成任务代码:项目号_班次code_工序code if (string.IsNullOrEmpty(entity.WorkOrderCode)) { entity.WorkOrderCode = $"{entity.ProjectNumber}_{entity.ShiftCode}_{entity.ProcessCode}"; } // 设置默认状态 if (entity.Status == 0) { entity.Status = (int)WorkOrderStatusEnum.PendingSubmit; } // 获取任务班次信息 ConvertWorkOrderShift(entity); var result = await _workOrderRepository.InsertAsync(entity); // 添加FL人员关联 if (input.FLPersonnels?.Any() == true) { await AddWorkOrderFLPersonnelsAsync(result.Id, input.FLPersonnels); } return result.Id; } /// /// 添加工作任务FL人员关联 /// /// /// /// private async Task AddWorkOrderFLPersonnelsAsync(long workOrderId, List flPersonnels) { if (flPersonnels?.Any() == true) { var entities = flPersonnels.Select(fp => new WorkOrderFLPersonnelEntity { WorkOrderId = workOrderId, FLPersonnelId = fp.PersonnelId, FLPersonnelName = fp.PersonnelName, CreatedTime = DateTime.Now }).ToList(); await _workOrderFlPersonnelRepository.InsertAsync(entities); } } /// /// 更新工作任务FL人员关联 /// /// /// /// private async Task UpdateWorkOrderFLPersonnelsAsync(long workOrderId, List flPersonnels) { // 删除现有关联 await _workOrderFlPersonnelRepository.DeleteAsync(a => a.WorkOrderId == workOrderId); // 添加新关联 await AddWorkOrderFLPersonnelsAsync(workOrderId, flPersonnels); } private async Task ConvertWorkOrderShift(WorkOrderEntity workOrderEntity) { // 获取任务班次信息 var shift = await _shiftService.GetAsync(workOrderEntity.ShiftId ?? 0); if (shift != null) { // 计算实际工作时间段 var workDate = workOrderEntity.WorkOrderDate.Date; // 取日期部分 workOrderEntity.PlannedStartTime = workDate.Add(shift.StartTime); workOrderEntity.PlannedEndTime = workDate.Add(shift.EndTime); // 处理跨天班次(如夜班:22:00-06:00) if (shift.EndTime < shift.StartTime) { workOrderEntity.PlannedEndTime = workOrderEntity.PlannedEndTime.AddDays(1); } } return workOrderEntity; } #endregion #region 任务自检功能 /// /// 单个任务自检 /// /// /// public async Task ValidateWorkOrderAsync(SingleWorkOrderValidationInput input) { var workOrder = await _workOrderRepository.GetAsync(input.WorkOrderId); if (workOrder == null) { return new WorkOrderValidationOutput { WorkOrderId = input.WorkOrderId, IsValid = false, Errors = new List { new NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError { ErrorType = ValidationErrorType.TaskTimeConflict, Message = "任务不存在" } } }; } var result = new WorkOrderValidationOutput { WorkOrderId = input.WorkOrderId, WorkOrderCode = workOrder.WorkOrderCode, IsValid = true }; // 如果任务已分配实施人员,则检查人员空闲状态 if (workOrder.AssignedPersonnelId.HasValue) { // 检查分配人员的空闲状态 var personnelResult = await CheckPersonnelAvailabilityAsync( workOrder.AssignedPersonnelId.Value, workOrder.AssignedPersonnelName ?? $"人员{workOrder.AssignedPersonnelId}", workOrder); result.PersonnelResults.Add(personnelResult); if (!personnelResult.IsAvailable) { result.IsValid = false; // 添加具体的错误信息 foreach (var detail in personnelResult.ConflictDetails) { result.Errors.Add(new NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError { ErrorType = GetErrorTypeFromDetail(detail), Message = detail, PersonnelId = workOrder.AssignedPersonnelId.Value, PersonnelName = workOrder.AssignedPersonnelName }); } } } else { // 任务未分配实施人员,自检通过,但添加信息说明 result.PersonnelResults.Add(new PersonnelAvailabilityResult { PersonnelId = 0, PersonnelName = "未分配", IsAvailable = true, ConflictDetails = new List { "任务尚未分配实施人员" } }); } // 如果自检通过且需要更新状态为待整合 if (result.IsValid && input.UpdateStatusToPendingIntegration) { await UpdateStatusAsync(workOrder.Id, WorkOrderStatusEnum.PendingIntegration); } return result; } /// /// 多个任务自检 /// /// /// public async Task ValidateMultipleWorkOrdersAsync(WorkOrderValidationInput input) { var result = new MultipleWorkOrderValidationOutput { IsValid = true }; // 获取所有待验证的任务信息(用于上下文计算) var allWorkOrders = await _workOrderRepository.Select .Where(wo => input.WorkOrderIds.Contains(wo.Id)) .ToListAsync(); // 检查每个任务(考虑批次内其他任务的影响) foreach (var workOrderId in input.WorkOrderIds) { var currentWorkOrder = allWorkOrders.FirstOrDefault(wo => wo.Id == workOrderId); if (currentWorkOrder == null) { result.WorkOrderResults.Add(new WorkOrderValidationOutput { WorkOrderId = workOrderId, IsValid = false, Errors = new List { new NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError { ErrorType = ValidationErrorType.TaskTimeConflict, Message = "任务不存在" } } }); result.IsValid = false; continue; } // 获取批次内其他任务作为上下文 var batchContext = allWorkOrders.Where(wo => wo.Id != workOrderId).ToList(); var singleResult = await ValidateWorkOrderWithBatchContextAsync(currentWorkOrder, batchContext); result.WorkOrderResults.Add(singleResult); if (!singleResult.IsValid) { result.IsValid = false; } } // 注意:原有的跨任务冲突检查已整合到单任务验证的批次上下文中 // 所有冲突检查现在都通过ValidateWorkOrderWithBatchContextAsync完成 // 如果所有任务自检通过且需要更新状态为待整合 if (result.IsValid && input.UpdateStatusToPendingIntegration) { try { var updateTasks = new List(); foreach (var workOrderId in input.WorkOrderIds) { updateTasks.Add(UpdateStatusAsync(workOrderId, WorkOrderStatusEnum.PendingIntegration)); } // 并行执行状态更新,提高性能 await Task.WhenAll(updateTasks); // 验证所有任务状态确实已更新 var updatedWorkOrders = await _workOrderRepository.Where(wo => input.WorkOrderIds.Contains(wo.Id)) .ToListAsync(); var failedUpdates = updatedWorkOrders .Where(wo => wo.Status != (int)WorkOrderStatusEnum.PendingIntegration) .ToList(); if (failedUpdates.Any()) { throw new InvalidOperationException($"状态更新失败,涉及任务ID: {string.Join(",", failedUpdates.Select(wo => wo.Id))}"); } _logger.LogInformation($"成功批量更新任务状态为待整合,任务数量: {input.WorkOrderIds?.Count ?? 0}"); } catch (Exception ex) { _logger.LogError(ex, $"批量更新任务状态失败,任务ID: {string.Join(",", input.WorkOrderIds ?? new List())}"); throw new InvalidOperationException($"批量状态更新失败: {ex.Message}", ex); } } return result; } /// /// 带批次上下文的单任务验证(考虑本批次其他任务的影响) /// /// 当前任务 /// 批次内其他任务 /// private async Task ValidateWorkOrderWithBatchContextAsync(WorkOrderEntity workOrder, List batchContext) { var result = new WorkOrderValidationOutput { WorkOrderId = workOrder.Id, WorkOrderCode = workOrder.WorkOrderCode, IsValid = true }; // 如果任务已分配实施人员,则检查人员空闲状态(包含批次上下文) if (workOrder.AssignedPersonnelId.HasValue) { // 检查分配人员的空闲状态(考虑批次上下文) var personnelResult = await CheckPersonnelAvailabilityWithBatchContextAsync( workOrder.AssignedPersonnelId.Value, workOrder.AssignedPersonnelName ?? $"人员{workOrder.AssignedPersonnelId}", workOrder, batchContext); result.PersonnelResults.Add(personnelResult); if (!personnelResult.IsAvailable) { result.IsValid = false; // 添加具体的错误信息 foreach (var detail in personnelResult.ConflictDetails) { result.Errors.Add(new NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError { ErrorType = GetErrorTypeFromDetail(detail), Message = detail, PersonnelId = workOrder.AssignedPersonnelId.Value, PersonnelName = workOrder.AssignedPersonnelName }); } } } else { // 任务未分配实施人员,自检通过,但添加信息说明 result.PersonnelResults.Add(new PersonnelAvailabilityResult { PersonnelId = 0, PersonnelName = "未分配", IsAvailable = true, ConflictDetails = new List { "任务尚未分配实施人员" } }); } return result; } /// /// 带批次上下文的人员空闲状态检查 /// private async Task CheckPersonnelAvailabilityWithBatchContextAsync( long personnelId, string personnelName, WorkOrderEntity workOrder, List batchContext) { var result = new PersonnelAvailabilityResult { PersonnelId = personnelId, PersonnelName = personnelName, IsAvailable = true }; // 根据任务日期和班次信息计算工作时间段 var shift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (shift == null) { result.IsAvailable = false; result.ConflictDetails.Add("班次信息不存在"); return result; } var workDate = workOrder.WorkOrderDate.Date; var workStartTime = workDate.Add(shift.StartTime); var workEndTime = workDate.Add(shift.EndTime); // 处理跨天班次 if (shift.EndTime < shift.StartTime) { workEndTime = workEndTime.AddDays(1); } // 检查请假冲突 var hasLeaveConflict = await _employeeLeaveService.HasApprovedLeaveInTimeRangeAsync( personnelId, workStartTime, workEndTime); if (hasLeaveConflict) { result.HasLeaveConflict = true; result.IsAvailable = false; result.ConflictDetails.Add($"人员 {personnelName} 在任务时间段内有请假安排"); } // 检查任务时间冲突(包含批次上下文) var taskConflicts = await CheckTaskTimeConflictsWithBatchContextAsync(personnelId, workOrder, workStartTime, workEndTime, batchContext); if (taskConflicts.Any()) { result.HasTaskTimeConflict = true; result.IsAvailable = false; result.ConflictDetails.AddRange(taskConflicts); } // 检查工作时间限制冲突(包含批次上下文) var workLimitConflicts = await CheckWorkLimitConflictsWithBatchContextAsync(personnelId, workOrder, workStartTime, workEndTime, batchContext); if (workLimitConflicts.Any()) { result.HasWorkLimitConflict = true; result.IsAvailable = false; result.ConflictDetails.AddRange(workLimitConflicts); } else { result.ConflictDetails.Add("工作时间限制检查:通过"); } // 检查班次规则冲突(包含批次上下文) var shiftRuleConflicts = await CheckShiftRuleConflictsWithBatchContextAsync(personnelId, workOrder, workStartTime, workEndTime, batchContext); if (shiftRuleConflicts.Any()) { result.IsAvailable = false; result.ConflictDetails.AddRange(shiftRuleConflicts); } else { result.ConflictDetails.Add("班次规则检查:通过"); } return result; } /// /// 检查人员空闲状态 /// /// 人员ID /// 人员姓名 /// 工作任务 /// private async Task CheckPersonnelAvailabilityAsync( long personnelId, string personnelName, WorkOrderEntity workOrder) { var result = new PersonnelAvailabilityResult { PersonnelId = personnelId, PersonnelName = personnelName, IsAvailable = true }; // 检查请假冲突 var hasLeaveConflict = await _employeeLeaveService.HasApprovedLeaveInTimeRangeAsync( personnelId, workOrder.PlannedStartTime, workOrder.PlannedEndTime); if (hasLeaveConflict) { result.HasLeaveConflict = true; result.IsAvailable = false; result.ConflictDetails.Add($"人员 {personnelName} 在任务时间段内有请假安排"); } // 检查任务时间冲突 var taskConflicts = await CheckTaskTimeConflictsAsync(personnelId, workOrder, workOrder.PlannedStartTime, workOrder.PlannedEndTime); if (taskConflicts.Any()) { result.HasTaskTimeConflict = true; result.IsAvailable = false; result.ConflictDetails.AddRange(taskConflicts); } // 检查工作时间限制冲突 var workLimitConflicts = await CheckWorkLimitConflictsAsync(personnelId, workOrder, workOrder.PlannedStartTime, workOrder.PlannedEndTime); if (workLimitConflicts.Any()) { result.HasWorkLimitConflict = true; result.IsAvailable = false; result.ConflictDetails.AddRange(workLimitConflicts); } else { result.ConflictDetails.Add("工作时间限制检查:通过"); } // 检查班次规则冲突 var shiftRuleConflicts = await CheckShiftRuleConflictsAsync(personnelId, workOrder, workOrder.PlannedStartTime, workOrder.PlannedEndTime); if (shiftRuleConflicts.Any()) { result.IsAvailable = false; result.ConflictDetails.AddRange(shiftRuleConflicts); } else { result.ConflictDetails.Add("班次规则检查:通过"); } return result; } /// /// 带批次上下文的任务时间冲突检查(包含精确的时间段重叠检查) /// private async Task> CheckTaskTimeConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List batchContext) { return await CheckTaskTimeConflictsWithBatchContextAsync(personnelId, workOrder, workStartTime, workEndTime, batchContext, null); } /// /// 带批次上下文和缓存优化的任务时间冲突检查(包含精确的时间段重叠检查) /// 【性能优化】:优先使用PersonnelHistoryTasks缓存,避免重复数据库查询 /// /// 人员ID /// 工作任务 /// 工作开始时间 /// 工作结束时间 /// 批次上下文中的其他任务 /// 人员历史任务缓存(可选,优先使用缓存避免数据库查询) /// 冲突详情列表 private async Task> CheckTaskTimeConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List batchContext, Dictionary>? personnelHistoryCache) { var conflicts = new List(); // 获取当前任务的班次信息 var currentShift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (currentShift == null) { conflicts.Add("当前任务班次信息不存在"); return conflicts; } // 1. 检查与数据库历史任务的冲突(包含时间段重叠检查) var dbConflictingTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) // 排除自己 .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .ToListAsync(); foreach (var conflict in dbConflictingTasks) { // 优化后的时间冲突检查:只检查真正的时间重叠,移除同日期强约束 // 深度业务思考:不同日期的任务允许分配给同一人员,只要时间段不重叠 var conflictShift = await _shiftService.GetAsync(conflict.ShiftId ?? 0); if (conflictShift != null) { var conflictStartTime = conflict.WorkOrderDate.Date.Add(conflictShift.StartTime); var conflictEndTime = conflict.WorkOrderDate.Date.Add(conflictShift.EndTime); // 处理跨天班次:如果结束时间小于开始时间,说明跨天 if (conflictShift.EndTime < conflictShift.StartTime) { conflictEndTime = conflictEndTime.AddDays(1); } // 精确检查时间段重叠:只有真正时间重叠才算冲突 if (IsTimeRangeOverlap(workStartTime, workEndTime, conflictStartTime, conflictEndTime)) { conflicts.Add($"与数据库任务 '{conflict.WorkOrderCode}' ({conflictShift.Name}: {conflictShift.StartTime:hh\\:mm}-{conflictShift.EndTime:hh\\:mm}) 时间重叠,日期: {conflict.WorkOrderDate:yyyy-MM-dd}"); } } else { // 如果没有班次信息,仍然需要检查同日期冲突作为兜底 if (conflict.WorkOrderDate.Date == workOrder.WorkOrderDate.Date) { conflicts.Add($"与数据库任务 '{conflict.WorkOrderCode}' 在同一天存在冲突 (日期: {conflict.WorkOrderDate:yyyy-MM-dd}),缺少班次信息无法精确检查"); } } } // 2. 检查与批次内其他任务的冲突(精确时间段重叠检查) foreach (var conflict in batchContext.Where(wo => wo.AssignedPersonnelId == personnelId && wo.Id != workOrder.Id)) { // 优化后的批次内冲突检查:只检查真正的时间重叠,支持不同日期同人员分配 var conflictShift = await _shiftService.GetAsync(conflict.ShiftId ?? 0); if (conflictShift != null) { var conflictStartTime = conflict.WorkOrderDate.Date.Add(conflictShift.StartTime); var conflictEndTime = conflict.WorkOrderDate.Date.Add(conflictShift.EndTime); // 处理跨天班次 if (conflictShift.EndTime < conflictShift.StartTime) { conflictEndTime = conflictEndTime.AddDays(1); } // 精确检查时间段重叠:只有真正时间重叠才算冲突 if (IsTimeRangeOverlap(workStartTime, workEndTime, conflictStartTime, conflictEndTime)) { conflicts.Add($"与批次内任务 '{conflict.WorkOrderCode}' ({conflictShift.Name}: {conflictShift.StartTime:hh\\:mm}-{conflictShift.EndTime:hh\\:mm}) 时间重叠,日期: {conflict.WorkOrderDate:yyyy-MM-dd}"); } } else { // 如果没有班次信息,检查同日期冲突作为兜底 if (conflict.WorkOrderDate.Date == workOrder.WorkOrderDate.Date) { conflicts.Add($"与批次内任务 '{conflict.WorkOrderCode}' 在同一天存在冲突 (日期: {conflict.WorkOrderDate:yyyy-MM-dd}),缺少班次信息无法精确检查"); } } } return conflicts; } /// /// 检查任务时间冲突 /// /// /// /// /// /// private async Task> CheckTaskTimeConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime) { var conflicts = new List(); // 查询该人员作为AssignedPersonnelId在相同日期的其他任务 // 人员相关冲突检查:只包含已分配人员的状态 var conflictingTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) // 排除自己 .Where(wo => wo.WorkOrderDate.Date == workOrder.WorkOrderDate.Date) // 同一天的任务 .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .ToListAsync(); foreach (var conflict in conflictingTasks) { conflicts.Add($"与任务 '{conflict.WorkOrderCode}' 在同一天存在冲突 (日期: {conflict.WorkOrderDate:yyyy-MM-dd})"); } return conflicts; } /// /// 带批次上下文的工作时间限制冲突检查 /// private async Task> CheckWorkLimitConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List batchContext) { var conflicts = new List(); try { // 获取人员工作时间限制设置 var workLimit = await _personnelWorkLimitService.GetPersonnelWorkLimitAsync(personnelId); if (workLimit == null) { // 如果没有设置工作时间限制,则认为通过 return conflicts; } // 检查连续工作天数限制(需要考虑批次内任务的顺序影响) if (workLimit.MaxContinuousWorkDays.HasValue) { var consecutiveDays = await CalculateConsecutiveWorkDaysWithBatchContextAsync(personnelId, workOrder.WorkOrderDate, batchContext); if (consecutiveDays >= workLimit.MaxContinuousWorkDays.Value) { conflicts.Add($"连续工作 {consecutiveDays} 天超过限制 {workLimit.MaxContinuousWorkDays.Value} 天(包含批次内任务影响)"); } } // 检查当周最大排班次数限制(需要考虑批次内同周任务) if (workLimit.MaxShiftsPerWeek.HasValue) { var weekStart = workOrder.WorkOrderDate.Date.AddDays(-(int)workOrder.WorkOrderDate.DayOfWeek); var weekEnd = weekStart.AddDays(7); // 数据库中的同周任务数量 var weeklyTaskCount = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) .Where(wo => wo.WorkOrderDate.Date >= weekStart && wo.WorkOrderDate.Date < weekEnd) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .CountAsync(); // 批次内的同周任务数量(排除当前任务) var batchWeeklyTaskCount = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date >= weekStart && wo.WorkOrderDate.Date < weekEnd) .Count(); // 总数包含当前任务,所以+1 var totalWeeklyTasks = weeklyTaskCount + batchWeeklyTaskCount + 1; if (totalWeeklyTasks > workLimit.MaxShiftsPerWeek.Value) { conflicts.Add($"本周排班次数 {totalWeeklyTasks} 次超过限制 {workLimit.MaxShiftsPerWeek.Value} 次(包含批次内任务 {batchWeeklyTaskCount} 次)"); } } // 检查每日工作量限制(整合原跨任务检查的逻辑) if (workOrder.EstimatedHours.HasValue && workOrder.EstimatedHours.Value > 0) { var currentDate = workOrder.WorkOrderDate.Date; // 计算数据库中同一天的总工作量 var dbDailyWorkHours = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .Where(wo => wo.EstimatedHours.HasValue && wo.EstimatedHours.Value > 0) .SumAsync(wo => wo.EstimatedHours.Value); // 计算批次内同一天的总工作量 var batchDailyWorkHours = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.EstimatedHours.HasValue && wo.EstimatedHours.Value > 0) .Sum(wo => wo.EstimatedHours.Value); // 总工作量 = 数据库 + 批次内 + 当前任务 var totalDailyWorkHours = dbDailyWorkHours + batchDailyWorkHours + workOrder.EstimatedHours.Value; // 假设每日最大工作时间为12小时(可配置化) const decimal MaxDailyWorkHours = 12m; if (totalDailyWorkHours > MaxDailyWorkHours) { var batchTaskCodes = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Select(wo => wo.WorkOrderCode) .ToList(); var batchInfo = batchTaskCodes.Any() ? $"(包含批次内任务: {string.Join(", ", batchTaskCodes)})" : ""; conflicts.Add($"每日工作量 {totalDailyWorkHours:F1}小时 超过限制 {MaxDailyWorkHours}小时 {batchInfo}"); } } } catch (Exception ex) { conflicts.Add($"工作时间限制检查异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的班次规则冲突检查 /// private async Task> CheckShiftRuleConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List batchContext) { var conflicts = new List(); try { // 获取班次对应的规则映射 var shiftRuleMappings = await _shiftRuleMappingService.GetRulesByShiftIdAsync(workOrder.ShiftId ?? 0); // 过滤启用的规则映射 var enabledRuleMappings = shiftRuleMappings .Where(mapping => mapping.IsEnabled) .OrderBy(mapping => mapping.ExecutionPriority) // 按优先级排序 .ToList(); if (!enabledRuleMappings.Any()) { return conflicts; // 没有启用的规则,直接返回 } foreach (var ruleMapping in enabledRuleMappings) { // 根据规则类型执行不同的检查逻辑(考虑批次上下文) var ruleConflicts = await ValidateShiftRuleWithBatchContextAsync(ruleMapping, personnelId, workOrder, workStartTime, workEndTime, batchContext); if (ruleConflicts.Any()) { conflicts.AddRange(ruleConflicts); } } } catch (Exception ex) { conflicts.Add($"班次规则检查异常: {ex.Message}"); } return conflicts; } /// /// 检查工作时间限制冲突 /// /// /// /// /// /// private async Task> CheckWorkLimitConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime) { var conflicts = new List(); try { // 获取人员工作时间限制设置 var workLimit = await _personnelWorkLimitService.GetPersonnelWorkLimitAsync(personnelId); if (workLimit == null) { // 如果没有设置工作时间限制,则认为通过 return conflicts; } // 检查连续工作天数限制 if (workLimit.MaxContinuousWorkDays.HasValue) { var consecutiveDays = await CalculateConsecutiveWorkDaysAsync(personnelId, workOrder.WorkOrderDate); if (consecutiveDays >= workLimit.MaxContinuousWorkDays.Value) { conflicts.Add($"连续工作 {consecutiveDays} 天超过限制 {workLimit.MaxContinuousWorkDays.Value} 天"); } } // 检查当周最大排班次数限制 if (workLimit.MaxShiftsPerWeek.HasValue) { var weekStart = workOrder.WorkOrderDate.Date.AddDays(-(int)workOrder.WorkOrderDate.DayOfWeek); var weekEnd = weekStart.AddDays(7); var weeklyTaskCount = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) .Where(wo => wo.WorkOrderDate.Date >= weekStart && wo.WorkOrderDate.Date < weekEnd) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .CountAsync(); // 包含当前任务,所以+1 if (weeklyTaskCount + 1 > workLimit.MaxShiftsPerWeek.Value) { conflicts.Add($"本周排班次数 {weeklyTaskCount + 1} 次超过限制 {workLimit.MaxShiftsPerWeek.Value} 次"); } } } catch (Exception ex) { conflicts.Add($"工作时间限制检查异常: {ex.Message}"); } return conflicts; } /// /// 计算连续工作天数 /// 修复硬编码限制,增加节假日处理,提升计算准确性 /// /// 人员ID /// 目标日期 /// 连续工作天数 private async Task CalculateConsecutiveWorkDaysAsync(long personnelId, DateTime targetDate) { var consecutiveDays = 1; // 包含目标日期 var currentDate = targetDate.Date.AddDays(-1); // 从目标日期前一天开始往前检查 // 使用配置化的最大检查天数,避免硬编码 const int maxCheckDays = 60; // 可考虑从配置文件读取 var checkedDays = 0; try { // 往前检查连续工作天数 while (checkedDays < maxCheckDays) { checkedDays++; // 检查当天是否为节假日或周末(根据业务规则决定是否跳过) var isWeekend = currentDate.DayOfWeek == DayOfWeek.Saturday || currentDate.DayOfWeek == DayOfWeek.Sunday; var hasWork = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (!hasWork) { // 如果是周末且没有工作任务,不计入连续工作但继续检查 if (isWeekend) { currentDate = currentDate.AddDays(-1); continue; } break; // 工作日没有工作任务,中断连续天数 } // 检查是否有请假 var hasLeave = await _employeeLeaveService.HasApprovedLeaveInTimeRangeAsync( personnelId, currentDate, currentDate.AddDays(1)); if (hasLeave) { break; // 有请假,中断连续天数 } consecutiveDays++; currentDate = currentDate.AddDays(-1); } if (checkedDays >= maxCheckDays) { _logger.LogWarning($"连续工作天数计算达到最大检查天数限制 {maxCheckDays},人员ID: {personnelId},目标日期: {targetDate:yyyy-MM-dd}"); } } catch (Exception ex) { _logger.LogError(ex, $"计算连续工作天数异常,人员ID: {personnelId},目标日期: {targetDate:yyyy-MM-dd}"); // 发生异常时返回保守的连续天数 return Math.Min(consecutiveDays, 7); // 保守估计,最多返回7天 } return consecutiveDays; } /// /// 计算连续工作天数(考虑批次上下文) /// private async Task CalculateConsecutiveWorkDaysWithBatchContextAsync(long personnelId, DateTime targetDate, List batchContext) { var consecutiveDays = 1; // 包含目标日期 // 获取批次内该人员的任务,按日期排序 var personnelBatchTasks = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .OrderBy(wo => wo.WorkOrderDate.Date) .ToList(); // 创建一个包含批次任务的日期集合 var batchWorkDates = new HashSet(personnelBatchTasks.Select(t => t.WorkOrderDate.Date)); batchWorkDates.Add(targetDate.Date); // 包含当前任务日期 var currentDate = targetDate.Date.AddDays(-1); // 从目标日期前一天开始往前检查 // 往前检查连续工作天数 while (true) { bool hasWork = false; // 检查批次内是否有工作 if (batchWorkDates.Contains(currentDate)) { hasWork = true; } else { // 检查数据库中是否有工作 hasWork = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); } if (!hasWork) { break; // 没有工作任务,中断连续天数 } // 检查是否有请假 var hasLeave = await _employeeLeaveService.HasApprovedLeaveInTimeRangeAsync( personnelId, currentDate, currentDate.AddDays(1)); if (hasLeave) { break; // 有请假,中断连续天数 } consecutiveDays++; currentDate = currentDate.AddDays(-1); // 避免无限循环,最多检查30天 if (consecutiveDays > 30) { break; } } return consecutiveDays; } /// /// 带批次上下文的班次规则验证 /// private async Task> ValidateShiftRuleWithBatchContextAsync( ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List batchContext) { var conflicts = new List(); // 根据 RuleID 查询具体的 ShiftRule 详细信息 ShiftRuleGetOutput shiftRule = null; if (ruleMapping.RuleId > 0) { try { shiftRule = await _shiftRuleService.GetAsync(ruleMapping.RuleId); if (shiftRule == null) { _logger.LogWarning($"无法找到ID为 {ruleMapping.RuleId} 的班次规则"); return conflicts; // 如果找不到规则,直接返回空冲突列表 } } catch (Exception ex) { _logger.LogError(ex, $"查询班次规则 {ruleMapping.RuleId} 时发生异常"); conflicts.Add($"查询班次规则详情失败: {ex.Message}"); return conflicts; } } try { // 根据规则类型执行不同的验证逻辑 switch (shiftRule?.RuleType) { case "3": // 一/二班不连续 conflicts.AddRange(await ValidateShiftSequenceRuleWithBatchContext(ruleMapping, personnelId, workOrder, "一", "二", batchContext)); break; case "4": // 二/三班不连续 conflicts.AddRange(await ValidateShiftSequenceRuleWithBatchContext(ruleMapping, personnelId, workOrder, "二", "三", batchContext)); break; case "5": // 不能这周日/下周六连 conflicts.AddRange(await ValidateSundayToSaturdayRuleWithBatchContext(ruleMapping, personnelId, workOrder, batchContext)); break; case "6": // 不能本周六/本周日连 conflicts.AddRange(await ValidateSaturdayToSundayRuleWithBatchContext(ruleMapping, personnelId, workOrder, batchContext)); break; case "7": // 不能连续7天排班 conflicts.AddRange(await ValidateMaxConsecutiveDaysRuleWithBatchContext(ruleMapping, personnelId, workOrder, 7, batchContext)); break; case "8": // 三班后一天不排班 conflicts.AddRange(await ValidateRestAfterShiftRuleWithBatchContext(ruleMapping, personnelId, workOrder, "三", batchContext)); break; case "9": // 二班后一天不排班 conflicts.AddRange(await ValidateRestAfterShiftRuleWithBatchContext(ruleMapping, personnelId, workOrder, "二", batchContext)); break; default: // 对于未知规则类型,记录警告但不阻止任务 break; } } catch (Exception ex) { conflicts.Add($"规则 '{ruleMapping.Rule?.RuleName}' 验证异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的班次后休息规则验证(示例实现) /// private async Task> ValidateRestAfterShiftRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, string shiftName, List batchContext) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 检查前一天是否是指定班次(包含批次上下文和数据库) var previousDay = currentDate.AddDays(-1); // 先检查批次内是否有前一天的指定班次任务 var batchPreviousTask = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == previousDay) .FirstOrDefault(); if (batchPreviousTask != null) { var batchPreviousShift = await _shiftService.GetAsync(batchPreviousTask.ShiftId ?? 0); if (batchPreviousShift != null && batchPreviousShift.Name?.Contains(shiftName) == true) { conflicts.Add($"违反批次内班次后休息规则 '{ruleMapping.Rule?.RuleName}': 批次内任务 '{batchPreviousTask.WorkOrderCode}' {shiftName}班后一天不能安排工作"); } } else { // 检查数据库中前一天是否有指定班次 var dbPreviousTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == previousDay) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .FirstAsync(); if (dbPreviousTask != null) { var dbPreviousShift = await _shiftService.GetAsync(dbPreviousTask.ShiftId ?? 0); if (dbPreviousShift != null && dbPreviousShift.Name?.Contains(shiftName) == true) { conflicts.Add($"违反班次后休息规则 '{ruleMapping.Rule?.RuleName}': 数据库任务 '{dbPreviousTask.WorkOrderCode}' {shiftName}班后一天不能安排工作"); } } } // 检查当前任务是指定班次,确保后一天不排班(包含批次上下文) var currentShift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (currentShift != null && 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) { conflicts.Add($"违反批次内班次后休息规则 '{ruleMapping.Rule?.RuleName}': {shiftName}班后一天不能安排批次内任务"); } else { // 检查数据库中是否有后一天的任务 var dbNextTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == nextDay) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (dbNextTask) { conflicts.Add($"违反班次后休息规则 '{ruleMapping.Rule?.RuleName}': {shiftName}班后一天不能安排工作(与数据库任务冲突)"); } } } } catch (Exception ex) { conflicts.Add($"班次后休息规则验证异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的班次连续性规则验证(规则3和4:一/二班不连续以及二/三班不连续) /// 深度思考:同一人员同一天不能有冲突的班次安排,需要综合考虑批次内任务和数据库历史任务 /// private async Task> ValidateShiftSequenceRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, string firstShiftName, string secondShiftName, List batchContext) { var conflicts = new List(); try { var currentShift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (currentShift == null) return conflicts; var currentDate = workOrder.WorkOrderDate.Date; // 检查当前班次是否为规则中的班次之一 bool currentIsFirstShift = currentShift.Name?.Contains(firstShiftName) == true; bool currentIsSecondShift = currentShift.Name?.Contains(secondShiftName) == true; if (!currentIsFirstShift && !currentIsSecondShift) { return conflicts; // 当前班次不是规则关注的班次 } // 1. 检查批次内同一天是否有冲突班次 var batchSameDayTasks = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == currentDate) .ToList(); foreach (var batchTask in batchSameDayTasks) { var batchShift = await _shiftService.GetAsync(batchTask.ShiftId ?? 0); if (batchShift == null) continue; bool batchIsFirstShift = batchShift.Name?.Contains(firstShiftName) == true; bool batchIsSecondShift = batchShift.Name?.Contains(secondShiftName) == true; // 检查班次冲突:当前是一班且批次内有二班,或当前是二班且批次内有一班 if ((currentIsFirstShift && batchIsSecondShift) || (currentIsSecondShift && batchIsFirstShift)) { conflicts.Add($"违反批次内班次连续性规则 '{ruleMapping.Rule?.RuleName}': 同一天内不能同时安排{firstShiftName}班和{secondShiftName}班 (与批次内任务 '{batchTask.WorkOrderCode}' {batchShift.Name} 冲突)"); } } // 2. 检查数据库中同一天是否有冲突班次 var dbSameDayTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .ToListAsync(); foreach (var dbTask in dbSameDayTasks) { var dbShift = await _shiftService.GetAsync(dbTask.ShiftId ?? 0); if (dbShift == null) continue; bool dbIsFirstShift = dbShift.Name?.Contains(firstShiftName) == true; bool dbIsSecondShift = dbShift.Name?.Contains(secondShiftName) == true; // 检查班次冲突 if ((currentIsFirstShift && dbIsSecondShift) || (currentIsSecondShift && dbIsFirstShift)) { conflicts.Add($"违反班次连续性规则 '{ruleMapping.Rule?.RuleName}': 同一天内不能同时安排{firstShiftName}班和{secondShiftName}班 (与数据库任务 '{dbTask.WorkOrderCode}' {dbShift.Name} 冲突)"); } } } catch (Exception ex) { conflicts.Add($"班次连续性规则验证异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的这周日/下周六连续规则验证(规则5) /// 深度思考:防止跨周连续工作,需要检查周日->下周六的7天跨度,批次内可能同时包含这两个时间点的任务 /// private async Task> ValidateSundayToSaturdayRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, List batchContext) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 情况1:当前任务是周日,检查下周六是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Sunday) { var nextSaturday = currentDate.AddDays(6); // 周日 + 6天 = 下周六 // 先检查批次内是否有下周六的任务 var batchNextSaturday = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == nextSaturday) .FirstOrDefault(); if (batchNextSaturday != null) { conflicts.Add($"违反批次内周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在这周日和下周六安排工作 (与批次内任务 '{batchNextSaturday.WorkOrderCode}' 冲突)"); } else { // 检查数据库中下周六是否有任务 var dbNextSaturday = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == nextSaturday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (dbNextSaturday) { conflicts.Add($"违反周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在这周日和下周六安排工作 (与数据库任务冲突)"); } } } // 情况2:当前任务是周六,检查上周日是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Saturday) { var lastSunday = currentDate.AddDays(-6); // 周六 - 6天 = 上周日 // 先检查批次内是否有上周日的任务 var batchLastSunday = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == lastSunday) .FirstOrDefault(); if (batchLastSunday != null) { conflicts.Add($"违反批次内周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在上周日和这周六安排工作 (与批次内任务 '{batchLastSunday.WorkOrderCode}' 冲突)"); } else { // 检查数据库中上周日是否有任务 var dbLastSunday = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == lastSunday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (dbLastSunday) { conflicts.Add($"违反周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在上周日和这周六安排工作 (与数据库任务冲突)"); } } } } catch (Exception ex) { conflicts.Add($"周日/周六连续规则验证异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的本周六/本周日连续规则验证(规则6) /// 深度思考:防止周末连续工作,批次内可能同时包含同一人员的周六和周日任务 /// private async Task> ValidateSaturdayToSundayRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, List batchContext) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 情况1:当前是周六,检查周日是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Saturday) { var sunday = currentDate.AddDays(1); // 先检查批次内是否有周日任务 var batchSunday = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == sunday) .FirstOrDefault(); if (batchSunday != null) { conflicts.Add($"违反批次内周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作 (与批次内任务 '{batchSunday.WorkOrderCode}' 冲突)"); } else { // 检查数据库中周日是否有任务 var dbSunday = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == sunday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (dbSunday) { conflicts.Add($"违反周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作 (与数据库任务冲突)"); } } } // 情况2:当前是周日,检查周六是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Sunday) { var saturday = currentDate.AddDays(-1); // 先检查批次内是否有周六任务 var batchSaturday = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == saturday) .FirstOrDefault(); if (batchSaturday != null) { conflicts.Add($"违反批次内周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作 (与批次内任务 '{batchSaturday.WorkOrderCode}' 冲突)"); } else { // 检查数据库中周六是否有任务 var dbSaturday = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == saturday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (dbSaturday) { conflicts.Add($"违反周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作 (与数据库任务冲突)"); } } } } catch (Exception ex) { conflicts.Add($"周六周日连续规则验证异常: {ex.Message}"); } return conflicts; } /// /// 带批次上下文的最大连续天数规则验证(规则7:不能连续7天排班) /// 深度思考:这是最严格的连续工作限制,需要基于批次+历史数据计算真实的连续工作天数序列 /// private async Task> ValidateMaxConsecutiveDaysRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, int maxDays, List batchContext) { var conflicts = new List(); try { // 使用已实现的带批次上下文的连续工作天数计算方法 var consecutiveDays = await CalculateConsecutiveWorkDaysWithBatchContextAsync(personnelId, workOrder.WorkOrderDate, batchContext); if (consecutiveDays >= maxDays) { // 获取相关的批次内任务信息用于详细说明 var relatedBatchTasks = batchContext .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => Math.Abs((wo.WorkOrderDate.Date - workOrder.WorkOrderDate.Date).Days) <= maxDays) .Select(wo => wo.WorkOrderCode) .ToList(); var batchTasksInfo = relatedBatchTasks.Any() ? $"(涉及批次内任务: {string.Join(", ", relatedBatchTasks)})" : ""; conflicts.Add($"违反连续排班天数限制规则 '{ruleMapping.Rule?.RuleName}': 连续工作 {consecutiveDays} 天超过限制 {maxDays} 天 {batchTasksInfo}"); } } catch (Exception ex) { conflicts.Add($"连续排班天数限制规则验证异常: {ex.Message}"); } return conflicts; } /// /// 检查班次规则冲突 /// /// /// /// /// /// private async Task> CheckShiftRuleConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime) { var conflicts = new List(); try { // 获取班次对应的规则映射 var shiftRuleMappings = await _shiftRuleMappingService.GetRulesByShiftIdAsync(workOrder.ShiftId ?? 0); // 过滤启用的规则映射 var enabledRuleMappings = shiftRuleMappings .Where(mapping => mapping.IsEnabled) .OrderBy(mapping => mapping.ExecutionPriority) // 按优先级排序 .ToList(); if (!enabledRuleMappings.Any()) { return conflicts; // 没有启用的规则,直接返回 } foreach (var ruleMapping in enabledRuleMappings) { // 根据规则类型执行不同的检查逻辑 var ruleConflicts = await ValidateShiftRuleAsync(ruleMapping, personnelId, workOrder, workStartTime, workEndTime); if (ruleConflicts.Any()) { conflicts.AddRange(ruleConflicts); } } } catch (Exception ex) { conflicts.Add($"班次规则检查异常: {ex.Message}"); } return conflicts; } /// /// 检查规则是否在有效期内 /// /// /// /// private bool IsRuleEffective(ShiftRuleMappingGetOutput ruleMapping, DateTime targetDate) { // 优先检查映射级别的时间设置 if (ruleMapping.EffectiveStartTime.HasValue && targetDate < ruleMapping.EffectiveStartTime.Value.Date) { return false; } if (ruleMapping.EffectiveEndTime.HasValue && targetDate > ruleMapping.EffectiveEndTime.Value.Date) { return false; } return true; } /// /// 验证具体的班次规则 /// /// /// /// /// /// /// private async Task> ValidateShiftRuleAsync( ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime) { var conflicts = new List(); // 根据 RuleID 查询具体的 ShiftRule 详细信息 ShiftRuleGetOutput shiftRule = null; if (ruleMapping.RuleId > 0) { try { shiftRule = await _shiftRuleService.GetAsync(ruleMapping.RuleId); if (shiftRule == null) { _logger.LogWarning($"无法找到ID为 {ruleMapping.RuleId} 的班次规则"); return conflicts; // 如果找不到规则,直接返回空冲突列表 } } catch (Exception ex) { _logger.LogError(ex, $"查询班次规则 {ruleMapping.RuleId} 时发生异常"); conflicts.Add($"查询班次规则详情失败: {ex.Message}"); return conflicts; } } try { // 根据规则类型执行不同的验证逻辑 switch (shiftRule.RuleType) { case "3": // 一/二班不连续 conflicts.AddRange(await ValidateShiftSequenceRule(ruleMapping, shiftRule, personnelId, workOrder, "一", "二")); break; case "4": // 二/三班不连续 conflicts.AddRange(await ValidateShiftSequenceRule(ruleMapping, shiftRule, personnelId, workOrder, "二", "三")); break; case "5": // 不能这周日/下周六连 conflicts.AddRange(await ValidateSundayToSaturdayRule(ruleMapping, shiftRule, personnelId, workOrder)); break; case "6": // 不能本周六/本周日连 conflicts.AddRange(await ValidateSaturdayToSundayRule(ruleMapping, shiftRule, personnelId, workOrder)); break; case "7": // 不能连续7天排班 conflicts.AddRange(await ValidateMaxConsecutiveDaysRule(ruleMapping, shiftRule, personnelId, workOrder, 7)); break; case "8": // 三班后一天不排班 conflicts.AddRange(await ValidateRestAfterShiftRule(ruleMapping, shiftRule, personnelId, workOrder, "三")); break; case "9": // 二班后一天不排班 conflicts.AddRange(await ValidateRestAfterShiftRule(ruleMapping, shiftRule, personnelId, workOrder, "二")); break; default: break; } } catch (Exception ex) { conflicts.Add($"规则 '{ruleMapping.Rule?.RuleName}' 验证异常: {ex.Message}"); } return conflicts; } /// /// 验证班次连续性规则(如一/二班不连续) /// 检查同一天内同一人员不能同时安排指定的两种班次 /// /// 规则映射 /// 班次规则详细信息 /// 人员ID /// 当前工作任务 /// 第一个班次名称 /// 第二个班次名称 /// private async Task> ValidateShiftSequenceRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, string firstShiftName, string secondShiftName) { var conflicts = new List(); try { // 使用详细的班次规则信息进行验证 if (shiftRule != null) { _logger.LogInformation($"执行班次连续性规则验证: {shiftRule.RuleName} (ID: {shiftRule.Id}), " + $"规则描述: {shiftRule.Description}, " + $"规则类型: {shiftRule.RuleType}, " + $"是否启用: {shiftRule.IsEnabled}"); // 如果规则被禁用,跳过验证 if (!shiftRule.IsEnabled) { _logger.LogInformation($"规则 {shiftRule.RuleName} 已禁用,跳过验证"); return conflicts; } } var currentShift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (currentShift == null) return conflicts; var currentDate = workOrder.WorkOrderDate.Date; // 检查当前班次是否为规则中的班次之一 bool currentIsFirstShift = currentShift.Name?.Contains(firstShiftName) == true; bool currentIsSecondShift = currentShift.Name?.Contains(secondShiftName) == true; if (!currentIsFirstShift && !currentIsSecondShift) { // 当前班次不是规则关注的班次,直接返回 return conflicts; } // 获取同一天内该人员的所有其他任务 // 人员相关规则检查:只包含已分配人员的状态 var sameDayTasks = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.Id != workOrder.Id) .Where(wo => wo.WorkOrderDate.Date == currentDate) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .ToListAsync(); // 检查同一天内是否已经有另一种班次的安排 foreach (var task in sameDayTasks) { var taskShift = await _shiftService.GetAsync(task.ShiftId ?? 0); if (taskShift == null) continue; bool taskIsFirstShift = taskShift.Name?.Contains(firstShiftName) == true; bool taskIsSecondShift = taskShift.Name?.Contains(secondShiftName) == true; // 如果当前是一班,检查同一天是否有二班;如果当前是二班,检查同一天是否有一班 if ((currentIsFirstShift && taskIsSecondShift) || (currentIsSecondShift && taskIsFirstShift)) { conflicts.Add($"违反班次连续性规则 '{ruleMapping.Rule?.RuleName}': 同一天内不能同时安排{firstShiftName}班和{secondShiftName}班 (已有 {taskShift.Name})"); break; // 找到一个冲突即可 } } } catch (Exception ex) { conflicts.Add($"班次连续性规则验证异常: {ex.Message}"); } return conflicts; } /// /// 验证这周日/下周六不连续规则 /// private async Task> ValidateSundayToSaturdayRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 检查当前任务是否在周日 if (currentDate.DayOfWeek == DayOfWeek.Sunday) { // 检查下周六是否有排班 var nextSaturday = currentDate.AddDays(6); // 周日 + 6天 = 下周六 // 时间相关规则检查:包含待分配及以后的状态 var nextSaturdayTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == nextSaturday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (nextSaturdayTask) { conflicts.Add($"违反周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在这周日和下周六安排工作"); } } // 检查当前任务是否在周六 if (currentDate.DayOfWeek == DayOfWeek.Saturday) { // 检查上周日是否有排班 var lastSunday = currentDate.AddDays(-6); // 周六 - 6天 = 上周日 // 时间相关规则检查:包含待分配及以后的状态 var lastSundayTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == lastSunday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (lastSundayTask) { conflicts.Add($"违反周日/周六连续规则 '{ruleMapping.Rule?.RuleName}': 不能在上周日和这周六安排工作"); } } } catch (Exception ex) { conflicts.Add($"周日/周六连续规则验证异常: {ex.Message}"); } return conflicts; } /// /// 验证本周六/本周日不连续规则 /// private async Task> ValidateSaturdayToSundayRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 如果当前是周六,检查周日是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Saturday) { var sunday = currentDate.AddDays(1); // 时间相关规则检查:包含待分配及以后的状态 var sundayTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == sunday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (sundayTask) { conflicts.Add($"违反周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作"); } } // 如果当前是周日,检查周六是否有排班 if (currentDate.DayOfWeek == DayOfWeek.Sunday) { var saturday = currentDate.AddDays(-1); // 时间相关规则检查:包含待分配及以后的状态 var saturdayTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == saturday) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.PendingAssignment || wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (saturdayTask) { conflicts.Add($"违反周六周日连续规则 '{ruleMapping.Rule?.RuleName}': 不能在周六和周日连续安排工作"); } } } catch (Exception ex) { conflicts.Add($"周六周日连续规则验证异常: {ex.Message}"); } return conflicts; } /// /// 验证连续排班天数限制规则 /// private async Task> ValidateMaxConsecutiveDaysRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, int maxDays) { var conflicts = new List(); try { var consecutiveDays = await CalculateConsecutiveWorkDaysAsync(personnelId, workOrder.WorkOrderDate); if (consecutiveDays >= maxDays) { conflicts.Add($"违反连续排班天数限制规则 '{ruleMapping.Rule?.RuleName}': 连续工作 {consecutiveDays} 天超过限制 {maxDays} 天"); } } catch (Exception ex) { conflicts.Add($"连续排班天数限制规则验证异常: {ex.Message}"); } return conflicts; } /// /// 验证指定班次后一天不排班规则 /// private async Task> ValidateRestAfterShiftRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, string shiftName) { var conflicts = new List(); try { var currentDate = workOrder.WorkOrderDate.Date; // 检查前一天是否是指定班次 var previousDay = currentDate.AddDays(-1); // 人员相关规则检查:只包含已分配人员的状态 var previousTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == previousDay) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .FirstAsync(); if (previousTask != null) { var previousShift = await _shiftService.GetAsync(previousTask.ShiftId ?? 0); if (previousShift != null && previousShift.Name?.Contains(shiftName) == true) { conflicts.Add($"违反班次后休息规则 '{ruleMapping.Rule?.RuleName}': {shiftName}班后一天不能安排工作"); } } // 检查当前任务是指定班次,确保后一天不排班 var currentShift = await _shiftService.GetAsync(workOrder.ShiftId ?? 0); if (currentShift != null && currentShift.Name?.Contains(shiftName) == true) { var nextDay = currentDate.AddDays(1); // 人员相关规则检查:只包含已分配人员的状态 var nextTask = await _workOrderRepository.Select .Where(wo => wo.AssignedPersonnelId == personnelId) .Where(wo => wo.WorkOrderDate.Date == nextDay) .Where(wo => wo.Status == (int)WorkOrderStatusEnum.Assigned || wo.Status == (int)WorkOrderStatusEnum.InProgress || wo.Status == (int)WorkOrderStatusEnum.Completed) .AnyAsync(); if (nextTask) { conflicts.Add($"违反班次后休息规则 '{ruleMapping.Rule?.RuleName}': {shiftName}班后一天不能安排工作"); } } } catch (Exception ex) { conflicts.Add($"班次后休息规则验证异常: {ex.Message}"); } return conflicts; } /// /// 检查时间段是否重叠 /// 修复边界情况处理:相邻时间点不应被视为重叠,支持跨天班次检查 /// 优化同一天多任务场景:增加缓冲时间机制,提升排班灵活性 /// /// 第一个时间段的开始时间 /// 第一个时间段的结束时间 /// 第二个时间段的开始时间 /// 第二个时间段的结束时间 /// 如果时间段重叠返回true,否则返回false private bool IsTimeRangeOverlap(DateTime start1, DateTime end1, DateTime start2, DateTime end2) { // 【新增】缓冲时间配置:允许班次间有30分钟缓冲,支持同一天多任务灵活调度 const int bufferMinutes = 30; // 处理跨天班次:如果结束时间小于开始时间,说明跨天 var normalizedEnd1 = end1 < start1 ? end1.AddDays(1) : end1; var normalizedEnd2 = end2 < start2 ? end2.AddDays(1) : end2; var normalizedStart1 = start1; var normalizedStart2 = end2 < start2 ? start2.AddDays(-1) : start2; // 【优化】应用缓冲时间:在时间比较时减少缓冲时间,允许合理的班次间隔 var bufferedEnd1 = normalizedEnd1.AddMinutes(-bufferMinutes); var bufferedStart1 = normalizedStart1.AddMinutes(bufferMinutes); var bufferedEnd2 = normalizedEnd2.AddMinutes(-bufferMinutes); var bufferedStart2 = normalizedStart2.AddMinutes(bufferMinutes); // 【深度业务思考】: // 1. 一班(08:00-17:30) + 二班(14:00-22:00) 原本重叠14:00-17:30(3.5小时) // 2. 应用30分钟缓冲后:一班实际到17:00,二班实际从14:30开始 // 3. 重叠时间减少为14:30-17:00(2.5小时),仍然冲突但影响降低 // 4. 如果是一班(08:00-14:00) + 二班(14:30-22:00),则完全无冲突 // 标准重叠检查:考虑缓冲时间后判断是否仍有重叠 bool hasOverlap = bufferedStart1 < normalizedEnd2 && normalizedStart2 < bufferedEnd1; // 【同一天多任务特殊处理】:如果是同一天且时间接近,允许适度重叠 if (normalizedStart1.Date == normalizedStart2.Date) { var overlapMinutes = CalculateOverlapMinutes(normalizedStart1, normalizedEnd1, normalizedStart2, normalizedEnd2); // 【业务规则】:同一天任务重叠时间小于2小时时,降低冲突等级(警告而非阻止) if (overlapMinutes > 0 && overlapMinutes <= 120) // 2小时以内的重叠 { _logger.LogWarning($"同一天任务存在 {overlapMinutes} 分钟重叠,但在可接受范围内(≤2小时)"); return false; // 不视为严重冲突,允许排班 } } return hasOverlap; } /// /// 计算两个时间段的重叠分钟数 /// 用于同一天多任务场景的精确重叠时间计算 /// /// 第一个时间段开始 /// 第一个时间段结束 /// 第二个时间段开始 /// 第二个时间段结束 /// 重叠分钟数,无重叠返回0 private double CalculateOverlapMinutes(DateTime start1, DateTime end1, DateTime start2, DateTime end2) { var overlapStart = start1 > start2 ? start1 : start2; var overlapEnd = end1 < end2 ? end1 : end2; if (overlapStart < overlapEnd) { return (overlapEnd - overlapStart).TotalMinutes; } return 0; // 无重叠 } /// /// 根据冲突详情获取错误类型 /// /// /// private ValidationErrorType GetErrorTypeFromDetail(string detail) { if (detail.Contains("请假")) return ValidationErrorType.PersonnelLeaveConflict; else if (detail.Contains("任务") && detail.Contains("冲突")) return ValidationErrorType.TaskTimeConflict; else if (detail.Contains("连续工作") || detail.Contains("工作时间")) return ValidationErrorType.ContinuousWorkTimeExceeded; else return ValidationErrorType.WorkLimitConflict; } /// /// 根据用户ID查询任务列表 /// 只返回员工相关的任务状态:已分配、进行中、已完成 /// /// 用户ID /// public async Task> GetByUserIdAsync(long userId) { var workOrders = await _workOrderRepository.Select .Where(a => a.AssignedPersonnelId == userId) .Where(a => a.Status == (int)WorkOrderStatusEnum.Assigned || a.Status == (int)WorkOrderStatusEnum.InProgress || a.Status == (int)WorkOrderStatusEnum.Completed) .OrderBy(a => a.Status == (int)WorkOrderStatusEnum.InProgress ? 0 : a.Status == (int)WorkOrderStatusEnum.Assigned ? 1 : 2) .OrderByDescending(a => a.WorkOrderDate) .OrderByDescending(a => a.Id) .ToListAsync(); if (workOrders?.Any() == true) { var workOrderIds = workOrders.Select(x => x.Id).ToList(); var flPersonnelsList = await _workOrderFlPersonnelRepository.Select .Where(a => workOrderIds.Contains(a.WorkOrderId)) .ToListAsync(); var flPersonnelsDict = flPersonnelsList .GroupBy(fp => fp.WorkOrderId) .ToDictionary( g => g.Key, g => g.Select(fp => new FLPersonnelInfo { PersonnelId = fp.FLPersonnelId, PersonnelName = fp.FLPersonnelName }).ToList() ); foreach (var item in workOrders) { if (flPersonnelsDict.TryGetValue(item.Id, out var flPersonnels)) { item.FLPersonnels = flPersonnels; } else { item.FLPersonnels = new List(); } } } return workOrders ?? new List(); } #endregion }