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
}