已添加3个文件
已修改25个文件
1710 ■■■■ 文件已修改
.omc/project-memory.json 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/task.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/System/Sys_DictionaryController.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs 500 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue 628 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/system/Sys_Log.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/WMSTaskDTO.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_SystemService/Sys_DictionaryService.cs 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_AGV.cs 142 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_WCS.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/极卷库AGV接口/~$极卷库出入库.docx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/拘束机对接协议/拘束机对接协议.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/机械手协议/~$交互流程表(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
项目资料/设备协议/机械手协议/交互流程表(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
.omc/project-memory.json
@@ -1,6 +1,6 @@
{
  "version": "1.0.0",
  "lastScanned": 1774488038530,
  "lastScanned": 1775442733141,
  "projectRoot": "D:\\Git\\ShanMeiXinNengYuan",
  "techStack": {
    "languages": [],
@@ -36,72 +36,17 @@
      "path": "Code",
      "purpose": null,
      "fileCount": 0,
      "lastAccessed": 1774488038511,
      "lastAccessed": 1775442733121,
      "keyFiles": []
    },
    "项目资料": {
      "path": "项目资料",
      "purpose": null,
      "fileCount": 0,
      "lastAccessed": 1774488038512,
      "lastAccessed": 1775442733121,
      "keyFiles": []
    }
  },
  "hotPaths": [
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockSerivce.cs",
      "accessCount": 14,
      "lastAccessed": 1774492937086,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\appsettings.json",
      "accessCount": 5,
      "lastAccessed": 1774490361799,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\Program.cs",
      "accessCount": 3,
      "lastAccessed": 1774488093580,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_WMSServer\\WIDESEA_WMSServer.csproj",
      "accessCount": 1,
      "lastAccessed": 1774488065335,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockViewService.cs",
      "accessCount": 1,
      "lastAccessed": 1774492577060,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockInfoService.cs",
      "accessCount": 1,
      "lastAccessed": 1774492577097,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_StockService\\StockInfoDetailService.cs",
      "accessCount": 1,
      "lastAccessed": 1774492577122,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_Core\\BaseServices\\ServiceBase.cs",
      "accessCount": 1,
      "lastAccessed": 1774492577619,
      "type": "file"
    },
    {
      "path": "Code\\WMS\\WIDESEA_WMSServer\\WIDESEA_IStockService\\IStockInfoService.cs",
      "accessCount": 1,
      "lastAccessed": 1774492782997,
      "type": "file"
    }
  ],
  "hotPaths": [],
  "userDirectives": []
}
Code/WCS/WIDESEAWCS_Client/src/views/taskinfo/task.vue
@@ -52,7 +52,7 @@
        },
        {
          title: "任务状态",
          field: "taskState",
          field: "taskStatus",
          type: "selectList",
          dataKey: "taskState",
          data: [],
@@ -111,7 +111,7 @@
        bind: { key: "taskType", data: [] },
      },
      {
        field: "taskState",
        field: "taskStatus",
        title: "任务状态",
        type: "int",
        width: 150,
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_DTO/TaskInfo/WMSTaskDTO.cs
@@ -1,4 +1,6 @@
using System;
using Magicodes.ExporterAndImporter.Core;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -11,17 +13,17 @@
        /// <summary>
        /// WMS任务主键
        /// </summary>
        public int Id {  get; set; }
        public int Id { get; set; }
        /// <summary>
        /// ä»»åŠ¡å·
        /// </summary>
        public int TaskNum {  get; set; }
        public int TaskNum { get; set; }
        /// <summary>
        /// æ‰˜ç›˜å·
        /// </summary>
        public string PalletCode {  get; set; }
        public string PalletCode { get; set; }
        /// <summary>
        /// å··é“号
@@ -31,7 +33,7 @@
        /// <summary>
        /// ä»»åŠ¡ç±»åž‹
        /// </summary>
        public int TaskType {  get; set; }
        public int TaskType { get; set; }
        /// <summary>
        /// ä»»åŠ¡çŠ¶æ€
@@ -41,16 +43,44 @@
        /// <summary>
        /// èµ·ç‚¹
        /// </summary>
        public string SourceAddress {  get; set; }
        public string SourceAddress { get; set; }
        /// <summary>
        /// ç»ˆç‚¹
        /// </summary>
        public string TargetAddress {  get; set; }
        public string TargetAddress { get; set; }
        /// <summary>
        /// å½“前位置
        /// </summary>
        public string CurrentAddress { get; set; }
        /// <summary>
        /// ä¸‹ä¸€åœ°å€
        /// </summary>
        public string NextAddress { get; set; }
        /// <summary>
        /// ä¼˜å…ˆçº§
        /// </summary>
        public int Grade {  get; set; }
        public int Grade { get; set; }
        /// <summary>
        ///
        /// </summary>
        public int WarehouseId { get; set; }
        /// <summary>
        ///
        /// </summary>
        public string AGVArea { get; set; }
        /// <summary>
        ///
        /// </summary>
        public int PalletType { get; set; }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/System/Sys_DictionaryController.cs
@@ -318,6 +318,28 @@
                            }
                            #endregion
                            #region TaskRobotStatusEnum
                            {
                                Type type = typeof(TaskRobotStatusEnum);
                                List<int> enums = Enum.GetValues(typeof(TaskRobotStatusEnum)).Cast<int>().ToList();
                                int index = 0;
                                foreach (var item in enums)
                                {
                                    FieldInfo? fieldInfo = typeof(TaskRobotStatusEnum).GetField(((TaskRobotStatusEnum)item).ToString());
                                    DescriptionAttribute? description = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
                                    if (description != null)
                                    {
                                        data.Add(new { key = item.ToString(), value = description.Description });
                                    }
                                    else
                                    {
                                        data.Add(new { key = item.ToString(), value = item.ToString() });
                                    }
                                    index++;
                                }
                            }
                            #endregion
                            result = new VueDictionaryDTO { DicNo = key, Config = "", Data = data };
                        }
                        break;
@@ -369,7 +391,7 @@
                            result = new VueDictionaryDTO { DicNo = key, Config = "", Data = data };
                        }
                        break;
                        case "dispatchId":
                    case "dispatchId":
                        {
                            Type type = typeof(IJob);
                            var basePath = AppContext.BaseDirectory;
@@ -403,4 +425,4 @@
            }
        }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -33,6 +33,7 @@
  "DBType": "SqlServer",
  //连接字符串
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWCS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //跨域
  "Cors": {
@@ -128,4 +129,4 @@
      "MaxMemoryPolicy": "allkeys-lru" //内存淘汰策略:allkeys-lru, volatile-lru, noeviction等
    }
  }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
@@ -112,6 +112,7 @@
                    InitializeTaskOnReceive(task, item);
                    tasks.Add(task);
                }
                // TOOD: è¿™é‡Œæ³¨æ„æ·»åŠ é”™è¯¯è¦è¿”å›žé”™è¯¯
                BaseDal.AddData(tasks);
                _taskExecuteDetailService.AddTaskExecuteDetail(tasks.Select(x => x.TaskNum).ToList(), "接收WMS任务");
