1717 lines
71 KiB
C#
1717 lines
71 KiB
C#
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 IShiftUnavailabilityService _shiftUnavailabilityService;
|
||
private readonly ILogger<WorkOrderService> _logger;
|
||
|
||
public WorkOrderService(
|
||
WorkOrderRepository workOrderRepository,
|
||
WorkOrderFLPersonnelRepository workOrderFlPersonnelRepository,
|
||
IEmployeeLeaveService employeeLeaveService,
|
||
IPersonnelWorkLimitService personnelWorkLimitService,
|
||
IShiftService shiftService,
|
||
IShiftRuleMappingService shiftRuleMappingService,
|
||
IShiftRuleService shiftRuleService,
|
||
IShiftUnavailabilityService shiftUnavailabilityService,
|
||
ILogger<WorkOrderService> logger)
|
||
{
|
||
_workOrderRepository = workOrderRepository;
|
||
_workOrderFlPersonnelRepository = workOrderFlPersonnelRepository;
|
||
_employeeLeaveService = employeeLeaveService;
|
||
_personnelWorkLimitService = personnelWorkLimitService;
|
||
_shiftService = shiftService;
|
||
_shiftRuleMappingService = shiftRuleMappingService;
|
||
_shiftRuleService = shiftRuleService;
|
||
_shiftUnavailabilityService = shiftUnavailabilityService;
|
||
_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
|
||
{
|
||
FLPersonnelId = fp.FLPersonnelId,
|
||
FLPersonnelName = 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
|
||
{
|
||
FLPersonnelId = fp.FLPersonnelId,
|
||
FLPersonnelName = 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)
|
||
{
|
||
return await AddSingleWorkOrderAsync(input);
|
||
}
|
||
|
||
/// <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>();
|
||
|
||
// 开始批量处理
|
||
for (int i = 0; i < input.WorkOrders.Count; i++)
|
||
{
|
||
var workOrder = input.WorkOrders[i];
|
||
|
||
try
|
||
{
|
||
// 添加单个任务
|
||
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);
|
||
|
||
await 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 result = new WorkOrderEntity();
|
||
|
||
for (int i = 1; i <= input.RequiredPersonnel; i++)
|
||
{
|
||
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;
|
||
}
|
||
|
||
if (input.RequiredPersonnel > 1 && i == 1)
|
||
{
|
||
entity.NeedPostHead = true;
|
||
}
|
||
|
||
// 获取任务班次信息
|
||
await ConvertWorkOrderShift(entity);
|
||
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.FLPersonnelId,
|
||
FLPersonnelName = fp.FLPersonnelName,
|
||
CreatedTime = DateTime.Now
|
||
}).ToList();
|
||
|
||
await _workOrderFlPersonnelRepository.InsertAsync(entities);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新工作任务FL人员关联
|
||
/// </summary>
|
||
/// <param name="workOrderId"></param>
|
||
/// <param name="flPersonnels"></param>
|
||
private async Task UpdateWorkOrderFLPersonnelsAsync(long workOrderId, List<FLPersonnelInfo> flPersonnels)
|
||
{
|
||
// 删除现有关联
|
||
await _workOrderFlPersonnelRepository.DeleteAsync(a => a.WorkOrderId == workOrderId);
|
||
|
||
// 添加新关联
|
||
await AddWorkOrderFLPersonnelsAsync(workOrderId, flPersonnels);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换工作任务班次信息,计算计划开始和结束时间
|
||
/// 【核心业务逻辑】:将班次的时间配置与任务日期结合,生成具体的执行时间段
|
||
/// 【深度思考】:该方法解决了班次时间模板与具体任务执行日期的映射问题,
|
||
/// 特别处理了跨天班次的复杂场景,确保时间计算的准确性
|
||
/// </summary>
|
||
/// <param name="workOrderEntity">待转换的工作任务实体</param>
|
||
/// <returns>转换后的工作任务实体,包含计算好的计划开始和结束时间</returns>
|
||
private async Task<WorkOrderEntity> ConvertWorkOrderShift(WorkOrderEntity workOrderEntity)
|
||
{
|
||
// 【业务逻辑】:根据任务的班次ID获取班次定义信息
|
||
// 班次定义包含: StartTime、EndTime、班次名称等基础配置
|
||
var shift = await _shiftService.GetAsync(workOrderEntity.ShiftId ?? 0);
|
||
if (shift != null)
|
||
{
|
||
// 【时间计算核心】:将任务执行日期与班次时间模板结合
|
||
// 关键设计:只取日期部分,避免时间部分干扰班次时间的准确计算
|
||
var workDate = workOrderEntity.WorkOrderDate.Date; // 取日期部分,确保时间为00:00:00
|
||
|
||
// 【计划时间生成】:组合任务日期和班次时间,生成具体的执行时间点
|
||
// 例:任务日期2024-01-15 + 班次开始08:00 = 2024-01-15 08:00:00
|
||
workOrderEntity.PlannedStartTime = workDate.Add(shift.StartTime);
|
||
workOrderEntity.PlannedEndTime = workDate.Add(shift.EndTime);
|
||
|
||
// 【跨天班次特殊处理】:处理夜班等跨越午夜的班次场景
|
||
// 深度业务思考:如夜班22:00-06:00,结束时间小于开始时间表示跨天
|
||
// 解决方案:将结束时间推迟一天,确保时间逻辑正确
|
||
// 示例:2024-01-15 22:00 - 2024-01-16 06:00
|
||
if (shift.EndTime < shift.StartTime)
|
||
{
|
||
// 跨天班次:结束时间需要加一天
|
||
// 业务场景:夜班从今天22:00工作到明天06:00
|
||
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<WorkOrderValidationError>
|
||
{
|
||
new WorkOrderValidationError
|
||
{
|
||
ErrorType = ValidationErrorType.TaskTimeConflict,
|
||
Message = "任务不存在"
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
var result = new WorkOrderValidationOutput
|
||
{
|
||
WorkOrderId = input.WorkOrderId,
|
||
WorkOrderCode = workOrder.WorkOrderCode,
|
||
IsValid = true
|
||
};
|
||
|
||
// 如果任务已分配实施人员,则检查人员空闲状态
|
||
if (workOrder.AssignedPersonnelId.HasValue)
|
||
{
|
||
// 检查分配人员的空闲状态
|
||
var personnelResult = await CheckPersonnelAvailabilityWithBatchContextAsync(
|
||
workOrder.AssignedPersonnelId.Value,
|
||
workOrder.AssignedPersonnelName ?? $"人员{workOrder.AssignedPersonnelId}",
|
||
workOrder,
|
||
new List<WorkOrderEntity>() { workOrder });
|
||
|
||
result.PersonnelResults.Add(personnelResult);
|
||
|
||
if (!personnelResult.IsAvailable)
|
||
{
|
||
result.IsValid = false;
|
||
// 添加具体的错误信息
|
||
foreach (var detail in personnelResult.ConflictDetails)
|
||
{
|
||
result.Errors.Add(new 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;
|
||
}
|
||
}
|
||
|
||
// 如果所有任务自检通过且需要更新状态为待整合
|
||
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 hasLeaveConflict = !await _shiftUnavailabilityService.CheckUnavailablePersonnelByShift(
|
||
workOrder.WorkOrderDate,
|
||
workOrder.ShiftId ?? 0, workOrder.AssignedPersonnelId ?? 0);
|
||
|
||
if (hasLeaveConflict)
|
||
{
|
||
result.HasLeaveConflict = true;
|
||
result.IsAvailable = false;
|
||
result.ConflictDetails.Add($"人员 {personnelName} 在任务时间段的班次存在其他意愿");
|
||
}
|
||
|
||
// 检查任务时间冲突(包含批次上下文)
|
||
var taskConflicts =
|
||
await CheckTaskTimeConflictsWithBatchContextAsync(personnelId, workOrder, workOrder.PlannedStartTime,
|
||
workOrder.PlannedEndTime,
|
||
batchContext);
|
||
|
||
if (taskConflicts.Any())
|
||
{
|
||
result.HasTaskTimeConflict = true;
|
||
result.IsAvailable = false;
|
||
result.ConflictDetails.AddRange(taskConflicts);
|
||
}
|
||
|
||
// 检查班次规则冲突(包含批次上下文)
|
||
var shiftRuleConflicts =
|
||
await CheckShiftRuleConflictsWithBatchContextAsync(personnelId, workOrder, workOrder.PlannedStartTime,
|
||
workOrder.PlannedEndTime,
|
||
batchContext);
|
||
|
||
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>
|
||
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 shiftRules = await _shiftRuleService.GetListAsync();
|
||
|
||
foreach (var shiftRule in shiftRules)
|
||
{
|
||
if (shiftRule.IsEnabled)
|
||
{
|
||
// 根据规则类型执行不同的检查逻辑(考虑批次上下文)
|
||
var ruleConflicts = await ValidateShiftRuleWithBatchContextAsync(personnelId, workOrder, shiftRule,
|
||
workStartTime, workEndTime, batchContext);
|
||
|
||
if (ruleConflicts.Any())
|
||
{
|
||
conflicts.AddRange(ruleConflicts);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
conflicts.Add($"班次规则检查异常: {ex.Message}");
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
|
||
/// <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(
|
||
long personnelId,
|
||
WorkOrderEntity workOrder,
|
||
ShiftRuleGetPageOutput shiftRule,
|
||
DateTime workStartTime,
|
||
DateTime workEndTime,
|
||
List<WorkOrderEntity> batchContext)
|
||
{
|
||
var conflicts = new List<string>();
|
||
|
||
try
|
||
{
|
||
// 根据规则类型执行不同的验证逻辑
|
||
switch (shiftRule?.RuleType)
|
||
{
|
||
case "3": // 一/二班不连续
|
||
conflicts.AddRange(await ValidateShiftSequenceRuleWithBatchContext(shiftRule.RuleName, personnelId,
|
||
workOrder, "一", "二", batchContext));
|
||
break;
|
||
|
||
case "4": // 二/三班不连续
|
||
conflicts.AddRange(await ValidateShiftSequenceRuleWithBatchContext(shiftRule.RuleName, personnelId,
|
||
workOrder, "二", "三", batchContext));
|
||
break;
|
||
|
||
case "5": // 不能这周日/下周六连
|
||
conflicts.AddRange(await ValidateSundayToSaturdayRuleWithBatchContext( shiftRule.RuleName,personnelId,
|
||
workOrder, batchContext));
|
||
break;
|
||
|
||
case "6": // 不能本周六/本周日连
|
||
conflicts.AddRange(await ValidateSaturdayToSundayRuleWithBatchContext(shiftRule.RuleName,
|
||
personnelId,
|
||
workOrder, batchContext));
|
||
break;
|
||
|
||
case "7": // 不能连续7天排班
|
||
conflicts.AddRange(await ValidateMaxConsecutiveDaysRuleWithBatchContext(shiftRule.RuleName,personnelId,
|
||
workOrder, 7, batchContext));
|
||
break;
|
||
|
||
case "8": // 三班后一天不排班
|
||
conflicts.AddRange(await ValidateRestAfterShiftRuleWithBatchContext(shiftRule.RuleName,personnelId,
|
||
workOrder, "三", batchContext));
|
||
break;
|
||
|
||
case "9": // 二班后一天不排班
|
||
conflicts.AddRange(await ValidateRestAfterShiftRuleWithBatchContext(shiftRule.RuleName,personnelId,
|
||
workOrder, "二", batchContext));
|
||
break;
|
||
|
||
default:
|
||
// 对于未知规则类型,记录警告但不阻止任务
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
conflicts.Add($"规则 '{shiftRule?.RuleName}' 验证异常: {ex.Message}");
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带批次上下文的班次后休息规则验证(示例实现)
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateRestAfterShiftRuleWithBatchContext(string ruleName,
|
||
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(
|
||
$"违反批次内班次后休息规则 '{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(
|
||
$"违反班次后休息规则 '{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($"违反批次内班次后休息规则 '{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($"违反班次后休息规则 '{ruleName}': {shiftName}班后一天不能安排工作(与数据库任务冲突)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
conflicts.Add($"班次后休息规则验证异常: {ex.Message}");
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带批次上下文的班次连续性规则验证(规则3和4:一/二班不连续以及二/三班不连续)
|
||
/// 深度思考:同一人员同一天不能有冲突的班次安排,需要综合考虑批次内任务和数据库历史任务
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateShiftSequenceRuleWithBatchContext(string ruleName,
|
||
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(
|
||
$"违反批次内班次连续性规则 '{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(
|
||
$"违反班次连续性规则 '{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(string ruleName,
|
||
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(
|
||
$"违反批次内周日/周六连续规则 '{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($"违反周日/周六连续规则 '{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(
|
||
$"违反批次内周日/周六连续规则 '{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($"违反周日/周六连续规则 '{ruleName}': 不能在上周日和这周六安排工作 (与数据库任务冲突)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
conflicts.Add($"周日/周六连续规则验证异常: {ex.Message}");
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带批次上下文的本周六/本周日连续规则验证(规则6)
|
||
/// 深度思考:防止周末连续工作,批次内可能同时包含同一人员的周六和周日任务
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateSaturdayToSundayRuleWithBatchContext(string ruleName,
|
||
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(
|
||
$"违反批次内周六周日连续规则 '{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($"违反周六周日连续规则 '{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(
|
||
$"违反批次内周六周日连续规则 '{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($"违反周六周日连续规则 '{ruleName}': 不能在周六和周日连续安排工作 (与数据库任务冲突)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
conflicts.Add($"周六周日连续规则验证异常: {ex.Message}");
|
||
}
|
||
|
||
return conflicts;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带批次上下文的最大连续天数规则验证(规则7:不能连续7天排班)
|
||
/// 深度思考:这是最严格的连续工作限制,需要基于批次+历史数据计算真实的连续工作天数序列
|
||
/// </summary>
|
||
private async Task<List<string>> ValidateMaxConsecutiveDaysRuleWithBatchContext(string ruleName,
|
||
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(
|
||
$"违反连续排班天数限制规则 '{ruleName}': 连续工作 {consecutiveDays} 天超过限制 {maxDays} 天 {batchTasksInfo}");
|
||
}
|
||
}
|
||
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: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;
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
FLPersonnelId = fp.FLPersonnelId,
|
||
FLPersonnelName = 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
|
||
} |