wanshenmean
2 天以前 ff006f77f6267fc0d2c4ee810d897a85165f5b8f
Merge branch 'xiaoyang' into dev
已添加2个文件
已修改24个文件
673 ■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Siemens/SiemensDBDataType.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Siemens/SiemensS7Communicator.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/RobotTaskController.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/OutboundTimeConstants.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/TaskAddressConstants.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-29-outbound-task-flow-todo-implementation.md 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-29-outbound-task-flow-todo-design.md 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs
@@ -11,7 +11,7 @@
        /// <summary>
        /// WMS接口基础URL
        /// </summary>
        public const string WMSBaseUrl = "http://localhost:9291/api/";
        public const string WMSBaseUrl = "http://192.168.60.30:9291/api/";
        /// <summary>
        /// WCS接口基础URL
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Siemens/SiemensDBDataType.cs
@@ -112,6 +112,7 @@
                DataType_Char => TypeCode.Char,
                DataType_UInt => TypeCode.UInt16,
                DataType_UDInt => TypeCode.UInt32,
                DataType_ByteArray => TypeCode.SByte,
                _ => throw new CommunicationException($"数据类型错误:【{dataType}】", CommunicationErrorType.TypeError),
            };
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Siemens/SiemensS7Communicator.cs
@@ -358,6 +358,9 @@
                            return Encoding.Default.GetString((byte[])GetContent(plc.Read(address, length), address)).ToArray();
                        return (char)GetContent(plc.ReadByte(address), address);
                    case TypeCode.SByte:
                        return Encoding.Default.GetString((byte[])GetContent(plc.Read(address, 20), address)).ToArray();
                    default:
                        throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, typeCode.ToString(), address), CommunicationErrorType.TypeError);
                }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs
@@ -83,12 +83,28 @@
        WebResponseContent GetWMSRobotTask(Dt_Task task);
        /// <summary>
        ///
        /// </summary>
        /// <param name="taskType"></param>
        /// <param name="palletCode"></param>
        /// <returns></returns>
        int GetRobotTaskTotalNum(int taskType, string? palletCode);
        /// <summary>
        /// åœ¨æœ¬åœ°ç›´æŽ¥åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡ï¼Œä¸è°ƒç”¨WMS接口
        /// </summary>
        /// <param name="task">出库任务</param>
        /// <returns>操作结果</returns>
        WebResponseContent CreateLocalRobotTask(Dt_Task task);
        /// <summary>
        /// æ£€æŸ¥æºçº¿ä½“是否有托盘号,并根据结果创建机械手任务。
        /// æœ‰æ‰˜ç›˜å·æ—¶è°ƒç”¨ CreateLocalRobotTask,无托盘号时从 WMS èŽ·å–ä»»åŠ¡ã€‚
        /// </summary>
        /// <param name="task">出库任务实体</param>
        /// <returns>操作结果</returns>
        WebResponseContent CheckSourceLineAndCreateRobotTask(Dt_Task task);
        int MapWarehouseIdConfigKey(string? targetAddress);
        string ResolveRobotRuleValue(string? targetAddress, string addressSectionName, string? fallback);
        WebResponseContent CreateRobotTaskManually(ManualRobotTaskDto request);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs
