杨前锦
1 天以前 06a8b6963d6d4f229d4e196b4b09ce57e2cbf2dd
HH.WCS.Mobox3/HH.WCS.Mobox3.YNJT_BZP/wms/WMSHelper.cs
@@ -10,12 +10,15 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
using static HH.WCS.Mobox3.YNJT_BZP.api.ApiModel;
using static HH.WCS.Mobox3.YNJT_BZP.api.WmsController;
using static HH.WCS.Mobox3.YNJT_BZP.dispatch.WCSDispatch;
using static HH.WCS.Mobox3.YNJT_BZP.util.Settings;
namespace HH.WCS.Mobox3.YNJT_BZP.wms {
    /// <summary>
@@ -425,33 +428,24 @@
        /// 获取空托开始货位
        /// </summary>
        /// <param name="trayType"></param>
        /// <param name="exclude">是否排除母拖缓存位 0.不排除 1.排除</param>
        /// <returns></returns>
        public static Location GetEmptyTrayStartLoc(int trayType ,int exclude = 1)
        public static Location GetEmptyTrayStartLoc(int trayType)
        {
            Location result = null;
            try
            {
                // 1、查询当前库区-排-物料 有托盘的货位
                var db = new SqlHelper<object>().GetInstance();
                var query = db.Queryable<Location>()
                result = db.Queryable<Location>()
                     .LeftJoin<LocCntrRel>((a, b) => a.S_CODE == b.S_LOC_CODE)
                     .LeftJoin<Container>((a, b, c) => b.S_CNTR_CODE == c.S_CODE)
                     .LeftJoin<CntrItemRel>((a, b, c, d) => c.S_CODE == d.S_CNTR_CODE)
                     .Where((a, b, c, d) => a.S_AREA_CODE == Settings.storeAreaCode && (trayType == 0 && a.N_CURRENT_NUM == 1 || trayType > 0 && a.N_CURRENT_NUM > 0) && a.N_LOCK_STATE == 0 && (a.C_ENABLE != "禁用" && a.C_ENABLE != "N") && c.N_TYPE == trayType && d.S_ITEM_CODE == null);
                if (exclude == 1)
                {
                    List<string> bufferInLocs = Settings.baseTrayBufferLocList.Select(a => a.bufferInLoc).ToList();
                    query = query.Where((a, b, c, d) =>  !bufferInLocs.Contains(a.S_CODE));
                }
                else
                {
                    List<string> bufferInLocs = Settings.baseTrayBufferLocList.Select(a => a.bufferInLoc).ToList();
                    query = query.OrderByDescending((a, b, c, d) => bufferInLocs.Contains(a.S_CODE));
                }
                result = query.OrderByDescending((a, b, c, d) => a.N_LAYER).First();
                     .Where((a, b, c, d) => a.S_AREA_CODE == Settings.storeAreaCode &&
                                    (trayType == 0 && a.N_CURRENT_NUM == 1 || trayType > 0 && a.N_CURRENT_NUM > 0) &&
                                    a.N_LOCK_STATE == 0 && (a.C_ENABLE != "禁用" && a.C_ENABLE != "N") &&
                                    c.N_TYPE == trayType && d.S_ITEM_CODE == null)
                    .OrderByDescending((a, b, c, d) => a.N_LAYER)
                    .First();
            }
            catch (Exception ex)
            {
@@ -518,7 +512,6 @@
                                    && a.N_CURRENT_NUM > 0
                                    && a.N_LOCK_STATE == 0
                                    && c.S_ITEM_CODE == itemCode
                                    && c.S_FOVRAGE != "Y"
                                    && c.S_ITEM_STATE == "OK"
                                    && SqlFunc.ToDate(c.S_EFFECTIVE_TIME) <= SqlFunc.GetDate()  // 生效时间早于当前时间
                                    && SqlFunc.ToDate(c.S_EXPIRATION_TIME) >= SqlFunc.GetDate() // 失效时间晚于当前时间
@@ -534,10 +527,10 @@
                if (jbLoc != null) 
                {
                    List<int> roadwayList = new List<int>();
                    var agvJBLoc = Settings.getAgvJBLoc(jbLoc);
                    var agvJBLoc = WMSHelper.getAgvJBLocByLocCode(jbLoc);
                    if (agvJBLoc != null)
                    {
                        roadwayList = agvJBLoc.roadway;
                        roadwayList = agvJBLoc.S_ROADWAY.Split(',').Select(a => int.Parse(a)).ToList();
                        query = query.Where((a, b, c) => roadwayList.Contains(a.N_ROADWAY));
                    }
                }
@@ -552,58 +545,281 @@
            return loc;
        }
        public class InstockEndLocResult
        {
            public Location endLoc { get; set; }
            public Location agvLoc { get; set; }
        }
        /// <summary>
        /// 获取入库终点货位
        /// 入库策略
        /// 1.巷道均衡
        /// 2.物料均衡
        /// 入库策略:先物料均衡再巷道均衡
        /// </summary>
        /// <param name="locCode"></param>
        /// <param name="trayType"></param>
        /// <param name="inWay">入库方式 1.自动入库 2.人工入库</param>
        /// <param name="groupNo">分组(1.产线 2.成型机)</param>
        /// <param name="locCode">入库站台</param>
        /// <param name="trayType">托盘类型</param>
        /// <param name="itemCode">物料编码</param>
        /// <param name="itemType">物料类型</param>
        /// <returns></returns>
        public static Location GetInstockEndLoc(int trayType , string locCode = null , int inWay = 1 ,string itemCode = null)
        public static InstockEndLocResult GetInstockEndLoc(int groupNo, int trayType, int itemType = 0, string itemCode = null, string locCode = null)
        {
            const string LOG_PREFIX = "【获取入库终点货位】";
            LogHelper.Info($"{LOG_PREFIX}开始获取入库终点货位", "WMS");
            var result = new InstockEndLocResult();
            try
            {
                // 1. 确定巷道范围
                var roadwayList = GetTargetRoadways(locCode, itemType);
                if (roadwayList == null || roadwayList.Count == 0)
                {
                    LogHelper.Info($"{LOG_PREFIX}无法确定目标巷道", "WMS");
                    return result;
                }
                // 2. 确定层数范围和业务类型
                var (layerList, busType) = GetLayerConfig(trayType, itemType);
                LogHelper.Info($"{LOG_PREFIX}巷道范围:{string.Join(",", roadwayList)} 层数范围:{string.Join(",", layerList)} 业务类型:{busType}", "WMS");
                // 3. 批量获取可用货位
                var locList = GetAvailableLocations(roadwayList, layerList);
                if (locList.Count == 0)
                {
                    LogHelper.Info($"{LOG_PREFIX}没有满足条件的空货位", "WMS");
                    return result;
                }
                // 4. 获取巷道容积率(按空货位数量排序)
                var roadwayVolumes = GetRoadwayVolumes(locList);
                if (roadwayVolumes.Count == 0) return result;
                // 5. 获取巷道物料分布(按同物料数量排序)
                var roadwayOrders = GetRoadwayItemDistribution(roadwayList, layerList, itemCode);
                // 6. 合并排序:物料均衡 > 巷道均衡
                var sortedRoadways = roadwayOrders
                    .OrderBy(r => r.ItemCount)
                    .ThenBy(r => roadwayVolumes[r.Roadway])
                    .ToList();
                // 7. 预获取所有巷道的接驳位状态
                var agvLocCache = new Dictionary<int, Location>();
                foreach (var roadway in sortedRoadways.Select(r => r.Roadway).Distinct())
                {
                    agvLocCache[roadway] = getAgvJBLoc(roadway, 1, busType, groupNo);
                }
                // 8. 按排序查找第一个可用货位
                foreach (var roadwayOrder in sortedRoadways)
                {
                    int roadway = roadwayOrder.Roadway;
                    // 获取巷道内最低层的空货位
                    var endLoc = locList
                        .Where(l => l.N_ROADWAY == roadway)
                        .OrderBy(l => l.N_LAYER)
                        .FirstOrDefault();
                    if (endLoc == null) continue;
                    // 检查接驳位是否可用(使用缓存)
                    Location agvLoc = null;
                    if (locCode != null)
                    {
                        agvLoc = LocationHelper.GetLoc(locCode);
                    }
                    else
                    {
                        agvLocCache.TryGetValue(roadway, out agvLoc);
                    }
                    if ( agvLoc != null)
                    {
                        result.endLoc = endLoc;
                        result.agvLoc = agvLoc;
                        LogHelper.Info($"{LOG_PREFIX}找到可用终点货位:{endLoc.S_CODE} 接驳位:{agvLoc.S_CODE}", "WMS");
                        return result;
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Info($"{LOG_PREFIX}内部错误:{ex.Message}", "WMS");
            }
            LogHelper.Info($"{LOG_PREFIX}无可用终点货位", "WMS");
            return result;
        }
        // --- 辅助方法 ---
        private static List<int> GetTargetRoadways(string locCode, int itemType)
        {
            if (!string.IsNullOrEmpty(locCode))
            {
                var agvJBLoc = getAgvJBLocByLocCode(locCode);
                return agvJBLoc?.S_ROADWAY.Split(',')
                    .Select(int.Parse)
                    .ToList();
            }
            if (itemType != 0)
            {
                switch (itemType)
                {
                    case 1:
                    case 2:
                    case 3:
                        return new List<int> { 1, 2, 3, 4, 5 };
                    case 4:
                    case 5:
                    case 6:
                        return new List<int> { 6, 7 };
                    default:
                        return null;
                }
            }
            return null;
        }
        private static (List<int> Layers, int BusType) GetLayerConfig(int trayType, int itemType)
        {
            // 托盘类型4(胎圈)使用特殊配置
            if (trayType == 4)
            {
                return (new List<int> { 1, 2, 3, 4 }, 1);
            }
            // 物料类型4-5(内衬/帘布)使用高层
            if (itemType == 4 || itemType == 5)
            {
                return (new List<int> { 5, 6, 7, 8 }, 2);
            }
            // 默认配置(全楼层)
            return (new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2);
        }
        private static List<Location> GetAvailableLocations(List<int> roadwayList, List<int> layerList)
        {
            var db = new SqlHelper<object>().GetInstance();
            return db.Queryable<Location>()
                .Where(a => a.S_AREA_CODE == Settings.storeAreaCode
                            && a.N_CURRENT_NUM == 0
                            && a.N_LOCK_STATE == 0
                            && a.C_ENABLE == "Y"
                            && roadwayList.Contains(a.N_ROADWAY))
                .ToList()
                .Where(a => layerList.Contains(a.N_LAYER)) // 内存过滤层数
                .ToList();
        }
        private static Dictionary<int, int> GetRoadwayVolumes(List<Location> locList)
        {
            return locList
                .GroupBy(l => l.N_ROADWAY)
                .ToDictionary(
                    g => g.Key,
                    g => g.Count()
                );
        }
        private static List<(int Roadway, int ItemCount)> GetRoadwayItemDistribution(
            List<int> roadwayList, List<int> layerList, string itemCode)
        {
            var db = new SqlHelper<object>().GetInstance();
            // 无物料编码时返回空计数
            if (string.IsNullOrEmpty(itemCode))
            {
                return roadwayList.Select(r => (r, 0)).ToList();
            }
            var query = db.Queryable<Location>()
                .LeftJoin<LocCntrRel>((loc, rel) => loc.S_CODE == rel.S_LOC_CODE)
                .LeftJoin<CntrItemRel>((loc, rel, itemRel) => rel.S_CNTR_CODE == itemRel.S_CNTR_CODE)
                .Where((loc, rel, itemRel) =>
                    loc.S_AREA_CODE == Settings.storeAreaCode
                    && loc.N_LOCK_STATE == 0
                    && loc.C_ENABLE == "Y"
                    && roadwayList.Contains(loc.N_ROADWAY)
                    && layerList.Contains(loc.N_LAYER)
                    && itemRel.S_ITEM_CODE == itemCode)
                .GroupBy((loc, rel, itemRel) => loc.N_ROADWAY)
                .Select((loc, rel, itemRel) => new
                {
                    Roadway = loc.N_ROADWAY,
                    ItemCount = SqlFunc.AggregateCount(itemRel.S_ITEM_CODE)
                })
                .ToList();
            // 补充没有物料的巷道
            return roadwayList
                .Select(r => (r, query.FirstOrDefault(q => q.Roadway == r)?.ItemCount ?? 0))
                .ToList();
        }
        /// <summary>
        /// 获取入库终点货位
        /// 入库策略:先物料均衡再巷道均衡
        /// </summary>
        /// <param name="groupNo">分组(1.产线 2.成型机)</param>
        /// <param name="locCode">入库站台</param>
        /// <param name="trayType">托盘类型</param>
        /// <param name="itemCode">物料编码</param>
        /// <param name="itemType">物料类型</param>
        /// <returns></returns>
       /* public static InstockEndLocResult GetInstockEndLoc(int groupNo, int trayType, int itemType = 0 , string itemCode = null, string locCode = null)
        {
            LogHelper.Info("【获取入库终点货位】开始获取入库终点货位", "WMS");
            var db = new SqlHelper<object>().GetInstance();
            Location loc = null;
            InstockEndLocResult result = new InstockEndLocResult();
            Location endLoc = null;
            Location agvLoc = null;
            int busType = 2;    // 业务类型(1.要母拖 2.不要母拖 3.混用)
            try
            {
                // 托盘 1.带束 2.胎侧 3.BEC 入巷道 1-5 进行混放 ; 4.内衬 5.帘布 6.胎圈 入巷道 6-7 ;其中内衬、帘布放5-8层 胎圈放 1-4 层
                // 物料类型 1.带束 2.胎侧 3.BEC 入巷道 1-5 进行混放 ; 4.内衬 5.帘布 6.胎圈 入巷道 6-7 ;其中内衬、帘布放5-8层 胎圈放 1-4 层
                // 托盘类型 1.带束、内衬、帘布 2.胎侧 3.BEC 4.胎圈
                List<int> roadwayList = null;
                List<int> layerList = null;
                if (locCode != null)
                {
                    var agvJBLoc = Settings.getAgvJBLoc(locCode);
                    var agvJBLoc = getAgvJBLoc(locCode);
                    if (agvJBLoc != null)
                    {
                        roadwayList = agvJBLoc.roadway;
                        roadwayList = agvJBLoc.S_ROADWAY.Split(',').Select(a => int.Parse(a)).ToList();
                    }
                }
                else 
                {
                    if (trayType > 0 && trayType <= 3)
                    if (itemType != 0)
                    {
                        roadwayList = new List<int>() { 1, 2, 3, 4, 5 };
                    }
                    else
                    {
                        roadwayList = new List<int>() { 6,7 };
                        List<int> itemTypeA = new List<int> { 1,2,3 };
                        List<int> itemTypeB = new List<int> { 4,5,6 };
                        if (itemTypeA.Contains(itemType))
                        {
                            roadwayList = new List<int>() { 1, 2, 3, 4, 5 };
                        }
                        if(itemTypeB.Contains(itemType))
                        {
                            roadwayList = new List<int>() { 6, 7 };
                        }
                    }
                }
                // 确定物料的入库层数
                if (trayType > 0 && trayType <= 3)
                if (trayType == 4)
                {
                    layerList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
                    layerList = new List<int>() { 1, 2, 3, 4 };
                    busType = 1;
                }
                else if (trayType > 3 && trayType <= 5)
                else if (itemType == 4 || itemType == 5)
                {
                    layerList = new List<int>() { 5, 6, 7, 8 };
                }
                else
                else
                {
                    layerList = new List<int>() { 1, 2, 3, 4 };
                    layerList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
                }
                LogHelper.Info("【获取入库终点货位】查询是否有满足条件的空货位", "WMS"); 
@@ -620,12 +836,9 @@
                if (locList.Count == 0)
                {
                    LogHelper.Info("【获取入库终点货位】没有满足条件的空货位", "WMS");
                    return loc;
                    return result;
                }
                else
                {
                    LogHelper.Info($"【获取入库终点货位】满足条件的空货位数量:{locList.Count}", "WMS");
                }
                LogHelper.Info($"【获取入库终点货位】满足条件的空货位数量:{locList.Count}", "WMS");
                LogHelper.Info($"【获取入库终点货位】开始查询巷道的容积率", "WMS");
                var roadwayVolumeList = db.Queryable<Location>()
@@ -636,80 +849,47 @@
                    .ToList();
                LogHelper.Info($"【获取入库终点货位】查询巷道的容积率,并按从小到大排序:{JsonConvert.SerializeObject(roadwayVolumeList)}", "WMS");
                Dictionary<int,int> roadwayDic = new Dictionary<int,int>();
                foreach (var item in roadwayVolumeList)
                if (roadwayVolumeList.Count > 0)
                {
                    roadwayDic.Add(item.roadway, item.num);
                }
                    Dictionary<int, int> roadwayDic = new Dictionary<int, int>();
                    foreach (var item in roadwayVolumeList)
                    {
                        roadwayDic.Add(item.roadway, item.num);
                    }
                LogHelper.Info($"【获取入库终点货位】开始查询巷道的同物料数量", "WMS");
                var roadwayItemNumList = db.Queryable<Location>()
                    .LeftJoin<LocCntrRel>((a,b) => a.S_CODE == b.S_LOC_CODE)
                    .LeftJoin<CntrItemRel>((a,b,c) => b.S_CNTR_CODE == c.S_CNTR_CODE )
                    .Where((a, b, c) => a.S_AREA_CODE == Settings.storeAreaCode && a.N_LOCK_STATE == 0 && a.C_ENABLE == "Y" && roadwayList.Contains(a.N_ROADWAY) && layerList.Contains(a.N_LAYER))
                    .GroupBy((a, b, c) => a.N_ROADWAY)
                    .Select((a, b, c) => new { roadway = a.N_ROADWAY, volumNum = SqlFunc.AggregateDistinctCount(c.S_ITEM_CODE == itemCode) })
                    .ToList();
                LogHelper.Info($"【获取入库终点货位】查询巷道的同物料数量,并按从小到到排序:{JsonConvert.SerializeObject(roadwayItemNumList)}", "WMS");
                    LogHelper.Info($"【获取入库终点货位】开始查询巷道的同物料数量", "WMS");
                    var roadwayOrderList = db.Queryable<Location>()
                        .LeftJoin<LocCntrRel>((a, b) => a.S_CODE == b.S_LOC_CODE)
                        .LeftJoin<CntrItemRel>((a, b, c) => b.S_CNTR_CODE == c.S_CNTR_CODE)
                        .Where((a, b, c) => a.S_AREA_CODE == Settings.storeAreaCode && a.N_LOCK_STATE == 0 && a.C_ENABLE == "Y" && roadwayList.Contains(a.N_ROADWAY) && layerList.Contains(a.N_LAYER))
                        .GroupBy((a, b, c) => a.N_ROADWAY)
                        .Select((a, b, c) => new { roadway = a.N_ROADWAY, volumNum = SqlFunc.AggregateDistinctCount(c.S_ITEM_CODE == itemCode) })
                        .ToList();
                    roadwayOrderList = roadwayOrderList.OrderBy(a => a.volumNum).ThenBy(a => roadwayDic[a.roadway]).ToList();
                    LogHelper.Info($"【获取入库终点货位】查询巷道的同物料数量,并按从小到到排序:{JsonConvert.SerializeObject(roadwayOrderList)}", "WMS");
                roadwayItemNumList = roadwayItemNumList.OrderBy(a => a.volumNum).ThenBy(a => roadwayDic[a.roadway]).ToList();
                if (inWay == 1)
                {
                    LogHelper.Info($"【获取入库终点货位】开始循环查询巷道对应接驳位的状态", "WMS");
                    foreach (var roadwayVolume in roadwayItemNumList)
                    foreach (var roadwayVolume in roadwayOrderList)
                    {
                        int roadway = roadwayVolume.roadway;
                        LogHelper.Info($"【获取入库终点货位】查询巷道内堆垛机的设备状态", "WMS");
                        List<string> deviceNos = new List<string>() { roadway.ToString()};
                        LogHelper.Info($"调用WCS的设备状态查询接口,参数:{deviceNos}", "WMS");
                        var deviceStatuses = WCSDispatch.getDeviceStatus(deviceNos);
                        var agvJbLocList = Settings.getAgvJBLocList( roadway, 1);
                        if (agvJbLocList.Count > 0)
                        {
                            foreach (var agvLocCode in agvJbLocList)
                            {
                                var agvLoc = LocationHelper.GetLoc(agvLocCode);
                                if (agvLoc != null && agvLoc.N_LOCK_STATE == 0 && agvLoc.C_ENABLE == "Y")
                                {
                                    LogHelper.Info($"【查询入库终点货位】,接驳位:{agvLoc.S_CODE}状态正常,查询巷道:{roadway}的空货位");
                                    loc = db.Queryable<Location>()
                                        .Where(a => a.N_CURRENT_NUM == 0 && a.S_AREA_CODE == Settings.storeAreaCode && a.N_LOCK_STATE == 0 && a.C_ENABLE == "Y" && a.N_ROADWAY == roadway && layerList.Contains(a.N_LAYER))
                                        .OrderBy(l => l.N_LAYER)
                                        .First();
                                    if (loc != null)
                                    {
                                        return loc;
                                    }
                                    else
                                    {
                                        LogHelper.Info($"【查询入库终点货位】,没有查询巷道:{roadway}的空货位,跳过此巷道");
                                    }
                                }
                                else
                                {
                                    LogHelper.Info($"【查询入库终点货位】,接驳位:{agvLoc.S_CODE},暂时不可用,跳过此巷道");
                                }
                            }
                        }
                    }
                }
                else if (inWay == 2)
                {
                    // 查询空货位
                    loc = db.Queryable<Location>()
                        .Where(a => a.N_CURRENT_NUM == 0 && a.S_AREA_CODE == Settings.storeAreaCode && a.N_LOCK_STATE == 0 && a.C_ENABLE == "Y" && roadwayList.Contains(a.N_ROADWAY)  && layerList.Contains(a.N_LAYER))
                        LogHelper.Info($"【查询入库终点货位】查询终点货位");
                        endLoc = db.Queryable<Location>()
                        .Where(a => a.N_CURRENT_NUM == 0 && a.S_AREA_CODE == Settings.storeAreaCode && a.N_LOCK_STATE == 0 && a.C_ENABLE == "Y" && a.N_ROADWAY == roadway && layerList.Contains(a.N_LAYER))
                        .OrderBy(l => l.N_LAYER)
                        .First();
                    if (loc != null)
                    {
                        return loc;
                    }
                    else
                    {
                        LogHelper.Info($"【查询入库终点货位】,巷道:{JsonConvert.SerializeObject(roadwayList)}内没有空货位");
                        if (endLoc != null)
                        {
                            LogHelper.Info($"【获取入库终点货位】查询对应的接驳位是否可用", "WMS");
                            agvLoc = getAgvJBLoc(roadway,1, busType, groupNo);
                            if (agvLoc != null)
                            {
                                result.endLoc = endLoc;
                                result.agvLoc = agvLoc;
                                return result;
                            }
                        }
                    }
                }
            }
@@ -717,7 +897,190 @@
            {
                LogHelper.Error("【查询入库终点货位】内部错误,错误原因:" + ex.Message, ex);
            }
            return loc;
            return result;
        }*/
        /// <summary>
        /// 获取AGV接驳位
        /// </summary>
        /// <param name="locCode"></param>
        /// <returns></returns>
        public static JbLocStatus getAgvJBLocByLocCode(string locCode)
        {
            List<string> locCodes = new List<string>();
            var db = new SqlHelper<object>().GetInstance();
            return db.Queryable<JbLocStatus>().Where(a => a.S_LOC_CODE == locCode).First();
        }
        /// <summary>
        /// 获取AGV接驳位
        /// </summary>
        /// <param name="busType">业务类型(1.要母拖 2.不要母拖 3.混用 )</param>
        /// <param name="workPattern">工作模式(1.入 2.出 3.混用)</param>
        /// <returns></returns>
        public static List<JbLocStatus> getAgvJBLocListByBusTypeAndWorkPattern(int busType ,int workPattern)
        {
            List<string> locCodes = new List<string>();
            var db = new SqlHelper<object>().GetInstance();
            return db.Queryable<JbLocStatus>().Where(a => a.N_BUS_TYPE == busType && a.N_WORK_PATTERN == workPattern).ToList();
        }
        /// <summary>
        /// 更新AGV接驳位状态
        /// </summary>
        /// <param name="jbLocStatus"></param>
        /// <returns></returns>
        public static bool updateAgvJBLoc(JbLocStatus jbLocStatus)
        {
            var db = new SqlHelper<object>().GetInstance();
            return db.Updateable(jbLocStatus).ExecuteCommand() > 0;
        }
        /// <summary>
        /// 获取AGV接驳位
        /// </summary>
        /// <param name="roadway">目标巷道</param>
        /// <param name="workPattern">工作模式(1.入 2.出 3.混用)</param>
        /// <param name="busType">业务类型(1.存在母拖 2.不存在母拖 3.混用 )</param>
        /// <param name="groupNo">分组(1.产线 2.成型机)</param>
        /// <returns></returns>
        public static Location getAgvJBLoc(int roadway, int workPattern, int busType, int groupNo)
        {
            const string LOG_PREFIX = "【获取AGV接驳位-getAgvJBLoc】";
            LogHelper.Info(LOG_PREFIX+ $",参数: roadway={roadway},workPattern={workPattern},busType={busType},groupNo={groupNo}", "WMS");
            try
            {
                var db = new SqlHelper<object>().GetInstance();
                // 1. 优化数据库查询:拆分巷道匹配 + 多级排序
                var jbLocStatuses = db.Queryable<JbLocStatus>()
                    .Where(a => a.N_MANUAL_STATUS == 1
                             && (a.N_WORK_PATTERN == workPattern || a.N_WORK_PATTERN == 3)
                             && (a.N_BUS_TYPE == busType || a.N_BUS_TYPE == 3)
                             && a.N_GROUP_NO == groupNo
                             && a.S_ROADWAY.Split(',').Contains(roadway.ToString()))
                    .OrderBy(a => a.N_WORK_PATTERN)   // 主排序条件
                    .OrderBy(a => a.N_BUS_TYPE)        // 次排序条件
                    .OrderByDescending(a => a.N_PRIORITY) // 优先级排序
                    .ToList();
                if (jbLocStatuses.Count == 0)
                {
                    LogHelper.Info($"{LOG_PREFIX} 未找到符合条件的接驳位", "WMS");
                    return null;
                }
                // 2. 预加载设备配置(避免循环内重复查询)
                var ddjDeviceConfig = Settings.ddjDeviceConfig?
                    .FirstOrDefault(a => a.roadway == roadway && a.type == 1);
                // 3. 预查询堆垛机状态(所有接驳位共享)
                var ddjDeviceStatus = ddjDeviceConfig != null ?
                    GetAvailableDeviceStatus(ddjDeviceConfig.deviceNo) : null;
                // 4. 批量获取接驳位设备状态(减少网络/DB调用)
                var jbDeviceNos = jbLocStatuses.Select(j => j.S_DEVICE_NO).Distinct().ToList();
                var deviceStatusCache = GetDeviceStatusCache(jbDeviceNos);
                foreach (var jbLoc in jbLocStatuses)
                {
                    LogHelper.Info($"{LOG_PREFIX} 检查接驳位:{jbLoc.S_LOC_CODE}", "WMS");
                    var location = LocationHelper.GetLoc(jbLoc.S_LOC_CODE);
                    if (location == null || location.C_ENABLE == "N") continue;
                    // 5. 检查接驳位设备状态(使用缓存)
                    if (!IsDeviceAvailable(deviceStatusCache, jbLoc.S_DEVICE_NO))
                    {
                        jbLoc.N_DEVICE_STATUS = 2;
                        updateAgvJBLoc(jbLoc);
                        LogHelper.Info($"{LOG_PREFIX} 接驳位设备不可用:{jbLoc.S_LOC_CODE}", "WMS");
                        continue;
                    }
                    else
                    {
                        // 更新接驳位设备状态
                        if (jbLoc.N_DEVICE_STATUS == 2)
                        {
                            jbLoc.N_DEVICE_STATUS = 1;
                            updateAgvJBLoc(jbLoc);
                        }
                    }
                    // 6. 检查堆垛机状态(使用预查询结果)
                    if (ddjDeviceStatus == null)
                    {
                        LogHelper.Info($"{LOG_PREFIX} 堆垛机配置缺失或不可用", "WMS");
                        continue;
                    }
                    // 7. 巷道不一致时检查穿梭机
                    if (roadway != location.N_ROADWAY)
                    {
                        LogHelper.Info($"{LOG_PREFIX} 目标巷道与接驳位巷道不一致,检查穿梭机", "WMS");
                        if (!IsShuttleAvailable(roadway))
                        {
                            continue;
                        }
                    }
                    LogHelper.Info($"{LOG_PREFIX} 找到可用接驳位:{jbLoc.S_LOC_CODE}", "WMS");
                    return location;
                }
                LogHelper.Info($"{LOG_PREFIX} 无可用接驳位", "WMS");
                return null;
            }
            catch (Exception ex)
            {
                LogHelper.Info($"{LOG_PREFIX} 系统异常:{ex.Message}", "WMS");
                return null;
            }
        }
        // --- 辅助方法 ---
        private static bool IsDeviceAvailable(Dictionary<string, DeviceStatusData> cache, string deviceNo)
        {
            return cache.TryGetValue(deviceNo, out var status) &&
                       status?.workStatus == 1 &&
                       status?.manualStatus == 2;
        }
        private static Dictionary<string, DeviceStatusData> GetDeviceStatusCache(List<string> deviceNos)
        {
            var cache = new Dictionary<string, DeviceStatusData>();
            if (deviceNos.Count == 0) return cache;
            var statuses = WCSDispatch.getDeviceStatus(deviceNos);
            LogHelper.Info($"获取接驳位设备号:{deviceNos} 的设备状态信息:{JsonConvert.SerializeObject(statuses)}", "WMS");
            foreach (var s in statuses.Where(s => s != null))
            {
                cache[s.deviceNo] = s;
            }
            return cache;
        }
        private static bool IsShuttleAvailable(int roadway)
        {
            var csjConfig = Settings.ddjDeviceConfig?
                .FirstOrDefault(a => a.roadway == roadway && a.type == 2); // 假设type=2是穿梭机
            if (csjConfig == null)
            {
                LogHelper.Info("穿梭机配置缺失", "WMS");
                return false;
            }
            var status = GetAvailableDeviceStatus(csjConfig.deviceNo);
            return status != null;
        }
        private static DeviceStatusData GetAvailableDeviceStatus(string deviceNo)
        {
            var statuses = WCSDispatch.getDeviceStatus(new List<string> { deviceNo });
            LogHelper.Info($"获取堆垛机:{deviceNo}的设备状态信息:{JsonConvert.SerializeObject(statuses)}", "WMS");
            return statuses?.FirstOrDefault(s =>
                s.workStatus == 1 &&
                s.manualStatus == 2);
        }
@@ -817,26 +1180,23 @@
            try
            {
                db.BeginTran();
                itemBarcodeInfos.ForEach(item => {
                    var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.S_BC_ENTRIED == item.S_BC_ENTRIED).First();
                    if (itemBarcodeInfo == null)
                    {
                        var bo = db.Insertable<ItemBarcodeInfo>(item).ExecuteCommand() > 0;
                        if (!bo)
                if (itemBarcodeInfos.Count > 0)
                {
                    itemBarcodeInfos.ForEach(item => {
                        var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.N_RECID == item.N_RECID).First();
                        if (itemBarcodeInfo != null)
                        {
                            db.Deleteable<ItemBarcodeInfo>(itemBarcodeInfo).ExecuteCommand();
                        }
                        var bo = db.Insertable<ItemBarcodeInfo>(item).ExecuteCommand() == 0;
                        if (bo)
                        {
                            result = false;
                            LogHelper.Info($"添加物料条码信息失败,物料条码信息:{JsonConvert.SerializeObject(item)}", "GT");
                        }
                    }
                });
                if (result)
                {
                    db.CommitTran();
                    });
                }
                else
                {
                    db.RollbackTran();
                }
                db.CommitTran();
            }
            catch (Exception ex) 
            {
@@ -844,6 +1204,102 @@
                db.RollbackTran();
            }
           return result;
        }
        /// <summary>
        /// 同步条码状态表
        /// </summary>
        /// <returns></returns>
        public static bool synBarcodeStatus(List<IwmsSemiBldBcstatus> model)
        {
            bool result = true;
            var db = new SqlHelper<object>().GetInstance();
            try
            {
                db.BeginTran();
                if (model.Count > 0)
                {
                    model.ForEach( bcstatus => {
                        // 同步条码状态信息表
                        var iwmsSemiBldBcstatus = db.Queryable<IwmsSemiBldBcstatus>().Where(a => a.N_RECID == bcstatus.N_RECID).First();
                        if (iwmsSemiBldBcstatus != null)
                        {
                            db.Deleteable(iwmsSemiBldBcstatus).ExecuteCommand();
                        }
                        db.Insertable(bcstatus).ExecuteCommand();
                        // 同步条码信息表
                        var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.S_BC_ENTRIED == bcstatus.S_BC_ENTRIED).First();
                        if (itemBarcodeInfo != null)
                        {
                            itemBarcodeInfo.S_JDGE = bcstatus.S_JDGE;
                            itemBarcodeInfo.LAST_MODIFY_TIME = bcstatus.LAST_MODIFY_TIME;
                            db.Updateable(itemBarcodeInfo).ExecuteCommand();
                        }
                        // 同步容器物料表
                        var cntrItemRel = db.Queryable<CntrItemRel>().Where(a => a.S_CG_ID == bcstatus.S_BC_ENTRIED).First();
                        if (cntrItemRel != null)
                        {
                            cntrItemRel.S_ITEM_STATE = bcstatus.S_JDGE;
                            cntrItemRel.T_MODIFY = DateTime.Now;
                            db.Updateable(cntrItemRel).ExecuteCommand();
                        }
                    });
                }
                db.CommitTran();
            }
            catch (Exception ex)
            {
                LogHelper.Info($"同步条码状态表错误,错误信息:{ex.Message}", "GT");
                db.RollbackTran();
                result = false;
            }
            return result;
        }
        /// <summary>
        /// 同步抽检状态表
        /// </summary>
        /// <returns></returns>
        public static bool synSamplingStatus(List<IwmsSemiBldBcsample> model)
        {
            bool result = true;
            var db = new SqlHelper<object>().GetInstance();
            try
            {
                db.BeginTran();
                if (model.Count > 0)
                {
                    model.ForEach(bcsample => {
                        // 同步抽检条码状态信息表
                        var iwmsSemiBldBcstatus = db.Queryable<IwmsSemiBldBcsample>().Where(a => a.N_RECID == bcsample.N_RECID).First();
                        if (iwmsSemiBldBcstatus != null)
                        {
                            db.Deleteable(iwmsSemiBldBcstatus).ExecuteCommand();
                        }
                        db.Insertable(bcsample).ExecuteCommand();
                        // 同步条码信息表
                        var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.S_BC_ENTRIED == bcsample.S_BC_ENTRIED).First();
                        if (itemBarcodeInfo != null)
                        {
                            itemBarcodeInfo.S_JDGE = bcsample.S_JDGE;
                            itemBarcodeInfo.LAST_MODIFY_TIME = bcsample.LAST_MODIFY_TIME;
                            db.Updateable(itemBarcodeInfo).ExecuteCommand();
                        }
                    });
                }
                db.CommitTran();
            }
            catch (Exception ex)
            {
                LogHelper.Info($"同步抽检状态表错误,错误信息:{ex.Message}", "GT");
                db.RollbackTran();
                result = false;
            }
            return result;
        }
        /// <summary>