@@ -726,4 +727,4 @@
        [DataLength(22)]
        public string Barcode { get; set; }
    }
}
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -20,227 +20,447 @@
    public class ConveyorLineTargetAddressSelector
    {
        /// <summary>
        /// æ‹˜æŸæœºåç§°å¸¸é‡
        /// è®¾å¤‡å±‚级(上层/下层)
        /// </summary>
        private const string ConstraintMachineName = "拘束机";
        /// <remarks>
        /// ç”¨äºŽåŒºåˆ†æ‹˜æŸæœºå’Œæ’拔钉机的上层工位和下层工位。
        /// å…¥åº“任务对应上层(MaterialRequestUpper),出库任务对应下层(MaterialRequestLower)。
        /// </remarks>
        private enum Layer
        {
            /// <summary>
            /// ä¸Šå±‚工位
            /// </summary>
            Upper,
            /// <summary>
            /// ä¸‹å±‚工位
            /// </summary>
            Lower
        }
        /// <summary>
        /// æ’拔钉机名称常量
        /// ç›®æ ‡è®¾å¤‡ç±»åž‹æžšä¸¾
        /// </summary>
        private const string PinMachineName = "插拔钉机";
        /// <remarks>
        /// ç”¨äºŽæ ¹æ®ç›®æ ‡åœ°å€ç¼–码识别需要对接的设备类型。
        /// </remarks>
        private enum TargetDeviceType
        {
            /// <summary>
            /// æ— æœ‰æ•ˆè®¾å¤‡ï¼ˆåœ°å€ä¸åœ¨å·²çŸ¥èŒƒå›´å†…)
            /// </summary>
            None,
            /// <summary>
            /// æ‹˜æŸæœº
            /// </summary>
            /// <remarks>
            /// è´Ÿè´£å›ºå®š/约束电池托盘的设备,有上下两层。
            /// </remarks>
            ConstraintMachine,
            /// <summary>
            /// æ’拔钉机
            /// </summary>
            /// <remarks>
            /// è´Ÿè´£æ’针和拔针操作的设备,有上下两层。
            /// </remarks>
            PinMachine
        }
        /// <summary>
        /// æ‹˜æŸæœºå¯¹åº”的点位编码列表
        /// æ‹˜æŸæœºå¯¹åº”的点位编码集合
        /// </summary>
        /// <remarks>
        /// å½“目标地址在这些编码中时,表示需要与拘束机交互。
        /// ä½¿ç”¨ HashSet ä¿è¯ O(1) çš„ Contains æŸ¥æ‰¾æ€§èƒ½ã€‚
        /// </remarks>
        private static readonly List<string> ConstraintMachineCodes = new List<string> { "10180", "20090" };
        private static readonly HashSet<string> ConstraintMachineCodes = new HashSet<string> { "10180", "20090" };
        /// <summary>
        /// æ’拔钉机对应的点位编码列表
        /// æ’拔钉机对应的点位编码集合
        /// </summary>
        /// <remarks>
        /// å½“目标地址在这些编码中时,表示需要与插拔钉机交互。
        /// ä½¿ç”¨ HashSet ä¿è¯ O(1) çš„ Contains æŸ¥æ‰¾æ€§èƒ½ã€‚
        /// </remarks>
        private static readonly List<string> PinMachineCodes = new List<string> { "10190", "20100" };
        private static readonly HashSet<string> PinMachineCodes = new HashSet<string> { "10190", "20100" };
        /// <summary>
        /// ç›®æ ‡åœ°å€åˆ°è®¾å¤‡ç±»åž‹çš„æ˜ å°„
        /// </summary>
        /// <remarks>
        /// é€šè¿‡å•一字典实现 O(1) æŸ¥æ‰¾ï¼Œæ›¿ä»£åŽŸå…ˆåˆ†åˆ«ç”¨ä¸¤ä¸ª List + Contains + if/else if çš„写法。
        /// Key: ç›®æ ‡åœ°å€ç¼–码,Value: å¯¹åº”的设备类型。
        /// </remarks>
        private static readonly Dictionary<string, TargetDeviceType> AddressToDeviceType = new Dictionary<string, TargetDeviceType>
        {
            // æ‹˜æŸæœºçš„两个点位编码都映射到 ConstraintMachine ç±»åž‹
            { "10180", TargetDeviceType.ConstraintMachine },
            { "20090", TargetDeviceType.ConstraintMachine },
            // æ’拔钉机的两个点位编码都映射到 PinMachine ç±»åž‹
            { "10190", TargetDeviceType.PinMachine },
            { "20100", TargetDeviceType.PinMachine }
        };
        /// <summary>
        /// æ—¥å¿—记录器
        /// </summary>
        /// <remarks>
        /// é€šè¿‡ Microsoft.Extensions.Logging æŽ¥å£æ³¨å…¥ï¼Œç”¨äºŽç»“构化日志输出。
        /// </remarks>
        private readonly ILogger _logger;
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="logger">日志记录器</param>
        /// <param name="logger">日志记录器,由依赖注入容器自动注入</param>
        public ConveyorLineTargetAddressSelector(ILogger logger)
        {
            _logger = logger;
            _logger = logger; // ä¿å­˜æ—¥å¿—记录器实例,供后续方法使用
        }
        /// <summary>
        /// å¤„理入库场景的下一地址请求
        /// </summary>
        /// <remarks>
        /// å½“入库任务执行到某个位置时调用此方法。
        /// åˆ¤æ–­ç›®æ ‡è®¾å¤‡æ˜¯å¦éœ€è¦ç‰©æ–™æˆ–可以出料。
        /// å…¥åº“任务到达某个位置时调用此方法,判断目标设备是否需要物料。
        /// å…¥åº“对应上层工位(Layer.Upper),因为物料从上层进入仓库。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象</param>
        /// <param name="nextAddress">下一地址/目标设备编码</param>
        /// <param name="childDeviceCode">当前子设备编码</param>
        /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK ä¿¡å·</param>
        /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param>
        /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param>
        public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            _logger.LogDebug("HandleInboundNextAddress:入库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress);
            QuartzLogger.Debug($"HandleInboundNextAddress:入库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode);
            // è°ƒç”¨é€šç”¨å¤„理方法,isUpper = true è¡¨ç¤ºå¤„理上层
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true);
            // è®°å½•入库场景的调试日志,包含子设备和目标地址信息
            WriteDebug(conveyorLine, "入库下一地址", childDeviceCode, nextAddress);
            // å§”托通用处理方法,入库对应上层(isUpper: true)
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Upper);
        }
        /// <summary>
        /// å¤„理出库场景的下一地址请求
        /// </summary>
        /// <remarks>
        /// å½“出库任务执行到某个位置时调用此方法。
        /// åˆ¤æ–­ç›®æ ‡è®¾å¤‡æ˜¯å¦éœ€è¦ç‰©æ–™æˆ–可以出料。
        /// å‡ºåº“任务到达某个位置时调用此方法,判断目标设备是否需要出料。
        /// å‡ºåº“对应下层工位(Layer.Lower),因为物料从下层离开仓库。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象</param>
        /// <param name="nextAddress">下一地址/目标设备编码</param>
        /// <param name="childDeviceCode">当前子设备编码</param>
        /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK ä¿¡å·</param>
        /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param>
        /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param>
        public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            _logger.LogDebug("HandleOutboundNextAddress:出库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress);
            QuartzLogger.Debug($"HandleOutboundNextAddress:出库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode);
            // è°ƒç”¨é€šç”¨å¤„理方法,isUpper = false è¡¨ç¤ºå¤„理下层
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false);
            // è®°å½•出库场景的调试日志,包含子设备和目标地址信息
            WriteDebug(conveyorLine, "出库下一地址", childDeviceCode, nextAddress);
            // å§”托通用处理方法,出库对应下层(isUpper: false)
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Lower);
        }
        /// <summary>
        /// é€šç”¨è®¾å¤‡è¯·æ±‚处理方法
        /// æ ¹æ®ç›®æ ‡åœ°å€ç±»åž‹åˆ†å‘到对应设备处理
        /// </summary>
        /// <remarks>
        /// æ ¹æ®ç›®æ ‡åœ°å€ç±»åž‹ï¼ˆæ‹˜æŸæœº/插拔钉机)调用相应的处理逻辑。
        /// å¤„理上下层设备的物料请求和出料协调。
        /// é€šè¿‡ AddressToDeviceType å­—典将目标地址映射到设备类型,
        /// ç„¶åŽåˆ†å‘到对应的专用处理方法(HandleConstraintMachine / HandlePinMachine)。
        /// å¦‚果目标地址不在已知映射表中,直接返回,不做任何处理。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象</param>
        /// <param name="nextAddress">下一地址/目标设备编码</param>
        /// <param name="childDeviceCode">当前子设备编码</param>
        /// <param name="isUpper">是否处理上层(true=上层,false=下层)</param>
        private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, bool isUpper)
        /// <param name="conveyorLine">输送线设备对象,传递到具体设备处理方法</param>
        /// <param name="nextAddress">目标设备编码,通过字典查找识别设备类型</param>
        /// <param name="childDeviceCode">子设备编码,传递到具体设备处理方法</param>
        /// <param name="layer">设备层级(上层或下层),决定读取哪组请求标志</param>
        private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, Layer layer)
        {
            // èŽ·å–å…¨å±€è®¾å¤‡åˆ—è¡¨
            var devices = Storage.Devices;
            // åˆ¤æ–­ç›®æ ‡è®¾å¤‡ç±»åž‹
            if (ConstraintMachineCodes.Contains(nextAddress))
            // é€šè¿‡å­—典查找目标地址对应的设备类型,如果找不到则 deviceType ä¸º None
            if (!AddressToDeviceType.TryGetValue(nextAddress, out var deviceType) || deviceType == TargetDeviceType.None)
            {
                // æ‹˜æŸæœºå¤„理分支
                // æŸ¥æ‰¾æ‹˜æŸæœºè®¾å¤‡
                ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
                if (constraint == null)
                {
                    _logger.LogDebug("HandleDeviceRequest:未找到拘束机设备");
                    QuartzLogger.Debug("HandleDeviceRequest:未找到拘束机设备", conveyorLine.DeviceCode);
                    // æœªæ‰¾åˆ°æ‹˜æŸæœºè®¾å¤‡ï¼Œç›´æŽ¥è¿”回
                    return;
                }
                // å¤„理拘束机的请求
                ProcessDeviceRequest(
                    conveyorLine,
                    childDeviceCode,
                    // èŽ·å–ç‰©æ–™è¯·æ±‚æ ‡å¿—ï¼ˆä¸Šå±‚æˆ–ä¸‹å±‚ï¼‰
                    getMaterialRequest: () => isUpper
                        ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestUpper) != 0
                        : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestLower) != 0,
                    // èŽ·å–å‡ºæ–™è¯·æ±‚æ ‡å¿—ï¼ˆä¸Šå±‚æˆ–ä¸‹å±‚ï¼‰
                    getOutputRequest: () => isUpper
                        ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestUpper) != 0
                        : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestLower) != 0,
                    // è®¾ç½®è¾“出就绪标志(上层或下层)
                    setOutputReady: outputReq =>
                    {
                        if (isUpper)
                        {
                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0);
                        }
                        else
                        {
                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    },
                    "拘束机");
                // ç›®æ ‡åœ°å€ä¸åœ¨å·²çŸ¥æ˜ å°„表中,直接返回(可能是其他类型设备)
                return;
            }
            else if (PinMachineCodes.Contains(nextAddress))
            // æ ¹æ®è¯†åˆ«å‡ºçš„设备类型分发到对应的处理方法
            switch (deviceType)
            {
                // æ’拔钉机处理分支
                // æŸ¥æ‰¾æ’拔钉机设备
                PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
                if (pinMachine == null)
                {
                    _logger.LogDebug("HandleDeviceRequest:未找到插拔钉机设备");
                    QuartzLogger.Debug("HandleDeviceRequest:未找到插拔钉机设备", conveyorLine.DeviceCode);
                    return;
                }
                case TargetDeviceType.ConstraintMachine:
                    // æ‹˜æŸæœºå¤„理分支:获取拘束机实例并处理其上下层请求
                    HandleConstraintMachine(conveyorLine, childDeviceCode, layer);
                    break; // å¤„理完毕,跳出 switch
                // å¤„理插拔钉机的请求
                ProcessDeviceRequest(
                    conveyorLine,
                    childDeviceCode,
                    // èŽ·å–ç‰©æ–™è¯·æ±‚æ ‡å¿—ï¼ˆä¸Šå±‚æˆ–ä¸‹å±‚ï¼‰
                    getMaterialRequest: () => isUpper
                        ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestUpper) != 0
                        : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestLower) != 0,
                    // èŽ·å–å‡ºæ–™è¯·æ±‚æ ‡å¿—ï¼ˆä¸Šå±‚æˆ–ä¸‹å±‚ï¼‰
                    getOutputRequest: () => isUpper
                        ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestUpper) != 0
                        : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestLower) != 0,
                    // è®¾ç½®è¾“出就绪标志(上层或下层)
                    setOutputReady: outputReq =>
                    {
                        if (isUpper)
                        {
                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0);
                        }
                        else
                        {
                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    },
                    "插拔钉机");
                case TargetDeviceType.PinMachine:
                    // æ’拔钉机处理分支:获取插拔钉机实例并处理其上下层请求
                    HandlePinMachine(conveyorLine, childDeviceCode, layer);
                    break; // å¤„理完毕,跳出 switch
            }
        }
        /// <summary>
        /// å¤„理拘束机的物料/出料请求
        /// </summary>
        /// <remarks>
        /// æŸ¥æ‰¾æ‹˜æŸæœºè®¾å¤‡ï¼ŒèŽ·å–å½“å‰å±‚çº§çš„ç‰©æ–™è¯·æ±‚å’Œå‡ºæ–™è¯·æ±‚çŠ¶æ€ï¼Œ
        /// ç„¶åŽè°ƒç”¨é€šç”¨å¤„理逻辑 ProcessDeviceRequest。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象,用于通知目标地址和 ACK</param>
        /// <param name="childDeviceCode">子设备编码,用于精确定位</param>
        /// <param name="layer">设备层级,决定读取上层还是下层的请求标志</param>
        private void HandleConstraintMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer)
        {
            // ä»Žå…¨å±€è®¾å¤‡åˆ—表中查找名为"拘束机"的拘束机设备实例
            var constraint = FindDevice<ConstraintMachine>("拘束机");
            if (constraint == null)
            {
                // æœªæ‰¾åˆ°æ‹˜æŸæœºè®¾å¤‡ï¼Œå·²åœ¨ FindDevice ä¸­è®°å½•日志,此处直接返回
                return;
            }
            // èŽ·å–å½“å‰å±‚çº§ï¼ˆä¸Šå±‚/下层)的物料请求标志,非零表示设备需要物料
            bool materialRequest = GetConstraintFlag(constraint, layer, isMaterial: true);
            // èŽ·å–å½“å‰å±‚çº§ï¼ˆä¸Šå±‚/下层)的出料请求标志,非零表示设备有货要出
            bool outputRequest = GetConstraintFlag(constraint, layer, isMaterial: false);
            // æž„造设置输出就绪标志的委托(根据层级写入 Upper æˆ– Lower å¯¹åº”的寄存器)
            Action<bool> setOutputReady = outputReady =>
                SetConstraintOutputReady(constraint, layer, outputReady);
            // è°ƒç”¨é€šç”¨è¯·æ±‚处理逻辑,传入设备类型描述用于日志记录
            ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "拘束机");
        }
        /// <summary>
        /// å¤„理插拔钉机的物料/出料请求
        /// </summary>
        /// <remarks>
        /// æŸ¥æ‰¾æ’拔钉机设备,获取当前层级的物料请求和出料请求状态,
        /// ç„¶åŽè°ƒç”¨é€šç”¨å¤„理逻辑 ProcessDeviceRequest。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象,用于通知目标地址和 ACK</param>
        /// <param name="childDeviceCode">子设备编码,用于精确定位</param>
        /// <param name="layer">设备层级,决定读取上层还是下层的请求标志</param>
        private void HandlePinMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer)
        {
            // ä»Žå…¨å±€è®¾å¤‡åˆ—表中查找名为"插拔钉机"的插拔钉机设备实例
            var pinMachine = FindDevice<PinMachine>("插拔钉机");
            if (pinMachine == null)
            {
                // æœªæ‰¾åˆ°æ’拔钉机设备,已在 FindDevice ä¸­è®°å½•日志,此处直接返回
                return;
            }
            // èŽ·å–å½“å‰å±‚çº§ï¼ˆä¸Šå±‚/下层)的物料请求标志,非零表示设备需要物料
            bool materialRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: true);
            // èŽ·å–å½“å‰å±‚çº§ï¼ˆä¸Šå±‚/下层)的出料请求标志,非零表示设备有货要出
            bool outputRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: false);
            // æž„造设置输出就绪标志的委托(根据层级写入 Upper æˆ– Lower å¯¹åº”的寄存器)
            Action<bool> setOutputReady = outputReady =>
                SetPinMachineOutputReady(pinMachine, layer, outputReady);
            // è°ƒç”¨é€šç”¨è¯·æ±‚处理逻辑,传入设备类型描述用于日志记录
            ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "插拔钉机");
        }
        /// <summary>
        /// æŸ¥æ‰¾æŒ‡å®šåç§°çš„设备
        /// </summary>
        /// <remarks>
        /// ä»Žå…¨å±€è®¾å¤‡åˆ—表 Storage.Devices ä¸­é€šè¿‡è®¾å¤‡ç±»åž‹å’Œåç§°æŸ¥æ‰¾è®¾å¤‡å®žä¾‹ã€‚
        /// è¿™æ˜¯ä¸€ä¸ªæ³›åž‹æ–¹æ³•,可以适用于 ConstraintMachine、PinMachine ç­‰å¤šç§è®¾å¤‡ç±»åž‹ã€‚
        /// </remarks>
        /// <typeparam name="T">设备类型,必须是引用类型(如 ConstraintMachine、PinMachine)</typeparam>
        /// <param name="deviceName">设备名称,用于精确匹配设备的 DeviceName å±žæ€§</param>
        /// <returns>找到的设备实例,未找到则返回 null</returns>
        private T? FindDevice<T>(string deviceName) where T : class
        {
            // OfType<T>() ç­›é€‰å‡ºæŒ‡å®šç±»åž‹çš„设备,FirstOrDefault æŒ‰åç§°ç²¾ç¡®åŒ¹é…
            var device = Storage.Devices.OfType<T>().FirstOrDefault(d => GetDeviceName(d) == deviceName);
            if (device == null)
            {
                // è®¾å¤‡æœªæ‰¾åˆ°æ—¶è®°å½•调试日志,方便排查配置问题
                _logger.LogDebug("FindDevice:未找到 {DeviceName}", deviceName);
            }
            return device; // å¯èƒ½ä¸º null,由调用方负责 null æ£€æŸ¥
        }
        /// <summary>
        /// é€šè¿‡å¤šæ€èŽ·å–ä»»æ„è®¾å¤‡çš„åç§°
        /// </summary>
        /// <remarks>
        /// ä½¿ç”¨ switch è¡¨è¾¾å¼æ ¹æ®è®¾å¤‡çš„具体类型获取其 DeviceName å±žæ€§ï¼Œ
        /// é¿å…ä¸ºæ¯ç§è®¾å¤‡ç±»åž‹ç¼–写独立的反射或属性访问代码。
        /// å½“前支持 ConstraintMachine å’Œ PinMachine ä¸¤ç§ç±»åž‹ã€‚
        /// </remarks>
        /// <typeparam name="T">设备类型</typeparam>
        /// <param name="device">设备实例,非空</param>
        /// <returns>设备的名称字符串,如果类型不匹配则返回空字符串</returns>
        private static string GetDeviceName<T>(T device) where T : class
        {
            // æ¨¡å¼åŒ¹é…ï¼šæ ¹æ®è®¾å¤‡çš„具体运行时类型返回对应的 DeviceName
            return device switch
            {
                ConstraintMachine cm => cm.DeviceName, // æ‹˜æŸæœºè¿”回其设备名称
                PinMachine pm => pm.DeviceName,       // æ’拔钉机返回其设备名称
                _ => string.Empty                     // æœªçŸ¥ç±»åž‹è¿”回空字符串(理论上不会走到这里)
            };
        }
        /// <summary>
        /// èŽ·å–æ‹˜æŸæœºçš„è¯·æ±‚æ ‡å¿—ï¼ˆç‰©æ–™è¯·æ±‚æˆ–å‡ºæ–™è¯·æ±‚ï¼‰
        /// </summary>
        /// <remarks>
        /// æ ¹æ® isMaterial å‚数决定读取物料请求还是出料请求寄存器,
        /// å†æ ¹æ® layer å‚数决定读取上层(Upper)还是下层(Lower)寄存器。
        /// è¿”回值表示请求是否有效(非零为有效)。
        /// </remarks>
        /// <param name="constraint">拘束机设备实例,用于读取 PLC å¯„存器值</param>
        /// <param name="layer">设备层级,决定读取 Upper è¿˜æ˜¯ Lower å¯„存器</param>
        /// <param name="isMaterial">true=读取物料请求标志,false=读取出料请求标志</param>
        /// <returns>请求标志是否有效(true=有请求,false=无请求)</returns>
        private bool GetConstraintFlag(ConstraintMachine constraint, Layer layer, bool isMaterial)
        {
            // æ ¹æ® isMaterial é€‰æ‹©å¯¹åº”的寄存器名称对(物料请求或出料请求)
            var (materialKey, outputKey) = isMaterial
                ? (ConstraintMachineDBName.MaterialRequestUpper, ConstraintMachineDBName.MaterialRequestLower)   // ç‰©æ–™è¯·æ±‚
                : (ConstraintMachineDBName.OutputRequestUpper, ConstraintMachineDBName.OutputRequestLower);        // å‡ºæ–™è¯·æ±‚
            // æ ¹æ® layer é€‰æ‹©å…·ä½“使用 Upper è¿˜æ˜¯ Lower ç‰ˆæœ¬çš„寄存器
            var key = layer == Layer.Upper ? materialKey : outputKey;
            // è¯»å–寄存器值,非零表示有请求,返回布尔值
            return constraint.GetValue<ConstraintMachineDBName, short>(key) != 0;
        }
        /// <summary>
        /// èŽ·å–æ’æ‹”é’‰æœºçš„è¯·æ±‚æ ‡å¿—ï¼ˆç‰©æ–™è¯·æ±‚æˆ–å‡ºæ–™è¯·æ±‚ï¼‰
        /// </summary>
        /// <remarks>
        /// ä¸Ž GetConstraintFlag é€»è¾‘相同,但操作对象是插拔钉机(PinMachine)。
        /// æ ¹æ® isMaterial å‚数决定读取物料请求还是出料请求寄存器,
        /// å†æ ¹æ® layer å‚数决定读取上层(Upper)还是下层(Lower)寄存器。
        /// </remarks>
        /// <param name="pinMachine">插拔钉机设备实例,用于读取 PLC å¯„存器值</param>
        /// <param name="layer">设备层级,决定读取 Upper è¿˜æ˜¯ Lower å¯„存器</param>
        /// <param name="isMaterial">true=读取物料请求标志,false=读取出料请求标志</param>
        /// <returns>请求标志是否有效(true=有请求,false=无请求)</returns>
        private bool GetPinMachineFlag(PinMachine pinMachine, Layer layer, bool isMaterial)
        {
            // æ ¹æ® isMaterial é€‰æ‹©å¯¹åº”的寄存器名称对(物料请求或出料请求)
            var (materialKey, outputKey) = isMaterial
                ? (PinMachineDBName.MaterialRequestUpper, PinMachineDBName.MaterialRequestLower)   // ç‰©æ–™è¯·æ±‚
                : (PinMachineDBName.OutputRequestUpper, PinMachineDBName.OutputRequestLower);       // å‡ºæ–™è¯·æ±‚
            // æ ¹æ® layer é€‰æ‹©å…·ä½“使用 Upper è¿˜æ˜¯ Lower ç‰ˆæœ¬çš„寄存器
            var key = layer == Layer.Upper ? materialKey : outputKey;
            // è¯»å–寄存器值,非零表示有请求,返回布尔值
            return pinMachine.GetValue<PinMachineDBName, short>(key) != 0;
        }
        /// <summary>
        /// è®¾ç½®æ‹˜æŸæœºçš„输出就绪标志
        /// </summary>
        /// <remarks>
        /// å‘拘束机的 PLC å¯„存器写入输出就绪状态,告知拘束机可以继续出料。
        /// æ ¹æ® layer å‚数决定写入上层(ConstraintTrayOutputReadyUpper)还是下层(ConstraintTrayOutputReadyLower)寄存器。
        /// </remarks>
        /// <param name="constraint">拘束机设备实例,用于写入 PLC å¯„存器</param>
        /// <param name="layer">设备层级,决定写入 Upper è¿˜æ˜¯ Lower å¯„存器</param>
        /// <param name="outputReady">输出就绪状态,true=可以出料,false=不能出料</param>
        private void SetConstraintOutputReady(ConstraintMachine constraint, Layer layer, bool outputReady)
        {
            // æ ¹æ® layer é€‰æ‹©å¯¹åº”的输出就绪寄存器(Upper æˆ– Lower)
            var key = layer == Layer.Upper
                ? ConstraintMachineDBName.ConstraintTrayOutputReadyUpper   // ä¸Šå±‚输出就绪寄存器
                : ConstraintMachineDBName.ConstraintTrayOutputReadyLower;  // ä¸‹å±‚输出就绪寄存器
            // å‘ PLC å†™å…¥å€¼ï¼ŒoutputReady ä¸º true æ—¶å†™å…¥ 1,否则写入 0
            constraint.SetValue(key, outputReady ? true : false);
        }
        /// <summary>
        /// è®¾ç½®æ’拔钉机的输出就绪标志
        /// </summary>
        /// <remarks>
        /// å‘插拔钉机的 PLC å¯„存器写入输出就绪状态,告知插拔钉机可以继续出料。
        /// æ ¹æ® layer å‚数决定写入上层(PlugPinTrayOutputReadyUpper)还是下层(PlugPinTrayOutputReadyLower)寄存器。
        /// </remarks>
        /// <param name="pinMachine">插拔钉机设备实例,用于写入 PLC å¯„存器</param>
        /// <param name="layer">设备层级,决定写入 Upper è¿˜æ˜¯ Lower å¯„存器</param>
        /// <param name="outputReady">输出就绪状态,true=可以出料,false=不能出料</param>
        private void SetPinMachineOutputReady(PinMachine pinMachine, Layer layer, bool outputReady)
        {
            // æ ¹æ® layer é€‰æ‹©å¯¹åº”的输出就绪寄存器(Upper æˆ– Lower)
            var key = layer == Layer.Upper
                ? PinMachineDBName.PlugPinTrayOutputReadyUpper   // ä¸Šå±‚输出就绪寄存器
                : PinMachineDBName.PlugPinTrayOutputReadyLower; // ä¸‹å±‚输出就绪寄存器
            // å‘ PLC å†™å…¥å€¼ï¼ŒoutputReady ä¸º true æ—¶å†™å…¥ 1,否则写入 0
            pinMachine.SetValue(key, outputReady ? true : false);
        }
        /// <summary>
        /// å¤„理设备请求的核心逻辑
        /// </summary>
        /// <remarks>
        /// æ ¹æ®ç‰©æ–™è¯·æ±‚和出料请求的状态:
        /// - å¦‚果有物料请求,设置目标地址并发送 ACK
        /// - å¦‚果有出料请求,设置设备的输出就绪标志
        /// æ ¹æ®ç‰©æ–™è¯·æ±‚和出料请求的状态决定行为:
        /// - å¦‚果设备需要物料(materialRequest=true),设置输送线的目标地址为 1(有料进来)并回复 ACK ç¡®è®¤ä¿¡å·
        /// - å¦‚果设备不需要物料(materialRequest=false),则通知设备可以继续出料(setOutputReady)
        ///
        /// è¿™æ˜¯æ‹˜æŸæœºå’Œæ’拔钉机共用的处理逻辑,设备特有的部分已在此方法外封装。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象</param>
        /// <param name="childDeviceCode">当前子设备编码</param>
        /// <param name="getMaterialRequest">获取物料请求状态的委托</param>
        /// <param name="getOutputRequest">获取出料请求状态的委托</param>
        /// <param name="setOutputReady">设置输出就绪标志的委托</param>
        /// <param name="deviceType">设备类型描述</param>
        /// <param name="conveyorLine">输送线设备对象,用于写入目标地址(Target)和 ACK ä¿¡å·</param>
        /// <param name="childDeviceCode">子设备编码,用于精确定位写入哪个子设备</param>
        /// <param name="materialRequest">物料请求标志,true=设备当前需要补充物料</param>
        /// <param name="outputRequest">出料请求标志,true=设备当前有物料需要输出(配合 materialRequest=false æ—¶ä½¿ç”¨ï¼‰</param>
        /// <param name="setOutputReady">设置设备输出就绪标志的委托,根据层级写入 Upper æˆ– Lower å¯„存器</param>
        /// <param name="deviceType">设备类型描述字符串,用于日志输出</param>
        private void ProcessDeviceRequest(
            CommonConveyorLine conveyorLine,
            string childDeviceCode,
            Func<bool> getMaterialRequest,
            Func<bool> getOutputRequest,
            bool materialRequest,
            bool outputRequest,
            Action<bool> setOutputReady,
            string deviceType)
        {
            // èŽ·å–ç‰©æ–™è¯·æ±‚çŠ¶æ€
            bool materialReq = getMaterialRequest();
            // èŽ·å–å‡ºæ–™è¯·æ±‚çŠ¶æ€
            bool outputReq = getOutputRequest();
            // è®°å½•当前请求状态的调试日志,供排查问题使用
            _logger.LogDebug("ProcessDeviceRequest:{DeviceType},子设备: {ChildDeviceCode},物料请求: {MaterialReq},出料请求: {OutputReq}",
                deviceType, childDeviceCode, materialReq, outputReq);
            QuartzLogger.Debug($"ProcessDeviceRequest:{deviceType},子设备: {childDeviceCode},物料请求: {materialReq},出料请求: {outputReq}", conveyorLine.DeviceCode);
                deviceType, childDeviceCode, materialRequest, outputRequest);
            // åŒæ­¥å†™å…¥ Quartz æ—¥å¿—文件(双写可追溯,这里保留与原逻辑一致的行为)
            QuartzLogger.Debug($"ProcessDeviceRequest:{deviceType},子设备: {childDeviceCode},物料请求: {materialRequest},出料请求: {outputRequest}", conveyorLine.DeviceCode);
            // å¦‚果设备需要物料
            if (materialReq)
            // åˆ†æ”¯åˆ¤æ–­ï¼šè®¾å¤‡æ˜¯éœ€è¦ç‰©æ–™è¿˜æ˜¯éœ€è¦å‡ºæ–™
            if (materialRequest)
            {
                // è®¾ç½®ç›®æ ‡åœ°å€ä¸º 1(表示有料进来)
                // è®¾å¤‡éœ€è¦ç‰©æ–™ -> é€šçŸ¥è¾“送线有料过来
                // 1. è®¾ç½®ç›®æ ‡åœ°å€ä¸º 1,表示"有料进入"
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
                // å›žå¤ ACK ç¡®è®¤ä¿¡å·
                // 2. å›žå¤ ACK ç¡®è®¤ä¿¡å·ï¼Œå‘ŠçŸ¥è®¾å¤‡å·²æ”¶åˆ°è¯·æ±‚
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
                // 3. è®°å½•信息日志,表明已完成目标地址设置和 ACK å›žå¤
                _logger.LogInformation("ProcessDeviceRequest:{DeviceType} éœ€è¦ç‰©æ–™ï¼Œå·²è®¾ç½®ç›®æ ‡åœ°å€å’ŒACK", deviceType);
                QuartzLogger.Info($"ProcessDeviceRequest:{deviceType} éœ€è¦ç‰©æ–™ï¼Œå·²è®¾ç½®ç›®æ ‡åœ°å€å’ŒACK", conveyorLine.DeviceCode);
            }
            else
            {
                // è®¾å¤‡ä¸éœ€è¦ç‰©æ–™ï¼Œè®¾ç½®è¾“出就绪标志
                // é€šçŸ¥è®¾å¤‡å¯ä»¥ç»§ç»­å‡ºæ–™
                setOutputReady(outputReq);
                // è®¾å¤‡ä¸éœ€è¦ç‰©æ–™ -> é€šçŸ¥è®¾å¤‡å¯ä»¥ç»§ç»­å‡ºæ–™ï¼ˆæ— è®ºå½“前是否有货要出,都要通知)
                // outputRequest è¡¨ç¤ºè®¾å¤‡å½“前是否确实有货,如果没有货则 outputReady=false,设备收到后等待
                setOutputReady(outputRequest);
            }
        }
        /// <summary>
        /// å†™å…¥è°ƒè¯•日志(同时输出到两个日志系统)
        /// </summary>
        /// <remarks>
        /// ç»Ÿä¸€å…¥å£ç‚¹æ—¥å¿—格式,同时向 Microsoft.Extensions.Logging å’Œ QuartzLogger å†™å…¥ï¼Œ
        /// ä¿è¯æ—¥å¿—既能在控制台查看也能在文件中追溯。
        /// </remarks>
        /// <param name="conveyorLine">输送线设备对象,用于获取设备编码写入 QuartzLogger</param>
        /// <param name="scenario">场景描述,如"入库下一地址"或"出库下一地址"</param>
        /// <param name="childDeviceCode">子设备编码</param>
        /// <param name="nextAddress">目标设备编码</param>
        private void WriteDebug(CommonConveyorLine conveyorLine, string scenario, string childDeviceCode, string nextAddress)
        {
            // å†™å…¥ç»“构化日志(可被 Serilog ç­‰æ—¥å¿—框架捕获)
            _logger.LogDebug("Handle{Scenario}:子设备: {ChildDeviceCode},目标地址: {NextAddress}",
                scenario, childDeviceCode, nextAddress);
            // å†™å…¥ Quartz ä¸“用日志文件(供定时任务轨迹追踪)
            QuartzLogger.Debug($"Handle{scenario}:子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -229,12 +229,12 @@
                    }
                    // æ£€æŸ¥ä»»åŠ¡æ€»æ•°æ˜¯å¦æœªè¾¾åˆ°ä¸Šé™
                    if (latestState.RobotTaskTotalNum < MaxTaskTotalNum)
                    {
                    //if (latestState.RobotTaskTotalNum < MaxTaskTotalNum)
                    //{
                        // è°ƒç”¨å·¥ä½œæµç¼–排器执行任务
                        // ç¼–排器会根据当前状态决定下一步动作
                        await _workflowOrchestrator.ExecuteAsync(latestState, task, ipAddress);
                    }
                    //}
                }
            }
            catch (Exception ex)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -79,6 +79,26 @@
                    state.OperStatus = "Homed";
                    return true;
                // æœºå™¨äººæ­£åœ¨è¿è¡Œ
                case "running":
                    state.OperStatus = "Running";
                    return true;
                // æœºå™¨äººæ­£åœ¨æš‚停
                case "pausing":
                    state.OperStatus = "Pausing";
                    return true;
                // æœºå™¨äººæ­£åœ¨é¢„热
                case "warming":
                    state.OperStatus = "Warming";
                    return true;
                // æœºå™¨äººæ­£åœ¨æ€¥åœ
                case "emstoping":
                    state.OperStatus = "Emstoping";
                    return true;
                // æœºå™¨äººæ­£åœ¨å–è´§
                case "picking":
                    state.CurrentAction = "Picking";
