9a1c82a7b77dd27d9bd9b71bbd2909e60aed6c10..fac6be89f3a02270b7b4a1bbe1f0dc05788c368e
3 天以前 wanshenmean
Merge branch 'dev' of http://115.159.85.185:8098/r/SuZhouGuanHong/ShanMeiXi...
fac6be 对比 | 目录
3 天以前 wanshenmean
refactor: 统一日志组件为Serilog并优化相关功能
75ef0d 对比 | 目录
3 天以前 xiazhengtongxue
feat: 添加AGV任务管理和托盘组功能 feat(task): 新增AGV任务页面和路由配置 feat(stock): 添加托盘组管理页面及进站出...
4f40a6 对比 | 目录
3 天以前 wanshenmean
feat: 添加MES异步上传辅助服务并重构相关代码
96adc2 对比 | 目录
3 天以前 wanshenmean
docs: 更新MES上传状态实现计划,补充Tasks 11-16
b0442e 对比 | 目录
3 天以前 wanshenmean
fix(StockInfoDetailController): MES凭证改为动态获取
c36c5c 对比 | 目录
3 天以前 wanshenmean
fix(StockInfoController): MES凭证改为动态获取
fba665 对比 | 目录
3 天以前 wanshenmean
refactor(TaskService_Inbound): InboundInContainer改为Task.Run异步执行
63ca4a 对比 | 目录
3 天以前 wanshenmean
refactor(StockInfoController): MES调用改为Task.Run异步执行
343d51 对比 | 目录
3 天以前 wanshenmean
refactor(StockService): MES调用改为Task.Run异步执行
4506a1 对比 | 目录
3 天以前 wanshenmean
refactor(StockInfoDetailController): MES调用改为Task.Run异步执行
57feef 对比 | 目录
3 天以前 wanshenmean
docs: add MES upload status tracking design spec
406a7d 对比 | 目录
3 天以前 wanshenmean
feat(stock): 新增组盘、拆盘按钮调用MES接口
0be927 对比 | 目录
3 天以前 wanshenmean
feat(MesApiLogDto): 新增PalletCode字段支持MES日志按托盘查询
32ece0 对比 | 目录
3 天以前 wanshenmean
refactor(TaskService): MES出站调用改为Task.Run异步执行,不阻塞主逻辑
91e326 对比 | 目录
已添加10个文件
已修改37个文件
3643 ■■■■ 文件已修改
Code/.omc/state/last-tool-error.json 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/mission-state.json 388 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/subagent-tracking.json 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/extension/taskinfo/agvTask.jsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/router/viewGird.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/agvTask.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/JobFactory.cs 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Common/CommonStackerCrane.cs 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Storage.cs 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Program.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineJob/CommonConveyorLineJob.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/QuartzLogHelper.cs 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/api/http.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/stock/groupPalle.jsx 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/stock/stockInfo.jsx 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/agvTask.jsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/stock/groupPalle.vue 487 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_agv.vue 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Filter/ApiAuthorizeFilter.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Middlewares/HttpRequestMiddleware.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesApiLogDto.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesUploadHelper.cs 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/MesUploadHelper.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/plans/2026-04-20-MES上传状态与异步上传实现计划.md 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-20-MES上传状态与异步上传设计.md 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/.omc/state/last-tool-error.json
@@ -1,7 +1,7 @@
{
  "tool_name": "Read",
  "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WMS\\\\WIDESEA_WMSClient_Vben_v2\\\\apps\\\\web-ele\\\\src\\\\views\\\\dashboard\\\\index.vue\"}",
  "error": "File does not exist. Note: your current working directory is D:\\Git\\ShanMeiXinNengYuan\\Code.",
  "timestamp": "2026-04-20T01:28:36.836Z",
  "tool_name": "Bash",
  "tool_input_preview": "{\"command\":\"cd \\\"D:/Git/ShanMeiXinNengYuan/Code\\\" && dotnet build WMS/WIDESEA_WMSServer/WIDESEA_WMSServer.sln 2>&1\",\"timeout\":120000,\"description\":\"Build WMS solution to verify changes\"}",
  "error": "Exit code 1\n  æ­£åœ¨ç¡®å®šè¦è¿˜åŽŸçš„é¡¹ç›®â€¦\r\n  æ‰€æœ‰é¡¹ç›®å‡æ˜¯æœ€æ–°çš„,无法还原。\r\nD:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\AOP\\LogAOP.cs(169,123): warning CS8625: æ— æ³•å°† null å­—面量转换为非 null çš„引用类型。 [D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\WIDESEA_Core.csproj]\r\nD:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\Authorization\\AuthorizationResponse.cs(21,30): warning CS8625: æ— æ³•å°† null å­—面量转换为非 null çš„引用类型。 [D:\\Git\\ShanMeiXinNengYuan\\Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\WIDES...",
  "timestamp": "2026-04-20T16:56:32.862Z",
  "retry_count": 1
}
Code/.omc/state/mission-state.json
@@ -1,5 +1,5 @@
{
  "updatedAt": "2026-04-20T02:13:36.765Z",
  "updatedAt": "2026-04-20T17:02:18.624Z",
  "missions": [
    {
      "id": "session:9007b9ea-1eb6-4d24-8fe7-2c3a949eac88:none",
@@ -1964,6 +1964,392 @@
          "sourceKey": "session-stop:a6eae12446c31bdfe"
        }
      ]
    },
    {
      "id": "session:bce34684-82ff-42dd-b951-6194cfe1e77c:none",
      "source": "session",
      "name": "none",
      "objective": "Session mission",
      "createdAt": "2026-04-20T15:49:37.351Z",
      "updatedAt": "2026-04-20T17:02:18.624Z",
      "status": "done",
      "workerCount": 30,
      "taskCounts": {
        "total": 30,
        "pending": 0,
        "blocked": 0,
        "inProgress": 0,
        "completed": 30,
        "failed": 0
      },
      "agents": [
        {
          "name": "general-purpose:af087f2",
          "role": "general-purpose",
          "ownership": "af087f2e64d433e39",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T17:02:18.624Z"
        },
        {
          "name": "general-purpose:a151e5f",
          "role": "general-purpose",
          "ownership": "a151e5f87f0cbdac6",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:50:53.499Z"
        },
        {
          "name": "general-purpose:a9435dc",
          "role": "general-purpose",
          "ownership": "a9435dc250bc7482e",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:50:38.393Z"
        },
        {
          "name": "general-purpose:a32b075",
          "role": "general-purpose",
          "ownership": "a32b0751a039672ef",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:51:30.118Z"
        },
        {
          "name": "general-purpose:aaa2a0f",
          "role": "general-purpose",
          "ownership": "aaa2a0f6b2a25bec0",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:51:23.092Z"
        },
        {
          "name": "general-purpose:a86b879",
          "role": "general-purpose",
          "ownership": "a86b879fb7a0801be",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:52:07.543Z"
        },
        {
          "name": "general-purpose:a12823e",
          "role": "general-purpose",
          "ownership": "a12823e675f358745",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:52:29.208Z"
        },
        {
          "name": "general-purpose:ae97a4d",
          "role": "general-purpose",
          "ownership": "ae97a4de553f0b4c3",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:52:16.206Z"
        },
        {
          "name": "general-purpose:a4a2d7d",
          "role": "general-purpose",
          "ownership": "a4a2d7dfcc130e3c8",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:53:19.832Z"
        },
        {
          "name": "general-purpose:afef0a2",
          "role": "general-purpose",
          "ownership": "afef0a2e4255227bd",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:53:08.591Z"
        },
        {
          "name": "general-purpose:ae1e2c3",
          "role": "general-purpose",
          "ownership": "ae1e2c3f798ee872a",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:53:36.457Z"
        },
        {
          "name": "general-purpose:a66e87c",
          "role": "general-purpose",
          "ownership": "a66e87c9223005128",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:56:26.414Z"
        },
        {
          "name": "general-purpose:a113f70",
          "role": "general-purpose",
          "ownership": "a113f704503b35113",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:54:23.336Z"
        },
        {
          "name": "general-purpose:a7cc134",
          "role": "general-purpose",
          "ownership": "a7cc1344397da9324",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:58:27.436Z"
        },
        {
          "name": "general-purpose:aeac471",
          "role": "general-purpose",
          "ownership": "aeac471bcaec36af3",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:00:25.655Z"
        },
        {
          "name": "general-purpose:a6c7e45",
          "role": "general-purpose",
          "ownership": "a6c7e458af6948696",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T15:57:35.457Z"
        },
        {
          "name": "general-purpose:a5144b6",
          "role": "general-purpose",
          "ownership": "a5144b6e94595b89f",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:01:24.405Z"
        },
        {
          "name": "general-purpose:ac4bbba",
          "role": "general-purpose",
          "ownership": "ac4bbba9b05c57c6a",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:03:43.062Z"
        },
        {
          "name": "general-purpose:ad3b6fa",
          "role": "general-purpose",
          "ownership": "ad3b6fad5f339a4e5",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:02:27.528Z"
        },
        {
          "name": "general-purpose:a72a84d",
          "role": "general-purpose",
          "ownership": "a72a84d56ec83e520",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:03:48.315Z"
        },
        {
          "name": "general-purpose:a62f850",
          "role": "general-purpose",
          "ownership": "a62f85025e6fd117b",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:04:24.590Z"
        },
        {
          "name": "general-purpose:a3265e6",
          "role": "general-purpose",
          "ownership": "a3265e63c320b7fb7",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:04:17.712Z"
        },
        {
          "name": "general-purpose:a75fe8e",
          "role": "general-purpose",
          "ownership": "a75fe8e3716ab2841",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:05:07.601Z"
        },
        {
          "name": "general-purpose:ab70218",
          "role": "general-purpose",
          "ownership": "ab7021856484bc64e",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:18:18.639Z"
        },
        {
          "name": "general-purpose:a9c7d87",
          "role": "general-purpose",
          "ownership": "a9c7d87d13a26caa1",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:18:25.585Z"
        },
        {
          "name": "general-purpose:ac4481c",
          "role": "general-purpose",
          "ownership": "ac4481cc8f2fd18cf",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:18:05.225Z"
        },
        {
          "name": "general-purpose:ad8e51b",
          "role": "general-purpose",
          "ownership": "ad8e51b3e49b68c5b",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:19:04.231Z"
        },
        {
          "name": "general-purpose:a8d6992",
          "role": "general-purpose",
          "ownership": "a8d69927a9c24e9b5",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:36:32.273Z"
        },
        {
          "name": "general-purpose:a9f7c77",
          "role": "general-purpose",
          "ownership": "a9f7c771d702937f2",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:35:27.503Z"
        },
        {
          "name": "general-purpose:aaecb7b",
          "role": "general-purpose",
          "ownership": "aaecb7b0d7ead5188",
          "status": "done",
          "currentStep": null,
          "latestUpdate": "completed",
          "completedSummary": null,
          "updatedAt": "2026-04-20T16:32:32.486Z"
        }
      ],
      "timeline": [
        {
          "id": "session-start:a8d69927a9c24e9b5:2026-04-20T16:31:25.163Z",
          "at": "2026-04-20T16:31:25.163Z",
          "kind": "update",
          "agent": "general-purpose:a8d6992",
          "detail": "started general-purpose:a8d6992",
          "sourceKey": "session-start:a8d69927a9c24e9b5"
        },
        {
          "id": "session-start:a9f7c771d702937f2:2026-04-20T16:31:25.222Z",
          "at": "2026-04-20T16:31:25.222Z",
          "kind": "update",
          "agent": "general-purpose:a9f7c77",
          "detail": "started general-purpose:a9f7c77",
          "sourceKey": "session-start:a9f7c771d702937f2"
        },
        {
          "id": "session-start:aaecb7b0d7ead5188:2026-04-20T16:31:25.289Z",
          "at": "2026-04-20T16:31:25.289Z",
          "kind": "update",
          "agent": "general-purpose:aaecb7b",
          "detail": "started general-purpose:aaecb7b",
          "sourceKey": "session-start:aaecb7b0d7ead5188"
        },
        {
          "id": "session-stop:aaecb7b0d7ead5188:2026-04-20T16:32:32.486Z",
          "at": "2026-04-20T16:32:32.486Z",
          "kind": "completion",
          "agent": "general-purpose:aaecb7b",
          "detail": "completed",
          "sourceKey": "session-stop:aaecb7b0d7ead5188"
        },
        {
          "id": "session-stop:a9f7c771d702937f2:2026-04-20T16:35:27.503Z",
          "at": "2026-04-20T16:35:27.503Z",
          "kind": "completion",
          "agent": "general-purpose:a9f7c77",
          "detail": "completed",
          "sourceKey": "session-stop:a9f7c771d702937f2"
        },
        {
          "id": "session-stop:a8d69927a9c24e9b5:2026-04-20T16:36:32.273Z",
          "at": "2026-04-20T16:36:32.273Z",
          "kind": "completion",
          "agent": "general-purpose:a8d6992",
          "detail": "completed",
          "sourceKey": "session-stop:a8d69927a9c24e9b5"
        },
        {
          "id": "session-stop:a02e13efa2a4d4a7d:2026-04-20T16:43:22.723Z",
          "at": "2026-04-20T16:43:22.723Z",
          "kind": "completion",
          "agent": "general-purpose:af087f2",
          "detail": "completed",
          "sourceKey": "session-stop:a02e13efa2a4d4a7d"
        },
        {
          "id": "session-stop:a82856d42f4ef095b:2026-04-20T17:02:18.624Z",
          "at": "2026-04-20T17:02:18.624Z",
          "kind": "completion",
          "agent": "general-purpose:af087f2",
          "detail": "completed",
          "sourceKey": "session-stop:a82856d42f4ef095b"
        }
      ]
    }
  ]
}
Code/.omc/state/subagent-tracking.json
@@ -1184,10 +1184,280 @@
      "status": "completed",
      "completed_at": "2026-04-20T02:08:23.744Z",
      "duration_ms": 81044
    },
    {
      "agent_id": "af087f2e64d433e39",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:49:37.351Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:50:06.600Z",
      "duration_ms": 29249
    },
    {
      "agent_id": "a151e5f87f0cbdac6",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:50:24.517Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:50:53.498Z",
      "duration_ms": 28981
    },
    {
      "agent_id": "a9435dc250bc7482e",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:50:24.578Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:50:38.390Z",
      "duration_ms": 13812
    },
    {
      "agent_id": "a32b0751a039672ef",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:51:08.683Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:51:30.117Z",
      "duration_ms": 21434
    },
    {
      "agent_id": "aaa2a0f6b2a25bec0",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:51:08.748Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:51:23.091Z",
      "duration_ms": 14343
    },
    {
      "agent_id": "a86b879fb7a0801be",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:51:49.188Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:52:07.541Z",
      "duration_ms": 18353
    },
    {
      "agent_id": "a12823e675f358745",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:51:49.202Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:52:29.206Z",
      "duration_ms": 40004
    },
    {
      "agent_id": "ae97a4de553f0b4c3",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:51:49.272Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:52:16.205Z",
      "duration_ms": 26933
    },
    {
      "agent_id": "a4a2d7dfcc130e3c8",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:52:54.548Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:53:19.831Z",
      "duration_ms": 25283
    },
    {
      "agent_id": "afef0a2e4255227bd",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:52:54.560Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:53:08.589Z",
      "duration_ms": 14029
    },
    {
      "agent_id": "ae1e2c3f798ee872a",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:52:54.621Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:53:36.456Z",
      "duration_ms": 41835
    },
    {
      "agent_id": "a66e87c9223005128",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:53:53.740Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:56:26.413Z",
      "duration_ms": 152673
    },
    {
      "agent_id": "a113f704503b35113",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:53:53.756Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:54:23.335Z",
      "duration_ms": 29579
    },
    {
      "agent_id": "a7cc1344397da9324",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:56:59.419Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:58:27.434Z",
      "duration_ms": 88015
    },
    {
      "agent_id": "aeac471bcaec36af3",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:56:59.484Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:00:25.654Z",
      "duration_ms": 206170
    },
    {
      "agent_id": "a6c7e458af6948696",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T15:56:59.548Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T15:57:35.456Z",
      "duration_ms": 35908
    },
    {
      "agent_id": "a5144b6e94595b89f",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:00:50.066Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:01:24.403Z",
      "duration_ms": 34337
    },
    {
      "agent_id": "ad3b6fad5f339a4e5",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:01:46.364Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:02:27.526Z",
      "duration_ms": 41162
    },
    {
      "agent_id": "ac4bbba9b05c57c6a",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:01:46.304Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:03:43.060Z",
      "duration_ms": 116756
    },
    {
      "agent_id": "a72a84d56ec83e520",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:01:46.423Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:03:48.313Z",
      "duration_ms": 121890
    },
    {
      "agent_id": "a62f85025e6fd117b",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:04:06.835Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:04:24.588Z",
      "duration_ms": 17753
    },
    {
      "agent_id": "a3265e63c320b7fb7",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:04:06.894Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:04:17.711Z",
      "duration_ms": 10817
    },
    {
      "agent_id": "a75fe8e3716ab2841",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:04:35.938Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:05:07.599Z",
      "duration_ms": 31661
    },
    {
      "agent_id": "ab7021856484bc64e",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:15:18.619Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:18:18.637Z",
      "duration_ms": 180018
    },
    {
      "agent_id": "a9c7d87d13a26caa1",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:15:18.684Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:18:25.584Z",
      "duration_ms": 186900
    },
    {
      "agent_id": "ac4481cc8f2fd18cf",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:15:18.748Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:18:05.224Z",
      "duration_ms": 166476
    },
    {
      "agent_id": "ad8e51b3e49b68c5b",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:18:33.453Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:19:04.229Z",
      "duration_ms": 30776
    },
    {
      "agent_id": "a8d69927a9c24e9b5",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:31:25.163Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:36:32.272Z",
      "duration_ms": 307109
    },
    {
      "agent_id": "a9f7c771d702937f2",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:31:25.222Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:35:27.501Z",
      "duration_ms": 242279
    },
    {
      "agent_id": "aaecb7b0d7ead5188",
      "agent_type": "general-purpose",
      "started_at": "2026-04-20T16:31:25.289Z",
      "parent_mode": "none",
      "status": "completed",
      "completed_at": "2026-04-20T16:32:32.484Z",
      "duration_ms": 67195
    }
  ],
  "total_spawned": 118,
  "total_completed": 127,
  "total_spawned": 135,
  "total_completed": 157,
  "total_failed": 0,
  "last_updated": "2026-04-20T02:13:36.867Z"
  "last_updated": "2026-04-20T17:02:18.739Z"
}
Code/WCS/WIDESEAWCS_Client/src/extension/taskinfo/agvTask.jsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: "",
    gridBody: "",
    gridFooter: "",
    //新建、编辑弹出框扩展组件
    modelHeader: "",
    modelBody: "",
    modelFooter: "",
  },
  tableAction: "", //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [], box: [], detail: [] }, //扩展的按钮
  methods: {
    //下面这些方法可以保留也可以删除
    onInit() {},
    onInited() {
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
    },
searchBefore(param) {
  //界面查询前,可以给param.wheres添加查询参数
  //返回false,则不会执行查询
  // ç¬¬ä¸€ä¸ªè¿‡æ»¤æ¡ä»¶
  const roadwayFilter1 = {
    name: "roadway",
    value: "ZJ1",
    displayType: "like",
  };
  // ç¬¬äºŒä¸ªè¿‡æ»¤æ¡ä»¶
  const roadwayFilter2 = {
    name: "roadway",
    value: "FJ1",
    displayType: "like",
  };
  if (!param.wheres) {
    param.wheres = [];
  }
  // å°†ä¸¤ä¸ªè¿‡æ»¤æ¡ä»¶æ·»åŠ åˆ°æŸ¥è¯¢å‚æ•°ä¸­
  param.wheres.push(roadwayFilter1);
  param.wheres.push(roadwayFilter2);
  return true;
},
    searchAfter(result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      return true;
    },
    addBefore(formData) {
      //新建保存前formData为对象,包括明细表,可以给给表单设置值,自己输出看formData的值
      return true;
    },
    updateBefore(formData) {
      //编辑保存前formData为对象,包括明细表、删除行的Id
      return true;
    },
    rowClick({ row, column, event }) {
      //查询界面点击行事件
      this.$refs.table.$refs.table.toggleRowSelection(row); //单击行时选中当前行;
    },
    modelOpenAfter(row) {
      //点击编辑、新建按钮弹出框后,可以在此处写逻辑,如,从后台获取数据
      //(1)判断是编辑还是新建操作: this.currentAction=='Add';
      //(2)给弹出框设置默认值
      //(3)this.editFormFields.字段='xxx';
      //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
      //看不懂就把输出看:console.log(this.editFormOptions)
    },
  },
};
export default extension;
Code/WCS/WIDESEAWCS_Client/src/router/viewGird.js
@@ -77,6 +77,10 @@
    path: '/task',
    name: 'task',
    component: () => import('@/views/taskinfo/task.vue')
  },{
    path: '/agvTask',
    name: 'agvTask',
    component: () => import('@/views/taskinfo/agvTask.vue')
  },
  {
    path: '/taskHty',
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/agvTask.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,236 @@
<template>
  <view-grid
    ref="grid"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
    :editFormOptions="editFormOptions"
    :searchFormFields="searchFormFields"
    :searchFormOptions="searchFormOptions"
    :table="table"
    :extend="extend"
  >
  </view-grid>
</template>
  <script>
import extend from "@/extension/taskinfo/agvTask.jsx";
import { ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const table = ref({
      key: "taskId",
      footer: "Foots",
      cnName: "任务信息",
      name: "task",
      url: "/Task/",
      sortName: "CreateDate",
    });
    const editFormFields = ref({});
    const editFormOptions = ref([]);
    const searchFormFields = ref({
      taskNum: "",
      palletCode: "",
      roadway: "",
      sourceAddress: "",
      targetAddress: "",
      currentAddress: "",
      nextAddress: "",
      creater: "",
      createDate: "",
    });
    const searchFormOptions = ref([
      [
        { title: "任务号", field: "taskNum", type: "int" },
        { title: "托盘编号", field: "palletCode", type: "like" },
        {
          title: "任务类型",
          field: "taskType",
          type: "selectList",
          dataKey: "taskType",
          data: [],
        },
        {
          title: "任务状态",
          field: "taskStatus",
          type: "selectList",
          dataKey: "taskState",
          data: [],
        },
      ],
      [
        { title: "起始地址", field: "sourceAddress", type: "like" },
        { title: "目标地址", field: "targetAddress", type: "like" },
        { title: "当前位置", field: "currentAddress", type: "like" },
        { title: "下一位置", field: "nextAddress", type: "like" },
      ],
      [
        { title: "巷道号", field: "roadway", type: "like" },
        { title: "创建人", field: "creater", type: "like" },
        { title: "创建时间", field: "createDate", type: "datetime" },
      ],
    ]);
    const columns = ref([
      {
        field: "taskId",
        title: "TaskId",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "taskNum",
        title: "任务号",
        type: "int",
        width: 90,
        align: "left",
      },
      {
        field: "palletCode",
        title: "托盘编号",
        type: "string",
        width: 200,
        align: "left",
      },
      {
        field: "roadway",
        title: "巷道号",
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "taskType",
        title: "任务类型",
        type: "int",
        width: 90,
        align: "left",
        bind: { key: "taskType", data: [] },
      },
      {
        field: "taskStatus",
        title: "任务状态",
        type: "int",
        width: 150,
        align: "left",
        bind: { key: "taskState", data: [] },
      },
      {
        field: "sourceAddress",
        title: "起始地址",
        type: "int",
        width: 120,
        align: "left",
      },
      {
        field: "targetAddress",
        title: "目标地址",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "currentAddress",
        title: "当前位置",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "nextAddress",
        title: "下一位置",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "exceptionMessage",
        title: "异常信息",
        type: "string",
        width: 90,
        align: "left",
        hidden: true,
      },
      {
        field: "grade",
        title: "优先级",
        type: "int",
        width: 80,
        align: "left",
      },
      {
        field: "dispatchertime",
        title: "任务下发时间",
        type: "datetime",
        width: 150,
        align: "left",
      },
      {
        field: "wMSId",
        title: "WMS任务主键",
        type: "int",
        width: 120,
        align: "left",
        hidden: true,
      },
      {
        field: "creater",
        title: "创建人",
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "createDate",
        title: "创建时间",
        type: "datetime",
        width: 150,
        align: "left",
      },
      {
        field: "modifier",
        title: "修改人",
        type: "string",
        width: 100,
        align: "left",
      },
      {
        field: "modifyDate",
        title: "修改时间",
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "remark",
        title: "备注",
        type: "string",
        width: 100,
        align: "left",
        hidden: true,
      },
    ]);
    const detail = ref({
      cnName: "",
      table: "",
      columns: [],
      sortName: "",
      key: "",
    });
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
    };
  },
});
</script>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/JobFactory.cs
@@ -19,6 +19,7 @@
using Quartz.Spi;
using Quartz;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -28,7 +29,7 @@
namespace WIDESEAWCS_QuartzJob
{
    /// <summary>
    /// Job注入
    /// Job注入工厂,管理 Job å®žä¾‹çš„ DI ä½œç”¨åŸŸç”Ÿå‘½å‘¨æœŸ
    /// </summary>
    public class JobFactory : IJobFactory
    {
@@ -38,48 +39,71 @@
        private readonly IServiceProvider _serviceProvider;
        /// <summary>
        /// Job å®žä¾‹ä¸Žå¯¹åº”çš„ DI ä½œç”¨åŸŸæ˜ å°„,用于在 ReturnJob æ—¶æ­£ç¡®é‡Šæ”¾èµ„源
        /// </summary>
        private readonly ConcurrentDictionary<IJob, IServiceScope> _scopes = new();
        /// <summary>
        /// Job注入
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="serviceProvider">服务提供者</param>
        public JobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        /// <summary>
        /// å®žçŽ°æŽ¥å£Job
        /// åˆ›å»º Job å®žä¾‹ï¼Œå¹¶ä¸ºæ¯ä¸ª Job åˆ›å»ºç‹¬ç«‹çš„ DI ä½œç”¨åŸŸ
        /// </summary>
        /// <param name="bundle"></param>
        /// <param name="scheduler"></param>
        /// <returns></returns>
        /// <param name="bundle">触发器触发上下文</param>
        /// <param name="scheduler">调度器实例</param>
        /// <returns>Job å®žä¾‹</returns>
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            try
            {
                if (App.ExpDateTime != null && (DateTime.Now - App.ExpDateTime.GetValueOrDefault()).Seconds > 0)
                // éªŒè¯æœ‰æ•ˆæœŸï¼Œä½¿ç”¨ TotalSeconds é¿å… .Seconds å–模导致间歇性判断错误
                if (App.ExpDateTime != null && (DateTime.Now - App.ExpDateTime.GetValueOrDefault()).TotalSeconds > 0)
                {
                    throw new InvalidOperationException($"验证错误");
                }
                // ä¸ºæ¯ä¸ª Job åˆ›å»ºç‹¬ç«‹çš„ DI ä½œç”¨åŸŸï¼Œç¡®ä¿ Scoped æœåŠ¡æ­£ç¡®é‡Šæ”¾
                IServiceScope serviceScope = _serviceProvider.CreateScope();
                IJob? job = serviceScope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
                if (job == null)
                {
                    // Job è§£æžå¤±è´¥æ—¶ç«‹å³é‡Šæ”¾ä½œç”¨åŸŸï¼Œé¿å…æ³„漏
                    serviceScope.Dispose();
                    throw new InvalidOperationException($"无法解析 Job ç±»åž‹: {bundle.JobDetail.JobType.Name}");
                }
                // ä¿å­˜ scope å¼•用,以便 ReturnJob æ—¶é‡Šæ”¾
                _scopes[job] = serviceScope;
                return job;
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex.ToString());
                throw new Exception(ex.Message);
                throw;
            }
        }
        /// <summary>
        /// Job注入
        /// é‡Šæ”¾ Job å®žä¾‹åŠå…¶å…³è”çš„ DI ä½œç”¨åŸŸ
        /// </summary>
        /// <param name="job"></param>
        /// <param name="job">待释放的 Job å®žä¾‹</param>
        public void ReturnJob(IJob job)
        {
            IDisposable? disposable = job as IDisposable;
            disposable?.Dispose();
            // å…ˆé‡Šæ”¾å…³è”çš„ DI ä½œç”¨åŸŸï¼ˆåŒ…括其中所有 Scoped æœåŠ¡ï¼‰
            if (_scopes.TryRemove(job, out IServiceScope? scope))
            {
                scope.Dispose();
            }
            // å†é‡Šæ”¾ Job æœ¬èº«
            (job as IDisposable)?.Dispose();
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs
@@ -50,7 +50,7 @@
                deviceInfos.ForEach(x =>
                {
                    if (!Storage.Devices.Exists(d => d.DeviceCode == x.DeviceCode))
                    if (!Storage.Devices.Any(d => d.DeviceCode == x.DeviceCode))
                    {
                        try
                        {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs
@@ -86,7 +86,7 @@
            WebResponseContent result = new WebResponseContent();
            try
            {
                if (_scheduler.IsShutdown && _scheduler.IsStarted)
                if (_scheduler.IsShutdown || !_scheduler.IsStarted)
                {
                    // ä»ŽFactory中获取Scheduler实例
                    NameValueCollection collection = new NameValueCollection
@@ -110,7 +110,7 @@
                }
                else
                {
                    await _scheduler.Shutdown();
                    // è°ƒåº¦å™¨å·²åœ¨è¿è¡Œï¼Œç›´æŽ¥è¿”回提示
                    result = WebResponseContent.Instance.Error(QuartzJobInfoMessage.JobHasStart);
                    return result;
                }
@@ -135,16 +135,13 @@
                    //等待任务运行完成
                    await _scheduler.Shutdown(false);
                    await Console.Out.WriteLineAsync(QuartzJobInfoMessage.StopJobSuccess);
                    QuartzLogger.Info(QuartzJobInfoMessage.StopJobSuccess);
                    result = WebResponseContent.Instance.OK(QuartzJobInfoMessage.StopJobSuccess);
                    return result;
                }
                else
                {
                    IReadOnlyCollection<string> jobGroupNames = await _scheduler.GetJobGroupNames();
                    await _scheduler.PauseAll();
                    // è°ƒåº¦å™¨å·²åœæ­¢ï¼Œç›´æŽ¥è¿”回提示(不再对已 shutdown çš„ scheduler è°ƒç”¨ PauseAll)
                    result = WebResponseContent.Instance.Error(QuartzJobInfoMessage.JobHasStop);
                    return result;
                }
@@ -169,7 +166,7 @@
            {
                try
                {
                    if (_scheduler.IsShutdown && _scheduler.IsStarted)
                    if (_scheduler.IsShutdown || !_scheduler.IsStarted)
                    {
                        // ä»ŽFactory中获取Scheduler实例
                        NameValueCollection collection = new NameValueCollection
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Common/CommonStackerCrane.cs
@@ -20,12 +20,14 @@
using HslCommunication;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
using WIDESEAWCS_Communicator;
using WIDESEAWCS_QuartzJob.DeviceBase;
using WIDESEAWCS_QuartzJob.DTO;
using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_QuartzJob.StackerCrane.Common;
using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
using WIDESEAWCS_Core.LogHelper;
namespace WIDESEAWCS_QuartzJob
{
@@ -67,7 +69,10 @@
        /// </summary>
        private int _lastTaskNum;
        private bool _isChecked = false;
        /// <summary>
        /// æ ‡è®°æ˜¯å¦æ­£åœ¨æ£€æŸ¥ä»»åŠ¡å®ŒæˆçŠ¶æ€ï¼ˆvolatile ä¿è¯å¤šçº¿ç¨‹å¯è§æ€§ï¼‰
        /// </summary>
        private volatile bool _isChecked = false;
        private bool _heartStatr = true;
@@ -399,30 +404,33 @@
                        {
                            OperateResult<TimeSpan> operateResult = new OperateResult<TimeSpan>();
                            TypeCode typeCode = SiemensDBDataType.GetTypeCode(devicePro.DeviceDataType);
                            // è¶…时从 10*6000ms (60s) é™ä½Žåˆ° 10*1000ms (10s),防止断连时长时间阻塞
                            int timeout = 10 * 1000;
                            switch (typeCode)
                            {
                                case TypeCode.Boolean:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToBoolean(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToBoolean(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                case TypeCode.Byte:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToByte(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToByte(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                case TypeCode.Int16:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToInt16(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToInt16(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                case TypeCode.Int32:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToInt32(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToInt32(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                case TypeCode.UInt16:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToUInt16(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToUInt16(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                case TypeCode.UInt32:
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000, Convert.ToUInt32(deviceProtocolDetail.ProtocalDetailValue));
                                    operateResult = Communicator.Wait(devicePro.DeviceProAddress, 500, timeout, Convert.ToUInt32(deviceProtocolDetail.ProtocalDetailValue));
                                    break;
                                default:
@@ -440,6 +448,8 @@
                }
                catch (Exception ex)
                {
                    // è®°å½•异常,避免错误被静默吞掉
                    QuartzLogger.Error($"CheckStackerCraneTaskCompleted: è®¾å¤‡ {_deviceCode} æ£€æŸ¥å¼‚常", "CommonStackerCrane", ex);
                }
                finally
                {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Storage.cs
@@ -3,6 +3,7 @@
using Quartz;
using SqlSugar;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
@@ -19,14 +20,14 @@
namespace WIDESEAWCS_QuartzJob
{
    /// <summary>
    /// é™æ€å˜è„¸å­˜å‚¨åŒºï¼Œå¯ä½¿ç”¨é™æ€å˜é‡ï¼Œä¹Ÿå¯æ³¨å…¥ä½¿ç”¨
    /// é™æ€å˜é‡å­˜å‚¨åŒºï¼Œå¯ä½¿ç”¨é™æ€å˜é‡ï¼Œä¹Ÿå¯æ³¨å…¥ä½¿ç”¨
    /// </summary>
    public class Storage
    {
        /// <summary>
        /// å·²è¿žæŽ¥è®¾å¤‡å¯¹è±¡é›†åˆ
        /// å·²è¿žæŽ¥è®¾å¤‡å¯¹è±¡é›†åˆï¼ˆçº¿ç¨‹å®‰å…¨ï¼‰
        /// </summary>
        public static List<IDevice> Devices = new List<IDevice>();
        public static ConcurrentBag<IDevice> Devices = new ConcurrentBag<IDevice>();
        /// <summary>
        /// è®¾å¤‡å¯¹è±¡
@@ -44,8 +45,8 @@
        /// <summary>
        /// èŽ·å–è®¾å¤‡
        /// </summary>
        /// <param name="deviceCode"></param>
        /// <returns></returns>
        /// <param name="deviceCode">设备编码</param>
        /// <returns>设备实例,未找到返回 null</returns>
        public IDevice? GetDevice(string deviceCode)
        {
            return Pro_Devices.FirstOrDefault(x => x.DeviceCode == deviceCode);
@@ -54,8 +55,8 @@
        /// <summary>
        /// èŽ·å–è®¾å¤‡
        /// </summary>
        /// <param name="deviceCodes"></param>
        /// <returns></returns>
        /// <param name="deviceCodes">设备编码列表</param>
        /// <returns>匹配的设备列表</returns>
        public List<IDevice> GetDevices(List<string> deviceCodes)
        {
            return Pro_Devices.Where(x => deviceCodes.Contains(x.DeviceCode)).ToList();
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Program.cs
@@ -49,7 +49,7 @@
        .Enrich.WithProperty("Application", "WCS")
        // è®¾ç½®Microsoft命名空间的日志级别为Information
        // è¿™æ ·å¯ä»¥å‡å°‘Microsoft框架本身的详细日志,避免过多的Debug日志
        .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
        .MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
        .WriteTo.Console()  // æ·»åŠ æŽ§åˆ¶å°è¾“å‡ºæŽ¥æ”¶å™¨ï¼Œæ—¥å¿—å°†æ˜¾ç¤ºåœ¨æŽ§åˆ¶å°çª—å£ä¸­
                            // æ·»åŠ æ–‡ä»¶è¾“å‡ºæŽ¥æ”¶å™¨ï¼Œå°†æ—¥å¿—å†™å…¥æ–‡ä»¶ç³»ç»Ÿ
        .WriteTo.File(
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -18,7 +18,8 @@
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
      "Microsoft.AspNetCore": "Warning",
      "Quartz": "Debug"
    }
  },
  "dics": "deviceType,devicePlcType,jobAssembly,jobClassName,deviceStatus,taskType,taskState,inOutType,dispatchId",
@@ -38,7 +39,7 @@
  //跨域
  "Cors": {
    "PolicyName": "CorsIpAccess", //策略名称
    "EnableAllIPs": false, //当为true时,开放所有IP均可访问。
    "EnableAllIPs": true, //当为true时,开放所有IP均可访问。
    // æ”¯æŒå¤šä¸ªåŸŸåç«¯å£ï¼Œæ³¨æ„ç«¯å£å·åŽä¸è¦å¸¦/斜杆:比如localhost:8000/,是错的
    // æ³¨æ„ï¼Œhttp://127.0.0.1:1818 å’Œ http://localhost:1818 æ˜¯ä¸ä¸€æ ·çš„
    "IPs": "http://127.0.0.1:8080,http://localhost:8080,http://localhost:8081"
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineJob/CommonConveyorLineJob.cs
@@ -28,6 +28,7 @@
using WIDESEAWCS_Communicator;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -87,6 +88,8 @@
            }
            catch (Exception ex)
            {
                // è®°å½•异常,避免错误被静默吞掉
                QuartzLogger.Error("CommonConveyorLineJob Execute å¼‚常", "CommonConveyorLineJob", ex);
            }
            return Task.CompletedTask;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -1,15 +1,14 @@
using MapsterMapper;
using Masuit.Tools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Quartz;
using Serilog;
using SqlSugar;
using WIDESEA_Core;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -41,7 +40,6 @@
        /// ä»»åŠ¡æœåŠ¡
        /// </summary>
        private readonly ITaskService _taskService;
        /// <summary>
        /// æœºå™¨äººä»»åŠ¡æœåŠ¡
@@ -82,7 +80,7 @@
        /// <summary>
        /// æ—¥å¿—记录器
        /// </summary>
        private readonly ILogger<CommonConveyorLineNewJob> _logger;
        private readonly ILogger _logger;
        /// <summary>
        /// ç›®æ ‡åœ°å€åˆ°è®¾å¤‡ç±»åž‹çš„æ˜ å°„
@@ -90,6 +88,11 @@
        /// <remarks>
        /// </remarks>
        private static List<string> AddressToDeviceType = new List<string> { "11020", "11028" };
        /// <summary>
        /// æ‰˜ç›˜æ£€æŸ¥ä½ç½®çš„æœ€è¿‘执行时间(用于30秒间隔限制)
        /// </summary>
        private static readonly Dictionary<string, DateTime> _lastPalletCheckTime = new();
        /// <summary>
        /// æž„造函数
@@ -100,7 +103,7 @@
        /// <param name="mapper">对象映射器</param>
        /// <param name="httpClientHelper">HTTP å®¢æˆ·ç«¯å¸®åŠ©ç±»</param>
        /// <param name="logger">日志记录器</param>
        public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger<CommonConveyorLineNewJob> logger, IRobotTaskService robotTaskService)
        public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger logger, IRobotTaskService robotTaskService)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
@@ -179,6 +182,13 @@
                            // å¦‚果当前设备在检查列表中
                            if (checkPalletPositions.Any(x => x.Code == childDeviceCode))
                            {
                                // 30秒间隔限制
                                if (_lastPalletCheckTime.TryGetValue(childDeviceCode, out var lastTime) &&
                                    (DateTime.Now - lastTime).TotalSeconds < 30)
                                {
                                    continue;
                                }
                                // æ£€æŸ¥è¾“送线状态(是否有托盘)
                                if (command.CV_State == 2)
                                {
@@ -196,6 +206,8 @@
                                            TargetAddress = childDeviceCode
                                        }.Serialize());
                                        _lastPalletCheckTime[childDeviceCode] = DateTime.Now;
                                        // å¦‚果请求成功,接收 WMS è¿”回的任务
                                        if (responseResult.IsSuccess && responseResult.Data.Status)
                                        {
@@ -208,7 +220,7 @@
                                }
                            }
                            #endregion
                            #endregion æ£€æµ‹æ˜¯å¦éœ€è¦ç©ºæ‰˜ç›˜
                            // ========== æ£€æŸ¥ PLC_STB æ ‡å¿— ==========
                            // åªæœ‰å½“ PLC_STB ä¸º 1 æ—¶æ‰å¤„理任务
@@ -278,7 +290,6 @@
                                        RobotTargetAddressLineCode = childDeviceCode,
                                        RobotTaskNum = num, // ç”Ÿæˆä»»åŠ¡å·
                                        RobotDispatchertime = DateTime.Now,
                                    };
                                    if (_robotTaskService.AddData(robotTask).Status)
                                    {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -1,9 +1,7 @@
using MapsterMapper;
using Microsoft.Extensions.Logging;
using Serilog;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -201,7 +199,6 @@
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task != null)
            {
                // æ›´æ–°ä»»åŠ¡çŠ¶æ€åˆ°ä¸‹ä¸€é˜¶æ®µï¼ˆé€šå¸¸æ˜¯å®Œæˆï¼‰
                if (_taskService.UpdateTaskStatusToNext(task).Status)
                {
@@ -209,8 +206,6 @@
                    conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode);
                    QuartzLogHelper.LogInfo(_logger, "ConveyorLineInFinish:入库完成,任务号: {TaskNum},子设备: {ChildDeviceCode}", $"入库完成,任务号: {task.TaskNum}", conveyorLine.DeviceCode, task.TaskNum, childDeviceCode);
                }
            }
        }
@@ -316,4 +311,4 @@
            }
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Core.LogHelper;
using Serilog;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
@@ -276,7 +275,7 @@
            if (device == null)
            {
                // è®¾å¤‡æœªæ‰¾åˆ°æ—¶è®°å½•调试日志,方便排查配置问题
                _logger.LogDebug("FindDevice:未找到 {DeviceName}", deviceName);
                _logger.Debug("FindDevice:未找到 {DeviceName}", deviceName);
            }
            return device; // å¯èƒ½ä¸º null,由调用方负责 null æ£€æŸ¥
        }
@@ -456,4 +455,4 @@
            QuartzLogHelper.LogDebug(_logger, "Handle{Scenario}:子设备: {ChildDeviceCode},目标地址: {NextAddress}", $"Handle{scenario}:子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode, scenario, childDeviceCode, nextAddress);
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Core.LogHelper;
using Serilog;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -94,4 +93,4 @@
            return result.Status;
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/QuartzLogHelper.cs
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Serilog;
using WIDESEAWCS_Core.LogHelper;
namespace WIDESEAWCS_Tasks;
@@ -23,7 +23,7 @@
    /// <param name="args">ILogger ç»“构化日志的参数</param>
    public static void LogError(ILogger logger, Exception ex, string loggerMessage, string quartzMessage, string deviceCode, params object[] args)
    {
        logger.LogError(ex, loggerMessage, args);
        logger.Error(ex, loggerMessage, args);
        QuartzLogger.Error(quartzMessage, deviceCode, ex);
    }
@@ -37,7 +37,7 @@
    /// <param name="args">ILogger ç»“构化日志的参数</param>
    public static void LogError(ILogger logger, string loggerMessage, string quartzMessage, string deviceCode, params object[] args)
    {
        logger.LogError(loggerMessage, args);
        logger.Error(loggerMessage, args);
        QuartzLogger.Error(quartzMessage, deviceCode);
    }
@@ -51,8 +51,21 @@
    /// <param name="args">ILogger ç»“构化日志的参数</param>
    public static void LogInfo(ILogger logger, string loggerMessage, string quartzMessage, string deviceCode, params object[] args)
    {
        logger.LogInformation(loggerMessage, args);
        logger.Information(loggerMessage, args);
        QuartzLogger.Info(quartzMessage, deviceCode);
    }
    /// <summary>
    /// è®°å½•信息日志
    /// </summary>
    /// <param name="logger">ILogger å®žä¾‹</param>
    /// <param name="loggerMessage">ILogger çš„结构化日志模板</param>
    /// <param name="quartzMessage">QuartzLogger çš„æ—¥å¿—消息</param>
    /// <param name="deviceCode">设备编码</param>
    public static void LogInfo(ILogger logger, string loggerMessage, string deviceCode)
    {
        logger.Information(loggerMessage);
        QuartzLogger.Info(loggerMessage, deviceCode);
    }
    /// <summary>
@@ -65,7 +78,7 @@
    /// <param name="args">ILogger ç»“构化日志的参数</param>
    public static void LogWarn(ILogger logger, string loggerMessage, string quartzMessage, string deviceCode, params object[] args)
    {
        logger.LogWarning(loggerMessage, args);
        logger.Warning(loggerMessage, args);
        QuartzLogger.Warn(quartzMessage, deviceCode);
    }
@@ -79,7 +92,7 @@
    /// <param name="args">ILogger ç»“构化日志的参数</param>
    public static void LogDebug(ILogger logger, string loggerMessage, string quartzMessage, string deviceCode, params object[] args)
    {
        logger.LogDebug(loggerMessage, args);
        logger.Debug(loggerMessage, args);
        QuartzLogger.Debug(quartzMessage, deviceCode);
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -1,7 +1,8 @@
using Microsoft.Extensions.Logging;
//using Microsoft.Extensions.Logging;
using Quartz;
using Serilog;
using WIDESEA_Core;
using WIDESEAWCS_Core;
using WIDESEAWCS_Common.Constants;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
@@ -10,7 +11,6 @@
using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
using WIDESEAWCS_Common.Constants;
using WIDESEAWCS_Tasks.StackerCraneJob;
namespace WIDESEAWCS_Tasks
@@ -81,7 +81,7 @@
        /// <summary>
        /// æ—¥å¿—记录器
        /// </summary>
        private readonly ILogger<CommonStackerCraneJob> _logger;
        private readonly ILogger  _logger;
        /// <summary>
        /// å †åž›æœºè®¾å¤‡ç¼–码
@@ -103,7 +103,7 @@
            ITaskRepository taskRepository,
            IRouterService routerService,
            HttpClientHelper httpClientHelper,
            ILogger<CommonStackerCraneJob> logger)
            ILogger logger)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
@@ -111,13 +111,13 @@
            _logger = logger;
            // åŠ è½½é…ç½®æ–‡ä»¶
            _config = LoadConfig();
            //_config = LoadConfig();
            // åˆå§‹åŒ–任务选择器
            _taskSelector = new StackerCraneTaskSelector(taskService, routerService, httpClientHelper, _logger);
            // åˆå§‹åŒ–命令构建器
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config, _logger);
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _logger);
        }
        /// <summary>
@@ -169,10 +169,12 @@
        {
            try
            {
                //QuartzLogger.Info($"CommonStackerCraneJob Execute:开始执行堆垛机任务调度 ã€{DateTime.Now.ToString("F")}】", "CommonStackerCraneJob Execute ");
                // ä»Ž JobDataMap èŽ·å–å †åž›æœºè®¾å¤‡å‚æ•°
                bool flag = context.JobDetail.JobDataMap.TryGetValue("JobParams", out object? value);
                if (!flag || value is not CommonStackerCrane commonStackerCrane)
                {
                    _logger.Information("Execute:参数无效,未找到 JobParams æˆ–类型不匹配");
                    // å‚数无效,直接返回
                    QuartzLogHelper.LogWarn(_logger, "Execute:参数无效", "Execute:参数无效", "CommonStackerCraneJob");
                    return Task.CompletedTask;
@@ -193,7 +195,7 @@
                // ========== æ£€æŸ¥æ˜¯å¦å¯ä»¥å‘送新任务 ==========
                //if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                if (commonStackerCrane.StackerCraneStatusValue != StackerCraneStatus.Normal )
                if (commonStackerCrane.StackerCraneStatusValue != StackerCraneStatus.Normal)
                {
                    // å †åž›æœºä¸å¯ç”¨ï¼ˆå¦‚正在执行上一任务),直接返回
                    return Task.CompletedTask;
@@ -222,7 +224,7 @@
                bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
                if (sendFlag)
                {
                    Task.Delay(1000).Wait();
                    Thread.Sleep(1000);
                    commonStackerCrane.SetValue(StackerCraneDBName.WorkAction, (short)StackerCraneWorkActionEnum.StartTask);
                    // å‘送成功,更新状态
                    commonStackerCrane.LastTaskType = task.TaskType;
@@ -240,6 +242,10 @@
                // è®°å½•异常
                QuartzLogHelper.LogError(_logger, ex, "Execute:执行异常,设备: {DeviceCode}", $"执行异常: {ex.Message}", _deviceCode, _deviceCode);
            }
            finally
            {
                QuartzLogHelper.LogInfo(_logger, $"CommonStackerCraneJob Execute:堆垛机任务调度执行完成 ã€{DateTime.Now.ToString("F")}】", _deviceCode);
            }
            return Task.CompletedTask;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
@@ -1,9 +1,7 @@
using Microsoft.Extensions.Logging;
using System;
using Serilog;
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.Constants;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob.Models;
@@ -37,11 +35,6 @@
        private readonly IRouterService _routerService;
        /// <summary>
        /// å †åž›æœºå‘½ä»¤é…ç½®
        /// </summary>
        private readonly StackerCraneCommandConfig _config;
        /// <summary>
        /// æ—¥å¿—记录器
        /// </summary>
        private readonly ILogger _logger;
@@ -56,12 +49,10 @@
        public StackerCraneCommandBuilder(
            ITaskService taskService,
            IRouterService routerService,
            StackerCraneCommandConfig config,
            ILogger logger)
        {
            _taskService = taskService;
            _routerService = routerService;
            _config = config;
            _logger = logger;
        }
@@ -75,7 +66,7 @@
        /// <returns>堆垛机命令对象,转换失败返回 null</returns>
        public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
        {
            return  BuildCommand(task, CreateStandardCommand(task));
            return BuildCommand(task, CreateStandardCommand(task));
            // æ ¹æ®å··é“获取命令类型
            //string commandType = GetCommandType(task.Roadway);
@@ -99,20 +90,20 @@
        /// </remarks>
        /// <param name="roadway">巷道编码</param>
        /// <returns>命令类型(Standard æˆ– Formation)</returns>
        private string GetCommandType(string roadway)
        {
            foreach (var mapping in _config.RoadwayCommandMapping)
            {
                if (roadway.Contains(mapping.Key))
                {
                    QuartzLogHelper.LogDebug(_logger, "GetCommandType:匹配巷道 {Roadway},命令类型: {CommandType}", $"GetCommandType:匹配巷道 {roadway},命令类型: {mapping.Value}", roadway, roadway, mapping.Value);
                    return mapping.Value;
                }
            }
        //private string GetCommandType(string roadway)
        //{
        //    foreach (var mapping in _config.RoadwayCommandMapping)
        //    {
        //        if (roadway.Contains(mapping.Key))
        //        {
        //            QuartzLogHelper.LogDebug(_logger, "GetCommandType:匹配巷道 {Roadway},命令类型: {CommandType}", $"GetCommandType:匹配巷道 {roadway},命令类型: {mapping.Value}", roadway, roadway, mapping.Value);
        //            return mapping.Value;
        //        }
        //    }
            QuartzLogHelper.LogDebug(_logger, "GetCommandType:巷道 {Roadway} æœªåŒ¹é…ï¼Œä½¿ç”¨é»˜è®¤å‘½ä»¤ç±»åž‹: {DefaultType}", $"GetCommandType:巷道 {roadway} æœªåŒ¹é…ï¼Œä½¿ç”¨é»˜è®¤å‘½ä»¤ç±»åž‹: {_config.DefaultCommandType}", roadway, roadway, _config.DefaultCommandType);
            return _config.DefaultCommandType;
        }
        //    QuartzLogHelper.LogDebug(_logger, "GetCommandType:巷道 {Roadway} æœªåŒ¹é…ï¼Œä½¿ç”¨é»˜è®¤å‘½ä»¤ç±»åž‹: {DefaultType}", $"GetCommandType:巷道 {roadway} æœªåŒ¹é…ï¼Œä½¿ç”¨é»˜è®¤å‘½ä»¤ç±»åž‹: {_config.DefaultCommandType}", roadway, roadway, _config.DefaultCommandType);
        //    return _config.DefaultCommandType;
        //}
        /// <summary>
        /// åˆ›å»ºæ ‡å‡†å‘½ä»¤
@@ -205,7 +196,7 @@
            {
                taskType = StackerCraneConst.EmptyPalletTaskType;
            }
            else if(task.TaskType == (int)TaskInboundTypeEnum.InEmpty)
            else if (task.TaskType == (int)TaskInboundTypeEnum.InEmpty)
            {
                taskType = StackerCraneConst.EmptyInPalletTaskType;
            }
@@ -395,4 +386,4 @@
                && short.TryParse(parts[2], out layer);
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -1,12 +1,11 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Serilog;
using System.Diagnostics.CodeAnalysis;
using WIDESEA_Core;
using WIDESEAWCS_Common.Constants;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -316,4 +315,4 @@
            return isOccupied;
        }
    }
}
}
Code/WMS/WIDESEA_WMSClient/src/api/http.js
@@ -12,7 +12,7 @@
let loadingInstance;
let loadingStatus = false;
if (process.env.NODE_ENV == 'development') {
    axios.defaults.baseURL = 'http://127.0.0.1:9291/';
    axios.defaults.baseURL = window.webConfig.webApiProduction;
}
else if (process.env.NODE_ENV == 'debug') {
    axios.defaults.baseURL = window.webConfig.webApiBaseUrl;
Code/WMS/WIDESEA_WMSClient/src/extension/stock/groupPalle.jsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: '',
    gridBody: '',
    gridFooter: '',
    //新建、编辑弹出框扩展组件
    modelHeader: '',
    modelBody: '',
    modelFooter: ''
  },
  tableAction: '',
  buttons: { view: [], box: [], detail: [] },
  methods: {
    onInit() {
      // æ·»åŠ MES操作列
      this.columns.push({
        title: '操作',
        field: '操作',
        align: 'center',
        width: 200,
        fixed: 'right',
        render: (h, { row, column, index }) => {
          return (
            <div>
              <el-button
                type="primary"
                size="small"
                onClick={($e) => { this.handleInbound(row); }}
              >进站</el-button>
              <el-button
                type="success"
                size="small"
                style="margin-left: 8px"
                onClick={($e) => { this.handleOutbound(row); }}
              >出站</el-button>
            </div>
          );
        }
      });
    },
    // æ‰˜ç›˜è¿›ç«™æ“ä½œ
    async handleInbound(row) {
      try {
        await this.$confirm(`确认执行托盘进站操作?\n托盘编号:${row.palletCode}`, "进站确认", {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning"
        });
        const result = await this.http.post("/api/StockInfo/inboundInContainer", {
          palletCode: row.palletCode,
          stockId: row.id
        }, "正在调用MES接口...");
        if (result.status) {
          this.$Message.success(result.message || "托盘进站成功");
          this.$refs.table.load();
        } else {
          this.$error(result.message || "托盘进站失败");
        }
      } catch (error) {
        if (error !== "cancel") {
          this.$error(error.message || "网络错误,请稍后重试");
        }
      }
    },
    // æ‰˜ç›˜å‡ºç«™æ“ä½œ
    async handleOutbound(row) {
      try {
        await this.$confirm(`确认执行托盘出站操作?\n托盘编号:${row.palletCode}`, "出站确认", {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning"
        });
        const result = await this.http.post("/api/StockInfo/outboundInContainer", {
          palletCode: row.palletCode,
          stockId: row.id
        }, "正在调用MES接口...");
        if (result.status) {
          this.$Message.success(result.message || "托盘出站成功");
          this.$refs.table.load();
        } else {
          this.$error(result.message || "托盘出站失败");
        }
      } catch (error) {
        if (error !== "cancel") {
          this.$error(error.message || "网络错误,请稍后重试");
        }
      }
    },
    onInited() {
      // æ¡†æž¶åˆå§‹åŒ–配置后
    },
    searchBefore(param) {
      const locationCodeFilter = {
        name: "stockStatus",
        value: "1",
        displayType: "int"
      };
      if (!param.wheres) {
        param.wheres = [];
      }
      // å°†è¿‡æ»¤æ¡ä»¶æ·»åŠ åˆ°æŸ¥è¯¢å‚æ•°ä¸­
      param.wheres.push(locationCodeFilter);
      return true;
    },
    searchAfter(result) {
      return true;
    },
    addBefore(formData) {
      return true;
    },
    updateBefore(formData) {
      return true;
    },
    rowClick({ row, column, event }) {
      this.$refs.table.$refs.table.toggleRowSelection(row);
    },
    modelOpenAfter(row) {
      // ç‚¹å‡»ç¼–辑、新建按钮弹出框后
    }
  }
};
export default extension;
Code/WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx
@@ -1,4 +1,4 @@
// é¡µé¢æ‰©å±•配置:预留给库存页面二次开发。
// é¡µé¢æ‰©å±•配置:库存页面组盘/拆盘按钮扩展
let extension = {
  components: {
    gridHeader: "",
@@ -8,14 +8,39 @@
    modelBody: "",
    modelFooter: "",
  },
  tableAction: "",
  tableAction: "stock",
  buttons: { view: [], box: [], detail: [] },
  methods: {
    onInit() {
      return true;
    },
    onInited() {
      // æ³¨å…¥ç»„盘、拆盘按钮到操作列
      this.editTableButtons = [
        { name: "组盘", onClick: this.onGroupPallet },
        { name: "拆盘", onClick: this.onSplitPallet }
      ];
      return true;
    },
    async onGroupPallet({ row }) {
      // è°ƒç”¨ç»„盘接口
      let result = await this.$api.post("/Stock/GroupPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("组盘成功,MES数据已异步上传");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "组盘失败");
      }
    },
    async onSplitPallet({ row }) {
      // è°ƒç”¨æ‹†ç›˜æŽ¥å£
      let result = await this.$api.post("/Stock/SplitPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("拆盘成功,MES数据已异步上传");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "拆盘失败");
      }
    },
    searchBefore(param) {
      return true;
@@ -38,4 +63,4 @@
  },
};
export default extension;
export default extension;
Code/WMS/WIDESEA_WMSClient/src/extension/stock/stockInfo.jsx
@@ -28,6 +28,11 @@
              <el-button
                type="primary"
                size="small"
                onClick={($e) => { this.handleBind(row); }}
              >绑定</el-button>
              <el-button
                type="primary"
                size="small"
                onClick={($e) => { this.handleInbound(row); }}
              >进站</el-button>
              <el-button
@@ -36,10 +41,42 @@
                style="margin-left: 8px"
                onClick={($e) => { this.handleOutbound(row); }}
              >出站</el-button>
              <el-button
                type="success"
                size="small"
                style="margin-left: 8px"
                onClick={($e) => { this.handleUnbind(row); }}
              >解绑</el-button>
            </div>
          );
        }
      });
    },
    // æ‰˜ç›˜ç»„盘操作
    async handleBind(row) {
      try {
        await this.$confirm(`确认执行托盘组盘操作?\n托盘编号:${row.palletCode}`, "组盘确认", {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning"
        });
        const result = await this.http.post("/api/StockInfoDetail/BindContainer", {
          palletCode: row.palletCode
        }, "正在调用MES接口...");
        if (result.status) {
          this.$Message.success(result.message || "托盘组盘成功");
          this.$refs.table.load();
        } else {
          this.$error(result.message || "托盘组盘失败");
        }
      } catch (error) {
        if (error !== "cancel") {
          this.$error(error.message || "网络错误,请稍后重试");
        }
      }
    },
    // æ‰˜ç›˜è¿›ç«™æ“ä½œ
@@ -96,6 +133,32 @@
      }
    },
    // æ‰˜ç›˜æ‹†ç›˜æ“ä½œ
    async handleUnbind(row) {
      try {
        await this.$confirm(`确认执行托盘拆盘操作?\n托盘编号:${row.palletCode}`, "拆盘确认", {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning"
        });
        const result = await this.http.post("/api/StockInfoDetail/UnbindContainer", {
          palletCode: row.palletCode,
        }, "正在调用MES接口...");
        if (result.status) {
          this.$Message.success(result.message || "托盘拆盘成功");
          this.$refs.table.load();
        } else {
          this.$error(result.message || "托盘拆盘失败");
        }
      } catch (error) {
        if (error !== "cancel") {
          this.$error(error.message || "网络错误,请稍后重试");
        }
      }
    },
    onInited() {
      // æ¡†æž¶åˆå§‹åŒ–配置后
    },
@@ -103,7 +166,7 @@
      return true;
    },
    searchAfter(result) {
      return true;
      return result;
    },
    addBefore(formData) {
      return true;
Code/WMS/WIDESEA_WMSClient/src/extension/system/Mes_Log.jsx
@@ -48,13 +48,15 @@
        this.showJsonDetail(row, 'request');
      } else if (column.property === 'responseJson' && row.responseJson) {
        this.showJsonDetail(row, 'response');
      } else if (column.property === 'errorMessage' && row.errorMessage) {
        this.showJsonDetail(row, 'errorMessage');
      }
    },
    // æ˜¾ç¤º JSON è¯¦æƒ…抽屉
    showJsonDetail(row, type = 'request') {
      const jsonContent = type === 'request' ? row.requestJson : row.responseJson;
      const title = type === 'request' ? '📋 è¯·æ±‚ JSON' : '📥 å“åº” JSON';
      const jsonContent = type === 'request' ? row.requestJson :  type === 'response' ? row.responseJson : row.errorMessage;
      const title = type === 'request' ? '📋 è¯·æ±‚ JSON' : type === 'response' ? '📋 è¯·æ±‚ JSON': '📥 é”™è¯¯æ¶ˆæ¯';
      // è§£æž JSON å¯¹è±¡ï¼Œè§£æžå¤±è´¥åˆ™ä¿ç•™åŽŸå§‹å­—ç¬¦ä¸²
      let jsonData;
Code/WMS/WIDESEA_WMSClient/src/extension/taskinfo/agvTask.jsx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
//此js文件是用来自定义扩展业务代码,可以扩展一些自定义页面或者重新配置生成的代码
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: "",
    gridBody: "",
    gridFooter: "",
    //新建、编辑弹出框扩展组件
    modelHeader: "",
    modelBody: "",
    modelFooter: "",
  },
  tableAction: "", //指定某张表的权限(这里填写表名,默认不用填写)
  buttons: { view: [], box: [], detail: [] }, //扩展的按钮
  methods: {
    //下面这些方法可以保留也可以删除
    onInit() {},
    onInited() {
      //框架初始化配置后
      //如果要配置明细表,在此方法操作
      //this.detailOptions.columns.forEach(column=>{ });
    },
searchBefore(param) {
  //界面查询前,可以给param.wheres添加查询参数
  //返回false,则不会执行查询
  // ç¬¬ä¸€ä¸ªè¿‡æ»¤æ¡ä»¶
  const roadwayFilter1 = {
    name: "roadway",
    value: "ZJ1",
    displayType: "like",
  };
  // ç¬¬äºŒä¸ªè¿‡æ»¤æ¡ä»¶
  const roadwayFilter2 = {
    name: "roadway",
    value: "FJ1",
    displayType: "like",
  };
  if (!param.wheres) {
    param.wheres = [];
  }
  // å°†ä¸¤ä¸ªè¿‡æ»¤æ¡ä»¶æ·»åŠ åˆ°æŸ¥è¯¢å‚æ•°ä¸­
  param.wheres.push(roadwayFilter1);
  param.wheres.push(roadwayFilter2);
  return true;
},
    searchAfter(result) {
      //查询后,result返回的查询数据,可以在显示到表格前处理表格的值
      return true;
    },
    addBefore(formData) {
      //新建保存前formData为对象,包括明细表,可以给给表单设置值,自己输出看formData的值
      return true;
    },
    updateBefore(formData) {
      //编辑保存前formData为对象,包括明细表、删除行的Id
      return true;
    },
    rowClick({ row, column, event }) {
      //查询界面点击行事件
      this.$refs.table.$refs.table.toggleRowSelection(row); //单击行时选中当前行;
    },
    modelOpenAfter(row) {
      //点击编辑、新建按钮弹出框后,可以在此处写逻辑,如,从后台获取数据
      //(1)判断是编辑还是新建操作: this.currentAction=='Add';
      //(2)给弹出框设置默认值
      //(3)this.editFormFields.字段='xxx';
      //如果需要给下拉框设置默认值,请遍历this.editFormOptions找到字段配置对应data属性的key值
      //看不懂就把输出看:console.log(this.editFormOptions)
    },
  },
};
export default extension;
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js
@@ -95,7 +95,17 @@
    path: '/task_hty',
    name: 'task_hty',
    component: () => import('@/views/taskinfo/task_hty.vue')
  }, {
  },{
    path: '/task_agv',
    name: 'task_agv',
    component: () => import('@/views/taskinfo/task_agv.vue')
  },
  {
    path: '/groupPalle',
    name: 'groupPalle',
    component: () => import('@/views/stock/groupPalle.vue')
  },
  {
    path: '/stockView',
    name: 'stockView',
    component: () => import('@/views/stock/stockView.vue')
Code/WMS/WIDESEA_WMSClient/src/views/stock/groupPalle.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,487 @@
<template>
  <view-grid
    ref="grid"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
    :editFormOptions="editFormOptions"
    :searchFormFields="searchFormFields"
    :searchFormOptions="searchFormOptions"
    :table="table"
    :tableExpand="tableExpand"
    :extend="extend"
  >
  </view-grid>
</template>
<script>
import extend from "@/extension/stock/groupPalle.jsx";
import {
  defineComponent,
  getCurrentInstance,
  h,
  reactive,
  ref,
  resolveComponent,
} from "vue";
const TEXT = {
  pageName: "库存信息",
  palletCode: "托盘编号",
  stockStatus: "库存状态",
  locationCode: "货位编号",
  warehouse: "仓库",
  creator: "创建人",
  createDate: "创建时间",
  modifier: "修改人",
  modifyDate: "修改时间",
  detailName: "库存明细",
  materielName: "物料名称",
  serialNumber: "电芯码",
  stockQuantity: "库存数量",
  status: "状态",
  inboundOrderRowNo: "通道号",
  detailLoading: "库存明细加载中...",
  detailLoadFailed: "库存明细加载失败",
  detailEmpty: "当前库存头暂无明细数据",
  expandPrefix: "托盘:",
  expandMiddle: " / ",
  expandLocation: "货位:",
};
export default defineComponent({
  setup() {
    const { proxy } = getCurrentInstance();
    const ElTable = resolveComponent("el-table");
    const ElTableColumn = resolveComponent("el-table-column");
    const table = ref({
      key: "id",
      footer: "Foots",
      cnName: TEXT.pageName,
      name: "stockInfo",
      url: "/StockInfo/",
      sortName: "id",
    });
    const editFormFields = ref({
      palletCode: "",
      locationCode: "",
    });
    const editFormOptions = ref([
      [
        { field: "palletCode", title: TEXT.palletCode, type: "string" },
        { field: "locationCode", title: TEXT.locationCode, type: "string" },
      ],
    ]);
    const searchFormFields = ref({
      palletCode: "",
      locationCode: "",
    });
    const searchFormOptions = ref([
      [
        { title: TEXT.palletCode, field: "palletCode", type: "like" },
        { title: TEXT.locationCode, field: "locationCode", type: "like" },
      ],
    ]);
    const columns = ref([
      {
        field: "id",
        title: "Id",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "palletCode",
        title: TEXT.palletCode,
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "stockStatus",
        title: TEXT.stockStatus,
        type: "int",
        width: 120,
        align: "left",
        bind: { key: "stockStatusEmun", data: [] },
      },
    //   {
    //     field: "locationCode",
    //     title: TEXT.locationCode,
    //     type: "string",
    //     width: 150,
    //     align: "left",
    //   },
      {
        field: "warehouseId",
        title: TEXT.warehouse,
        type: "select",
        width: 100,
        align: "left",
        bind: { key: "warehouseEnum", data: [] },
      },
      {
        field: "creater",
        title: TEXT.creator,
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "createDate",
        title: TEXT.createDate,
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "modifier",
        title: TEXT.modifier,
        type: "string",
        width: 100,
        align: "left",
        hidden: true,
      },
      {
        field: "modifyDate",
        title: TEXT.modifyDate,
        type: "datetime",
        width: 160,
        align: "left",
        hidden: true,
      },
    ]);
    const detail = ref({
      cnName: "#detailCnName",
      table: "",
      columns: [],
      sortName: "",
    });
    const detailState = reactive({
      rowsMap: {},
      loadingMap: {},
      errorMap: {},
    });
    const stockStatusOptions = ref([]);
    const detailColumns = [
      { field: "materielName", title: TEXT.materielName, minWidth: 160 },
      { field: "serialNumber", title: TEXT.serialNumber, minWidth: 160 },
      { field: "stockQuantity", title: TEXT.stockQuantity, minWidth: 120 },
      { field: "status", title: TEXT.status, minWidth: 120 },
      { field: "inboundOrderRowNo", title: TEXT.inboundOrderRowNo, minWidth: 120 },
    ];
    const normalizeValue = (value) => {
      return value === null || value === undefined || value === "" ? "--" : value;
    };
    const formatStatusText = (value) => {
      const matched = stockStatusOptions.value.find((item) => `${item.key}` === `${value}`);
      return matched ? matched.value || matched.label : normalizeValue(value);
    };
    const getDetailRows = (stockId) => {
      return detailState.rowsMap[stockId] || [];
    };
    const loadDetailRows = async (row) => {
      if (!row || !row.id || detailState.loadingMap[row.id]) {
        return;
      }
      if (detailState.rowsMap[row.id]) {
        return;
      }
      detailState.loadingMap[row.id] = true;
      detailState.errorMap[row.id] = "";
      try {
        const result = await proxy.http.post("/api/StockInfoDetail/getPageData", {
          page: 1,
          rows: 200,
          sort: "id",
          order: "asc",
          wheres: JSON.stringify([
            {
              name: "stockId",
              value: String(row.id),
              displayType: "int",
            },
          ]),
        });
        detailState.rowsMap[row.id] = (result && result.rows) || [];
      } catch (error) {
        detailState.rowsMap[row.id] = null;
        detailState.errorMap[row.id] = error?.message || TEXT.detailLoadFailed;
      } finally {
        detailState.loadingMap[row.id] = false;
      }
    };
    const loadStockStatusOptions = async () => {
      try {
        const result = await proxy.http.post("/api/Sys_Dictionary/GetVueDictionary", ["stockStatusEmun"]);
        const matched = (result || []).find((item) => item.dicNo === "stockStatusEmun");
        stockStatusOptions.value = matched ? matched.data || [] : [];
      } catch (error) {
        stockStatusOptions.value = [];
      }
    };
    loadStockStatusOptions();
    const renderStatus = (row) => {
      if (detailState.loadingMap[row.id]) {
        return h("div", { class: "stock-detail-status" }, TEXT.detailLoading);
      }
      if (detailState.errorMap[row.id]) {
        return h(
          "div",
          { class: "stock-detail-status stock-detail-status--error" },
          detailState.errorMap[row.id]
        );
      }
      return null;
    };
    const renderDetailTable = (row) => {
      const statusNode = renderStatus(row);
      if (statusNode) {
        return statusNode;
      }
      const rows = getDetailRows(row.id);
      if (!rows.length) {
        return h("div", { class: "stock-detail-status" }, TEXT.detailEmpty);
      }
      return h("div", { class: "stock-detail-table-wrapper" }, [
        h("div", { class: "stock-detail-toolbar" }, [
          h("div", { class: "stock-detail-toolbar__left" }, TEXT.detailName),
          h("div", { class: "stock-detail-toolbar__right" }, [
            h("span", { class: "stock-detail-count" }, `${rows.length} æ¡`),
          ]),
        ]),
        h(
          ElTable,
          {
            data: rows,
            border: true,
            stripe: true,
            size: "small",
            class: "stock-detail-el-table",
            maxHeight: 420,
            emptyText: TEXT.detailEmpty,
          },
          () =>
            detailColumns.map((column) =>
              h(ElTableColumn, {
                key: column.field,
                prop: column.field,
                label: column.title,
                minWidth: column.minWidth,
                showOverflowTooltip: true,
                formatter: (detailRow) =>
                  column.field === "status"
                    ? formatStatusText(detailRow[column.field])
                    : normalizeValue(detailRow[column.field]),
              })
            )
        ),
      ]);
    };
    const tableExpand = ref({
      width: 55,
      onChange(row, expandedRows) {
        const isExpanded = expandedRows.some((item) => item.id === row.id);
        if (isExpanded) {
          loadDetailRows(row);
        }
      },
      render(render, { row }) {
        return render("div", { class: "stock-detail-panel" }, [
          render("div", { class: "stock-detail-header" }, [
            render("div", { class: "stock-detail-header__main" }, [
              render("div", { class: "stock-detail-title" }, TEXT.detailName),
              render(
                "div",
                { class: "stock-detail-subtitle" },
                `${TEXT.expandPrefix}${normalizeValue(row.palletCode)}${TEXT.expandMiddle}${TEXT.expandLocation}${normalizeValue(row.locationCode)}`
              ),
            ]),
            // render("div", { class: "stock-detail-tags" }, [
            //   render("span", { class: "stock-detail-tag" }, normalizeValue(row.palletCode)),
            //   render(
            //     "span",
            //     { class: "stock-detail-tag stock-detail-tag--muted" },
            //     normalizeValue(row.locationCode)
            //   ),
            // ]),
          ]),
          renderDetailTable(row),
        ]);
      },
    });
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
      tableExpand,
    };
  },
});
</script>
<style scoped>
.stock-detail-panel {
  margin: 4px 8px 12px;
  padding: 14px 16px 16px;
  background: linear-gradient(180deg, #ffffff 0%, #fafbfc 100%);
  border: 1px solid #e8edf3;
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.stock-detail-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid #edf1f5;
}
.stock-detail-header__main {
  min-width: 0;
}
.stock-detail-title {
  margin-bottom: 4px;
  font-size: 15px;
  font-weight: 700;
  color: #303133;
}
.stock-detail-subtitle {
  font-size: 13px;
  color: #606266;
  line-height: 1.6;
}
.stock-detail-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}
.stock-detail-tag {
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 10px;
  color: #1f5eff;
  background: #edf4ff;
  border: 1px solid #d8e6ff;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
.stock-detail-tag--muted {
  color: #4e5969;
  background: #f4f6f8;
  border-color: #e5e9ef;
}
.stock-detail-status {
  padding: 14px 12px;
  color: #606266;
  background: #f8fafc;
  border: 1px dashed #d9e2ec;
  border-radius: 8px;
}
.stock-detail-status--error {
  color: #f56c6c;
  background: #fef0f0;
  border-color: #fde2e2;
}
.stock-detail-table-wrapper {
  overflow-x: auto;
  border: 1px solid #ebeef5;
  border-radius: 8px;
  background: #fff;
}
.stock-detail-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: #f8fafc;
  border-bottom: 1px solid #edf1f5;
}
.stock-detail-toolbar__left {
  font-size: 13px;
  font-weight: 600;
  color: #303133;
}
.stock-detail-count {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  color: #606266;
  background: #fff;
  border: 1px solid #e5e9ef;
  border-radius: 999px;
  font-size: 12px;
}
:deep(.stock-detail-el-table) {
  border-top: none;
}
:deep(.stock-detail-el-table .el-table__inner-wrapper::before) {
  display: none;
}
:deep(.stock-detail-el-table th.el-table__cell) {
  background: #f5f7fa;
  color: #303133;
  font-weight: 600;
}
:deep(.stock-detail-el-table td.el-table__cell) {
  color: #606266;
}
:deep(.stock-detail-el-table .el-table__body tr:hover > td.el-table__cell) {
  background: #f0f7ff;
}
</style>
Code/WMS/WIDESEA_WMSClient/src/views/system/Mes_Log.vue
@@ -81,7 +81,19 @@
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      { field: "errorMessage", title: "错误信息", width: 250 },
      {
        field: "errorMessage",
        title: "错误信息",
        width: 250,
        formatter: (row) => {
          if (!row.responseJson) return '-';
          const preview = row.responseJson.length > 50
            ? row.responseJson.substring(0, 50) + '...'
            : row.responseJson;
          return `<span style="color: #409EFF; cursor: pointer;">${preview}</span>`;
        }
      },
      { field: "elapsedMs", title: "耗时(ms)", width: 100, sortable: true },
      { field: "createDate", title: "调用时间", width: 160, sortable: true },
      { field: "creator", title: "操作人", width: 100 }
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_agv.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,231 @@
<template>
  <view-grid
    ref="grid"
    :columns="columns"
    :detail="detail"
    :editFormFields="editFormFields"
    :editFormOptions="editFormOptions"
    :searchFormFields="searchFormFields"
    :searchFormOptions="searchFormOptions"
    :table="table"
    :extend="extend"
  >
  </view-grid>
</template>
    <script>
import extend from "@/extension/taskinfo/agvTask.jsx";
import { ref, defineComponent } from "vue";
export default defineComponent({
  setup() {
    const table = ref({
      key: "taskId",
      footer: "Foots",
      cnName: "任务信息",
      name: "task",
      url: "/Task/",
      sortName: "CreateDate",
    });
    const editFormFields = ref({});
    const editFormOptions = ref([]);
    const searchFormFields = ref({
      taskNum: "",
      palletCode: "",
      roadway: "",
      taskStatus: "",
      taskType: "",
      sourceAddress: "",
      targetAddress: "",
      currentAddress: "",
      nextAddress: "",
      creater: "",
      createDate: "",
    });
    const searchFormOptions = ref([
      [
        { title: "任务号", field: "taskNum", type: "int" },
        { title: "托盘编号", field: "palletCode", type: "like" },
        { title: "创建人", field: "creater", type: "like" },
      ],
      [
        { title: "任务类型",field: "taskType",type: "selectList",dataKey: "taskTypeEnum",data: [],},
        { title: "任务状态",field: "taskStatus",type: "selectList",dataKey: "taskStatusEnum",data: [],},
        { title: "巷道号", field: "roadway", type: "like" },
      ],
      [
        { title: "起始地址", field: "sourceAddress", type: "like" },
        { title: "目标地址", field: "targetAddress", type: "like" },
        { title: "创建时间", field: "createDate", type: "datetime" },
      ],
    ]);
    const columns = ref([
      {
        field: "taskId",
        title: "taskId",
        type: "int",
        width: 90,
        hidden: true,
        readonly: true,
        require: true,
        align: "left",
      },
      {
        field: "taskNum",
        title: "任务号",
        type: "int",
        width: 120,
        align: "left",
      },
      {
        field: "palletCode",
        title: "托盘编号",
        type: "string",
        width: 160,
        align: "left",
      },
      {
        field: "roadway",
        title: "巷道号",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "taskType",
        title: "任务类型",
        type: "int",
        width: 120,
        align: "left",
        bind: { key: "taskTypeEnum", data: [] },
      },
      {
        field: "taskStatus",
        title: "任务状态",
        type: "int",
        width: 150,
        align: "left",
        bind: { key: "taskStatusEnum", data: [] },
      },
      {
        field: "sourceAddress",
        title: "起始地址",
        type: "int",
        width: 120,
        align: "left",
      },
      {
        field: "targetAddress",
        title: "目标地址",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "currentAddress",
        title: "当前位置",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "nextAddress",
        title: "下一位置",
        type: "string",
        width: 120,
        align: "left",
      },
      {
        field: "exceptionMessage",
        title: "异常信息",
        type: "string",
        width: 90,
        align: "left",
      },
      // {
      //   field: "grade",
      //   title: "优先级",
      //   type: "int",
      //   width: 80,
      //   align: "left",
      // },
      {
        field: "depth",
        title: "深度",
        type: "int",
        width: 80,
        align: "left",
        bind: { key: "locationDepth", data: [] },
      },
      {
        field: "dispatchertime",
        title: "任务下发时间",
        type: "datetime",
        width: 160,
        align: "left",
        // hidden:true,
      },
      {
        field: "wMSId",
        title: "WMS任务主键",
        type: "int",
        width: 120,
        align: "left",
        hidden: true,
      },
      {
        field: "creater",
        title: "创建人",
        type: "string",
        width: 90,
        align: "left",
      },
      {
        field: "createDate",
        title: "创建时间",
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "modifier",
        title: "修改人",
        type: "string",
        width: 100,
        align: "left",
      },
      {
        field: "modifyDate",
        title: "修改时间",
        type: "datetime",
        width: 160,
        align: "left",
      },
      {
        field: "remark",
        title: "备注",
        type: "string",
        width: 100,
        align: "left",
        hidden: true,
      },
    ]);
    const detail = ref({
      cnName: "#detailCnName",
      table: "",
      columns: [],
      sortName: "",
    });
    return {
      table,
      extend,
      editFormFields,
      editFormOptions,
      searchFormFields,
      searchFormOptions,
      columns,
      detail,
    };
  },
});
</script>
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue
@@ -89,6 +89,12 @@
        type: "string",
        width: 120,
        align: "left",
      },{
        field: "operateType",
        title: "操作类型",
        type: "string",
        width: 120,
        align: "left"
      },
      {
        field: "taskType",
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Filter/ApiAuthorizeFilter.cs
@@ -84,7 +84,7 @@
                int ExpMinutes = AppSettings.Get("ExpMinutes").ObjToInt();
                if ((expDate.GetValueOrDefault() - DateTime.Now).TotalMinutes < ExpMinutes / 3 && context.HttpContext.Request.Path != ReplaceTokenPath)
                {
                    context.HttpContext.Response.Headers.Add("widesea_exp", "1");
                    context.HttpContext.Response.Headers.Append("widesea_exp", "1");
                }
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Middlewares/HttpRequestMiddleware.cs
@@ -18,7 +18,7 @@
        public async Task InvokeAsync(HttpContext context)
        {
            context.Response.Headers.Add("Access-Control-Expose-Headers", "widesea_exp");
            context.Response.Headers.Append("Access-Control-Expose-Headers", "widesea_exp");
            await _next(context);
        }
    }
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/MES/MesApiLogDto.cs
@@ -11,6 +11,11 @@
        public string ApiType { get; set; }
        /// <summary>
        /// æ‰˜ç›˜å·
        /// </summary>
        public string PalletCode { get; set; }
        /// <summary>
        /// è¯·æ±‚JSON
        /// </summary>
        public string RequestJson { get; set; }
Code/WMS/WIDESEA_WMSServer/WIDESEA_IBasicService/IMesUploadHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
namespace WIDESEA_IBasicService
{
    /// <summary>
    /// MES异步上传辅助服务 - å°è£…Task.Run + çŠ¶æ€æ›´æ–° + æ—¥å¿—记录的统一模式
    /// </summary>
    public interface IMesUploadHelper : IDependency
    {
        /// <summary>
        /// ä»¥fire-and-forget方式异步执行MES调用,自动更新上传状态并记录日志
        /// </summary>
        /// <param name="palletCode">托盘号</param>
        /// <param name="successStatus">成功时的状态枚举值(奇数=成功,偶数=失败)</param>
        /// <param name="apiType">MES接口类型名称</param>
        /// <param name="requestJson">请求JSON(用于日志记录)</param>
        /// <param name="mesCall">MES调用委托,返回(是否成功, å“åº”JSON, é”™è¯¯æ¶ˆæ¯)</param>
        /// <param name="creator">操作人</param>
        void FireAndForget(
            string palletCode,
            MesUploadStatusEnum successStatus,
            string apiType,
            string requestJson,
            Func<(bool isSuccess, string responseJson, string errorMessage)> mesCall,
            string creator = "System");
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/MesUploadHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,93 @@
using System.Diagnostics;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_DTO.MES;
using WIDESEA_IStockService;
using WIDESEA_IBasicService;
namespace WIDESEA_StockService
{
    /// <summary>
    /// MES异步上传辅助服务实现
    /// </summary>
    public class MesUploadHelper : IMesUploadHelper
    {
        private readonly IStockInfoService _stockInfoService;
        private readonly IMesLogService _mesLogService;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="stockInfoService">库存信息服务</param>
        /// <param name="mesLogService">MES日志服务</param>
        public MesUploadHelper(IStockInfoService stockInfoService, IMesLogService mesLogService)
        {
            _stockInfoService = stockInfoService;
            _mesLogService = mesLogService;
        }
        /// <summary>
        /// ä»¥fire-and-forget方式异步执行MES调用,自动更新上传状态并记录日志
        /// </summary>
        public void FireAndForget(
            string palletCode,
            MesUploadStatusEnum successStatus,
            string apiType,
            string requestJson,
            Func<(bool isSuccess, string responseJson, string errorMessage)> mesCall,
            string creator = "System")
        {
            _ = Task.Run(async () =>
            {
                var stopwatch = Stopwatch.StartNew();
                try
                {
                    var (isSuccess, responseJson, errorMessage) = mesCall();
                    stopwatch.Stop();
                    // å¥‡æ•°=成功,偶数=失败
                    int status = isSuccess ? (int)successStatus : (int)successStatus + 1;
                    await _stockInfoService.UpdateMesUploadStatusAsync(palletCode, status);
                    await LogAsync(palletCode, apiType, requestJson, responseJson,
                        stopwatch.ElapsedMilliseconds, isSuccess, errorMessage, creator);
                }
                catch (Exception ex)
                {
                    stopwatch.Stop();
                    int status = (int)successStatus + 1;
                    await _stockInfoService.UpdateMesUploadStatusAsync(palletCode, status);
                    await LogAsync(palletCode, apiType, requestJson, "",
                        stopwatch.ElapsedMilliseconds, false, ex.Message, creator);
                }
            });
        }
        /// <summary>
        /// è®°å½•MES接口调用日志
        /// </summary>
        private async Task LogAsync(string palletCode, string apiType, string requestJson,
            string responseJson, long elapsedMs, bool isSuccess, string errorMessage, string creator)
        {
            try
            {
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    PalletCode = palletCode,
                    ApiType = apiType,
                    RequestJson = requestJson,
                    ResponseJson = responseJson,
                    IsSuccess = isSuccess,
                    ErrorMessage = errorMessage,
                    ElapsedMs = (int)elapsedMs,
                    Creator = creator
                });
            }
            catch
            {
                // æ—¥å¿—记录失败不影响主流程
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -391,7 +391,7 @@
        {
            try
            {
                var stockInfo = await BaseDal.QueryDataFirstAsync(x => x.PalletCode == palletCode);
                var stockInfo = await BaseDal.QueryFirstAsync(x => x.PalletCode == palletCode);
                if (stockInfo == null)
                    return false;
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -1,6 +1,5 @@
using Newtonsoft.Json;
using SqlSugar;
using System.Diagnostics;
using WIDESEA_Common.Constants;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
@@ -54,6 +53,7 @@
        public IMesService _mesService { get; }
        private readonly IMesLogService _mesLogService;
        private readonly IMesUploadHelper _mesUploadHelper;
        /// <summary>
        /// æž„造函数
@@ -62,6 +62,7 @@
        /// <param name="stockInfoService">库存信息服务</param>
        /// <param name="stockInfoDetail_HtyService">库存明细历史服务</param>
        /// <param name="stockInfo_HtyService">库存历史服务</param>
        /// <param name="mesUploadHelper">MES异步上传辅助服务</param>
        public StockService(
            IStockInfoDetailService stockInfoDetailService,
            IStockInfoService stockInfoService,
@@ -70,7 +71,8 @@
            IMesService mesService,
            IWarehouseService warehouseService,
            ISqlSugarClient sqlSugarClient,
            IMesLogService mesLogService)
            IMesLogService mesLogService,
            IMesUploadHelper mesUploadHelper)
        {
            StockInfoDetailService = stockInfoDetailService;
            StockInfoService = stockInfoService;
@@ -80,6 +82,7 @@
            _warehouseService = warehouseService;
            SqlSugarClient = sqlSugarClient;
            _mesLogService = mesLogService;
            _mesUploadHelper = mesUploadHelper;
        }
        /// <summary>
@@ -435,7 +438,7 @@
                    }
                }
                // 3. è°ƒç”¨MES解绑
                // 3. Fire-and-forget异步调用MES解绑
                var unbindRequest = new UnBindContainerRequest
                {
                    EquipmentCode = equipmentCode,
@@ -444,13 +447,25 @@
                    ContainCode = palletCode,
                    SfcList = sfcList
                };
                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 ?? "未知错误"}");
                }
                string requestJson = unbindRequest.ToJson();
                var localToken = token;
                _mesUploadHelper.FireAndForget(
                    palletCode,
                    MesUploadStatusEnum.拆盘上传成功,
                    "UnBindContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(localToken)
                            ? _mesService.UnBindContainer(unbindRequest)
                            : _mesService.UnBindContainer(unbindRequest, localToken);
                        return (
                            result?.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    });
                // 4. åˆ é™¤ä¸´æ—¶è¡¨è®°å½•
                await SqlSugarClient.Deleteable<Dt_SplitTemp>().Where(t => t.PalletCode == palletCode).ExecuteCommandAsync();
@@ -472,7 +487,6 @@
        public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName)
        {
            WebResponseContent content = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
                if (string.IsNullOrWhiteSpace(palletCode))
@@ -518,24 +532,25 @@
                    }).ToList()
                };
                string requestJson = bindRequest.ToJson();
                var bindResult = string.IsNullOrWhiteSpace(token)
                    ? _mesService.BindContainer(bindRequest)
                    : _mesService.BindContainer(bindRequest, token);
                stopwatch.Stop();
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "BindContainer",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(bindResult),
                    IsSuccess = bindResult.IsSuccess,
                    ErrorMessage = bindResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = "systeam"
                });
                if (bindResult == null || bindResult.Data == null || !bindResult.Data.IsSuccess)
                {
                    return content.Error($"MES绑定失败: {bindResult?.Data?.Msg ?? bindResult?.ErrorMessage ?? "未知错误"}");
                }
                var localToken = token;
                // 3. Fire-and-forget异步调用MES绑定
                _mesUploadHelper.FireAndForget(
                    palletCode,
                    MesUploadStatusEnum.组盘上传成功,
                    "BindContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(localToken)
                            ? _mesService.BindContainer(bindRequest)
                            : _mesService.BindContainer(bindRequest, localToken);
                        return (
                            result?.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    });
                return content.OK("批量组盘确认成功");
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -17,10 +17,7 @@
using WIDESEA_Core.Helper;
using WIDESEA_DTO.GradingMachine;
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Stock;
using WIDESEA_DTO.Task;
using Newtonsoft.Json;
using System.Diagnostics;
using WIDESEA_IBasicService;
using WIDESEA_IRecordService;
using WIDESEA_IStockService;
@@ -44,6 +41,7 @@
        private readonly IRecordService _recordService;
        private readonly IMESDeviceConfigService _mesDeviceConfigService;
        private readonly IMesLogService _mesLogService;
        private readonly IMesUploadHelper _mesUploadHelper;
        public IRepository<Dt_Task> Repository => BaseDal;