@@ -459,11 +459,14 @@
        /// <returns></returns>
        private ITrigger CreateSimpleTrigger(DispatchInfoDTO sysSchedule)
        {
            // Quartz要求间隔至少1秒,防止数据库中IntervalSecond为0或负值导致ArgumentOutOfRangeException
            var intervalSeconds = sysSchedule.IntervalSecond <= 0 ? 1 : sysSchedule.IntervalSecond;
            ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup)
            .StartAt(sysSchedule.BeginTime.GetValueOrDefault())
            .WithSimpleSchedule(x => x
                .WithIntervalInSeconds(sysSchedule.IntervalSecond)
                .WithIntervalInSeconds(intervalSeconds)
                .RepeatForever()
            )
            .EndAt(sysSchedule.EndTime.GetValueOrDefault())
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs
@@ -82,6 +82,8 @@
        public IServer GetServer()
        {
            var endpoints = _connection.Value.GetEndPoints();
            if (endpoints == null || endpoints.Length == 0)
                throw new InvalidOperationException("Redis没有可用的终结点,请检查连接配置");
            return _connection.Value.GetServer(endpoints[0]);
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/RobotTaskController.cs
@@ -1,14 +1,9 @@
using Autofac.Core;
using Autofac.Core;
using Masuit.Tools;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.BaseController;
using WIDESEAWCS_Core.Enums;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ISystemServices;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -20,17 +15,24 @@
    {
        public RobotTaskController(IRobotTaskService service) : base(service)
        {
        }
        [HttpGet, HttpPost, Route("DeleteRobotTask"), AllowAnonymous]
        public WebResponseContent DeleteRobotTask(int id)
        {
            if (Service.DeleteRobotTask(id)){
            if (Service.DeleteRobotTask(id))
            {
                return WebResponseContent.Instance.OK();
            }
            return WebResponseContent.Instance.Error();
        }
        [HttpGet, HttpPost, Route("GetRobotTaskTotalNum"), AllowAnonymous]
        public int GetRobotTaskTotalNum( int taskType, string? palletCode)
        {
            return Service.GetRobotTaskTotalNum(taskType, palletCode);
        }
        // æ‰‹åŠ¨æœºæ¢°æ‰‹ä»»åŠ¡
        [HttpGet, HttpPost, Route("CreateRobotTaskManually"), AllowAnonymous]
        public WebResponseContent CreateRobotTaskManually([FromBody] ManualRobotTaskDto request) 
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -34,7 +34,7 @@
  //5.PostgreSQL
  "DBType": "SqlServer",
  //连接字符串
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  "ConnectionString": "Data Source=192.168.60.30;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //跨域
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs
@@ -107,7 +107,11 @@
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.Outbound)
            {
                return WebResponseContent.Instance.OK();
                //return WebResponseContent.Instance.OK();
                //return _robotTaskService.CheckSourceLineAndCreateRobotTask(task);
                // Todo:获取对向线体是否有托盘号,如果有托盘号直接生成机械手任务
                return GetWMSOutboundTrayTask(task);
            }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -22,10 +22,12 @@
using Masuit.Tools;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SqlSugar;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using WIDESEA_Core;
using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
@@ -75,6 +77,7 @@
            _taskExecuteDetailService = taskExecuteDetailService;
            _logger = logger;
        }
        public override WebResponseContent DeleteData(object[] keys)
        {
            List<int> taskKeys = new List<int>();
@@ -85,8 +88,8 @@
            List<Dt_RobotTask> tasks = BaseDal.QueryData(x => taskKeys.Contains(x.RobotTaskId));
            BaseDal.DeleteAndMoveIntoHty(tasks, OperateTypeEnum.人工删除);
            return WebResponseContent.Instance.OK($"成功删除{tasks.Count}条数据");
        }
        public bool DeleteRobotTask(int id)
        {
            Dt_RobotTask task = BaseDal.QueryFirst(x => x.RobotTaskId == id);
@@ -135,7 +138,7 @@
        public Dt_RobotTask? QueryRobotCraneTask(string deviceCode)
        {
            return BaseDal.QueryFirst(x => x.RobotRoadway == deviceCode && x.RobotTaskState != (int)TaskRobotStatusEnum.RobotExecuting, TaskOrderBy);
            return BaseDal.QueryFirst(x => x.RobotRoadway == deviceCode, TaskOrderBy);
        }
        public Dt_RobotTask? QueryRobotCraneExecutingTask(string deviceCode)
@@ -242,10 +245,108 @@
        }
        /// <summary>
        /// æ£€æŸ¥æºçº¿ä½“是否有托盘号,并根据结果创建机械手任务。
        /// </summary>
        /// <param name="task">出库任务实体</param>
        /// <returns>
        /// æœ‰æ‰˜ç›˜å·æ—¶è¿”回 CreateLocalRobotTask ç»“果;
        /// æ— æ‰˜ç›˜å·æ—¶è¿”回 GetWMSOutboundTrayTask ç»“果。
        /// </returns>
        public WebResponseContent CheckSourceLineAndCreateRobotTask(Dt_Task task)
        {
            // 1. èŽ·å–æºçº¿ä½“ç¼–å·ï¼ˆå¤ç”¨å·²æœ‰é€»è¾‘ï¼‰
            string configKey = ResolveRobotTaskConfigKey(task.TargetAddress);
            StockDTO stock = BuildRobotTaskStock(task, configKey);
            string sourceLineNo = stock.SourceLineNo;
            if (string.IsNullOrWhiteSpace(sourceLineNo))
            {
                return GetWMSOutboundTrayTaskFromWMS(task);
            }
            // 2. é€šè¿‡è®¾å¤‡é€šä¿¡è¯»å–线体托盘号
            string? palletCode = ReadLineBarcode(sourceLineNo);
            if (!string.IsNullOrWhiteSpace(palletCode))
            {
                // æœ‰æ‰˜ç›˜å·ï¼Œæœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡
                return CreateLocalRobotTask(task);
            }
            // æ— æ‰˜ç›˜å·ï¼Œä»Ž WMS èŽ·å–ä»»åŠ¡
            return GetWMSOutboundTrayTaskFromWMS(task);
        }
        /// <summary>
        /// ä»ŽWMS获取空托盘出库任务。
        /// </summary>
        /// <param name="task">任务实体。</param>
        /// <returns>调用结果。</returns>
        private WebResponseContent GetWMSOutboundTrayTaskFromWMS(Dt_Task task)
        {
            int warehouseId = MapWarehouseIdConfigKey(task.TargetAddress);
            string sourceLineNo = ResolveRobotRuleValue(task.TargetAddress, "AddressSourceLineNoMap", task.TargetAddress);
            string configKey = nameof(ConfigKey.GetOutBoundTrayTaskAsync);
            string requestParam = new CreateTaskDto { WarehouseId = warehouseId, TargetAddress = sourceLineNo }.ToJson();
            DateTime startTime = DateTime.Now;
            var result = _httpClientHelper.Post<WebResponseContent>(configKey, requestParam);
            if (!result.IsSuccess || !result.Data.Status)
            {
                QuartzLogHelper.LogError(_logger, $"调用WMS接口失败,接口:【{configKey}】,请求参数:【{requestParam}】,错误信息:【{result.Data?.Message}】", "RobotTaskService");
                return WebResponseContent.Instance.Error($"获取WMS系统空托盘出库任务失败,任务号:【{task.TaskNum}】,托盘号:【{task.PalletCode}】,错误信息:【{result.Data?.Message}】");
            }
            QuartzLogHelper.LogInfo(_logger, $"调用WMS接口成功,接口:【{configKey}】,响应数据:【{result.Data?.Data}】,耗时:{(DateTime.Now - startTime).TotalMilliseconds}ms", "RobotTaskService");
            WMSTaskDTO? wMSTask = JsonConvert.DeserializeObject<WMSTaskDTO>(result.Data.Data?.ToString() ?? string.Empty);
            if (wMSTask == null)
                return WebResponseContent.Instance.Error($"获取WMS系统空托盘出库任务失败,任务号:【{task.TaskNum}】,托盘号:【{task.PalletCode}】,错误信息:【WMS未返回有效任务数据】");
            // æž„建StockDTO并调用ReceiveWMSTask创建本地入库任务
            var stockDto = new StockDTO
            {
                Roadway = task.Roadway,
                SourceLineNo = sourceLineNo,
                TargetLineNo = task.TargetAddress,
                SourcePalletNo = string.Empty,
                TargetPalletNo = string.Empty
            };
            return ReceiveWMSTask(wMSTask, stockDto);
        }
        /// <summary>
        /// è¯»å–指定线体的托盘号。
        /// </summary>
        /// <param name="sourceLineNo">源线体编号</param>
        /// <returns>托盘号,如有异常返回 null</returns>
        private string? ReadLineBarcode(string sourceLineNo)
        {
            try
            {
                IDevice? device = Storage.Devices.FirstOrDefault(x =>
                    x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceLineNo));
                if (device == null)
                    return null;
                CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
                return conveyorLine.GetValue<ConveyorLineDBNameNew, string>(
                    ConveyorLineDBNameNew.Barcode, sourceLineNo);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, $"读取线体[{sourceLineNo}]托盘号异常");
                return null;
            }
        }
        /// <summary>
        /// èŽ·å–æœºæ¢°æ‰‹ä»»åŠ¡æ€»æ•°é‡ã€‚
        /// ç»„盘任务固定48,换盘和拆盘任务通过托盘号查询WMS库存明细数量。
        /// </summary>
        private int GetRobotTaskTotalNum(int taskType, string? palletCode)
        public int GetRobotTaskTotalNum(int taskType, string? palletCode)
        {
            if (taskType == (int)RobotTaskTypeEnum.GroupPallet)
                return 48;
@@ -255,8 +356,10 @@
            try
            {
                QuartzLogHelper.LogInfo(_logger, $"开始调用WMS接口获取库存明细数量,托盘号:【{palletCode}】", "RobotTaskService");
                string url = $"{BaseAPI.WMSBaseUrl}Stock/GetStockDetailCount?palletCode={Uri.EscapeDataString(palletCode)}";
                var result = _httpClientHelper.Get(url);
                QuartzLogHelper.LogInfo(_logger, $"调用WMS获取库存明细数量接口,请求URL:【{url}】,响应数据:【{result.Content}】,耗时:{result.Duration}ms", "RobotTaskService");
                if (!result.IsSuccess || string.IsNullOrEmpty(result.Content))
                    return 1;
@@ -264,8 +367,8 @@
                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;
                var detailCount = (response.Data as JObject)?["detailCount"]?.Value<int>();
                return detailCount.HasValue && detailCount.Value > 0 ? detailCount.Value : 1;
            }
            catch
            {
@@ -370,8 +473,10 @@
                    CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
                    DeviceProDTO? devicePro = conveyorLine.DeviceProDTOs.FirstOrDefault(x => x.DeviceProParamName == nameof(ConveyorLineDBNameNew.Barcode) && x.DeviceChildCode == sourceLineNo);
                    //conveyorLine.Communicator.Read(devicePro.DeviceProAddress, 20);
                    //ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(sourceLineNo);  // æµ‹è¯•用
                    var barcode = conveyorLine.GetValue<ConveyorLineDBNameNew, string>(ConveyorLineDBNameNew.Barcode, sourceLineNo);
                    var bytes = conveyorLine.Communicator.Read(devicePro.DeviceProAddress, 20);
                    var barcode = Encoding.Default.GetString(bytes).Trim();
                    stock.SourcePalletNo = string.IsNullOrEmpty(barcode) ? string.Empty : barcode;
                }
            }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -1,16 +1,11 @@
