wanshenmean
12 小时以前 419159c8611dca81b94b3103cdeaeb9272aed5ea
feat(机器人任务): 实现本地创建机器人任务功能

refactor(任务选择器): 优化堆垛机任务选择逻辑并增加日志

fix(库存服务): 完善拆盘和换盘操作后的库存清理逻辑

style(前端): 调整机器人任务表格列显示和字段命名

perf(堆垛机作业): 增加关键步骤日志输出提升可观测性

docs: 添加本地创建机器人任务设计文档

chore: 更新子代理追踪和任务状态文件
已添加1个文件
已修改15个文件
379 ■■■■ 文件已修改
Code/.omc/state/mission-state.json 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/subagent-tracking.json 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/robotState.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/robotTask.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/TaskAddressConstants.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-23-local-robot-task-creation-design.md 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@
{
  "updatedAt": "2026-04-22T08:00:38.578Z",
  "updatedAt": "2026-04-23T06:52:14.501Z",
  "missions": [
    {
      "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none",
@@ -2534,6 +2534,94 @@
          "sourceKey": "session-stop:a70a5d0efcc81d263"
        }
      ]
    },
    {
      "id": "session:97f7871e-58c9-49b8-b7b1-d67ca866e495:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-23T02:51:49.771Z",
      "updatedAt": "2026-04-23T02:55:09.431Z",
      "status": "done",
      "workerCount": 1,
      "taskCounts": {
        "total": 1,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 1,
        "failed": 0
      },
      "agents": [
        {
          "name": "Explore:a90c490",
          "role": "Explore",
          "ownership": "a90c490b825a81654",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-23T02:55:09.431Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a90c490b825a81654:2026-04-23T02:51:49.771Z",
          "at": "2026-04-23T02:51:49.771Z",
          "kind": "update",
          "agent": "Explore:a90c490",
          "detail": "started Explore:a90c490",
          "sourceKey": "session-start:a90c490b825a81654"
        },
        {
          "id": "session-stop:a90c490b825a81654:2026-04-23T02:55:09.431Z",
          "at": "2026-04-23T02:55:09.431Z",
          "kind": "completion",
          "agent": "Explore:a90c490",
          "detail": "completed",
          "sourceKey": "session-stop:a90c490b825a81654"
        }
      ]
    },
    {
      "id": "session:7faab803-bef5-44c3-a296-6a6757237fd7:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-23T06:52:14.501Z",
      "updatedAt": "2026-04-23T06:52:14.501Z",
      "status": "running",
      "workerCount": 1,
      "taskCounts": {
        "total": 1,
        "pending": 0,
        "blocked": 0,
        "inProgress": 1,
        "completed": 0,
        "failed": 0
      },
      "agents": [
        {
          "name": "Explore:a20f390",
          "role": "Explore",
          "ownership": "a20f390d480b5890b",
          "status": "running",
          "currentStep": null,
          "latestUpdate": null,
          "completedSummary": null,
          "updatedAt": "2026-04-23T06:52:14.501Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a20f390d480b5890b:2026-04-23T06:52:14.501Z",
          "at": "2026-04-23T06:52:14.501Z",
          "kind": "update",
          "agent": "Explore:a20f390",
          "detail": "started Explore:a20f390",
          "sourceKey": "session-start:a20f390d480b5890b"
        }
      ]
    }
  ]
}
Code/.omc/state/subagent-tracking.json
@@ -1571,10 +1571,26 @@
      "status": "completed",
      "completed_at": "2026-04-22T08:00:38.576Z",
      "duration_ms": 191600
    },
    {
      "agent_id": "a90c490b825a81654",
      "agent_type": "Explore",
      "started_at": "2026-04-23T02:51:49.771Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-23T02:55:09.429Z",
      "duration_ms": 199658
    },
    {
      "agent_id": "a20f390d480b5890b",
      "agent_type": "Explore",
      "started_at": "2026-04-23T06:52:14.501Z",
      "parent_mode": "none",
      "status": "running"
    }
  ],
  "total_spawned": 147,
  "total_completed": 170,
  "total_spawned": 149,
  "total_completed": 171,
  "total_failed": 0,
  "last_updated": "2026-04-22T08:00:38.695Z"
  "last_updated": "2026-04-23T06:52:14.616Z"
}
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/robotState.vue
@@ -90,34 +90,46 @@
        {
          title: "是否拆盘",
          field: "isSplitPallet",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
        {
          title: "是否组盘",
          field: "isGroupPallet",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
        {
          title: "假电芯模式",
          field: "isInFakeBatteryMode",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
      ],
      [
        {
          title: "是否扫码NG",
          field: "isScanNG",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
        {
          title: "电芯是否到位",
          field: "batteryArrived",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
        {
          title: "消息已订阅",
          field: "isEventSubscribed",
          type: "checkbox",
          type: "select",
          dataKey: "isTrue",
          data: [],
        },
      ],
    ]);
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/robotTask.vue
@@ -186,6 +186,7 @@
        type: "string",
        width: 120,
        align: "left",
        hidden: true,
      },
      {
        field: "robotTargetAddress",
@@ -193,37 +194,35 @@
        type: "string",
        width: 120,
        align: "left",
        hidden: true,
      },
      {
        field: "robotSourceAddressLineCode",
        title: "来源线代码",
        title: "来源线体地址",
        type: "string",
        width: 130,
        align: "left",
      },
      {
        field: "robotTargetAddressLineCode",
        title: "目标线代码",
        title: "目标线体地址",
        type: "string",
        width: 130,
        align: "left",
        hidden: true,
      },
      {
        field: "robotSourceAddressPalletCode",
        title: "来源托盘代码",
        title: "来源托盘",
        type: "string",
        width: 130,
        align: "left",
        hidden: true,
      },
      {
        field: "robotTargetAddressPalletCode",
        title: "目标托盘代码",
        title: "目标托盘",
        type: "string",
        width: 130,
        align: "left",
        hidden: true,
      },
      {
        field: "robotExceptionMessage",
@@ -256,6 +255,7 @@
        type: "datetime",
        width: 160,
        align: "left",
        hidden: true,
      },
      {
        field: "robotremark",
@@ -271,6 +271,7 @@
        type: "string",
        width: 100,
        align: "left",
        hidden: true,
      },
      {
        field: "createDate",
@@ -279,6 +280,7 @@
        width: 160,
        align: "left",
        sortable: true,
        hidden: true,
      },
      {
        field: "modifier",
@@ -286,6 +288,7 @@
        type: "string",
        width: 100,
        align: "left",
        hidden: true,
      },
      {
        field: "modifyDate",
@@ -293,6 +296,7 @@
        type: "datetime",
        width: 160,
        align: "left",
        hidden: true,
      },
    ]);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IRobotTaskService.cs