@@ -70,7 +68,8 @@
            IUnitOfWorkManage unitOfWorkManage,
            IRecordService recordService,
            IMESDeviceConfigService mesDeviceConfigService,
            IMesLogService mesLogService) : base(BaseDal)
            IMesLogService mesLogService,
            IMesUploadHelper mesUploadHelper) : base(BaseDal)
        {
            _mapper = mapper;
            _stockInfoService = stockInfoService;
@@ -85,6 +84,7 @@
            _recordService = recordService;
            _mesDeviceConfigService = mesDeviceConfigService;
            _mesLogService = mesLogService;
            _mesUploadHelper = mesUploadHelper;
        }
        /// <summary>
@@ -200,80 +200,5 @@
            return DetermineTargetAddress(roadway, addressMap);
        }
        /// <summary>
        /// å¼‚步执行MES上传 - ä¸é˜»å¡žä¸»ä¸šåŠ¡é€»è¾‘
        /// </summary>
        /// <param name="palletCode">托盘号</param>
        /// <param name="successStatus">成功时的状态枚举值(奇数)</param>
        /// <param name="uploadFunc">具体的MES调用函数</param>
        private async Task MesUploadAsync(string palletCode, MesUploadStatusEnum successStatus, Func<Task<HttpResponseResult<MesResponse>>> uploadFunc)
        {
            var stopwatch = Stopwatch.StartNew();
            string requestJson = "";
            string responseJson = "";
            bool isSuccess = false;
            string errorMessage = "";
            try
            {
                // è°ƒç”¨MES
                var result = await uploadFunc();
                stopwatch.Stop();
                isSuccess = result?.Data?.IsSuccess ?? false;
                errorMessage = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误";
                responseJson = JsonConvert.SerializeObject(result);
                // æ ¹æ®æˆåŠŸ/失败决定状态值:奇数=成功,偶数=失败
                var uploadStatus = isSuccess ? (int)successStatus : (int)successStatus + 1;
                // æ›´æ–°åº“存表状态(不等待)
                _ = _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus);
                // è®°å½•MES日志
                await LogMesCallAsync(palletCode, successStatus.ToString(), requestJson, responseJson,
                    stopwatch.ElapsedMilliseconds, isSuccess ? "成功" : "失败", errorMessage);
            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                errorMessage = ex.Message;
                // æ›´æ–°çŠ¶æ€ä¸ºå¤±è´¥ï¼ˆsuccessStatus+1 å³ä¸ºå¤±è´¥çŠ¶æ€ï¼‰
                var uploadStatus = (int)successStatus + 1;
                _ = _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus);
                // è®°å½•异常日志
                await LogMesCallAsync(palletCode, successStatus.ToString(), requestJson, responseJson,
                    stopwatch.ElapsedMilliseconds, "失败", errorMessage);
            }
        }
        /// <summary>
        /// è®°å½•MES接口调用日志
        /// </summary>
        private async Task LogMesCallAsync(string palletCode, string apiType, string requestJson,
            string responseJson, long durationMs, string status, string errorMessage)
        {
            try
            {
                var mesLog = new MesApiLogDto
                {
                    PalletCode = palletCode,
                    ApiType = apiType,
                    RequestJson = requestJson,
                    ResponseJson = responseJson,
                    IsSuccess = status == "成功",
                    ErrorMessage = errorMessage,
                    ElapsedMs = (int)durationMs,
                    Creator = "System"
                };
                await _mesLogService.LogAsync(mesLog);
            }
            catch
            {
                // æ—¥å¿—记录失败不影响主流程
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Newtonsoft.Json;
using System.Diagnostics;
using WIDESEA_Common.Constants;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
@@ -122,7 +120,6 @@
        /// </summary>
        public async Task<WebResponseContent> InboundFinishTaskAsync(CreateTaskDto taskDto)
        {
            var stopwatch = Stopwatch.StartNew();
            try
            {
                var task = await BaseDal.QueryFirstAsync(s => s.PalletCode == taskDto.PalletCode);
@@ -153,8 +150,6 @@
                }
                else
                {
                    // åˆ¤æ–­æ˜¯ä¸æ˜¯æžå·åº“任务
                    if (taskDto.WarehouseId == (int)WarehouseEnum.FJ1 || taskDto.WarehouseId == (int)WarehouseEnum.ZJ1)
                    {
@@ -179,41 +174,42 @@
                            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;
                        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 = equipmentCode,
                        //    ResourceCode = resourceCode,
                        //    LocalTime = DateTime.Now,
                        //    ContainerCode = taskDto.PalletCode
                        //};
                        //string requestJson = inboundRequest.ToJson();
                        //var inboundResult = string.IsNullOrWhiteSpace(token)
                        //    ? _mesService.InboundInContainer(inboundRequest)
                        //    : _mesService.InboundInContainer(inboundRequest, token);
                        //stopwatch.Stop();
                        //await _mesLogService.LogAsync(new MesApiLogDto
                        //{
                        //    ApiType = "InboundInContainer",
                        //    RequestJson = requestJson,
                        //    ResponseJson = JsonConvert.SerializeObject(inboundResult),
                        //    IsSuccess = inboundResult.IsSuccess,
                        //    ErrorMessage = inboundResult.ErrorMessage,
                        //    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                        //    Creator = "systeam"
                        //});
                        //if (inboundResult == null || inboundResult.Data == null || !inboundResult.Data.IsSuccess)
                        //{
                        //    return content.Error($"任务完成失败:MES进站失败: {inboundResult?.Data?.Msg ?? inboundResult?.ErrorMessage ?? "未知错误"}");
                        //}
                        // å¼‚步调用MES托盘进站,不阻塞主逻辑
                        var inboundRequest = new InboundInContainerRequest
                        {
                            EquipmentCode = equipmentCode,
                            ResourceCode = resourceCode,
                            LocalTime = DateTime.Now,
                            ContainerCode = taskDto.PalletCode
                        };
                        string requestJson = inboundRequest.ToJson();
                        var palletCode = taskDto.PalletCode;
                        _mesUploadHelper.FireAndForget(
                            palletCode,
                            MesUploadStatusEnum.进站上传成功,
                            "InboundInContainer",
                            requestJson,
                            () =>
                            {
                                var result = string.IsNullOrWhiteSpace(token)
                                    ? _mesService.InboundInContainer(inboundRequest)
                                    : _mesService.InboundInContainer(inboundRequest, token);
                                return (
                                    result?.Data?.IsSuccess ?? false,
                                    JsonConvert.SerializeObject(result),
                                    result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                                );
                            });
                        return await CompleteTaskAsync(task, "入库完成");
                    });
                }
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs
@@ -1,4 +1,3 @@
using System.Diagnostics;
using WIDESEA_Common.Constants;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
@@ -60,7 +59,6 @@
        /// </summary>
        public async Task<WebResponseContent> OutboundFinishTaskAsync(CreateTaskDto taskDto)
        {
            var stopwatch = Stopwatch.StartNew();
            try
            {
                var task = await BaseDal.QueryFirstAsync(s => s.PalletCode == taskDto.PalletCode);
@@ -138,25 +136,26 @@
                        LocalTime = DateTime.Now,
                        ContainerCode = taskDto.PalletCode
                    };
                    string palletCode = taskDto.PalletCode;
                    string requestJson = outboundRequest.ToJson();
                    var outboundResult = string.IsNullOrWhiteSpace(token)
                        ? _mesService.OutboundInContainer(outboundRequest)
                        : _mesService.OutboundInContainer(outboundRequest, token);
                    stopwatch.Stop();
                    await _mesLogService.LogAsync(new MesApiLogDto
                    {
                        ApiType = "UnbindContainer",
                        RequestJson = requestJson,
                        ResponseJson = System.Text.Json.JsonSerializer.Serialize(outboundResult),
                        IsSuccess = outboundResult.IsSuccess,
                        ErrorMessage = outboundResult.ErrorMessage,
                        ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                        Creator = "systeam"
                    });
                    if (outboundResult == null || outboundResult.Data == null || !outboundResult.Data.IsSuccess)
                    {
                        return content.Error($"出库完成失败:MES出站失败: {outboundResult?.Data?.Msg ?? outboundResult?.ErrorMessage ?? "未知错误"}");
                    }
                    // Fire-and-forget: å¼‚步执行MES出站,不阻塞主业务逻辑
                    _mesUploadHelper.FireAndForget(
                        palletCode,
                        MesUploadStatusEnum.出站上传成功,
                        "OutboundInContainer",
                        requestJson,
                        () =>
                        {
                            var result = string.IsNullOrWhiteSpace(token)
                                ? _mesService.OutboundInContainer(outboundRequest)
                                : _mesService.OutboundInContainer(outboundRequest, token);
                            return (
                                result?.Data?.IsSuccess ?? false,
                                Newtonsoft.Json.JsonConvert.SerializeObject(result),
                                result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                            );
                        });
                    var completeResult = await CompleteTaskAsync(task, "出库完成");
                    if (!completeResult.Status)
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs
@@ -9,8 +9,8 @@
using WIDESEA_IBasicService;
using WIDESEA_ISystemService;
using WIDESEA_Model.Models;
using WIDESEA_Common.Constants;
using WIDESEA_Common.StockEnum;
using System.Diagnostics;
namespace WIDESEA_WMSServer.Controllers.Stock
{
@@ -21,19 +21,22 @@
    [ApiController]
    public class StockInfoController : ApiBaseController<IStockInfoService, Dt_StockInfo>
    {
        private readonly IMesLogService _mesLogService;
        private readonly IMesService _mesService;
        private readonly IMESDeviceConfigService _mesDeviceConfigService;
        private readonly ISys_DictionaryService _sysDictionaryService;
        private readonly IMesUploadHelper _mesUploadHelper;
        public StockInfoController(
            IStockInfoService service,
            IMesLogService mesLogService,
            IMesService mesService,
            ISys_DictionaryService sysDictionaryService) : base(service)
            IMESDeviceConfigService mesDeviceConfigService,
            ISys_DictionaryService sysDictionaryService,
            IMesUploadHelper mesUploadHelper) : base(service)
        {
            _mesLogService = mesLogService;
            _mesService = mesService;
            _mesDeviceConfigService = mesDeviceConfigService;
            _sysDictionaryService = sysDictionaryService;
            _mesUploadHelper = mesUploadHelper;
        }
        /// <summary>
@@ -57,7 +60,6 @@
        public async Task<WebResponseContent> InboundInContainer([FromBody] InboundInContainerRequestDto dto)
        {
            var response = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
@@ -81,57 +83,48 @@
                    return response.Error($"当前库存状态不允许进站操作,当前状态:{stockInfo.StockStatus}");
                }
                // 4. æž„造MES请求
                // 4. åŠ¨æ€èŽ·å–MES凭证
                string deviceName = stockInfo.Remark == "GW_1" ? "高温静置1"
                    : stockInfo.Remark == "GW_2" ? "高温静置2"
                    : "常温静置1";
                var mesConfig = _mesDeviceConfigService.GetByDeviceName(deviceName);
                var mesRequest = new InboundInContainerRequest
                {
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    EquipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE,
                    ResourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE,
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode
                };
                string token = mesConfig?.Token;
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                string palletCode = stockInfo.PalletCode;
                // 5. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.InboundInContainer(mesRequest);
                stopwatch.Stop();
                // 5. å¼‚步执行MES调用(fire-and-forget)
                _mesUploadHelper.FireAndForget(
                    palletCode,
                    MesUploadStatusEnum.进站上传成功,
                    "InboundInContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(token)
                            ? _mesService.InboundInContainer(mesRequest)
                            : _mesService.InboundInContainer(mesRequest, token);
                        return (
                            result.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    },
                    App.User.UserName);
                // 6. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "InboundInContainer",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult),
                    IsSuccess = mesResult.IsSuccess,
                    ErrorMessage = mesResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                // 7. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘进站成功");
                }
                else
                {
                    return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}");
                }
                // 6. ç«‹å³è¿”回成功
                return response.OK("托盘进站成功");
            }
            catch (System.Exception ex)
            {
                stopwatch.Stop();
                // è®°å½•错误日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "InboundInContainer",
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                return response.Error($"托盘进站失败: {ex.Message}");
            }
        }
