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;
|
using WIDESEAWCS_QuartzJob.Models;
|
using WIDESEAWCS_QuartzJob.Service;
|
|
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;
|
|
/// <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)
|
{
|
}
|
|
/// <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;
|
var deviceCode = commonStackerCrane.DeviceCode;
|
|
//_logger.LogInformation("SelectTask:开始选择任务,设备: {DeviceCode},上一任务类型: {LastTaskType}", deviceCode, commonStackerCrane.LastTaskType);
|
//QuartzLogger.Info($"开始选择任务,设备: {deviceCode},上一任务类型: {commonStackerCrane.LastTaskType}", deviceCode);
|
|
// 根据上一任务类型决定查询策略
|
if (commonStackerCrane.LastTaskType == null)
|
{
|
// 没有上一任务类型,查询普通任务
|
candidateTask = _taskService.QueryStackerCraneTask(deviceCode);
|
_logger.LogDebug("SelectTask:查询普通任务,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
|
QuartzLogger.Debug($"查询普通任务,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
|
}
|
else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
|
{
|
// 上一任务是出库,优先查入库任务
|
candidateTask = _taskService.QueryStackerCraneInTask(deviceCode);
|
// 如果没有入库任务,再查一下出库任务
|
candidateTask ??= _taskService.QueryStackerCraneOutTask(deviceCode);
|
_logger.LogDebug("SelectTask:出库后优先查入库,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
|
QuartzLogger.Debug($"出库后优先查入库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
|
}
|
else
|
{
|
// 上一任务是入库(非出库),优先查出库任务
|
candidateTask = _taskService.QueryStackerCraneOutTask(deviceCode);
|
_logger.LogDebug("SelectTask:入库后优先查出库,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
|
QuartzLogger.Debug($"入库后优先查出库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
|
}
|
|
// 如果没有候选任务,返回 null
|
if (candidateTask == null)
|
{
|
_logger.LogDebug("SelectTask:没有候选任务,设备: {DeviceCode}", deviceCode);
|
QuartzLogger.Debug($"没有候选任务,设备: {deviceCode}", deviceCode);
|
return null;
|
}
|
|
// 如果不是出库任务,直接返回
|
if (candidateTask.TaskType.GetTaskTypeGroup() != TaskTypeGroup.OutbondGroup)
|
{
|
_logger.LogInformation("SelectTask:选中非出库任务,设备: {DeviceCode},任务号: {TaskNum},任务类型: {TaskType}", deviceCode, candidateTask.TaskNum, candidateTask.TaskType);
|
QuartzLogger.Info($"选中非出库任务,任务号: {candidateTask.TaskNum},任务类型: {candidateTask.TaskType}", deviceCode);
|
return candidateTask;
|
}
|
|
// 尝试选择出库任务(可能需要移库检查和站台可用性判断)
|
Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
|
if (selectedTask != null)
|
{
|
_logger.LogInformation("SelectTask:选中出库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, selectedTask.TaskNum);
|
QuartzLogger.Info($"选中出库任务,任务号: {selectedTask.TaskNum}", deviceCode);
|
return selectedTask;
|
}
|
|
// 查找其他可用的出库站台
|
var otherOutStationCodes = _routerService
|
.QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
|
.Select(x => x.ChildPosi)
|
.ToList();
|
|
// 查询其他站台的出库任务
|
var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes);
|
foreach (var alternativeTask in tasks)
|
{
|
selectedTask = TrySelectOutboundTask(alternativeTask);
|
if (selectedTask != null)
|
{
|
_logger.LogInformation("SelectTask:选中备选出库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, selectedTask.TaskNum);
|
QuartzLogger.Info($"选中备选出库任务,任务号: {selectedTask.TaskNum}", deviceCode);
|
return selectedTask;
|
}
|
}
|
|
// 没有可用出库任务,尝试返回入库任务
|
var inboundTask = _taskService.QueryStackerCraneInTask(deviceCode);
|
_logger.LogInformation("SelectTask:返回入库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, inboundTask?.TaskNum);
|
QuartzLogger.Info($"返回入库任务,任务号: {inboundTask?.TaskNum}", deviceCode);
|
return inboundTask;
|
}
|
|
/// <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)
|
{
|
// 确定任务类型
|
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)
|
{
|
// 未找到站台路由信息
|
_logger.LogWarning("IsOutTaskStationAvailable:未找到站台路由信息,站台: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum);
|
QuartzLogger.Warn($"IsOutTaskStationAvailable:未找到站台路由信息,站台: {task.NextAddress}", task.Roadway);
|
_taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
|
return false;
|
}
|
|
// 查找站台对应的设备
|
IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
|
if (device == null)
|
{
|
// 未找到设备
|
_logger.LogWarning("IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {ChildPosiDeviceCode},任务号: {TaskNum}", router.ChildPosiDeviceCode, task.TaskNum);
|
QuartzLogger.Warn($"IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {router.ChildPosiDeviceCode}", task.Roadway);
|
_taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
|
return false;
|
}
|
|
// 转换为输送线设备
|
CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
|
|
// 检查站台是否被占用
|
bool isOccupied = conveyorLine.IsOccupied(router.ChildPosi);
|
_logger.LogInformation("IsOutTaskStationAvailable:站台 {ChildPosi},是否被占用: {IsOccupied},任务号: {TaskNum}", router.ChildPosi, isOccupied, task.TaskNum);
|
QuartzLogger.Info($"IsOutTaskStationAvailable:站台 {router.ChildPosi},是否被占用: {isOccupied}", task.Roadway);
|
|
return isOccupied;
|
}
|
}
|
}
|