wanshenmean
2026-03-26 8e42d0c1b7ae36cff2e7c69999117911a4b6f300
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;
using WIDESEA_Core;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -10,80 +13,277 @@
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;
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService)
        /// <summary>
        /// 移库检查委托函数
        /// </summary>
        /// <remarks>
        /// 用于调用 WMS 判断出库任务是否需要先执行移库。
        /// </remarks>
        private readonly Func<int, Dt_Task?> _transferCheck;
        /// <summary>
        /// 构造函数(使用 HTTP 客户端帮助类)
        /// </summary>
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper)
            : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum))
        {
        }
        /// <summary>
        /// 构造函数(使用委托函数)
        /// </summary>
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="transferCheck">移库检查函数</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, Func<int, Dt_Task?> transferCheck)
        {
            _taskService = taskService;
            _routerService = routerService;
            _transferCheck = transferCheck;
        }
        /// <summary>
        /// 选择合适的任务
        /// </summary>
        /// <remarks>
        /// 根据堆垛机的上一任务类型和当前状态,选择下一个应该执行的任务。
        ///
        /// 选择策略:
        /// 1. 如果没有上一任务类型,查询普通任务
        /// 2. 如果上一任务是出库,优先查入库任务,再查出库任务
        /// 3. 如果上一任务是入库,优先查出库任务
        /// 4. 对于出库任务,需要判断站台是否可用
        /// </remarks>
        /// <param name="commonStackerCrane">堆垛机设备对象</param>
        /// <returns>选中的任务,如果没有可用任务返回 null</returns>
        public Dt_Task? SelectTask(IStackerCrane commonStackerCrane)
        {
            Dt_Task? task;
            Dt_Task? candidateTask;
            // 根据上一任务类型决定查询策略
            if (commonStackerCrane.LastTaskType == null)
            {
                task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
                // 没有上一任务类型,查询普通任务
                candidateTask = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
            }
            else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                // 上一任务是出库,优先查入库任务
                candidateTask = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                // 如果没有入库任务,再查一下出库任务
                candidateTask ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            else
            {
                task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                // 上一任务是入库(非出库),优先查出库任务
                candidateTask = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            // 如果没有候选任务,返回 null
            if (candidateTask == null)
            {
                if (IsOutTaskStationAvailable(task))
                {
                    return task;
                }
                var otherOutStationCodes = _routerService
                    .QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
                    .Select(x => x.ChildPosi)
                    .ToList();
                var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
                foreach (var alternativeTask in tasks)
                {
                    if (IsOutTaskStationAvailable(alternativeTask))
                    {
                        return alternativeTask;
                    }
                }
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                return null;
            }
            return task;
            // 如果不是出库任务,直接返回
            if (candidateTask.TaskType.GetTaskTypeGroup() != TaskTypeGroup.OutbondGroup)
            {
                return candidateTask;
            }
            // 尝试选择出库任务(可能需要移库检查和站台可用性判断)
            Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
            if (selectedTask != null)
            {
                return selectedTask;
            }
            // 查找其他可用的出库站台
            var otherOutStationCodes = _routerService
                .QueryNextRoutes(commonStackerCrane.DeviceCode, candidateTask.NextAddress, candidateTask.TaskType)
                .Select(x => x.ChildPosi)
                .ToList();
            // 查询其他站台的出库任务
            var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
            foreach (var alternativeTask in tasks)
            {
                selectedTask = TrySelectOutboundTask(alternativeTask);
                if (selectedTask != null)
                {
                    return selectedTask;
                }
            }
            // 没有可用出库任务,尝试返回入库任务
            return _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
        }
        /// <summary>
        /// 尝试选择出库任务
        /// </summary>
        /// <remarks>
        /// 对候选出库任务进行:
        /// 1. 移库检查(调用 WMS 判断是否需要移库)
        /// 2. 站台可用性判断
        ///
        /// 如果任务被判定为需要移库,则返回移库后的任务。
        /// </remarks>
        /// <param name="outboundTask">候选出库任务</param>
        /// <returns>可选中的任务,或 null(站台不可用)</returns>
        private Dt_Task? TrySelectOutboundTask(Dt_Task outboundTask)
        {
            // 对于所有出库任务,必须先调用 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 = 100;
            }
            else
                taskType = task.TaskType;
            // 查询站台路由信息
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, taskType);
            if (router == null)
            {
                // 未找到站台路由信息
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
            // 查找站台对应的设备
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
            if (device == null)
            {
                // 未找到设备
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
            // 转换为输送线设备
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            // 检查站台是否被占用
            return conveyorLine.IsOccupied(router.ChildPosi);
        }
    }