@@ -145,7 +138,6 @@
        public async Task<WebResponseContent> OutboundInContainer([FromBody] OutboundInContainerRequestDto dto)
        {
            var response = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
@@ -175,11 +167,16 @@
                    return response.Error($"当前库存状态不允许出站操作,当前状态:{stockInfo.StockStatus}");
                }
                // 4. æž„造MES请求
                // 4. åŠ¨æ€èŽ·å–MES凭证
                string deviceName = stockInfo.Remark == "GW_1" ? "高温静置1"
                    : stockInfo.Remark == "GW_2" ? "高温静置2"
                    : "常温静置1";
                var mesConfig = _mesDeviceConfigService.GetByDeviceName(deviceName);
                var mesRequest = new OutboundInContainerRequest
                {
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    EquipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE,
                    ResourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE,
                    LocalTime = DateTime.Now,
                    ContainerCode = dto.PalletCode,
                    ParamList = dto.ParamList?.Select(p => new ParamItem
@@ -190,48 +187,34 @@
                    }).ToList()
                };
                string token = mesConfig?.Token;
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                string palletCode = stockInfo.PalletCode;
                // 5. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.OutboundInContainer(mesRequest);
                stopwatch.Stop();
                // 5. å¼‚步执行MES调用(fire-and-forget)
                _mesUploadHelper.FireAndForget(
                    palletCode,
                    MesUploadStatusEnum.出站上传成功,
                    "OutboundInContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(token)
                            ? _mesService.OutboundInContainer(mesRequest)
                            : _mesService.OutboundInContainer(mesRequest, token);
                        return (
                            result?.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    },
                    App.User.UserName);
                // 6. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "OutboundInContainer",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult),
                    IsSuccess = mesResult.IsSuccess,
                    ErrorMessage = mesResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                // 7. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘出站成功");
                }
                else
                {
                    return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}");
                }
                // 6. ç«‹å³è¿”回成功
                return response.OK("托盘出站成功");
            }
            catch (System.Exception ex)
            {
                stopwatch.Stop();
                // è®°å½•错误日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "OutboundInContainer",
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                return response.Error($"托盘出站失败: {ex.Message}");
            }
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs
@@ -1,13 +1,13 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Common.Constants;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.MES;
using WIDESEA_IStockService;
using WIDESEA_IBasicService;
using WIDESEA_IStockService;
using WIDESEA_ISystemService;
using WIDESEA_Model.Models;
using System.Diagnostics;
namespace WIDESEA_WMSServer.Controllers.Stock
{
@@ -18,22 +18,25 @@
    [ApiController]
    public class StockInfoDetailController : ApiBaseController<IStockInfoDetailService, Dt_StockInfoDetail>
    {
        private readonly IMesLogService _mesLogService;
        private readonly IMesService _mesService;
        private readonly ISys_DictionaryService _sysDictionaryService;
        private readonly IStockInfoService _stockInfoService;
        private readonly IMESDeviceConfigService _mesDeviceConfigService;
        private readonly IMesUploadHelper _mesUploadHelper;
        public StockInfoDetailController(
            IStockInfoDetailService service,
            IMesLogService mesLogService,
            IMesService mesService,
            ISys_DictionaryService sysDictionaryService,
            IStockInfoService stockInfoService) : base(service)
            IStockInfoService stockInfoService,
            IMESDeviceConfigService mesDeviceConfigService,
            IMesUploadHelper mesUploadHelper) : base(service)
        {
            _mesLogService = mesLogService;
            _mesService = mesService;
            _sysDictionaryService = sysDictionaryService;
            _stockInfoService = stockInfoService;
            _mesDeviceConfigService = mesDeviceConfigService;
            _mesUploadHelper = mesUploadHelper;
        }
        /// <summary>
@@ -45,81 +48,70 @@
        public async Task<WebResponseContent> BindContainer([FromBody] BindContainerRequestDto dto)
        {
            var response = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
                // 1. å‚数验证
                if (dto.SfcList == null || !dto.SfcList.Any())
                {
                    return response.Error("电芯码列表不能为空");
                }
                //// 1. å‚数验证
                //if (dto.SfcList == null || !dto.SfcList.Any())
                //{
                //    return response.Error("电芯码列表不能为空");
                //}
                // 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许绑定)
                var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetail != null && stockDetail.Status == 99)
                {
                    return response.Error("当前库存明细包含已锁定状态,不允许执行绑定操作");
                }
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                //var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                //if (stockDetail != null && stockDetail.Status == 99)
                //{
                //    return response.Error("当前库存明细包含已锁定状态,不允许执行绑定操作");
                //}
                var stockInfo = await _stockInfoService.Repository.QueryDataNavFirstAsync(x => x.PalletCode == dto.PalletCode);
                // 3. æž„造MES请求 - å°†ç”µèŠ¯åˆ—è¡¨è½¬æ¢ä¸ºContainerSfcItem格式
                // 3. åŠ¨æ€èŽ·å–MES凭证
                var mesConfig = _mesDeviceConfigService.GetByDeviceName("组盘机械手");
                string equipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE;
                string resourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE;
                string token = mesConfig?.Token;
                // 4. æž„造MES请求 - å°†ç”µèŠ¯åˆ—è¡¨è½¬æ¢ä¸ºContainerSfcItem格式
                var mesRequest = new BindContainerRequest
                {
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    ContainerCode = stockInfo.PalletCode,
                    ContainerSfcList = dto.SfcList.Select(sfc => new ContainerSfcItem
                    ContainerSfcList = stockInfo.Details.Select(sfc => new ContainerSfcItem
                    {
                        Sfc = sfc,
                        Location = dto.Location ?? ""
                        Sfc = sfc.SerialNumber,
                        Location = sfc.InboundOrderRowNo.ToString() ?? ""
                    }).ToList(),
                    OperationType = dto.OperationType
                };
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                // 4. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.BindContainer(mesRequest);
                stopwatch.Stop();
                // 5. å¼‚步调用MES接口(fire-and-forget)
                _mesUploadHelper.FireAndForget(
                    stockInfo.PalletCode,
                    MesUploadStatusEnum.组盘上传成功,
                    "BindContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(token)
                            ? _mesService.BindContainer(mesRequest)
                            : _mesService.BindContainer(mesRequest, token);
                        return (
                            result?.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    },
                    App.User.UserName);
                // 5. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "BindContainer",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult),
                    IsSuccess = mesResult.IsSuccess,
                    ErrorMessage = mesResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                // 6. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘电芯绑定成功");
                }
                else
                {
                    return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}");
                }
                // 6. ç«‹å³è¿”回成功响应
                return response.OK("托盘电芯绑定成功");
            }
            catch (System.Exception ex)
            catch (Exception ex)
            {
                stopwatch.Stop();
                // è®°å½•错误日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "BindContainer",
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                return response.Error($"托盘电芯绑定失败: {ex.Message}");
            }
        }
