using HH.WCS.HangYang.api; using HH.WCS.HangYang.LISTA.models; using HH.WCS.HangYang.LISTA.models.wms; using HH.WCS.HangYang.models.other; using HH.WCS.HangYang.util; using Newtonsoft.Json; using NLog; using NLog.Fluent; using Opc.Ua; using SqlSugar; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IdentityModel.Protocols.WSTrust; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Remoting.Messaging; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; using static HH.WCS.HangYang.api.ApiModel; namespace HH.WCS.HangYang.wms { /// /// wms管到作业 /// internal class WMSHelper { internal static string GenerateTaskNo() { var id = SYSHelper.GetSerialNumber("作业号", "OP"); var date = DateTime.Now.ToString("yyMMdd"); return $"OP{date}{id.ToString().PadLeft(4, '0')}"; } internal static string GenerateSortingNo() { var id = SYSHelper.GetSerialNumber("分拣单", "SO"); var date = DateTime.Now.ToString("yyMMdd"); return $"SO{date}{id.ToString().PadLeft(4, '0')}"; } internal static List GetWaitingOperationList() { var db = new SqlHelper().GetInstance(); return db.Queryable().Where(a => a.N_B_STATE == 0 || a.N_B_STATE == 3).ToList(); } internal static bool CreateSortingOrder(List list) { var res = false; //遍历获取发货单,然后判断库存,如果全都没库存,则不生成分拣单,如果有生成分拣单 //更新波次单,即使只有一个发货单也更新波次单 var db = new SqlHelper().GetInstance(); var sortingOrderNo = ""; SortingOrder sortingOrder = null; try { db.BeginTran(); list.ForEach(a => { var so = db.Queryable().Includes(s => s.Details).Where(s => s.S_NO == a).First(); //判断库存,只有已经入库的才可以计算,码盘的不算,入库后出库途中的也要计算,码盘后默认叫待入库,入库后叫正常 //生成出库任务,只有托盘位置在立库才需要创建出库任务(先写死立库) //只有全部分拣完成才允许回库(一个托盘可能对应多个分拣明细) //查找仓库量表和库区量表,出库单需要定位仓库和库区了,出库定位物理库区就行,入库可能物料库区还要区分逻辑库区 //暂时只查仓库量表(量表如何重置,查找所有查找C_ENABLE=N的,判断如果在仓库中,改成Y,然后统计) //生成分拣单时候增加仓库量表分配量,生成分拣单明细时候,增加库区量表分配量 if (so != null && so.Details.Count > 0) { var fail = true; for (int i = 0; i < so.Details.Count; i++) { var whi = db.Queryable().Where(w => w.S_ITEM_CODE == so.Details[i].S_ITEM_CODE).First(); if (whi != null && whi.F_QTY - whi.F_ALLOC_QTY > 0) { //有货就出 float qty = whi.F_QTY - whi.F_ALLOC_QTY > so.Details[i].F_QTY ? so.Details[i].F_QTY : whi.F_QTY - whi.F_ALLOC_QTY; fail = false; //有可用库存,生成分拣单 if (sortingOrderNo == "") { sortingOrderNo = GenerateSortingNo(); sortingOrder = new SortingOrder { S_NO = sortingOrderNo, S_SHIPPING_NO = so.S_NO, Composes = new List() }; //创建分拣单 db.Insertable(sortingOrder).ExecuteCommand(); } else { //获取最新分拣单 sortingOrder = db.Queryable().Includes(s => s.Composes).Where(s => s.S_NO == sortingOrderNo).First(); //更新分拣单中的发货单单号 sortingOrder.S_SHIPPING_NO = sortingOrder.S_SHIPPING_NO + ";" + so.S_NO; sortingOrder.T_MODIFY = DateTime.Now; db.Updateable(sortingOrder).UpdateColumns(it => new { it.S_SHIPPING_NO, it.T_MODIFY }).ExecuteCommand(); } //查询分拣单子表(正常情况还需要增加批次判断) var soc = db.Queryable().Where(s => s.S_ITEM_CODE == so.Details[i].S_ITEM_CODE && s.S_SORTING_NO == sortingOrder.S_NO).First(); if (soc == null) { soc = new SortingCompose { S_ITEM_CODE = so.Details[i].S_ITEM_CODE, S_SORTING_NO = sortingOrder.S_NO, F_QTY = qty, N_ROW_NO = sortingOrder.Composes.Count() + 1, S_BATCH_NO = so.Details[i].S_BATCH_NO }; //创建分拣单子表 db.Insertable(soc).ExecuteCommand(); } else { soc.F_QTY += qty; soc.T_MODIFY = DateTime.Now; //更新分拣单子表 db.Updateable(soc).UpdateColumns(it => new { it.F_QTY, it.T_MODIFY }).ExecuteCommand(); } //更新仓库量表分配量 whi.F_ALLOC_QTY += qty; whi.T_MODIFY = DateTime.Now; db.Updateable(whi).UpdateColumns(it => new { it.F_ALLOC_QTY, it.T_MODIFY }).ExecuteCommand(); //跟新发货单子表配货数量 so.Details[i].F_ACC_D_QTY += qty; so.Details[i].T_MODIFY = DateTime.Now; db.Updateable(so.Details[i]).UpdateColumns(it => new { it.F_ACC_D_QTY, it.T_MODIFY }).ExecuteCommand(); } } //更新发货单状态 so.N_B_STATE = fail ? 5 : 1; if (fail) { so.S_NOTE = "没有库存"; } so.S_WAVE_CODE = sortingOrderNo; so.T_MODIFY = DateTime.Now; db.Updateable(so).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY, it.S_WAVE_CODE, it.S_NOTE }).ExecuteCommand(); } //查找库区内托盘生成托盘明细(包括分拣中)怎么判断库内?查找货位绑定?还是托盘上加标识比较好,如果是整托出的,分拣确认后去掉分拣标识。 //1、只有码盘入库后的才需要加标识,分拣回的不用,分拣回的标识还在,没有变。写死码盘入的完成后加标识,整托分拣的去掉标识,防止后面重新码盘脏数据 //2、或者关联查询库内的,只给分拣出的加标识,每次分拣回再把标识清除了(如果不清除带标识的会很多,还不如全部加标识),整托的清除。 //综合选择还是方案1,这样创建分拣明细,只需要托盘物料两表查询就能定位托盘(如果信息不准,可以重置),方案2需要货位、托盘、物料三表联查 //暂时不计库区,标识用容器表 C_ENABLE 来判断,表示能不能出库 //执行分拣创建任务,遍历分拣明细中的托盘,如果在仓库就出库,如果不在就不用出库 }); //全部分拣单生成之后将分拣单状态设置为开始配货,波次号为发货单 sortingOrder = db.Queryable().Where(s => s.S_NO == sortingOrderNo).First(); sortingOrder.N_B_STATE = 1; sortingOrder.T_MODIFY = DateTime.Now; db.Updateable(sortingOrder).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); db.CommitTran(); res = true; } catch (Exception ex) { Console.WriteLine(ex.Message); db.RollbackTran(); } return res; } internal static List GetSortingDetailByCntr(string cntr) { var db = new SqlHelper().GetInstance(); var result = db.Queryable().Where(a => a.S_CNTR_CODE == cntr && a.N_B_STATE != 2).ToList(); return result; } internal static bool CreateWmsTask(WMSTask wmsTask) { try { var db = new SqlHelper().GetInstance(); return db.Insertable(wmsTask).ExecuteCommand() > 0; } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } } internal static WMSTask GetWmsTaskByCntr(string cntr, bool active = true) { WMSTask result = null; var db = new SqlHelper().GetInstance(); if (active) { result = db.Queryable().Where(a => a.S_CNTR_CODE.Contains(cntr) && a.N_B_STATE < 2).First(); } else { result = db.Queryable().Where(a => a.S_CNTR_CODE.Contains(cntr)).First(); } return result; } internal static Location GetEnd(WMSTask a) { throw new NotImplementedException(); } internal static void UpdateTaskState(WMSTask task) { var db = new SqlHelper().GetInstance(); task.T_MODIFY = DateTime.Now; task.S_B_STATE = WMSTask.GetStateStr(task.N_B_STATE); db.Updateable(task).UpdateColumns(a => new { a.N_B_STATE, a.S_B_STATE, a.T_MODIFY }).ExecuteCommand(); } internal static bool UpdateTaskEnd(WMSTask a) { var db = new SqlHelper().GetInstance(); a.T_MODIFY = DateTime.Now; return db.Updateable(a).UpdateColumns(it => new { it.S_END_LOC, it.T_MODIFY }).ExecuteCommand() > 0; } internal static void CreateSortingOrderDetail(string so_no) { //分拣单配货 var db = new SqlHelper().GetInstance(); var so = db.Queryable().Includes(a => a.Composes).Where(a => a.S_NO == so_no && (a.N_B_STATE == 1 || a.N_B_STATE == 20)).First();// if (so != null && so.Composes.Count > 0) { db.BeginTran(); try { int rowNo = 1; so.Composes.ForEach(a => { //按分拣单子表去配货,查找可用托盘(先查所有符合的,后面再优化) int param1 = 10; int param2 = 20; Expression> exp = x => x > param1; var cirList = db.Queryable().Includes(c => c.Cntr).Where(c => c.Cntr.C_ENABLE == "Y" && c.S_ITEM_CODE == a.S_ITEM_CODE && (c.F_QTY - c.F_ALLOC_QTY) > 0).OrderBy(c => c.T_CREATE).ToList(); for (int i = 0; i < cirList.Count; i++) { var cir = cirList[i]; var sd = new SortingDetail { N_ROW_NO = rowNo, S_BATCH_NO = a.S_BATCH_NO, S_ITEM_CODE = a.S_ITEM_CODE, S_CNTR_CODE = cir.S_CNTR_CODE, S_SORTING_NO = a.S_SORTING_NO }; bool needBreak = false; if (cir.F_QTY - cir.F_ALLOC_QTY >= (a.F_QTY - a.F_ACC_S_QTY)) { //容器可用数量大于分拣单子表要求的数量,生成分拣明细,然后跳出循环 sd.F_QTY = a.F_QTY - a.F_ACC_S_QTY; needBreak = true; } else { //生成分拣明细,继续创建 sd.F_QTY = cir.F_QTY - cir.F_ALLOC_QTY; } db.Insertable(sd).ExecuteCommand(); rowNo++; //更新容器货品表分配量 cir.F_ALLOC_QTY += sd.F_QTY; cir.T_MODIFY = DateTime.Now; db.Updateable(cir).UpdateColumns(it => new { it.F_ALLOC_QTY, it.T_MODIFY }).ExecuteCommand(); //更新分拣单子表的量 a.F_ACC_S_QTY += sd.F_QTY; a.T_MODIFY = DateTime.Now; db.Updateable(cir).UpdateColumns(it => new { it.F_ALLOC_QTY, it.T_MODIFY }).ExecuteCommand(); if (needBreak) { break; } } }); //全部分拣单子表生成分拣明细,修改分拣单状态为配货完成(反正仓库的货能配的都配完了,除非数据有异常) so.N_B_STATE = 2; so.T_MODIFY = DateTime.Now; db.Updateable(so).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); db.CommitTran(); } catch (Exception ex) { Console.WriteLine(ex.Message); db.RollbackTran(); } } } /// /// 获取配货完成的分拣单,每个分拣单单独创建分拣作业 /// /// internal static List GetWaitingSortingOperationList() { var db = new SqlHelper().GetInstance(); return db.Queryable().Includes(a => a.Details).Where(a => a.N_B_STATE == 2 || a.N_B_STATE == 3).ToList(); } /// ///单个分拣单的分拣明细创建作业 /// internal static void CreateSortingOperation(SortingOrder so) { var list = so.Details.Where(a => a.N_B_STATE == 0).ToList(); var db = new SqlHelper().GetInstance(); if (list.Count > 0) { try { db.BeginTran(); list.GroupBy(g => g.S_CNTR_CODE).ToList().ForEach(g => { var cntr = g.Key; var sdList = g.ToList(); //查询托盘货位,查到了创建任务,查不到说明在别的分拣作业中,不创建任务,需要回库后重新创建 var lcr = db.Queryable().Where(c => c.S_CNTR_CODE == cntr).First(); if (lcr != null) { //判断托盘是否已经创建任务了,可能多个分拣明细是同一个托盘,如果创建任务了,其它分拣的要稍后,只出一次人工会搞不清楚是哪个分拣单的 var wmsTask = db.Queryable().Where(op => op.S_CNTR_CODE.Contains(cntr) && op.N_B_STATE < 2).First(); if (wmsTask == null) { wmsTask = new WMSTask { S_CNTR_CODE = cntr, S_CODE = WMSHelper.GenerateTaskNo(), S_START_LOC = lcr.S_LOC_CODE, S_END_LOC = "", N_TYPE = 2, S_TYPE = WMSTask.GetTypeStr(2), S_OP_DEF_CODE = "", S_OP_DEF_NAME = "分拣出立库" }; if (db.Insertable(wmsTask).ExecuteCommand() > 0) { LocationHelper.LockLoc(lcr.S_LOC_CODE, 2); } sdList.ForEach(a => { a.N_B_STATE = 1; a.T_MODIFY = DateTime.Now; db.Updateable(a).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); }); } } //修改分拣单状态为开始作业(不代表作业全部创建了,因为有的托盘可能已经被占用了) so.N_B_STATE = 3; //如果所有的作业都已经创建了,就设置为作业已经创建 if (so.Details.Count(s => s.N_B_STATE == 0) == 0) { so.N_B_STATE = 4; } so.T_MODIFY = DateTime.Now; db.Updateable(so).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); }); db.CommitTran(); } catch (Exception ex) { Console.WriteLine(ex.Message); db.RollbackTran(); } } } internal static void SortingConfrim(List models) { if (models.Count > 0) { var db = new SqlHelper().GetInstance(); try { db.BeginTran(); //先查寻该分拣单所有执行中的分拣明细 var sdList = db.Queryable().Where(a => a.S_SORTING_NO == models[0].sortNo).ToList(); var soList = db.Queryable().Includes(a => a.Details).Where(a => a.S_WAVE_CODE == models[0].sortNo).ToList(); //查出所有关联的发货单 models.ForEach(a => { //根据条目修改累计分拣数量,添加分拣结果表,同时批分到发货单中 var sd = sdList.Where(b => b.S_ITEM_CODE == a.itemCode).First(); //简单一点,要求人工必须一次性分拣完,分拣数量和分拣明细中一致才创建分拣结果,不支持一条分拣明细多次分拣,多条分拣结果 //(sd.F_QTY - sd.F_ACC_SR_QTY) == a.qty if (sd != null && sd.F_QTY == a.qty) { //查询分拣结果是否已经存在,存在就累加 //分拣单结果 var sdr = new SortingResult { S_SORTING_NO = models[0].sortNo, F_QTY = a.qty, N_ROW_NO = sd.N_ROW_NO, S_ITEM_CODE = sd.S_ITEM_CODE, S_ITEM_NAME = "", S_BATCH_NO = sd.S_BATCH_NO, S_CNTR_CODE = a.cntrCode }; db.Insertable(sdr).ExecuteCommand(); //sd.F_ACC_SR_QTY += a.qty; sd.N_B_STATE = 2; sd.T_MODIFY = DateTime.Now; db.Updateable(sd).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); //批分到发货单中 var dList = soList.SelectMany(s => s.Details).Where(d => d.S_ITEM_CODE == a.itemCode && (d.F_QTY - d.F_ACC_S_QTY) > 0).ToList(); var qty = a.qty; for (int i = 0; i < dList.Count; i++) { var d = dList[i]; //发货信息中间表 if (d.F_QTY - d.F_ACC_S_QTY >= a.qty) { //发货单数量超过托盘分拣数量,批分qty d.F_ACC_S_QTY += a.qty; qty = 0; } else { // 发货单数量小于托盘分拣数量 d.F_ACC_S_QTY = d.F_QTY; qty -= (d.F_QTY - d.F_ACC_S_QTY); } d.T_MODIFY = DateTime.Now; db.Updateable(d).UpdateColumns(it => new { it.F_ACC_S_QTY, it.T_MODIFY }).ExecuteCommand(); //插入到大元出库中间表 if (qty == 0) { break; } } //更新托盘明细表 var cir = db.Queryable().Where(c => c.S_CNTR_CODE == a.cntrCode && c.S_ITEM_CODE == a.itemCode).First(); cir.F_QTY -= a.qty; cir.F_ALLOC_QTY -= a.qty; cir.T_MODIFY = DateTime.Now; db.Updateable(cir).UpdateColumns(it => new { it.F_QTY, it.F_ALLOC_QTY, it.T_MODIFY }).ExecuteCommand(); //更新仓库量表和库区量表 //查询仓库量表 var wi = db.Queryable().Where(i => i.S_ITEM_CODE == a.itemCode).First(); if (wi != null) { wi.F_QTY -= a.qty; wi.F_ALLOC_QTY -= a.qty; wi.T_MODIFY = DateTime.Now; db.Updateable(wi).UpdateColumns(it => new { it.F_QTY, it.F_ALLOC_QTY, it.T_MODIFY }).ExecuteCommand(); } //麻烦了,分拣明细只记托盘,不记住起点,如果要统计库区量表,必须对托盘加标记,记录托盘的来源库区,托盘清空后再清除标记 //逻辑库区更不需要量表了,因为从一个逻辑库区出来,可能回其它逻辑库区,那就应该直接删除逻辑库区量表,后面再加到别的逻辑库区中 //但是会带来新的问题,就是分拣回的托盘不属于任何逻辑库区的量,只有回库了才能计算到 //var aziList = db.Queryable().Where(i => i.S_ITEM_CODE == a.itemCode).ToList(); } // }); //判断分拣单是否全部完成 if (sdList.Count(sd => sd.N_B_STATE != 2) == 0) { var spo = db.Queryable().Where(s => s.S_NO == models[0].sortNo).First(); if (spo != null) { spo.N_B_STATE = 10; spo.T_MODIFY = DateTime.Now; db.Updateable(spo).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); } } //判断发货单配货是否分拣完成 soList.ForEach(s => { if (s.Details.Count > 0 && s.Details.Count(sd => sd.F_ACC_D_QTY != sd.F_ACC_S_QTY) == 0) { //配货单的数量等于分拣的数量,分拣完成 s.N_B_STATE = 3; s.T_MODIFY = DateTime.Now; db.Updateable(s).UpdateColumns(it => new { it.N_B_STATE, it.T_MODIFY }).ExecuteCommand(); } }); db.CommitTran(); } catch (Exception ex) { Console.WriteLine(ex.Message); db.RollbackTran(); } } } internal static bool CheckSortingWholeCntr(string cntr, bool autoSort) { var result = false; //判断分拣明细是不是只有一个托盘,如果是,判断容器货品明细是不是和分拣明细完全一样 var db = new SqlHelper().GetInstance(); var sdList = db.Queryable().Where(a => a.S_CNTR_CODE == cntr).ToList(); if (sdList.Count > 0) { var groups = sdList.GroupBy(a => a.S_SORTING_NO).ToList(); if (groups.Count == 1) { var list = groups.ToList(); //只有一个分拣单的情况下,分配量和托盘量都相同,表示是整托 var cirList = db.Queryable().Where(a => a.S_CNTR_CODE == cntr).ToList(); if (list.Count == cirList.Count && cirList.Count(c => c.F_QTY != c.F_ALLOC_QTY) == 0) { result = true; if (autoSort) { var data = sdList.Select(s => new SortingResultCheck { cntrCode = s.S_CNTR_CODE, itemCode = s.S_ITEM_CODE, qty = s.F_QTY, sortNo = s.S_SORTING_NO }).ToList(); SortingConfrim(data); } } } } return result; } #region 杭氧WMS帮助方法 /// /// 新增物料主数据 /// /// /// internal static bool CreateMaterialData(List materials) { try { bool res = false; var db = new SqlHelper().GetInstance(); db.BeginTran(); foreach (var material in materials) { var _Material = db.Queryable().Where(c => c.S_ITEM_CODE == material.S_ITEM_CODE).First(); if (_Material != null) { db.Updateable(material).UpdateColumns(a => new { a.S_ITEM_NAME, a.S_AREA_CODE, a.S_ITEM_SPEC, a.S_MATERIAL, a.S_UOM, a.C_CLEAN, a.T_MODIFY }).ExecuteCommand(); } else { db.Insertable(material).ExecuteCommand(); } } db.CommitTran(); res = true; return res; } catch (Exception ex) { var sugarEx = ex as SqlSugar.SqlSugarException; if (sugarEx != null) { LogHelper.Error($"创建入库单SQL错误: {sugarEx.Sql}", sugarEx, "杭氧"); } else { LogHelper.Error($"创建入库单失败:{ex.Message}", ex, "杭氧"); } return false; } } /// /// 新增入库单 /// /// /// internal static bool CreateOrderIn(List order) { try { bool res = false; var db = new SqlHelper().GetInstance(); db.BeginTran(); foreach (var orderItem in order) { var _inOrder = db.Queryable().Where(c => c.S_NO == orderItem.S_NO).First(); if (_inOrder != null) { foreach (var item in orderItem.InOrderDetail) { var _orderDetail = db.Queryable().Where(c => c.S_BS_NO == orderItem.S_BS_NO && c.S_ITEM_CODE == item.S_ITEM_CODE).First(); if (_orderDetail != null) { var _order = db.Queryable().Where(c => c.S_NO == _orderDetail.S_IO_NO).First(); if (_order.N_B_STATE == 0) { _orderDetail.S_ITEM_NAME = item.S_ITEM_NAME; _orderDetail.F_QTY = item.F_QTY; _orderDetail.T_MODIFY = DateTime.Now; db.Updateable(_orderDetail).UpdateColumns(a => new { a.S_ITEM_NAME, a.F_QTY ,a.T_MODIFY}).ExecuteCommand(); } } } } else { db.Insertable(orderItem).ExecuteCommand(); db.Insertable(orderItem.InOrderDetail).ExecuteCommand(); } //if (_order != null) //{ // if (_order.N_B_STATE == 0) // { // foreach (var item in orderItem.InOrderDetail) // { // } // } //} //else //{ //} } db.CommitTran(); res = true; return res; } catch (Exception ex) { var sugarEx = ex as SqlSugar.SqlSugarException; if (sugarEx != null) { LogHelper.Error($"创建入库单SQL错误: {sugarEx.Sql}", sugarEx, "杭氧"); } else { LogHelper.Error($"创建入库单失败:{ex.Message}", ex, "杭氧"); } return false; } } /// /// 新增出库单 /// /// /// internal static bool CreateOrderOut(List order) { try { bool res = false; var db = new SqlHelper().GetInstance(); db.BeginTran(); foreach (var orderItem in order) { var _outOrder = db.Queryable().Where(c => c.S_NO == orderItem.S_NO).First(); if (_outOrder != null) { foreach (var item in orderItem.OutOrderDetail) { var _orderDetail = db.Queryable().Where(c => c.S_BS_NO == orderItem.S_BS_NO && c.S_ITEM_CODE == item.S_ITEM_CODE).First(); if (_orderDetail != null) { var _order = db.Queryable().Where(c => c.S_NO == _orderDetail.S_OO_NO).First(); if (_order.N_B_STATE == 0) { _orderDetail.S_ITEM_NAME = item.S_ITEM_NAME; _orderDetail.F_QTY = item.F_QTY; _orderDetail.T_MODIFY = DateTime.Now; db.Updateable(_orderDetail).UpdateColumns(a => new { a.S_ITEM_NAME, a.F_QTY,a.T_MODIFY }).ExecuteCommand(); } } } } else { db.Insertable(orderItem).ExecuteCommand(); db.Insertable(orderItem.OutOrderDetail).ExecuteCommand(); } //var _order = db.Queryable().Where(c => c.S_NO == orderItem.S_NO).First(); //if (_order != null) //{ // if (_order.N_B_STATE == 0) // { // db.Updateable(orderItem).UpdateColumns(a => new { a.S_OP_TYPE, a.S_BS_TYPE, a.S_NOTE, a.T_MODIFY }).ExecuteCommand(); // foreach (var item in orderItem.OutOrderDetail) // { // db.Updateable(item).UpdateColumns(a => new { a.S_ITEM_CODE, a.S_ITEM_NAME, a.F_QTY }).ExecuteCommand(); // } // } //} //else //{ // db.Insertable(orderItem).ExecuteCommand(); // db.Insertable(orderItem.OutOrderDetail).ExecuteCommand(); //} } db.CommitTran(); res = true; return res; } catch (Exception ex) { LogHelper.Error($"创建出库单失败:{ex.Message}", ex, "杭氧"); return false; } } /// /// 入库单取消 /// /// /// internal static bool CancelOrderIn(InOrderCancel order) { try { bool res = false; var db = new SqlHelper().GetInstance(); db.BeginTran(); foreach (var item in order.OrderDetail) { var _orderDetail = db.Queryable().Where(c => c.S_BS_NO == order.S_BS_NO).ToList(); if (_orderDetail.Any()) { var orderItem = _orderDetail.FirstOrDefault(a => a.S_ITEM_CODE.Trim() == item.S_ITEM_CODE); if (orderItem != null) { var _order = db.Queryable().Where(c => c.S_NO == orderItem.S_IO_NO).First(); if (_order.N_B_STATE == 0) { db.Deleteable().Where(it => it.S_ITEM_CODE.Trim() == item.S_ITEM_CODE && it.S_IO_NO == orderItem.S_IO_NO).ExecuteCommand(); _orderDetail.RemoveAll(s => s.S_ITEM_CODE == item.S_ITEM_CODE); if (!_orderDetail.Any()) { db.Deleteable().Where(it => it.S_NO == orderItem.S_IO_NO).ExecuteCommand(); } } } } } db.CommitTran(); res = true; return res; } catch (Exception ex) { var sugarEx = ex as SqlSugar.SqlSugarException; if (sugarEx != null) { LogHelper.Error($"取消入库单SQL错误: {sugarEx.Sql}", sugarEx, "杭氧"); } else { LogHelper.Error($"取消入库单失败:{ex.Message}", ex, "杭氧"); } return false; } } /// /// 出库单取消 /// /// /// internal static bool CancelOrderOut(OutOrderCancel order) { try { bool res = false; var db = new SqlHelper().GetInstance(); db.BeginTran(); foreach (var item in order.OrderDetail) { var _orderDetail = db.Queryable().Where(c => c.S_BS_NO == order.S_BS_NO).ToList(); if (_orderDetail.Any()) { var orderItem = _orderDetail.FirstOrDefault(a => a.S_ITEM_CODE.Trim() == item.S_ITEM_CODE); if (orderItem != null) { var _order = db.Queryable().Where(c => c.S_NO == orderItem.S_OO_NO).First(); if (_order.N_B_STATE == 0) { db.Deleteable().Where(it => it.S_ITEM_CODE.Trim() == item.S_ITEM_CODE && it.S_OO_NO == orderItem.S_OO_NO).ExecuteCommand(); _orderDetail.RemoveAll(s => s.S_ITEM_CODE == item.S_ITEM_CODE); if (!_orderDetail.Any()) { db.Deleteable().Where(it => it.S_NO == orderItem.S_OO_NO).ExecuteCommand(); } } } } } db.CommitTran(); res = true; return res; } catch (Exception ex) { var sugarEx = ex as SqlSugar.SqlSugarException; if (sugarEx != null) { LogHelper.Error($"取消出库单SQL错误: {sugarEx.Sql}", sugarEx, "杭氧"); } else { LogHelper.Error($"取消出库单失败:{ex.Message}", ex, "杭氧"); } return false; } } #endregion #region 杭氧特殊帮助方法 /// /// 查询物料的属性 /// /// /// internal static TN_Material GetItemType(string itemCode) { var db = new SqlHelper().GetInstance(); var lcrItem = db.Queryable().Where(a => a.S_ITEM_CODE.Trim() == itemCode).First(); if (lcrItem == null) { return null; } return lcrItem; } /// /// 获取需要合托的托盘 /// /// 托盘号 /// internal static LocCntrRel GetLocMerge(string trayCode) { var db = new SqlHelper().GetInstance(); var reservoirs = Settings.ReservoirAreas.Where(s => s.areaName == "托盘立库区").FirstOrDefault(); if (reservoirs == null) { LogHelper.Info("Settings出现错误未查询到杭氧的托盘立库区!", "杭氧"); return null; } var lcrItem = db.Queryable().Where(a => a.S_CNTR_CODE.Trim() == trayCode).First(); // 计算时间范围:前一天和今天的 00:00:00 到 23:59:59 DateTime yesterdayStart = DateTime.Today.AddDays(-1); DateTime yesterdayEnd = DateTime.Today.AddDays(-1).AddDays(1).AddSeconds(-1); DateTime NowdayStart = DateTime.Now.AddDays(-1); DateTime NowdayEnd = DateTime.Now.AddDays(-1).AddDays(1).AddSeconds(-1); var locCntrs = db.Queryable().LeftJoin((p, m) => p.S_CNTR_CODE == m.S_CNTR_CODE) .LeftJoin((p, m, s) => m.S_ITEM_CODE == s.S_ITEM_CODE) .Where((p, m, s) => p.T_CREATE >= yesterdayStart && p.T_CREATE < yesterdayEnd || p.T_CREATE >= NowdayStart && p.T_CREATE < NowdayEnd) // 前一天或者今天的数据 .Where((p, m, s) => m.S_ITEM_CODE == lcrItem.S_ITEM_CODE) .Where((p, m, s) => m.F_QTY < s.F_MAX_QTY) .OrderBy((p, m, s) => p.T_CREATE) .Select((p, m, s) => p) // 选择托盘物料表的数据 .ToList(); //只查询立库的货位 var locList = db.Queryable().Where(p => p.S_AREA_CODE == reservoirs.areaCode && p.S_LOCK_STATE == "无").Select(s => s.S_CODE.Trim()).ToList(); locCntrs = locCntrs.Where(p => locList.Contains(p.S_LOC_CODE.Trim())).ToList(); if (!locCntrs.Any()) { return null; } return locCntrs.FirstOrDefault(); } /// /// 根据托盘查询货位明细 /// /// 托盘号 /// internal static LocCntrRel GetCntrLoc(string trayCode) { var db = new SqlHelper().GetInstance(); var locCntr = db.Queryable().Where(a => a.S_CNTR_CODE.Trim() == trayCode).First(); var locList = db.Queryable().Where(p => p.S_CODE == locCntr.S_LOC_CODE && p.N_CURRENT_NUM == p.N_CAPACITY && p.S_LOCK_STATE.Trim() == "无" && p.S_AREA_CODE == "JXHCQ").First(); if (locList == null) { return null; } return locCntr; } /// /// 任务状态转换 /// /// 状态号 /// internal static string GetStateStr(int state) { //0 等待/1 执行/2 完成/3 错误/4 启劢失败 /5 暂停启劢 var status = ""; switch (state) { case 0: status = "等待"; break; case 1: status = "执行"; break; case 2: status = "完成"; break; case 3: status = "错误"; break; case 4: status = "启劢失败"; break; case 5: status = "暂停启劢"; break; } return status; } /// /// 查询配盘单 /// /// 配盘单状态 /// internal static List GetPickingListByState(int state) { var db = new SqlHelper().GetInstance(); var distributionCntr = db.Queryable().Where(a => a.N_B_STATE == state).ToList(); return distributionCntr; } /// /// 配盘单状态转换 /// /// 状态号 /// internal static string GetDistributionStateStr(int state) { var status = ""; switch (state) { case 1: status = "已配货"; break; case 2: status = "出库中"; break; case 3: status = "已出库"; break; case 4: status = "分拣完成 "; break; } return status; } /// /// 修改配盘单状态 /// /// 作业状态 /// 状态 /// 托盘号 /// internal static bool UpdateDistributionCntrState(int taskState, int state, string trayCode) { var db = new SqlHelper().GetInstance(); var distributionCntr = db.Queryable().Where(a => a.N_B_STATE == taskState && a.S_CNTR_CODE == trayCode).First(); if (distributionCntr != null) { distributionCntr.N_B_STATE = state; distributionCntr.S_B_STATE = GetDistributionStateStr(state); return db.Updateable(distributionCntr).UpdateColumns(it => new { it.S_B_STATE, it.N_B_STATE }).ExecuteCommand() > 0; } return false; } #endregion #region 杭氧作业逻辑方法 /// /// 根据状态查询作业 /// /// /// internal static List GetWmsTaskListByState(string status) { var db = new SqlHelper().GetInstance(); return db.Queryable().Where(a => a.S_B_STATE.Trim() == status).OrderBy(s => s.T_CREATE).ToList(); } /// /// 根据状态和托盘号查询作业 /// /// /// /// internal static WMSTask GetWmsTaskList(string status, string trayCode) { var db = new SqlHelper().GetInstance(); return db.Queryable().Where(a => a.S_CNTR_CODE == trayCode && a.S_B_STATE == status).OrderBy(s => s.T_CREATE).First(); } /// /// 作业状态修改 /// /// 任务 /// 状态 /// internal static bool UpdateStatus(WMSTask task, int status) { var res = false; var db = new SqlHelper().GetInstance(); task.N_B_STATE = status; task.S_B_STATE = GetStateStr(status); db.Updateable(task).UpdateColumns(it => new { it.N_B_STATE, it.S_B_STATE }).ExecuteCommand(); return res; } /// /// 根据作业编码修改状态 /// /// 任务号 /// 状态 /// internal static bool UpdateTaskStatus(string taskNo, int status) { var res = false; var db = new SqlHelper().GetInstance(); var task = db.Queryable().Where(a => a.S_CODE == taskNo).First(); task.N_B_STATE = status; task.S_B_STATE = GetStateStr(status); db.Updateable(task).UpdateColumns(it => new { it.N_B_STATE, it.S_B_STATE }).ExecuteCommand(); return res; } /// /// 根据任务的作业编码修改状态和终点 /// /// /// /// internal static bool UpdateWmsTask(WCSTask wcsTask, int state) { var res = false; var db = new SqlHelper().GetInstance(); var task = db.Queryable().Where(a => a.S_CODE == wcsTask.S_OP_CODE).First(); if (task != null) { task.N_B_STATE = state; task.S_B_STATE = GetStateStr(state); task.S_END_AREA = wcsTask.S_END_AREA; task.S_END_LOC = wcsTask.S_END_LOC; //需要判断任务是否失败或者已完成,不允许再修改 res = db.Updateable(task).UpdateColumns(it => new { it.N_B_STATE, it.S_B_STATE, it.S_END_LOC, it.S_END_AREA }).ExecuteCommand() > 0; } return res; } internal static bool UpdateTask(WMSTask a, int state) { var db = new SqlHelper().GetInstance(); a.T_MODIFY = DateTime.Now; a.N_B_STATE = state; a.S_B_STATE = GetStateStr(state); return db.Updateable(a).UpdateColumns(it => new { it.N_B_STATE, it.S_B_STATE, it.S_CNTR_CODE, it.S_START_LOC, it.T_MODIFY }).ExecuteCommand() > 0; } #endregion #region 杭氧立库出入库逻辑算法 /// /// 立库入库封装算法 /// public class Warehouse { private readonly List _locations; private readonly Dictionary _depthOuterMap = new Dictionary { { 1, 2 }, // 第1排深位对应第2排外侧 { 4, 3 } // 第4排深位对应第3排外侧 }; public Warehouse(Inbound inbound) { var db = new SqlHelper().GetInstance(); var locations = db.Queryable().Where(a => a.S_AREA_CODE == inbound.areaCode).ToList(); if (inbound.roadWay != 0) { locations.RemoveAll(s => s.N_ROADWAY != inbound.roadWay); } _locations = new List(); // 初始化所有货位 for (int r = 0; r < locations.Count; r++) { bool isDepthRow = _depthOuterMap.ContainsKey(locations[r].N_ROW); bool isOuterRow = _depthOuterMap.Values.Contains(locations[r].N_ROW); // 深位排有双深位 if (isDepthRow) { _locations.Add(new LocationParams { Row = locations[r].N_ROW, Column = locations[r].N_COL, Layer = locations[r].N_LAYER, roadWay = locations[r].N_ROADWAY, loationCode = locations[r].S_CODE, IsOccupied = locations[r].N_CURRENT_NUM > 0 ? true : false, HasOutboundLock = locations[r].S_LOCK_STATE == "无" ? false : true, Depth = 1, IsStorageDepth = true, IsOuterPosition = false }); } // 其他排只有单深位 else { _locations.Add(new LocationParams { Row = locations[r].N_ROW, Column = locations[r].N_COL, Layer = locations[r].N_LAYER, roadWay = locations[r].N_ROADWAY, loationCode = locations[r].S_CODE, Depth = 0, IsStorageDepth = false, IsOuterPosition = isOuterRow }); } } } /// /// 货位锁定(算法内锁定不实际锁定货位) /// /// /// /// public void SetOutboundLock(int row, int col, int layer) { var loc = _locations.FirstOrDefault(l => l.Row == row && l.Column == col && l.Layer == layer && l.IsStorageDepth); if (loc != null) loc.HasOutboundLock = true; } public LocationParams FindNextStorageLocation() { // 阶段1: 查找所有可用深位(后深位) var depthLocs = _locations .Where(l => l.IsStorageDepth && !l.IsOccupied && !l.HasOutboundLock) .OrderBy(l => l.Row) .ThenBy(l => l.Column) .ThenBy(l => l.Layer); foreach (var loc in depthLocs) { int outerRow = _depthOuterMap.First(x => x.Key == loc.Row).Value; // 检查前深位是否被占用 var frontLoc = _locations.FirstOrDefault(l => l.Row == outerRow && l.Column == loc.Column && l.Layer == loc.Layer && l.Depth == 0); if (frontLoc == null || !frontLoc.IsOccupied) return loc; } // 阶段2: 查找普通排(非深位非外侧) var normalLoc = _locations .FirstOrDefault(l => !l.IsOuterPosition && !l.IsStorageDepth && !l.IsOccupied && !_depthOuterMap.Values.Contains(l.Row)); if (normalLoc != null) return normalLoc; // 阶段3: 最后查找外侧排(确保对应深位无锁) foreach (var outerRow in _depthOuterMap.Values.OrderBy(r => r)) { // 查找该外侧排所有可用货位(按巷道均衡排序) var availableOuterLocs = _locations .Where(l => l.IsOuterPosition && l.Row == outerRow && !l.IsOccupied) .GroupBy(l => l.roadWay) .SelectMany(g => g.OrderBy(l => l.Row) .ThenBy(l => l.Column) .ThenBy(l => l.Layer)); foreach (var outerLoc in availableOuterLocs) { // 获取对应的深位排(第1排或第4排) var depthRow = _depthOuterMap.First(x => x.Value == outerLoc.Row).Key; // 查找对应深位货位(相同列和层) var depthLoc = _locations.FirstOrDefault(l => l.Row == depthRow && l.Column == outerLoc.Column && l.Layer == outerLoc.Layer && l.IsStorageDepth); // 只有对应深位没有出库锁时才使用此外侧货位 if (depthLoc != null && !depthLoc.HasOutboundLock) { return outerLoc; } } } return null; // 无可用位置 } private readonly ConcurrentDictionary _locationLocks = new ConcurrentDictionary(); public async Task StoreItemAsync() { LocationParams location = null; try { // 1. 查找可用货位(不需要锁) location = FindNextStorageLocation(); if (location == null) return location; // 2. 获取该货位的专用锁 var locationLock = _locationLocks.GetOrAdd( $"{location.loationCode}", _ => new SemaphoreSlim(1, 1)); await locationLock.WaitAsync(); // 3. 执行入库 location.IsOccupied = true; Console.WriteLine($"入库I:排号{location.Row},列号{location.Column},层号{location.Layer},货位{location.loationCode}"); return location; } finally { if (location != null) { var key = $"{location.loationCode}"; if (_locationLocks.TryGetValue(key, out var locLock)) { locLock.Release(); } } } } } /// /// 配盘出库封装算法 /// public class DoubleDeepOutboundScheduler { private readonly List _allLocations;// 所有货位 public DoubleDeepOutboundScheduler(List allLocations) { var db = new SqlHelper().GetInstance(); //查询这个库区的货位 var list = db.Queryable().Where(a => a.S_AREA_CODE == allLocations.First().S_AREA_CODE).ToList(); _allLocations = list; } // 生成出库任务队列(含移库任务) /// /// 立库WMS货位出库算法(运用于配盘单出库) /// /// 出库参数 /// public List GenerateOutboundTasks(List outbound) { // 阶段1: 标记未来需要保留的货位 MarkReservedLocations(outbound); var tasks = new List(); var outboundLocCodes = outbound.Select(o => o.locCode).ToHashSet(); foreach (var outboundItem in outbound) { // 1. 查找目标货位 var targetLoc = FindTargetLocation(outboundItem.locCode); if (targetLoc == null) continue; var wmsTask = WMSHelper.GetWmsTaskList("执行", outboundItem.trayCode); if (wmsTask != null) { //LogHelper.Info($"未查询到在执行中的作业:{outboundItem.trayCode}!", "杭氧"); outboundItem.opCode = wmsTask.S_CODE; } // 2. 处理双深位逻辑(第1排或第4排) if (IsDoubleDeepRow(targetLoc.N_ROW)) { // 2.1 深位出库需先移出外侧物料 var outerLoc = FindOuterLocation(targetLoc); //如果找不到,则跳过此条出库任务 if (outerLoc != null) { //如果货位已满,则生成移库任务 if (outerLoc.N_CURRENT_NUM == outerLoc.N_CAPACITY) { if (outboundLocCodes.Contains(outerLoc.S_CODE)) { // 方案1:合并任务(直接出库外侧货位) tasks.Add(CreateOutboundTask(outerLoc, outbound.First(o => o.locCode == outerLoc.S_CODE), "1")); } else { // 优先移到深位,其次外侧 var bestTarget = FindBestRelocationTarget(outerLoc); if (bestTarget != null) { //计算到了外侧把外侧的货位锁定 _allLocations .Where(x => x.S_CODE == outerLoc.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 3); var trayCode = ContainerHelper.GetLocCntr(outerLoc.S_CODE); tasks.Add(new WCSTask { S_CODE = GenerateTaskNo(), S_START_AREA = outerLoc.S_AREA_CODE, S_END_AREA = bestTarget.S_AREA_CODE, S_START_LOC = outerLoc.S_CODE, S_END_LOC = bestTarget.S_CODE, S_TYPE = "深位移库", S_OP_CODE = outboundItem.opCode, N_PRIORITY = 1, N_SCHEDULE_TYPE = 1, N_B_STATE = 0, S_B_STATE = WCSTask.GetStateStr(0), S_CNTR_CODE = trayCode.S_CNTR_CODE, N_START_LAYER = 1, N_END_LAYER = 1, N_CNTR_COUNT = 1 }); } else continue; } } } else { continue; } // 2.2 生成出库任务(深位) tasks.Add(CreateOutboundTask(targetLoc, outboundItem)); } else { // 3. 非双深位直接出库 tasks.Add(CreateOutboundTask(targetLoc, outboundItem)); } break; } // 4. 任务排序:移库任务优先 + 高优先级优先 return tasks; } // 关键辅助方法 private Location FindTargetLocation(string code) => _allLocations.FirstOrDefault(loc => loc.S_CODE == code && loc.N_LOCK_STATE == 0); private bool IsDoubleDeepRow(int row) => row == 1 || row == 4; //查询深位外侧的货位 private Location FindOuterLocation(Location deepLoc) => _allLocations.FirstOrDefault(loc => loc.N_ROW != deepLoc.N_ROW && loc.N_ROADWAY == deepLoc.N_ROADWAY && loc.N_COL == deepLoc.N_COL && loc.N_LAYER == deepLoc.N_LAYER && loc.N_LOCK_STATE == 0 || loc.N_LOCK_STATE == 5 ); private Location FindBestRelocationTarget(Location outerLoc) { return _allLocations .Where(loc => loc.N_ROADWAY == outerLoc.N_ROADWAY && loc.N_LOCK_STATE == 0 && loc.N_CURRENT_NUM == 0 && loc.S_CODE != outerLoc.S_CODE) .OrderBy(loc => loc.N_ROW == outerLoc.N_ROW ? 1 : 0) .ThenBy(loc => loc.N_COL) .ThenBy(loc => Math.Abs(loc.N_LAYER - outerLoc.N_LAYER)) .FirstOrDefault(); } private WCSTask CreateOutboundTask(Location loc, Outbound outbound, string priority = "") => new WCSTask { S_CODE = GenerateTaskNo(), S_START_AREA = loc.S_AREA_CODE, S_END_AREA = outbound.endArea, S_START_LOC = loc.S_CODE, S_END_LOC = outbound.endBit, S_TYPE = outbound.taskType, S_OP_CODE = outbound.opCode, N_PRIORITY = string.IsNullOrEmpty(priority) ? 0 : int.Parse(priority), N_SCHEDULE_TYPE = 1, N_B_STATE = 0, S_B_STATE = WCSTask.GetStateStr(0), S_CNTR_CODE = outbound.trayCode, N_START_LAYER = 1, N_END_LAYER = 1, N_CNTR_COUNT = 1 }; private void MarkReservedLocations(List outbound) { //查询已经被锁住的货位 var lockLoc = _allLocations .Where(loc => loc.N_LOCK_STATE != 0) .ToList(); //把锁住的货位标记为占用 foreach (var item in lockLoc) { if (IsDoubleDeepRow(item.N_ROW)) { _allLocations .Where(x => x.S_CODE == item.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 3); } } foreach (var item in outbound) { var loc = FindTargetLocation(item.locCode); if (loc != null && IsDoubleDeepRow(loc.N_ROW)) { // 标记该深位对应的外侧货位(如果存在) var outerLoc = FindOuterLocation(loc); if (outerLoc != null) { // 标记深位的外侧货位(避免被移库占用) _allLocations .Where(x => x.S_CODE == outerLoc.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 5); //标记深位的外侧货位为5 } } } } } /// /// 空托出库封装算法 /// public class EmptyPalletOutboundScheduler { private readonly List _allLocations; public EmptyPalletOutboundScheduler(string areaCode) { var db = new SqlHelper().GetInstance(); _allLocations = db.Queryable() .Where(a => a.S_AREA_CODE == areaCode) .ToList(); } /// /// 空托盘出库算法(自动寻找可用空托并生成任务) /// public List GenerateEmptyPalletTasks(Outbound outbound) { var tasks = new List(); var foundPallets = 0; if (foundPallets < outbound.requiredCount) { var doubleDeepPallets = FindDoubleDeepEmptyPallets(); foreach (var palletLoc in doubleDeepPallets) { if (IsDoubleDeepRow(palletLoc.N_ROW)) { var outerLoc = FindOuterLocation(palletLoc); if (outerLoc != null) { if (outerLoc.N_CURRENT_NUM == outerLoc.N_CAPACITY) { // 优先移到深位,其次外侧 var bestTarget = FindBestRelocationTarget(outerLoc); if (bestTarget != null) { //计算到了外侧把外侧的货位锁定 _allLocations .Where(x => x.S_CODE == outerLoc.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 3); var trayCode = ContainerHelper.GetLocCntr(outerLoc.S_CODE); tasks.Add(new WCSTask { S_CODE = GenerateTaskNo(), S_START_AREA = outerLoc.S_AREA_CODE, S_END_AREA = bestTarget.S_AREA_CODE, S_START_LOC = outerLoc.S_CODE, S_END_LOC = bestTarget.S_CODE, S_TYPE = "深位移库", S_OP_CODE = outbound.opCode, N_PRIORITY = 1, N_SCHEDULE_TYPE = 1, N_B_STATE = 0, S_B_STATE = WCSTask.GetStateStr(0), S_CNTR_CODE = trayCode.S_CNTR_CODE, N_START_LAYER = 1, N_END_LAYER = 1, N_CNTR_COUNT = 1 }); } else continue; } } } var tray = ContainerHelper.GetLocCntr(palletLoc.S_CODE); outbound.trayCode = tray.S_CNTR_CODE; tasks.Add(CreateEmptyPalletTask(palletLoc, outbound)); foundPallets++; if (foundPallets >= outbound.requiredCount) break; } } return tasks; } // 关键辅助方法 private List FindDoubleDeepEmptyPallets() => _allLocations.Where(loc => loc.N_LOCK_STATE == 0 && loc.N_CURRENT_NUM == loc.N_CAPACITY && ContainerHelper.GetLocItemRel(loc.S_CODE) ).OrderBy(loc => loc.N_ROW) // 按排排序 .ThenBy(loc => loc.N_COL) .ToList(); private WCSTask CreateEmptyPalletTask(Location loc, Outbound outbound) => new WCSTask { S_CODE = GenerateTaskNo(), S_START_AREA = loc.S_AREA_CODE, S_END_AREA = outbound.endArea, S_START_LOC = loc.S_CODE, S_END_LOC = outbound.endBit, S_TYPE = outbound.taskType, S_OP_CODE = outbound.opCode, N_PRIORITY = 0, N_SCHEDULE_TYPE = 1, N_B_STATE = 0, S_B_STATE = WCSTask.GetStateStr(0), S_CNTR_CODE = outbound.trayCode, N_START_LAYER = 1, N_END_LAYER = 1, N_CNTR_COUNT = 1 }; // 复用原有双深位方法 private bool IsDoubleDeepRow(int row) => row == 1 || row == 4; private Location FindTargetLocation(string code) => _allLocations.FirstOrDefault(loc => loc.S_CODE == code && loc.N_LOCK_STATE == 0); //查询深位外侧的货位 private Location FindOuterLocation(Location deepLoc) => _allLocations.FirstOrDefault(loc => loc.N_ROW != deepLoc.N_ROW && loc.N_ROADWAY == deepLoc.N_ROADWAY && loc.N_COL == deepLoc.N_COL && loc.N_LAYER == deepLoc.N_LAYER && loc.N_LOCK_STATE == 0 || loc.N_LOCK_STATE == 5 ); private Location FindBestRelocationTarget(Location outerLoc) { return _allLocations .Where(loc => loc.N_ROADWAY == outerLoc.N_ROADWAY && loc.N_LOCK_STATE == 0 && loc.N_CURRENT_NUM == 0 && loc.S_CODE != outerLoc.S_CODE) .OrderBy(loc => loc.N_ROW == outerLoc.N_ROW ? 1 : 0) .ThenBy(loc => loc.N_COL) .ThenBy(loc => Math.Abs(loc.N_LAYER - outerLoc.N_LAYER)) .FirstOrDefault(); } private void MarkReservedLocations(List outbound) { //查询已经被锁住的货位 var lockLoc = _allLocations .Where(loc => loc.N_LOCK_STATE != 0) .ToList(); //把锁住的货位标记为占用 foreach (var item in lockLoc) { if (IsDoubleDeepRow(item.N_ROW)) { _allLocations .Where(x => x.S_CODE == item.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 3); } } foreach (var item in outbound) { var loc = FindTargetLocation(item.locCode); if (loc != null && IsDoubleDeepRow(loc.N_ROW)) { // 标记该深位对应的外侧货位(如果存在) var outerLoc = FindOuterLocation(loc); if (outerLoc != null) { // 标记深位的外侧货位(避免被移库占用) _allLocations .Where(x => x.S_CODE == outerLoc.S_CODE) .ToList() .ForEach(x => x.N_LOCK_STATE = 5); //标记深位的外侧货位为5 } } } } } #endregion #region 杭氧升降量表帮助方法 /// /// MD5加密 /// /// /// public static string GetMd5FromString(string sInput) { var lstData = Encoding.GetEncoding("utf-8").GetBytes(sInput); var lstHash = new MD5CryptoServiceProvider().ComputeHash(lstData); var result = new StringBuilder(32); for (int i = 0; i < lstHash.Length; i++) { result.Append(lstHash[i].ToString("x2").ToUpper()); } return result.ToString(); } /// /// 根据任务升量 /// /// internal static bool AddChange(WCSTask task) { var db = new SqlHelper().GetInstance(); var result = true; try { var itemlist = db.Queryable().Where(a => a.S_CNTR_CODE == task.S_CNTR_CODE).ToList(); if (itemlist.Count > 0) { var url = Settings.MoboxSeverUrl + "inventory/AddChange"; //仓库量表升量 var req = new AddChangeModel { op_type = 5 }; //库区量表升量 var req2 = new AddChangeModel { op_type = 6 }; itemlist.ForEach(a => { LogHelper.Info($"填充数据"); LogHelper.Info($"添加仓库量表数据 仓库{task.S_END_WH} 物料编码{a.S_ITEM_CODE} 物料名称{a.S_ITEM_NAME} 数量{a.F_QTY}"); req.item_info.Add(new AddChangeModel.itemModel { wh_code = task.S_END_WH, item_code = a.S_ITEM_CODE, item_name = a.S_ITEM_NAME, qty = a.F_QTY }); LogHelper.Info($"添加库区量表数据 库区{task.S_END_AREA} 物料编码{a.S_ITEM_CODE} 物料名称{a.S_ITEM_NAME} 数量{a.F_QTY}"); req2.item_info.Add(new AddChangeModel.itemModel { wh_code = task.S_END_WH, area_code = task.S_END_AREA, item_code = a.S_ITEM_CODE, item_name = a.S_ITEM_NAME, qty = a.F_QTY }); }); var reqData = JsonConvert.SerializeObject(req); var AppKey = Settings.AppKey; var AppSecret = Settings.AppSecret; var ReqTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); //var ReqTime = GetTimeStamp(DateTime.Now.AddHours(-8), 1, 2); LogHelper.Info($"加密前 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime}"); var ReqVerify = GetMd5FromString(AppKey + AppSecret + ReqTime); LogHelper.Info($"加密后 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime} ReqVerify={ReqVerify} url={url} req={reqData}"); var res = new HttpHelper().WebPost(url, reqData, "application/json", AppKey, ReqTime, ReqVerify); if (!string.IsNullOrEmpty(res)) { LogHelper.Info($"mobox 仓库升量接口返回 {res}"); var moboxres = JsonConvert.DeserializeObject(res); if (moboxres.err_code != 0) { result = false; } } else { LogHelper.Info($"mobox 仓库升量接口返回为空"); result = false; } var reqData2 = JsonConvert.SerializeObject(req2); var ReqTime2 = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); //var ReqTime2 = GetTimeStamp(DateTime.Now.AddHours(-8), 1, 2); LogHelper.Info($"加密前 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime2}"); var ReqVerify2 = GetMd5FromString(AppKey + AppSecret + ReqTime2); LogHelper.Info($"加密后 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime2} ReqVerify={ReqVerify2} url={url} req={reqData2}"); var res2 = new HttpHelper().WebPost(url, reqData2, "application/json", AppKey, ReqTime2, ReqVerify2); if (!string.IsNullOrEmpty(res2)) { LogHelper.Info($"mobox 库区升量接口返回 {res2}"); var moboxres = JsonConvert.DeserializeObject(res); if (moboxres.err_code != 0) { result = false; } } else { LogHelper.Info($"mobox 库区升量接口返回为空"); result = false; } } else LogHelper.Info($"托盘{task.S_CNTR_CODE} 在容器货品明细中找不到数据"); } catch (Exception ex) { LogHelper.Error($"仓库升量异常 异常信息={ex.Message}", ex); result = false; } return result; } /// /// 根据任务降量 /// /// internal static bool DeleteChange(WCSTask wmstask) { var db = new SqlHelper().GetInstance(); var result = true; try { //var wmstask = db.Queryable().Where(a => a == task.S_OP_CODE).First(); if (wmstask != null) { var itemlist = db.Queryable().Where(a => a.S_CNTR_CODE == wmstask.S_CNTR_CODE).ToList(); if (itemlist.Count > 0) { var url = Settings.MoboxSeverUrl + "inventory/AddChange"; //仓库量表降量 var req = new AddChangeModel { op_type = 8 }; //库区量表降量 var req2 = new AddChangeModel { op_type = 9 }; itemlist.ForEach(a => { LogHelper.Info($"填充数据"); LogHelper.Info($"减仓库量表数据 仓库{wmstask.S_START_WH} 物料编码{a.S_ITEM_CODE} 物料名称{a.S_ITEM_NAME} 数量{a.F_QTY}"); req.item_info.Add(new AddChangeModel.itemModel { wh_code = wmstask.S_START_WH, item_code = a.S_ITEM_CODE, item_name = a.S_ITEM_NAME, qty = a.F_QTY }); LogHelper.Info($"减库区量表数据 库区{wmstask.S_START_AREA} 物料编码{a.S_ITEM_CODE} 物料名称{a.S_ITEM_NAME} 数量{a.F_QTY}"); req2.item_info.Add(new AddChangeModel.itemModel { wh_code = wmstask.S_START_WH, area_code = wmstask.S_START_AREA, item_code = a.S_ITEM_CODE, item_name = a.S_ITEM_NAME, qty = a.F_QTY }); }); var reqData = JsonConvert.SerializeObject(req); var AppKey = Settings.AppKey; var AppSecret = Settings.AppSecret; var ReqTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); //var ReqTime = GetTimeStamp(DateTime.Now.AddHours(-8), 1, 2); LogHelper.Info($"加密前 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime}"); var ReqVerify = GetMd5FromString(AppKey + AppSecret + ReqTime); LogHelper.Info($"加密后 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime} ReqVerify={ReqVerify} url={url} req={reqData}"); var res = new HttpHelper().WebPost(url, reqData, "application/json", AppKey, ReqTime, ReqVerify); if (!string.IsNullOrEmpty(res)) { LogHelper.Info($"mobox 仓库降量接口返回 {res}"); var moboxres = JsonConvert.DeserializeObject(res); if (moboxres.err_code != 0) { result = false; } } else { LogHelper.Info($"mobox 仓库降量接口返回为空"); result = false; } var reqData2 = JsonConvert.SerializeObject(req2); var ReqTime2 = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); //var ReqTime2 = GetTimeStamp(DateTime.Now.AddHours(-8), 1, 2); LogHelper.Info($"加密前 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime2}"); var ReqVerify2 = GetMd5FromString(AppKey + AppSecret + ReqTime2); LogHelper.Info($"加密后 AppKey={AppKey} AppSecret={AppSecret} ReqTime={ReqTime2} ReqVerify={ReqVerify2} url={url} req={reqData2}"); var res2 = new HttpHelper().WebPost(url, reqData2, "application/json", AppKey, ReqTime2, ReqVerify2); if (!string.IsNullOrEmpty(res2)) { LogHelper.Info($"mobox 库区降量接口返回 {res2}"); var moboxres = JsonConvert.DeserializeObject(res); if (moboxres.err_code != 0) { result = false; } } else { LogHelper.Info($"mobox 库区降量接口返回为空"); result = false; } } else LogHelper.Info($"托盘{wmstask.S_CNTR_CODE} 在容器货品明细中找不到数据"); } else LogHelper.Info($"未找到任务{wmstask.S_CODE} 对应的任务"); } catch (Exception ex) { LogHelper.Error($"仓库降量异常 异常信息={ex.Message}", ex); result = false; } return result; } #endregion } }