wanshenmean
18 小时以前 1515ffa15c11e106f35e1447bc990b7867c449bb
feat(Robot): 机械手换盘任务特殊处理

feat(WMS): 更新机器人任务地址规则配置

fix: 修复临时文档文件变更问题
chore: 更新系统状态和代理跟踪信息

feat(Robot): RobotWorkflowOrchestrator 重写 HandlePutFinishedStateAsync 和 HandlePickFinishedStateAsync 实现批次指令与双流向

feat(Robot): RobotPrefixCommandHandler 换盘任务根据阶段区分处理逻辑

feat(Robot): RobotSimpleCommandHandler 换盘任务添加 ChangePalletPhase 阶段守卫

feat(Robot): 假电芯仓储层新增 MarkAsAvailable 方法

feat(Robot): RobotSocketState 新增 CurrentBatchIndex 和 ChangePalletPhase

docs: 修复实现计划逻辑错误(批次计算、阶段过渡)

- Flow B Phase 2 batchStart 改为基于 currentCompletedCount 推导
- 移除 Phase 1 中冗余的递归和 remainingNormal<=0 检查
- 阶段切换统一在 Phase 2 中处理(避免重复进入)
- 添加 null-safe 注释

docs: 更新换盘任务批次指令实现计划(修复review问题)

- 删除重复的Task5(依赖已存在)
- 修复Task7 HandlePutFinishedAsync逻辑错误
- 修复流向B Phase2 batchStart计算错误
- 新增Task8 HandlePickFinishedStateAsync重写
- 修复Task9依赖注入注册方式
- 修正所有HIGH/CRITICAL问题

docs: 修复设计规格review问题(流向判断、MarkAsAvailable、阶段转换条件)

docs: 添加换盘任务批次指令与双流向设计规格
已添加5个文件
已删除1个文件
已修改16个文件
2115 ■■■■■ 文件已修改
Code/.omc/state/agent-replay-358cfb75-d493-40fb-94ed-f795f943182b.jsonl 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/checkpoints/checkpoint-2026-04-16T05-56-05-020Z.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/idle-notif-cooldown.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/last-tool-error.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/mission-state.json 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/subagent-tracking.json 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 66 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 267 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-16-change-pallet-batch-command.md 1054 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-16-change-pallet-batch-command-design.md 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/技术协议/~$验证平台物流仓储系统技术规格书-11.24.docx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/机械手协议/~$交互流程表(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/机械手协议/交互流程表(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/agent-replay-358cfb75-d493-40fb-94ed-f795f943182b.jsonl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:brainstorming"}
{"t":0,"agent":"aab4d29","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"aab4d29","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":301448}
{"t":0,"agent":"a3ca65d","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a3ca65d","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":110707}
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:writing-plans"}
{"t":0,"agent":"a8078d8","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a8078d8","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":48817}
{"t":0,"agent":"a789651","agent_type":"code-reviewer","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a789651","agent_type":"code-reviewer","event":"agent_stop","success":true,"duration_ms":363405}
{"t":0,"agent":"system","event":"skill_invoked","skill_name":"superpowers:subagent-driven-development"}
{"t":0,"agent":"a9cfe1f","agent_type":"unknown","event":"agent_stop","success":true}
{"t":0,"agent":"a089308","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"a089308","agent_type":"general-purpose","event":"agent_stop","success":true,"duration_ms":171986}
{"t":0,"agent":"a4b1abc","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"ab8096e","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
{"t":0,"agent":"adc57d5","agent_type":"general-purpose","event":"agent_start","parent_mode":"none"}
Code/.omc/state/checkpoints/checkpoint-2026-04-16T05-56-05-020Z.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
{
  "created_at": "2026-04-16T05:56:05.018Z",
  "trigger": "auto",
  "active_modes": {},
  "todo_summary": {
    "pending": 0,
    "in_progress": 0,
    "completed": 0
  },
  "wisdom_exported": false,
  "background_jobs": {
    "active": [],
    "recent": [],
    "stats": null
  }
}
Code/.omc/state/idle-notif-cooldown.json
@@ -1,3 +1,3 @@
{
  "lastSentAt": "2026-04-15T14:42:46.838Z"
  "lastSentAt": "2026-04-16T07:39:40.298Z"
}
Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@
{
  "tool_name": "Bash",
  "tool_input_preview": "{\"command\":\"dotnet build D:/Git/ShanMeiXinNengYuan/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln\",\"timeout\":300000,\"description\":\"Build WCS solution to verify fix\"}",
  "error": "Exit code 1\n  æ­£åœ¨ç¡®å®šè¦è¿˜åŽŸçš„é¡¹ç›®â€¦\r\n  æ‰€æœ‰é¡¹ç›®å‡æ˜¯æœ€æ–°çš„,无法还原。\r\n  WIDESEAWCS_Common -> D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Common\\bin\\Debug\\net8.0\\WIDESEAWCS_Common.dll\r\nD:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\AllenBrandly\\AllenBrandlyEtherNetCommunicator.cs(110,80): warning CS1570: XML æ³¨é‡Šå‡ºçް XML æ ¼å¼é”™è¯¯ --“结束标记“param”与开始标记“T”不匹配。” [D:\\Git\\ShanMeiXinNengYuan\\Code\\WCS\\WIDESEAWCS_Server\\WIDESEAWCS_Communicator\\WIDESEAWCS_Communicator.csproj]\r\nD:\\Git\\ShanM...",
  "timestamp": "2026-04-15T14:12:47.386Z",
  "tool_input_preview": "{\"command\":\"cd /d D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WCS\\\\WIDESEAWCS_Server && dotnet build WIDESEAWCS_Server.sln 2>&1\",\"timeout\":120000,\"description\":\"Build WCS solution to verify changes\"}",
  "error": "Exit code 1\n/usr/bin/bash: line 1: cd: too many arguments",
  "timestamp": "2026-04-16T06:02:11.459Z",
  "retry_count": 1
}
Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@
{
  "updatedAt": "2026-04-15T14:16:41.286Z",
  "updatedAt": "2026-04-16T06:05:51.361Z",
  "missions": [
    {
      "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none",
@@ -620,6 +620,132 @@
          "sourceKey": "session-stop:a991ee262522932f5"
        }
      ]
    },
    {
      "id": "session:358cfb75-d493-40fb-94ed-f795f943182b:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-16T02:59:26.042Z",
      "updatedAt": "2026-04-16T06:05:51.361Z",
      "status": "running",
      "workerCount": 8,
      "taskCounts": {
        "total": 8,
        "pending": 0,
        "blocked": 0,
        "inProgress": 3,
        "completed": 5,
        "failed": 0
      },
      "agents": [
        {
          "name": "code-reviewer:aab4d29",
          "role": "code-reviewer",
          "ownership": "aab4d29faa1a3cd33",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-16T05:57:18.762Z"
        },
        {
          "name": "code-reviewer:a3ca65d",
          "role": "code-reviewer",
          "ownership": "a3ca65d5e9e566acc",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-16T03:21:01.279Z"
        },
        {
          "name": "code-reviewer:a8078d8",
          "role": "code-reviewer",
          "ownership": "a8078d862c25a2973",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-16T05:40:16.461Z"
        },
        {
          "name": "code-reviewer:a789651",
          "role": "code-reviewer",
          "ownership": "a78965129a4ecaee2",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-16T05:50:59.155Z"
        },
        {
          "name": "general-purpose:a089308",
          "role": "general-purpose",
          "ownership": "a089308fce13261b6",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-16T06:00:35.344Z"
        },
        {
          "name": "general-purpose:a4b1abc",
          "role": "general-purpose",
          "ownership": "a4b1abcbbe45f220a",
          "status": "running",
          "currentStep": null,
          "latestUpdate": null,
          "completedSummary": null,
          "updatedAt": "2026-04-16T06:00:51.582Z"
        },
        {
          "name": "general-purpose:ab8096e",
          "role": "general-purpose",
          "ownership": "ab8096ee80268e720",
          "status": "running",
          "currentStep": null,
          "latestUpdate": null,
          "completedSummary": null,
          "updatedAt": "2026-04-16T06:05:29.655Z"
        },
        {
          "name": "general-purpose:adc57d5",
          "role": "general-purpose",
          "ownership": "adc57d5ba6bbc20c1",
          "status": "running",
          "currentStep": null,
          "latestUpdate": null,
          "completedSummary": null,
          "updatedAt": "2026-04-16T06:05:51.361Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a4b1abcbbe45f220a:2026-04-16T06:00:51.582Z",
          "at": "2026-04-16T06:00:51.582Z",
          "kind": "update",
          "agent": "general-purpose:a4b1abc",
          "detail": "started general-purpose:a4b1abc",
          "sourceKey": "session-start:a4b1abcbbe45f220a"
        },
        {
          "id": "session-start:ab8096ee80268e720:2026-04-16T06:05:29.655Z",
          "at": "2026-04-16T06:05:29.655Z",
          "kind": "update",
          "agent": "general-purpose:ab8096e",
          "detail": "started general-purpose:ab8096e",
          "sourceKey": "session-start:ab8096ee80268e720"
        },
        {
          "id": "session-start:adc57d5ba6bbc20c1:2026-04-16T06:05:51.361Z",
          "at": "2026-04-16T06:05:51.361Z",
          "kind": "update",
          "agent": "general-purpose:adc57d5",
          "detail": "started general-purpose:adc57d5",
          "sourceKey": "session-start:adc57d5ba6bbc20c1"
        }
      ]
    }
  ]
}
Code/.omc/state/subagent-tracking.json
@@ -377,10 +377,76 @@
      "status": "completed",
      "completed_at": "2026-04-15T14:16:41.286Z",
      "duration_ms": 246360
    },
    {
      "agent_id": "aab4d29faa1a3cd33",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-16T02:59:26.042Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-16T03:04:27.490Z",
      "duration_ms": 301448
    },
    {
      "agent_id": "a3ca65d5e9e566acc",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-16T03:19:10.572Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-16T03:21:01.279Z",
      "duration_ms": 110707
    },
    {
      "agent_id": "a8078d862c25a2973",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-16T05:39:27.644Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-16T05:40:16.461Z",
      "duration_ms": 48817
    },
    {
      "agent_id": "a78965129a4ecaee2",
      "agent_type": "code-reviewer",
      "started_at": "2026-04-16T05:44:55.750Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-16T05:50:59.155Z",
      "duration_ms": 363405
    },
    {
      "agent_id": "a089308fce13261b6",
      "agent_type": "general-purpose",
      "started_at": "2026-04-16T05:57:43.358Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-16T06:00:35.344Z",
      "duration_ms": 171986
    },
    {
      "agent_id": "a4b1abcbbe45f220a",
      "agent_type": "general-purpose",
      "started_at": "2026-04-16T06:00:51.582Z",
      "parent_mode": "none",
      "status": "running"
    },
    {
      "agent_id": "ab8096ee80268e720",
      "agent_type": "general-purpose",
      "started_at": "2026-04-16T06:05:29.655Z",
      "parent_mode": "none",
      "status": "running"
    },
    {
      "agent_id": "adc57d5ba6bbc20c1",
      "agent_type": "general-purpose",
      "started_at": "2026-04-16T06:05:51.361Z",
      "parent_mode": "none",
      "status": "running"
    }
  ],
  "total_spawned": 42,
  "total_completed": 42,
  "total_spawned": 50,
  "total_completed": 47,
  "total_failed": 0,
  "last_updated": "2026-04-15T14:16:41.394Z"
  "last_updated": "2026-04-16T06:05:51.474Z"
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
@@ -29,6 +29,13 @@
        bool MarkAsUsed(List<int> positions);
        /// <summary>
        /// æ ‡è®°æŒ‡å®šç‚¹ä½ä¸ºå¯ç”¨ï¼ˆé‡Šæ”¾ç‚¹ä½ï¼‰
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsAvailable(List<int> positions);
        /// <summary>
        /// æ ¹æ®è¡Œå’Œåˆ—获取点位索引
        /// </summary>
        /// <param name="row">行(1-3)</param>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
@@ -29,6 +29,13 @@
        bool MarkAsUsed(List<int> positions);
        /// <summary>
        /// æ ‡è®°æŒ‡å®šç‚¹ä½ä¸ºå¯ç”¨ï¼ˆé‡Šæ”¾ç‚¹ä½ï¼‰
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsAvailable(List<int> positions);
        /// <summary>
        /// æ ¹æ®è¡Œå’Œåˆ—获取点位索引
        /// </summary>
        /// <param name="row">行(1-3)</param>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
@@ -78,6 +78,18 @@
        }
        /// <inheritdoc/>
        public bool MarkAsAvailable(List<int> positions)
        {
            if (positions == null || positions.Count == 0)
                return true;
            return Db.Updateable<Dt_FakeBatteryPosition>()
                .SetColumns(x => x.IsUsed, false)
                .Where(x => positions.Contains(x.PositionIndex))
                .ExecuteCommand() > 0;
        }
        /// <inheritdoc/>
        public int? GetPositionIndex(int row, int col)
        {
            var entity = Db.Queryable<Dt_FakeBatteryPosition>()
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
@@ -33,6 +33,12 @@
        }
        /// <inheritdoc/>
        public bool MarkAsAvailable(List<int> positions)
        {
            return BaseDal.MarkAsAvailable(positions);
        }
        /// <inheritdoc/>
        public int? GetPositionIndex(int row, int col)
        {
            return BaseDal.GetPositionIndex(row, col);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -125,7 +125,7 @@
            // ç®€å•命令处理器:处理状态更新等简单命令
            var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor, socketGateway);
            // å‰ç¼€å‘½ä»¤å¤„理器:处理 pickfinished、putfinished ç­‰å¸¦å‚数的命令
            var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway);
            var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway, fakeBatteryPositionService);
            // åˆå§‹åŒ–消息路由器
            _messageRouter = new RobotMessageHandler(socketGateway, _stateManager, cache, simpleCommandHandler, prefixCommandHandler, logger);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -181,5 +181,27 @@
        /// å½“正常电芯任务完成后设为 true,机器人从假电芯位置补充电芯至48个。
        /// </remarks>
        public bool IsInFakeBatteryMode { get; set; }
        /// <summary>
        /// å½“前批次起始编号(用于递增计算取货/放货编号)
        /// </summary>
        /// <remarks>
        /// åœ¨æ‰¹æ¬¡æ¨¡å¼ä¸‹ï¼Œæ¯æ‰¹å–è´§/放货的起始编号从1开始递增。
        /// ç”¨äºŽè®¡ç®— {start}-{end} æ ¼å¼ä¸­çš„ start å€¼ã€‚
        /// </remarks>
        public int CurrentBatchIndex { get; set; } = 1;
        /// <summary>
        /// æ¢ç›˜ä»»åŠ¡å½“å‰é˜¶æ®µ
        /// </summary>
        /// <remarks>
        /// é˜¶æ®µå®šä¹‰ï¼š
        /// 0: æœªå¼€å§‹
        /// 1: å–正常电芯(流向B) / å–假电芯(流向A)
        /// 2: æ”¾æ­£å¸¸ç”µèŠ¯ï¼ˆæµå‘B) / æ”¾å‡ç”µèŠ¯ï¼ˆæµå‘A)
        /// 3: å–假电芯(流向B Phase2)
        /// 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(流向B Phase2)
        /// </remarks>
        public int ChangePalletPhase { get; set; }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -268,6 +268,125 @@
        }
        /// <summary>
        /// è®¡ç®—批次编号范围
        /// </summary>
        /// <remarks>
        /// è¿”回格式:(start, end)
        /// - remaining >= 4: (currentIndex, currentIndex + 3)
        /// - remaining > 1: (currentIndex, currentIndex + remaining - 1)
        /// - remaining == 1: (currentIndex, 0)  -- å•个物品用 0 è¡¨ç¤º end
        /// </remarks>
        /// <param name="currentIndex">当前批次起始编号</param>
        /// <param name="remaining">剩余数量</param>
        /// <returns>(start, end) å…ƒç»„</returns>
        public (int Start, int End) BuildBatchRange(int currentIndex, int remaining)
        {
            if (remaining >= 4)
                return (currentIndex, currentIndex + 3);
            else if (remaining > 1)
                return (currentIndex, currentIndex + remaining - 1);
            else  // remaining == 1
                return (currentIndex, 0);
        }
        /// <summary>
        /// ä¸‹å‘取货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// å‘送顺序:
        /// 1. PickTotalNum,{N} -- çœŸå®žç”µèŠ¯æ€»æ•°
        /// 2. Pickbattery,{位置},{start}-{end} -- æ‰¹æ¬¡å–货指令
        ///
        /// ä¸‹å‘成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">取货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // å…ˆå‘送总数指令
            string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // å†å‘送批次取货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Pickbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发批次取货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发批次取货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发批次取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发批次取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// ä¸‹å‘放货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// å‘送顺序:
        /// 1. PutTotalNum,{N} -- çœŸå®žç”µèŠ¯æ€»æ•°
        /// 2. Putbattery,{位置},{start}-{end} -- æ‰¹æ¬¡æ”¾è´§æŒ‡ä»¤
        ///
        /// ä¸‹å‘成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">放货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // å…ˆå‘送总数指令
            string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // å†å‘送批次放货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Putbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发放货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发放货指令失败,指令: {TaskString},设备: {DeviceName}",
                    taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// å¤„理入库任务回传(拆盘/组盘/换盘场景)
        /// </summary>
        /// <remarks>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -58,22 +58,33 @@
        private readonly ISocketClientGateway _socketClientGateway;
        /// <summary>
        /// å‡ç”µèŠ¯ä½ç½®æœåŠ¡
        /// </summary>
        /// <remarks>
        /// ç”¨äºŽé‡Šæ”¾å‡ç”µèŠ¯ç‚¹ä½ã€‚
        /// </remarks>
        private readonly IFakeBatteryPositionService _fakeBatteryPositionService;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="robotTaskService">任务服务</param>
        /// <param name="taskProcessor">任务处理器</param>
        /// <param name="stateManager">状态管理器</param>
        /// <param name="socketClientGateway">Socket ç½‘å…³</param>
        /// <param name="fakeBatteryPositionService">假电芯位置服务</param>
        public RobotPrefixCommandHandler(
            IRobotTaskService robotTaskService,
            RobotTaskProcessor taskProcessor,
            RobotStateManager stateManager,
            ISocketClientGateway socketClientGateway)
            ISocketClientGateway socketClientGateway,
            IFakeBatteryPositionService fakeBatteryPositionService)
        {
            _robotTaskService = robotTaskService;
            _taskProcessor = taskProcessor;
            _stateManager = stateManager;
            _socketClientGateway = socketClientGateway;
            _fakeBatteryPositionService = fakeBatteryPositionService;
        }
        /// <summary>
