已添加6个文件
已删除1个文件
已修改40个文件
2108 ■■■■ 文件已修改
Code/.omc/state/last-tool-error.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/mission-state.json 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/subagent-tracking.json 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/HostedService/ApiRouteCacheWarmupHostedService.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotBarcodeGenerator.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 335 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/docs/换盘任务流程图.md 291 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/public/webconfig.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/api/http.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/basic/extend/GetLocationStatus.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/Home.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/basic/locationInfo.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesService.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMESDeviceConfigService.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesService.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MESDeviceConfig.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MESDeviceConfig.sql 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/MESDeviceConfigService.cs 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_AGV.cs 149 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Robot.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Record/LocationStatusChangeRecordController.cs 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/极卷库AGV接口/~$极卷库出入库.docx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/上位系统对接/高温2常温1及机械手设备账号信息表(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@
{
  "tool_name": "Read",
  "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WCS\\\\WIDESEAWCS_Server\\\\WIDESEA_DTO\\\\Stock\\\\StockDTO.cs\"}",
  "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WMS\\\\WIDESEA_WMSServer\\\\WIDESEA_TaskInfoService\\\\WCS\\\\TaskService.cs\"}",
  "error": "File does not exist. Note: your current working directory is D:\\Git\\ShanMeiXinNengYuan\\Code.",
  "timestamp": "2026-04-16T15:04:27.059Z",
  "retry_count": 2
  "timestamp": "2026-04-18T07:45:29.125Z",
  "retry_count": 1
}
Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@
{
  "updatedAt": "2026-04-16T13:59:54.835Z",
  "updatedAt": "2026-04-18T08:52:24.581Z",
  "missions": [
    {
      "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none",
@@ -1070,6 +1070,204 @@
          "sourceKey": "session-stop:a93149b10e8b430e7"
        }
      ]
    },
    {
      "id": "session:f36717d7-c4c4-4816-8723-6f7d562203f6:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-18T06:36:59.451Z",
      "updatedAt": "2026-04-18T07:46:50.058Z",
      "status": "done",
      "workerCount": 8,
      "taskCounts": {
        "total": 8,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 8,
        "failed": 0
      },
      "agents": [
        {
          "name": "Explore:a1ab626",
          "role": "Explore",
          "ownership": "a1ab62633715919c8",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:39:31.702Z"
        },
        {
          "name": "general-purpose:ae116cb",
          "role": "general-purpose",
          "ownership": "ae116cb86a435866d",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:50:41.503Z"
        },
        {
          "name": "general-purpose:a4e061e",
          "role": "general-purpose",
          "ownership": "a4e061e4a6d17c682",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:52:02.047Z"
        },
        {
          "name": "general-purpose:afdf6ef",
          "role": "general-purpose",
          "ownership": "afdf6efad435c036a",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:50:50.428Z"
        },
        {
          "name": "general-purpose:ab1f8d2",
          "role": "general-purpose",
          "ownership": "ab1f8d2625bd8a667",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:54:35.702Z"
        },
        {
          "name": "general-purpose:a99f2fa",
          "role": "general-purpose",
          "ownership": "a99f2fa258ad9b5a1",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T06:50:30.602Z"
        },
        {
          "name": "general-purpose:aa71986",
          "role": "general-purpose",
          "ownership": "aa71986c72a8dd1f4",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T07:46:50.058Z"
        },
        {
          "name": "general-purpose:aadbd70",
          "role": "general-purpose",
          "ownership": "aadbd702e8fdf756c",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T07:46:15.279Z"
        }
      ],
      "timeline": [
        {
          "id": "session-stop:ab1f8d2625bd8a667:2026-04-18T06:54:35.702Z",
          "at": "2026-04-18T06:54:35.702Z",
          "kind": "completion",
          "agent": "general-purpose:ab1f8d2",
          "detail": "completed",
          "sourceKey": "session-stop:ab1f8d2625bd8a667"
        },
        {
          "id": "session-start:aa71986c72a8dd1f4:2026-04-18T07:45:17.089Z",
          "at": "2026-04-18T07:45:17.089Z",
          "kind": "update",
          "agent": "general-purpose:aa71986",
          "detail": "started general-purpose:aa71986",
          "sourceKey": "session-start:aa71986c72a8dd1f4"
        },
        {
          "id": "session-start:aadbd702e8fdf756c:2026-04-18T07:45:17.153Z",
          "at": "2026-04-18T07:45:17.153Z",
          "kind": "update",
          "agent": "general-purpose:aadbd70",
          "detail": "started general-purpose:aadbd70",
          "sourceKey": "session-start:aadbd702e8fdf756c"
        },
        {
          "id": "session-stop:aadbd702e8fdf756c:2026-04-18T07:46:15.279Z",
          "at": "2026-04-18T07:46:15.279Z",
          "kind": "completion",
          "agent": "general-purpose:aadbd70",
          "detail": "completed",
          "sourceKey": "session-stop:aadbd702e8fdf756c"
        },
        {
          "id": "session-stop:aa71986c72a8dd1f4:2026-04-18T07:46:50.058Z",
          "at": "2026-04-18T07:46:50.058Z",
          "kind": "completion",
          "agent": "general-purpose:aa71986",
          "detail": "completed",
          "sourceKey": "session-stop:aa71986c72a8dd1f4"
        }
      ]
    },
    {
      "id": "session:78e67b30-83ce-4757-a175-68c3442c4534:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-18T08:34:11.529Z",
      "updatedAt": "2026-04-18T08:52:24.581Z",
      "status": "done",
      "workerCount": 1,
      "taskCounts": {
        "total": 1,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 1,
        "failed": 0
      },
      "agents": [
        {
          "name": "general-purpose:a6a0c97",
          "role": "general-purpose",
          "ownership": "a6a0c97facebc27a6",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-18T08:52:24.581Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a6a0c97facebc27a6:2026-04-18T08:34:11.529Z",
          "at": "2026-04-18T08:34:11.529Z",
          "kind": "update",
          "agent": "general-purpose:a6a0c97",
          "detail": "started general-purpose:a6a0c97",
          "sourceKey": "session-start:a6a0c97facebc27a6"
        },
        {
          "id": "session-stop:acc34e8d2cd052b69:2026-04-18T08:42:23.674Z",
          "at": "2026-04-18T08:42:23.674Z",
          "kind": "completion",
          "agent": "general-purpose:a6a0c97",
          "detail": "completed",
          "sourceKey": "session-stop:acc34e8d2cd052b69"
        },
        {
          "id": "session-stop:a6a0c97facebc27a6:2026-04-18T08:52:24.581Z",
          "at": "2026-04-18T08:52:24.581Z",
          "kind": "completion",
          "agent": "general-purpose:a6a0c97",
          "detail": "completed",
          "sourceKey": "session-stop:a6a0c97facebc27a6"
        }
      ]
    }
  ]
}
Code/.omc/state/subagent-tracking.json
@@ -686,10 +686,91 @@
      "status": "completed",
      "completed_at": "2026-04-16T13:59:54.835Z",
      "duration_ms": 111569
    },
    {
      "agent_id": "a1ab62633715919c8",
      "agent_type": "Explore",
      "started_at": "2026-04-18T06:36:59.451Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:39:31.702Z",
      "duration_ms": 152251
    },
    {
      "agent_id": "ae116cb86a435866d",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T06:49:33.031Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:50:41.503Z",
      "duration_ms": 68472
    },
    {
      "agent_id": "a4e061e4a6d17c682",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T06:49:33.090Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:52:02.047Z",
      "duration_ms": 148957
    },
    {
      "agent_id": "afdf6efad435c036a",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T06:49:33.151Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:50:50.428Z",
      "duration_ms": 77277
    },
    {
      "agent_id": "ab1f8d2625bd8a667",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T06:49:33.209Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:54:35.702Z",
      "duration_ms": 302493
    },
    {
      "agent_id": "a99f2fa258ad9b5a1",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T06:49:33.266Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T06:50:30.602Z",
      "duration_ms": 57336
    },
    {
      "agent_id": "aa71986c72a8dd1f4",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T07:45:17.089Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T07:46:50.058Z",
      "duration_ms": 92969
    },
    {
      "agent_id": "aadbd702e8fdf756c",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T07:45:17.153Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T07:46:15.279Z",
      "duration_ms": 58126
    },
    {
      "agent_id": "a6a0c97facebc27a6",
      "agent_type": "general-purpose",
      "started_at": "2026-04-18T08:34:11.529Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-18T08:52:24.581Z",
      "duration_ms": 1093052
    }
  ],
  "total_spawned": 77,
  "total_completed": 74,
  "total_spawned": 83,
  "total_completed": 83,
  "total_failed": 0,
  "last_updated": "2026-04-16T13:59:54.939Z"
  "last_updated": "2026-04-18T14:04:59.113Z"
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/HostedService/ApiRouteCacheWarmupHostedService.cs
@@ -46,8 +46,16 @@
        public Task StartAsync(CancellationToken cancellationToken)
        {
            const string cacheKey = $"{RedisPrefix.Code}";
            _cache.RemoveByPrefix($"{cacheKey}");
            const string cacheKeyPrefix = $"{RedisPrefix.Code}:{RedisName.API}:";
            int warmedCount = 0;
            foreach ((string configKey, string routePath) in ApiRouteMappings)
            {
@@ -55,7 +63,7 @@
                warmedCount++;
            }
            _logger.LogInformation("��API·�ɻ���Ԥ����ɡ�����={Count}", warmedCount);
            _logger.LogInformation("API路由缓存预热完成,共加载={Count}个路由映射", warmedCount);
            return Task.CompletedTask;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -88,11 +88,11 @@
      "110681": "Split"
    },
    "AddressRoadwayMap": { // å¯¹åº”设备地址映射
      "11001": "换盘机械手",
      "11010": "换盘机械手",
      "11068": "注液组盘机械手",
      "10010": "换盘机械手",
      "10030": "换盘机械手"
      "11001": "HCSC1",
      "11010": "HCSC1",
      "11068": "GWSC1",
      "10010": "GWSC1",
      "10030": "GWSC1"
    },
    "AddressSourceLineNoMap": { // å¯¹åº”输送线编号地址映射
      "11001": "10010",
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/Flows/OutboundTaskFlowService.cs
@@ -93,12 +93,12 @@
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutFinish && task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
            {
                WebResponseContent content = _robotTaskService.GetWMSRobotTask(task);
                if (!content.Status)
                {
                    return content;
                }
                return OutboundFinishTaskTray(task);
                return _robotTaskService.GetWMSRobotTask(task);
                //if (!content.Status)
                //{
                //    return content;
                //}
                //return OutboundFinishTaskTray(task);
            }
            if (task.TaskStatus == (int)TaskOutStatusEnum.Line_OutExecuting)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -144,9 +144,6 @@
                    //    MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2),
                    //};
                    //_logger.LogDebug("Execute:开始并行处理输送线 {DeviceCode},子设备数量: {Count}", conveyorLine.DeviceCode, childDeviceCodes.Count);
                    //QuartzLogger.Debug($"开始并行处理输送线,子设备数量: {childDeviceCodes.Count}", conveyorLine.DeviceCode);
                    // å¹¶è¡Œå¤„理每个子设备
                    //Parallel.For(0, childDeviceCodes.Count, parallelOptions, i =>
                    foreach (var childDeviceCode in childDeviceCodes)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotBarcodeGenerator.cs
@@ -1,4 +1,5 @@
using Masuit.Tools;
using WIDESEAWCS_Communicator;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
@@ -18,7 +19,11 @@
            var device = Storage.Devices.Where(d => d.DeviceName == "A区_一注输送线").FirstOrDefault();
            if (!device.IsNullOrEmpty() && device != null && device.Communicator.IsConnected)
            {
                var trayBarcode = device.Communicator.Read<string>(prefix);
                var trayBarcode = device.Communicator.Read<string>(prefix).Trim().Replace("\u0018", "").Replace("\u0006", "");
                if(trayBarcode == "NoRead")
                {
                    trayBarcode = "";
                }
                return trayBarcode;
            }
            return "";
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -197,10 +197,11 @@
        /// <remarks>
        /// é˜¶æ®µå®šä¹‰ï¼š
        /// 0: æœªå¼€å§‹
        /// 1: å–正常电芯(流向B) / å–假电芯(流向A)
        /// 2: æ”¾æ­£å¸¸ç”µèŠ¯ï¼ˆæµå‘B) / æ”¾å‡ç”µèŠ¯ï¼ˆæµå‘A)
        /// 3: å–假电芯(流向B Phase2)
        /// 4: æ”¾å‡ç”µèŠ¯åˆ°5号位(流向B Phase2)
        /// 1: å–正常电芯(两流向相同)
        /// 2: æ”¾æ­£å¸¸ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆä¸¤æµå‘ç›¸åŒï¼‰
        /// 3: æµå‘A:正常取完→空托盘回库→取假电芯从5号位 / æµå‘B:正常取完→取假电芯从源地址
        /// 4: æµå‘A:放假电芯到目标托盘 / æµå‘B:放假电芯到5号位
        /// 5: æµå‘A:假放完→allputfinished入库HCSC1 / æµå‘B:假取完→空托盘回库HCSC1+组盘入库GWSC1
        /// </remarks>
        public int ChangePalletPhase { get; set; }
@@ -211,5 +212,15 @@
        /// æ‹‰å¸¦çº¿ä¸Šç”µèŠ¯æ‰«ç æ˜¯å¦NG。
        /// </remarks>
        public bool IsScanNG { get; set; } = false;
        /// <summary>
        /// æ˜¯å¦ç”µèŠ¯åˆ°ä½
        /// </summary>
        /// <remarks>
        /// æ‹‰å¸¦çº¿ä¸Šç”µèŠ¯æ˜¯å¦åˆ°ä½ã€‚
        /// </remarks>
        public bool BatteryArrived { get; set; } = false;
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -165,7 +165,7 @@
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="isScanNG">是否扫码NG</param>
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG)
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG = false)
        {
            // æž„建取货指令,格式:Pickbattery,{源地址}
            string taskString = $"Pickbattery,{task.RobotSourceAddress}";
@@ -340,6 +340,53 @@
            {
                _logger.LogError("下发批次取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发批次取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// ä¸‹å‘假电芯取货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// å‘送顺序:
        /// 1. PickTotalNum,{N} -- çœŸå®žç”µèŠ¯æ€»æ•°
        /// 2. Pickbattery,5,{start}-{end} -- æ‰¹æ¬¡å–货指令(固定从5号位取)
        ///
        /// ä¸‹å‘成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendFakeBatteryPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, int batchStart, int batchEnd)
        {
            // å…ˆå‘送总数指令
            string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // å†å‘送批次取货指令(假电芯固定从5号位取)
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Pickbattery,5,{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);
            }
        }
@@ -600,7 +647,8 @@
                        Channel = x,
                        // ç”µæ± æ¡ç ï¼šå¦‚果状态中有条码列表,取对应位置的条码;否则为空
                        CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
                        //CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
                        CellBarcode = state.CellBarcode[idx].ToString()
                    })
                    .ToList()
            };