using MapsterMapper;
using Masuit.Tools;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Quartz;
using Serilog;
using SqlSugar;
using WIDESEA_Core;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -228,7 +223,11 @@
                            {
                                // å¦‚æžœ WCS_ACK ä¸º 1,先清除(表示处理过上一次请求)
                                if (command.WCS_ACK == 1)
                                    conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)0, childDeviceCode);
                                {
                                    conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (byte)0, childDeviceCode);
                                    //Thread.Sleep(300);
                                    //conveyorLine.SetValue(ConveyorLineDBNameNew.Target, (short)0, childDeviceCode);
                                }
                                // å¤„理手动入库任务(起点为线体点位的任务)
                                try
@@ -247,17 +246,36 @@
                                }
                                continue;
                            }
                            else
                            {
                                if (childDeviceCode == "2103" || childDeviceCode == "2101")
                                {
                                    try
                                    {
                                        var task = _taskService.QueryManualInboundTask(childDeviceCode);
                                        if (task != null)
                                        {
                                            QuartzLogHelper.LogInfo(_logger, $"获取到输送线开始任务,任务号:{task.TaskNum},状态: {task.TaskStatus},当前地址:{conveyorLine.DeviceCode}", conveyorLine.DeviceCode);
                                            var handler = new ManualInboundTaskHandler(_taskService, _logger);
                                            handler.WriteTaskToPlc(conveyorLine, childDeviceCode, task);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        QuartzLogHelper.LogError(_logger, ex, "处理手动入库任务异常", $"处理手动入库任务异常: {ex.Message}", "CommonConveyorLineNewJob");
                                    }
                                }
                            }
                            // ========== å¤„理无托盘条码的情况 ==========
                            // æ— æ‰˜ç›˜æ¡ç æ—¶ï¼Œè¯·æ±‚出库任务
                            if (command.Barcode.IsNullOrEmpty() || command.Barcode.Replace("\0", "") == "")
                            // å¦‚æžœ PLC_STB ä¸º 1,但没有任务号,可能是新任务的开始,先请求出库任务(适用于无条码的情况)
                            if (command.TaskNo == 1000)
                            {
                                _conveyorLineDispatch.RequestOutbound(conveyorLine, command, childDeviceCode);
                                continue;
                            }
                            // ========== å¤„理已有任务号的情况 ==========
                            if (command.TaskNo > 0 && !command.Barcode.IsNullOrEmpty())
                            if (command.TaskNo > 0)
                            {
                                // æŸ¥è¯¢æ­£åœ¨æ‰§è¡Œçš„任务
                                Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
@@ -299,48 +317,6 @@
                                        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/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -257,21 +257,21 @@
            // ç¡®å®šç›®æ ‡åœ°å€ï¼šç©ºæ‰˜ç›˜ä»»åŠ¡ä½¿ç”¨ "2201",其他任务使用 NextAddress
            var isEmptyTask = task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty;
            var targetAddress = task.CurrentAddress == "2217" ? (isEmptyTask ? "2201" : task.NextAddress) : task.NextAddress;
            var targetAddress = task.CurrentAddress == "2217" ? (isEmptyTask ? task.TargetAddress : task.NextAddress) : task.NextAddress;
            // å¤„理特殊地址 2217,需要调用目标地址选择器
            if (task.CurrentAddress == "2217" && !_targetAddressSelector.HandleOutboundNextAddress(conveyorLine, targetAddress, childDeviceCode))
            {
                return Task.CompletedTask; ;
            }
            //if (task.CurrentAddress == "2217" && !_targetAddressSelector.HandleOutboundNextAddress(conveyorLine, targetAddress, childDeviceCode))
            //{
            //    return Task.CompletedTask; ;
            //}
            // è®¾ç½®ä»»åŠ¡å·ã€æ‰˜ç›˜æ¡ç ã€ç›®æ ‡åœ°å€ã€WCS_ACK
            var isTaskNoSet = conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, task.TaskNum, childDeviceCode);
            Thread.Sleep(100); // ç¡®ä¿ PLC èƒ½æ­£ç¡®è¯»å–任务号后再写入条码
            Thread.Sleep(300); // ç¡®ä¿ PLC èƒ½æ­£ç¡®è¯»å–任务号后再写入条码
            var isPalletSet = conveyorLine.SetValue(ConveyorLineDBNameNew.Barcode, task.PalletCode, childDeviceCode);
            Thread.Sleep(100); // ç¡®ä¿ PLC èƒ½æ­£ç¡®è¯»å–任务号后再写入条码
            Thread.Sleep(300); // ç¡®ä¿ PLC èƒ½æ­£ç¡®è¯»å–任务号后再写入条码
            bool isTargetSet = conveyorLine.SetValue(ConveyorLineDBNameNew.Target, targetAddress, childDeviceCode);
            //if (targetAddress == "2217" && !isEmptyTask)
            //{
@@ -287,10 +287,10 @@
            bool isWmsResult = false;
            // æ›´æ–°ä»»åŠ¡çŠ¶æ€æˆ–ä½ç½®
            if (isEmptyTask && task.NextAddress == "2217")
            if (isEmptyTask && (task.TargetAddress == "2103" || task.TargetAddress == "2101"))
            {
                task.TaskStatus = task.TaskStatus.GetNextNotCompletedStatus<TaskOutStatusEnum>();
                task.NextAddress = "2201";
                task.NextAddress = "2103";
                isWmsResult = _taskService.Repository.UpdateData(task);
            }
            else
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -68,8 +68,11 @@
            var cvState = conveyorLine.GetValue<ConveyorLineDBNameNew, byte>(ConveyorLineDBNameNew.CV_State, nextAddress);
            bool isAvailable = cvState == 2;
            WriteDebug(conveyorLine, "出库下一地址状态", childDeviceCode, $"CV_State={cvState},可用={isAvailable}");
            if (isAvailable)
            {
                WriteDebug(conveyorLine, "出库下一地址可用,写入目标地址", childDeviceCode, nextAddress);
                Thread.Sleep(300); // çŸ­æš‚等待,确保设备状态稳定后再写入目标地址
                return conveyorLine.SetValue(ConveyorLineDBNameNew.Target, Convert.ToInt16(nextAddress), childDeviceCode);
            }
            return false;
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -627,6 +627,12 @@
            // è§£æž WMS è¿”回的任务信息
            WMSTaskDTO taskDTO = JsonConvert.DeserializeObject<WMSTaskDTO>(result.Data.Data.ToJson() ?? string.Empty) ?? new WMSTaskDTO();
            var task = await _taskService.Repository.QueryFirstAsync(x => x.PalletCode == taskDTO.PalletCode);
            if(task != null)
            {
                await _taskService.Repository.DeleteDataAsync(task);
            }
            // è°ƒç”¨ä»»åŠ¡æœåŠ¡æŽ¥æ”¶ WMS ä»»åŠ¡
            var content = _taskService.ReceiveWMSTask(new List<WMSTaskDTO> { taskDTO });
            if (!content.Status)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -197,7 +197,7 @@
                            // è°ƒç”¨æ‰¹é‡æ‹†ç›˜ç¡®è®¤æŽ¥å£
                            var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode;
                            var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            if (!confirmResult.IsSuccess && !confirmResult.Data.Status)
                            {
                                QuartzLogHelper.LogError(_logger, $"批量拆盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
@@ -314,7 +314,7 @@
                            // è°ƒç”¨æ‰¹é‡ç»„盘确认接口
                            var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                            var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            if (!confirmResult.IsSuccess && !confirmResult.Data.Status)
                            {
                                QuartzLogHelper.LogError(_logger, $"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -396,10 +396,13 @@
                // ç›®æ ‡æ•°é‡ä¸º48:直接走原有逻辑,不进入批次模式
                if (targetNormalCount + currentCompletedCount == targetTotal)
                {
                    QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:目标数量已达48,直接下发取货指令,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                    return;
                }
                QuartzLogHelper.LogDebug(_logger,$"HandlePutFinishedStateAsync:换盘任务目标数量: {targetNormalCount},当前已完成数量: {currentCompletedCount},流向: {(isFlowA ? "A" : "B")},任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                // åˆå§‹åŒ–批次模式
                if (stateForUpdate.ChangePalletPhase == 0)
                {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
@@ -3,6 +3,7 @@
using System.Text.Json;
using System.IO;
using WIDESEAWCS_Model.Models;
using Serilog.Core;
namespace WIDESEAWCS_Tasks.SocketServer
{
@@ -64,6 +65,7 @@
                        //{
                        //    if (_clientLastMessage.TryGetValue(clientId, out var prev) && message == prev)
                        //    {
                        //        QuartzLogHelper.LogInfo(Logger.None, $"来自客户端 {clientId} çš„重复消息,内容: {message}", clientId);
                        //        continue;
                        //    }
                        //    _clientLastMessage[clientId] = message;
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -121,36 +121,6 @@
        }
        /// <summary>
        /// åŠ è½½é…ç½®æ–‡ä»¶ï¼ˆä¼˜å…ˆçº§ï¼šé…ç½®æ–‡ä»¶ > é»˜è®¤é…ç½®ï¼‰
        /// </summary>
        /// <remarks>
        /// ä»Žåº”用程序目录下的 StackerCraneJob/stackercrane-command-config.json è¯»å–配置。
        /// å¦‚果文件不存在或解析失败,使用默认配置。
        /// </remarks>
        /// <returns>堆垛机命令配置</returns>
        private static StackerCraneCommandConfig LoadConfig()
        {
            try
            {
                // æž„造配置文件路径
                string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "StackerCraneJob", "stackercrane-command-config.json");
                if (File.Exists(configPath))
                {
                    // è¯»å–并解析 JSON é…ç½®
                    string json = File.ReadAllText(configPath);
                    return System.Text.Json.JsonSerializer.Deserialize<StackerCraneCommandConfig>(json) ?? new StackerCraneCommandConfig();
                }
            }
            catch (Exception ex)
            {
                // é…ç½®åŠ è½½å¤±è´¥ï¼Œä½¿ç”¨é»˜è®¤é…ç½®
                Console.WriteLine($"配置加载失败: {ex.Message},使用默认配置");
            }
            return new StackerCraneCommandConfig();
        }
        /// <summary>
        /// Quartz Job çš„æ‰§è¡Œå…¥å£
        /// </summary>
        /// <remarks>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -1,7 +1,6 @@
using Newtonsoft.Json;
using Serilog;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEAWCS_Common.Constants;
using WIDESEAWCS_Common.HttpEnum;
@@ -10,7 +9,6 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.ConveyorLine.Enum;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
@@ -208,7 +206,7 @@
        {
            // å…ˆè¿›è¡Œæœ¬åœ°ç«™å°æ£€æŸ¥ï¼ˆPLC è¯»å–,快速),避免不必要的 WMS HTTP è°ƒç”¨
            if (outboundTask.TaskType != (int)TaskOutboundTypeEnum.OutEmpty && outboundTask.Roadway != "GWSC1" && outboundTask.TargetAddress != "CWSC1")
            if (outboundTask.TaskType != (int)TaskOutboundTypeEnum.OutEmpty && outboundTask.TargetAddress != "CWSC1")
            {
                // åˆ¤æ–­ TargetAddress è¾“送线站台是否空闲
                if (!IsTargetAddressConveyorStationAvailable(outboundTask))
@@ -223,8 +221,8 @@
                return null;
            }
            if (outboundTask.TargetAddress != "CWSC1")
            {
            //if (outboundTask.TargetAddress != "CWSC1")
            //{
                // æ£€æŸ¥æ˜¯å¦æœ‰æ­£åœ¨æ‰§è¡Œçš„输送线任务去往同一 TargetAddress
                if (_taskService.HasExecutingTaskToTarget(outboundTask.Roadway, outboundTask.TargetAddress))
                {
@@ -232,7 +230,7 @@
                        $"TrySelectOutboundTask:TargetAddress: {outboundTask.TargetAddress} å·²æœ‰æ­£åœ¨æ‰§è¡Œçš„输送线任务", outboundTask.Roadway, outboundTask.TargetAddress, outboundTask.TaskNum);
                    return null;
                }
            }
            //}
            if(outboundTask.Roadway != "GWSC1")
            {
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -159,9 +159,12 @@
                    && x.LocationType == locationType)
                .CountAsync();
            if(roadwayNo != "CWSC1")
            {
            // ç©ºé—²è´§ä½ä¸è¶³æœ€ä½Žä¿ç•™æ•°é‡æ—¶è¿”回null,避免将巷道分配耗尽
            const int minFreeLocationThreshold = 5;
                const int minFreeLocationThreshold = 1;
            if (freeCount < minFreeLocationThreshold) return null;
            }
            // æ•°æ®åº“端排序取第一条(只传输单行数据)
            return await BaseDal.Db.Queryable<Dt_LocationInfo>()
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/OutboundTimeConstants.cs
@@ -29,16 +29,16 @@
        /// <summary>
        /// GW_1首放入库时效(24小时)
        /// </summary>
        public const int OUTBOUND_HOURS_GW1_FIRST = 24;
        public const double OUTBOUND_HOURS_GW1_FIRST = 24;
        /// <summary>
        /// GW_1二放入库时效(24小时)
        /// GW_1二放入库时效(5分钟)
        /// </summary>
        public const int OUTBOUND_HOURS_GW1_SECOND = 24;
        public const double OUTBOUND_HOURS_GW1_SECOND = 0.05;
        /// <summary>
        /// CW_1出库时效(12小时)
        /// CW_1出库时效(3小时)
        /// </summary>
        public const int OUTBOUND_HOURS_CW1 = 12;
        public const double OUTBOUND_HOURS_CW1 = 3;
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/TaskAddressConstants.cs
@@ -13,7 +13,7 @@
        /// <summary>
        /// é«˜æ¸©1号出库地址列表(轮询)
        /// </summary>
        public static readonly string[] GW1_ADDRESSES = { "11001" };
        public static readonly string[] GW1_ADDRESSES = { "11010" };
        /// <summary>
        /// é«˜æ¸©2号出库地址
@@ -28,7 +28,7 @@
        /// <summary>
        /// åˆ†å®¹åº“出库地址
        /// </summary>
        public const string GRADING_OUTBOUND_ADDRESS = "2103";
        public const string GRADING_OUTBOUND_ADDRESS = "2101";
        /// <summary>
        /// åˆ†å®¹åº“出库地址
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -141,8 +141,10 @@
                            Creater = StockConstants.SYSTEM_USER,
                            Details = null,
                            LocationCode = location.LocationCode,
                            LocationId = location.Id
                            LocationId = location.Id,
                            OutboundDate = DateTime.Now
                        };
                        location.LocationStatus = LocationStatusEnum.InStock.GetHashCode();
                        var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(location);
                        var updateStockResult = await _stockInfoService.Repository.AddDataAsync(stockInfo);
                        return await CompleteTaskAsync(task, "入库完成");
@@ -167,6 +169,8 @@
                        stockInfo.StockStatus = StockStatusEmun.入库完成.GetHashCode();
                        stockInfo.CreateDate = DateTime.Now;
                        location.LocationStatus = LocationStatusEnum.InStock.GetHashCode();
                        var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(location);
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -34,7 +34,7 @@
  "MainDB": "DB_WIDESEA", //当前项目的主库,所对应的连接字符串的Enabled必须为true
  //连接字符串
  //"ConnectionString": "HTI6FB1H05Krd07mNm9yBCNhofW6edA5zLs9TY~MNthRYW3kn0qKbMIsGp~3yyPDF1YZUCPBQx8U0Jfk4PH~ajNFXVIwlH85M3F~v_qKYQ3CeAz3q1mLVDn8O5uWt1~3Ut2V3KRkEwYHvW2oMDN~QIDXPxDgXN0R2oTIhc9dNu7QNaLEknblqmHhjaNSSpERdDVZIgHnMKejU_SL49tralBkZmDNi0hmkbL~837j1NWe37u9fJKmv91QPb~16JsuI9uu0EvNZ06g6PuZfOSAeFH9GMMIZiketdcJG3tHelo=",
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  "ConnectionString": "Data Source=192.168.60.30;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=10.30.4.92;Initial Catalog=WMS_TC;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //旧WMS数据库连接
Code/docs/superpowers/plans/2026-04-29-outbound-task-flow-todo-implementation.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
# OutboundTaskFlowService.MoveToNextStatus TODO å®žçŽ°è®¡åˆ’
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** è¡¥å…¨ `OutboundTaskFlowService.MoveToNextStatus` æ–¹æ³•中的 TODO,实现在出库任务到达 `Line_OutFinish` çŠ¶æ€æ—¶æ£€æŸ¥å¯¹å‘çº¿ä½“æ‰˜ç›˜å·ï¼Œæœ‰æ‰˜ç›˜å·åˆ™æœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡ï¼Œæ— æ‰˜ç›˜å·åˆ™ä»Ž WMS èŽ·å–ä»»åŠ¡ã€‚
**Architecture:** åœ¨ `RobotTaskService` ä¸­æ–°å¢ž `CheckSourceLineAndCreateRobotTask` æ–¹æ³•,复用已有的 `BuildRobotTaskStock` èŽ·å–æºçº¿ä½“ç¼–å·ï¼Œé€šè¿‡è®¾å¤‡é€šä¿¡è¯»å–çº¿ä½“æ‰˜ç›˜å·ï¼Œæ ¹æ®ç»“æžœå†³å®šè°ƒç”¨ `CreateLocalRobotTask` æˆ– `GetWMSOutboundTrayTask`。
**Tech Stack:** C# / .NET 6, ASP.NET Core, SqlSugar ORM
---
## æ¶‰åŠæ–‡ä»¶
| æ–‡ä»¶ | æ”¹åЍ |
|------|------|
| `WIDESEAWCS_TaskInfoService/RobotTaskService.cs` | æ–°å¢ž `CheckSourceLineAndCreateRobotTask` æ–¹æ³• |
| `WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs` | æ›¿æ¢ TODO ä»£ç æ®µä¸ºå§”托调用 |
---
## Task 1: åœ¨ RobotTaskService æ–°å¢ž CheckSourceLineAndCreateRobotTask æ–¹æ³•
**Files:**
- Modify: `WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs:241`(在 `CreateLocalRobotTask` æ–¹æ³•之后)
- [ ] **Step 1: åœ¨ `CreateLocalRobotTask` æ–¹æ³•后添加新方法**
在 `RobotTaskService.cs` ç¬¬ 240 è¡Œï¼ˆ`CreateLocalRobotTask` æ–¹æ³•结束后的位置)添加以下方法:
```csharp
/// <summary>
/// æ£€æŸ¥æºçº¿ä½“是否有托盘号,并根据结果创建机械手任务。
/// </summary>
/// <param name="task">出库任务实体</param>
/// <returns>
/// æœ‰æ‰˜ç›˜å·æ—¶è¿”回 CreateLocalRobotTask ç»“果;
/// æ— æ‰˜ç›˜å·æ—¶è¿”回 GetWMSOutboundTrayTask ç»“果。
/// </returns>
public WebResponseContent CheckSourceLineAndCreateRobotTask(Dt_Task task)
{
    // 1. èŽ·å–æºçº¿ä½“ç¼–å·ï¼ˆå¤ç”¨å·²æœ‰é€»è¾‘ï¼‰
    string configKey = ResolveRobotTaskConfigKey(task.TargetAddress);
    StockDTO stock = BuildRobotTaskStock(task, configKey);
    string sourceLineNo = stock.SourceLineNo;
    if (string.IsNullOrWhiteSpace(sourceLineNo))
    {
        return GetWMSOutboundTrayTask(task);
    }
    // 2. é€šè¿‡è®¾å¤‡é€šä¿¡è¯»å–线体托盘号
    string? palletCode = ReadLineBarcode(sourceLineNo);
    if (!string.IsNullOrWhiteSpace(palletCode))
    {
        // æœ‰æ‰˜ç›˜å·ï¼Œæœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡
        return CreateLocalRobotTask(task);
    }
    // æ— æ‰˜ç›˜å·ï¼Œä»Ž WMS èŽ·å–ä»»åŠ¡
    return GetWMSOutboundTrayTask(task);
}
/// <summary>
/// è¯»å–指定线体的托盘号。
/// </summary>
/// <param name="sourceLineNo">源线体编号</param>
/// <returns>托盘号,如有异常返回 null</returns>
private string? ReadLineBarcode(string sourceLineNo)
{
    try
    {
        IDevice? device = Storage.Devices.FirstOrDefault(x =>
            x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceLineNo));
        if (device == null)
            return null;
        CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
        return conveyorLine.GetValue<ConveyorLineDBNameNew, string>(
            ConveyorLineDBNameNew.Barcode, sourceLineNo);
    }
    catch (Exception ex)
    {
        _logger.Error(ex, $"读取线体[{sourceLineNo}]托盘号异常");
        return null;
    }
}
```
- [ ] **Step 2: éªŒè¯ç¼–译**
Run: `dotnet build WIDESEAWCS_Server/WIDESEAWCS_Server.sln --no-restore`
Expected: BUILD SUCCEEDED(无编译错误)
- [ ] **Step 3: æäº¤**
```bash
git add WIDESEAWCS_TaskInfoService/RobotTaskService.cs
git commit -m "feat(机械手任务): æ–°å¢žCheckSourceLineAndCreateRobotTask方法支持检查线体托盘号决策
- æ–°å¢žCheckSourceLineAndCreateRobotTask方法,根据源线体托盘号决定创建机械手任务方式
- æ–°å¢žReadLineBarcode私有方法,读取指定线体的托盘号
- æœ‰æ‰˜ç›˜å·æ—¶è°ƒç”¨CreateLocalRobotTask,无托盘号时调用GetWMSOutboundTrayTask
- å¼‚常时降级为从WMS获取任务"
```
---
## Task 2: ä¿®æ”¹ OutboundTaskFlowService.MoveToNextStatus è°ƒç”¨æ–°æ–¹æ³•
**Files:**
- Modify: `WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs:107-114`
- [ ] **Step 1: æ›¿æ¢ TODO ä»£ç æ®µ**
将 `OutboundTaskFlowService.cs` ç¬¬ 107-114 è¡Œï¼š
```csharp
if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.Outbound)
{
    return WebResponseContent.Instance.OK();
    // Todo:获取对向线体是否有托盘号,如果有托盘号直接生成机械手任务
    return GetWMSOutboundTrayTask(task);
}
```
替换为:
```csharp
if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.Outbound)
{
    // èŽ·å–å¯¹å‘çº¿ä½“æ˜¯å¦æœ‰æ‰˜ç›˜å·ï¼Œå¦‚æžœæœ‰æ‰˜ç›˜å·ç›´æŽ¥ç”Ÿæˆæœºæ¢°æ‰‹ä»»åŠ¡
    return _robotTaskService.CheckSourceLineAndCreateRobotTask(task);
}
```
- [ ] **Step 2: éªŒè¯ç¼–译**
Run: `dotnet build WIDESEAWCS_Server/WIDESEAWCS_Server.sln --no-restore`
Expected: BUILD SUCCEEDED(无编译错误)
- [ ] **Step 3: æäº¤**
```bash
git add WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs
git commit -m "feat(出库任务): MoveToNextStatus调用CheckSourceLineAndCreateRobotTask决策机械手任务创建方式
- æ›¿æ¢TODO代码段为_robotTaskService.CheckSourceLineAndCreateRobotTask调用
- æœ‰æ‰˜ç›˜å·æ—¶æœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡ï¼Œæ— æ‰˜ç›˜å·æ—¶ä»ŽWMS获取"
```
---
## éªŒè¯è¦ç‚¹
1. å½“输送线有料时(对向线体 Barcode æœ‰å€¼ï¼‰ï¼Œæœºæ¢°æ‰‹ä»»åŠ¡ç›´æŽ¥æœ¬åœ°åˆ›å»ºï¼Œä¸è°ƒç”¨ WMS æŽ¥å£
2. å½“输送线无料时(对向线体 Barcode ä¸ºç©ºï¼‰ï¼Œé™çº§è°ƒç”¨ WMS èŽ·å–ç©ºæ‰˜ç›˜ä»»åŠ¡
3. å¼‚常场景不阻塞主流程,降级到 WMS èŽ·å–
---
## æ³¨æ„äº‹é¡¹
- `ReadLineBarcode` æ–¹æ³•复用了 `BuildRobotTaskStock` ä¸­è¯»å– Barcode çš„设备查询逻辑(见 `RobotTaskService.cs:365-374`)
- å¼‚常处理返回 null,触发降级逻辑调用 `GetWMSOutboundTrayTask`
- è®¾è®¡æ–‡æ¡£ä½ç½®ï¼š`docs/superpowers/specs/2026-04-29-outbound-task-flow-todo-design.md`
Code/docs/superpowers/specs/2026-04-29-outbound-task-flow-todo-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
# OutboundTaskFlowService.MoveToNextStatus TODO è®¾è®¡æ–¹æ¡ˆ
## èƒŒæ™¯
`OutboundTaskFlowService.MoveToNextStatus` æ–¹æ³•中存在一个 TODO:
```csharp
if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.Outbound)
{
    return WebResponseContent.Instance.OK();
    // Todo:获取对向线体是否有托盘号,如果有托盘号直接生成机械手任务
    return GetWMSOutboundTrayTask(task);
}
```
该 TODO éœ€è¦åœ¨å‡ºåº“任务到达 `Line_OutFinish` çŠ¶æ€æ—¶ï¼Œæ£€æŸ¥å¯¹å‘çº¿ä½“æ˜¯å¦æœ‰æ‰˜ç›˜å·ï¼Œä»¥å†³å®šåŽç»­æœºæ¢°æ‰‹ä»»åŠ¡çš„åˆ›å»ºæ–¹å¼ã€‚
## ä¸šåŠ¡é€»è¾‘
出库任务流程中,当物料通过输送线到达目标位置后:
- **对向线体有托盘号** â†’ æ‰˜ç›˜ä¸Šæœ‰è´§ï¼Œç›´æŽ¥åœ¨æœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡æ‰§è¡Œæ¢ç›˜/组盘/拆盘
- **对向线体无托盘号** â†’ éœ€è¦ä»Ž WMS èŽ·å–ç©ºæ‰˜ç›˜å‡ºåº“ä»»åŠ¡
## æ–¹æ¡ˆé€‰æ‹©
**采用方案 B**:在 `RobotTaskService` ä¸­å°è£…检查逻辑,复用已有的 `BuildRobotTaskStock` åŸºç¡€è®¾æ–½ã€‚
### åŽŸå› 
- `RobotTaskService.BuildRobotTaskStock` å·²å®žçŽ°é€šè¿‡ `AddressSourceLineNoMap` è§£æžæºçº¿ä½“编号,并通过设备通信读取 `Barcode` çš„完整逻辑
- æ–°å¢žæ–¹æ³•职责单一:检查 â†’ å†³ç­–,仅做判断不执行副作用
- `OutboundTaskFlowService` ä¿æŒç®€æ´ï¼Œåªéœ€è°ƒç”¨å³å¯
## å®žçŽ°è®¾è®¡
### 1. RobotTaskService æ–°å¢žæ–¹æ³•
```csharp
/// <summary>
/// æ£€æŸ¥æºçº¿ä½“是否有托盘号,并根据结果创建机械手任务。
/// </summary>
/// <param name="task">出库任务实体</param>
/// <returns>
/// æœ‰æ‰˜ç›˜å·æ—¶è¿”回 CreateLocalRobotTask ç»“果;
/// æ— æ‰˜ç›˜å·æ—¶è¿”回 GetWMSOutboundTrayTask ç»“果。
/// </returns>
public WebResponseContent CheckSourceLineAndCreateRobotTask(Dt_Task task)
{
    // 1. èŽ·å–æºçº¿ä½“ç¼–å·ï¼ˆå¤ç”¨å·²æœ‰é€»è¾‘ï¼‰
    string configKey = ResolveRobotTaskConfigKey(task.TargetAddress);
    StockDTO stock = BuildRobotTaskStock(task, configKey);
    string sourceLineNo = stock.SourceLineNo;
    if (string.IsNullOrWhiteSpace(sourceLineNo))
    {
        return GetWMSOutboundTrayTask(task);
    }
    // 2. é€šè¿‡è®¾å¤‡é€šä¿¡è¯»å–线体托盘号
    string? palletCode = ReadLineBarcode(sourceLineNo);
    if (!string.IsNullOrWhiteSpace(palletCode))
    {
        // æœ‰æ‰˜ç›˜å·ï¼Œæœ¬åœ°åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡
        return CreateLocalRobotTask(task);
    }
    // æ— æ‰˜ç›˜å·ï¼Œä»Ž WMS èŽ·å–ä»»åŠ¡
    return GetWMSOutboundTrayTask(task);
}
/// <summary>
/// è¯»å–指定线体的托盘号。
/// </summary>
private string? ReadLineBarcode(string sourceLineNo)
{
    try
    {
        IDevice? device = Storage.Devices.FirstOrDefault(x =>
            x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceLineNo));
        if (device == null)
            return null;
        CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
        return conveyorLine.GetValue<ConveyorLineDBNameNew, string>(
            ConveyorLineDBNameNew.Barcode, sourceLineNo);
    }
    catch
    {
        return null;
    }
}
```
### 2. OutboundTaskFlowService ä¿®æ”¹
```csharp
if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.Outbound)
{
    // èŽ·å–å¯¹å‘çº¿ä½“æ˜¯å¦æœ‰æ‰˜ç›˜å·ï¼Œå¦‚æžœæœ‰æ‰˜ç›˜å·ç›´æŽ¥ç”Ÿæˆæœºæ¢°æ‰‹ä»»åŠ¡
    return _robotTaskService.CheckSourceLineAndCreateRobotTask(task);
}
```
## æ•°æ®æµ
```
MoveToNextStatus (OutboundTaskFlowService)
    â”‚
    â–¼
CheckSourceLineAndCreateRobotTask (RobotTaskService)
    â”‚
    â”œâ”€â”€â”€â–º BuildRobotTaskStock â”€â”€â–º ResolveRobotTaskConfigKey
    â”‚                              â””──► AddressSourceLineNoMap èŽ·å– sourceLineNo
    â”‚
    â–¼
ReadLineBarcode(sourceLineNo)
    â”‚
    â”œâ”€â”€â”€â–º Storage.Devices æŸ¥æ‰¾è®¾å¤‡
    â””───► CommonConveyorLine.GetValue(Barcode)
                â”‚
                â–¼
        â”Œâ”€â”€â”€â”€â”€â”€â”€â”´â”€â”€â”€â”€â”€â”€â”€â”
        â”‚  æ‰˜ç›˜å·æœ‰å€¼    â”‚     æ‰˜ç›˜å·ä¸ºç©º
        â–¼               â–¼
CreateLocalRobotTask   GetWMSOutboundTrayTask
```
## é”™è¯¯å¤„理
- è®¾å¤‡æŸ¥æ‰¾å¤±è´¥æˆ–读取异常 â†’ é™çº§ä¸ºè°ƒç”¨ `GetWMSOutboundTrayTask`(从 WMS èŽ·å–ä»»åŠ¡ï¼‰
- ä¸é˜»å¡žä¸»æµç¨‹ï¼Œå¼‚常仅记录日志
## æ¶‰åŠæ–‡ä»¶
| æ–‡ä»¶ | æ”¹åЍ |
|------|------|
| `WIDESEAWCS_TaskInfoService/RobotTaskService.cs` | æ–°å¢ž `CheckSourceLineAndCreateRobotTask` å’Œ `ReadLineBarcode` æ–¹æ³• |
| `WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs` | æ›¿æ¢ TODO ä»£ç æ®µä¸ºå§”托调用 |
## éªŒè¯è¦ç‚¹
1. å½“输送线有料时,机械手任务直接本地创建,不调用 WMS æŽ¥å£
2. å½“输送线无料时,降级调用 WMS èŽ·å–ç©ºæ‰˜ç›˜ä»»åŠ¡
3. å¼‚常场景不阻塞主流程,降级到 WMS èŽ·å–