wanshenmean
20 小时以前 2e7937ca43b0712ddf96b29b04cc7baf34fa1305
feat: 添加托盘库存明细查询功能并优化机械手任务处理

- 在ConfigKey枚举中添加GetStockDetailCount配置项
- 实现根据托盘号查询库存明细数量的接口和服务
- 优化TcpSocketServer的消息去重逻辑,改为按客户端记录
- 在CommonConveyorLineNewJob中增加对11001和11010设备的空托盘入库处理
- 重构RobotTaskService,添加根据库存明细数量动态设置任务总数功能
已修改9个文件
175 ■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs
@@ -92,7 +92,12 @@
        /// <summary>
        /// 批量组盘确认
        /// </summary>
        GroupPalletConfirm
        GroupPalletConfirm,
        /// <summary>
        /// 根据托盘号查询库存明细数量
        /// </summary>
        GetStockDetailCount
        #endregion WMS接口
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -25,6 +25,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using WIDESEA_Core;
using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
@@ -205,14 +206,20 @@
                    RobotTargetAddress = section[1]!,
                    RobotSourceAddressLineCode = stock?.SourceLineNo ?? string.Empty,
                    RobotTargetAddressLineCode = stock?.TargetLineNo ?? string.Empty,
                    RobotRoadway = stock?.TargetLineNo == "11068" ? "注液组盘机械手" : "换盘机械手" ?? string.Empty, // todo
                    RobotRoadway = stock?.TargetLineNo switch
                    {
                        "11068" => "注液组盘机械手",
                        "1000" => "拆盘机械手",
                        "2000" => "成品组盘机械手",
                        _ => "换盘机械手"
                    },
                    RobotSourceAddressPalletCode = stock?.SourcePalletNo ?? string.Empty,
                    RobotTargetAddressPalletCode = stock?.TargetPalletNo ?? string.Empty,
                    RobotTaskType = taskType,
                    RobotTaskState = (int)TaskRobotStatusEnum.RobotNew,
                    RobotGrade = task.Grade,
                    Creater = "WCS_Local",
                    RobotTaskTotalNum = taskType == (int)RobotTaskTypeEnum.GroupPallet ? 48 : 1,
                    RobotTaskTotalNum = GetRobotTaskTotalNum(taskType, stock.SourcePalletNo),
                    CreateDate = DateTime.Now
                };
@@ -233,6 +240,38 @@
        }
        /// <summary>
        /// 获取机械手任务总数量。
        /// 组盘任务固定48,换盘和拆盘任务通过托盘号查询WMS库存明细数量。
        /// </summary>
        private int GetRobotTaskTotalNum(int taskType, string? palletCode)
        {
            if (taskType == (int)RobotTaskTypeEnum.GroupPallet)
                return 48;
            if (string.IsNullOrWhiteSpace(palletCode))
                return 1;
            try
            {
                string url = $"{BaseAPI.WMSBaseUrl}Stock/GetStockDetailCount?palletCode={Uri.EscapeDataString(palletCode)}";
                var result = _httpClientHelper.Get(url);
                if (!result.IsSuccess || string.IsNullOrEmpty(result.Content))
                    return 1;
                var response = JsonConvert.DeserializeObject<WebResponseContent>(result.Content);
                if (response == null || !response.Status)
                    return 1;
                var detailCount = response.Data?.GetType().GetProperty("DetailCount")?.GetValue(response.Data);
                return detailCount is int count and > 0 ? count : 1;
            }
            catch
            {
                return 1;
            }
        }
        /// <summary>
        /// 将配置键映射到机械手任务类型枚举值。
        /// </summary>
        /// <param name="configKey">配置键名称</param>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -6,6 +6,7 @@
