using Tiger.Model; using SqlSugar; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using Rhea.Common; using System.Net; using System.Linq; using Tiger.IBusiness; using Tiger.Business.MES; using Tiger.Model.Entitys.MES.Position; namespace Tiger.Business { public partial class Biz { /// /// 生产中的工单批次 /// public partial class WorkBatch : IWorkBatch, IDisposable { public WorkBatch(string order) { OrderNo = order; } #region Propertys & Variables public string OrderNo { get; set; } public string LineCode { get; set; } public BIZ_MES_WO WO { get; set; } public BAS_ITEM Product { get; set; } public BIZ_MES_WO_BATCH Batch { get; set; } public BAS_CUSTOMER Customer { get; set; } public List WoSNs { get; set; } public List Edges { get; set; } public List Nodes { get; set; } public List NodeSets { get; set; } public List NodeActs { get; set; } public List ActionSets { get; set; } public List NodePosts { get; set; } public List NodeDftgs { get; set; } public List DefectGroups { get; set; } public List Defects => DefectGroups.SelectMany(q => q.Defects).ToList(); /// /// 事务锁 /// public object TransLock { get; } #endregion #region Functions /// /// 初始化工单资料 /// /// public WorkBatch Init(string lineCode) { LineCode = lineCode; WO = Biz.Db.Queryable().Where(q => q.ORDER_NO == OrderNo).IncludesAllFirstLayer().First(); Product = Biz.Db.Queryable().Where(q => q.ITEM_CODE == WO.ITEM_CODE && q.AUTH_ORG == WO.AUTH_ORG).First(); Batch = Biz.Db.Queryable().Where(q => q.ORDER_NO == OrderNo && q.ACT_LINE == LineCode).First(); Customer = Biz.Db.Queryable().Where(q => q.CUST_CODE == WO.CUST_CODE).First(); WoSNs = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); Edges = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); Nodes = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).IncludesAllFirstLayer().ToList(); NodeSets = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); NodeActs = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).IncludesAllFirstLayer().ToList(); ActionSets = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); NodePosts = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); NodeDftgs = Biz.Db.Queryable().Where(q => q.WORK_ORDER == OrderNo).ToList(); DefectGroups = Biz.Db.Queryable().IncludesAllFirstLayer().ToList(); //工序节点排序 var first = Nodes.First(q => q.IS_FIRST_NODE == "Y"); first.Sequence = 1; NodeSorting(first); return this; } /// /// 添加节点的下一个行为到工步列表 /// /// private void NodeSorting(MES_WO_NODE parent) { var edges = Edges.Where(q => q.SRC_NODE == parent.ID && Nodes.Any(a => a.ID == q.TGT_NODE)).ToList(); foreach (var edge in edges) { var next = Nodes.First(q => q.ID == edge.TGT_NODE); //排除维修工序 if (next.Operation.OPER_TYPE != MES_OPERATION.OPER_TYPEs.Repair.GetValue()) { next.Sequence = next.Sequence > parent.Sequence ? next.Sequence : (parent.Sequence + 1); //如果父节点启用且不可跳站,则添加为下一个工序的前置工序 var setting = NodeSets.FirstOrDefault(q => q.NODE_ID == parent.ID); if (!setting.IsNullOrEmpty() && setting.IS_ACTIVE == "Y" && setting.CAN_SKIP != "Y") { next.PrepNodeIDs.Add(parent.ID); } //继承父结点的前置工序 next.PrepNodeIDs.AddRange(parent.PrepNodeIDs); next.PrepNodeIDs = next.PrepNodeIDs.Distinct().ToList(); NodeSorting(next); } } } /// /// 检查工单在当前工序节点是否可以生产 /// /// /// public ApiAction CheckCanProduce(MES_WO_NODE curNode) { var action = new ApiAction(new SubmitOutput(), true); WO = Biz.Db.Queryable().Where(q => q.ORDER_NO == OrderNo).IncludesAllFirstLayer().First(); Batch = Biz.Db.Queryable().Where(q => q.ORDER_NO == OrderNo && q.ACT_LINE == LineCode).First(); //工单批次状态不是已下发或者生产中,则不允许生产 if (Batch.STATUS != BIZ_MES_WO_BATCH.STATUSs.Release.GetValue() && Batch.STATUS != BIZ_MES_WO_BATCH.STATUSs.Working.GetValue()) { action.IsSuccessed = false; action.Data.SetValue(this, null); //action.LocaleMsg = new($"工单批次[{Batch.BATCH_NO}]状态[{Batch.STATUS.GetEnum().GetName()}]不是允许生产的状态,请扫描允许生产的产品条码"); action.LocaleMsg = new("MES.WorkBatch.WoBatchStatusCanNotWork", Batch.BATCH_NO, Batch.STATUS.GetEnum().GetName()); } //工单状态不是已下发或者生产中,则不允许生产 if (WO.STATUS != BIZ_MES_WO.STATUSs.Release.GetValue() && WO.STATUS != BIZ_MES_WO.STATUSs.Working.GetValue()) { action.IsSuccessed = false; action.Data.SetValue(this, null); //action.LocaleMsg = new($"工单[{WO.ORDER_NO}]状态[{Batch.STATUS.GetEnum().GetName()}]不是允许生产的状态,请扫描允许生产的产品条码"); action.LocaleMsg = new("MES.WorkBatch.WoStatusCanNotWork", WO.ORDER_NO, Batch.STATUS.GetEnum().GetName()); } //工单批次投入数量减去报废数量如果大于等于计划数量,则不允许生产 if (curNode.IS_INPUT == "Y" && Batch.INPUT_QTY - Batch.SCRAP_QTY >= Batch.PLAN_QTY) { action.IsSuccessed = false; action.Data.SetValue(this, null); action.LocaleMsg = new($"工单批次[{0}]已投入 {1},其中报废 {2},以满足计划数量[{3}],无需继续投入"); action.LocaleMsg = new("MES.WorkBatch.WoInputEnough", Batch.BATCH_NO, Batch.INPUT_QTY, Batch.SCRAP_QTY, Batch.PLAN_QTY); } return action; } /// /// 工单开工 /// /// /// public bool StartWorking(string user) { if (Batch.STATUS == BIZ_MES_WO_BATCH.STATUSs.Release.GetValue() || Batch.STATUS == BIZ_MES_WO_BATCH.STATUSs.Paused.GetValue()) { WO.STATUS = BIZ_MES_WO.STATUSs.Working.GetValue(); WO.ACT_START_TIME = WO.ACT_START_TIME < new DateTime(2000, 1, 1) ? DateTime.Now : WO.ACT_START_TIME; Batch.STATUS = BIZ_MES_WO_BATCH.STATUSs.Working.GetValue(); Batch.ACT_START_TIME = WO.ACT_START_TIME < new DateTime(2000, 1, 1) ? DateTime.Now : WO.ACT_START_TIME; //保存数据库 var db = Biz.Db; var dbTran = db.UseTran(() => { db.Updateable(WO, user).UpdateColumns(q => new { q.UPDATE_TIME, q.UPDATE_USER, q.STATUS, q.ACT_START_TIME }).ExecuteCommand(); db.Updateable(Batch, user).UpdateColumns(q => new { q.UPDATE_TIME, q.UPDATE_USER, q.STATUS, q.ACT_START_TIME }).ExecuteCommand(); }); if (!dbTran.IsSuccess) { //throw dbTran.ErrorException; return false; } } return true; } /// /// 工单暂停 /// /// /// public bool PausedWorking(string user) { if (Batch.STATUS == BIZ_MES_WO_BATCH.STATUSs.Working.GetValue()) { WO.STATUS = BIZ_MES_WO.STATUSs.Paused.GetValue(); Batch.STATUS = BIZ_MES_WO_BATCH.STATUSs.Paused.GetValue(); //保存数据库 var db = Biz.Db; var dbTran = db.UseTran(() => { db.Updateable(WO, user).UpdateColumns(q => new { q.UPDATE_TIME, q.UPDATE_USER, q.STATUS }).ExecuteCommand(); db.Updateable(Batch, user).UpdateColumns(q => new { q.UPDATE_TIME, q.UPDATE_USER, q.STATUS }).ExecuteCommand(); }); if (!dbTran.IsSuccess) { //throw dbTran.ErrorException; return false; } } return true; } /// /// 根据岗位编码判断是不是首站 /// /// /// public bool IsFirstNode(string postCode) { return Nodes.Any(q => q.IS_FIRST_NODE == "Y" && NodePosts.Any(p => p.NODE_ID == q.ID && p.POST_CODE == postCode)); } /// /// 根据岗位编码返回工序节点 /// /// /// public MES_WO_NODE GetNode(string postCode) { return Nodes.FirstOrDefault(q => NodePosts.Any(p => p.NODE_ID == q.ID && p.POST_CODE == postCode)); } /// /// 根据传入的条码过站信息和下一站的目标工序,判断条码是否能进入下一站 /// /// 本次提交的数据 /// 当前的条码过站记录 /// 要进入的目标工序 /// public ApiAction CanGotoNext(SubmitInput input, MES_WIP_DATA wipSN, MES_WO_NODE nextNode) { var action = new ApiAction(new SubmitOutput()); //条码在本工单第一次过站 if (wipSN.NODE_ID.IsNullOrEmpty()) { if (nextNode.IS_FIRST_NODE == "Y") { action.IsSuccessed = true; return action; } else { action.IsSuccessed = false; action.Data.SetValue(this, null); var nextList = Nodes.Where(q => q.IS_FIRST_NODE == "Y"); action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (q.CAN_SKIP == "Y" ? $"({T(L("MES.WorkBatch.Optional"), input.Locale)})" : "")))); return action; } } //条码已有过站记录 else { var curNode = Nodes.First(q => q.ID == wipSN.NODE_ID); var curSetting = NodeSets.FirstOrDefault(q => q.NODE_ID == curNode.ID); //进入维修:如果目标工序是维修工序则判断当前条码在本工单有未维修的不良记录且当前工序节点有连线到维修工序,则允许进入维修工序 if (nextNode.Operation.OPER_TYPE == MES_OPERATION.OPER_TYPEs.Repair.GetValue() && wipSN.Defects.Any(q => q.WORK_ORDER == WO.ORDER_NO && q.STATUS < MES_WIP_DFT.STATUSs.Resolved.GetValue()) && Edges.Any(q => q.SRC_NODE == curNode.ID && q.TGT_NODE == nextNode.ID)) { action.IsSuccessed = true; return action; } //维修回流:如果条码的当前工序是维修工序,则认为是维修回流 else if (curNode.Operation.OPER_TYPE == MES_OPERATION.OPER_TYPEs.Repair.GetValue()) { //查找所有可以回流的工序 var reflowNodes = Nodes.Where(q => Edges.Any(e => e.SRC_NODE == curNode.ID && e.TGT_NODE == q.ID) && NodeSets.Any(s => s.NODE_ID == q.ID && s.IS_ACTIVE == "Y")).ToList(); //可回流的工序包含目标工序且条码在维修站选择的回流工序为空或者等于目标工序,则允许进站 if (reflowNodes.Any(q => q.ID == nextNode.ID) && (wipSN.REFLOW_NODE.IsNullOrEmpty() || wipSN.REFLOW_NODE == nextNode.NODE_NAME)) { action.IsSuccessed = true; return action; } else { action.IsSuccessed = false; action.Data.SetValue(this, null); var nextList = reflowNodes.Where(q => wipSN.REFLOW_NODE.IsNullOrEmpty() || wipSN.REFLOW_NODE == q.NODE_NAME); action.LocaleMsg = new("MES.WorkBatch.ReflowToNodeException", nextNode.NODE_NAME, input.SN, curNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (q.CAN_SKIP == "Y" ? $"({T(L("MES.WorkBatch.Optional"), input.Locale)})" : "")))); return action; } } //不良品入站:如果产品有不良记录且目标工序不是维修工序且不允许不良品入站,则报错 else if (wipSN.Defects.Any(q => q.WORK_ORDER == WO.ORDER_NO && q.STATUS < MES_WIP_DFT.STATUSs.Resolved.GetValue()) && curSetting.ALLOW_DFT_IN != "Y" && nextNode.Operation.OPER_TYPE != MES_OPERATION.OPER_TYPEs.Repair.GetValue()) { action.IsSuccessed = false; action.Data.SetValue(this, null); action.LocaleMsg = new("MES.WorkBatch.PleaseGotoRepair", curNode.NODE_NAME, input.SN); return action; } //正常工序过站 else { //添加条码当前工序的下一个可执行工序 var nextNodes = GetNextNodes(curNode, wipSN); //如果下一个可执行工序包含目标工序则允许进站 if (nextNodes.Any(q => q.ID == nextNode.ID)) { action.IsSuccessed = true; return action; } //如果当前工序有必须执行的后续工序,则报错 if (nextNodes.Any(q => NodeSets.Any(s => s.NODE_ID == q.ID && s.IS_ACTIVE == "Y" && s.CAN_SKIP != "Y"))) { action.IsSuccessed = false; action.Data.SetValue(this, null); var nextList = nextNodes.Where(q => NodeSets.Any(s => s.NODE_ID == q.ID && s.IS_ACTIVE == "Y" && s.CAN_SKIP != "Y")); action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (q.CAN_SKIP == "Y" ? $"({T(L("MES.WorkBatch.Optional"), input.Locale)})" : "")))); return action; } //如果当前工序没有必须执行的后续工序,则在前置工序查找还有没有后续工序没完成的工序,有则尝试执行 else { //在前置工序查找还有没有后续工序没完成的前置工序 var prepIDs = curNode.PrepNodeIDs.Where(id => Edges.Any(e => e.SRC_NODE == id && !wipSN.History.Any(h => h.WORK_ORDER == WO.ORDER_NO && h.NODE_ID == e.TGT_NODE && h.IsFinished)) ).ToList(); foreach (var prepID in prepIDs) { //如果连线的目标工序的前置工序都已完成,则把连线的目标工序添加到可执行工序列表 var prep = Nodes.First(q => q.ID == prepID); var next = GetNextNodes(prep, wipSN); nextNodes.AddRange(next); } //如果下一个可执行工序包含目标工序则允许进站 if (nextNodes.Any(q => q.ID == nextNode.ID)) { action.IsSuccessed = true; return action; } else { action.IsSuccessed = false; action.Data.SetValue(this, null); var nextList = nextNodes.Where(q => NodeSets.Any(s => s.NODE_ID == q.ID && s.IS_ACTIVE == "Y")); action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (q.CAN_SKIP == "Y" ? $"({T(L("MES.WorkBatch.Optional"), input.Locale)})" : "")))); return action; } } } } return action; } /// /// 添加节点的下一个可执行节点 /// /// /// private List GetNextNodes(MES_WO_NODE parent, MES_WIP_DATA wipSN) { var result = new List(); var nextNodes = Nodes.Where(q => q.Operation.OPER_TYPE != MES_OPERATION.OPER_TYPEs.Repair.GetValue() && Edges.Any(e => e.SRC_NODE == parent.ID && e.TGT_NODE == q.ID) && !wipSN.History.Any(h => h.WORK_ORDER == WO.ORDER_NO && h.NODE_ID == q.ID && h.IsFinished) ).ToList(); //尝试将当前工序的后续工序添加到可以执行的工序列表 foreach (var next in nextNodes) { //查找有没有前置工序找不到已良品过站的历史记录,若有则不允许继续执行 if (!next.PrepNodeIDs.Any(id => !wipSN.History.Any(h => h.WORK_ORDER == WO.ORDER_NO && h.NODE_ID == id && h.IsFinished))) { var setting = NodeSets.FirstOrDefault(q => q.NODE_ID == next.ID); //后续工序是启用的,则添加 if (!result.Any(q => q.ID == next.ID) && setting.IS_ACTIVE == "Y") { result.Add(next); } //后续工序是不启用或者可跳站,则继续添加其后续工序 if (!setting.IsNullOrEmpty() && (setting.IS_ACTIVE != "Y" || setting.CAN_SKIP == "Y")) { result.AddRange(GetNextNodes(next, wipSN)); } } } return result; } /// /// 根据岗位编码返回工序不良代码 /// /// /// public List GetNodeDefects(string postCode) { var result = new List(); var groups = DefectGroups.Where((b, s) => NodeDftgs.Any(d => d.NODE_ID == GetNode(postCode).ID && d.DFTG_CODE == b.DFTG_CODE)).ToList(); foreach (var group in groups) { result.AddRange(group.Defects.Select(q => new DefectOutput { DFTG_CODE = q.DFTG_CODE, DFTG_NAME = group.DFTG_NAME, DFT_CODE = q.DFT_CODE, DFT_NAME = q.DFT_NAME, DFT_LEVEL = q.DFT_LEVEL, })); } return result; } #endregion /// /// 关闭工单 /// /// public bool Close() { Dispose(); return true; } public virtual void Dispose() { WoContext.WoBatchDic.Remove(OrderNo); } } } }