@@ -133,76 +125,65 @@
        public async Task<WebResponseContent> UnbindContainer([FromBody] UnbindContainerRequestDto dto)
        {
            var response = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
                // 1. å‚数验证
                if (dto.SfcList == null || !dto.SfcList.Any())
                {
                    return response.Error("电芯码列表不能为空");
                }
                //if (dto.SfcList == null || !dto.SfcList.Any())
                //{
                //    return response.Error("电芯码列表不能为空");
                //}
                // 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许解绑)
                var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                if (stockDetail != null && stockDetail.Status == 99)
                {
                    return response.Error("当前库存明细包含已锁定状态,不允许执行解绑操作");
                }
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                //// 2. éªŒè¯ç”µèŠ¯çŠ¶æ€ï¼ˆéž'已锁定'状态允许解绑)
                //var stockDetail = await Service.Repository.QueryFirstAsync(x => dto.SfcList.Contains(x.SerialNumber));
                //if (stockDetail != null && stockDetail.Status == 99)
                //{
                //    return response.Error("当前库存明细包含已锁定状态,不允许执行解绑操作");
                //}
                var stockInfo = await _stockInfoService.Repository.QueryDataNavFirstAsync(x => dto.PalletCode == x.PalletCode);
                // 3. æž„造MES请求
                // 3. åŠ¨æ€èŽ·å–MES凭证
                var mesConfig = _mesDeviceConfigService.GetByDeviceName("组盘机械手");
                string equipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE;
                string resourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE;
                string token = mesConfig?.Token;
                // 4. æž„造MES请求
                var mesRequest = new UnBindContainerRequest
                {
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "STK-GROUP-001",
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    ContainCode = stockInfo.PalletCode,
                    SfcList = dto.SfcList
                    SfcList = stockInfo.Details.Select(x => x.SerialNumber).ToList(),
                };
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                // 4. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.UnBindContainer(mesRequest);
                stopwatch.Stop();
                // 5. å¼‚步调用MES接口(fire-and-forget)
                _mesUploadHelper.FireAndForget(
                    stockInfo.PalletCode,
                    MesUploadStatusEnum.拆盘上传成功,
                    "UnbindContainer",
                    requestJson,
                    () =>
                    {
                        var result = string.IsNullOrWhiteSpace(token)
                            ? _mesService.UnBindContainer(mesRequest)
                            : _mesService.UnBindContainer(mesRequest, token);
                        return (
                            result?.Data?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误"
                        );
                    },
                    App.User.UserName);
                // 5. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "UnbindContainer",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult),
                    IsSuccess = mesResult.IsSuccess,
                    ErrorMessage = mesResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                // 6. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("托盘电芯解绑成功");
                }
                else
                {
                    return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}");
                }
                // 6. ç«‹å³è¿”回成功响应
                return response.OK("托盘电芯解绑成功");
            }
            catch (System.Exception ex)
            catch (Exception ex)
            {
                stopwatch.Stop();
                // è®°å½•错误日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "UnbindContainer",
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                return response.Error($"托盘电芯解绑失败: {ex.Message}");
            }
        }