@@ -75,12 +75,19 @@
        WebResponseContent ReceiveWMSTask([NotNull] WMSTaskDTO taskDTO, StockDTO stockDTO);
        /// <summary>
        ///
        /// è°ƒç”¨WMS接口获取机械手任务
        /// </summary>
        /// <param name="task"></param>
        /// <returns></returns>
        /// <param name="task">出库任务</param>
        /// <returns>操作结果</returns>
        WebResponseContent GetWMSRobotTask(Dt_Task task);
        /// <summary>
        /// åœ¨æœ¬åœ°ç›´æŽ¥åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡ï¼Œä¸è°ƒç”¨WMS接口
        /// </summary>
        /// <param name="task">出库任务</param>
        /// <returns>操作结果</returns>
        WebResponseContent CreateLocalRobotTask(Dt_Task task);
        int MapWarehouseIdConfigKey(string? targetAddress);
        string ResolveRobotRuleValue(string? targetAddress, string addressSectionName, string? fallback);
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -18,7 +18,7 @@
  },
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Default": "Warning",
      "Microsoft.AspNetCore": "Warning",
      "Quartz": "Debug"
    }
@@ -103,6 +103,13 @@
      "10030": "11010"
    }
  },
  "RobotAddressRules": {
    "11068": [ "1", "2" ],
    "11001": [ "3", "1" ],
    "11010": [ "4", "2" ],
    "2101": [ "1", "3" ],
    "2103": [ "2", "4" ]
  },
  "RedisConfig": {
    "Enabled": true, //是否启用Redis,false时仅使用内存缓存
    "ConnectionString": "127.0.0.1:6379,password=P@ssw0rd,defaultDatabase=0,connectTimeout=5000,abortConnect=false", //Redis连接字符串
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs
@@ -99,7 +99,7 @@
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
            {
                 _robotTaskService.GetWMSRobotTask(task);
                _robotTaskService.CreateLocalRobotTask(task);
                //if (!content.Status)
                //{
                //    return content;
@@ -228,7 +228,10 @@
            if (wMSTask == null)
                return WebResponseContent.Instance.Error($"获取WMS系统空托盘出库任务失败,任务号:【{task.TaskNum}】,托盘号:【{task.PalletCode}】,错误信息:【WMS未返回有效任务数据】");
            return WebResponseContent.Instance.OK("成功", new List<WMSTaskDTO> { wMSTask });
            if (TaskService.ReceiveWMSTask(new List<WMSTaskDTO> { wMSTask }).Status)
                return WebResponseContent.Instance.OK("成功");
            return WebResponseContent.Instance.Error("接收WMS任务失败");
        }
        /// <summary>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/RobotTaskService.cs
@@ -157,6 +157,78 @@
        }
        /// <summary>
        /// åœ¨æœ¬åœ°ç›´æŽ¥åˆ›å»ºæœºæ¢°æ‰‹ä»»åŠ¡ï¼Œä¸è°ƒç”¨WMS接口。
        /// æ ¹æ®ç›®æ ‡åœ°å€è§£æžä»»åŠ¡ç±»åž‹ï¼Œæž„å»ºä»»åŠ¡æ•°æ®å¹¶å†™å…¥æ•°æ®åº“ã€‚
        /// </summary>
        /// <param name="task">出库任务实体</param>
        /// <returns>操作结果</returns>
        public WebResponseContent CreateLocalRobotTask(Dt_Task task)
        {
            WebResponseContent content = new();
            try
            {
                // æ ¹æ®ç›®æ ‡åœ°å€è§£æžä»»åŠ¡ç±»åž‹é…ç½®é”®
                string configKey = ResolveRobotTaskConfigKey(task.TargetAddress);
                // æž„建Stock数据
                StockDTO stock = BuildRobotTaskStock(task, configKey);
                // èŽ·å–æŠ“å–å’Œæ”¾ç½®åœ°å€çš„çº¿ä½“é…ç½®ï¼ˆå¦‚æžœæœ‰ï¼‰
                var section = App.Configuration.GetSection("RobotTaskAddressRules").GetSection(stock?.TargetLineNo ?? string.Empty).GetChildren().Select(c => c.Value).ToArray();
                if (section.Length < 2)
                    return WebResponseContent.Instance.Error($"未找到线体[{stock?.TargetLineNo}]的地址配置");
                // åˆ›å»ºæœ¬åœ°æœºå™¨äººä»»åŠ¡
                Dt_RobotTask robotTask = new()
                {
                    RobotTaskNum = Random.Shared.Next(),
                    RobotSourceAddress = section[0]!,
                    RobotTargetAddress = section[1]!,
                    RobotSourceAddressLineCode = stock?.SourceLineNo ?? string.Empty,
                    RobotTargetAddressLineCode = stock?.TargetLineNo ?? string.Empty,
                    RobotRoadway = stock?.Roadway ?? string.Empty,
                    RobotSourceAddressPalletCode = stock?.SourcePalletNo ?? string.Empty,
                    RobotTargetAddressPalletCode = stock?.TargetPalletNo ?? string.Empty,
                    RobotTaskType = MapConfigKeyToRobotTaskType(configKey),
                    RobotTaskState = (int)TaskRobotStatusEnum.RobotNew,
                    RobotGrade = task.Grade,
                    Creater = "WCS_Local",
                    RobotTaskTotalNum = 1,
                    CreateDate = DateTime.Now
                };
                BaseDal.AddData(robotTask);
                _taskExecuteDetailService.AddTaskExecuteDetail(new List<int> { robotTask.RobotTaskNum }, "本地创建机器人任务");
                QuartzLogHelper.LogInfo(_logger, $"本地创建机器人任务成功,任务号:【{robotTask.RobotTaskNum}】,源地址:【{robotTask.RobotSourceAddress}】,目标地址:【{robotTask.RobotTargetAddress}】,任务类型:【{configKey}】", "RobotTaskService");
                content = WebResponseContent.Instance.OK("本地创建机器人任务成功", robotTask);
            }
            catch (Exception ex)
            {
                QuartzLogHelper.LogError(_logger, $"本地创建机器人任务失败,任务号:【{task.TaskNum}】,错误信息:{ex.Message}", "RobotTaskService");
                content = WebResponseContent.Instance.Error($"本地创建机器人任务失败,错误信息:{ex.Message}");
            }
            return content;
        }
        /// <summary>
        /// å°†é…ç½®é”®æ˜ å°„到机械手任务类型枚举值。
        /// </summary>
        /// <param name="configKey">配置键名称</param>
        /// <returns>任务类型枚举值</returns>
        public int MapConfigKeyToRobotTaskType(string? configKey)
        {
            return configKey switch
            {
                nameof(ConfigKey.CreateRobotGroupPalletTask) => (int)RobotTaskTypeEnum.GroupPallet,
                nameof(ConfigKey.CreateRobotSplitPalletTask) => (int)RobotTaskTypeEnum.SplitPallet,
                _ => (int)RobotTaskTypeEnum.ChangePallet
            };
        }
        /// <summary>
        /// æ ¹æ®è¾“送线目标地址解析机械手任务接口。
        /// è§„则:
        /// 1. ä»Žé…ç½®è¯»å–精确地址映射(AddressMap)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -118,7 +118,7 @@
                // - æ‰‹è‡‚上有物料(RobotArmObject == 1)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPickFinish(已记录取货完成)
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 1)
                    //&& (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 1)
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    QuartzLogHelper.LogInfo(_logger, $"ExecuteAsync:满足放货条件,开始下发放货任务,任务号: {task.RobotTaskNum}", latestState.RobotCrane?.DeviceName ?? ipAddress);
@@ -133,7 +133,7 @@
                // - æ‰‹è‡‚上无物料(RobotArmObject == 0)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPutFinish æˆ–不是 RobotExecuting
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction.IsNullOrEmpty())
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 0)
                    //&& (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 0)
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode() || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    QuartzLogHelper.LogInfo(_logger, $"ExecuteAsync:满足取货条件,开始下发取货任务,任务号: {task.RobotTaskNum}", latestState.RobotCrane?.DeviceName ?? ipAddress);
@@ -339,7 +339,7 @@
                {
                    if (stateForUpdate.CellBarcode.Contains(trayBarcode1) || stateForUpdate.CellBarcode.Contains(trayBarcode2))
                    {
                        QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取的托盘条码已存在,可能存在重复,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                        QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取的电芯条码已存在,可能存在重复,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                        // å‘送取货指令 æ ‡è®°æ‰«ç NG,放货时不使用这些条码,并放入NG口
                        //await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
@@ -347,7 +347,13 @@
                    }
                    else
                    {
                        QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取的托盘条码唯一,继续执行,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                        if (trayBarcode1.Length < 13 || trayBarcode2.Length < 13)
                        {
                            QuartzLogHelper.LogError(_logger, $"HandlePutFinishedStateAsync:电芯条码长度不达标,任务号: {task.RobotTaskNum},读取电芯条码成功: ã€{trayBarcode1}】-----【{trayBarcode2}】", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                            return;
                        }
                        QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取的电芯条码唯一,继续执行,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                        // å°†æ¡ç æ·»åŠ åˆ°çŠ¶æ€ä¸­ï¼Œä¾›åŽç»­æ”¾è´§æ—¶ä½¿ç”¨
                        stateForUpdate.CellBarcode = new List<string>()
@@ -357,7 +363,7 @@
                    }
                    // è®°å½•日志:读取托盘条码成功
                    QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取托盘条码成功: ã€{trayBarcode1}】-----【{trayBarcode2}】,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                    QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取电芯条码成功: ã€{trayBarcode1}】-----【{trayBarcode2}】,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                    // å‘送取货指令
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -189,8 +189,11 @@
                    QuartzLogHelper.LogInfo(_logger, "Execute:订阅任务完成事件,设备: {DeviceCode}", "订阅任务完成事件", _deviceCode, _deviceCode);
                }
                QuartzLogHelper.LogInfo(_logger, $"开始检查堆垛机完成状态,【{_deviceCode}】", _deviceCode);
                // ========== æ£€æŸ¥å †åž›æœºä»»åŠ¡å®ŒæˆçŠ¶æ€ ==========
                commonStackerCrane.CheckStackerCraneTaskCompleted();
                QuartzLogHelper.LogInfo(_logger, $"检查完成,正在监听堆垛机任务完成,【{_deviceCode}】", _deviceCode);
                // ========== æ£€æŸ¥æ˜¯å¦å¯ä»¥å‘送新任务 ==========
                //if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
@@ -199,6 +202,8 @@
                    // å †åž›æœºä¸å¯ç”¨ï¼ˆå¦‚正在执行上一任务),直接返回
                    return Task.CompletedTask;
                }
                QuartzLogHelper.LogInfo(_logger, $"堆垛机可下发任务,【{_deviceCode}】", _deviceCode);
                // ========== é€‰æ‹©ä»»åŠ¡ ==========
                // ä»»åŠ¡é€‰æ‹©ä¸‹æ²‰åˆ°ä¸“ç”¨é€‰æ‹©å™¨