@@ -646,10 +694,11 @@
        /// å½“拆盘任务全部取完时调用,一次性上传整个托盘的解绑数据到 MES。
        /// </remarks>
        /// <param name="palletCode">源托盘号</param>
        /// <param name="deviceName">设备名称,用于传递到 WMS</param>
        /// <returns>HTTP å“åº”结果</returns>
        public HttpResponseResult<WebResponseContent> PostSplitPalletConfirmAsync(string palletCode)
        public HttpResponseResult<WebResponseContent> PostSplitPalletConfirmAsync(string palletCode, string deviceName)
        {
            var request = new { PalletCode = palletCode };
            var request = new { PalletCode = palletCode, DeviceName = deviceName };
            return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.SplitPalletConfirm), request.ToJson());
        }
@@ -660,10 +709,11 @@
        /// å½“组盘任务全部放完时调用,一次性上传整个托盘的绑定数据到 MES。
        /// </remarks>
        /// <param name="palletCode">目标托盘号</param>
        /// <param name="deviceName">设备名称,用于传递到 WMS</param>
        /// <returns>HTTP å“åº”结果</returns>
        public HttpResponseResult<WebResponseContent> PostGroupPalletConfirmAsync(string palletCode)
        public HttpResponseResult<WebResponseContent> PostGroupPalletConfirmAsync(string palletCode, string deviceName)
        {
            var request = new { PalletCode = palletCode };
            var request = new { PalletCode = palletCode, DeviceName = deviceName };
            return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.GroupPalletConfirm), request.ToJson());
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -1,7 +1,6 @@
using System.Net.Sockets;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
@@ -270,28 +269,36 @@
                    // æ¢ç›˜ä»»åŠ¡ï¼šæ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†
                    if (state.ChangePalletPhase == 2)
                    {
                        if (isFlowA)
                        {
                            // æµå‘A Phase2:放假电芯到目标托盘,不调用 API,不递增计数
                            // ä»…更新状态
                        }
                        else
                        {
                            // æµå‘B Phase2:放正常电芯,递增计数
                            state.RobotTaskTotalNum += positions.Length;
                            if (task != null)
                                task.RobotTaskTotalNum -= positions.Length;
                        // Phase 2:放正常电芯到目标托盘完成,递增计数
                        state.RobotTaskTotalNum += positions.Length;
                        if (task != null)
                            task.RobotTaskTotalNum -= positions.Length;
                            // æž„建库存 DTO å¹¶è°ƒç”¨ ChangePalletAsync API
                            var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                            var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.ChangePalletAsync), stockDTO);
                            putSuccess = result.Data.Status && result.IsSuccess;
                        }
                        // ä¸¤æµå‘均调用换盘 API
                        var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                        var result = _taskProcessor.PostGroupPalletAsync(nameof(ConfigKey.ChangePalletAsync), stockDTO);
                        putSuccess = result.Data.Status && result.IsSuccess;
                        // åˆ‡å›ž Phase 1,继续取正常电芯
                        state.ChangePalletPhase = 1;
                    }
                    else if (state.ChangePalletPhase == 4)
                    {
                        // æµå‘B Phase4:放假电芯到5号位,不调用 API,不递增计数,释放点位
                        _fakeBatteryPositionService.MarkAsAvailable(positions.ToList());
                        if (isFlowA)
                        {
                            // æµå‘A:放假电芯到目标托盘,仅递增计数,不调用 API
                            state.RobotTaskTotalNum += positions.Length;
                            if (task != null)
                                task.RobotTaskTotalNum -= positions.Length;
                        }
                        else
                        {
                            // æµå‘B:放假电芯到5号位,释放点位
                            _fakeBatteryPositionService.MarkAsAvailable(positions.ToList());
                        }
                        // åˆ‡å›ž Phase 3,继续取假电芯
                        state.ChangePalletPhase = 3;
                    }
                    else
                    {
@@ -352,4 +359,4 @@
            }
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -119,6 +119,11 @@
                    state.CurrentAction = "Putting";
                    return true;
                // æ˜¯å¦ç”µèŠ¯åˆ°ä½
                case "batteryarrived":
                    state.BatteryArrived = true;
                    return true;
                // ==================== å…¨éƒ¨å®Œæˆå‘½ä»¤ ====================
                // å…¨éƒ¨å–货完成