@@ -216,7 +197,6 @@
        public async Task<WebResponseContent> ContainerNgReport([FromBody] ContainerNgReportRequestDto dto)
        {
            var response = new WebResponseContent();
            var stopwatch = Stopwatch.StartNew();
            try
            {
@@ -240,11 +220,16 @@
                }
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => stockDetail.StockId == x.Id);
                // 3. æž„造MES请求 - å°†DTO格式转换为MES请求格式
                // 3. åŠ¨æ€èŽ·å–MES凭证
                var mesConfig = _mesDeviceConfigService.GetByDeviceName("组盘机械手");
                string equipmentCode = mesConfig?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE;
                string resourceCode = mesConfig?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE;
                // 4. æž„造MES请求 - å°†DTO格式转换为MES请求格式
                var mesRequest = new ContainerNgReportRequest
                {
                    EquipmentCode = "STK-GROUP-001",
                    ResourceCode = "RESOURCE-001",
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    ContainerCode = stockInfo.PalletCode,
                    NgSfcList = dto.NgSfcList.Select(ng => new NgSfcItem
@@ -258,46 +243,28 @@
                string requestJson = System.Text.Json.JsonSerializer.Serialize(mesRequest);
                // 4. è°ƒç”¨MES接口(同步方法)
                var mesResult = _mesService.ContainerNgReport(mesRequest);
                stopwatch.Stop();
                // 5. å¼‚步调用MES接口(fire-and-forget)
                _mesUploadHelper.FireAndForget(
                    stockInfo.PalletCode,
                    MesUploadStatusEnum.NG上报成功,
                    "ContainerNgReport",
                    requestJson,
                    () =>
                    {
                        var result = _mesService.ContainerNgReport(mesRequest);
                        return (
                            result?.IsSuccess ?? false,
                            System.Text.Json.JsonSerializer.Serialize(result),
                            result?.ErrorMessage ?? "未知错误"
                        );
                    },
                    App.User.UserName);
                // 5. è®°å½•日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "ContainerNgReport",
                    RequestJson = requestJson,
                    ResponseJson = System.Text.Json.JsonSerializer.Serialize(mesResult),
                    IsSuccess = mesResult.IsSuccess,
                    ErrorMessage = mesResult.ErrorMessage,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                // 6. è¿”回结果
                if (mesResult.IsSuccess)
                {
                    return response.OK("NG电芯上报成功");
                }
                else
                {
                    return response.Error($"MES接口调用失败: {mesResult.ErrorMessage}");
                }
                // 6. ç«‹å³è¿”回成功响应
                return response.OK("NG电芯上报成功");
            }
            catch (System.Exception ex)
            catch (Exception ex)
            {
                stopwatch.Stop();
                // è®°å½•错误日志
                await _mesLogService.LogAsync(new MesApiLogDto
                {
                    ApiType = "ContainerNgReport",
                    IsSuccess = false,
                    ErrorMessage = ex.Message,
                    ElapsedMs = (int)stopwatch.ElapsedMilliseconds,
                    Creator = App.User.UserName
                });
                return response.Error($"NG电芯上报失败: {ex.Message}");
            }
        }