@@ -209,7 +214,7 @@
                    return Task.CompletedTask;
                }
                QuartzLogHelper.LogInfo(_logger, $"获取到任务,开始构建任务下发命令,【{_deviceCode}】", _deviceCode);
                // ========== æž„建命令 ==========
                // å‘½ä»¤æž„建下沉到专用构建器
@@ -221,6 +226,9 @@
                    return Task.CompletedTask;
                }
                QuartzLogHelper.LogInfo(_logger, $"命令构建完成,开始下发任务,【{_deviceCode}】", _deviceCode);
                // ========== å‘送命令 ==========
                bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
                if (sendFlag)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -101,14 +101,14 @@
            Dt_Task? candidateTask;
            var deviceCode = commonStackerCrane.DeviceCode;
            QuartzLogHelper.LogDebug(_logger, $"开始选择任务,设备: {deviceCode},上一任务类型: {commonStackerCrane.LastTaskType}",commonStackerCrane.DeviceName);
            QuartzLogHelper.LogInfo(_logger, $"开始选择任务,设备: {deviceCode},上一任务类型: {commonStackerCrane.LastTaskType}", commonStackerCrane.DeviceName);
            // æ ¹æ®ä¸Šä¸€ä»»åŠ¡ç±»åž‹å†³å®šæŸ¥è¯¢ç­–ç•¥
            if (commonStackerCrane.LastTaskType == null || commonStackerCrane.LastTaskType == TaskRelocationTypeEnum.Relocation.GetHashCode())
            {
                // æ²¡æœ‰ä¸Šä¸€ä»»åŠ¡ç±»åž‹ï¼ŒæŸ¥è¯¢æ™®é€šä»»åŠ¡
                candidateTask = _taskService.QueryStackerCraneTask(deviceCode);
                QuartzLogHelper.LogDebug(_logger, $"查询普通任务,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
                QuartzLogHelper.LogInfo(_logger, $"查询普通任务,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
            }
            else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
@@ -116,26 +116,29 @@
                candidateTask = _taskService.QueryStackerCraneInTask(deviceCode);
                // å¦‚果没有入库任务,再查一下出库任务
                candidateTask ??= _taskService.QueryStackerCraneOutTask(deviceCode);
                QuartzLogHelper.LogDebug(_logger, $"出库后优先查入库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
                QuartzLogHelper.LogInfo(_logger, $"出库后优先查入库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
            }
            else
            {
                // ä¸Šä¸€ä»»åŠ¡æ˜¯å…¥åº“ï¼ˆéžå‡ºåº“ï¼‰ï¼Œä¼˜å…ˆæŸ¥å‡ºåº“ä»»åŠ¡
                candidateTask = _taskService.QueryStackerCraneOutTask(deviceCode);
                QuartzLogHelper.LogDebug(_logger, $"入库后优先查出库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
                // å¦‚果没有出库任务,再查一下入库任务
                candidateTask ??= _taskService.QueryStackerCraneInTask(deviceCode);
                QuartzLogHelper.LogInfo(_logger, $"入库后优先查出库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", commonStackerCrane.DeviceName);
            }
            // å¦‚果没有候选任务,返回 null
            if (candidateTask == null)
            {
                QuartzLogHelper.LogDebug(_logger, $"没有候选任务,设备: {deviceCode}", commonStackerCrane.DeviceName);
                QuartzLogHelper.LogInfo(_logger, $"没有候选任务,设备: {deviceCode}", commonStackerCrane.DeviceName);
                return null;
            }
            // å¦‚果不是出库任务,直接返回
            if (candidateTask.TaskType.GetTaskTypeGroup() != TaskTypeGroup.OutbondGroup)
            {
                QuartzLogHelper.LogDebug(_logger, $"选中非出库任务,任务号: {candidateTask.TaskNum},任务类型: {candidateTask.TaskType}", commonStackerCrane.DeviceName);
                QuartzLogHelper.LogInfo(_logger, $"选中非出库任务,任务号: {candidateTask.TaskNum},任务类型: {candidateTask.TaskType}", commonStackerCrane.DeviceName);
                return candidateTask;
            }
@@ -143,7 +146,7 @@
            Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
            if (selectedTask != null)
            {
                QuartzLogHelper.LogDebug(_logger, $"选中出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                QuartzLogHelper.LogInfo(_logger, $"选中出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                return selectedTask;
            }
@@ -158,7 +161,7 @@
                selectedTask = TrySelectOutboundTask(sameStationTask);
                if (selectedTask != null)
                {
                    QuartzLogHelper.LogDebug(_logger, $"选中同站台备选出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                    QuartzLogHelper.LogInfo(_logger, $"选中同站台备选出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                    return selectedTask;
                }
            }
@@ -177,14 +180,14 @@
                selectedTask = TrySelectOutboundTask(alternativeTask);
                if (selectedTask != null)
                {
                    QuartzLogHelper.LogDebug(_logger, $"选中备选出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                    QuartzLogHelper.LogInfo(_logger, $"选中备选出库任务,任务号: {selectedTask.TaskNum}", commonStackerCrane.DeviceName);
                    return selectedTask;
                }
            }
            // æ²¡æœ‰å¯ç”¨å‡ºåº“任务,尝试返回入库任务
            var inboundTask = _taskService.QueryStackerCraneInTask(deviceCode);
            QuartzLogHelper.LogDebug(_logger, $"返回入库任务,任务号: {inboundTask?.TaskNum}", commonStackerCrane.DeviceName);
            QuartzLogHelper.LogInfo(_logger, $"返回入库任务,任务号: {inboundTask?.TaskNum}", commonStackerCrane.DeviceName);
            return inboundTask;
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_Common/Constants/TaskAddressConstants.cs
@@ -13,7 +13,7 @@
        /// <summary>
        /// é«˜æ¸©1号出库地址列表(轮询)
        /// </summary>
        public static readonly string[] GW1_ADDRESSES = { "11001", "11010" };
        public static readonly string[] GW1_ADDRESSES = { "11001" };
        /// <summary>
        /// é«˜æ¸©2号出库地址
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -197,7 +197,7 @@
        /// <returns>库存信息</returns>
        public async Task<Dt_StockInfo> GetStockInfoAsync(string palletCode, string locationCode)
        {
            return await BaseDal.QueryFirstAsync(x => x.PalletCode == palletCode && x.LocationCode == locationCode);
            return await BaseDal.QueryDataNavFirstAsync(x => x.PalletCode == palletCode && x.LocationCode == locationCode);
        }
        /// <summary>
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -247,6 +247,14 @@
                    var result = await StockInfoDetailService.Repository.UpdateDataAsync(detailEntities);
                    if (!result) return content.Error("换盘失败");
                    // æ£€æŸ¥æºæ‰˜ç›˜æ˜¯å¦è¿˜æœ‰å‰©ä½™åº“存明细,若无则删除源托盘库存头
                    var remainingSourceDetails = StockInfoDetailService.Repository.QueryData(d => d.StockId == sourceStock.Id);
                    if (!remainingSourceDetails.Any())
                    {
                        if (await StockInfoService.Repository.Db.Deleteable<Dt_StockInfo>().Where(s => s.Id == sourceStock.Id).ExecuteCommandAsync() <= 0)
                            return content.Error("删除源托盘库存失败");
                    }
                    return content.OK("换盘成功");
                });
            }
@@ -315,6 +323,19 @@
                    if (await StockInfo_HtyService.Repository.AddDataAsync(CreateStockHistory(new[] { sourceStock }, "拆盘")) <= 0)
                        return content.Error("拆盘历史记录保存失败");
                    // åˆ é™¤å·²æ‹†å‡ºçš„库存明细
                    var detailIds = detailEntities.Select(d => d.Id).ToList();
                    if (await StockInfoDetailService.Repository.Db.Deleteable<Dt_StockInfoDetail>().In(detailIds).ExecuteCommandAsync() <= 0)
                        return content.Error("删除库存明细失败");
                    // æ£€æŸ¥æºæ‰˜ç›˜æ˜¯å¦è¿˜æœ‰å‰©ä½™åº“存明细,若无则删除源托盘库存头
                    var remainingSourceDetails = StockInfoDetailService.Repository.QueryData(d => d.StockId == sourceStock.Id);
                    if (!remainingSourceDetails.Any())
                    {
                        if (await StockInfoService.Repository.Db.Deleteable<Dt_StockInfo>().Where(s => s.Id == sourceStock.Id).ExecuteCommandAsync() <= 0)
                            return content.Error("删除源托盘库存失败");
                    }
                    return content.OK("拆盘成功");
                });
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -74,13 +74,6 @@
    "BaseUrl": "http://192.168.98.11:20033",
    "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMwMTcyNzM5Mzk5NzYxOTIwIiwibmFtZSI6IlBBQ0voo4XphY3lt6XkvY0wMSIsIkZhY3RvcnlJZCI6IjEyMzQ1NiIsIlNpdGVJZCI6IjEyMzQ1NiIsIkNvZGUiOiJYWExQQUNLMDRBRTAzMiIsIm5iZiI6MTcwNDE4NzY5MCwiZXhwIjoyMTQ1NjkxNjkwLCJpc3MiOiJodHRwczovL3d3dy5oeW1zb24uY29tIiwiYXVkIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSJ9.An1BE7UgfcSP--LtTOmmmWVE2RQFPDahLkDg1xy5KqY"
  },
  "RobotTaskAddressRules": {
    "11068": [ "1", "2" ],
    "11001": [ "3", "1" ],
    "11010": [ "4", "2" ],
    "2101": [ "1", "3" ],
    "2103": [ "2", "4" ]
  },
  "A0GV_OutTaskComplete": "http://localhost:9999/OutTaskComplete", // ä¸ŠæŠ¥AGV出库输送线完成