@@ -162,24 +173,31 @@
        /// <remarks>
        /// å¤„理逻辑:
        /// 1. å¦‚果是拆盘任务,构建库存 DTO å¹¶è°ƒç”¨æ‹†ç›˜ API
        /// 2. æ›´æ–°å½“前动作为"取货完成"
        /// 3. è®°å½•取货完成的位置
        /// 4. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸º"机器人取货完成"
        /// 5. å®‰å…¨æ›´æ–°çŠ¶æ€åˆ° Redis
        /// 2. æ¢ç›˜ä»»åŠ¡ Phase3 å–假电芯时不调用拆盘 API
        /// 3. æ›´æ–°å½“前动作为"取货完成"
        /// 4. è®°å½•取货完成的位置
        /// 5. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸º"机器人取货完成"
        /// 6. å®‰å…¨æ›´æ–°çŠ¶æ€åˆ° Redis
        /// </remarks>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">取货完成的位置编号数组</param>
        /// <param name="task">机器人任务记录</param>
        private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
        {
            // å¦‚果是拆盘任务
            if (state.IsSplitPallet)
            // è®°å½•取货完成的位置
            state.LastPickPositions = positions;
            // æ¢ç›˜ä»»åŠ¡ Phase3 å–假电芯:不调用拆盘 API
            if (state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
                && state.ChangePalletPhase == 3)
            {
                state.CurrentAction = "PickFinished";
            }
            // æ‹†ç›˜ä»»åŠ¡
            else if (state.IsSplitPallet)
            {
                // æž„建库存 DTO,包含位置信息和托盘条码
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                // è®°å½•取货完成的位置
                state.LastPickPositions = positions;
                // è°ƒç”¨æ‹†ç›˜ API
                var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
@@ -187,7 +205,6 @@
                // å¦‚æžœ API è°ƒç”¨æˆåŠŸ
                if (result.Data.Status && result.IsSuccess)
                {
                    // æ›´æ–°å½“前动作为"取货完成"
                    state.CurrentAction = "PickFinished";
                }
            }
@@ -196,9 +213,6 @@
                // éžæ‹†ç›˜ä»»åŠ¡ï¼Œç›´æŽ¥æ›´æ–°åŠ¨ä½œ
                state.CurrentAction = "PickFinished";
            }
            // è®°å½•取货完成的位置(无论是否拆盘都记录)
            state.LastPickPositions = positions;
            // å¦‚果任务存在
            if (task != null)