@@ -347,4 +314,4 @@
            return defaultValue;
        }
    }
}
}
Code/docs/superpowers/plans/2026-04-20-MESÉÏ´«×´Ì¬ÓëÒì²½ÉÏ´«ÊµÏּƻ®.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
# MES ä¸Šä¼ çŠ¶æ€è·Ÿè¸ªä¸Žå¼‚æ­¥ä¸Šä¼ å®žçŽ°è®¡åˆ’
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** åœ¨åº“存表添加 MES ä¸Šä¼ çŠ¶æ€å­—æ®µï¼Œé€šè¿‡ `Task.Run` å¼‚步方式上传 MES æ•°æ®ä¸å¹²æ‰°ä¸»é€»è¾‘,所有 MES è°ƒç”¨è®°å½•详细日志,前端显示状态并提供组盘/拆盘按钮,所有 MES å‡­è¯æ”¹ä¸ºåŠ¨æ€èŽ·å–ã€‚
**Architecture:**
- `Dt_StockInfo.MesUploadStatus` å•字段记录最近一次 MES æ“ä½œç»“果状态
- æ‰€æœ‰ MES è°ƒç”¨ç»Ÿä¸€é€šè¿‡ `Task.Run` å¼‚步执行,不阻塞主逻辑
- ä¸Šä¼ å®ŒæˆåŽæ›´æ–° `MesUploadStatus`,并通过 `IMesLogService.LogAsync` è®°å½•详细日志
- æ‰€æœ‰ MES å‡­è¯ï¼ˆEquipmentCode、ResourceCode、Token)改为从 `Dt_MESDeviceConfig` è¡¨åŠ¨æ€èŽ·å–
- å‰ç«¯ `stockInfo.vue` æ–°å¢žçŠ¶æ€åˆ—ï¼Œ`stock.jsx` æ–°å¢žç»„盘/拆盘按钮
**Tech Stack:** .NET 6/8, C#, SqlSugar ORM, ASP.NET Core WebAPI, Vue 3, Element Plus
---
## æ–‡ä»¶å˜æ›´æ¦‚览
| æ“ä½œ | æ–‡ä»¶ |
|------|------|
| æ–°å¢ž | `WIDESEA_Common/StockEnum/MesUploadStatusEnum.cs` |
| ä¿®æ”¹ | `WIDESEA_Model/Models/Stock/Dt_StockInfo.cs` - æ–°å¢ž `MesUploadStatus` å­—段 |
| ä¿®æ”¹ | `WIDESEA_IStockService/IStockInfoService.cs` - æ–°å¢ž `UpdateMesUploadStatusAsync` |
| ä¿®æ”¹ | `WIDESEA_StockService/StockInfoService.cs` - å®žçް `UpdateMesUploadStatusAsync` |
| ä¿®æ”¹ | `WIDESEA_DTO/MES/MesApiLogDto.cs` - æ–°å¢ž `PalletCode` å­—段 |
| ä¿®æ”¹ | `WIDESEA_TaskInfoService/TaskService.cs` - æ–°å¢ž `MesUploadAsync` + `LogMesCallAsync` |
| ä¿®æ”¹ | `WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs` - å‡ºç«™ MES æ”¹ä¸ºå¼‚æ­¥ |
| ä¿®æ”¹ | `WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs` - è¿›ç«™ MES æ”¹ä¸ºå¼‚æ­¥ |
| ä¿®æ”¹ | `WIDESEA_StockService/StockSerivce.cs` - ç»„盘/拆盘 MES æ”¹ä¸ºå¼‚æ­¥ |
| ä¿®æ”¹ | `WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs` - è¿›ç«™/出站 MES æ”¹ä¸ºå¼‚æ­¥ + åŠ¨æ€å‡­è¯ |
| ä¿®æ”¹ | `WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs` - ç»‘定/解绑/NG MES æ”¹ä¸ºå¼‚æ­¥ + åŠ¨æ€å‡­è¯ |
| ä¿®æ”¹ | `WIDESEA_WMSClient/src/views/stock/stockInfo.vue` - æ–°å¢ž `mesUploadStatus` åˆ— |
| ä¿®æ”¹ | `WIDESEA_WMSClient/src/extension/stock/stock.jsx` - æ–°å¢žç»„盘/拆盘按钮 |
| æ–°å¢ž | æ•°æ®åº“变更脚本 |
---
## Phase 1: åŸºç¡€è®¾æ–½
### Task 1: æ–°å¢ž MesUploadStatusEnum æžšä¸¾ âœ…
**Files:** Create `WIDESEA_Common/StockEnum/MesUploadStatusEnum.cs`
- [x] åˆ›å»ºæžšä¸¾ç±»ï¼ˆ11个值:0=未上传, 1/2=组盘, 3/4=拆盘, 5/6=进站, 7/8=出站, 9/10=NG上报)
- [x] Commit: `b21d0f2`
### Task 2: ä¿®æ”¹ Dt_StockInfo å®žä½“ âœ…
**Files:** Modify `WIDESEA_Model/Models/Stock/Dt_StockInfo.cs`
- [x] åœ¨ `Remark` å’Œ `OutboundDate` ä¹‹é—´æ–°å¢ž `MesUploadStatus` å­—段(int, é»˜è®¤0)
- [x] Commit: `58759d4`
### Task 3: ä¿®æ”¹ IStockInfoService æŽ¥å£ âœ…
**Files:** Modify `WIDESEA_IStockService/IStockInfoService.cs`
- [x] æ–°å¢ž `UpdateMesUploadStatusAsync(string palletCode, int status)` æ–¹æ³•声明
- [x] Commit: `18e0765`
### Task 4: ä¿®æ”¹ StockInfoService å®žçް âœ…
**Files:** Modify `WIDESEA_StockService/StockInfoService.cs`
- [x] å®žçް `UpdateMesUploadStatusAsync`:按托盘号查询 â†’ æ›´æ–° MesUploadStatus
- [x] Commit: `25a246f`(后修复缺失闭合括号 â†’ `c329a05`)
### Task 5: ä¿®æ”¹ TaskService æ–°å¢žå¼‚步方法 âœ…
**Files:** Modify `WIDESEA_TaskInfoService/TaskService.cs`
- [x] æ·»åŠ  using: `WIDESEA_Common.StockEnum`, `System.Diagnostics`, `Newtonsoft.Json`
- [x] æ–°å¢ž `MesUploadAsync` ç§æœ‰å¼‚步方法(Task.Run è°ƒç”¨ MES + æ›´æ–°çŠ¶æ€ + æ—¥å¿—)
- [x] æ–°å¢ž `LogMesCallAsync` ç§æœ‰æ—¥å¿—方法
- [x] Commit: `1330eff`
### Task 9: æ•°æ®åº“变更脚本 âœ…
**Files:** Create `Database/Scripts/20260420_Dt_StockInfo_MesUploadStatus.sql`
- [x] ALTER TABLE æ–°å¢ž MesUploadStatus TINYINT NOT NULL DEFAULT 0
- [x] Commit: `eec94e8`
### é™„加修复: MesApiLogDto æ–°å¢ž PalletCode âœ…
**Files:** Modify `WIDESEA_DTO/MES/MesApiLogDto.cs`
- [x] æ–°å¢ž `PalletCode` å±žæ€§æ”¯æŒæŒ‰æ‰˜ç›˜å·æŸ¥è¯¢æ—¥å¿—
- [x] Commit: `32ece02`
---
## Phase 2: åŽç«¯ MES è°ƒç”¨æ”¹é€ 
### Task 6: TaskService_Outbound å‡ºç«™ MES å¼‚æ­¥ âœ…
**Files:** Modify `WIDESEA_TaskInfoService/WCS/TaskService_Outbound.cs`
- [x] `OutboundInContainer` æ”¹ä¸º `Task.Run` å¼‚步执行
- [x] ä½¿ç”¨ `MesUploadStatusEnum.出站上传成功`(7)/ `出站上传失败`(8)
- [x] Commit: `91e3264`
### Task 11: StockService ç»„盘/拆盘 MES å¼‚æ­¥ âœ…
**Files:** Modify `WIDESEA_StockService/StockSerivce.cs`
- [x] `GroupPalletConfirmAsync` â†’ `BindContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥
  - æˆåŠŸ: `MesUploadStatusEnum.组盘上传成功`(1)
  - å¤±è´¥: `MesUploadStatusEnum.组盘上传失败`(2)
- [x] `SplitPalletConfirmAsync` â†’ `UnBindContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥
  - æˆåŠŸ: `MesUploadStatusEnum.拆盘上传成功`(3)
  - å¤±è´¥: `MesUploadStatusEnum.拆盘上传失败`(4)