@@ -158,28 +178,6 @@
                    }
                    return false;
                }
                // ==================== è¿è¡ŒçŠ¶æ€å‘½ä»¤ï¼ˆç»­ï¼‰ ====================
                // æœºå™¨äººæ­£åœ¨è¿è¡Œ
                case "running":
                    state.OperStatus = "Running";
                    return true;
                // æœºå™¨äººæ­£åœ¨æš‚停
                case "pausing":
                    state.OperStatus = "Pausing";
                    return true;
                // æœºå™¨äººæ­£åœ¨é¢„热
                case "warming":
                    state.OperStatus = "Warming";
                    return true;
                // æœºå™¨äººæ­£åœ¨æ€¥åœ
                case "emstoping":
                    state.OperStatus = "Emstoping";
                    return true;
                // ==================== æ¨¡å¼åˆ‡æ¢å‘½ä»¤ ====================
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using WIDESEA_Core;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -109,16 +110,21 @@
            // æ£€æŸ¥æ˜¯å¦æ»¡è¶³è‡ªåŠ¨æŽ§åˆ¶æ¡ä»¶ï¼š
            // 1. è¿è¡Œæ¨¡å¼ä¸ºè‡ªåŠ¨ï¼ˆ2)
            // 2. æŽ§åˆ¶æ¨¡å¼ä¸ºå®¢æˆ·ç«¯æŽ§åˆ¶ï¼ˆ1)
            // 3. è¿è¡ŒçŠ¶æ€ä¸æ˜¯ Running(说明已完成当前动作)
            if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running")
            // 3. è¿è¡ŒçŠ¶æ€æ˜¯ Running
            if (latestState.RobotRunMode == 2 /*&& latestState.RobotControlMode == 1*/ && latestState.OperStatus == "Running")
            {
                if(latestState.CurrentAction == "Picking" ||  latestState.CurrentAction == "Puting")
                {
                    return;
                }
                // ========== å–货完成后的放货处理 ==========
                // æ¡ä»¶ï¼š
                // - å½“前动作是 PickFinished æˆ– AllPickFinished(取货完成)
                // - æ‰‹è‡‚上有物料(RobotArmObject == 1)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPickFinish(已记录取货完成)
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
                    && latestState.RobotArmObject == 1
                    && latestState.RobotArmObject == 0
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum);
@@ -126,14 +132,14 @@
                    // å‘送放货指令
                    await HandlePickFinishedStateAsync(task, ipAddress);
                }
                // ========== æ”¾è´§å®ŒæˆåŽçš„取货处理 ==========
                // ========== åˆå§‹åŒ–或者放货完成后的取货处理 ==========
                // æ¡ä»¶ï¼š
                // - å½“前动作是 PutFinished、AllPutFinished æˆ– null(放货完成)
                // - è¿è¡ŒçŠ¶æ€ä¸º Homed(已归位)
                // - æ‰‹è‡‚上无物料(RobotArmObject == 0)
                // - ä»»åŠ¡çŠ¶æ€ä¸º RobotPutFinish æˆ–不是 RobotExecuting
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction == null)
                    && latestState.OperStatus == "Homed"
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished" || latestState.CurrentAction.IsNullOrEmpty())
                    && latestState.RobotArmObject == 0
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
                    || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/ViewGrid.vue