@@ -857,28 +1313,22 @@
            try
            {
                db.BeginTran();
                updateMatlStatuses.ForEach(update => {
                    var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.S_BC_ENTRIED == update.bc_entried).First();
                    if (itemBarcodeInfo != null)
                    {
                        itemBarcodeInfo.S_JDGE = update.jdge;
                        db.Updateable(itemBarcodeInfo).ExecuteCommand();
                        var cntrItemRel = db.Queryable<CntrItemRel>().Where(a => a.S_CG_ID == update.bc_entried).First();
                        if (cntrItemRel != null)
                if (updateMatlStatuses.Count > 0)
                {
                    updateMatlStatuses.ForEach(update => {
                        var itemBarcodeInfo = db.Queryable<ItemBarcodeInfo>().Where(a => a.S_BC_ENTRIED == update.barcode).First();
                        if (itemBarcodeInfo != null)
                        {
                            // 将MES的物料状态转化成WMS可识别的物料状态
                            if (update.jdge == "" || update.jdge == null)
                            itemBarcodeInfo.S_JDGE = update.jdge;
                            db.Updateable(itemBarcodeInfo).ExecuteCommand();
                            var cntrItemRel = db.Queryable<CntrItemRel>().Where(a => a.S_CG_ID == update.barcode).First();
                            if (cntrItemRel != null)
                            {
                                update.jdge = "OK";
                                db.Updateable<CntrItemRel>().SetColumns(a => new CntrItemRel() { S_ITEM_STATE = update.jdge }).Where(a => a.S_CG_ID == update.barcode).ExecuteCommand();
                            }
                            else if (update.jdge != "OK")
                            {
                                update.jdge = "HOLD";
                            }
                            db.Updateable<CntrItemRel>().SetColumns(a => new CntrItemRel() { S_ITEM_STATE = update.jdge }).Where(a => a.S_CG_ID == update.bc_entried).ExecuteCommand();
                        }
                    }
                });
                    });
                }
                db.CommitTran();
            }
            catch (Exception ex)