@@ -220,10 +234,11 @@
        /// <remarks>
        /// å¤„理逻辑:
        /// 1. å¦‚果是组盘任务,构建库存 DTO å¹¶è°ƒç”¨ç»„盘/换盘 API
        /// 2. å¦‚果组盘成功,增加任务计数
        /// 3. æ›´æ–°å½“前动作为"放货完成"
        /// 4. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸º"机器人放货完成"
        /// 5. å®‰å…¨æ›´æ–°çŠ¶æ€åˆ° Redis
        /// 2. æ¢ç›˜ä»»åŠ¡æ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†ï¼šæµå‘A Phase2 ä¸è°ƒç”¨ API;流向B Phase2 æ­£å¸¸è°ƒç”¨ï¼›Phase4 è°ƒç”¨ MarkAsAvailable
        /// 3. å¦‚果组盘成功,增加任务计数
        /// 4. æ›´æ–°å½“前动作为"放货完成"
        /// 5. æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸º"机器人放货完成"
        /// 6. å®‰å…¨æ›´æ–°çŠ¶æ€åˆ° Redis
        /// </remarks>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">放货完成的位置编号数组</param>
@@ -239,20 +254,62 @@
                // è®°å½•放货完成的位置
                state.LastPutPositions = positions;
                // æž„建库存 DTO
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                // åˆ¤æ–­æ˜¯å¦ä¸ºæ¢ç›˜ä»»åŠ¡
                var isChangePallet = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
                var isFlowA = state.CurrentTask?.RobotSourceAddressLineCode is "11001" or "11010";
                // æ ¹æ®ä»»åŠ¡ç±»åž‹å†³å®šè°ƒç”¨å“ªä¸ª API
                // æ¢ç›˜ä»»åŠ¡è°ƒç”¨ ChangePalletAsync,组盘任务调用 GroupPalletAsync
                var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
                    ? nameof(ConfigKey.ChangePalletAsync)
                    : nameof(ConfigKey.GroupPalletAsync);
                if (isChangePallet)
                {
                    // æ¢ç›˜ä»»åŠ¡ï¼šæ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†
                    if (state.ChangePalletPhase == 2)
                    {
                        if (isFlowA)
                        {
                            // æµå‘A Phase2:放假电芯到目标托盘,不调用 API,不递增计数
                            // ä»…更新状态
                        }
                        else
                        {
                            // æµå‘B Phase2:放正常电芯,递增计数
                            state.RobotTaskTotalNum += positions.Length;
                            if (task != null)
                                task.RobotTaskTotalNum -= positions.Length;
                // è°ƒç”¨ç»„盘/换盘 API
                var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
                            // æž„建库存 DTO å¹¶è°ƒç”¨ ChangePalletAsync API
                            var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                            var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.ChangePalletAsync), stockDTO);
                            putSuccess = result.Data.Status && result.IsSuccess;
                        }
                    }
                    else if (state.ChangePalletPhase == 4)
                    {
                        // æµå‘B Phase4:放假电芯到5号位,不调用 API,不递增计数,释放点位
                        _fakeBatteryPositionService.MarkAsAvailable(positions.ToList());
                    }
                    else
                    {
                        // éžæ‰¹æ¬¡æ¨¡å¼ï¼šæ­£å¸¸é€’增计数并调用 API
                        state.RobotTaskTotalNum += positions.Length;
                        if (task != null)
                            task.RobotTaskTotalNum -= positions.Length;
                // æ£€æŸ¥ API è¿”回状态
                putSuccess = result.Data.Status && result.IsSuccess;
                        var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                        var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.GroupPalletAsync), stockDTO);
                        putSuccess = result.Data.Status && result.IsSuccess;
                    }
                }
                else
                {
                    // ç»„盘任务:原有逻辑
                    var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                    var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.GroupPalletAsync), stockDTO);
                    putSuccess = result.Data.Status && result.IsSuccess;
                    // å¢žåŠ ä»»åŠ¡è®¡æ•°
                    state.RobotTaskTotalNum += positions.Length;
                    if (task != null)
                        task.RobotTaskTotalNum -= positions.Length;
                }
            }
            // å¦‚果放货成功