@@ -456,6 +456,7 @@
        <vol-table
          ref="table"
          :single="single"
          :expand="tableExpand"
          :rowKey="rowKey"
          :loadTreeChildren="loadTreeTableChildren"
          @loadBefore="loadTableBefore"
Code/WMS/WIDESEA_WMSClient/src/components/basic/ViewGrid/props.js
@@ -1,55 +1,61 @@
let props = {
  columns: {//当前表的配置信息
  columns: {
    type: Array,
    default: () => {
      return [];
    }
  },
  detail: {//从表明细配置
  detail: {
    type: Object,
    default: () => {
      return {
        columns: [],//从表列
        sortName: ""//从表排序字段
        columns: [],
        sortName: ""
      };
    }
  },
  editFormFields: {//新建、编辑字段(key/value)
  editFormFields: {
    type: Object,
    default: () => {
      return {};
    }
  },
  editFormOptions: {//新建、编辑配置信息
  editFormOptions: {
    type: Array,
    default: () => {
      return [];
    }
  },
  searchFormFields: {//查询字段(key/value)
  searchFormFields: {
    type: Object,
    default: () => {
      return {};
    }
  },
  searchFormOptions: {//查询配置信息(key/value)
  searchFormOptions: {
    type: Array,
    default: () => {
      return [];
    }
  },
  table: {//表的配置信息:主键、排序等
  table: {
    type: Object,
    default: () => {
      return {};
    }
  },
  extend: {//表的扩展方法与组件都合并到此属性中
  tableExpand: {
    type: Object,
    default: () => {
      return {};
    }
  },
  extend: {
    type: Object,
    default: () => {
      return {};
    }
  }
}
};
export default props;
export default props;
Code/WMS/WIDESEA_WMSClient/src/components/basic/VolTable.vue
@@ -21,6 +21,7 @@
      @select="userSelect"
      @select-all="userSelect"
      @selection-change="selectionChange"
      @expand-change="expandChange"
      @row-dblclick="rowDbClick"
      @row-click="rowClick"
      @header-click="headerClick"
@@ -38,6 +39,24 @@
      style="width: 100%"
      :scrollbar-always-on="true"
    >
      <el-table-column
        v-if="showExpand"
        type="expand"
        :fixed="expand.fixed"
        :width="expand.width || 55"
      >
        <template #default="scope">
          <div class="expand-cell">
            <table-render
              v-if="expand.render && typeof expand.render == 'function'"
              :row="scope.row"
              :index="scope.$index"
              :column="expand"
              :render="expand.render"
            ></table-render>
          </div>
        </template>
      </el-table-column>
      <el-table-column
        v-if="ck"
        type="selection"
@@ -559,6 +578,12 @@
        return 1;
      },
    },
    expand: {
      type: Object,
      default: () => {
        return {};
      },
    },
    pagination: {
      type: Object,
      default: function () {
@@ -834,6 +859,9 @@
    this.defaultLoadPage && this.load();
  },
  computed: {
    showExpand() {
      return !!(this.expand && typeof this.expand.render === "function");
    },
    filterColumns() {
      return this.columns.filter((x, index) => {
        if (!x.field) {
@@ -844,6 +872,12 @@
    },
  },
  methods: {
    expandChange(row, expandedRows) {
      if (this.expand && typeof this.expand.onChange === "function") {
        this.expand.onChange(row, expandedRows, this);
      }
      this.$emit("expandChange", { row, expandedRows });
    },
    watchRowSelectChange(newLen, oldLen) {
      if (newLen < oldLen && this.selectRows.length) {
        this.selectRows = [];
@@ -1784,6 +1818,9 @@
  border-bottom: 1px solid;
  padding-bottom: 2px;
}
.vol-table .expand-cell {
  padding: 8px 0;
}
.vol-table .empty-tag {
  border: none;
  background: none;
Code/WMS/WIDESEA_WMSClient/src/views/stock/stockInfo.vue
@@ -1,154 +1,478 @@
<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/stock/stockInfo.js";
  import { ref, defineComponent } from "vue";
  export default defineComponent({
    setup() {
      const table = ref({
        key: "id",
        footer: "Foots",
        cnName: "库存信息",
        name: "stockInfo",
        url: "/StockInfo/",
        sortName: "id",
      });
      const editFormFields = ref({
        deviceCode: "",
        deviceName: "",
        deviceType: "",
        deviceStatus: "",
        deviceIp: "",
        devicePort: "",
        devicePlcType: "",
        deviceRemark: "",
      });
      const editFormOptions = ref([
       [
        {field:'palletCode',title:'托盘编号',type:'string'},
        {field:'locationCode',title:'货位编号',type:'string'},
       ]
  <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/stockInfo.js";
import {
  defineComponent,
  getCurrentInstance,
  h,
  reactive,
  ref,
  resolveComponent,
} from "vue";
const TEXT = {
  pageName: "库存信息",
  palletCode: "托盘编号",
  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: "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 searchFormFields = ref({
        palletCode: "",
        locationCode: "",
      });
      const searchFormOptions = ref([
        [
          { title: "托盘编号", field: "palletCode" },
          { title: "货位编号", field: "locationCode" },
        ],
      ]);
      const columns = ref([
        {
          field: "id",
          title: "Id",
          type: "int",
          width: 90,
          hidden: true,
          readonly: true,
          require: true,
          align: "left",
        },
        {
          field: "palletCode",
          title: "托盘编号",
          type: "string",
          width: 90,
          align: "left",
        },
        {
          field: "locationCode",
          title: "货位编号",
          type: "string",
          width: 150,
          align: "left",
        },
        // {
        //   field: "isFull",
        //   title: "是否满盘",
        //   type: "string",
        //   width: 150,
        //   align: "left",
        //   bind: { key: "yesno", data: [] },
        // },
         {
          field: "warehouseId",
          title: "仓库",
          type: "select",
          width: 100,
          align: "left",
          bind: { key: "warehouseEnum", data: [] },
        },
        {
          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",
          hidden:true
        },
        {
          field: "modifyDate",
          title: "修改时间",
          type: "datetime",
          width: 160,
          align: "left",
          hidden:true
        },
        {
          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>
    };
    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/Sys_Log.vue
@@ -62,7 +62,7 @@
        field: "beginDate",
        title: "开始时间",
        type: "datetime",
        width: 140,
        width: 70,
        align: "left",
        sortable: true,
      },
@@ -70,7 +70,7 @@
        field: "elapsedTime",
        title: "时长",
        type: "int",
        width: 60,
        width: 40,
        align: "left",
      },
      {
@@ -86,14 +86,14 @@
        field: "requestParam",
        title: "请求参数",
        type: "string",
        width: 70,
        width: 100,
        align: "left",
      },
      {
        field: "responseParam",
        title: "响应参数",
        type: "string",
        width: 70,
        width: 100,
        align: "left",
      },
      {
@@ -153,5 +153,4 @@
    };
  },
});
</script>
</script>
Code/WMS/WIDESEA_WMSClient/src/views/taskinfo/task_hty.vue
@@ -96,7 +96,7 @@
        type: "int",
        width: 120,
        align: "left",
        bind: { key: "taskType", data: [] },
        bind: { key: "taskTypeEnum", data: [] },
      },
      {
        field: "taskStatus",
Code/WMS/WIDESEA_WMSServer/WIDESEA_BasicService/LocationInfoService.cs
@@ -25,6 +25,7 @@
        private readonly IRepository<Dt_Task> _taskRepository;
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        private readonly IRecordService _recordService;
        private readonly IRepository<Dt_Warehouse> _warehouseRepository;
        /// <summary>
        /// æž„造函数
@@ -36,6 +37,7 @@
            IRepository<Dt_LocationInfo> baseDal,
            IRepository<Dt_Task> taskRepository,
            IRepository<Dt_StockInfo> stockInfoRepository,
            IRepository<Dt_Warehouse> warehouseRepository,
            IMapper mapper,
            IRecordService recordService) : base(baseDal)
        {
@@ -43,6 +45,7 @@
            _stockInfoRepository = stockInfoRepository;
            _mapper = mapper;
            _recordService = recordService;
            _warehouseRepository = warehouseRepository;
        }
        /// <summary>
@@ -141,10 +144,10 @@
                x.LocationStatus == LocationStatusEnum.Free.GetHashCode());
            return locations?
                .OrderBy(x => x.Layer)
                .ThenByDescending(x => x.Depth)
                .ThenBy(x => x.Column)
                .ThenBy(x => x.Row)
                .OrderByDescending(x => x.Depth)  // 1. æ·±åº¦ä¼˜å…ˆï¼ˆä»Žå¤§åˆ°å°ï¼‰
                .ThenBy(x => x.Layer)             // 2. å±‚æ•°
                .ThenBy(x => x.Column)            // 3. åˆ—
                .ThenBy(x => x.Row)               // 4. è¡Œ
                .FirstOrDefault();
        }
@@ -494,11 +497,18 @@
        /// <param name="layer">层数</param>
        /// <param name="depth">深度</param>
        /// <returns>货位信息对象</returns>
        private static Dt_LocationInfo CreateLocationInfo(string roadwayNo, int row, int col, int layer, int depth)
        private Dt_LocationInfo CreateLocationInfo(string roadwayNo, int row, int col, int layer, int depth)
        {
            var warehouse = _warehouseRepository.QueryData(x => x.WarehouseCode == roadwayNo).FirstOrDefault();
            if (warehouse == null)
            {
                throw new InvalidOperationException($"未找到巷道编号为 {roadwayNo} çš„仓库信息");
            }
            return new Dt_LocationInfo
            {
                WarehouseId = 0,
                WarehouseId = warehouse.WarehouseId,
                Row = row,
                Column = col,
                Layer = layer,
@@ -507,7 +517,7 @@
                EnableStatus = EnableStatusEnum.Normal.GetHashCode(),
                LocationStatus = LocationStatusEnum.Free.GetHashCode(),
                LocationType = LocationTypeEnum.Undefined.GetHashCode(),
                LocationCode = $"{row:D3}-{col:D3}-{layer:D3}",
                LocationCode = $"{roadwayNo}-{row:D3}-{col:D3}-{layer:D3}",
                LocationName = $"{roadwayNo}巷道{row:D3}行{col:D3}列{layer:D3}层{depth:D2}æ·±"
            };
        }
Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Task/WMSTaskDTO.cs
@@ -52,6 +52,16 @@
        public string TargetAddress { get; set; }
        /// <summary>
        /// å½“前位置
        /// </summary>
        public string CurrentAddress { get; set; }
        /// <summary>
        /// ä¸‹ä¸€åœ°å€
        /// </summary>
        public string NextAddress { get; set; }
        /// <summary>
        /// ä¼˜å…ˆçº§
        /// </summary>
        public int Grade { get; set; }
@@ -72,4 +82,4 @@
        public int PalletType { get; set; }
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -172,24 +172,19 @@
        public Task<WebResponseContent> OutTaskComplete(OutTaskCompleteDto outTaskCompleteDto);
        /// <summary>
        /// ä»»åŠ¡å®ŒæˆæŽ¥å£
        /// </summary>
        /// <param name="wCSTask"></param>
        /// <returns></returns>
        //public Task<WebResponseContent> TaskCompleted(WCSTaskDTO wCSTask);
        /// <summary>
        /// ä»»åŠ¡å–æ¶ˆ
        /// </summary>
        /// <param name="taskCancelDto">请求参数</param>
        /// <returns></returns>
        public Task<AGVResponse> TaskCancelAsync(TaskCancelDto taskCancelDto);
        /// <summary>
        /// å–放货完成
        /// </summary>
        /// <param name="taskCompleteDto">请求参数</param>
        /// <returns></returns>
        public Task<AGVResponse> TaskCompleteAsync(TaskCompleteDto taskCompleteDto);
        /// <summary>
        /// è¾“送线申请进入
        /// </summary>
@@ -199,4 +194,4 @@
        #endregion æžå·åº“任务模块
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_SystemService/Sys_DictionaryService.cs
@@ -204,12 +204,12 @@
                            List<object> data = new List<object>();
                            {
                                Type type = typeof(TaskTypeEnum);
                                List<int> enums = Enum.GetValues(typeof(TaskTypeEnum)).Cast<int>().ToList();
                                Type type = typeof(TaskInboundTypeEnum);
                                List<int> enums = Enum.GetValues(typeof(TaskInboundTypeEnum)).Cast<int>().ToList();
                                int index = 0;
                                foreach (var item in enums)
                                {
                                    FieldInfo? fieldInfo = typeof(TaskTypeEnum).GetField(((TaskTypeEnum)item).ToString());
                                    FieldInfo? fieldInfo = typeof(TaskInboundTypeEnum).GetField(((TaskInboundTypeEnum)item).ToString());
                                    DescriptionAttribute? description = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
                                    if (description != null)
                                    {
@@ -223,6 +223,65 @@
                                }
                            }
                            {
                                Type type = typeof(TaskOutboundTypeEnum);
                                List<int> enums = Enum.GetValues(typeof(TaskOutboundTypeEnum)).Cast<int>().ToList();
                                int index = 0;
                                foreach (var item in enums)
                                {
                                    FieldInfo? fieldInfo = typeof(TaskOutboundTypeEnum).GetField(((TaskOutboundTypeEnum)item).ToString());
                                    DescriptionAttribute? description = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
                                    if (description != null)
                                    {
                                        data.Add(new { key = item.ToString(), value = description.Description });
                                    }
                                    else
                                    {
                                        data.Add(new { key = item.ToString(), value = item.ToString() });
                                    }
                                    index++;
                                }
                            }
                            {
                                Type type = typeof(TaskRelocationTypeEnum);
                                List<int> enums = Enum.GetValues(typeof(TaskRelocationTypeEnum)).Cast<int>().ToList();
                                int index = 0;
                                foreach (var item in enums)
                                {
                                    FieldInfo? fieldInfo = typeof(TaskRelocationTypeEnum).GetField(((TaskRelocationTypeEnum)item).ToString());
                                    DescriptionAttribute? description = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
                                    if (description != null)
                                    {
                                        data.Add(new { key = item.ToString(), value = description.Description });
                                    }
                                    else
                                    {
                                        data.Add(new { key = item.ToString(), value = item.ToString() });
                                    }
                                    index++;
                                }
                            }
                            {
                                Type type = typeof(RobotTaskTypeEnum);
                                List<int> enums = Enum.GetValues(typeof(RobotTaskTypeEnum)).Cast<int>().ToList();
                                int index = 0;
                                foreach (var item in enums)
                                {
                                    FieldInfo? fieldInfo = typeof(RobotTaskTypeEnum).GetField(((RobotTaskTypeEnum)item).ToString());
                                    DescriptionAttribute? description = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
                                    if (description != null)
                                    {
                                        data.Add(new { key = item.ToString(), value = description.Description });
                                    }
                                    else
                                    {
                                        data.Add(new { key = item.ToString(), value = item.ToString() });
                                    }
                                    index++;
                                }
                            }
                            result = new VueDictionaryDTO { DicNo = key, Config = "", Data = data };
                        }
                        break;
@@ -491,4 +550,4 @@
            }
        }
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_AGV.cs
@@ -14,13 +14,12 @@
{
    public partial class TaskService
    {
        #region æžå·åº“任务模块
        public string AGV_OutTaskComplete = WIDESEA_Core.Helper.AppSettings.Configuration["AGV_OutTaskComplete"];
        public string WCS_ReceiveTask = WIDESEA_Core.Helper.AppSettings.Configuration["WCS_ReceiveTask"];
        /// <summary>
        /// å‡ºå…¥åº“申请。
        /// æžå·åº“出入库申请
        /// </summary>
        public async Task<AGVResponse> ApplyInOutAsync(ApplyInOutDto applyInOutDto)
        {
@@ -32,7 +31,7 @@
                if (validationMessage != null)
                    return response.Error(validationMessage);
                var existingTask = await BaseDal.QueryFirstAsync(x => x.PalletCode == applyInOutDto.TrayNumber);
                var existingTask = await BaseDal.QueryFirstAsync(x => x.PalletCode == applyInOutDto.TrayNumber || x.OrderNo == applyInOutDto.TaskId);
                if (existingTask != null)
                    return response.Error($"WMS已有当前任务,不可重复下发,任务号:{applyInOutDto.TaskId}");
@@ -61,7 +60,7 @@
        }
        /// <summary>
        /// æ‰‹åŠ¨å‡ºåº“å®Œæˆåé¦ˆç»™AGV。
        /// æ‰‹åŠ¨å‡ºåº“å®Œæˆåé¦ˆç»™AGV
        /// </summary>
        public async Task<WebResponseContent> OutTaskComplete(OutTaskCompleteDto outTaskCompleteDto)
        {
@@ -83,7 +82,7 @@
                    return response.Error(httpResponse?.Msg ?? "AGV接口调用异常");
                if (!httpResponse.Code)
                    return response.Error(string.IsNullOrWhiteSpace(httpResponse.Msg) ? "AGV接口调用失败" : httpResponse.Msg);
                        return response.Error(string.IsNullOrWhiteSpace(httpResponse.Msg) ? "AGV接口调用失败" : httpResponse.Msg);
                var syncResult = await CompleteLocalOutboundAfterAgvAckAsync(task);
                return syncResult.Status ? response.OK(httpResponse.Msg) : syncResult;
@@ -96,7 +95,7 @@
        }
        /// <summary>
        /// è¾“送线申请进入。
        /// è¾“送线申请进入
        /// </summary>
        public async Task<AGVResponse> ApplyEnterAsync(ApplyEnterDto applyEnterDto)
        {
@@ -124,7 +123,7 @@
        }
        /// <summary>
        /// å–放货完成。
        /// å–放货完成
        /// </summary>
        public async Task<AGVResponse> TaskCompleteAsync(TaskCompleteDto taskCompleteDto)
        {
@@ -152,7 +151,7 @@
        }
        /// <summary>
        /// ä»»åŠ¡å–æ¶ˆã€‚
        /// ä»»åŠ¡å–æ¶ˆ
        /// </summary>
        public async Task<AGVResponse> TaskCancelAsync(TaskCancelDto taskCancelDto)
        {
@@ -183,6 +182,7 @@
            }
        }
        #region å‚数验证
        private static string? ValidateApplyInOutRequest(ApplyInOutDto dto)
        {
            if (dto == null) return "请求参数不能为空";
@@ -231,7 +231,10 @@
            if (string.IsNullOrWhiteSpace(dto.TaskId)) return "任务号不能为空";
            return null;
        }
        #endregion å‚数验证
        #region å…·ä½“实现
        // å‡ºå…¥åº“共用创建任务
        private Dt_Task BuildAgvTask(ApplyInOutDto dto)
        {
            var task = new Dt_Task
@@ -265,6 +268,7 @@
            return task;
        }
        // æž„建返回AGV出入库请求体
        private AGVDataDto BuildAgvDataDto(Dt_Task task, ApplyInOutDto dto)
        {
            return new AGVDataDto
@@ -287,6 +291,7 @@
            };
        }
        // å…¥åº“创建
        private async Task<AGVResponse?> CreateAgvInboundTaskAsync(Dt_Task task, ApplyInOutDto dto)
        {
            AGVResponse response = new AGVResponse();
@@ -302,6 +307,7 @@
            return addResult ? null : response.Error("入库任务创建失败");
        }
        // å‡ºåº“创建
        private async Task<AGVResponse?> CreateAgvOutboundTaskAsync(Dt_Task task, ApplyInOutDto dto)
        {
            AGVResponse response = new AGVResponse();
@@ -309,13 +315,13 @@
            if (stockInfo == null)
                return response.Error($"未找到托盘{dto.TrayNumber}的库存信息");
            if (stockInfo.WarehouseId != dto.YinYang)
                return response.Error($"托盘{dto.TrayNumber}不属于当前{(dto.YinYang == 1 ? "阴极" : "阳极")}");
            //if (stockInfo.WarehouseId != dto.YinYang)
            //    return response.Error($"托盘{dto.TrayNumber}不属于当前{(dto.YinYang == 1 ? "阴极" : "阳极")}");
            if (stockInfo.StockStatus != (int)StockStatusEmun.入库完成)
                return response.Error($"托盘{dto.TrayNumber}正在移动中,请稍后!");
            var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode);
            var locationInfo = await _locationInfoService.GetLocationInfo(stockInfo.LocationCode);
            if (locationInfo == null)
                return response.Error($"未找到托盘{stockInfo.LocationCode}的货位信息");
@@ -327,6 +333,14 @@
            task.SourceAddress = stockInfo.LocationCode;
            task.CurrentAddress = stockInfo.LocationCode;
            task.TargetAddress = dto.YinYang == 1 ? "D10020" : "D10090";
            var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task);
            var taskList = new List<WMSTaskDTO> { wmsTaskDto };
            var requestBody = JsonSerializer.Serialize(taskList);
            var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody);
            if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status)
                return response.Error(httpResponse?.Data?.Message ?? "下发WCS失败");
            stockInfo.StockStatus = (int)StockStatusEmun.出库锁定;
            locationInfo.LocationStatus = (int)LocationStatusEnum.InStockLock;
@@ -340,34 +354,22 @@
                _unitOfWorkManage.RollbackTran();
                return response.Error("出库任务创建失败");
            }
            _unitOfWorkManage.CommitTran();
            return null;
        }
        private async Task<WebResponseContent> CompleteLocalOutboundAfterAgvAckAsync(Dt_Task task)
        {
            var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
            if (stockInfo == null)
                return WebResponseContent.Instance.Error($"未找到托盘{task.PalletCode}的库存信息");
            var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode);
            if (locationInfo == null)
                return WebResponseContent.Instance.Error($"未找到托盘{stockInfo.LocationCode}的货位信息");
            if (stockInfo.StockStatus != (int)StockStatusEmun.出库锁定 || locationInfo.LocationStatus != (int)LocationStatusEnum.InStockLock)
                return WebResponseContent.Instance.Error($"当前库存{stockInfo.StockStatus}或者货位{locationInfo.LocationStatus}状态信息错误");
            locationInfo.LocationStatus = (int)LocationStatusEnum.Free;
            task.TaskStatus = (int)TaskOutStatusEnum.Line_OutFinish;
            _unitOfWorkManage.BeginTran();
            var deleteTaskResult = BaseDal.DeleteData(task);
            var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
            var deleteStockResult = _stockInfoService.DeleteData(stockInfo);
            if (!deleteTaskResult || !updateLocationResult.Status || !deleteStockResult.Status)
            var updateResult = BaseDal.UpdateData(task);
            if (!updateResult)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error("AGV完成回传后,本地任务/库存/货位更新失败");
                return WebResponseContent.Instance.Error("AGV完成回传后,任务更新失败");
            }
            _unitOfWorkManage.CommitTran();
@@ -376,11 +378,62 @@
        private bool CanApplyEnter(Dt_Task task, ApplyEnterDto dto)
        {
            return dto.InOut == 1
                ? task.TaskType == (int)TaskInboundTypeEnum.Inbound && task.TaskStatus == (int)TaskInStatusEnum.InNew
                : task.TaskType == (int)TaskOutboundTypeEnum.Outbound && task.TaskStatus == (int)TaskStatusEnum.Line_Finish;
            if (dto.InOut == 1)
            {
                var hasExecutingOutTask = BaseDal.QueryFirst(x => x.TaskType == (int)TaskOutboundTypeEnum.Outbound
                    && x.TargetAddress == task.SourceAddress
                    && (x.TaskStatus == (int)TaskOutStatusEnum.SC_OutExecuting
                        || x.TaskStatus == (int)TaskOutStatusEnum.Line_OutExecuting));
                // å¦‚果没有正在执行的出库任务,则允许入库
                return hasExecutingOutTask == null;
            }
            else
            {
                return task.TaskType == (int)TaskOutboundTypeEnum.Outbound
                    && task.TaskStatus == (int)TaskStatusEnum.Line_Finish;
            }
        }
        // WCS入库完成
        private async Task<WebResponseContent> CompleteAgvInboundTaskAsync(CreateTaskDto taskDto)
        {
            WebResponseContent response = new WebResponseContent();
            var task = await BaseDal.QueryFirstAsync(x => x.PalletType == taskDto.PalletType);
            if (task == null)
                return response.Error($"没有当前托盘{taskDto.PalletType}入库任务");
            var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
            if (stockInfo == null)
                return response.Error($"未找到托盘{task.PalletCode}的库存信息");
            var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress);
            if (locationInfo == null)
                return response.Error($"未找到货位{task.TargetAddress}的信息");
            if (locationInfo.LocationStatus == (int)LocationStatusEnum.InStock)
                return response.Error($"当前货位{locationInfo.LocationStatus}状态不是空闲状态,无法入库");
            // æ›´æ–°è´§ä½çŠ¶æ€ä¸ºå ç”¨
            locationInfo.LocationStatus = (int)LocationStatusEnum.InStock;
            task.TaskStatus = (int)TaskInStatusEnum.InFinish;
            stockInfo.StockStatus = (int)StockStatusEmun.入库完成;
            _unitOfWorkManage.BeginTran();
            var addStockResult = _stockInfoService.UpdateData(stockInfo);
            var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
            BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
            if (!addStockResult.Status || !updateLocationResult.Status)
            {
                _unitOfWorkManage.RollbackTran();
                return response.Error("入库完成后,添加库存或货位更新失败");
            }
            _unitOfWorkManage.CommitTran();
            return response.OK();
        }
        // AGV出库完成
        private async Task<AGVResponse> CompleteAgvOutboundTaskAsync(Dt_Task task)
        {
            AGVResponse response = new AGVResponse();
@@ -400,9 +453,9 @@
            _unitOfWorkManage.BeginTran();
            var deleteStockResult = _stockInfoService.DeleteData(stockInfo);
            var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo);
            var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
            BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
            if (!deleteStockResult.Status || !updateLocationResult)
            if (!deleteStockResult.Status || !updateLocationResult.Status)
            {
                _unitOfWorkManage.RollbackTran();
                return response.Error("出库完成后,本地库存或货位更新失败");
@@ -412,6 +465,7 @@
            return response.OK();
        }
        // AGV已放货,准备输送线入库
        private async Task<AGVResponse> CompleteAgvInboundTaskAsync(Dt_Task task)
        {
            AGVResponse response = new AGVResponse();
@@ -421,12 +475,15 @@
            task.TargetAddress = availableLocation.LocationCode;
            var wcsTaskDto = _mapper.Map<WCSTaskDTO>(task);
            var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, JsonSerializer.Serialize(wcsTaskDto));
            var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task);
            var taskList = new List<WMSTaskDTO> { wmsTaskDto };
            var requestBody = JsonSerializer.Serialize(taskList);
            var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody);
            if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status)
                return response.Error(httpResponse?.Data?.Message ?? "下发WCS失败");
            task.TaskStatus = (int)TaskStatusEnum.Line_Executing;
            task.TaskStatus = (int)TaskInStatusEnum.Line_InExecuting;
            task.Dispatchertime = DateTime.Now;
            var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress);