@@ -138,35 +143,72 @@
                        // åˆ¤æ–­ä»»åŠ¡ç±»åž‹
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        // æ¢ç›˜ä»»åŠ¡ï¼šæ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            if (state.ChangePalletPhase == 0)
                            // è°ƒç”¨æ‰¹é‡æ‹†ç›˜ç¡®è®¤æŽ¥å£
                            var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode;
                            var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            {
                                // è°ƒç”¨æ‰¹é‡æ‹†ç›˜ç¡®è®¤æŽ¥å£ï¼ˆæ¢ç›˜å–完阶段)
                                var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode;
                                var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet);
                                if (!confirmResult.IsSuccess)
                                {
                                    QuartzLogger.Error($"批量拆盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                }
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                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;
                                }
                                QuartzLogger.Error($"批量拆盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            if (state.ChangePalletPhase == 5)
                            {
                                // FlowB æœ€ç»ˆé˜¶æ®µï¼šå‡ç”µèŠ¯å–å®Œï¼Œæºç©ºæ‰˜ç›˜å›žåº“ HCSC1
                                if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                                {
                                    return false;
                                }
                                if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                                {
                                    QuartzLogger.Error($"allpickfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                    return false;
                                }
                                await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                                QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                                state.CurrentTask = null;
                                state.RobotTaskTotalNum = 0;
                                state.CellBarcode = new List<string>();
                                state.ChangePalletPhase = 0;
                                state.CurrentBatchIndex = 1;
                                state.IsInFakeBatteryMode = false;
                                return true;
                            }
                            else if (state.ChangePalletPhase != 0)
                            {
                                // FlowA ä¸­é—´é˜¶æ®µï¼šæ­£å¸¸ç”µèŠ¯å–å®Œï¼Œæºç©ºæ‰˜ç›˜å›žåº“ GWSC1
                                // ä¸åˆ é™¤ä»»åŠ¡ï¼Œä¸é‡ç½®çŠ¶æ€ï¼Œç»§ç»­ Phase 3-4 å‡ç”µèŠ¯æµç¨‹
                                if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                                {
                                    return false;
                                }
                                return true;
                            }
                            // Phase == 0: éžæ‰¹æ¬¡æ¨¡å¼ï¼ˆç›®æ ‡æ€»æ•°==48)
                            if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                            {
                                return false;
                            }
                            if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                            {
                                QuartzLogger.Error($"allpickfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            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;
                        }
@@ -175,20 +217,28 @@
                        {
                            // è°ƒç”¨æ‰¹é‡æ‹†ç›˜ç¡®è®¤æŽ¥å£
                            var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode;
                            var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet);
                            var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            {
                                QuartzLogger.Error($"批量拆盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                            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;
                            }
                            // å…¥åº“成功,删除任务记录
                            if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                            {
                                QuartzLogger.Error($"allpickfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Swap,diskFinished");
                            QuartzLogger.Info($"发送消息:【Swap,diskFinished】", state.RobotCrane.DeviceName);
                            return true;
                        }
                        return false;
                    }
@@ -209,41 +259,77 @@
                        // åˆ¤æ–­ä»»åŠ¡ç±»åž‹
                        var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                        // æ¢ç›˜ä»»åŠ¡ï¼šä»…å½“æ‰€æœ‰é˜¶æ®µå®Œæˆæ—¶æ‰å¤„ç†å…¥åº“
                        // æ¢ç›˜ä»»åŠ¡ï¼šæ ¹æ®é˜¶æ®µåŒºåˆ†å¤„ç†
                        if (robotTaskType == RobotTaskTypeEnum.ChangePallet)
                        {
                            if (state.ChangePalletPhase == 0)
                            // è°ƒç”¨æ‰¹é‡ç»„盘确认接口
                            var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                            var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            {
                                // è°ƒç”¨æ‰¹é‡ç»„盘确认接口(换盘放完阶段)
                                var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                                var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet);
                                if (!confirmResult.IsSuccess)
                                {
                                    QuartzLogger.Error($"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                }
                                // æ‰€æœ‰é˜¶æ®µå®Œæˆï¼Œå¤„理入库
                                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;
                                }
                                QuartzLogger.Error($"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            // ä¸­é—´é˜¶æ®µä¸å¤„理,仅更新状态
                            if (state.ChangePalletPhase == 5)
                            {
                                // FlowA æœ€ç»ˆé˜¶æ®µï¼šå‡ç”µèŠ¯æ”¾å®Œï¼Œç›®æ ‡æ‰˜ç›˜æ»¡48入库 HCSC1
                                if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                                {
                                    return false;
                                }
                                if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                                {
                                    QuartzLogger.Error($"allputfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                    return false;
                                }
                                state.CurrentTask = null;
                                state.RobotTaskTotalNum = 0;
                                state.CellBarcode = new List<string>();
                                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;
                            }
                            else if (state.ChangePalletPhase != 0)
                            {
                                // FlowB ä¸­é—´é˜¶æ®µï¼šæ­£å¸¸ç”µèŠ¯æ”¾å®Œï¼Œæœ‰è´§æ‰˜ç›˜ç»„ç›˜å…¥åº“ GWSC1
                                // ä¸åˆ é™¤ä»»åŠ¡ï¼Œä¸é‡ç½®çŠ¶æ€ï¼Œç»§ç»­ Phase 3-4 å‡ç”µèŠ¯æµç¨‹
                                if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                                {
                                    return false;
                                }
                                return true;
                            }
                            // Phase == 0: éžæ‰¹æ¬¡æ¨¡å¼ï¼ˆç›®æ ‡æ€»æ•°==48)
                            if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                            {
                                return false;
                            }
                            if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                            {
                                QuartzLogger.Error($"allputfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            state.CurrentTask = null;
                            state.RobotTaskTotalNum = 0;
                            state.CellBarcode = new List<string>();
                            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;
                        }
@@ -252,28 +338,35 @@
                        {
                            // è°ƒç”¨æ‰¹é‡ç»„盘确认接口
                            var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode;
                            var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet);
                            var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet, state.RobotCrane?.DeviceName);
                            if (!confirmResult.IsSuccess)
                            {
                                QuartzLogger.Error($"批量组盘确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            // å¤„理入库任务回传
                            // useSourceAddress: false è¡¨ç¤ºä½¿ç”¨ç›®æ ‡åœ°å€ï¼ˆç»„盘场景)
                            if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                            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;
                            }
                            // å…¥åº“成功,删除任务记录
                            if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true)
                            {
                                QuartzLogger.Error($"allputfinished:删除任务记录失败,任务号: {currentTask.RobotTaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                                return false;
                            }
                            // æ¸…理状态,为下一个任务做准备
                            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;
                    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -112,7 +112,7 @@
            // 1. è¿è¡Œæ¨¡å¼ä¸ºè‡ªåŠ¨ï¼ˆ2)
            // 2. æŽ§åˆ¶æ¨¡å¼ä¸ºå®¢æˆ·ç«¯æŽ§åˆ¶ï¼ˆ1)
            // 3. è¿è¡ŒçŠ¶æ€æ˜¯ Running
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && latestState.Homed == "Homed")
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running" && (latestState.Homed == "Homed" || latestState.Homed.IsNullOrEmpty()))
            {
                // ========== å–货完成后的放货处理 ==========
                // æ¡ä»¶ï¼š
@@ -120,11 +120,11 @@
                // - æ‰‹è‡‚上有物料(RobotArmObject == 1)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPickFinish(已记录取货完成)
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
                    && latestState.RobotArmObject == 1
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 1)
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始处理取货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始下发放货任务,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始下发放货任务", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // å‘送放货指令
                    await HandlePickFinishedStateAsync(task, ipAddress);
                }
@@ -136,12 +136,12 @@
                // - æ‰‹è‡‚上无物料(RobotArmObject == 0)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPutFinish æˆ–不是 RobotExecuting
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction.IsNullOrEmpty())
                    && latestState.RobotArmObject == 0
                    && (latestState.RobotArmObject.IsNullOrEmpty() || latestState.RobotArmObject == 0)
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
                    || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    _logger.LogInformation("ExecuteAsync:满足取货条件,开始处理放货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始处理放货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    _logger.LogInformation("ExecuteAsync:满足取货条件,开始下发取货任务,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始下发取货任务", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // å‘送取货指令
                    await HandlePutFinishedStateAsync(task, ipAddress);
                }
@@ -166,7 +166,7 @@
            var state = _stateManager.GetState(ipAddress);
            // æ¢ç›˜ä»»åŠ¡ä½¿ç”¨æ‰¹æ¬¡æ ¼å¼
            // æ¢ç›˜ä»»åŠ¡æ‰¹æ¬¡æ¨¡å¼
            if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                int targetNormalCount = task.RobotTaskTotalNum;
@@ -174,78 +174,94 @@
                bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010";
                // æµå‘A Phase 2:放假电芯到目标托盘
                if (isFlowA && state?.ChangePalletPhase == 2)
                // ==================== Phase 2: æ”¾æ­£å¸¸ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆä¸¤æµå‘ç›¸åŒï¼‰====================
                // PickFinished åˆ°è¾¾ï¼šPhase 1 çš„ Pick å‘½ä»¤å®Œæˆï¼ŒçŽ°åœ¨ä¸‹å‘ Put å‘½ä»¤æ”¾æ­£å¸¸ç”µèН
                if (state?.ChangePalletPhase == 2)
                {
                    int remaining = 48 - currentCompletedCount;
                    if (remaining <= 0) return;
                    int remainingNormal = targetNormalCount - currentCompletedCount;
                    if (remainingNormal <= 0)
                    {
                        // æ­£å¸¸ç”µèŠ¯å…¨éƒ¨æ”¾å®Œï¼Œç­‰å¾… HandlePutFinishedAsync åˆ‡æ¢åˆ° Phase 3
                        return;
                    }
                    int batchStart = targetNormalCount + 1 + (state.CurrentBatchIndex - 1);
                    int putCount = Math.Min(4, remaining);
                    int batchStart = (currentCompletedCount / 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);
                    // Phase ä¿æŒä¸º 2,等 HandlePutFinishedAsync å¤„理完放货计数后再切回 Phase 1
                    _stateManager.TryUpdateStateSafely(ipAddress, state);
                    return;
                }
                // æµå‘B Phase 4:放假电芯到5号位
                if (!isFlowA && state?.ChangePalletPhase == 4)
                // ==================== Phase 4: æ”¾å‡ç”µèŠ¯ï¼ˆä¸¤æµå‘åˆ†å‰ï¼‰====================
                // PickFinished åˆ°è¾¾ï¼šPhase 3 çš„ Pick å‘½ä»¤å®Œæˆï¼ŒçŽ°åœ¨ä¸‹å‘ Put å‘½ä»¤æ”¾å‡ç”µèН
                if (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)
                    if (remainingFake <= 0)
                    {
                        _logger.LogError("HandlePickFinishedStateAsync:无可用假电芯点位,任务号: {TaskNum}", task.RobotTaskNum);
                        // å‡ç”µèŠ¯å…¨éƒ¨æ”¾å®Œï¼Œç­‰å¾… allputfinished è§¦å‘ Phase 5 å…¥åº“
                        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
            {
                // éžæ¢ç›˜ä»»åŠ¡ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
                if (state != null && state.IsGroupPallet && task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
                {
                    // ç»„盘任务:放货需判断是否NG,如果NG则放到NG口
                    if (state.IsScanNG)
                    if (isFlowA)
                    {
                        taskString = $"Putbattery,NG";
                        // æµå‘A:放假电芯到目标托盘
                        int batchStart = targetNormalCount + 1 + (state.CurrentBatchIndex - 1);
                        int putCount = Math.Min(4, remainingFake);
                        var (start, end) = _taskProcessor.BuildBatchRange(batchStart, putCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, state, task.RobotTargetAddress, start, end);
                        state.CurrentBatchIndex += putCount;
                        // Phase ä¿æŒä¸º 4,等 HandlePutFinishedAsync å¤„理完后再切回 Phase 3
                        _stateManager.TryUpdateStateSafely(ipAddress, state);
                    }
                    else
                    {
                        taskString = $"Putbattery,{task.RobotTargetAddress}";
                        // æµå‘B:放假电芯到5号位
                        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);
                        // Phase ä¿æŒä¸º 4,等 HandlePutFinishedAsync å¤„理完后再切回 Phase 3
                        _stateManager.TryUpdateStateSafely(ipAddress, state);
                    }
                    return;
                }
                // éžæ‰¹æ¬¡æ¨¡å¼æˆ–其他阶段不下发指令
                return;
            }
            // éžæ¢ç›˜ä»»åŠ¡ï¼šä½¿ç”¨åŽŸæœ‰æ ¼å¼
            if (state != null && state.IsGroupPallet && task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                if (state.IsScanNG)
                {
                    taskString = $"Putbattery,1";
                }
                else
                {
                    taskString = $"Putbattery,{task.RobotTargetAddress}";
                }
            }
            else
                taskString = $"Putbattery,{task.RobotTargetAddress}";
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
@@ -316,6 +332,11 @@
            // å¦‚果是组盘任务
            if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                // æ£€æŸ¥ç”µæ± æ˜¯å¦å·²åˆ°ä½
                if (!stateForUpdate.BatteryArrived)
                {
                    return;
                }
                // è¯»å–线体电芯条码
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.990");
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.1020");
@@ -332,14 +353,18 @@
                        // å‘送取货指令 æ ‡è®°æ‰«ç NG,放货时不使用这些条码,并放入NG口
                        await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                        return;
                    }
                    else
                    {
                        _logger.LogInformation("HandlePutFinishedStateAsync:读取的托盘条码唯一,继续执行,任务号: {TaskNum}", task.RobotTaskNum);
                        QuartzLogger.Info($"读取的托盘条码唯一,继续执行", stateForUpdate.RobotCrane.DeviceName);
                        // å°†æ¡ç æ·»åŠ åˆ°çŠ¶æ€ä¸­ï¼Œä¾›åŽç»­æ”¾è´§æ—¶ä½¿ç”¨
                        stateForUpdate.CellBarcode.Add(trayBarcode1);
                        stateForUpdate.CellBarcode.Add(trayBarcode2);
                        stateForUpdate.CellBarcode = new List<string>()
                        {
                            trayBarcode1,trayBarcode2
                        };
                    }
@@ -348,7 +373,7 @@
                    QuartzLogger.Info($"读取托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // å‘送取货指令
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
                else
                {
@@ -359,12 +384,15 @@
                    // å‘送取货指令 æ ‡è®°æ‰«ç NG,放货时不使用这些条码,并放入NG口
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true);
                    return;
                }
            }
            else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode())
            {
                const int targetTotal = 48;
                // æ¢ç›˜ä»»åŠ¡è¿›å…¥æ‰¹æ¬¡æ¨¡å¼ï¼Œåˆ†é˜¶æ®µå¤„ç†æ­£å¸¸ç”µèŠ¯å’Œå‡ç”µèŠ¯ ï¼ˆæœºå™¨äººä»»åŠ¡æ€»æ•°ï¼‰
                int targetNormalCount = task.RobotTaskTotalNum;
                // å½“前已完成数量(取货完成的数量),初始为状态中的 RobotTaskTotalNum,后续根据取货完成的数量动态更新
                int currentCompletedCount = stateForUpdate.RobotTaskTotalNum;
                // åˆ¤æ–­æµå‘(null-safe)
@@ -373,7 +401,7 @@
                // ç›®æ ‡æ•°é‡ä¸º48:直接走原有逻辑,不进入批次模式
                if (targetNormalCount == targetTotal)
                {
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                    return;
                }
@@ -384,165 +412,88 @@
                    stateForUpdate.CurrentBatchIndex = 1;
                    _logger.LogInformation("HandlePutFinishedStateAsync:换盘任务进入批次模式,任务号: {TaskNum},流向: {Flow}",
                        task.RobotTaskNum, isFlowA ? "A" : "B");
                    _stateManager.TryUpdateStateSafely(ipAddress, stateForUpdate);
                }
                // ==================== æµå‘A:补假电芯到目标托盘 ====================
                if (isFlowA)
                // ==================== Phase 1: å–正常电芯(两流向相同)====================
                if (stateForUpdate.ChangePalletPhase == 1)
                {
                    // Phase 1: å–假电芯(从5号位,使用 PositionIndex)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    int remainingNormal = targetNormalCount - currentCompletedCount;
                    if (remainingNormal <= 0)
                    {
                        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 3 å–假电芯
                        stateForUpdate.ChangePalletPhase = 3;
                        stateForUpdate.CurrentBatchIndex = 1; // å‡ç”µèŠ¯æ‰¹æ¬¡ä»Žå¤´å¼€å§‹
                        _logger.LogInformation("HandlePutFinishedStateAsync:正常电芯取完,切换到Phase 3取假电芯,任务号: {TaskNum}", task.RobotTaskNum);
                        _stateManager.TryUpdateStateSafely(ipAddress, stateForUpdate);
                        return;
                    }
                    // 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);
                    int pickCount = Math.Min(4, remainingNormal);
                    var (start, end) = _taskProcessor.BuildBatchRange(stateForUpdate.CurrentBatchIndex, pickCount);
                        await _taskProcessor.SendPutWithBatchAsync(task, stateForUpdate, task.RobotTargetAddress, start, end);
                    await _taskProcessor.SendPickWithBatchAsync(task, stateForUpdate, task.RobotSourceAddress, start, end);
                        stateForUpdate.CurrentBatchIndex += putCount;
                        stateForUpdate.ChangePalletPhase = 1;
                    }
                    stateForUpdate.CurrentBatchIndex += pickCount;
                    // å‘完 Pick åŽåˆ‡æ¢åˆ° Phase=2,等 PickFinished è§¦å‘ HandlePickFinishedStateAsync ä¸‹å‘放货指令
                    stateForUpdate.ChangePalletPhase = 2;
                    _stateManager.TryUpdateStateSafely(ipAddress, stateForUpdate);
                    return;
                }
                // ==================== æµå‘B:取正常电芯 + å›žæ”¶å‡ç”µèН ====================
                else
                // ==================== Phase 3: å¤„理假电芯(流向A/B åˆ†å‰ï¼‰ï¼Œè‡ªå·±å¾ªçޝ ====================
                if (stateForUpdate.ChangePalletPhase == 3)
                {
                    // Phase 1: å–正常电芯(从源地址,从1开始递增)
                    if (stateForUpdate.ChangePalletPhase == 1)
                    int fakeCount = targetTotal - targetNormalCount;
                    int completedFake = Math.Max(0, currentCompletedCount - targetNormalCount);
                    int remainingFake = fakeCount - completedFake;
                    if (remainingFake <= 0)
                    {
                        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 5 ç­‰å¾…入库
                        stateForUpdate.ChangePalletPhase = 5;
                        _logger.LogInformation("HandlePutFinishedStateAsync:假电芯处理完毕,切换到Phase 5等待入库,任务号: {TaskNum}", task.RobotTaskNum);
                        _stateManager.TryUpdateStateSafely(ipAddress, stateForUpdate);
                        return;
                    }
                    // Phase 2: æ”¾æ­£å¸¸ç”µèŠ¯åˆ°ç›®æ ‡æ‰˜ç›˜ï¼ˆæ”¾è´§ç¼–å·ä¸Žå–è´§ç¼–å·ä¸€è‡´ï¼‰
                    else if (stateForUpdate.ChangePalletPhase == 2)
                    if (isFlowA)
                    {
                        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;
                        }
                        // æµå‘A:从5号位取假电芯
                        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;
                        await _taskProcessor.SendSocketRobotFakeBatteryPickAsync(task, stateForUpdate, positions);
                    }
                    else
                    {
                        // æµå‘B:从源地址取假电芯
                        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;
                    }
                    // å‘完 Pick åŽåˆ‡æ¢åˆ° Phase=4,等 PickFinished è§¦å‘ HandlePickFinishedStateAsync ä¸‹å‘放货指令
                    stateForUpdate.ChangePalletPhase = 4;
                    _stateManager.TryUpdateStateSafely(ipAddress, stateForUpdate);
                    return;
                }
                // ==================== Phase 5: å®Œæˆå…¥åº“(allputfinished / allpickfinished è§¦å‘)====================
                if (stateForUpdate.ChangePalletPhase == 5)
                {
                    // Phase 5 ç”± allpickfinished/allputfinished è§¦å‘入库,本方法不再下发指令
                    return;
                }
            }
            else
            {
                // éžç»„盘任务,直接发送取货指令
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false);
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
            }
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
@@ -59,6 +59,12 @@
                            break;
                        }
                        if(message == lastMessage)
                        {
                            // é‡å¤æ¶ˆæ¯ï¼Œå¿½ç•¥
                            continue;
                        }
                        // æ›´æ–°å®¢æˆ·ç«¯çŠ¶æ€
                        UpdateClientStatus(clientId, message);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.cs