- [x] ä¿ç•™åŠ¨æ€ Token èŽ·å–é€»è¾‘ï¼ˆ`ResolveMesConfig`)
- [x] Commit: `4506a1e`
### Task 12: StockInfoDetailController MES å¼‚æ­¥ + åŠ¨æ€å‡­è¯ âœ…
**Files:** Modify `WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs`
- [x] `BindContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥ â†’ `MesUploadStatusEnum.组盘上传成功/失败`
- [x] `UnbindContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥ â†’ `MesUploadStatusEnum.拆盘上传成功/失败`
- [x] `ContainerNgReport` æ”¹ä¸º `Task.Run` å¼‚æ­¥ â†’ `MesUploadStatusEnum.NG上报成功/失败`
- [x] ç¡¬ç¼–码 `"STK-GROUP-001"` æ”¹ä¸º `_mesDeviceConfigService.GetByDeviceName()` åŠ¨æ€èŽ·å–
- [x] æ³¨å…¥ `IMESDeviceConfigService`
- [x] Commit: `57feefd` â†’ `c36c5c6`
### Task 13: StockInfoController MES å¼‚æ­¥ + åŠ¨æ€å‡­è¯ âœ…
**Files:** Modify `WIDESEA_WMSServer/Controllers/Stock/StockInfoController.cs`
- [x] `InboundInContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥ â†’ `MesUploadStatusEnum.进站上传成功/失败`
- [x] `OutboundInContainer` æ”¹ä¸º `Task.Run` å¼‚æ­¥ â†’ `MesUploadStatusEnum.出站上传成功/失败`
- [x] ç¡¬ç¼–码 `"STK-GROUP-001"` æ”¹ä¸ºåŸºäºŽ `stockInfo.Remark` åŠ¨æ€é€‰æ‹©è®¾å¤‡åæŸ¥è¯¢ MES å‡­è¯
- [x] æ³¨å…¥ `IMESDeviceConfigService` å’Œ `IStockInfoService`
- [x] Commit: `343d512` â†’ `fba665e`
### Task 14: TaskService_Inbound è¿›ç«™ MES å¼‚æ­¥ âœ…
**Files:** Modify `WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs`
- [x] `InboundInContainer` æ”¹ä¸º `Task.Run` å¼‚步执行
- [x] ä½¿ç”¨ `MesUploadStatusEnum.进站上传成功`(5)/ `进站上传失败`(6)
- [x] ä¿ç•™åŠ¨æ€ MES å‡­è¯èŽ·å–é€»è¾‘ï¼ˆ`_mesDeviceConfigService.GetByDeviceName`)
- [x] `CompleteTaskAsync` ç«‹å³è¿”回,不等待 MES ç»“æžœ
- [x] Commit: `63ca4ac`
---
## Phase 3: å‰ç«¯
### Task 7: stockInfo.vue æ–°å¢ž MES çŠ¶æ€åˆ— âœ…
**Files:** Modify `WIDESEA_WMSClient/src/views/stock/stockInfo.vue`
- [x] columns ä¸­ `stockStatus` ä¹‹åŽæ–°å¢ž `mesUploadStatus` åˆ—(bind: mesUploadStatusEnum)
- [x] `loadStockStatusOptions` æ”¹ä¸ºåŒæ—¶åŠ è½½ `stockStatusEmun` + `mesUploadStatusEnum`
- [x] Commit: `9a1c82a`
### Task 8: stock.jsx æ–°å¢žç»„盘/拆盘按钮 âœ…
**Files:** Modify `WIDESEA_WMSClient/src/extension/stock/stock.jsx`
- [x] `onInited` ä¸­æ³¨å…¥ `editTableButtons`:组盘 + æ‹†ç›˜
- [x] `onGroupPallet` â†’ POST `/Stock/GroupPalletConfirm`
- [x] `onSplitPallet` â†’ POST `/Stock/SplitPalletConfirm`
- [x] Commit: `0be9278`
---
## Phase 4: æž„建验证
### Task 10: æž„建验证 âœ…
- [x] åŽç«¯ `dotnet build WIDESEA_WMSServer.sln` â†’ 0 é”™è¯¯
- [x] å‰ç«¯ `npm run build` â†’ 0 é”™è¯¯
---
## MES è°ƒç”¨ç‚¹å…¨è¦†ç›–
| ä½ç½® | æ–¹æ³• | MES接口 | çŠ¶æ€æžšä¸¾ | åŠ¨æ€å‡­è¯ | å¼‚æ­¥ |
|------|------|---------|---------|---------|------|
| `StockSerivce.cs` | `GroupPalletConfirmAsync` | `BindContainer` | 1成功/2失败 | âœ… ResolveMesConfig | âœ… |
| `StockSerivce.cs` | `SplitPalletConfirmAsync` | `UnBindContainer` | 3成功/4失败 | âœ… ResolveMesConfig | âœ… |
| `TaskService_Inbound.cs` | `InboundFinishTaskAsync` | `InboundInContainer` | 5成功/6失败 | âœ… MESDeviceConfigService | âœ… |
| `TaskService_Outbound.cs` | `OutboundFinishTaskAsync` | `OutboundInContainer` | 7成功/8失败 | âœ… MESDeviceConfigService | âœ… |
| `StockInfoController.cs` | `InboundInContainer` | `InboundInContainer` | 5成功/6失败 | âœ… MESDeviceConfigService | âœ… |
| `StockInfoController.cs` | `OutboundInContainer` | `OutboundInContainer` | 7成功/8失败 | âœ… MESDeviceConfigService | âœ… |
| `StockInfoDetailController.cs` | `BindContainer` | `BindContainer` | 1成功/2失败 | âœ… MESDeviceConfigService | âœ… |
| `StockInfoDetailController.cs` | `UnbindContainer` | `UnBindContainer` | 3成功/4失败 | âœ… MESDeviceConfigService | âœ… |
| `StockInfoDetailController.cs` | `ContainerNgReport` | `ContainerNgReport` | 9成功/10失败 | âœ… MESDeviceConfigService | âœ… |
---
## è‡ªæ£€æ¸…单
- [x] `MesUploadStatusEnum` æžšä¸¾å€¼å¥‡æ•°ä¸ºæˆåŠŸï¼Œå¶æ•°ä¸ºå¤±è´¥
- [x] æ‰€æœ‰ MES è°ƒç”¨é€šè¿‡ `Task.Run` å¼‚步执行,不阻塞主逻辑
- [x] æ‰€æœ‰ MES è°ƒç”¨è®°å½•详细日志(托盘号、接口类型、请求JSON、响应JSON、耗时、状态、错误原因)
- [x] æ‰€æœ‰ MES å‡­è¯åŠ¨æ€èŽ·å–ï¼Œä¸å†ç¡¬ç¼–ç  `"STK-GROUP-001"`
- [x] å‰ç«¯ç»„盘/拆盘按钮正确调用 `GroupPalletConfirmAsync` / `SplitPalletConfirmAsync`
- [x] `stockInfo.vue` æ­£ç¡®æ˜¾ç¤º `mesUploadStatus` å­—段
- [x] æ‰€æœ‰ public æ–¹æ³•均有 XML æ–‡æ¡£æ³¨é‡Š
- [x] æ•°æ®åº“脚本含 IF NOT EXISTS é˜²æ­¢é‡å¤æ·»åŠ 
- [x] åŽç«¯å’Œå‰ç«¯å‡å¯æ­£å¸¸ç¼–译构建(0 é”™è¯¯ï¼‰
Code/docs/superpowers/specs/2026-04-20-MESÉÏ´«×´Ì¬ÓëÒì²½ÉÏ´«Éè¼Æ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,288 @@
# MES ä¸Šä¼ çŠ¶æ€è·Ÿè¸ªä¸Žå¼‚æ­¥ä¸Šä¼ è®¾è®¡
> **Date:** 2026-04-20
> **Author:** Claude
## 1. ç›®æ ‡
在库存表添加 MES ä¸Šä¼ çŠ¶æ€å­—æ®µï¼Œé€šè¿‡å¼‚æ­¥æ–¹å¼ä¸Šä¼  MES æ•°æ®ï¼Œä¸å¹²æ‰°ä¸»ä¸šåŠ¡é€»è¾‘ï¼Œæ‰€æœ‰ MES è°ƒç”¨å‡è®°å½•详细日志。
## 2. æ•°æ®åº“变更
### 2.1 Dt_StockInfo è¡¨æ–°å¢žå­—段
```sql
ALTER TABLE [dbo].[Dt_StockInfo] ADD [MesUploadStatus] TINYINT NOT NULL DEFAULT 0;
GO
```
### 2.2 æžšä¸¾å®šä¹‰
| å€¼ | å«ä¹‰ |
|----|------|
| 0 | æœªä¸Šä¼ ï¼ˆä»Žæœªè°ƒç”¨è¿‡MES) |
| 1 | ç»„盘上传成功 |
| 2 | ç»„盘上传失败 |
| 3 | æ‹†ç›˜ä¸Šä¼ æˆåŠŸ |
| 4 | æ‹†ç›˜ä¸Šä¼ å¤±è´¥ |
| 5 | è¿›ç«™ä¸Šä¼ æˆåŠŸ |
| 6 | è¿›ç«™ä¸Šä¼ å¤±è´¥ |
| 7 | å‡ºç«™ä¸Šä¼ æˆåŠŸ |
| 8 | å‡ºç«™ä¸Šä¼ å¤±è´¥ |
| 9 | NG上报成功 |
| 10 | NG上报失败 |
在 `WIDESEA_Common` é¡¹ç›®æ–°å¢žæžšä¸¾ç±» `MesUploadStatusEnum`。
## 3. åŽç«¯è®¾è®¡
### 3.1 æ ¸å¿ƒå¼‚步方法
在 `WIDESEA_TaskInfoService/TaskService.cs` ä¸­æ–°å¢ž `MesUploadAsync` ç§æœ‰å¼‚步方法:
```csharp
/// <summary>
/// å¼‚步执行MES上传 - ä¸é˜»å¡žä¸»ä¸šåŠ¡é€»è¾‘
/// </summary>
/// <param name="palletCode">托盘号</param>
/// <param name="mesType">MES操作类型枚举</param>
/// <param name="uploadFunc">具体的MES调用函数</param>
private async Task MesUploadAsync(string palletCode, MesUploadStatusEnum mesType, Func<Task<HttpResponseResult<MesResponse>>> uploadFunc)
{
    var stopwatch = Stopwatch.StartNew();
    string requestJson = "";
    string responseJson = "";
    bool isSuccess = false;
    string errorMessage = "";
    try
    {
        // è®°å½•请求日志
        var mesLog = new MesApiLogDto
        {
            PalletCode = palletCode,
            ApiType = mesType.ToString(),
            RequestTime = DateTime.Now
        };
        // è°ƒç”¨MES
        var result = await uploadFunc();
        stopwatch.Stop();
        isSuccess = result?.Data?.IsSuccess ?? false;
        errorMessage = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误";
        responseJson = JsonConvert.SerializeObject(result);
        // æ›´æ–°åº“存表状态
        var uploadStatus = isSuccess ? (int)mesType : (int)mesType + 1; // å¥‡æ•°=成功,偶数=失败
        await _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus);
        // è®°å½•日志
        mesLog.ResponseTime = DateTime.Now;
        mesLog.Duration = stopwatch.ElapsedMilliseconds;
        mesLog.RequestParams = requestJson;
        mesLog.ResponseResult = responseJson;
        mesLog.Status = isSuccess ? "成功" : "失败";
        mesLog.ErrorMessage = errorMessage;
        mesLog.CreateTime = DateTime.Now;
        _ = _mesLogService.LogAsync(mesLog); // ä¸ç­‰å¾…
    }
    catch (Exception ex)
    {
        stopwatch.Stop();
        errorMessage = ex.Message;
        // æ›´æ–°çŠ¶æ€ä¸ºå¤±è´¥
        var uploadStatus = (int)mesType + 1;
        _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus).ConfigureAwait(false);
        // è®°å½•异常日志
        var mesLog = new MesApiLogDto
        {
            PalletCode = palletCode,
            ApiType = mesType.ToString(),
            RequestTime = DateTime.Now,
            ResponseTime = DateTime.Now,
            Duration = stopwatch.ElapsedMilliseconds,
            RequestParams = requestJson,
            ResponseResult = responseJson,
            Status = "失败",
            ErrorMessage = errorMessage,
            CreateTime = DateTime.Now
        };
        _ = _mesLogService.LogAsync(mesLog); // ä¸ç­‰å¾…
    }
}
```
### 3.2 è°ƒç”¨æ–¹å¼
在各个 MES æ“ä½œå¤„统一使用 `Task.Run` + `MesUploadAsync`:
```csharp
// ç»„盘确认
public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName)
{
    // ... ä¸šåŠ¡é€»è¾‘ ...
    // å¼‚æ­¥MES上传,不阻塞主逻辑
    _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.GroupPalletSuccess, async () =>
    {
        return await _mesService.BindContainerAsync(bindRequest, token);
    }));
    return content.OK("组盘成功");
}
// æ‹†ç›˜ç¡®è®¤
public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode, string deviceName)
{
    // ... ä¸šåŠ¡é€»è¾‘ ...
    _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.SplitPalletSuccess, async () =>
    {
        return await _mesService.UnBindContainerAsync(unbindRequest, token);
    }));
    return content.OK("拆盘成功");
}
```
### 3.3 IStockInfoService æ–°å¢žæ–¹æ³•
在 `IStockInfoService` æŽ¥å£æ–°å¢žï¼š
```csharp
/// <summary>
/// æ›´æ–°MES上传状态
/// </summary>
/// <param name="palletCode">托盘号</param>
/// <param name="status">状态值</param>
Task<bool> UpdateMesUploadStatusAsync(string palletCode, int status);
```
在 `StockInfoService` ä¸­å®žçŽ°è¯¥æ–¹æ³•ã€‚
### 3.4 æ—¥å¿—记录规范
所有 MES è°ƒç”¨ç»Ÿä¸€è®°å½•以下字段:
| å­—段 | è¯´æ˜Ž |
|------|------|
| PalletCode | æ‰˜ç›˜å· |
| ApiType | æŽ¥å£ç±»åž‹ï¼ˆç»„盘/拆盘/进站/出站/NG上报) |
| RequestTime | è¯·æ±‚æ—¶é—´ |
| ResponseTime | å“åº”æ—¶é—´ |
| Duration | è€—æ—¶(ms) |
| RequestParams | è¯·æ±‚JSON |
| ResponseResult | å“åº”JSON |
| Status | æˆåŠŸ/失败 |
| ErrorMessage | å¤±è´¥åŽŸå›  |
## 4. å‰ç«¯å˜æ›´
### 4.1 åº“存页面操作列新增按钮
在 `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` ä¸­æ‰©å±•操作列:
```jsx
let extension = {
  components: {
    gridHeader: "",
    gridBody: "",
    gridFooter: "",
    modelHeader: "",
    modelBody: "",
    modelFooter: "",
  },
  tableAction: "stock",
  buttons: {
    view: ["Export"],
    box: []
  },
  methods: {
    onInit() {
      return true;
    },
    onInited() {
      // æ³¨å…¥ç»„盘/拆盘按钮
      this.editTableButtons = [
        { name: "组盘", onClick: this.onGroupPallet },
        { name: "拆盘", onClick: this.onSplitPallet }
      ];
      return true;
    },
    async onGroupPallet({ row }) {
      // è°ƒç”¨ç»„盘接口
      let result = await this.$api.post("/Stock/GroupPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("组盘成功");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "组盘失败");
      }
    },
    async onSplitPallet({ row }) {
      // è°ƒç”¨æ‹†ç›˜æŽ¥å£
      let result = await this.$api.post("/Stock/SplitPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("拆盘成功");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "拆盘失败");
      }
    }
  },
};
export default extension;
```
### 4.2 åº“存列表新增状态列
在 `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` çš„ `columns` ä¸­æ–°å¢žï¼š
```vue
{ field: "mesUploadStatus", title: "MES状态", type: "int", width: 120, bind: { key: "mesUploadStatusEnum", data: [] } }
```
### 4.3 å­—典配置
在 `mesUploadStatusEnum` å­—典中添加:
| value | label |
|-------|-------|
| 0 | æœªä¸Šä¼  |
| 1 | ç»„盘成功 |
| 2 | ç»„盘失败 |
| 3 | æ‹†ç›˜æˆåŠŸ |
| 4 | æ‹†ç›˜å¤±è´¥ |
| 5 | è¿›ç«™æˆåŠŸ |
| 6 | è¿›ç«™å¤±è´¥ |
| 7 | å‡ºç«™æˆåŠŸ |
| 8 | å‡ºç«™å¤±è´¥ |
| 9 | NG上报成功 |
| 10 | NG上报失败 |
## 5. æ–‡ä»¶å˜æ›´æ¸…单
| æ“ä½œ | æ–‡ä»¶ |
|------|------|
| æ–°å¢ž | `WMS/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/MesUploadStatusEnum.cs` |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfo.cs` - æ–°å¢ž MesUploadStatus å­—段 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs` - æ–°å¢ž UpdateMesUploadStatusAsync |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs` - å®žçް UpdateMesUploadStatusAsync |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs` - æ–°å¢ž MesUploadAsync + å„处调用 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` - æ–°å¢žç»„盘/拆盘按钮 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` - æ–°å¢žMES状态列 |
| æ–°å¢ž | æ•°æ®åº“变更脚本 |
## 6. è‡ªæ£€æ¸…单
- [ ] `MesUploadStatusEnum` æžšä¸¾å€¼å¥‡æ•°ä¸ºæˆåŠŸï¼Œå¶æ•°ä¸ºå¤±è´¥ï¼Œ+1运算正确
- [ ] `MesUploadAsync` æ–¹æ³•内所有 MES è°ƒç”¨è®°å½•详细日志(请求JSON、响应JSON、耗时、错误原因)
- [ ] ç»„盘/拆盘/进站/出站/NG上报均通过 `Task.Run` å¼‚步执行,不阻塞主逻辑
- [ ] å‰ç«¯ç»„盘/拆盘按钮调用 `GroupPalletConfirmAsync` / `SplitPalletConfirmAsync`
- [ ] åº“存列表正确显示 `mesUploadStatus` å­—段
- [ ] æ‰€æœ‰ public æ–¹æ³•均有 XML æ–‡æ¡£æ³¨é‡Š