@@ -435,6 +492,10 @@
            if (locationInfo.LocationStatus != (int)LocationStatusEnum.Free)
                return response.Error($"当前货位{locationInfo.LocationStatus}状态信息错误");
            var existingStock = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
            if (existingStock != null)
                return response.Error($"托盘{task.PalletCode}的库存信息已存在,请勿重复入库");
            Dt_StockInfo stockInfo = new Dt_StockInfo
            {
@@ -462,6 +523,7 @@
            return response.OK();
        }
        // AGV入库取消
        private AGVResponse CancelAgvInboundTask(Dt_Task task)
        {
            AGVResponse response = new AGVResponse();
@@ -473,6 +535,8 @@
            return response.OK();
        }
        // AGV出库取消
        private async Task<AGVResponse> CancelAgvOutboundTaskAsync(Dt_Task task)
        {
            AGVResponse response = new AGVResponse();
@@ -505,6 +569,6 @@
            return response.OK();
        }
        #endregion æžå·åº“任务模块
        #endregion å…·ä½“实现
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_WCS.cs
@@ -182,10 +182,11 @@
                if (stockInfo == null) return WebResponseContent.Instance.Error("未找到对应库存信息");
                // åˆ¤æ–­æ˜¯ä¸æ˜¯æžå·åº“任务
                //if (taskDto.WarehouseId == (int)WarehouseEnum.FJ1 || taskDto.WarehouseId == (int)WarehouseEnum.ZJ1)
                //{
                //    return WebResponseContent.Instance.OK();
                //}
                if (taskDto.WarehouseId == (int)WarehouseEnum.FJ1 || taskDto.WarehouseId == (int)WarehouseEnum.ZJ1)
                {
                    return await CompleteAgvInboundTaskAsync(taskDto);
                }
                return await ExecuteWithinTransactionAsync(async () =>
                {
@@ -809,4 +810,4 @@
        #endregion WCS逻辑处理
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -29,7 +29,7 @@
        /// </summary>
        /// <param name="taskDto"></param>
        /// <returns></returns>
        [HttpGet, HttpPost, Route("CreateTaskInbound"),AllowAnonymous]
        [HttpGet, HttpPost, Route("CreateTaskInbound"), AllowAnonymous]
        public async Task<WebResponseContent?> CreateTaskInboundAsync([FromBody] CreateTaskDto taskDto)
        {
            return await Service.CreateTaskInboundAsync(taskDto);
@@ -212,6 +212,7 @@
        {
            return await Service.ApplyInOutAsync(applyInOutDto);
        }
        /// <summary>
        /// æ‰‹åŠ¨å‡ºåº“å®Œæˆåé¦ˆç»™AGV
        /// </summary>
@@ -222,16 +223,7 @@
        {
            return await Service.OutTaskComplete(outTaskCompleteDto);
        }
        ///// <summary>
        ///// ä»»åŠ¡å®Œæˆ
        ///// </summary>
        ///// <param name="wCSTask">请求参数</param>
        ///// <returns></returns>
        //[HttpPost, Route("TaskCompleted"), AllowAnonymous]
        //public async Task<WebResponseContent?> TaskCompleted([FromBody] WCSTaskDTO wCSTask)
        //{
        //    return await Service.TaskCompleted(wCSTask);
        //}
        /// <summary>
        /// è¾“送线申请进入
        /// </summary>
@@ -264,6 +256,7 @@
        {
            return await Service.TaskCancelAsync(taskCancelDto);
        }
        #endregion æžå·åº“任务模块
    }
}
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -34,8 +34,8 @@
  "MainDB": "DB_WIDESEA", //当前项目的主库,所对应的连接字符串的Enabled必须为true
  //连接字符串
  //"ConnectionString": "HTI6FB1H05Krd07mNm9yBCNhofW6edA5zLs9TY~MNthRYW3kn0qKbMIsGp~3yyPDF1YZUCPBQx8U0Jfk4PH~ajNFXVIwlH85M3F~v_qKYQ3CeAz3q1mLVDn8O5uWt1~3Ut2V3KRkEwYHvW2oMDN~QIDXPxDgXN0R2oTIhc9dNu7QNaLEknblqmHhjaNSSpERdDVZIgHnMKejU_SL49tralBkZmDNi0hmkbL~837j1NWe37u9fJKmv91QPb~16JsuI9uu0EvNZ06g6PuZfOSAeFH9GMMIZiketdcJG3tHelo=",
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=10.30.4.92;Initial Catalog=WMS_TC;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //旧WMS数据库连接
  //"TeConnectionString": "Data Source=10.30.4.92;Initial Catalog=TeChuang;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
ÏîÄ¿×ÊÁÏ/¼«¾í¿âAGV½Ó¿Ú/~$¼«¾í¿â³öÈë¿â.docx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/¾ÐÊø»ú¶Ô½ÓЭÒé/¾ÐÊø»ú¶Ô½ÓЭÒé.xlsx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/»úеÊÖЭÒé/~$½»»¥Á÷³Ì±í(1).xlsx
Binary files differ
ÏîÄ¿×ÊÁÏ/É豸ЭÒé/»úеÊÖЭÒé/½»»¥Á÷³Ì±í(1).xlsx
Binary files differ