| | |
| | | using Newtonsoft.Json; |
| | | using Microsoft.Extensions.Logging; |
| | | using Newtonsoft.Json; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using WIDESEA_Core; |
| | | using WIDESEAWCS_Common.Constants; |
| | | using WIDESEAWCS_Common.HttpEnum; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_Core; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_QuartzJob; |
| | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// 堆垛机任务选择器:封装任务挑选与站台可用性判断。 |
| | | /// 堆垛机任务选择器 - 封装任务挑选与站台可用性判断 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 核心职责: |
| | | /// 1. 根据堆垛机上一任务类型选择下一个合适的任务 |
| | | /// 2. 对出库任务进行移库检查(WMS 判断) |
| | | /// 3. 判断出库站台是否可用(是否被占用) |
| | | /// 4. 尝试选择备选出库站台 |
| | | /// |
| | | /// 任务选择策略: |
| | | /// - 如果上一任务是出库,优先选择入库任务 |
| | | /// - 如果上一任务是入库,优先选择出库任务 |
| | | /// - 对于出库任务,先检查是否需要移库 |
| | | /// </remarks> |
| | | public class StackerCraneTaskSelector |
| | | { |
| | | /// <summary> |
| | | /// 任务服务 |
| | | /// </summary> |
| | | private readonly ITaskService _taskService; |
| | | |
| | | /// <summary> |
| | | /// 路由服务 |
| | | /// </summary> |
| | | private readonly IRouterService _routerService; |
| | | |
| | | /// <summary> |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// 移库检查委托函数 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于调用 WMS 判断出库任务是否需要先执行移库。 |
| | | /// </remarks> |
| | | private readonly Func<int, Dt_Task?> _transferCheck; |
| | | |
| | | public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper) |
| | | : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum)) |
| | | /// <summary> |
| | | /// 构造函数(使用 HTTP 客户端帮助类) |
| | | /// </summary> |
| | | /// <param name="taskService">任务服务</param> |
| | | /// <param name="routerService">路由服务</param> |
| | | /// <param name="httpClientHelper">HTTP 客户端帮助类</param> |
| | | /// <param name="logger">日志记录器</param> |
| | | public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper, ILogger logger) |
| | | : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum), logger) |
| | | { |
| | | } |
| | | |
| | | public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, Func<int, Dt_Task?> transferCheck) |
| | | /// <summary> |
| | | /// 构造函数(使用委托函数) |
| | | /// </summary> |
| | | /// <param name="taskService">任务服务</param> |
| | | /// <param name="routerService">路由服务</param> |
| | | /// <param name="transferCheck">移库检查函数</param> |
| | | /// <param name="logger">日志记录器</param> |
| | | public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, Func<int, Dt_Task?> transferCheck, ILogger logger) |
| | | { |
| | | _taskService = taskService; |
| | | _routerService = routerService; |
| | | _transferCheck = transferCheck; |
| | | _logger = logger; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 选择合适的任务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据堆垛机的上一任务类型和当前状态,选择下一个应该执行的任务。 |
| | | /// |
| | | /// 选择策略: |
| | | /// 1. 如果没有上一任务类型,查询普通任务 |
| | | /// 2. 如果上一任务是出库,优先查入库任务,再查出库任务 |
| | | /// 3. 如果上一任务是入库,优先查出库任务 |
| | | /// 4. 对于出库任务,需要判断站台是否可用 |
| | | /// </remarks> |
| | | /// <param name="commonStackerCrane">堆垛机设备对象</param> |
| | | /// <returns>选中的任务,如果没有可用任务返回 null</returns> |
| | | public Dt_Task? SelectTask(IStackerCrane commonStackerCrane) |
| | | { |
| | | Dt_Task? candidateTask; |
| | | if (commonStackerCrane.LastTaskType == null) |
| | | var deviceCode = commonStackerCrane.DeviceCode; |
| | | |
| | | //_logger.LogInformation("SelectTask:开始选择任务,设备: {DeviceCode},上一任务类型: {LastTaskType}", deviceCode, commonStackerCrane.LastTaskType); |
| | | //QuartzLogger.Info($"开始选择任务,设备: {deviceCode},上一任务类型: {commonStackerCrane.LastTaskType}", deviceCode); |
| | | |
| | | // 根据上一任务类型决定查询策略 |
| | | if (commonStackerCrane.LastTaskType == null || commonStackerCrane.LastTaskType == TaskRelocationTypeEnum.Relocation.GetHashCode()) |
| | | { |
| | | candidateTask = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode); |
| | | // 没有上一任务类型,查询普通任务 |
| | | candidateTask = _taskService.QueryStackerCraneTask(deviceCode); |
| | | QuartzLogHelper.LogDebug(_logger, "SelectTask:查询普通任务,设备: {DeviceCode},结果: {TaskNum}", $"查询普通任务,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode, deviceCode, candidateTask?.TaskNum); |
| | | } |
| | | else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup) |
| | | { |
| | | candidateTask = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode); |
| | | candidateTask ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode); |
| | | // 上一任务是出库,优先查入库任务 |
| | | candidateTask = _taskService.QueryStackerCraneInTask(deviceCode); |
| | | // 如果没有入库任务,再查一下出库任务 |
| | | candidateTask ??= _taskService.QueryStackerCraneOutTask(deviceCode); |
| | | QuartzLogHelper.LogDebug(_logger, "SelectTask:出库后优先查入库,设备: {DeviceCode},结果: {TaskNum}", $"出库后优先查入库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode, deviceCode, candidateTask?.TaskNum); |
| | | } |
| | | else |
| | | { |
| | | candidateTask = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode); |
| | | // 上一任务是入库(非出库),优先查出库任务 |
| | | candidateTask = _taskService.QueryStackerCraneOutTask(deviceCode); |
| | | QuartzLogHelper.LogDebug(_logger, "SelectTask:入库后优先查出库,设备: {DeviceCode},结果: {TaskNum}", $"入库后优先查出库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode, deviceCode, candidateTask?.TaskNum); |
| | | } |
| | | |
| | | // 如果没有候选任务,返回 null |
| | | if (candidateTask == null) |
| | | { |
| | | QuartzLogHelper.LogDebug(_logger, "SelectTask:没有候选任务,设备: {DeviceCode}", $"没有候选任务,设备: {deviceCode}", deviceCode, deviceCode); |
| | | return null; |
| | | } |
| | | |
| | | // 如果不是出库任务,直接返回 |
| | | if (candidateTask.TaskType.GetTaskTypeGroup() != TaskTypeGroup.OutbondGroup) |
| | | { |
| | | QuartzLogHelper.LogInfo(_logger, "SelectTask:选中非出库任务,设备: {DeviceCode},任务号: {TaskNum},任务类型: {TaskType}", $"选中非出库任务,任务号: {candidateTask.TaskNum},任务类型: {candidateTask.TaskType}", deviceCode, deviceCode, candidateTask.TaskNum, candidateTask.TaskType); |
| | | return candidateTask; |
| | | } |
| | | |
| | | // 尝试选择出库任务(可能需要移库检查和站台可用性判断) |
| | | Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogInfo(_logger, "SelectTask:选中出库任务,设备: {DeviceCode},任务号: {TaskNum}", $"选中出库任务,任务号: {selectedTask.TaskNum}", deviceCode, deviceCode, selectedTask.TaskNum); |
| | | return selectedTask; |
| | | } |
| | | |
| | | // 查找其他可用的出库站台 |
| | | var otherOutStationCodes = _routerService |
| | | .QueryNextRoutes(commonStackerCrane.DeviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | | .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType) |
| | | .Select(x => x.ChildPosi) |
| | | .ToList(); |
| | | |
| | | var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes); |
| | | // 查询其他站台的出库任务 |
| | | var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes); |
| | | foreach (var alternativeTask in tasks) |
| | | { |
| | | selectedTask = TrySelectOutboundTask(alternativeTask); |
| | | if (selectedTask != null) |
| | | { |
| | | QuartzLogHelper.LogInfo(_logger, "SelectTask:选中备选出库任务,设备: {DeviceCode},任务号: {TaskNum}", $"选中备选出库任务,任务号: {selectedTask.TaskNum}", deviceCode, deviceCode, selectedTask.TaskNum); |
| | | return selectedTask; |
| | | } |
| | | } |
| | | |
| | | return _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode); |
| | | // 没有可用出库任务,尝试返回入库任务 |
| | | var inboundTask = _taskService.QueryStackerCraneInTask(deviceCode); |
| | | QuartzLogHelper.LogInfo(_logger, "SelectTask:返回入库任务,设备: {DeviceCode},任务号: {TaskNum}", $"返回入库任务,任务号: {inboundTask?.TaskNum}", deviceCode, deviceCode, inboundTask?.TaskNum); |
| | | return inboundTask; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 尝试选择出库任务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 对候选出库任务进行: |
| | | /// 1. 移库检查(调用 WMS 判断是否需要移库) |
| | | /// 2. 站台可用性判断 |
| | | /// |
| | | /// 如果任务被判定为需要移库,则返回移库后的任务。 |
| | | /// </remarks> |
| | | /// <param name="outboundTask">候选出库任务</param> |
| | | /// <returns>可选中的任务,或 null(站台不可用)</returns> |
| | | private Dt_Task? TrySelectOutboundTask(Dt_Task outboundTask) |
| | | { |
| | | // 只要是出库任务,必须先调用WMS判断是否需要移库。 |
| | | // 对于所有出库任务,必须先调用 WMS 判断是否需要移库 |
| | | var taskAfterTransferCheck = _transferCheck(outboundTask.TaskNum) ?? outboundTask; |
| | | var taskGroup = taskAfterTransferCheck.TaskType.GetTaskTypeGroup(); |
| | | |
| | | // 如果是移库任务或出库任务,尝试从 WMS 添加任务 |
| | | if (taskGroup == TaskTypeGroup.RelocationGroup || taskGroup == TaskTypeGroup.OutbondGroup) |
| | | { |
| | | TryAddTaskFromWms(taskAfterTransferCheck); |
| | | } |
| | | |
| | | // 如果是移库任务,直接返回 |
| | | if (taskGroup == TaskTypeGroup.RelocationGroup) |
| | | { |
| | | return taskAfterTransferCheck; |
| | | } |
| | | |
| | | // 如果不是出库任务,返回原任务 |
| | | if (taskGroup != TaskTypeGroup.OutbondGroup) |
| | | { |
| | | return taskAfterTransferCheck; |
| | | } |
| | | |
| | | // 判断出库站台是否可用 |
| | | return IsOutTaskStationAvailable(taskAfterTransferCheck) ? taskAfterTransferCheck : null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 调用 WMS 检查移库 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 通过 HTTP 请求调用 WMS 的移库检查接口。 |
| | | /// </remarks> |
| | | /// <param name="httpClientHelper">HTTP 客户端帮助类</param> |
| | | /// <param name="taskNum">任务号</param> |
| | | /// <returns>如果需要移库返回移库任务,否则返回 null</returns> |
| | | private static Dt_Task? QueryTransferTask(HttpClientHelper httpClientHelper, int taskNum) |
| | | { |
| | | // 调用 WMS 的移库检查接口 |
| | | var response = httpClientHelper.Post<WebResponseContent>( |
| | | nameof(ConfigKey.TransferCheck), |
| | | taskNum.ToString()); |
| | | |
| | | // 检查响应是否成功 |
| | | if (response == null || !response.IsSuccess || response.Data == null || !response.Data.Status || response.Data.Data == null) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | // 解析返回的任务数据 |
| | | var taskJson = response.Data.Data.ToString(); |
| | | return string.IsNullOrWhiteSpace(taskJson) ? null : JsonConvert.DeserializeObject<Dt_Task>(taskJson); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 尝试从 WMS 添加任务 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 如果任务不存在于本地数据库,从 WMS 返回的数据添加到本地。 |
| | | /// </remarks> |
| | | /// <param name="task">任务对象</param> |
| | | private void TryAddTaskFromWms(Dt_Task task) |
| | | { |
| | | // 检查任务号是否有效 |
| | | if (task.TaskNum <= 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 检查任务是否已存在 |
| | | var existingTask = _taskService.QueryByTaskNum(task.TaskNum); |
| | | if (existingTask != null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 添加到本地数据库 |
| | | _taskService.AddData(task); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 判断出库站台是否可用 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 检查目标站台对应的输送线是否被占用。 |
| | | /// 如果站台上有货物,则该站台不可用。 |
| | | /// </remarks> |
| | | /// <param name="task">出库任务</param> |
| | | /// <returns>站台可用返回 true</returns> |
| | | private bool IsOutTaskStationAvailable([NotNull] Dt_Task task) |
| | | { |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType); |
| | | // 确定任务类型 |
| | | int taskType = 0; |
| | | if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty) |
| | | { |
| | | // 空托盘出库 |
| | | taskType = StackerCraneConst.EmptyPalletTaskType; |
| | | } |
| | | else |
| | | taskType = task.TaskType; |
| | | |
| | | // 查询站台路由信息 |
| | | Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, taskType); |
| | | if (router == null) |
| | | { |
| | | // 未找到站台路由信息 |
| | | QuartzLogHelper.LogWarn(_logger, "IsOutTaskStationAvailable:未找到站台路由信息,站台: {NextAddress},任务号: {TaskNum}", $"IsOutTaskStationAvailable:未找到站台路由信息,站台: {task.NextAddress}", task.Roadway, task.NextAddress, task.TaskNum); |
| | | _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台"); |
| | | return false; |
| | | } |
| | | |
| | | // 查找站台对应的设备 |
| | | IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode); |
| | | if (device == null) |
| | | { |
| | | // 未找到设备 |
| | | QuartzLogHelper.LogWarn(_logger, "IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {ChildPosiDeviceCode},任务号: {TaskNum}", $"IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {router.ChildPosiDeviceCode}", task.Roadway, router.ChildPosiDeviceCode, task.TaskNum); |
| | | _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用"); |
| | | return false; |
| | | } |
| | | |
| | | // 转换为输送线设备 |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)device; |
| | | return conveyorLine.IsOccupied(router.ChildPosi); |
| | | |
| | | // 检查站台是否被占用 |
| | | bool isOccupied = conveyorLine.IsOccupied(router.ChildPosi); |
| | | QuartzLogHelper.LogInfo(_logger, "IsOutTaskStationAvailable:站台 {ChildPosi},是否被占用: {IsOccupied},任务号: {TaskNum}", $"IsOutTaskStationAvailable:站台 {router.ChildPosi},是否被占用: {!isOccupied}", task.Roadway, router.ChildPosi, !isOccupied, task.TaskNum); |
| | | |
| | | return isOccupied; |
| | | } |
| | | } |
| | | } |
| | | } |