Code/docs/superpowers/specs/2026-04-23-local-robot-task-creation-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
# æœ¬åœ°åˆ›å»ºæœºå™¨äººä»»åŠ¡è®¾è®¡
## èƒŒæ™¯
`RobotTaskService.GetWMSRobotTask` å½“前通过 HTTP è°ƒç”¨ WMS èŽ·å–æœºå™¨äººä»»åŠ¡æ•°æ®ï¼Œå†å†™å…¥æœ¬åœ°æ•°æ®åº“ã€‚éœ€æ±‚æ˜¯è·³è¿‡ WMS è°ƒç”¨ï¼Œç›´æŽ¥åœ¨æœ¬åœ°åˆ›å»º `Dt_RobotTask`。
## æ–¹æ¡ˆ
新增 `CreateLocalRobotTask(Dt_Task task)` æ–¹æ³•,保留原 `GetWMSRobotTask` ä¸åˆ é™¤ã€‚
### æ”¹åŠ¨æ–‡ä»¶
1. **RobotTaskService.cs** â€” æ–°å¢ž `CreateLocalRobotTask` å’Œè¾…助方法 `MapConfigKeyToRobotTaskType`
2. **IRobotTaskService.cs** â€” æŽ¥å£æ–°å¢žæ–¹æ³•声明
3. **OutboundTaskFlowService.cs** â€” è°ƒç”¨ç‚¹ä»Ž `GetWMSRobotTask` æ”¹ä¸º `CreateLocalRobotTask`
### æ–¹æ³•逻辑
1. å¤ç”¨ `ResolveRobotTaskConfigKey` è§£æžä»»åŠ¡ç±»åž‹é…ç½®é”®
2. å¤ç”¨ `BuildRobotTaskStock` æž„建 StockDTO
3. æ–°å¢ž `MapConfigKeyToRobotTaskType` æ˜ å°„配置键到 `RobotTaskTypeEnum`:
   - `CreateRobotChangePalletTask` â†’ `ChangePallet` (510)
   - `CreateRobotGroupPalletTask` â†’ `GroupPallet` (500)
   - `CreateRobotSplitPalletTask` â†’ `SplitPallet` (520)
