| | |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using Rhea.Common; |
| | | using System.Net; |
| | | using System.Linq; |
| | | using Newtonsoft.Json; |
| | | using Tiger.IBusiness; |
| | | using Microsoft.AspNetCore.Http; |
| | | using Tiger.Business.MES; |
| | | using Org.BouncyCastle.Ocsp; |
| | | using Tiger.Model.Entitys.MES.Position; |
| | | |
| | | namespace Tiger.Business |
| | |
| | | public partial class Biz |
| | | { |
| | | /// <summary> |
| | | /// 工单批次 |
| | | /// 生产中的工单批次 |
| | | /// </summary> |
| | | public partial class WorkBatch : IWorkBatch, IDisposable |
| | | { |
| | |
| | | } |
| | | #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<BIZ_MES_WO_SN> WoSNs { get; set; } |
| | |
| | | public List<MES_WO_NODE_POST> NodePosts { get; set; } |
| | | public List<MES_WO_NODE_DFTG> NodeDftgs { get; set; } |
| | | public List<BAS_DEFECT_GRP> DefectGroups { get; set; } |
| | | public List<BAS_DEFECT> Defects => DefectGroups.SelectMany(q => q.Defects).ToList(); |
| | | /// <summary> |
| | | /// 事务锁 |
| | | /// </summary> |
| | |
| | | /// <returns></returns> |
| | | public WorkBatch Init(string lineCode) |
| | | { |
| | | WO = Biz.Db.Queryable<BIZ_MES_WO>().Where(q => q.ORDER_NO == OrderNo).First(); |
| | | Batch = Biz.Db.Queryable<BIZ_MES_WO_BATCH>().Where(q => q.ORDER_NO == OrderNo && q.ACT_LINE == lineCode |
| | | && (q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Release.GetValue() || q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Working.GetValue())).First(); |
| | | LineCode = lineCode; |
| | | WO = Biz.Db.Queryable<BIZ_MES_WO>().Where(q => q.ORDER_NO == OrderNo).IncludesAllFirstLayer().First(); |
| | | Product = Biz.Db.Queryable<BAS_ITEM>().Where(q => q.ITEM_CODE == WO.ITEM_CODE && q.AUTH_ORG == WO.AUTH_ORG).First(); |
| | | Batch = Biz.Db.Queryable<BIZ_MES_WO_BATCH>().Where(q => q.ORDER_NO == OrderNo && q.ACT_LINE == LineCode && |
| | | (q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Release.GetValue() || q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Working.GetValue())).First(); |
| | | Customer = Biz.Db.Queryable<BAS_CUSTOMER>().Where(q => q.CUST_CODE == WO.CUST_CODE).First(); |
| | | WoSNs = Biz.Db.Queryable<BIZ_MES_WO_SN>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | Edges = Biz.Db.Queryable<MES_WO_EDGE>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | Nodes = Biz.Db.Queryable<MES_WO_NODE>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | Nodes = Biz.Db.Queryable<MES_WO_NODE>().Where(q => q.WORK_ORDER == OrderNo).IncludesAllFirstLayer().ToList(); |
| | | NodeSets = Biz.Db.Queryable<MES_WO_OPER>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | NodeActs = Biz.Db.Queryable<MES_WO_NODE_ACT>().Where(q => q.WORK_ORDER == OrderNo).IncludesAllFirstLayer().ToList(); |
| | | ActionSets = Biz.Db.Queryable<MES_WO_ACTION>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | NodePosts = Biz.Db.Queryable<MES_WO_NODE_POST>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | NodeDftgs = Biz.Db.Queryable<MES_WO_NODE_DFTG>().Where(q => q.WORK_ORDER == OrderNo).ToList(); |
| | | DefectGroups = Biz.Db.Queryable<BAS_DEFECT_GRP>().IncludesAllFirstLayer().ToList(); |
| | | |
| | | //工序节点排序 |
| | | var first = Nodes.First(q => q.IS_FIRST_NODE == "Y"); |
| | | first.Sequence = 1; |
| | | NodeSorting(first); |
| | | |
| | | return this; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加节点的下一个行为到工步列表 |
| | | /// </summary> |
| | | /// <param name="parent"></param> |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查工单在当前工序节点是否可以生产 |
| | | /// </summary> |
| | | /// <param name="curNode"></param> |
| | | /// <returns></returns> |
| | | public ApiAction<SubmitOutput> CheckCanProduce(MES_WO_NODE curNode) |
| | | { |
| | | var action = new ApiAction<SubmitOutput>(new SubmitOutput(), true); |
| | | |
| | | WO = Biz.Db.Queryable<BIZ_MES_WO>().Where(q => q.ORDER_NO == OrderNo).IncludesAllFirstLayer().First(); |
| | | Batch = Biz.Db.Queryable<BIZ_MES_WO_BATCH>().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<BIZ_MES_WO_BATCH.STATUSs>().GetName()}]不是允许生产的状态,请扫描允许生产的产品条码"); |
| | | action.LocaleMsg = new("MES.WorkBatch.WoBatchStatusCanNotWork", Batch.BATCH_NO, Batch.STATUS.GetEnum<BIZ_MES_WO_BATCH.STATUSs>().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<BIZ_MES_WO.STATUSs>().GetName()}]不是允许生产的状态,请扫描允许生产的产品条码"); |
| | | action.LocaleMsg = new("MES.WorkBatch.WoStatusCanNotWork", WO.ORDER_NO, Batch.STATUS.GetEnum<BIZ_MES_WO.STATUSs>().GetName()); |
| | | } |
| | | //工单批次投入数量减去报废数量如果大于等于计划数量,则不允许生产 |
| | | //if (curNode.IS_INPUT == "Y" && Batch.INPUT_QTY - Batch.SCRAP_QTY >= Batch.PLAN_QTY) |
| | | if (curNode.IS_INPUT == "Y" && WoSNs.Count(q => q.BATCH_NO == Batch.BATCH_NO) - 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, WoSNs.Count(q => q.BATCH_NO == Batch.BATCH_NO), Batch.SCRAP_QTY, Batch.PLAN_QTY); |
| | | } |
| | | |
| | | return action; |
| | | } |
| | | |
| | | /// <summary> |
| | |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据传入的条码过站信息和下一站的目标工序,判断条码是否能进入下一站 |
| | | /// </summary> |
| | | /// <param name="input">本次提交的数据</param> |
| | | /// <param name="wipSN">当前的条码过站记录,需要导航查询生产过程记录信息</param> |
| | | /// <param name="nextNode">要进入的目标工序</param> |
| | | /// <returns></returns> |
| | | public ApiAction<SubmitOutput> CanGotoNext(SubmitInput input, MES_WIP_DATA wipSN, MES_WO_NODE nextNode) |
| | | { |
| | | var action = new ApiAction<SubmitOutput>(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); |
| | | var nextSetting = NodeSets.FirstOrDefault(q => q.NODE_ID == nextNode.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()) |
| | | && nextSetting.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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加节点的下一个可执行节点 |
| | | /// </summary> |
| | | /// <param name="parent"></param> |
| | | /// <param name="wipSN">当前的条码过站记录,需要导航查询生产过程记录信息</param> |
| | | private List<MES_WO_NODE> GetNextNodes(MES_WO_NODE parent, MES_WIP_DATA wipSN) |
| | | { |
| | | var result = new List<MES_WO_NODE>(); |
| | | 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)) || parent.IS_FIRST_NODE == "Y") |
| | | { |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据传入的条码返回下一站可进入进入的目标工序 |
| | | /// </summary> |
| | | /// <param name="wipSN">当前的条码过站记录,需要导航查询生产过程记录和生产不良记录信息</param> |
| | | /// <returns></returns> |
| | | public List<MES_WO_NODE> GetNextNodes(MES_WIP_DATA wipSN) |
| | | { |
| | | var result = new List<MES_WO_NODE>(); |
| | | |
| | | //条码在本工单第一次过站 |
| | | if (wipSN.NODE_ID.IsNullOrEmpty()) |
| | | { |
| | | result.AddRange(Nodes.Where(q => q.IS_FIRST_NODE == "Y")); |
| | | } |
| | | //条码已有过站记录 |
| | | else |
| | | { |
| | | var curNode = Nodes.First(q => q.ID == wipSN.NODE_ID); |
| | | //条码在维修工序,返回可回流工序 |
| | | 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(); |
| | | result.AddRange(reflowNodes.Where(q => wipSN.REFLOW_NODE.IsNullOrEmpty() || wipSN.REFLOW_NODE == q.NODE_NAME)); |
| | | } |
| | | else |
| | | { |
| | | //往下查找条码当前工序的下一个可执行工序 |
| | | var nextNodes = GetNextNodes(curNode, wipSN); |
| | | //如果当前工序没有必须执行的后续工序,则尝试在前置工序查找还有没有后续工序没完成的工序,有则加入 |
| | | if (!nextNodes.Any(q => NodeSets.Any(s => s.NODE_ID == q.ID && s.IS_ACTIVE == "Y" && s.CAN_SKIP != "Y"))) |
| | | { |
| | | //在前置工序查找还有没有后续工序没完成的前置工序 |
| | | 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); |
| | | } |
| | | } |
| | | result.AddRange(nextNodes); |
| | | //如果当前条码是不良则只返回允许不良进站的工序和工序连接的维修工序 |
| | | if (wipSN.Defects.Any(q => q.WORK_ORDER == WO.ORDER_NO && q.STATUS < MES_WIP_DFT.STATUSs.Resolved.GetValue())) |
| | | { |
| | | result.RemoveAll(q => NodeSets.Any(s => s.NODE_ID == q.ID && s.ALLOW_DFT_IN != "Y")); |
| | | //加入工序连接的维修工序 |
| | | var repairNodes = Nodes.Where(q => Edges.Any(e => e.SRC_NODE == curNode.ID && e.TGT_NODE == q.ID) && q.Operation.OPER_TYPE == MES_OPERATION.OPER_TYPEs.Repair.GetValue()); |
| | | result.AddRange(repairNodes); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据岗位编码返回工序不良代码 |
| | | /// </summary> |
| | | /// <param name="postCode"></param> |