@@ -261,13 +318,12 @@
                // æ›´æ–°å½“前动作为"放货完成"
                state.CurrentAction = "PutFinished";
                // å¢žåŠ ä»»åŠ¡è®¡æ•°ï¼ˆç´¯åŠ æœ¬æ¬¡å®Œæˆçš„æ•°é‡ï¼‰
                state.RobotTaskTotalNum += positions.Length;
                // å¦‚果任务存在,同步更新任务的计数
                if (task != null)
                // éžç»„盘任务时增加计数(组盘任务已在上面递增)
                if (!state.IsGroupPallet)
                {
                    task.RobotTaskTotalNum -= positions.Length;
                    state.RobotTaskTotalNum += positions.Length;
                    if (task != null)
                        task.RobotTaskTotalNum -= positions.Length;
                }
            }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -138,11 +138,33 @@
                        // åˆ¤æ–­ä»»åŠ¡ç±»åž‹
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // åªæœ‰æ‹†ç›˜æˆ–换盘任务需要处理入库
                        if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            // å¤„理入库任务回传
                            // useSourceAddress: true è¡¨ç¤ºä½¿ç”¨æºåœ°å€ï¼ˆæ‹†ç›˜/换盘场景)
                            if (state.ChangePalletPhase == 0)
                            {
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                                {
                                    // å…¥åº“成功,删除任务记录
                                    _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                    await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                                    QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                                    // é‡ç½®æ‰¹æ¬¡çŠ¶æ€
                                    state.ChangePalletPhase = 0;
                                    state.CurrentBatchIndex = 1;
                                    state.IsInFakeBatteryMode = false;
                                    return true;
                                }
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            return true;
                        }
                        // æ‹†ç›˜ä»»åŠ¡ï¼šç›´æŽ¥å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.SplitPallet)
                        {
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                            {
                                // å…¥åº“成功,删除任务记录
@@ -171,11 +193,41 @@
                        // åˆ¤æ–­ä»»åŠ¡ç±»åž‹
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // åªæœ‰ç»„盘或换盘任务需要处理入库
                        if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            if (state.ChangePalletPhase == 0)
                            {
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                                {
                                    // å…¥åº“成功,删除任务记录
                                    _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                    // æ¸…理状态,为下一个任务做准备
                                    state.CurrentTask = null;           // æ¸…除当前任务
                                    state.RobotTaskTotalNum = 0;        // é‡ç½®ä»»åŠ¡è®¡æ•°
                                    state.CellBarcode = new List<string>();  // æ¸…空条码列表
                                    await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                                    QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
                                    // é‡ç½®æ‰¹æ¬¡çŠ¶æ€
                                    state.ChangePalletPhase = 0;
                                    state.CurrentBatchIndex = 1;
                                    state.IsInFakeBatteryMode = false;
                                    return true;
                                }
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            return true;
                        }
                        // ç»„盘任务:直接处理入库
                        if (robotTaskType == RobotTaskTypeEnum.GroupPallet)
                        {
                            // å¤„理入库任务回传
                            // useSourceAddress: false è¡¨ç¤ºä½¿ç”¨ç›®æ ‡åœ°å€ï¼ˆç»„盘/换盘场景)
                            // useSourceAddress: false è¡¨ç¤ºä½¿ç”¨ç›®æ ‡åœ°å€ï¼ˆç»„盘场景)
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                            {
                                // å…¥åº“成功,删除任务记录
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -152,7 +152,7 @@
        /// </summary>
        /// <remarks>
        /// å½“取货完成后,向机器人发送放货指令(Putbattery)。
        /// æœºå™¨äººæ”¶åˆ°æŒ‡ä»¤åŽä¼šå°†è´§ç‰©æ”¾ç½®åˆ°ç›®æ ‡åœ°å€ã€‚
        /// æ¢ç›˜ä»»åŠ¡ä½¿ç”¨æ‰¹æ¬¡æ ¼å¼ SendPutWithBatchAsync。
        ///
        /// æŒ‡ä»¤æ ¼å¼ï¼šPutbattery,{目标地址}
        /// ä¾‹å¦‚:Putbattery,B01 è¡¨ç¤ºå°†è´§ç‰©æ”¾ç½®åˆ° B01 ä½ç½®
@@ -161,38 +161,99 @@
        /// <param name="ipAddress">机器人 IP åœ°å€</param>
        private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
        {
            // æž„建放货指令,格式:Putbattery,{目标地址}
            string taskString = $"Putbattery,{task.RobotTargetAddress}";
            string taskString;
            // é€šè¿‡å®¢æˆ·ç«¯ç®¡ç†å™¨å‘送指令到机器人
            // æ¢ç›˜ä»»åŠ¡ä½¿ç”¨æ‰¹æ¬¡æ ¼å¼
            if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                int targetNormalCount = task.RobotTaskTotalNum;
                var state = _stateManager.GetState(ipAddress);
                int currentCompletedCount = state?.RobotTaskTotalNum ?? 0;
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // æµå‘A Phase 2:放假电芯到目标托盘
                if (isFlowA && state?.ChangePalletPhase == 2)
                {
                    int remaining = 48 - currentCompletedCount;
                    if (remaining <= 0) return;
                    int batchStart = targetNormalCount + 1 + (state.CurrentBatchIndex - 1);
                    int putCount = Math.Min(4, remaining);
                    var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                    await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
                    return;
                }
                // æµå‘B Phase 4:放假电芯到5号位
                if (!isFlowA && state?.ChangePalletPhase == 4)
                {
                    int fakeCount = 48 - targetNormalCount;
                    int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                    int remainingFake = fakeCount - completedFake;
                    if (remainingFake <= 0) return;
                    var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
                    if (positions.Count == 0)
                    {
                        _logger.LogError("HandlePickFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                        return;
                    }
                    int start = positions.Min();
                    int end = positions.Max();
                    await _taskProcessor.SendPutWithBatchAsync(task, state, "5", start, end);
                    return;
                }
                // æµå‘B Phase 2:放正常电芯到目标托盘
                if (!isFlowA && state?.ChangePalletPhase == 2)
                {
                    int remainingNormal = targetNormalCount - currentCompletedCount;
                    if (remainingNormal <= 0) return;
                    int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1;
                    int putCount = Math.Min(4, remainingNormal);
                    var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                    await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
                    return;
                }
                // é»˜è®¤ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
                taskString = $"Putbattery,{task.RobotTargetAddress}";
            }
            else
            {
                // éžæ¢ç›˜ä»»åŠ¡ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
                taskString = $"Putbattery,{task.RobotTargetAddress}";
            }
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
            if (result)
            {
                // å‘送成功,记录 Info æ—¥å¿—
                _logger.LogInformation("HandlePickFinishedStateAsync:下发放货指令成功,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString}", task.RobotRoadway);
                // æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸º"机器人执行中"
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                // èŽ·å–æœ€æ–°çŠ¶æ€å¹¶æ›´æ–°ä»»åŠ¡å…³è”
                var stateToUpdate = _stateManager.GetState(ipAddress);
                if (stateToUpdate != null)
                {
                    stateToUpdate.CurrentTask = task;
                    // å®‰å…¨æ›´æ–°çŠ¶æ€åˆ° Redis
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                    {
                        // çŠ¶æ€æ›´æ–°æˆåŠŸåŽï¼Œæ›´æ–°ä»»åŠ¡è®°å½•
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                    }
                }
            }
            else
            {
                // å‘送失败,记录 Error æ—¥å¿—
                _logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway);
            }
@@ -284,61 +345,179 @@
            }
            else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                // æ¢ç›˜ä»»åŠ¡
                // ç›®æ ‡ï¼šæ­£å¸¸ç”µèŠ¯æŠ“å–å®ŒæˆåŽï¼Œè¡¥å……å‡ç”µèŠ¯è‡³48个
                const int targetTotal = 48;
                const int fakeBatteryPickPosition = 5;  // å‡ç”µèŠ¯æŠ“å–ä½ç½®
                const int pickCountPerExecution = 4;     // æ¯æ¬¡æŠ“取数量
                int targetNormalCount = task.RobotTaskTotalNum;
                int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;
                int targetNormalCount = task.RobotTaskTotalNum;  // æ­£å¸¸ç”µèŠ¯ç›®æ ‡æ•°é‡
                int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;  // å·²å®Œæˆæ•°é‡
                // åˆ¤æ–­æµå‘(null-safe)
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // å¦‚果目标数量为48,直接下发正常任务
                // ç›®æ ‡æ•°é‡ä¸º48:直接走原有逻辑,不进入批次模式
                if (targetNormalCount == targetTotal)
                {
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
                // å¦‚果已完成数量小于目标数量,继续抓取正常电芯
                else if (currentCompletedCount < targetNormalCount)
                {
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
                // æ­£å¸¸ç”µèŠ¯å·²å®Œæˆï¼Œè¿›å…¥å‡ç”µèŠ¯è¡¥å……æ¨¡å¼
                else if (currentCompletedCount == targetNormalCount && !stateForUpdate.IsInFakeBatteryMode)
                {
                    // é¦–次进入假电芯模式,设置标志
                    stateForUpdate.IsInFakeBatteryMode = true;
                    _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯抓取完成,进入假电芯补充模式,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"正常电芯抓取完成,进入假电芯补充模式", stateForUpdate.RobotCrane?.DeviceName);
                    return;
                }
                // å¦‚果处于假电芯补充模式,计算并下发补数任务
                if (stateForUpdate.IsInFakeBatteryMode)
                // åˆå§‹åŒ–批次模式
                if (stateForUpdate.ChangePalletPhase == 0)
                {
                    int remaining = targetTotal - currentCompletedCount;
                    if (remaining > 0)
                    stateForUpdate.ChangePalletPhase = 1;
                    stateForUpdate.CurrentBatchIndex = 1;
                    _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务进入批次模式,任务号: {TaskNum},流向: {Flow}",
                        task.RobotTaskNum, isFlowA ? "A" : "B");
                }
                // ==================== æµå‘A:补假电芯到目标托盘 ====================
                if (isFlowA)
                {
                    // Phase 1: å–假电芯(从5号位,使用 PositionIndex)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    {
                        // è®¡ç®—每次抓取的数量(最多4个)
                        int pickCount = Math.Min(pickCountPerExecution, remaining);
                        int remaining = targetTotal - currentCompletedCount;
                        if (remaining <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        // èŽ·å–å¯ç”¨çš„å‡ç”µèŠ¯å¹³é¢ç‚¹ä½
                        int pickCount = Math.Min(4, remaining);
                        var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount);
                        if (positions.Count == 0)
                        {
                            _logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                            QuartzLogger.Error($"无可用假电芯点位", stateForUpdate.RobotCrane?.DeviceName);
                            return;
                        }
                        // ä¸‹å‘假电芯取货指令
                        await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions);
                        stateForUpdate.ChangePalletPhase = 2;
                    }
                    else
                    // Phase 2: æ”¾å‡ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆä»Ž targetNormalCount+1 å¼€å§‹é€’增)
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    {
                        // å‡ç”µèŠ¯è¡¥å……å®Œæˆï¼Œé‡ç½®æ ‡å¿—
                        stateForUpdate.IsInFakeBatteryMode = false;
                        _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务完成,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"换盘任务完成", stateForUpdate.RobotCrane?.DeviceName);
                        int remaining = targetTotal - currentCompletedCount;
                        if (remaining <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        // è®¡ç®—放货批次编号:从 targetNormalCount + 1 å¼€å§‹
                        int batchStart = targetNormalCount + 1 + (stateForUpdate.CurrentBatchIndex - 1);
                        int putCount = Math.Min(4, remaining);
                        var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += putCount;
                        stateForUpdate.ChangePalletPhase = 1;
                    }
                }
                // ==================== æµå‘B:取正常电芯 + å›žæ”¶å‡ç”µèН ====================
                else
                {
                    // Phase 1: å–正常电芯(从源地址,从1开始递增)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    {
                        int remainingNormal = targetNormalCount - currentCompletedCount;
                        if (remainingNormal <= 0)
                        {
                            // æ­£å¸¸ç”µèŠ¯å–å®Œï¼Œåˆ‡æ¢åˆ° Phase 3
                            stateForUpdate.ChangePalletPhase = 3;
                            stateForUpdate.CurrentBatchIndex = targetNormalCount + 1;
                            _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯全部取完,进入Phase 3回收假电芯,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int pickCount = Math.Min(4, remainingNormal);
                        var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += pickCount;
                        stateForUpdate.ChangePalletPhase = 2;
                    }
                    // Phase 2: æ”¾æ­£å¸¸ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆæ”¾è´§ç¼–å·ä¸Žå–è´§ç¼–å·ä¸€è‡´ï¼‰
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    {
                        int remainingNormal = targetNormalCount - currentCompletedCount;
                        if (remainingNormal <= 0)
                        {
                            // æ­£å¸¸ç”µèŠ¯æ”¾å®Œï¼Œåˆ‡æ¢åˆ° Phase 3
                            stateForUpdate.ChangePalletPhase = 3;
                            stateForUpdate.CurrentBatchIndex = targetNormalCount + 1;
                            _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯全部放完,进入Phase 3回收假电芯,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        // è®¡ç®—本批放货编号:基于 currentCompletedCount æŽ¨å¯¼æ‰¹æ¬¡èµ·å§‹
                        int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1;
                        int putCount = Math.Min(4, remainingNormal);
                        var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
                        stateForUpdate.ChangePalletPhase = 1;
                    }
                    // Phase 3: å–假电芯(从源地址,从 targetNormalCount+1 å¼€å§‹é€’增)
                    else if (stateForUpdate.ChangePalletPhase == 3)
                    {
                        int fakeCount = targetTotal - targetNormalCount;
                        int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                        int remainingFake = fakeCount - completedFake;
                        if (remainingFake <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int pickCount = Math.Min(4, remainingFake);
                        var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += pickCount;
                        stateForUpdate.ChangePalletPhase = 4;
                    }
                    // Phase 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(使用 PositionIndex)
                    else if (stateForUpdate.ChangePalletPhase == 4)
                    {
                        int fakeCount = targetTotal - targetNormalCount;
                        int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                        int remainingFake = fakeCount - completedFake;
                        if (remainingFake <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
                        if (positions.Count == 0)
                        {
                            _logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int start = positions.Min();
                        int end = positions.Max();
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, "5", start, end);
                        stateForUpdate.ChangePalletPhase = 3;
                    }
                }
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -75,7 +75,11 @@
    "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMwMTcyNzM5Mzk5NzYxOTIwIiwibmFtZSI6IlBBQ0voo4XphY3lt6XkvY0wMSIsIkZhY3RvcnlJZCI6IjEyMzQ1NiIsIlNpdGVJZCI6IjEyMzQ1NiIsIkNvZGUiOiJYWExQQUNLMDRBRTAzMiIsIm5iZiI6MTcwNDE4NzY5MCwiZXhwIjoyMTQ1NjkxNjkwLCJpc3MiOiJodHRwczovL3d3dy5oeW1zb24uY29tIiwiYXVkIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSJ9.An1BE7UgfcSP--LtTOmmmWVE2RQFPDahLkDg1xy5KqY"
  },
  "RobotTaskAddressRules": {
    "11068": [ "1", "2" ]
    "11068": [ "1", "2" ],
    "11001": [ "3", "1" ],
    "11010": [ "4", "2" ],
    "2101": [ "1", "3" ],
    "2103": [ "2", "4" ]
  },
Code/docs/superpowers/plans/2026-04-16-change-pallet-batch-command.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1054 @@
# æ¢ç›˜ä»»åŠ¡æ‰¹æ¬¡æŒ‡ä»¤ä¸ŽåŒæµå‘å®žçŽ°è®¡åˆ’
> **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:** å‡çº§æ¢ç›˜ä»»åŠ¡å–è´§/放货指令格式为批次模式,支持 PickTotalNum/PutTotalNum,并根据 RobotSourceAddressLineCode åŒºåˆ†ä¸¤ç§æµå‘
**Architecture:**
- RobotSocketState æ–°å¢žæ‰¹æ¬¡çŠ¶æ€å­—æ®µï¼ˆCurrentBatchIndex, ChangePalletPhase)
- RobotTaskProcessor æ–°å¢žæ‰¹æ¬¡æŒ‡ä»¤æž„建方法(BuildBatchRange, SendPickWithBatchAsync, SendPutWithBatchAsync)
- RobotWorkflowOrchestrator é‡å†™ HandlePutFinishedStateAsync å’Œ HandlePickFinishedStateAsync å®žçŽ°ä¸¤ç§æµå‘çš„çŠ¶æ€æœº
- RobotPrefixCommandHandler å’Œ RobotSimpleCommandHandler æ ¹æ®é˜¶æ®µåŒºåˆ†å¤„理逻辑
- å‡ç”µèŠ¯æœåŠ¡å±‚æ–°å¢ž MarkAsAvailable æ–¹æ³•用于释放点位
**Tech Stack:** C# / .NET 8, SqlSugar ORM, Redis缓存
---
## File Structure
```
WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/
  = RobotSocketState.cs                # +CurrentBatchIndex, +ChangePalletPhase
  = RobotTaskProcessor.cs              # +BuildBatchRange, +SendPickWithBatchAsync, +SendPutWithBatchAsync
  = Workflow/RobotWorkflowOrchestrator.cs  # é‡å†™ HandlePutFinishedStateAsync + HandlePickFinishedStateAsync
  = Workflow/RobotPrefixCommandHandler.cs  # putfinished/pickfinished é˜¶æ®µåˆ¤æ–­
  = Workflow/RobotSimpleCommandHandler.cs  # allpickfinished/allputfinished é˜¶æ®µå®ˆå«
WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/
  = IFakeBatteryPositionService.cs     # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/
  = FakeBatteryPositionService.cs      # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/
  = IFakeBatteryPositionRepository.cs  # +MarkAsAvailable
WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/
  = FakeBatteryPositionRepository.cs   # +MarkAsAvailable
```
---
## Task 1: RobotSocketState æ–°å¢žæ‰¹æ¬¡çŠ¶æ€å­—æ®µ
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`
- [ ] **Step 1: æ·»åŠ  CurrentBatchIndex å’Œ ChangePalletPhase å±žæ€§**
在 `IsInFakeBatteryMode` å±žæ€§åŽæ·»åŠ ï¼š
```csharp
        /// <summary>
        /// æ˜¯å¦å¤„于假电芯补充模式
        /// </summary>
        public bool IsInFakeBatteryMode { get; set; }
        /// <summary>
        /// å½“前批次起始编号(用于递增计算取货/放货编号)
        /// </summary>
        /// <remarks>
        /// åœ¨æ‰¹æ¬¡æ¨¡å¼ä¸‹ï¼Œæ¯æ‰¹å–è´§/放货的起始编号从1开始递增。
        /// ç”¨äºŽè®¡ç®— {start}-{end} æ ¼å¼ä¸­çš„ start å€¼ã€‚
        /// </remarks>
        public int CurrentBatchIndex { get; set; } = 1;
        /// <summary>
        /// æ¢ç›˜ä»»åŠ¡å½“å‰é˜¶æ®µ
        /// </summary>
        /// <remarks>
        /// é˜¶æ®µå®šä¹‰ï¼š
        /// 0: æœªå¼€å§‹
        /// 1: å–正常电芯(流向B) / å–假电芯(流向A)
        /// 2: æ”¾æ­£å¸¸ç”µèŠ¯ï¼ˆæµå‘B) / æ”¾å‡ç”µèŠ¯ï¼ˆæµå‘A)
        /// 3: å–假电芯(流向B Phase2)
        /// 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(流向B Phase2)
        /// </remarks>
        public int ChangePalletPhase { get; set; }
```
- [ ] **Step 2: éªŒè¯ç¼–译**
```bash
cd D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
git commit -m "feat(Robot): RobotSocketState æ–°å¢ž CurrentBatchIndex å’Œ ChangePalletPhase"
```
---
## Task 2: å‡ç”µèŠ¯ä»“å‚¨å±‚æ–°å¢ž MarkAsAvailable æ–¹æ³•
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs`
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs`
- [ ] **Step 1: åœ¨æŽ¥å£ä¸­æ·»åŠ  MarkAsAvailable æ–¹æ³•声明(在 MarkAsUsed æ–¹æ³•后)**
在 `IFakeBatteryPositionRepository.cs` çš„ `MarkAsUsed` æ–¹æ³•后添加:
```csharp
        /// <summary>
        /// æ ‡è®°æŒ‡å®šç‚¹ä½ä¸ºå¯ç”¨ï¼ˆé‡Šæ”¾ç‚¹ä½ï¼‰
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsAvailable(List<int> positions);
```
- [ ] **Step 2: åœ¨ä»“储实现中添加 MarkAsAvailable æ–¹æ³•(在 MarkAsUsed æ–¹æ³•后)**
在 `FakeBatteryPositionRepository.cs` çš„ `MarkAsUsed` æ–¹æ³•后添加:
```csharp
        /// <inheritdoc/>
        public bool MarkAsAvailable(List<int> positions)
        {
            if (positions == null || positions.Count == 0)
                return true;
            return Db.Updateable<Dt_FakeBatteryPosition>()
                .SetColumns(x => x.IsUsed, false)
                .Where(x => positions.Contains(x.PositionIndex))
                .ExecuteCommand() > 0;
        }
```
- [ ] **Step 3: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IFakeBatteryPositionRepository.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/FakeBatteryPositionRepository.cs
git commit -m "feat(Robot): å‡ç”µèŠ¯ä»“å‚¨å±‚æ–°å¢ž MarkAsAvailable æ–¹æ³•"
```
---
## Task 3: å‡ç”µèŠ¯æœåŠ¡å±‚æ–°å¢ž MarkAsAvailable æ–¹æ³•
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs`
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs`
- [ ] **Step 1: åœ¨æœåŠ¡æŽ¥å£ä¸­æ·»åŠ  MarkAsAvailable æ–¹æ³•声明(在 MarkAsUsed åŽï¼‰**
在 `IFakeBatteryPositionService.cs` çš„ `MarkAsUsed` æ–¹æ³•后添加:
```csharp
        /// <summary>
        /// æ ‡è®°æŒ‡å®šç‚¹ä½ä¸ºå¯ç”¨ï¼ˆé‡Šæ”¾ç‚¹ä½ï¼‰
        /// </summary>
        /// <param name="positions">点位索引列表</param>
        /// <returns>是否成功</returns>
        bool MarkAsAvailable(List<int> positions);
```
- [ ] **Step 2: åœ¨æœåŠ¡å®žçŽ°ä¸­æ·»åŠ  MarkAsAvailable æ–¹æ³•**
```csharp
        /// <inheritdoc/>
        public bool MarkAsAvailable(List<int> positions)
        {
            return BaseDal.MarkAsAvailable(positions);
        }
```
- [ ] **Step 3: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoService/IFakeBatteryPositionService.cs
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/FakeBatteryPositionService.cs
git commit -m "feat(Robot): å‡ç”µèŠ¯æœåŠ¡å±‚æ–°å¢ž MarkAsAvailable æ–¹æ³•"
```
---
## Task 4: RobotTaskProcessor æ–°å¢žæ‰¹æ¬¡æŒ‡ä»¤è¾…助方法
**注意:** `_fakeBatteryPositionService` å­—段已在上一轮迭代中添加,无需重复注入。
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs`
- [ ] **Step 1: æ·»åŠ  BuildBatchRange è¾…助方法**
在 `GetNextAvailableFakeBatteryPositions` æ–¹æ³•后添加:
```csharp
        /// <summary>
        /// è®¡ç®—批次编号范围
        /// </summary>
        /// <remarks>
        /// è¿”回格式:(start, end)
        /// - remaining >= 4: (currentIndex, currentIndex + 3)
        /// - remaining > 1: (currentIndex, currentIndex + remaining - 1)
        /// - remaining == 1: (currentIndex, 0)  -- å•个物品用 0 è¡¨ç¤º end
        /// </remarks>
        /// <param name="currentIndex">当前批次起始编号</param>
        /// <param name="remaining">剩余数量</param>
        /// <returns>(start, end) å…ƒç»„</returns>
        public (int Start, int End) BuildBatchRange(int currentIndex, int remaining)
        {
            if (remaining >= 4)
                return (currentIndex, currentIndex + 3);
            else if (remaining > 1)
                return (currentIndex, currentIndex + remaining - 1);
            else  // remaining == 1
                return (currentIndex, 0);
        }
```
- [ ] **Step 2: æ·»åŠ  SendPickWithBatchAsync æ–¹æ³•**
```csharp
        /// <summary>
        /// ä¸‹å‘取货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// å‘送顺序:
        /// 1. PickTotalNum,{N} -- çœŸå®žç”µèŠ¯æ€»æ•°
        /// 2. Pickbattery,{位置},{start}-{end} -- æ‰¹æ¬¡å–货指令
        ///
        /// ä¸‹å‘成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">取货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // å…ˆå‘送总数指令
            string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // å†å‘送批次取货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Pickbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发批次取货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发批次取货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发批次取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发批次取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
```
- [ ] **Step 3: æ·»åŠ  SendPutWithBatchAsync æ–¹æ³•**
```csharp
        /// <summary>
        /// ä¸‹å‘放货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// å‘送顺序:
        /// 1. PutTotalNum,{N} -- çœŸå®žç”µèŠ¯æ€»æ•°
        /// 2. Putbattery,{位置},{start}-{end} -- æ‰¹æ¬¡æ”¾è´§æŒ‡ä»¤
        ///
        /// ä¸‹å‘成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">放货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // å…ˆå‘送总数指令
            string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // å†å‘送批次放货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Putbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发放货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发放货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
```
- [ ] **Step 4: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 5: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
git commit -m "feat(Robot): RobotTaskProcessor æ–°å¢žæ‰¹æ¬¡æŒ‡ä»¤è¾…助方法"
```
---
## Task 5: RobotSimpleCommandHandler æ·»åŠ é˜¶æ®µå®ˆå«
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs`
- [ ] **Step 1: ä¿®æ”¹ allpickfinished åˆ†æ”¯**
找到 `case "allpickfinished":` åˆ†æ”¯ï¼Œæ›¿æ¢ä¸ºï¼š
```csharp
                // å…¨éƒ¨å–货完成
                case "allpickfinished":
                    {
                        state.CurrentAction = "AllPickFinished";
                        var currentTask = state.CurrentTask;
                        if (currentTask == null)
                            return false;
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            if (state.ChangePalletPhase == 0)
                            {
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                                {
                                    _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                    await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                                    QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                                    // é‡ç½®æ‰¹æ¬¡çŠ¶æ€
                                    state.ChangePalletPhase = 0;
                                    state.CurrentBatchIndex = 1;
                                    state.IsInFakeBatteryMode = false;
                                    return true;
                                }
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            return true;
                        }
                        // æ‹†ç›˜ä»»åŠ¡ï¼šç›´æŽ¥å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.SplitPallet)
                        {
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                            {
                                _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                                QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                                return true;
                            }
                        }
                        return false;
                    }
```
- [ ] **Step 2: ä¿®æ”¹ allputfinished åˆ†æ”¯**
找到 `case "allputfinished":` åˆ†æ”¯ï¼Œæ›¿æ¢ä¸ºï¼š
```csharp
                // å…¨éƒ¨æ”¾è´§å®Œæˆ
                case "allputfinished":
                    {
                        state.CurrentAction = "AllPutFinished";
                        var currentTask = state.CurrentTask;
                        if (currentTask == null)
                            return false;
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            if (state.ChangePalletPhase == 0)
                            {
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                                {
                                    _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                    state.CurrentTask = null;
                                    state.RobotTaskTotalNum = 0;
                                    state.CellBarcode = new List<string>();
                                    await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                                    QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
                                    // é‡ç½®æ‰¹æ¬¡çŠ¶æ€
                                    state.ChangePalletPhase = 0;
                                    state.CurrentBatchIndex = 1;
                                    state.IsInFakeBatteryMode = false;
                                    return true;
                                }
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            return true;
                        }
                        // ç»„盘任务:直接处理入库
                        if (robotTaskType == RobotTaskTypeEnum.GroupPallet)
                        {
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                            {
                                _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                                state.CurrentTask = null;
                                state.RobotTaskTotalNum = 0;
                                state.CellBarcode = new List<string>();
                                await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                                QuartzLogger.Info($"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
                                return true;
                            }
                        }
                        return false;
                    }
```
- [ ] **Step 3: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 4: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
git commit -m "feat(Robot): RobotSimpleCommandHandler æ¢ç›˜ä»»åŠ¡æ·»åŠ  ChangePalletPhase é˜¶æ®µå®ˆå«"
```
---
## Task 6: RobotPrefixCommandHandler ä¿®æ”¹ putfinished/pickfinished å¤„理逻辑
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs`
- [ ] **Step 1: æ·»åŠ  IFakeBatteryPositionService ä¾èµ–**
在类顶部添加字段和构造函数参数:
```csharp
        /// <summary>
        /// å‡ç”µèŠ¯ä½ç½®æœåŠ¡
        /// </summary>
        private readonly IFakeBatteryPositionService _fakeBatteryPositionService;
        public RobotPrefixCommandHandler(
            IRobotTaskService robotTaskService,
            RobotTaskProcessor taskProcessor,
            RobotStateManager stateManager,
            ISocketClientGateway socketClientGateway,
            IFakeBatteryPositionService fakeBatteryPositionService)
        {
            _robotTaskService = robotTaskService;
            _taskProcessor = taskProcessor;
            _stateManager = stateManager;
            _socketClientGateway = socketClientGateway;
            _fakeBatteryPositionService = fakeBatteryPositionService;
        }
```
- [ ] **Step 2: ä¿®æ”¹ HandlePutFinishedAsync æ–¹æ³•中的放货处理逻辑**
找到 `if (putSuccess)` å—(现有代码约在第258行附近),替换其中的放货成功处理逻辑为:
```csharp
            // å¦‚果放货成功
            if (putSuccess)
            {
                state.CurrentAction = "PutFinished";
                // åˆ¤æ–­æ˜¯å¦ä¸ºæ¢ç›˜ä»»åŠ¡ä¸”å¤„äºŽæ‰¹æ¬¡æ¨¡å¼
                var isChangePallet = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
                var isFlowA = state.CurrentTask?.RobotSourceAddressLineCode is "11001" or "11010";
                if (isChangePallet)
                {
                    if (state.ChangePalletPhase == 2)
                    {
                        if (isFlowA)
                        {
                            // æµå‘A Phase 2:放假电芯到目标托盘,不调用 API,不递增计数
                            // ä¸åšä»»ä½•额外处理,仅更新状态
                        }
                        else
                        {
                            // æµå‘B Phase 2:放正常电芯,递增计数
                            state.RobotTaskTotalNum += positions.Length;
                            if (task != null)
                                task.RobotTaskTotalNum -= positions.Length;
                        }
                    }
                    else if (state.ChangePalletPhase == 4)
                    {
                        // æµå‘B Phase 4:放假电芯到5号位,不调用 API,不递增计数,释放点位
                        _fakeBatteryPositionService.MarkAsAvailable(positions.ToList());
                    }
                    else
                    {
                        // éžæ‰¹æ¬¡æ¨¡å¼æˆ–非换盘任务:递增计数
                        state.RobotTaskTotalNum += positions.Length;
                        if (task != null)
                            task.RobotTaskTotalNum -= positions.Length;
                    }
                }
                else
                {
                    // éžæ¢ç›˜ä»»åŠ¡ï¼šåŽŸæœ‰é€»è¾‘
                    state.RobotTaskTotalNum += positions.Length;
                    if (task != null)
                        task.RobotTaskTotalNum -= positions.Length;
                }
            }
```
- [ ] **Step 3: ä¿®æ”¹ HandlePickFinishedAsync æ–¹æ³•**
找到 `if (state.IsSplitPallet)` å—(现有代码约在第173-198行),替换为:
```csharp
            // å¦‚果是拆盘任务
            if (state.IsSplitPallet)
            {
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                state.LastPickPositions = positions;
                var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
                if (result.Data.Status && result.IsSuccess)
                {
                    state.CurrentAction = "PickFinished";
                }
            }
            // æ¢ç›˜ä»»åŠ¡å–å‡ç”µèŠ¯æ—¶ï¼ˆPhase 3)不调用拆盘 API
            else if (state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
                     && state.ChangePalletPhase == 3)
            {
                state.CurrentAction = "PickFinished";
                state.LastPickPositions = positions;
            }
            else
            {
                state.CurrentAction = "PickFinished";
                state.LastPickPositions = positions;
            }
```
- [ ] **Step 4: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 5: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
git commit -m "feat(Robot): RobotPrefixCommandHandler æ¢ç›˜ä»»åŠ¡æ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†é€»è¾‘"
```
---
## Task 7: RobotWorkflowOrchestrator é‡å†™ HandlePutFinishedStateAsync çš„ ChangePallet åˆ†æ”¯
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs`
- [ ] **Step 1: æ›¿æ¢ ChangePallet åˆ†æ”¯é€»è¾‘**
找到 `else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())` åˆ†æ”¯ï¼Œå°†å…¶å®Œæ•´æ›¿æ¢ä¸ºï¼š
```csharp
            else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                const int targetTotal = 48;
                int targetNormalCount = task.RobotTaskTotalNum;
                int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;
                // åˆ¤æ–­æµå‘(null-safe)
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // ç›®æ ‡æ•°é‡ä¸º48:直接走原有逻辑,不进入批次模式
                if (targetNormalCount == targetTotal)
                {
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                    return;
                }
                // åˆå§‹åŒ–批次模式
                if (stateForUpdate.ChangePalletPhase == 0)
                {
                    stateForUpdate.ChangePalletPhase = 1;
                    stateForUpdate.CurrentBatchIndex = 1;
                    _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务进入批次模式,任务号: {TaskNum},流向: {Flow}",
                        task.RobotTaskNum, isFlowA ? "A" : "B");
                }
                // ==================== æµå‘A:补假电芯到目标托盘 ====================
                if (isFlowA)
                {
                    // Phase 1: å–假电芯(从5号位,使用 PositionIndex)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    {
                        int remaining = targetTotal - currentCompletedCount;
                        if (remaining <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int pickCount = Math.Min(4, remaining);
                        var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(pickCount);
                        if (positions.Count == 0)
                        {
                            _logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions);
                        stateForUpdate.ChangePalletPhase = 2;
                    }
                    // Phase 2: æ”¾å‡ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆä»Ž targetNormalCount+1 å¼€å§‹é€’增)
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    {
                        int remaining = targetTotal - currentCompletedCount;
                        if (remaining <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向A完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        // è®¡ç®—放货批次编号:从 targetNormalCount + 1 å¼€å§‹
                        int batchStart = targetNormalCount + 1 + (stateForUpdate.CurrentBatchIndex - 1);
                        int putCount = Math.Min(4, remaining);
                        var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += putCount;
                        stateForUpdate.ChangePalletPhase = 1;
                    }
                }
                // ==================== æµå‘B:取正常电芯 + å›žæ”¶å‡ç”µèН ====================
                else
                {
                    // Phase 1: å–正常电芯(从源地址,从1开始递增)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    {
                        int remainingNormal = targetNormalCount - currentCompletedCount;
                        int pickCount = Math.Min(4, remainingNormal);
                        var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        // é€’增批次索引(如果刚好取完最后一个,索引会超过目标,但putfinished回来时会切换阶段)
                        stateForUpdate.CurrentBatchIndex += pickCount;
                        // åˆ‡æ¢åˆ° Phase 2
                        stateForUpdate.ChangePalletPhase = 2;
                    }
                    // Phase 2: æ”¾æ­£å¸¸ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆæ”¾è´§ç¼–å·ä¸Žå–è´§ç¼–å·ä¸€è‡´ï¼‰
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    {
                        int remainingNormal = targetNormalCount - currentCompletedCount;
                        if (remainingNormal <= 0)
                        {
                            // æ­£å¸¸ç”µèŠ¯æ”¾å®Œï¼Œåˆ‡æ¢åˆ° Phase 3
                            stateForUpdate.ChangePalletPhase = 3;
                            stateForUpdate.CurrentBatchIndex = targetNormalCount + 1;
                            _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯全部放完,进入Phase 3回收假电芯,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        // è®¡ç®—本批放货编号:基于 currentCompletedCount æŽ¨å¯¼æ‰¹æ¬¡èµ·å§‹ï¼ˆä¸Žå–货编号一致)
                        // batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1
                        int batchStart = ((currentCompletedCount - 1) / 4) * 4 + 1;
                        int putCount = Math.Min(4, remainingNormal);
                        var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
                        stateForUpdate.ChangePalletPhase = 1;
                    }
                    // Phase 3: å–假电芯(从源地址,从 targetNormalCount+1 å¼€å§‹é€’增)
                    else if (stateForUpdate.ChangePalletPhase == 3)
                    {
                        int fakeCount = targetTotal - targetNormalCount;
                        int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                        int remainingFake = fakeCount - completedFake;
                        if (remainingFake <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int pickCount = Math.Min(4, remainingFake);
                        var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += pickCount;
                        stateForUpdate.ChangePalletPhase = 4;
                    }
                    // Phase 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(使用 PositionIndex)
                    else if (stateForUpdate.ChangePalletPhase == 4)
                    {
                        int fakeCount = targetTotal - targetNormalCount;
                        int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                        int remainingFake = fakeCount - completedFake;
                        if (remainingFake <= 0)
                        {
                            stateForUpdate.ChangePalletPhase = 0;
                            stateForUpdate.CurrentBatchIndex = 1;
                            stateForUpdate.IsInFakeBatteryMode = false;
                            _logger.LogInformation("HandlePutFinishedStateAsync:流向B完成,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
                        if (positions.Count == 0)
                        {
                            _logger.LogError("HandlePutFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                            return;
                        }
                        int start = positions.Min();
                        int end = positions.Max();
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, "5", start, end);
                        stateForUpdate.ChangePalletPhase = 3;
                    }
                }
            }
```
- [ ] **Step 2: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator é‡å†™ HandlePutFinishedStateAsync å®žçŽ°æ‰¹æ¬¡æŒ‡ä»¤ä¸ŽåŒæµå‘"
```
---
## Task 8: RobotWorkflowOrchestrator é‡å†™ HandlePickFinishedStateAsync çš„ ChangePallet åˆ†æ”¯
**Files:**
- Modify: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs`
- [ ] **Step 1: ä¿®æ”¹ HandlePickFinishedStateAsync æ–¹æ³•**
找到 `HandlePickFinishedStateAsync` æ–¹æ³•(现有代码约第162-199行),将整个方法替换为:
```csharp
        /// <summary>
        /// å¤„理取货完成后的放货指令
        /// </summary>
        /// <remarks>
        /// æ ¹æ®ä»»åŠ¡ç±»åž‹å†³å®šæ”¾è´§æŒ‡ä»¤æ ¼å¼ï¼š
        /// - æ¢ç›˜ä»»åŠ¡ï¼ˆChangePallet):使用批次格式 SendPutWithBatchAsync
        /// - ç»„盘任务(GroupPallet):使用原有格式 Putbattery,{目标地址}
        /// - å…¶ä»–任务:使用原有格式
        /// </remarks>
        /// <param name="task">当前任务</param>
        /// <param name="ipAddress">机器人 IP åœ°å€</param>
        private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
        {
            string taskString;
            // æ¢ç›˜ä»»åŠ¡ä½¿ç”¨æ‰¹æ¬¡æ ¼å¼
            if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                int targetNormalCount = task.RobotTaskTotalNum;
                var state = _stateManager.GetState(ipAddress);
                int currentCompletedCount = state?.RobotTaskTotalNum ?? 0;
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // æµå‘A:放假电芯到目标托盘(Phase 2)
                if (isFlowA && state?.ChangePalletPhase == 2)
                {
                    int remaining = 48 - currentCompletedCount;
                    if (remaining <= 0) return;
                    // è®¡ç®—批次编号:从 targetNormalCount + 1 å¼€å§‹
                    int batchStart = targetNormalCount + 1;
                    int putCount = Math.Min(4, remaining);
                    var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                    await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
                    return;
                }
                // æµå‘B Phase 4:放假电芯到5号位
                if (!isFlowA && state?.ChangePalletPhase == 4)
                {
                    int fakeCount = 48 - targetNormalCount;
                    int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                    int remainingFake = fakeCount - completedFake;
                    if (remainingFake <= 0) return;
                    var positions = _taskProcessor.GetNextAvailableFakeBatteryPositions(Math.Min(4, remainingFake));
                    if (positions.Count == 0)
                    {
                        _logger.LogError("HandlePickFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                        return;
                    }
                    int start = positions.Min();
                    int end = positions.Max();
                    await _taskProcessor.SendPutWithBatchAsync(task, state, "5", start, end);
                    return;
                }
                // æµå‘B Phase 2:放正常电芯到目标托盘
                if (!isFlowA && state?.ChangePalletPhase == 2)
                {
                    int remainingNormal = targetNormalCount - currentCompletedCount;
                    if (remainingNormal <= 0) return;
                    int batchStart = state.CurrentBatchIndex - (state.CurrentBatchIndex - 1) / 4 * 4;
                    int putCount = Math.Min(4, remainingNormal);
                    var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                    await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
                    return;
                }
                // é»˜è®¤ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
                taskString = $"Putbattery,{task.RobotTargetAddress}";
            }
            else
            {
                // éžæ¢ç›˜ä»»åŠ¡ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
                taskString = $"Putbattery,{task.RobotTargetAddress}";
            }
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
            if (result)
            {
                _logger.LogInformation("HandlePickFinishedStateAsync:下发放货指令成功,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString}", task.RobotRoadway);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                var stateToUpdate = _stateManager.GetState(ipAddress);
                if (stateToUpdate != null)
                {
                    stateToUpdate.CurrentTask = task;
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                    {
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                    }
                }
            }
            else
            {
                _logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway);
            }
        }
```
- [ ] **Step 2: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj
```
Expected: 0 errors
- [ ] **Step 3: Commit**
```bash
git add WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
git commit -m "feat(Robot): RobotWorkflowOrchestrator é‡å†™ HandlePickFinishedStateAsync æ”¯æŒæ‰¹æ¬¡æŒ‡ä»¤"
```
---
## Task 9: æ³¨å†Œä¾èµ–注入
**Files:**
- Modify: AutofacModuleRegister.cs æˆ–检查现有注册
- [ ] **Step 1: æ£€æŸ¥ FakeBatteryPositionService æ˜¯å¦å·²è‡ªåŠ¨æ³¨å†Œ**
```bash
grep -r "FakeBatteryPositionService" D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Core --include="*.cs"
```
Expected: å¦‚果项目使用 `AsImplementedInterfaces()` è‡ªåŠ¨æ‰«æï¼Œè¯¥æœåŠ¡å·²è‡ªåŠ¨æ³¨å†Œã€‚
- [ ] **Step 2: æ£€æŸ¥ RobotPrefixCommandHandler æž„造函数变更后是否需要额外注册**
由于仅新增了依赖参数,如果项目使用 Autofac è‡ªåŠ¨è£…é…ï¼ˆå‚æ•°è‡ªåŠ¨æ³¨å…¥ï¼‰ï¼Œåˆ™æ— éœ€ä¿®æ”¹ã€‚å¦‚æžœéœ€è¦æ‰‹åŠ¨æ³¨å†Œï¼Œæ·»åŠ ï¼š
```csharp
// ç¡®ä¿ IFakeBatteryPositionService å·²è¢«æ³¨å†Œï¼ˆé€šå¸¸é€šè¿‡ AsImplementedInterfaces è‡ªåŠ¨å®Œæˆï¼‰
// RobotPrefixCommandHandler çš„æ–°æž„造函数参数会自动装配
```
- [ ] **Step 3: éªŒè¯ç¼–译**
```bash
dotnet build WIDESEAWCS_Server.sln
```
Expected: 0 errors
- [ ] **Step 4: Commit**(仅当有实际改动时才提交)
```bash
git add <修改的文件>
git commit -m "feat(Robot): æ³¨å†Œä¾èµ–注入(如有改动)"
```
---
## Task 10: éªŒè¯æ•´ä½“构建
- [ ] **Step 1: å®Œæ•´æž„建**
```bash
cd D:\Git\ShanMeiXinNengYuan\Code
dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
```
Expected: 0 errors, 0 warnings
- [ ] **Step 2: æ£€æŸ¥æµ‹è¯•**
```bash
dotnet test WCS/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj
```
Expected: çŽ°æœ‰æµ‹è¯•é€šè¿‡
- [ ] **Step 3: æäº¤å®Œæˆæ ‡è®°**
```bash
git commit --allow-empty -m "feat(Robot): æ¢ç›˜ä»»åŠ¡æ‰¹æ¬¡æŒ‡ä»¤ä¸ŽåŒæµå‘å®žçŽ°å®Œæˆ"
```
---
## é™„录:测试验证步骤
### æ‰‹åŠ¨éªŒè¯æµç¨‹
1. **流向A测试(RobotSourceAddressLineCode = 11001)**
   - targetNormalCount = 11
   - é¢„期:Phase1 å–假电芯(Pickbattery,5,{pos})→ Phase2 æ”¾å‡ç”µèŠ¯ï¼ˆPutbattery,{目标},12-15)→ å¾ªçŽ¯ç›´åˆ°48
2. **流向B测试(RobotSourceAddressLineCode != 11001/11010)**
   - targetNormalCount = 11
   - é¢„期:Phase1 å–正常(Pickbattery,{源},1-4)→ Phase2 æ”¾æ­£å¸¸ï¼ˆPutbattery,{目标},1-4)→ å¾ªçޝ â†’ Phase3 å–假(Pickbattery,{源},12-15)→ Phase4 æ”¾å‡åˆ°5号位(Putbattery,5,{pos})→ å¾ªçŽ¯ç›´åˆ°å›žæ”¶å®Œ37个假电芯
3. **边界条件**
   - targetNormalCount = 48:走原有逻辑
   - targetNormalCount = 1:批次 1-0 æ ¼å¼
---
**Plan complete.**
Code/docs/superpowers/specs/2026-04-16-change-pallet-batch-command-design.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,180 @@
# æ¢ç›˜ä»»åŠ¡æ‰¹æ¬¡æŒ‡ä»¤ä¸ŽåŒæµå‘è®¾è®¡
## æ¦‚è¿°
对换盘任务(ChangePallet)的取货/放货指令格式进行升级,支持批次编号和总数指令,并根据 `RobotSourceAddressLineCode` åŒºåˆ†ä¸¤ç§æµå‘。
## èƒŒæ™¯
当前换盘任务实现(上一轮迭代)仅处理了 `HandlePutFinishedStateAsync` ä¸­çš„假电芯补充逻辑,指令格式为简单的 `Pickbattery,{地址}` å’Œ `Pickbattery,5,{start}-{end}`。现需要:
1. æ‰€æœ‰æ¢ç›˜å–è´§/放货指令统一为批次格式 `{Command},{位置},{start}-{end}`
2. æ¯æ‰¹å‰å‘送总数指令 `PickTotalNum,{N}` / `PutTotalNum,{N}`
3. æ ¹æ®æºåœ°å€çº¿ä½“编码区分两种完全不同的操作流向
## æŒ‡ä»¤æ ¼å¼
### æ‰¹æ¬¡æŒ‡ä»¤
| æŒ‡ä»¤ç±»åž‹ | æ ¼å¼ | ç¤ºä¾‹ |
|----------|------|------|
| å–货总数 | `PickTotalNum,{N}` | `PickTotalNum,11` |
| æ”¾è´§æ€»æ•° | `PutTotalNum,{N}` | `PutTotalNum,11` |
| å–货批次 | `Pickbattery,{位置},{start}-{end}` | `Pickbattery,3,1-4` |
| æ”¾è´§æ‰¹æ¬¡ | `Putbattery,{位置},{start}-{end}` | `Putbattery,6,5-8` |
| å–货单个 | `Pickbattery,{位置},{n}-0` | `Pickbattery,3,11-0` |
| æ”¾è´§å•个 | `Putbattery,{位置},{n}-0` | `Putbattery,6,11-0` |
- `PickTotalNum/PutTotalNum` ä»…换盘任务发送,每批取/放之前都发
- `N` = çœŸå®žç”µèŠ¯æ•°é‡ï¼ˆå³ `task.RobotTaskTotalNum`),机器人固件用此值判断托盘上正常电芯总数,两种流向均发送相同的 N å€¼
- æ¯æ‰¹æœ€å¤š4个,不满4个按实际数发,剩1个时 end=0
### æ‰¹æ¬¡ç¼–号计算
`BuildBatchRange(currentIndex, remaining)` â†’ `(start, end)`:
- `remaining >= 4` â†’ `(currentIndex, currentIndex + 3)`
- `remaining > 1` â†’ `(currentIndex, currentIndex + remaining - 1)`
- `remaining == 1` â†’ `(currentIndex, 0)`
示例(targetNormalCount=11):第1批 1-4,第2批 5-8,第3批 9-11
示例(targetNormalCount=9):第1批 1-4,第2批 5-8,第3批 9-0(单个,end=0)
## ä¸¤ç§æµå‘
### æµå‘A:补假电芯到目标托盘
**条件:** `RobotSourceAddressLineCode == "11001" || "11010"`
**场景:** ç›®æ ‡æ‰˜ç›˜æœ‰ N ä¸ªæ­£å¸¸ç”µèŠ¯ï¼ˆN < 48),需从5号位假电芯托盘取假电芯补满48个。
**阶段流转:**
```
[取假电芯] Pickbattery,5,{positionIndex}        â† ä»Ž5号位取,编号用平面点位表PositionIndex
    â†“
[放假电芯] Putbattery,{目标地址},{N+1}-{N+4}    â† æ”¾åˆ°ç›®æ ‡æ‰˜ç›˜ï¼Œç¼–号从正常数+1递增
    â†“
重复直到补满48个
```
假电芯数量 = `48 - task.RobotTaskTotalNum`
### æµå‘B:取正常电芯 + å›žæ”¶å‡ç”µèН
**条件:** `RobotSourceAddressLineCode != "11001" && != "11010"`
**场景:** æºæ‰˜ç›˜åŽŸæœ¬æ»¡48个(正常电芯 + å‡ç”µèŠ¯æ··åˆï¼‰ï¼Œå…ˆå–èµ°æ­£å¸¸ç”µèŠ¯æ”¾åˆ°ç›®æ ‡æ‰˜ç›˜ï¼Œå†æŠŠæºæ‰˜ç›˜ä¸Šå‰©ä½™çš„å‡ç”µèŠ¯å–å‡ºæ”¾å›ž5号位。
**阶段流转:**
```
Phase 1 - å–正常电芯:
[取正常] Pickbattery,{源地址},{1}-{4}            â† ç¼–号从1递增
    â†“
[放正常] Putbattery,{目标地址},{1}-{4}           â† ç¼–号从1递增
    â†“
重复直到 N ä¸ªæ­£å¸¸ç”µèŠ¯å…¨éƒ¨å–å®Œ
Phase 2 - å›žæ”¶å‡ç”µèН:
[取假电芯] Pickbattery,{源地址},{N+1}-{N+4}     â† ç¼–号从正常数+1继续递增
    â†“
[放假电芯] Putbattery,5,{positionIndex}          â† æ”¾å›ž5号位,编号用平面点位表PositionIndex
    â†“
重复直到假电芯全部回收(48-N ä¸ªï¼‰
```
## çŠ¶æ€ç®¡ç†
### RobotSocketState æ–°å¢žå­—段
```csharp
/// <summary>
/// å½“前批次起始编号(用于递增计算取货/放货编号)
/// </summary>
public int CurrentBatchIndex { get; set; } = 1;
/// <summary>
/// æ¢ç›˜ä»»åŠ¡å½“å‰é˜¶æ®µ
/// </summary>
/// <remarks>
/// 0: æœªå¼€å§‹
/// 1: å–正常电芯 / å–假电芯(流向A)
/// 2: æ”¾æ­£å¸¸ç”µèН / æ”¾å‡ç”µèŠ¯ï¼ˆæµå‘A)
/// 3: å–假电芯(流向B Phase2)
/// 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(流向B Phase2)
/// </remarks>
public int ChangePalletPhase { get; set; }
```
### CurrentBatchIndex ç”Ÿå‘½å‘¨æœŸ
**流向A:**
- Phase 0→1:`CurrentBatchIndex = 1`(初始化,用于 PositionIndex æŸ¥è¯¢èµ·ç‚¹ï¼‰
- Phase 1→2:不重置(放货编号从 `targetNormalCount + 1` å¼€å§‹ï¼Œç”± Orchestrator è®¡ç®—)
- Phase 2→1:`CurrentBatchIndex += æœ¬æ‰¹æ•°é‡`(递增,下一批继续)
- å®Œæˆâ†’0:`CurrentBatchIndex = 1`(重置)
**流向B:**
- Phase 0→1:`CurrentBatchIndex = 1`(取正常电芯从1开始)
- Phase 1→2:不重置(放货编号与取货编号一致)
- Phase 2→1:`CurrentBatchIndex += æœ¬æ‰¹æ•°é‡`(递增)
- Phase 2→3(正常电芯取完,即 `state.RobotTaskTotalNum >= targetNormalCount` æ—¶ putfinished å®ŒæˆåŽè§¦å‘):`CurrentBatchIndex = targetNormalCount + 1`(假电芯从正常数+1开始)
- Phase 3→4:不重置(放假电芯用 PositionIndex,由 Orchestrator è®¡ç®—)
- Phase 4→3:`CurrentBatchIndex += æœ¬æ‰¹æ•°é‡`(递增)
- å®Œæˆâ†’0:`CurrentBatchIndex = 1`(重置)
### é˜¶æ®µæµè½¬çŠ¶æ€æœº
**流向A:**
```
Phase 0 â†’ Phase 1(取假电芯from 5号位)→ Phase 2(放假电芯to ç›®æ ‡ï¼‰â†’ Phase 1 â†’ ... â†’ Phase 0(完成)
```
**流向B:**
```
Phase 0 â†’ Phase 1(取正常from æºï¼‰â†’ Phase 2(放正常to ç›®æ ‡ï¼‰â†’ Phase 1 â†’ ...
        â†’ Phase 3(取假电芯from æºï¼‰â†’ Phase 4(放假电芯to 5号位)→ Phase 3 â†’ ... â†’ Phase 0(完成)
```
## ä»£ç æ”¹åŠ¨èŒƒå›´
| æ–‡ä»¶ | æ”¹åЍ |
|------|------|
| `RobotSocketState.cs` | +`CurrentBatchIndex`, +`ChangePalletPhase` |
| `RobotTaskProcessor.cs` | +`BuildBatchRange()`, +`SendPickWithBatchAsync()`, +`SendPutWithBatchAsync()`, ä¿®æ”¹çŽ°æœ‰å‡ç”µèŠ¯æ–¹æ³• |
| `RobotWorkflowOrchestrator.cs` | é‡å†™ ChangePallet åˆ†æ”¯ï¼ˆHandlePutFinishedStateAsync + HandlePickFinishedStateAsync) |
| `RobotPrefixCommandHandler.cs` | HandlePutFinishedAsync ä¸­åŒºåˆ†æ¢ç›˜é˜¶æ®µï¼šå‡ç”µèŠ¯æ”¾è´§ä¸è°ƒç”¨ ChangePalletAsync API,不递增 `RobotTaskTotalNum`;HandlePickFinishedAsync ä¸­å‡ç”µèŠ¯å–è´§ä¸è°ƒç”¨æ‹†ç›˜ API |
| `RobotSimpleCommandHandler.cs` | allpickfinished/allputfinished ä¸­å¢žåŠ  `ChangePalletPhase` å®ˆå«ï¼šä»…当 Phase==0(所有阶段完成)时才触发入库和删除任务,中间阶段不处理 |
| `IFakeBatteryPositionService.cs` | +`MarkAsAvailable(List<int> positions)` æ–¹æ³• |
| `FakeBatteryPositionService.cs` | +`MarkAsAvailable` å®žçް |
| `IFakeBatteryPositionRepository.cs` | +`MarkAsAvailable(List<int> positions)` æ–¹æ³• |
| `FakeBatteryPositionRepository.cs` | +`MarkAsAvailable` å®žçŽ°ï¼ˆå°†æŒ‡å®šç‚¹ä½ IsUsed è®¾ä¸º false) |
## å‘½ä»¤å¤„理器交互
### RobotPrefixCommandHandler å˜æ›´
**pickfinished å¤„理:**
- å½“ `ChangePalletPhase == 3`(流向B取假电芯)时,不调用拆盘 API,仅更新状态
- å…¶ä»–阶段保持现有逻辑
**putfinished å¤„理:**
- åˆ¤æ–­æµå‘:通过 `state.CurrentTask.RobotSourceAddressLineCode` åˆ¤æ–­æ˜¯æµå‘A还是流向B
- å½“ `ChangePalletPhase == 2` ä¸”流向B(放正常电芯)时,正常调用 ChangePalletAsync API,`state.RobotTaskTotalNum += positions.Length`
- å½“ `ChangePalletPhase == 4`(流向B放假电芯到5号位)时,不调用 API,不递增 `RobotTaskTotalNum`,调用 `MarkAsAvailable(positions)` é‡Šæ”¾ç‚¹ä½
- å½“ `ChangePalletPhase == 2` ä¸”流向A(放假电芯到目标)时,不调用 API,不递增 `RobotTaskTotalNum`
### RobotSimpleCommandHandler å˜æ›´
**allpickfinished / allputfinished:**
- å¢žåŠ å®ˆå«æ¡ä»¶ï¼šä»…å½“ `state.ChangePalletPhase == 0`(所有阶段完成)时,才执行入库回传和任务删除
- ä¸­é—´é˜¶æ®µæ”¶åˆ° allpickfinished/allputfinished æ—¶ï¼Œä»…æ›´æ–° `state.CurrentAction`,不触发入库逻辑
- ä»»åŠ¡å®Œæˆæ—¶é¢å¤–é‡ç½®ï¼š`state.ChangePalletPhase = 0`, `state.CurrentBatchIndex = 1`, `state.IsInFakeBatteryMode = false`
## è¾¹ç•Œæ¡ä»¶
- `task.RobotTaskTotalNum == 48`:直接走原有逻辑,不进入批次模式
- `task.RobotTaskTotalNum == 0`:流向A è¡¥æ»¡48个假电芯;流向B è·³è¿‡ Phase 1/2,直接进入 Phase 3/4 å›žæ”¶48个假电芯
- å‡ç”µèŠ¯å¹³é¢ç‚¹ä½ä¸è¶³ï¼šè®°å½•é”™è¯¯æ—¥å¿—ï¼Œä¸­æ­¢å½“å‰æ‰¹æ¬¡
- å‡ç”µèŠ¯ç‚¹ä½ç¢Žç‰‡åŒ–ï¼š`GetNextAvailable` è¦æ±‚同行连续,如果请求4个找不到,依次尝试3、2、1个
- æ‰¹æ¬¡ç¼–号溢出(超过48):不应发生,但需防御性检查
ÏîÄ¿×ÊÁÏ/¼¼ÊõЭÒé/~$Ñé֤ƽ̨ÎïÁ÷²Ö´¢ÏµÍ³¼¼Êõ¹æ¸ñÊé-11.24.docx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/»úеÊÖЭÒé/~$½»»¥Á÷³Ì±í(1).xlsx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/»úеÊÖЭÒé/½»»¥Á÷³Ì±í(1).xlsx
Binary files differ