@@ -136,6 +136,11 @@
        public bool IsRunning { get; private set; }
        /// <summary>
        /// ä¸Šæ¬¡æŽ¥æ”¶æ¶ˆæ¯æº
        /// </summary>
        public string lastMessage;
        /// <summary>
        /// æ¶ˆæ¯æŽ¥æ”¶äº‹ä»¶
        /// </summary>
        /// <remarks>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -190,16 +190,12 @@
                // ========== æ£€æŸ¥å †åž›æœºä»»åŠ¡å®ŒæˆçŠ¶æ€ ==========
                commonStackerCrane.CheckStackerCraneTaskCompleted();
                //_logger.LogDebug("Execute:检查任务完成状态,设备: {DeviceCode}", _deviceCode);
                //QuartzLogger.Debug($"检查任务完成状态,设备: {_deviceCode}", _deviceCode);
                // ========== æ£€æŸ¥æ˜¯å¦å¯ä»¥å‘送新任务 ==========
                //if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                if (commonStackerCrane.StackerCraneStatusValue != StackerCraneStatus.Normal /*&& commonStackerCrane.StackerCraneAutoStatusValue != StackerCraneAutoStatus.Automatic && commonStackerCrane.StackerCraneWorkStatusValue != StackerCraneWorkStatus.Standby*/)
                if (commonStackerCrane.StackerCraneStatusValue != StackerCraneStatus.Normal )
                {
                    // å †åž›æœºä¸å¯ç”¨ï¼ˆå¦‚正在执行上一任务),直接返回
                    //_logger.LogDebug("Execute:堆垛机不可用,设备: {DeviceCode}", _deviceCode);
                    //QuartzLogger.Debug($"堆垛机不可用,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
@@ -209,13 +205,8 @@
                if (task == null)
                {
                    // æ²¡æœ‰å¯ç”¨ä»»åŠ¡
                    //_logger.LogDebug("Execute:没有可用任务,设备: {DeviceCode}", _deviceCode);
                    //QuartzLogger.Debug($"没有可用任务,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
                //_logger.LogInformation("Execute:选择任务,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                //QuartzLogger.Info($"选择任务,任务号: {task.TaskNum}", _deviceCode);
                // ========== æž„建命令 ==========
                // å‘½ä»¤æž„建下沉到专用构建器
Code/WCS/WIDESEAWCS_Server/docs/»»ÅÌÈÎÎñÁ÷³Ìͼ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,291 @@
# æ¢ç›˜ä»»åŠ¡å®Œæ•´æµç¨‹åˆ†æž
## ä»»åŠ¡å‚æ•°
| å‚æ•° | å€¼ |
|------|-----|
| æµå‘ | **Flow A** (isFlowA = true, æºåœ°å€ LineCode = "11001") |
| æºåœ°å€ | 11001 |
| ç›®æ ‡åœ°å€ | 2101 |
| æ­£å¸¸ç”µèŠ¯æ•°é‡ | **33** |
| å‡ç”µèŠ¯æ•°é‡ | 48 - 33 = **15** |
| ç›®æ ‡æ€»æ•° | 48 |
---
## é˜¶æ®µçŠ¶æ€å®šä¹‰
| Phase | å«ä¹‰ |
|-------|------|
| 0 | æœªå¼€å§‹ |
| 1 | å–正常电芯 |
| 2 | æ”¾æ­£å¸¸ç”µèН |
| 3 | å–假电芯(Flow A: ä»Ž5号位取) |
| 4 | æ”¾å‡ç”µèŠ¯ï¼ˆFlow A: æ”¾åˆ°ç›®æ ‡æ‰˜ç›˜ï¼‰ |
| 5 | ç­‰å¾…入库完成 |
---
## å®Œæ•´ä»£ç æ‰§è¡Œæµç¨‹ï¼ˆ33 æ­£å¸¸ + 15 å‡ = 48 æ€»è®¡ï¼‰
### åˆå§‹çŠ¶æ€
```
ChangePalletPhase = 0
currentCompletedCount = 0
targetNormalCount = 33
```
---
### ç¬¬1轮:Pick 1-4 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 1 | HandlePutFinishedStateAsync | 1 | 0 | 33 - 0 = **33** | PickBatch(1, 4) â†’ Phase=2 |
| 2 | (机械手执行取货) | - | - | - | - |
| 3 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished, task状态=RobotPickFinish |
| 4 | HandlePickFinishedStateAsync | 2 | 0 | 33 - 0 = **33** | PutBatch(1, 4) â†’ Phase=1 |
| 5 | (机械手执行放货) | - | - | - | - |
| 6 | HandlePutFinishedAsync | - | **4** | - | currentCompletedCount+=4, task.RobotTaskTotalNum-=4 |
---
### ç¬¬2轮:Pick 5-8 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 7 | HandlePutFinishedStateAsync | 1 | 4 | 33 - 4 = **29** | PickBatch(5, 8) â†’ Phase=2 |
| 8 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 9 | HandlePickFinishedStateAsync | 2 | 4 | 33 - 4 = **29** | PutBatch(1, 4) â†’ Phase=1 |
| 10 | HandlePutFinishedAsync | - | **8** | - | currentCompletedCount+=4 |
---
### ç¬¬3轮:Pick 9-12 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 11 | HandlePutFinishedStateAsync | 1 | 8 | 33 - 8 = **25** | PickBatch(9, 12) â†’ Phase=2 |
| 12 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 13 | HandlePickFinishedStateAsync | 2 | 8 | 33 - 8 = **25** | PutBatch(1, 4) â†’ Phase=1 |
| 14 | HandlePutFinishedAsync | - | **12** | - | currentCompletedCount+=4 |
---
### ç¬¬4轮:Pick 13-16 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 15 | HandlePutFinishedStateAsync | 1 | 12 | 33 - 12 = **21** | PickBatch(13, 16) â†’ Phase=2 |
| 16 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 17 | HandlePickFinishedStateAsync | 2 | 12 | 33 - 12 = **21** | PutBatch(1, 4) â†’ Phase=1 |
| 18 | HandlePutFinishedAsync | - | **16** | - | currentCompletedCount+=4 |
---
### ç¬¬5轮:Pick 17-20 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 19 | HandlePutFinishedStateAsync | 1 | 16 | 33 - 16 = **17** | PickBatch(17, 20) â†’ Phase=2 |
| 20 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 21 | HandlePickFinishedStateAsync | 2 | 16 | 33 - 16 = **17** | PutBatch(1, 4) â†’ Phase=1 |
| 22 | HandlePutFinishedAsync | - | **20** | - | currentCompletedCount+=4 |
---
### ç¬¬6轮:Pick 21-24 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 23 | HandlePutFinishedStateAsync | 1 | 20 | 33 - 20 = **13** | PickBatch(21, 24) â†’ Phase=2 |
| 24 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 25 | HandlePickFinishedStateAsync | 2 | 20 | 33 - 20 = **13** | PutBatch(1, 4) â†’ Phase=1 |
| 26 | HandlePutFinishedAsync | - | **24** | - | currentCompletedCount+=4 |
---
### ç¬¬7轮:Pick 25-28 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 27 | HandlePutFinishedStateAsync | 1 | 24 | 33 - 24 = **9** | PickBatch(25, 28) â†’ Phase=2 |
| 28 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 29 | HandlePickFinishedStateAsync | 2 | 24 | 33 - 24 = **9** | PutBatch(1, 4) â†’ Phase=1 |
| 30 | HandlePutFinishedAsync | - | **28** | - | currentCompletedCount+=4 |
---
### ç¬¬8轮:Pick 29-32 æ­£å¸¸ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 31 | HandlePutFinishedStateAsync | 1 | 28 | 33 - 28 = **5** | PickBatch(29, 32) â†’ Phase=2 |
| 32 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 33 | HandlePickFinishedStateAsync | 2 | 28 | 33 - 28 = **5** | PutBatch(1, 4) â†’ Phase=1 |
| 34 | HandlePutFinishedAsync | - | **32** | - | currentCompletedCount+=4 |
---
### ç¬¬9轮:Pick 33 æ­£å¸¸ç”µèŠ¯ï¼ˆæœ€åŽ1个)
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 35 | HandlePutFinishedStateAsync | 1 | 32 | 33 - 32 = **1** | PickBatch(33, 33) â†’ Phase=2 |
| 36 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 37 | HandlePickFinishedStateAsync | 2 | 32 | 33 - 32 = **1** | PutBatch(33, 33) â†’ Phase=1 |
| 38 | HandlePutFinishedAsync | - | **33** | - | currentCompletedCount+=1 |
---
### ç¬¬10轮:正常电芯全部完成 â†’ åˆ‡æ¢ Phase 3
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingNormal | æ“ä½œ |
|------|------|-------|---------------------|----------------|------|
| 39 | HandlePutFinishedStateAsync | 1 | **33** | 33 - 33 = **0** â†’ â‰¤0 | **切换 Phase=3**, Pick fake from 5号位 |
> **Flow A ç‰¹æ€§**:正常电芯取完后,源托盘(11001)已空。此时机械手携带最后一批正常电芯放到目标托盘后,源托盘自动空出回库。代码中不需要显式处理这个回库动作——由输送线自动完成。
---
### ç¬¬11轮:Pick 1-4 å‡ç”µèŠ¯ï¼ˆä»Ž5号位)
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingFake | æ“ä½œ |
|------|------|-------|---------------------|--------------|------|
| 40 | HandlePutFinishedStateAsync | 3 | 33 | 15 - 0 = **15** | Pick fake from 5号位(1,4) â†’ Phase=4 |
| 41 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 42 | HandlePickFinishedStateAsync | 4 | 33 | 15 - 0 = **15** | Put fake to target(BatchStart=34, 4个) â†’ Phase=3 |
| 43 | HandlePutFinishedAsync | - | **37** | - | currentCompletedCount+=4 |
---
### ç¬¬12轮:Pick 5-8 å‡ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingFake | æ“ä½œ |
|------|------|-------|---------------------|--------------|------|
| 44 | HandlePutFinishedStateAsync | 3 | 37 | 15 - 4 = **11** | Pick fake from 5号位(5,8) â†’ Phase=4 |
| 45 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 46 | HandlePickFinishedStateAsync | 4 | 37 | 15 - 4 = **11** | Put fake to target(BatchStart=38, 4个) â†’ Phase=3 |
| 47 | HandlePutFinishedAsync | - | **41** | - | currentCompletedCount+=4 |
---
### ç¬¬13轮:Pick 9-12 å‡ç”µèН
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingFake | æ“ä½œ |
|------|------|-------|---------------------|--------------|------|
| 48 | HandlePutFinishedStateAsync | 3 | 41 | 15 - 8 = **7** | Pick fake from 5号位(9,12) â†’ Phase=4 |
| 49 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 50 | HandlePickFinishedStateAsync | 4 | 41 | 15 - 8 = **7** | Put fake to target(BatchStart=42, 4个) â†’ Phase=3 |
| 51 | HandlePutFinishedAsync | - | **45** | - | currentCompletedCount+=4 |
---
### ç¬¬14轮:Pick 13-15 å‡ç”µèŠ¯ï¼ˆæœ€åŽ3个)
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingFake | æ“ä½œ |
|------|------|-------|---------------------|--------------|------|
| 52 | HandlePutFinishedStateAsync | 3 | 45 | 15 - 12 = **3** | Pick fake from 5号位(13,15) â†’ Phase=4 |
| 53 | HandlePickFinishedAsync | - | - | - | CurrentAction=PickFinished |
| 54 | HandlePickFinishedStateAsync | 4 | 45 | 15 - 12 = **3** | Put fake to target(BatchStart=46, 3个) â†’ Phase=3 |
| 55 | HandlePutFinishedAsync | - | **48** | - | currentCompletedCount+=3 |
---
### ç¬¬15轮:假电芯全部完成 â†’ åˆ‡æ¢ Phase 5
| æ­¥éª¤ | æ–¹æ³• | Phase | currentCompletedCount | remainingFake | æ“ä½œ |
|------|------|-------|---------------------|--------------|------|
| 56 | HandlePutFinishedStateAsync | 3 | **48** | 15 - 15 = **0** â†’ â‰¤0 | **切换 Phase=5**, ç­‰å¾…入库 |
---
### ç¬¬16轮:全部放货完成 â†’ å…¥åº“
| æ­¥éª¤ | æ–¹æ³• | Phase | æ“ä½œ |
|------|------|-------|------|
| 57 | HandlePutFinishedAsync | - | PutFinished â†’ allputfinished |
| 58 | ExecuteAsync | - | æ£€æµ‹ allputfinished â†’ RobotSimpleCommandHandler å¤„理 |
| 59 | RobotSimpleCommandHandler | **5** | Phase=5 â†’ è°ƒç”¨ HandleInboundTaskAsync(useSourceAddress=false) å…¥åº“到 **HCSC1** |
| 60 | - | - | åˆ é™¤ä»»åŠ¡è®°å½•ï¼Œé‡ç½®çŠ¶æ€ï¼Œå‘é€ "Group,diskFinished" |
---
## æ•°æ®æµæ±‡æ€»
### currentCompletedCount å˜åŒ–
```
0 â†’ 4 â†’ 8 â†’ 12 â†’ 16 â†’ 20 â†’ 24 â†’ 28 â†’ 32 â†’ 33 â†’ 37 â†’ 41 â†’ 45 â†’ 48
 |    |    |    |    |    |    |    |    |    |    |    |    |    |
 æ­£å¸¸ç”µèŠ¯æ‰¹æ¬¡                  æ­£å¸¸å®Œæˆ   å‡ç”µèŠ¯æ‰¹æ¬¡                      æ€»å®Œæˆ
 (每批4个)                     +0        (每批4个)                    +3
```
### Phase åˆ‡æ¢å›¾
```
Phase=0 â”€â”€(初始化)──► Phase=1 â”€â”€â–º Phase=2 â”€â”€â–º Phase=1 â”€â”€â–º ... â”€â”€â–º Phase=1
                       (Pick 1-4)   (Put 1-4)    (Pick 5-8)         (Pick 33)
                                                    â†“
                                              Phase=2 â”€â”€â–º ... â”€â”€â–º Phase=2
                                              (Put 5-8)              (Put 33)
                                                    â†“
                               â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                               â”‚        Phase=1  (remainingNormal=0)   â”‚
                               â”‚              â†“                      â”‚
                               â”‚        Phase=3 (取假电芯)             â”‚
                               â”‚     Pick fake â†’ Phase=4 â†’ Put fake    â”‚
                               â”‚              â†º                        â”‚
                               â”‚        ç›´åˆ° remainingFake=0          â”‚
                               â”‚              â†“                      â”‚
                               â”‚        Phase=5 (入库)               â”‚
                               â””──────────────────────────────────────┘
```
### å…³é”®æ•°æ®å¯¹ç…§è¡¨
| è½®æ¬¡ | Phase | æ“ä½œ | currentCompletedCount | remainingNormal | remainingFake | å‘送指令 |
|------|-------|------|---------------------|----------------|--------------|---------|
| 1 | 1→2 | Pick 1-4 | 0 | 33 | - | PickBatch(1,4) |
| 1 | 2→1 | Put 1-4 | 4 | 33 | - | PutBatch(1,4) |
| 2 | 1→2 | Pick 5-8 | 4 | 29 | - | PickBatch(5,8) |
| 2 | 2→1 | Put 5-8 | 8 | 29 | - | PutBatch(1,4) |
| 3 | 1→2 | Pick 9-12 | 8 | 25 | - | PickBatch(9,12) |
| 3 | 2→1 | Put 9-12 | 12 | 25 | - | PutBatch(1,4) |
| 4 | 1→2 | Pick 13-16 | 12 | 21 | - | PickBatch(13,16) |
| 4 | 2→1 | Put 13-16 | 16 | 21 | - | PutBatch(1,4) |
| 5 | 1→2 | Pick 17-20 | 16 | 17 | - | PickBatch(17,20) |
| 5 | 2→1 | Put 17-20 | 20 | 17 | - | PutBatch(1,4) |
| 6 | 1→2 | Pick 21-24 | 20 | 13 | - | PickBatch(21,24) |
| 6 | 2→1 | Put 21-24 | 24 | 13 | - | PutBatch(1,4) |
| 7 | 1→2 | Pick 25-28 | 24 | 9 | - | PickBatch(25,28) |
| 7 | 2→1 | Put 25-28 | 28 | 9 | - | PutBatch(1,4) |
| 8 | 1→2 | Pick 29-32 | 28 | 5 | - | PickBatch(29,32) |
| 8 | 2→1 | Put 29-32 | 32 | 5 | - | PutBatch(1,4) |
| 9 | 1→2 | Pick 33 | 32 | 1 | - | PickBatch(33,33) |
| 9 | 2→1 | Put 33 | 33 | 0 | - | PutBatch(33,33) |
| 10 | 1→**3** | **切换** | 33 | **0** | 15 | Pick fake(5号位 1-4) |
| 11 | 3→4 | Pick fake 1-4 | 33 | 0 | 15 | PickBattery,5,1-4 |
| 11 | 4→3 | Put fake 34-37 | 37 | 0 | 11 | PutBatch(34,37) |
| 12 | 3→4 | Pick fake 5-8 | 37 | 0 | 11 | PickBattery,5,5-8 |
| 12 | 4→3 | Put fake 38-41 | 41 | 0 | 7 | PutBatch(38,41) |
| 13 | 3→4 | Pick fake 9-12 | 41 | 0 | 7 | PickBattery,5,9-12 |
| 13 | 4→3 | Put fake 42-45 | 45 | 0 | 3 | PutBatch(42,45) |
| 14 | 3→4 | Pick fake 13-15 | 45 | 0 | 3 | PickBattery,5,13-15 |
| 14 | 4→3 | Put fake 46-48 | 48 | 0 | 0 | PutBatch(46,48) |
| 15 | 3→**5** | **切换** | 48 | 0 | **0** | ç­‰å¾…入库 |
| 16 | 5 | **入库** | - | - | - | allputfinished â†’ HandleInboundTaskAsync â†’ HCSC1 |
---
## ä»£ç èŒè´£å¯¹ç…§
| æ–¹æ³• | ä½•时调用 | èŒè´£ |
|------|---------|------|
| HandlePutFinishedStateAsync | `ExecuteAsync` æ£€æµ‹ PutFinished | å‘送 **Pick** æŒ‡ä»¤ï¼Œæ›´æ–° Phase |
| HandlePickFinishedStateAsync | `ExecuteAsync` æ£€æµ‹ PickFinished | å‘送 **Put** æŒ‡ä»¤ |
| HandlePickFinishedAsync | prefix command `pickfinished` | æ›´æ–°çŠ¶æ€ CurrentAction |
| HandlePutFinishedAsync | prefix command `putfinished` | æ›´æ–° currentCompletedCount è®¡æ•° |
| RobotSimpleCommandHandler | å‘½ä»¤ `allpickfinished` / `allputfinished` | è§¦å‘入库,清理状态 |
Code/WMS/WIDESEA_WMSClient/public/webconfig.js
@@ -1,4 +1,4 @@
window.webConfig = {
    "webApiBaseUrl": "http://localhost:9291/",
    "webApiProduction":"http://localhost:9291/"
    "webApiProduction":"http://192.168.60.30:9291/"
}
Code/WMS/WIDESEA_WMSClient/src/api/http.js
@@ -15,7 +15,7 @@
    axios.defaults.baseURL = window.webConfig.webApiBaseUrl;
}
else if (process.env.NODE_ENV == 'debug') {
    axios.defaults.baseURL = 'http://127.0.0.1:8098/';
    axios.defaults.baseURL = window.webConfig.webApiBaseUrl;
}
else if (process.env.NODE_ENV == 'production') {
Code/WMS/WIDESEA_WMSClient/src/extension/basic/extend/GetLocationStatus.vue
@@ -200,6 +200,7 @@
    open(row) {
      this.row = row;
      this.showDetialBox = true;
      this.tableData = [];
      this.getDetailData();
      this.getDictionaryData();
    },
Code/WMS/WIDESEA_WMSClient/src/views/Home.vue
@@ -104,6 +104,7 @@
    async loadOverview() {
      try {
        const res = await this.http.get("/api/Dashboard/Overview");
        console.log("总览数据", res.Data);
        if (res.Status && res.Data) {
          this.overviewData = res.Data;
          this.updateTodayChart();
Code/WMS/WIDESEA_WMSClient/src/views/basic/locationInfo.vue
@@ -81,7 +81,7 @@
        field: "locationCode",
        title: "货位编号",
        type: "string",
        width: 200,
        width: 120,
        align: "left",
      },
      {
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockChat.vue
@@ -1,12 +1,8 @@
<template>
    <div class="stock-chat-container">
        <el-tabs v-model="activeWarehouse" @tab-change="onWarehouseChange">
            <el-tab-pane
                v-for="wh in warehouseList"
                :key="wh.warehouseId || wh.id"
                :label="wh.warehouseName"
                :name="wh.warehouseId || wh.id"
            />
            <el-tab-pane v-for="wh in warehouseList" :key="wh.warehouseId || wh.id" :label="wh.warehouseName"
                :name="wh.warehouseId || wh.id" />
        </el-tabs>
        <div class="toolbar">
@@ -57,11 +53,15 @@
            <div v-if="selectedLocation" class="detail-content">
                <el-descriptions :column="2" border>
                    <el-descriptions-item label="货位编号">{{ selectedLocation.locationCode }}</el-descriptions-item>
                    <el-descriptions-item label="货位状态">{{ getLocationStatusText(selectedLocation.locationStatus) }}</el-descriptions-item>
                    <el-descriptions-item label="货位状态">{{ getLocationStatusText(selectedLocation.locationStatus)
                        }}</el-descriptions-item>
                    <el-descriptions-item label="托盘编号">{{ selectedLocation.palletCode || '无' }}</el-descriptions-item>
                    <el-descriptions-item label="库存状态">{{ getStockStatusText(selectedLocation.stockStatus) }}</el-descriptions-item>
                    <el-descriptions-item label="总库存">{{ selectedLocation.stockQuantity }}{{ selectedLocation.unit || '' }}</el-descriptions-item>
                    <el-descriptions-item label="出库日期">{{ selectedLocation.outboundDate }}{{ selectedLocation.unit || '' }}</el-descriptions-item>
                    <el-descriptions-item label="库存状态">{{ getStockStatusText(selectedLocation.stockStatus)
                        }}</el-descriptions-item>
                    <el-descriptions-item label="总库存">{{ selectedLocation.stockQuantity }}{{ selectedLocation.unit || ''
                        }}</el-descriptions-item>
                    <el-descriptions-item label="出库日期">{{ selectedLocation.outboundDate }}{{ selectedLocation.unit || ''
                        }}</el-descriptions-item>
                </el-descriptions>
                <div v-if="selectedLocation.details && selectedLocation.details.length > 0" class="detail-table">
@@ -766,6 +766,16 @@
async function loadWarehouseData(warehouseId) {
    try {
        // æ¸…除高亮框
        if (selectionOutline) {
            selectionOutline.visible = false
        }
        selectedLocation.value = null
        // æ¸…空筛选条件
        filterStockStatus.value = null
        filterMaterielCode.value = null
        filterBatchNo.value = null
        const res = await proxy.http.get(`/api/StockInfo/Get3DLayout?warehouseId=${warehouseId}`)
        if (res.status && res.data) {
            const data = res.data
@@ -1139,9 +1149,25 @@
}
async function onWarehouseChange(warehouseId) {
    // æ¸…除选中的高亮框
    if (selectionOutline) {
        selectionOutline.visible = false
    }
    // æ¸…除选中的货位数据
    selectedLocation.value = null
    // æ¸…空所有筛选条件
    filterStockStatus.value = null
    filterMaterielCode.value = null
    filterBatchNo.value = null
    // æ¸…空筛选选项列表
    materielCodeList.value = []
    batchNoList.value = []
    // åŠ è½½æ–°ä»“åº“æ•°æ®
    await loadWarehouseData(warehouseId)
}
function onWindowResize() {
    if (!canvasContainer.value || !camera || !renderer) {
        return
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task.vue
@@ -110,14 +110,14 @@
        field: "sourceAddress",
        title: "起始地址",
        type: "int",
        width: 220,
        width: 120,
        align: "left",
      },
      {
        field: "targetAddress",
        title: "目标地址",
        type: "string",
        width: 220,
        width: 120,
        align: "left",
      },
      {
@@ -141,13 +141,13 @@
        width: 90,
        align: "left",
      },
      {
        field: "grade",
        title: "优先级",
        type: "int",
        width: 80,
        align: "left",
      },
      // {
      //   field: "grade",
      //   title: "优先级",
      //   type: "int",
      //   width: 80,
      //   align: "left",
      // },
      {
        field: "depth",
        title: "深度",
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue
@@ -110,14 +110,14 @@
        field: "sourceAddress",
        title: "起始地址",
        type: "int",
        width: 220,
        width: 120,
        align: "left",
      },
      {
        field: "targetAddress",
        title: "目标地址",
        type: "string",
        width: 220,
        width: 120,
        align: "left",
      },
      // {
@@ -142,13 +142,13 @@
        align: "left",
        hidden: true,
      },
      {
        field: "grade",
        title: "优先级",
        type: "int",
        width: 80,
        align: "left",
      },
      // {
      //   field: "grade",
      //   title: "优先级",
      //   type: "int",
      //   width: 80,
      //   align: "left",
      // },
      {
        field: "depth",
        title: "深度",
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/MesService.cs
@@ -43,10 +43,30 @@
            };
        }
        private HttpRequestConfig BuildConfig(string token)
        {
            return new HttpRequestConfig
            {
                Headers = new Dictionary<string, string>
                {
                    { "Authorization", token }
                },
                TimeoutMs = 30000,
                MaxRetryCount = 0,
                EnableLogging = true
            };
        }
        private HttpResponseResult<MesResponse> Post<T>(string url, T request)
        {
            string json = JsonConvert.SerializeObject(request);
            return _httpClient.Post<MesResponse>(url, json, "application/json", BuildConfig());
        }
        private HttpResponseResult<MesResponse> Post<T>(string url, T request, HttpRequestConfig config)
        {
            string json = JsonConvert.SerializeObject(request);
            return _httpClient.Post<MesResponse>(url, json, "application/json", config);
        }
        public HttpResponseResult<MesResponse> BindContainer(BindContainerRequest request)
@@ -57,6 +77,16 @@
        public HttpResponseResult<MesResponse> UnBindContainer(UnBindContainerRequest request)
        {
            return Post(_baseUrl + UnBindContainerPath, request);
        }
        public HttpResponseResult<MesResponse> BindContainer(BindContainerRequest request, string token)
        {
            return Post(_baseUrl + BindContainerPath, request, BuildConfig(token));
        }
        public HttpResponseResult<MesResponse> UnBindContainer(UnBindContainerRequest request, string token)
        {
            return Post(_baseUrl + UnBindContainerPath, request, BuildConfig(token));
        }
        public HttpResponseResult<MesResponse> ContainerNgReport(ContainerNgReportRequest request)
@@ -73,5 +103,15 @@
        {
            return Post(_baseUrl + OutboundInContainerPath, request);
        }
        public HttpResponseResult<MesResponse> InboundInContainer(InboundInContainerRequest request, string token)
        {
            return Post(_baseUrl + InboundInContainerPath, request, BuildConfig(token));
        }
        public HttpResponseResult<MesResponse> OutboundInContainer(OutboundInContainerRequest request, string token)
        {
            return Post(_baseUrl + OutboundInContainerPath, request, BuildConfig(token));
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs
@@ -9,5 +9,10 @@
        /// ç›®æ ‡æ‰˜ç›˜å·
        /// </summary>
        public string PalletCode { get; set; }
        /// <summary>
        /// è®¾å¤‡åç§°ï¼ˆç”¨äºŽåŠ¨æ€MES凭证查询)
        /// </summary>
        public string DeviceName { get; set; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs
@@ -9,5 +9,10 @@
        /// æºæ‰˜ç›˜å·
        /// </summary>
        public string PalletCode { get; set; }
        /// <summary>
        /// è®¾å¤‡åç§°ï¼ˆç”¨äºŽåŠ¨æ€MES凭证查询)
        /// </summary>
        public string DeviceName { get; set; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMESDeviceConfigService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
using WIDESEA_Core;
using WIDESEA_Model.Models;
namespace WIDESEA_IBasicService
{
    /// <summary>
    /// MES设备配置信息服务接口
    /// </summary>
    public interface IMESDeviceConfigService : IDependency
    {
        /// <summary>
        /// æ ¹æ®è®¾å¤‡åç§°èŽ·å–MES设备配置(精确匹配)
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <returns>MES设备配置,如果未找到则返回null</returns>
        Dt_MESDeviceConfig? GetByDeviceName(string deviceName);
        /// <summary>
        /// æ ¹æ®è®¾å¤‡åç§°å’Œä»“库编码获取MES设备配置
        /// åŒ¹é…æ¡ä»¶ï¼šDeviceName精确匹配且(WarehouseCode为空或等于指定的仓库编码)
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <param name="warehouseCode">仓库编码</param>
        /// <returns>MES设备配置,如果未找到则返回null</returns>
        Dt_MESDeviceConfig? GetByDeviceNameAndWarehouse(string deviceName, string warehouseCode);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesService.cs
@@ -20,6 +20,16 @@
        HttpResponseResult<MesResponse> UnBindContainer(UnBindContainerRequest request);
        /// <summary>
        /// æ‰˜ç›˜ç”µèŠ¯ç»‘å®šï¼ˆæ”¯æŒåŠ¨æ€Token)
        /// </summary>
        HttpResponseResult<MesResponse> BindContainer(BindContainerRequest request, string token);
        /// <summary>
        /// æ‰˜ç›˜ç”µèŠ¯è§£ç»‘ï¼ˆæ”¯æŒåŠ¨æ€Token)
        /// </summary>
        HttpResponseResult<MesResponse> UnBindContainer(UnBindContainerRequest request, string token);
        /// <summary>
        /// æ‰˜ç›˜NG电芯上报
        /// </summary>
        HttpResponseResult<MesResponse> ContainerNgReport(ContainerNgReportRequest request);
@@ -30,8 +40,18 @@
        HttpResponseResult<MesResponse> InboundInContainer(InboundInContainerRequest request);
        /// <summary>
        /// æ‰˜ç›˜è¿›ç«™ï¼ˆæ”¯æŒåŠ¨æ€Token)
        /// </summary>
        HttpResponseResult<MesResponse> InboundInContainer(InboundInContainerRequest request, string token);
        /// <summary>
        /// æ‰˜ç›˜å‡ºç«™
        /// </summary>
        HttpResponseResult<MesResponse> OutboundInContainer(OutboundInContainerRequest request);
        /// <summary>
        /// æ‰˜ç›˜å‡ºç«™ï¼ˆæ”¯æŒåŠ¨æ€Token)
        /// </summary>
        HttpResponseResult<MesResponse> OutboundInContainer(OutboundInContainerRequest request, string token);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs
@@ -1,4 +1,5 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Stock;
using WIDESEA_Model.Models;
@@ -55,6 +56,13 @@
        /// </summary>
        /// <param name="warehouseId">仓库ID</param>
        /// <returns>3D布局DTO</returns>
        Task<Stock3DLayoutDTO> Get3DLayoutAsync(int warehouseId);
        Task<Stock3DLayoutDTO> Get3DLayoutAsync(int warehouseId);
        /// <summary>
        /// ä½¿ç”¨äº‹åŠ¡åˆ é™¤åº“å­˜å’Œæ˜Žç»†ä¿¡æ¯ï¼ˆå…ˆæŸ¥è¯¢å†åˆ é™¤ï¼‰
        /// </summary>
        /// <param name="stockId">库存ID</param>
        /// <returns>删除结果</returns>
        Task<WebResponseContent> DeleteStockWithDetailsAsync(int stockId);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
@@ -60,14 +60,16 @@
        /// æ‰¹é‡æ‹†ç›˜ç¡®è®¤ - ä¸€æ¬¡æ€§è°ƒç”¨MES解绑整个托盘
        /// </summary>
        /// <param name="palletCode">源托盘号</param>
        /// <param name="deviceName">设备名称(用于动态MES凭证查询)</param>
        /// <returns>操作结果</returns>
        Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode);
        Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode, string deviceName);
        /// <summary>
        /// æ‰¹é‡ç»„盘确认 - ä¸€æ¬¡æ€§è°ƒç”¨MES绑定整个托盘
        /// </summary>
        /// <param name="palletCode">目标托盘号</param>
        /// <param name="deviceName">设备名称(用于动态MES凭证查询)</param>
        /// <returns>操作结果</returns>
        Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode);
        Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName);
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MESDeviceConfig.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
using SqlSugar;
using System;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_Model.Models
{
    /// <summary>
    /// MES设备配置实体
    /// </summary>
    [SugarTable(nameof(Dt_MESDeviceConfig))]
    public class Dt_MESDeviceConfig : BaseEntity
    {
        /// <summary>
        /// ä¸»é”®ID,自增
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键ID")]
        public int Id { get; set; }
        /// <summary>
        /// è®¾å¤‡åç§°ï¼Œå¦‚"注液组盘机械手"
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false, ColumnDescription = "设备名称")]
        public string DeviceName { get; set; }
        /// <summary>
        /// MES设备编码,如"A02-YZHJJS-001"
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false, ColumnDescription = "MES设备编码")]
        public string EquipmentCode { get; set; }
        /// <summary>
        /// MES资源编码,如"ZY25091001"
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = false, ColumnDescription = "MES资源编码")]
        public string ResourceCode { get; set; }
        /// <summary>
        /// MES API JWT令牌
        /// </summary>
        [SugarColumn(Length = 500, IsNullable = false, ColumnDescription = "MES API JWT令牌")]
        public string Token { get; set; }
        /// <summary>
        /// ä»“库编码,用于需要区分仓库的机械手(可为空)
        /// </summary>
        [SugarColumn(Length = 50, IsNullable = true, ColumnDescription = "仓库编码")]
        public string WarehouseCode { get; set; }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Mes/Dt_MESDeviceConfig.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
-- Dt_MESDeviceConfig SQL Script
-- å¯¹åº”实体 Dt_MESDeviceConfig,继承自 BaseEntity(包含 Creater, CreateDate, Modifier, ModifyDate)
DROP TABLE IF EXISTS Dt_MESDeviceConfig;
CREATE TABLE Dt_MESDeviceConfig (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    DeviceName NVARCHAR(50) NOT NULL,
    EquipmentCode NVARCHAR(50) NOT NULL,
    ResourceCode NVARCHAR(50) NOT NULL,
    Token NVARCHAR(500) NOT NULL,
    WarehouseCode NVARCHAR(50) NULL,
    Creater NVARCHAR(50) NOT NULL,
    CreateDate DATETIME NOT NULL,
    Modifier NVARCHAR(50) NULL,
    ModifyDate DATETIME NULL
);
-- INSERT device configuration data
INSERT INTO Dt_MESDeviceConfig (DeviceName, EquipmentCode, ResourceCode, Token, WarehouseCode, Creater, CreateDate)
VALUES
(N'注液组盘机械手', N'A02-YZHJJS-001', N'ZY25091001', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwODkyNjQ4MzYzODUzODI0MCIsIm5hbWUiOiLkuIDms6jlkI7mnLrmorDmiYsiLCJGYWN0b3J5SWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIlNpdGVJZCI6IjQyODc0NTYxNzc4MjUzODI0IiwiQ29kZSI6IkEwMi1ZWkhKSlMtMDAxIiwibmJmIjoxNzc2NDExODk0LCJleHAiOjIyMTc5MTU4OTQsImlzcyI6Imh0dHBzOi8vd3d3Lmh5bXNvbi5jb20iLCJhdWQiOiJodHRwczovL3d3dy5oeW1zb24uY29tIn0.HCBK-mq7zrbn6s335Ddn1ZwUffCXdFAmflgHFtOyOXg', NULL, N'Admin', GETDATE()),
(N'高温1换盘机械手', N'A02-GW1HJJS-001', N'ZY25091002', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwODkyNjUxOTA3NDExNTU4NCIsIm5hbWUiOiLpq5jmuKkx5ZCO5py65qKw5omLIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItR1cxSEpKUy0wMDEiLCJuYmYiOjE3NzY0MTE5MDgsImV4cCI6MjIxNzkxNTkwOCwiaXNzIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSIsImF1ZCI6Imh0dHBzOi8vd3d3Lmh5bXNvbi5jb20ifQ.A8frl5Txy6F3hCM2AuMbrPk_0x-rYmwjvL4a1RbxdeY', NULL, N'Admin', GETDATE()),
(N'化成换盘机械手', N'A02-HCHJJS-001', N'ZY25091003', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwODkyNjU0ODk2NTg3MTYxNiIsIm5hbWUiOiLljJbmiJDlkI7mnLrmorDmiYvvvIjpq5jmuKky77yJIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItSENISkpTLTAwMSIsIm5iZiI6MTc3NjQxMTkxNiwiZXhwIjoyMjE3OTE1OTE2LCJpc3MiOiJodHRwczovL3d3dy5oeW1zb24uLmNvbSIsImF1ZCI6Imh0dHBzOi8vd3d3Lmh5bXNvbi5jb20ifQ.SHxbsdYoV2m4oUkaJauOBu4A-TfuX__J8-W-mqghg_A', NULL, N'Admin', GETDATE()),
(N'常温机械手', N'A02-CW1JJS-001', N'ZY25091004', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwODkyNjU4MDA2ODczMjkyOCIsIm5hbWUiOiLluLjmuKkx5py65qKw5omLIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItQ1cxSkpTLTAwMSIsIm5iZiI6MTc3NjQxMTkyNCwiZXhwIjoyMjE3OTE1OTI0LCJpc3MiOiJodHRwczovL3d3dy5oeW1zb24uY29tIiwiYXVkIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSJ9.CnBUKdmdSGURyW-YzW_JZvtP93o17zG2r5ZxHx867PQ', NULL, N'Admin', GETDATE()),
(N'成品组盘机械手', N'A02-EHJJS-001', N'ZY25091005', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwODkyNjYwOTU1OTkzMjkyOCIsIm5hbWUiOiLkuozmrKHmsKbmo4DlkI7mnLrmorDmiYsiLCJGYWN0b3J5SWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIlNpdGVJZCI6IjQyODc0NTYxNzc4MjUzODI0IiwiQ29kZSI6IkEwMi1FSEpKUy0wMDEiLCJuYmYiOjE3NzY0MTE5MzIsImV4cCI6MjIxNzkxNTkzMiwiaXNzIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSIsImF1ZCI6Imh0dHRwczovL3d3dy5oeW1zb24uY29tIn0.Imc2-8pDFrXgPrE_ro9Riasb8eGJ3Vi6gDyUDnUTpfY', NULL, N'Admin', GETDATE()),
(N'高温静置1', N'A02-GWJZ-001', N'ZY25030001', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwNzU0NjM1MTU4NDY5NDI3MiIsIm5hbWUiOiLpq5jmuKnpnZnnva4xIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItR1dKWi0wMDEiLCJuYmYiOjE3NzUwOTkzMDEsImV4cCI6MjIxNjYwMzMwMSwiaXNzIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSIsImF1ZCI6Imh0dHBzOi8vd3d3Lmh5bXNvbi5jb20ifQ.EI3Q6kHieKk5q4a6Nmf9fozi0haan0WuqkA9QNA-bHk', NULL, N'Admin', GETDATE()),
(N'高温静置2', N'A02-GWJZ-002', N'ZY25060001', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwNzU0NjUxNTA0NTEwNTY2NCIsIm5hbWUiOiLpq5jmuKnpnZnnva4yIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItR1dKWi0wMDIiLCJuYmYiOjE3NzUwOTkzNDAsImV4cCI6MjIxNjYwMzM0MCwiaXNzIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSIsImF1ZCI6Imh0dHBzOi8vd3d3Lmh5bXNvbi5jb20ifQ.imaa_1Xd9bHZKF3cy6c82Lb1ODXJC2MVytks4_eIyR4', NULL, N'Admin', GETDATE()),
(N'常温静置1', N'A02-CWJZ-001', N'ZY25070001', N'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwNzU0NjU1ODU5OTgxMTA3MiIsIm5hbWUiOiLluLjmuKnpnZnnva4xIiwiRmFjdG9yeUlkIjoiNDI4NzQ1NjE3NzgyNTM4MjQiLCJTaXRlSWQiOiI0Mjg3NDU2MTc3ODI1MzgyNCIsIkNvZGUiOiJBMDItQ1dKWi0wMDEiLCJuYmYiOjE3NzUwOTkzNDgsImV4cCI6MjIxNjYwMzM0OCwiaXNzIjoiaHR0cHM6Ly93d3cuaHltc29uLmNvbSIsImF1ZCI6Imh0dHRzOi8vd3d3Lmh5bXNvbi5jb20ifQ.-WmBS4g4T0ZpJZ2qHszZMfe2pseMWCh6zyVeYgzsho4', NULL, N'Admin', GETDATE());
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/MESDeviceConfigService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_StockService
{
    /// <summary>
    /// MES设备配置服务实现类
    /// </summary>
    public class MESDeviceConfigService : ServiceBase<Dt_MESDeviceConfig, IRepository<Dt_MESDeviceConfig>>, IMESDeviceConfigService
    {
        /// <summary>
        /// èŽ·å–MES设备配置仓储接口
        /// </summary>
        public IRepository<Dt_MESDeviceConfig> Repository => BaseDal;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="baseDal">基础数据访问对象</param>
        public MESDeviceConfigService(IRepository<Dt_MESDeviceConfig> baseDal) : base(baseDal)
        {
        }
        /// <summary>
        /// æ ¹æ®è®¾å¤‡åç§°èŽ·å–MES设备配置(精确匹配)
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <returns>MES设备配置,如果未找到则返回null</returns>
        public Dt_MESDeviceConfig? GetByDeviceName(string deviceName)
        {
            if (string.IsNullOrWhiteSpace(deviceName))
            {
                return null;
            }
            return BaseDal.QueryFirst(x => x.DeviceName == deviceName);
        }
        /// <summary>
        /// æ ¹æ®è®¾å¤‡åç§°å’Œä»“库编码获取MES设备配置
        /// åŒ¹é…æ¡ä»¶ï¼šDeviceName精确匹配且(WarehouseCode为空或等于指定的仓库编码)
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <param name="warehouseCode">仓库编码</param>
        /// <returns>MES设备配置,如果未找到则返回null</returns>
        public Dt_MESDeviceConfig? GetByDeviceNameAndWarehouse(string deviceName, string warehouseCode)
        {
            if (string.IsNullOrWhiteSpace(deviceName))
            {
                return null;
            }
            return BaseDal.QueryFirst(x =>
                x.DeviceName == deviceName &&
                (string.IsNullOrEmpty(x.WarehouseCode) || x.WarehouseCode == warehouseCode));
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -30,6 +30,8 @@
        /// </summary>
        private readonly IWarehouseService _warehouseService;
        private readonly IRecordService _recordService;
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        private readonly IStockInfoDetailService _stockInfoDetailService;
        /// <summary>
        /// æž„造函数
@@ -39,11 +41,15 @@
            IRepository<Dt_StockInfo> baseDal,
            ILocationInfoService locationInfoService,
            IWarehouseService warehouseService,
            IRecordService recordService) : base(baseDal)
            IRecordService recordService,
            IUnitOfWorkManage unitOfWorkManage,
            IStockInfoDetailService stockInfoDetailService) : base(baseDal)
        {
            _locationInfoService = locationInfoService;
            _warehouseService = warehouseService;
            _recordService = recordService;
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoDetailService = stockInfoDetailService;
        }
        /// <summary>
@@ -316,5 +322,64 @@
                Locations = locationItems
            };
        }
        /// <summary>
        /// ä½¿ç”¨äº‹åŠ¡åˆ é™¤åº“å­˜å’Œæ˜Žç»†ä¿¡æ¯ï¼ˆå…ˆæŸ¥è¯¢å†åˆ é™¤ï¼‰
        /// </summary>
        /// <param name="stockId">库存ID</param>
        /// <returns>删除结果</returns>
        public async Task<WebResponseContent> DeleteStockWithDetailsAsync(int stockId)
        {
            if (stockId <= 0)
                return WebResponseContent.Instance.Error("库存ID无效");
            _unitOfWorkManage.BeginTran();
            try
            {
                // å…ˆæŸ¥è¯¢åº“存信息(包含明细)
                var stockInfo = await BaseDal.QueryDataNavFirstAsync(x => x.Id == stockId);
                if (stockInfo == null)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error("库存记录不存在");
                }
                // æŸ¥è¯¢å¹¶åˆ é™¤åº“存明细记录
                var existingDetails = await _stockInfoDetailService.Repository.QueryDataAsync(x => x.StockId == stockId);
                if (existingDetails != null && existingDetails.Any())
                {
                    var deleteDetailResult = await _stockInfoDetailService.Repository.DeleteDataAsync(existingDetails);
                    if (!deleteDetailResult)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error("删除库存明细记录失败");
                    }
                }
                // åˆ é™¤åº“存主记录
                var deleteStockResult = await BaseDal.DeleteDataAsync(stockInfo);
                if (!deleteStockResult)
                {
                    _unitOfWorkManage.RollbackTran();
                    return WebResponseContent.Instance.Error("删除库存主记录失败");
                }
                _unitOfWorkManage.CommitTran();
                // è®°å½•库存变更日志
                var saveRecordResult = await _recordService.AddStockChangeRecordAsync(stockInfo, null, StockChangeTypeEnum.Outbound, remark: "库存删除");
                if (!saveRecordResult)
                {
                    return WebResponseContent.Instance.Error("库存变更记录保存失败");
                }
                return WebResponseContent.Instance.OK("库存删除成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"删除库存和明细时发生异常: {ex.Message}");
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -392,8 +392,9 @@
        /// æ‰¹é‡æ‹†ç›˜ç¡®è®¤ - ä¸€æ¬¡æ€§è°ƒç”¨MES解绑整个托盘
        /// </summary>
        /// <param name="palletCode">源托盘号</param>
        /// <param name="deviceName">设备名称(用于动态MES凭证查询)</param>
        /// <returns>操作结果</returns>
        public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode)
        public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode, string deviceName)
        {
            WebResponseContent content = new WebResponseContent();
            try
@@ -412,22 +413,40 @@
                if (sfcList == null || !sfcList.Any())
                    return content.Error("临时表中电芯列表为空");
                // 2. è°ƒç”¨MES解绑
                // 2. èŽ·å–MES设备配置(动态凭证)
                string equipmentCode = StockConstants.MES_EQUIPMENT_CODE;
                string resourceCode = StockConstants.MES_RESOURCE_CODE;
                string token = null;
                if (!string.IsNullOrWhiteSpace(deviceName))
                {
                    var mesConfig = ResolveMesConfig(deviceName, palletCode);
                    if (mesConfig != null)
                    {
                        equipmentCode = mesConfig.EquipmentCode;
                        resourceCode = mesConfig.ResourceCode;
                        token = mesConfig.Token;
                    }
                }
                // 3. è°ƒç”¨MES解绑
                var unbindRequest = new UnBindContainerRequest
                {
                    EquipmentCode = StockConstants.MES_EQUIPMENT_CODE,
                    ResourceCode = StockConstants.MES_RESOURCE_CODE,
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    ContainCode = palletCode,
                    SfcList = sfcList
                };
                var unbindResult = _mesService.UnBindContainer(unbindRequest);
                var unbindResult = string.IsNullOrWhiteSpace(token)
                    ? _mesService.UnBindContainer(unbindRequest)
                    : _mesService.UnBindContainer(unbindRequest, token);
                if (unbindResult == null || unbindResult.Data == null || !unbindResult.Data.IsSuccess)
                {
                    return content.Error($"MES解绑失败: {unbindResult?.Data?.Msg ?? unbindResult?.ErrorMessage ?? "未知错误"}");
                }
                // 3. åˆ é™¤ä¸´æ—¶è¡¨è®°å½•
                // 4. åˆ é™¤ä¸´æ—¶è¡¨è®°å½•
                await SqlSugarClient.Deleteable<Dt_SplitTemp>().Where(t => t.PalletCode == palletCode).ExecuteCommandAsync();
                return content.OK("批量拆盘确认成功");
@@ -442,8 +461,9 @@
        /// æ‰¹é‡ç»„盘确认 - ä¸€æ¬¡æ€§è°ƒç”¨MES绑定整个托盘
        /// </summary>
        /// <param name="palletCode">目标托盘号</param>
        /// <param name="deviceName">设备名称(用于动态MES凭证查询)</param>
        /// <returns>操作结果</returns>
        public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode)
        public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName)
        {
            WebResponseContent content = new WebResponseContent();
            try
@@ -460,12 +480,28 @@
                if (details == null || !details.Any())
                    return content.Error("托盘下无电芯数据");
                // 2. è°ƒç”¨MES绑定
                // 2. èŽ·å–MES设备配置(动态凭证)
                string equipmentCode = StockConstants.MES_EQUIPMENT_CODE;
                string resourceCode = StockConstants.MES_RESOURCE_CODE;
                string token = null;
                if (!string.IsNullOrWhiteSpace(deviceName))
                {
                    var mesConfig = ResolveMesConfig(deviceName, palletCode);
                    if (mesConfig != null)
                    {
                        equipmentCode = mesConfig.EquipmentCode;
                        resourceCode = mesConfig.ResourceCode;
                        token = mesConfig.Token;
                    }
                }
                // 3. è°ƒç”¨MES绑定
                var bindRequest = new BindContainerRequest
                {
                    ContainerCode = palletCode,
                    EquipmentCode = StockConstants.MES_EQUIPMENT_CODE,
                    ResourceCode = StockConstants.MES_RESOURCE_CODE,
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    OperationType = StockConstants.MES_BIND_OPERATION_TYPE,
                    ContainerSfcList = details.Select(d => new ContainerSfcItem
@@ -474,7 +510,9 @@
                        Location = d.InboundOrderRowNo.ToString()
                    }).ToList()
                };
                var bindResult = _mesService.BindContainer(bindRequest);
                var bindResult = string.IsNullOrWhiteSpace(token)
                    ? _mesService.BindContainer(bindRequest)
                    : _mesService.BindContainer(bindRequest, token);
                if (bindResult == null || bindResult.Data == null || !bindResult.Data.IsSuccess)
                {
                    return content.Error($"MES绑定失败: {bindResult?.Data?.Msg ?? bindResult?.ErrorMessage ?? "未知错误"}");
@@ -487,5 +525,39 @@
                return content.Error($"批量组盘确认失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ ¹æ®è®¾å¤‡åç§°å’Œæ‰˜ç›˜å·è§£æžMES设备配置
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <param name="palletCode">托盘号(用于查询仓库编码)</param>
        /// <returns>MES设备配置,不存在时返回null</returns>
        private Dt_MESDeviceConfig ResolveMesConfig(string deviceName, string palletCode)
        {
            // æ¢ç›˜æœºæ¢°æ‰‹éœ€è¦åŒºåˆ†ä»“库
            if (deviceName.Contains("换盘"))
            {
                // æŸ¥è¯¢æ‰˜ç›˜å¯¹åº”的仓库编码
                var stockInfo = StockInfoService.Repository.QueryFirst(s => s.PalletCode == palletCode);
                if (stockInfo != null && stockInfo.WarehouseId > 0)
                {
                    var warehouse = _warehouseService.Repository.QureyDataById(stockInfo.WarehouseId);
                    if (warehouse != null && !string.IsNullOrWhiteSpace(warehouse.WarehouseCode))
                    {
                        // å…ˆæŒ‰è®¾å¤‡å+仓库编码查询
                        var config = SqlSugarClient.Queryable<Dt_MESDeviceConfig>()
                            .Where(c => c.DeviceName == deviceName && c.WarehouseCode == warehouse.WarehouseCode)
                            .First();
                        if (config != null)
                            return config;
                    }
                }
            }
            // æŒ‰è®¾å¤‡åç§°æŸ¥è¯¢ï¼ˆé€‚用于组盘机械手或换盘机械手未找到仓库匹配的情况)
            return SqlSugarClient.Queryable<Dt_MESDeviceConfig>()
                .Where(c => c.DeviceName == deviceName)
                .First();
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -40,6 +40,7 @@
        private readonly IStockInfo_HtyService _stockInfo_HtyService;
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        private readonly IRecordService _recordService;
        private readonly IMESDeviceConfigService _mesDeviceConfigService;
        public IRepository<Dt_Task> Repository => BaseDal;
@@ -64,7 +65,8 @@
            ITask_HtyService task_HtyService,
            IStockInfo_HtyService stockInfo_HtyService,
            IUnitOfWorkManage unitOfWorkManage,
            IRecordService recordService) : base(BaseDal)
            IRecordService recordService,
            IMESDeviceConfigService mesDeviceConfigService) : base(BaseDal)
        {
            _mapper = mapper;
            _stockInfoService = stockInfoService;
@@ -77,6 +79,7 @@
            _stockInfo_HtyService = stockInfo_HtyService;
            _unitOfWorkManage = unitOfWorkManage;
            _recordService = recordService;
            _mesDeviceConfigService = mesDeviceConfigService;
        }
        /// <summary>
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_AGV.cs
@@ -1,5 +1,6 @@
using Mapster;
using System.Text.Json;
using WIDESEA_Common.Constants;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
@@ -8,6 +9,7 @@
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Task;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_TaskInfoService
@@ -168,7 +170,7 @@
                    return response.OK();
                if (task.TaskStatus == (int)TaskInStatusEnum.InNew)
                    return CancelAgvInboundTask(task);
                    return await CancelAgvInboundTask(task);
                if (task.TaskStatus == (int)TaskOutStatusEnum.OutNew)
                    return await CancelAgvOutboundTaskAsync(task);
@@ -300,35 +302,117 @@
                return response.Error($"当前托盘{dto.TrayNumber}已经入库了");
            // åˆ›å»ºåº“存明细
            var details = new Dt_StockInfoDetail
            {
                MaterielCode = dto.ProductNo,
                MaterielName = dto.ProductName,
                StockQuantity = int.TryParse(dto.Quantity, out int quantity) ? quantity : 0,
                Unit = dto.UomCode,
                OrderNo = dto.ProductNo,
                ProductionDate =dto.ProductionDate,
                EffectiveDate = dto.LowerLimitTime,
                SerialNumber = dto.TrayNumber,
                Status = (int)StockStatusEmun.入库确认,
                InboundOrderRowNo = 1,
                Creater = StockConstants.AGV_USER,
                CreateDate = DateTime.Now,
                Remark = $"AGV入库任务创建,任务号:{dto.TaskId}"
            };
            // åˆ›å»ºåº“存主记录
            var stock = new Dt_StockInfo
            {
                PalletCode = dto.TrayNumber,
                PalletType = dto.Floor,
                WarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1,
                StockStatus = (int)StockStatusEmun.入库确认,
                Creater = StockConstants.AGV_USER,
                CreateDate = DateTime.Now,
                Remark = $"AGV入库任务创建,任务号:{dto.TaskId}",
                Details = new List<Dt_StockInfoDetail> { details }
            };
            task.TaskType = (int)TaskInboundTypeEnum.Inbound;
            task.TaskStatus = (int)TaskInStatusEnum.InNew;
            task.CurrentAddress = task.SourceAddress;
            var addResult = await BaseDal.AddDataAsync(task) > 0;
            return addResult ? null : response.Error("入库任务创建失败");
            _unitOfWorkManage.BeginTran();
            try
            {
                // å…ˆåˆ›å»ºä»»åŠ¡
                var taskResult = await BaseDal.AddDataAsync(task) > 0;
                if (!taskResult)
                {
                    _unitOfWorkManage.RollbackTran();
                    return response.Error("入库任务创建失败");
                }
                var result = _stockInfoService.Repository.AddData(stock, x => x.Details);
                if (result)
                {
                    _unitOfWorkManage.CommitTran();
                    return null;
                }
                else
                {
                    _unitOfWorkManage.RollbackTran();
                    return response.Error("库存信息创建失败");
                }
                // ä½¿ç”¨åº“存服务添加库存主记录和明细
                //var stockResult = await _stockInfoService.AddStockWithDetailsUsingTransactionAsync(stock);
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return response.Error($"入库任务创建异常:{ex.Message}");
            }
        }
        // å‡ºåº“创建
        private async Task<AGVResponse?> CreateAgvOutboundTaskAsync(Dt_Task task, ApplyInOutDto dto)
        {
            AGVResponse response = new AGVResponse();
            // æ£€æŸ¥åº“存是否存在
            var stockInfo = await _stockInfoService.GetStockInfoAsync(dto.TrayNumber);
            if (stockInfo == null)
                return response.Error($"未找到托盘{dto.TrayNumber}的库存信息");
            //if (stockInfo.WarehouseId != dto.YinYang)
            //    return response.Error($"托盘{dto.TrayNumber}不属于当前{(dto.YinYang == 1 ? "阴极" : "阳极")}");
            // æ£€æŸ¥åº“存是否有明细(即是否真的有库存)
            if (stockInfo.Details == null || !stockInfo.Details.Any())
                return response.Error($"托盘{dto.TrayNumber}没有库存明细,无法出库");
            // æ£€æŸ¥åº“存总数量是否大于0
            var totalQuantity = stockInfo.Details.Sum(d => d.StockQuantity);
            if (totalQuantity <= 0)
                return response.Error($"托盘{dto.TrayNumber}库存数量不足,无法出库");
            // æ ¹æ®dto参数进一步验证库存信息
            if (!string.IsNullOrEmpty(dto.ProductNo))
            {
                // æ£€æŸ¥åº“存明细中是否包含指定的物料编码
                var hasMatchingMaterial = stockInfo.Details.Any(d => d.MaterielCode == dto.ProductNo);
                if (!hasMatchingMaterial)
                    return response.Error($"托盘{dto.TrayNumber}中没有物料编码为{dto.ProductNo}的库存,无法出库");
            }
            // æ£€æŸ¥åº“存状态是否允许出库
            if (stockInfo.StockStatus != (int)StockStatusEmun.入库完成)
                return response.Error($"托盘{dto.TrayNumber}正在移动中,请稍后!");
            // æ£€æŸ¥è´§ä½ä¿¡æ¯
            var locationInfo = await _locationInfoService.GetLocationInfo(stockInfo.LocationCode);
            if (locationInfo == null)
                return response.Error($"未找到托盘{stockInfo.LocationCode}的货位信息");
            // æ£€æŸ¥è´§ä½çŠ¶æ€æ˜¯å¦å…è®¸å‡ºåº“
            if (locationInfo.LocationStatus != (int)LocationStatusEnum.InStock)
                return response.Error($"当前货位{locationInfo.LocationStatus}状态信息错误");
            // éªŒè¯ä»“库ID是否匹配(根据dto的阴阳极参数)
            var expectedWarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1;
            if (stockInfo.WarehouseId != expectedWarehouseId)
                return response.Error($"托盘{dto.TrayNumber}不在预期的仓库中,无法出库");
            task.TaskType = (int)TaskOutboundTypeEnum.Outbound;
            task.TaskStatus = (int)TaskOutStatusEnum.OutNew;
@@ -454,7 +538,8 @@
            task.TaskStatus = (int)TaskOutStatusEnum.OutFinish;
            _unitOfWorkManage.BeginTran();
            var deleteStockResult = _stockInfoService.DeleteData(stockInfo);
            //var deleteStockResult = _stockInfoService.DeleteData(stockInfo)
            var deleteStockResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id);
            var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
            BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
            if (!deleteStockResult.Status || !updateLocationResult.Status)
@@ -499,23 +584,23 @@
            if (existingStock != null)
                return response.Error($"托盘{task.PalletCode}的库存信息已存在,请勿重复入库");
            Dt_StockInfo stockInfo = new Dt_StockInfo
            {
                PalletCode = task.PalletCode,
                StockStatus = (int)StockStatusEmun.入库确认,
                LocationCode = locationInfo.LocationCode,
                WarehouseId = task.WarehouseId,
                Creater = "AGV",
                CreateDate = DateTime.Now
            };
            //Dt_StockInfo stockInfo = new Dt_StockInfo
            //{
            //    PalletCode = task.PalletCode,
            //    StockStatus = (int)StockStatusEmun.入库确认,
            //    LocationCode = locationInfo.LocationCode,
            //    WarehouseId = task.WarehouseId,
            //    Creater = "AGV",
            //    CreateDate = DateTime.Now
            //};
            locationInfo.LocationStatus = (int)LocationStatusEnum.FreeLock;
            _unitOfWorkManage.BeginTran();
            var updateTaskResult = BaseDal.UpdateData(task);
            var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
            var addStockResult = _stockInfoService.AddData(stockInfo);
            if (!updateTaskResult || !updateLocationResult.Status || !addStockResult.Status)
            //var addStockResult = _stockInfoService.AddData(stockInfo);
            if (!updateTaskResult || !updateLocationResult.Status /*|| !addStockResult.Status*/)
            {
                _unitOfWorkManage.RollbackTran();
                return response.Error("入库完成后,本地任务、库存或货位更新失败");
@@ -526,15 +611,35 @@
        }
        // AGV入库取消
        private AGVResponse CancelAgvInboundTask(Dt_Task task)
        private async Task<AGVResponse> CancelAgvInboundTask(Dt_Task task)
        {
            AGVResponse response = new AGVResponse();
            task.TaskStatus = (int)TaskInStatusEnum.InCancel;
            _unitOfWorkManage.BeginTran();
            BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
            _unitOfWorkManage.CommitTran();
            return response.OK();
            try
            {
                var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
                if (stockInfo != null)
                {
                    var deleteResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id);
                    if (!deleteResult.Status)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return response.Error($"删除库存失败: {deleteResult.Message}");
                    }
                }
                BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
                _unitOfWorkManage.CommitTran();
                return response.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return response.Error($"取消入库任务时发生异常: {ex.Message}");
            }
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -6,6 +6,7 @@
using WIDESEA_Core;
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Task;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_TaskInfoService
@@ -150,15 +151,26 @@
                    var updateStockResult = await _stockInfoService.UpdateStockAsync(stockInfo);
                    if (!updateLocationResult || !updateStockResult)
                        return WebResponseContent.Instance.Error("任务完成失败");
                    // æ ¹æ®åº“å­˜Remark选择静置设备,查MES动态凭证
                    string deviceName = stockInfo.Remark == "GW_1" ? "高温静置1"
                        : stockInfo.Remark == "GW_2" ? "高温静置2"
                        : "常温静置1";
                    var mesConfig = _mesDeviceConfigService.GetByDeviceName(deviceName);
                    string equipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE;
                    string resourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE;
                    string token = mesConfig?.Token;
                    // è°ƒç”¨MES托盘进站
                    var inboundRequest = new InboundInContainerRequest
                    {
                        EquipmentCode = "STK-GROUP-001",
                        ResourceCode = "STK-GROUP-001",
                        EquipmentCode = equipmentCode,
                        ResourceCode = resourceCode,
                        LocalTime = DateTime.Now,
                        ContainerCode = taskDto.PalletCode
                    };
                    var inboundResult = _mesService.InboundInContainer(inboundRequest);
                    var inboundResult = string.IsNullOrWhiteSpace(token)
                        ? _mesService.InboundInContainer(inboundRequest)
                        : _mesService.InboundInContainer(inboundRequest, token);
                    if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                    {
                        return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs
@@ -4,7 +4,9 @@
using WIDESEA_Common.TaskEnum;
using WIDESEA_Common.WareHouseEnum;
using WIDESEA_Core;
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Task;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_TaskInfoService
@@ -117,6 +119,30 @@
                        inboundTaskDto = _mapper.Map<WMSTaskDTO>(inboundTask);
                    }
                    // è°ƒç”¨MES托盘出站
                    string deviceName = stockInfo.Remark == "GW_1" ? "高温静置1"
                        : stockInfo.Remark == "GW_2" ? "高温静置2"
                        : "常温静置1";
                    var mesConfig = _mesDeviceConfigService.GetByDeviceName(deviceName);
                    string equipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE;
                    string resourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE;
                    string token = mesConfig?.Token;
                    var outboundRequest = new OutboundInContainerRequest
                    {
                        EquipmentCode = equipmentCode,
                        ResourceCode = resourceCode,
                        LocalTime = DateTime.Now,
                        ContainerCode = taskDto.PalletCode
                    };
                    var outboundResult = string.IsNullOrWhiteSpace(token)
                        ? _mesService.OutboundInContainer(outboundRequest)
                        : _mesService.OutboundInContainer(outboundRequest, token);
                    if (outboundResult == null || outboundResult.Data == null || !outboundResult.Data.IsSuccess)
                    {
                        return content.Error($"出库完成失败:MES出站失败: {outboundResult?.Data?.Msg ?? outboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    var completeResult = await CompleteTaskAsync(task, "出库完成");
                    if (!completeResult.Status)
                        return completeResult;
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Robot.cs
@@ -129,9 +129,9 @@
                    Creater = "system"
                };
                var result = await Repository.AddDataAsync(task) > 0;
                if (!result)
                    return WebResponseContent.Instance.Error($"机械手{taskName}任务创建失败");
                //var result = await Repository.AddDataAsync(task) > 0;
                //if (!result)
                //    return WebResponseContent.Instance.Error($"机械手{taskName}任务创建失败");
                var wmstaskDto = _mapper.Map<WMSTaskDTO>(task) ?? new WMSTaskDTO();
                wmstaskDto.TaskQuantity = stock.Details?.Sum(d => d.Quantity) ?? 0;
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Dashboard/DashboardController.cs
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using WIDESEA_Core;
@@ -22,7 +23,7 @@
        /// <summary>
        /// æ€»è§ˆæ•°æ®
        /// </summary>
        [HttpGet("Overview")]
        [HttpGet("Overview"), AllowAnonymous]
        public async Task<WebResponseContent> Overview()
        {
            try
@@ -76,7 +77,7 @@
        /// SqlSugar çš„ GroupBy ä¸æ”¯æŒå¯¹ .Date è¿™æ ·çš„计算列直接生成 SQL GROUP BY,
        /// å› æ­¤é‡‡ç”¨æ­¤æ–¹å¼ä»¥ç¡®ä¿è·¨æ•°æ®åº“兼容性。
        /// </remarks>
        [HttpGet("DailyStats")]
        [HttpGet("DailyStats"), AllowAnonymous]
        public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30)
        {
            try
@@ -117,7 +118,7 @@
        /// æ³¨æ„ï¼šæ•°æ®åœ¨ SQL å±‚过滤后,在应用层按 ISO 8601 å‘¨é”®åˆ†ç»„。
        /// å‘¨é”®ä¸º "YYYY-Www" æ ¼å¼ï¼Œæ— æ³•直接在 SQL å±‚用 GROUP BY å®žçŽ°ã€‚
        /// </remarks>
        [HttpGet("WeeklyStats")]
        [HttpGet("WeeklyStats"), AllowAnonymous]
        public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12)
        {
            try
@@ -168,7 +169,7 @@
        /// SqlSugar çš„ GroupBy ä¸æ”¯æŒåŒ¿åå¯¹è±¡ (Year, Month) ç›´æŽ¥æ˜ å°„到 SQL GROUP BY,
        /// å› æ­¤é‡‡ç”¨æ­¤æ–¹å¼ä»¥ç¡®ä¿è·¨æ•°æ®åº“兼容性。
        /// </remarks>
        [HttpGet("MonthlyStats")]
        [HttpGet("MonthlyStats"), AllowAnonymous]
        public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12)
        {
            try
@@ -205,7 +206,7 @@
        /// <summary>
        /// åº“存库龄分布
        /// </summary>
        [HttpGet("StockAgeDistribution")]
        [HttpGet("StockAgeDistribution"), AllowAnonymous]
        public async Task<WebResponseContent> StockAgeDistribution()
        {
            try
@@ -235,7 +236,7 @@
        /// <remarks>
        /// ä½¿ç”¨ SQL GROUP BY åœ¨æ•°æ®åº“层面聚合,避免加载全部库存记录到内存。
        /// </remarks>
        [HttpGet("StockByWarehouse")]
        [HttpGet("StockByWarehouse"), AllowAnonymous]
        public async Task<WebResponseContent> StockByWarehouse()
        {
            try
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Record/LocationStatusChangeRecordController.cs
@@ -17,7 +17,30 @@
    {
        public LocationStatusChangeRecordController(ILocationStatusChangeRecordService service) : base(service)
        {
        }
        /// <summary>
        /// æ ¹æ®ID获取货位状态变动记录
        /// </summary>
        /// <param name="id">货位状态变动记录ID</param>
        /// <returns>货位状态变动记录信息</returns>
        [HttpPost("GetLocationState"), AllowAnonymous]
        public WebResponseContent GetLocationState(int id)
        {
            try
            {
                if (id <= 0)
                    return WebResponseContent.Instance.Error("ID参数无效");
                var records = Service.Repository.QueryData(x => x.LocationId == id);
                if (records == null || records.Count == 0)
                    return WebResponseContent.Instance.Error("未找到对应的货位状态变动记录");
                return WebResponseContent.Instance.OK(null, records);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"获取货位状态变动记录失败: {ex.Message}");
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
@@ -72,7 +72,7 @@
        [HttpPost("SplitPalletConfirm"), AllowAnonymous]
        public async Task<WebResponseContent> SplitPalletConfirm([FromBody] SplitPalletConfirmRequestDto dto)
        {
            return await Service.SplitPalletConfirmAsync(dto.PalletCode);
            return await Service.SplitPalletConfirmAsync(dto.PalletCode, dto.DeviceName);
        }
        /// <summary>
@@ -83,7 +83,7 @@
        [HttpPost("GroupPalletConfirm"), AllowAnonymous]
        public async Task<WebResponseContent> GroupPalletConfirm([FromBody] GroupPalletConfirmRequestDto dto)
        {
            return await Service.GroupPalletConfirmAsync(dto.PalletCode);
            return await Service.GroupPalletConfirmAsync(dto.PalletCode, dto.DeviceName);
        }
    }
}
ÏîÄ¿×ÊÁÏ/¼«¾í¿âAGV½Ó¿Ú/~$¼«¾í¿â³öÈë¿â.docx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/ÉÏλϵͳ¶Ô½Ó/¸ßÎÂ2³£ÎÂ1¼°»úеÊÖÉ豸Õ˺ÅÐÅÏ¢±í(1).xlsx
Binary files differ