4. æž„建 `Dt_RobotTask`:
   - `RobotTaskNum` = `Random.Shared.Next()`
   - `RobotSourceAddress` / `RobotTargetAddress` = `task.SourceAddress` / `task.TargetAddress`
   - `RobotSourceAddressLineCode` / `RobotTargetAddressLineCode` = stock å¯¹åº”字段
   - `RobotRoadway` = `stock.Roadway`
   - `RobotSourceAddressPalletCode` / `RobotTargetAddressPalletCode` = stock å¯¹åº”字段
   - `RobotTaskType` = æ˜ å°„后的枚举值
   - `RobotTaskState` = `RobotNew` (300)
   - `RobotGrade` = `task.Grade`
   - `Creater` = `"WCS_Local"`
   - `RobotTaskTotalNum` = 1
5. `BaseDal.AddData` æ’入数据库
6. `_taskExecuteDetailService.AddTaskExecuteDetail` è®°å½•执行明细
### ä¸åŠ¨çš„éƒ¨åˆ†
- `GetWMSRobotTask` å’Œ `ReceiveWMSTask` ä¿æŒåŽŸæ ·
- `ResolveRobotTaskConfigKey`、`BuildRobotTaskStock` ç­‰çŽ°æœ‰æ–¹æ³•å¤ç”¨ï¼Œä¸ä¿®æ”¹
- é…ç½®æ–‡ä»¶ `appsettings.json` ä¸æ”¹