using Serilog;
using SqlSugar;
using WIDESEA_Core;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
@@ -148,15 +149,6 @@
                        return Task.CompletedTask;
                    }
                    // 创建并行选项,限制最大并发数
                    //var parallelOptions = new ParallelOptions
                    //{
                    //    // 限制并发数:子设备数量和 CPU 核心数*2 的较小值
                    //    MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2),
                    //};
                    // 并行处理每个子设备
                    //Parallel.For(0, childDeviceCodes.Count, parallelOptions, i =>
                    foreach (var childDeviceCode in childDeviceCodes)
                    {
                        //string childDeviceCode = childDeviceCodes[i];
@@ -274,15 +266,15 @@
                                    QuartzLogHelper.LogInfo(_logger, $"处理任务 {task.TaskNum},状态: {task.TaskStatus}", conveyorLine.DeviceCode);
                                    // 处理任务状态(根据状态分发到不同方法)
                                    ProcessTaskState(conveyorLine, command, task, childDeviceCode);
                                    return Task.CompletedTask;
                                    continue;
                                }
                                else if (!command.Barcode.IsNullOrEmpty() && childDeviceCode == "11068")
                                {
                                    var isWcsTask = _taskService.Db.Queryable<Dt_Task>().Any(x => x.PalletCode == command.Barcode && x.TaskStatus == (int)TaskOutStatusEnum.OutNew);
                                    var isRobotTask = _robotTaskService.Db.Queryable<Dt_RobotTask>().Any(x => x.RobotTargetAddressPalletCode == command.Barcode);
                                    var isWcsTask = _taskService.Db.Queryable<Dt_Task>().Any(x => x.PalletCode == command.Barcode && (x.TaskStatus == (int)TaskOutStatusEnum.OutNew || x.TaskStatus == (int)TaskInStatusEnum.InNew));
                                    var isRobotTask = _robotTaskService.Db.Queryable<Dt_RobotTask>().Any(x => x.RobotTargetAddressPalletCode == command.Barcode || x.RobotRoadway == "注液组盘机械手");
                                    if (isWcsTask || isRobotTask)
                                    {
                                        return Task.CompletedTask;
                                        continue;
                                    }
                                    // 直接添加机械手组盘任务
@@ -306,6 +298,48 @@
                                        conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode);
                                    }
                                }
                                else if (!command.Barcode.IsNullOrEmpty() && (childDeviceCode == "11001" || childDeviceCode == "11010"))
                                {
                                    var isWcsTask = _taskService.Db.Queryable<Dt_Task>().Any(x => x.PalletCode == command.Barcode && (x.TaskStatus == (int)TaskOutStatusEnum.OutNew || x.TaskStatus == (int)TaskInStatusEnum.InNew));
                                    var isRobotTask = _robotTaskService.Db.Queryable<Dt_RobotTask>().Any(x => x.RobotTargetAddressPalletCode == command.Barcode);
                                    if (isWcsTask || isRobotTask)
                                    {
                                        continue;
                                    }
                                    // 调用 WMS 创建空托盘入库任务
                                    string configKey = nameof(ConfigKey.CreateTaskInboundAsync);
                                    string requestParam = new CreateTaskDto()
                                    {
                                        PalletCode = command.Barcode,
                                        SourceAddress = childDeviceCode,
                                        TargetAddress = "GWSC1",  // 目标地址
                                        Roadway = "GWSC1",             // 巷道
                                        WarehouseId = 1,                   // 仓库 ID
                                        PalletType = 1,                             // 托盘类型(默认为1)
                                        TaskType = TaskTypeEnum.InEmpty.GetHashCode()                         // 任务类型(入库/空托盘入库)
                                    }.Serialize();
                                    DateTime startTime = DateTime.Now;
                                    var responseResult = _httpClientHelper.Post<WebResponseContent>(configKey, requestParam);
                                    if (responseResult.IsSuccess && responseResult.Data.Status)
                                    {
                                        QuartzLogHelper.LogInfo(_logger, $"调用WMS接口成功,接口:【{configKey}】,请求参数:【{requestParam}】,响应数据:【{responseResult.Data?.Data}】,耗时:{(DateTime.Now - startTime).TotalMilliseconds}ms", conveyorLine.DeviceCode);
                                        var wmsTask = JsonConvert.DeserializeObject<WMSTaskDTO>(responseResult?.Data?.Data?.ToString());
                                        List<WMSTaskDTO> taskDTOs = new List<WMSTaskDTO> { wmsTask };
                                        if (wmsTask == null) continue;
                                        if (_taskService.ReceiveWMSTask(taskDTOs).Status)
                                        {
                                            conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode);
                                        }
                                    }
                                    else
                                    {
                                        QuartzLogHelper.LogError(_logger, $"调用WMS接口失败,接口:【{configKey}】,请求参数:【{requestParam}】,错误信息:【{responseResult.Data?.Message}】", conveyorLine.DeviceCode);
                                    }
                                }
                            }
                        }
                        catch (Exception innerEx)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