@@ -888,69 +1338,6 @@
                result = false;
            }
            return result;
        }
        /// <summary>
        /// 批量更新物料状态
        /// </summary>
        /// <returns></returns>
        public static bool batchUpdateMatlTimeConfig(List<Overage> overages)
        {
            bool result = true;
            var db = new SqlHelper<object>().GetInstance();
            try
            {
                db.BeginTran();
                foreach (Overage overage in overages)
                {
                    var overage1 = db.Queryable<Overage>().Where(a => a.RECID == overage.RECID).First();
                    if (overage1 != null)
                    {
                        overage1.MCNGRP = overage.MCNGRP;
                        overage1.ITEMPATT = overage.ITEMPATT;
                        overage1.OVERAGE = overage.OVERAGE;
                        overage1.MINHOUR = overage.MINHOUR;
                        overage1.FLAG_STS = overage.FLAG_STS;
                        db.Updateable(overage1).ExecuteCommand();
                    }
                    else
                    {
                        db.Insertable(overage).ExecuteCommand();
                    }
                }
                db.CommitTran();
            }
            catch (Exception ex)
            {
                LogHelper.Info($"批量更新物料存放时间配置信息错误,错误信息:{ex.Message}", "GT");
                db.RollbackTran();
                result = false;
            }
            return result;
        }
        /// <summary>
        /// 查询物料存放时间配置信息
        /// </summary>
        /// <param name="bc_entried"></param>
        /// <returns></returns>
        public static Overage getOverage(string bc_entried)
        {
            var db = new SqlHelper<object>().GetInstance();
            // 直接执行 SQL(参数化查询)
            var sql = "SELECT get_ovg_bar(@barcode, @mcngrp) AS overage_value";
            var sql1 = "SELECT get_minhour_bar(@barcode, @mcngrp) AS overage_value";
            // 使用匿名对象传递参数
            var ovg_bar = db.Ado.SqlQuery<int>(sql, new { barcode = bc_entried, mcngrp = "1"}).First();
            var minhour_bar = db.Ado.SqlQuery<float>(sql1, new { barcode = bc_entried, mcngrp = "1"}).First();
            Overage overage = new Overage()
            {
                MINHOUR = minhour_bar,
                OVERAGE = ovg_bar
            };
            return overage;
        }
        /// <summary>
