Asoka.Wang 21f044712c 1
2025-08-27 18:39:19 +08:00

2467 lines
102 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 工作任务服务
/// </summary>
[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<WorkOrderService> _logger;
public WorkOrderService(
WorkOrderRepository workOrderRepository,
WorkOrderFLPersonnelRepository workOrderFlPersonnelRepository,
IEmployeeLeaveService employeeLeaveService,
IPersonnelWorkLimitService personnelWorkLimitService,
IShiftService shiftService,
IShiftRuleMappingService shiftRuleMappingService,
IShiftRuleService shiftRuleService,
ILogger<WorkOrderService> logger)
{
_workOrderRepository = workOrderRepository;
_workOrderFlPersonnelRepository = workOrderFlPersonnelRepository;
_employeeLeaveService = employeeLeaveService;
_personnelWorkLimitService = personnelWorkLimitService;
_shiftService = shiftService;
_shiftRuleMappingService = shiftRuleMappingService;
_shiftRuleService = shiftRuleService;
_logger = logger;
}
/// <summary>
/// 查询
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<WorkOrderGetOutput> GetAsync(long id)
{
var output = await _workOrderRepository.Select
.WhereDynamic(id)
.ToOneAsync<WorkOrderGetOutput>();
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;
}
/// <summary>
/// 查询分页
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<WorkOrderGetPageOutput>> GetPageAsync(PageInput<WorkOrderGetPageInput> 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<WorkOrderGetPageOutput>();
// 为分页结果添加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<FLPersonnelInfo>();
}
}
}
var data = new PageOutput<WorkOrderGetPageOutput>()
{
List = list,
Total = total
};
return data;
}
/// <summary>
/// 添加
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<long> AddAsync(WorkOrderAddInput input)
{
var entity = Mapper.Map<WorkOrderEntity>(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;
}
/// <summary>
/// 批量添加
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<BatchWorkOrderAddOutput> 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<long>();
var failedItems = new List<BatchWorkOrderError>();
// 验证重复任务(使用项目号+班次+工序组合)
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;
}
/// <summary>
/// 修改
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
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<FLPersonnelInfo>());
}
/// <summary>
/// 删除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task DeleteAsync(long id)
{
// 删除FL人员关联
await _workOrderFlPersonnelRepository.DeleteAsync(a => a.WorkOrderId == id);
// 删除工作任务
await _workOrderRepository.DeleteAsync(id);
}
/// <summary>
/// 软删除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task SoftDeleteAsync(long id)
{
await _workOrderRepository.SoftDeleteAsync(id);
}
/// <summary>
/// 批量软删除
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task BatchSoftDeleteAsync(long[] ids)
{
await _workOrderRepository.SoftDeleteAsync(ids);
}
/// <summary>
/// 更新任务状态
/// </summary>
/// <param name="id"></param>
/// <param name="status"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 开始任务
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 完成任务
/// </summary>
/// <param name="id"></param>
/// <param name="actualWorkHours"></param>
/// <returns></returns>
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
/// <summary>
/// 添加单个工作任务(内部方法)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private async Task<long> AddSingleWorkOrderAsync(WorkOrderAddInput input)
{
var entity = Mapper.Map<WorkOrderEntity>(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;
}
/// <summary>
/// 添加工作任务FL人员关联
/// </summary>
/// <param name="workOrderId"></param>
/// <param name="flPersonnels"></param>
/// <returns></returns>
private async Task AddWorkOrderFLPersonnelsAsync(long workOrderId, List<FLPersonnelInfo> 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);
}
}
/// <summary>
/// 更新工作任务FL人员关联
/// </summary>
/// <param name="workOrderId"></param>
/// <param name="flPersonnels"></param>
/// <returns></returns>
private async Task UpdateWorkOrderFLPersonnelsAsync(long workOrderId, List<FLPersonnelInfo> flPersonnels)
{
// 删除现有关联
await _workOrderFlPersonnelRepository.DeleteAsync(a => a.WorkOrderId == workOrderId);
// 添加新关联
await AddWorkOrderFLPersonnelsAsync(workOrderId, flPersonnels);
}
private async Task<WorkOrderEntity> 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
/// <summary>
/// 单个任务自检
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<WorkOrderValidationOutput> ValidateWorkOrderAsync(SingleWorkOrderValidationInput input)
{
var workOrder = await _workOrderRepository.GetAsync(input.WorkOrderId);
if (workOrder == null)
{
return new WorkOrderValidationOutput
{
WorkOrderId = input.WorkOrderId,
IsValid = false,
Errors = new List<NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError>
{
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<string> { "任务尚未分配实施人员" }
});
}
// 如果自检通过且需要更新状态为待整合
if (result.IsValid && input.UpdateStatusToPendingIntegration)
{
await UpdateStatusAsync(workOrder.Id, WorkOrderStatusEnum.PendingIntegration);
}
return result;
}
/// <summary>
/// 多个任务自检
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<MultipleWorkOrderValidationOutput> 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<NPP.SmartSchedue.Api.Contracts.Services.Work.Output.WorkOrderValidationError>
{
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<Task>();
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<long>())}");
throw new InvalidOperationException($"批量状态更新失败: {ex.Message}", ex);
}
}
return result;
}
/// <summary>
/// 带批次上下文的单任务验证(考虑本批次其他任务的影响)
/// </summary>
/// <param name="workOrder">当前任务</param>
/// <param name="batchContext">批次内其他任务</param>
/// <returns></returns>
private async Task<WorkOrderValidationOutput> ValidateWorkOrderWithBatchContextAsync(WorkOrderEntity workOrder, List<WorkOrderEntity> 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<string> { "任务尚未分配实施人员" }
});
}
return result;
}
/// <summary>
/// 带批次上下文的人员空闲状态检查
/// </summary>
private async Task<PersonnelAvailabilityResult> CheckPersonnelAvailabilityWithBatchContextAsync(
long personnelId,
string personnelName,
WorkOrderEntity workOrder,
List<WorkOrderEntity> 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;
}
/// <summary>
/// 检查人员空闲状态
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="personnelName">人员姓名</param>
/// <param name="workOrder">工作任务</param>
/// <returns></returns>
private async Task<PersonnelAvailabilityResult> 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;
}
/// <summary>
/// 带批次上下文的任务时间冲突检查(包含精确的时间段重叠检查)
/// </summary>
private async Task<List<string>> CheckTaskTimeConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List<WorkOrderEntity> batchContext)
{
return await CheckTaskTimeConflictsWithBatchContextAsync(personnelId, workOrder, workStartTime, workEndTime, batchContext, null);
}
/// <summary>
/// 带批次上下文和缓存优化的任务时间冲突检查(包含精确的时间段重叠检查)
/// 【性能优化】优先使用PersonnelHistoryTasks缓存避免重复数据库查询
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workOrder">工作任务</param>
/// <param name="workStartTime">工作开始时间</param>
/// <param name="workEndTime">工作结束时间</param>
/// <param name="batchContext">批次上下文中的其他任务</param>
/// <param name="personnelHistoryCache">人员历史任务缓存(可选,优先使用缓存避免数据库查询)</param>
/// <returns>冲突详情列表</returns>
private async Task<List<string>> CheckTaskTimeConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List<WorkOrderEntity> batchContext, Dictionary<long, List<WorkOrderHistoryItem>>? personnelHistoryCache)
{
var conflicts = new List<string>();
// 获取当前任务的班次信息
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;
}
/// <summary>
/// 检查任务时间冲突
/// </summary>
/// <param name="personnelId"></param>
/// <param name="workOrder"></param>
/// <param name="workStartTime"></param>
/// <param name="workEndTime"></param>
/// <returns></returns>
private async Task<List<string>> CheckTaskTimeConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime)
{
var conflicts = new List<string>();
// 查询该人员作为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;
}
/// <summary>
/// 带批次上下文的工作时间限制冲突检查
/// </summary>
private async Task<List<string>> CheckWorkLimitConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 带批次上下文的班次规则冲突检查
/// </summary>
private async Task<List<string>> CheckShiftRuleConflictsWithBatchContextAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 检查工作时间限制冲突
/// </summary>
/// <param name="personnelId"></param>
/// <param name="workOrder"></param>
/// <param name="workStartTime"></param>
/// <param name="workEndTime"></param>
/// <returns></returns>
private async Task<List<string>> CheckWorkLimitConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 计算连续工作天数
/// 修复硬编码限制,增加节假日处理,提升计算准确性
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="targetDate">目标日期</param>
/// <returns>连续工作天数</returns>
private async Task<int> 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;
}
/// <summary>
/// 计算连续工作天数(考虑批次上下文)
/// </summary>
private async Task<int> CalculateConsecutiveWorkDaysWithBatchContextAsync(long personnelId, DateTime targetDate, List<WorkOrderEntity> batchContext)
{
var consecutiveDays = 1; // 包含目标日期
// 获取批次内该人员的任务,按日期排序
var personnelBatchTasks = batchContext
.Where(wo => wo.AssignedPersonnelId == personnelId)
.OrderBy(wo => wo.WorkOrderDate.Date)
.ToList();
// 创建一个包含批次任务的日期集合
var batchWorkDates = new HashSet<DateTime>(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;
}
/// <summary>
/// 带批次上下文的班次规则验证
/// </summary>
private async Task<List<string>> ValidateShiftRuleWithBatchContextAsync(
ShiftRuleMappingGetOutput ruleMapping,
long personnelId,
WorkOrderEntity workOrder,
DateTime workStartTime,
DateTime workEndTime,
List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
// 根据 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;
}
/// <summary>
/// 带批次上下文的班次后休息规则验证(示例实现)
/// </summary>
private async Task<List<string>> ValidateRestAfterShiftRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, string shiftName, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 带批次上下文的班次连续性规则验证规则3和4一/二班不连续以及二/三班不连续)
/// 深度思考:同一人员同一天不能有冲突的班次安排,需要综合考虑批次内任务和数据库历史任务
/// </summary>
private async Task<List<string>> ValidateShiftSequenceRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, string firstShiftName, string secondShiftName, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 带批次上下文的这周日/下周六连续规则验证规则5
/// 深度思考:防止跨周连续工作,需要检查周日->下周六的7天跨度批次内可能同时包含这两个时间点的任务
/// </summary>
private async Task<List<string>> ValidateSundayToSaturdayRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 带批次上下文的本周六/本周日连续规则验证规则6
/// 深度思考:防止周末连续工作,批次内可能同时包含同一人员的周六和周日任务
/// </summary>
private async Task<List<string>> ValidateSaturdayToSundayRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 带批次上下文的最大连续天数规则验证规则7不能连续7天排班
/// 深度思考:这是最严格的连续工作限制,需要基于批次+历史数据计算真实的连续工作天数序列
/// </summary>
private async Task<List<string>> ValidateMaxConsecutiveDaysRuleWithBatchContext(ShiftRuleMappingGetOutput ruleMapping, long personnelId, WorkOrderEntity workOrder, int maxDays, List<WorkOrderEntity> batchContext)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 检查班次规则冲突
/// </summary>
/// <param name="personnelId"></param>
/// <param name="workOrder"></param>
/// <param name="workStartTime"></param>
/// <param name="workEndTime"></param>
/// <returns></returns>
private async Task<List<string>> CheckShiftRuleConflictsAsync(long personnelId, WorkOrderEntity workOrder, DateTime workStartTime, DateTime workEndTime)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 检查规则是否在有效期内
/// </summary>
/// <param name="ruleMapping"></param>
/// <param name="targetDate"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 验证具体的班次规则
/// </summary>
/// <param name="ruleMapping"></param>
/// <param name="personnelId"></param>
/// <param name="workOrder"></param>
/// <param name="workStartTime"></param>
/// <param name="workEndTime"></param>
/// <returns></returns>
private async Task<List<string>> ValidateShiftRuleAsync(
ShiftRuleMappingGetOutput ruleMapping,
long personnelId,
WorkOrderEntity workOrder,
DateTime workStartTime,
DateTime workEndTime)
{
var conflicts = new List<string>();
// 根据 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;
}
/// <summary>
/// 验证班次连续性规则(如一/二班不连续)
/// 检查同一天内同一人员不能同时安排指定的两种班次
/// </summary>
/// <param name="ruleMapping">规则映射</param>
/// <param name="shiftRule">班次规则详细信息</param>
/// <param name="personnelId">人员ID</param>
/// <param name="workOrder">当前工作任务</param>
/// <param name="firstShiftName">第一个班次名称</param>
/// <param name="secondShiftName">第二个班次名称</param>
/// <returns></returns>
private async Task<List<string>> ValidateShiftSequenceRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, string firstShiftName, string secondShiftName)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 验证这周日/下周六不连续规则
/// </summary>
private async Task<List<string>> ValidateSundayToSaturdayRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 验证本周六/本周日不连续规则
/// </summary>
private async Task<List<string>> ValidateSaturdayToSundayRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 验证连续排班天数限制规则
/// </summary>
private async Task<List<string>> ValidateMaxConsecutiveDaysRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, int maxDays)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 验证指定班次后一天不排班规则
/// </summary>
private async Task<List<string>> ValidateRestAfterShiftRule(ShiftRuleMappingGetOutput ruleMapping, ShiftRuleGetOutput shiftRule, long personnelId, WorkOrderEntity workOrder, string shiftName)
{
var conflicts = new List<string>();
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;
}
/// <summary>
/// 检查时间段是否重叠
/// 修复边界情况处理:相邻时间点不应被视为重叠,支持跨天班次检查
/// 优化同一天多任务场景:增加缓冲时间机制,提升排班灵活性
/// </summary>
/// <param name="start1">第一个时间段的开始时间</param>
/// <param name="end1">第一个时间段的结束时间</param>
/// <param name="start2">第二个时间段的开始时间</param>
/// <param name="end2">第二个时间段的结束时间</param>
/// <returns>如果时间段重叠返回true否则返回false</returns>
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:303.5小时)
// 2. 应用30分钟缓冲后一班实际到17:00二班实际从14:30开始
// 3. 重叠时间减少为14:30-17:002.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;
}
/// <summary>
/// 计算两个时间段的重叠分钟数
/// 用于同一天多任务场景的精确重叠时间计算
/// </summary>
/// <param name="start1">第一个时间段开始</param>
/// <param name="end1">第一个时间段结束</param>
/// <param name="start2">第二个时间段开始</param>
/// <param name="end2">第二个时间段结束</param>
/// <returns>重叠分钟数无重叠返回0</returns>
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; // 无重叠
}
/// <summary>
/// 根据冲突详情获取错误类型
/// </summary>
/// <param name="detail"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 根据用户ID查询任务列表
/// 只返回员工相关的任务状态:已分配、进行中、已完成
/// </summary>
/// <param name="userId">用户ID</param>
/// <returns></returns>
public async Task<List<WorkOrderGetOutput>> 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<WorkOrderGetOutput>();
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<FLPersonnelInfo>();
}
}
}
return workOrders ?? new List<WorkOrderGetOutput>();
}
#endregion
}