@@ -60,11 +60,15 @@
                            break;
                        }
                        if(message == lastMessage)
                        // 按客户端去重:检查是否与该客户端上次消息相同
                        lock (_syncRoot)
                        {
                            // 重复消息,忽略
                            if (_clientLastMessage.TryGetValue(clientId, out var prev) && message == prev)
                            {
                            continue;
                        }
                            _clientLastMessage[clientId] = message;
                        }
                        // 更新客户端状态
                        UpdateClientStatus(clientId, message);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs
@@ -161,6 +161,9 @@
                // 移除活跃时间记录
                _clientLastActive.Remove(clientId);
                // 移除上次消息记录
                _clientLastMessage.Remove(clientId);
                // 移除编码记录
                _clientEncodings.Remove(clientId);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs
@@ -1,13 +1,6 @@
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks.SocketServer
{
@@ -137,9 +130,13 @@
        public bool IsRunning { get; private set; }
        /// <summary>
        /// 上次接收消息源
        /// 每个客户端上次接收的消息,用于去重
        /// </summary>
        public string lastMessage;
        /// <remarks>
        /// Key: 客户端 ID
        /// Value: 上次接收的消息内容
        /// </remarks>
        public readonly Dictionary<string, string> _clientLastMessage = new();
        /// <summary>
        /// 消息接收事件
@@ -200,6 +197,7 @@
        /// <param name="message">日志消息</param>
        private void Log(string message)
        {
            //Logger.None.Information(message);
            Console.WriteLine(message);
            try { File.AppendAllText(_logFile, message + Environment.NewLine); } catch { }
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
@@ -71,5 +71,12 @@
        /// <param name="deviceName">设备名称(用于动态MES凭证查询)</param>
        /// <returns>操作结果</returns>
        Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName);
        /// <summary>
        /// 根据托盘号查询库存明细数量
        /// </summary>
        /// <param name="palletCode">托盘号</param>
        /// <returns>库存明细数量</returns>
        Task<WebResponseContent> GetStockDetailCountByPalletCodeAsync(string palletCode);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -584,6 +584,26 @@
        }
        /// <summary>
        /// 根据托盘号查询库存明细数量
        /// </summary>
        /// <param name="palletCode">托盘号</param>
        /// <returns>库存明细数量</returns>
        public async Task<WebResponseContent> GetStockDetailCountByPalletCodeAsync(string palletCode)
        {
            WebResponseContent content = new WebResponseContent();
            if (string.IsNullOrWhiteSpace(palletCode))
                return content.Error("托盘号不能为空");
            var stockInfo = StockInfoService.Repository.QueryFirst(s => s.PalletCode == palletCode);
            if (stockInfo == null)
                return content.Error("托盘不存在");
            var count = await StockInfoDetailService.Repository.Db.Queryable<Dt_StockInfoDetail>()
                .CountAsync(d => d.StockId == stockInfo.Id);
            return content.OK("查询成功", new { PalletCode = palletCode, DetailCount = count });
        }
        /// <summary>
        /// 根据设备名称和托盘号解析MES设备配置
        /// </summary>
        /// <param name="deviceName">设备名称</param>
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
@@ -85,5 +85,16 @@
        {
            return await Service.GroupPalletConfirmAsync(dto.PalletCode, dto.DeviceName);
        }
        /// <summary>
        /// 根据托盘号查询库存明细数量
        /// </summary>
        /// <param name="palletCode">托盘号</param>
        /// <returns>库存明细数量</returns>
        [HttpGet("GetStockDetailCount"), AllowAnonymous]
        public async Task<WebResponseContent> GetStockDetailCount(string palletCode)
        {
            return await Service.GetStockDetailCountByPalletCodeAsync(palletCode);
        }
    }
}