@@ -997,12 +1384,41 @@
        /// <summary>
        /// 查询上一次的同步时间
        /// </summary>
        /// <param name="recordTable"></param>
        /// <param name="tableType"></param>
        /// <returns></returns>
        public static SynDataTimeRecord getLastDataSynTime(string recordTable)
        public static string getDataLastSynTime(int tableType)
        {
            var db = new SqlHelper<object>().GetInstance();
            return db.Queryable<SynDataTimeRecord>().Where(a => a.RECORD_TABLE == recordTable).OrderByDescending(a => a.T_CREATE).First();
            string lastTimeStr = null;
            if (tableType == 1)
            {
                var lastTime = db.Queryable<ItemBarcodeInfo>().OrderByDescending(a => a.LAST_MODIFY_TIME).Select(a => a.LAST_MODIFY_TIME).First();
                if (lastTime != null)
                {
                    lastTimeStr = lastTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
                }
            }
            else if (tableType == 2)
            {
                var lastTime = db.Queryable<IwmsSemiBldBcstatus>().OrderByDescending(a => a.LAST_MODIFY_TIME).Select(a => a.LAST_MODIFY_TIME).First();
                if (lastTime != null)
                {
                    lastTimeStr = lastTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
                }
            }
            else if (tableType == 3)
            {
                var lastTime = db.Queryable<IwmsSemiBldBcsample>().OrderByDescending(a => a.LAST_MODIFY_TIME).Select(a => a.LAST_MODIFY_TIME).First();
                if (lastTime != null)
                {
                    lastTimeStr = lastTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
                }
            }
            if (lastTimeStr == null)
            {
                lastTimeStr = DateTime.Now.AddDays(-15).ToString("yyyy-MM-dd HH:mm:ss.fff");
            }
            return lastTimeStr;
        }
    }    
}