wanshenmean
5 天以前 5171d3f59b89389bf75293afd210cfa6de4ccff7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
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;
        }
    }
}