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.Linq;
|
using Tiger.IBusiness;
|
using Tiger.Model.Entitys.MES.Position;
|
using Tiger.Business.MES;
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
|
using Org.BouncyCastle.Ocsp;
|
using System.IO;
|
|
namespace Tiger.Business
|
{
|
/// <summary>
|
/// 生产中的工单批次
|
/// </summary>
|
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<BIZ_MES_WO_SN> WoSNs { get; set; }
|
public List<MES_WO_EDGE> Edges { get; set; }
|
public List<MES_WO_NODE> Nodes { get; set; }
|
public List<MES_WO_OPER> NodeSets { get; set; }
|
public List<MES_WO_NODE_ACT> NodeActs { get; set; }
|
public List<MES_WO_ACTION> ActionSets { 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>
|
public object TransLock { get; }
|
#endregion
|
|
#region Functions
|
/// <summary>
|
/// 初始化工单批次资料
|
/// </summary>
|
/// <returns></returns>
|
public WorkBatch Init(string lineCode)
|
{
|
LineCode = lineCode;
|
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 &&
|
(q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Release.GetValue() || q.STATUS == BIZ_MES_WO_BATCH.STATUSs.Working.GetValue())).First();
|
GetBatchInfo();
|
return this;
|
}
|
|
/// <summary>
|
/// 更新工单批次资料
|
/// </summary>
|
/// <param name="updateAll"></param>
|
public void Update(bool updateAll = false)
|
{
|
var strat = DateTime.Now;
|
var wo = Biz.Db.Queryable<BIZ_MES_WO>().Where(q => q.ORDER_NO == OrderNo).IncludesAllFirstLayer().First();
|
var batch = Biz.Db.Queryable<BIZ_MES_WO_BATCH>().Where(q => q.BATCH_NO == Batch.BATCH_NO).First();
|
if (WoContext.WoBatchDic.ContainsKey(batch.BATCH_NO))
|
{
|
if (wo.STATUS < BIZ_MES_WO.STATUSs.Release.GetValue() || batch.STATUS > BIZ_MES_WO_BATCH.STATUSs.Working.GetValue())
|
{
|
WoContext.WoBatchDic.Remove(Batch.BATCH_NO);
|
}
|
else if(updateAll || wo.CHANGE_TIME > WO.CHANGE_TIME || batch.CHANGE_TIME > Batch.CHANGE_TIME)
|
{
|
GetBatchInfo();
|
}
|
}
|
else
|
{
|
GetBatchInfo();
|
WoContext.WoBatchDic.Add(batch.BATCH_NO, this);
|
}
|
WO = wo;
|
Batch = batch;
|
ConsoleExt.WriteLine($"{DateTime.Now:HH:mm:ss} ##### : {(DateTime.Now - strat).TotalSeconds}s", ConsoleColor.Yellow);
|
}
|
|
private void GetBatchInfo()
|
{
|
ConsoleExt.WriteLine($"{DateTime.Now:HH:mm:ss} !!!!! ", ConsoleColor.Cyan);
|
Product = Biz.Db.Queryable<BAS_ITEM>().Where(q => q.ITEM_CODE == WO.ITEM_CODE && q.AUTH_ORG == WO.AUTH_ORG).IncludesAllFirstLayer().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).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);
|
}
|
|
/// <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, MES_WO_OPER curNodeSetting)
|
{
|
var action = new ApiAction<SubmitOutput>(new SubmitOutput(), true);
|
|
Update();
|
|
//工单批次状态不是已下发或者生产中,则不允许生产
|
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.GetEnumDesc<BIZ_MES_WO_BATCH.STATUSs>());
|
}
|
//工单状态不是已下发或者生产中,则不允许生产
|
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.GetEnumDesc<BIZ_MES_WO.STATUSs>());
|
}
|
//工单批次投入数量减去报废数量如果大于等于计划数量,则不允许生产
|
var batchInput = WoSNs.Where(q => q.BATCH_NO == Batch.BATCH_NO && BIZ_MES_WO_SN.STATUSs.NotInput.GetValue() < q.STATUS && q.STATUS <= BIZ_MES_WO_SN.STATUSs.Finished.GetValue()).DistinctBy(q => q.WIP_ID).Sum(q => q.QTY);
|
if (curNode.IS_FIRST_NODE == "Y" && batchInput >= Batch.PLAN_QTY)
|
{
|
action.IsSuccessed = false;
|
action.Data.SetValue(this, null);
|
//action.LocaleMsg = new($"工单批次[{0}]已投入 {1},其中报废 {2},以满足计划数量[{3}],无需继续投入");
|
action.LocaleMsg = new("MES.WorkBatch.BatchInputEnough", Batch.BATCH_NO, batchInput, WoSNs.Where(q => q.BATCH_NO == Batch.BATCH_NO && q.STATUS > BIZ_MES_WO_SN.STATUSs.Finished.GetValue()).DistinctBy(q => q.WIP_ID).Sum(q => q.QTY), Batch.PLAN_QTY);
|
}
|
//工单投入数量减去报废数量如果大于等于计划数量,则不允许生产
|
var woInput = WoSNs.Where(q => q.WORK_ORDER == WO.ORDER_NO && BIZ_MES_WO_SN.STATUSs.NotInput.GetValue() < q.STATUS && q.STATUS <= BIZ_MES_WO_SN.STATUSs.Finished.GetValue()).DistinctBy(q => q.WIP_ID).Sum(q => q.QTY);
|
if (curNode.IS_FIRST_NODE == "Y" && woInput >= WO.PLAN_QTY)
|
{
|
action.IsSuccessed = false;
|
action.Data.SetValue(this, null);
|
//action.LocaleMsg = new($"工单[{0}]已投入 {1},其中报废 {2},以满足计划数量[{3}],无需继续投入");
|
action.LocaleMsg = new("MES.WorkBatch.WoInputEnough", WO.ORDER_NO, woInput, WoSNs.Where(q => q.WORK_ORDER == WO.ORDER_NO && q.STATUS > BIZ_MES_WO_SN.STATUSs.Finished.GetValue()).DistinctBy(q => q.WIP_ID).Sum(q => q.QTY), WO.PLAN_QTY);
|
}
|
|
return action;
|
}
|
|
/// <summary>
|
/// 工单开工
|
/// </summary>
|
/// <param name="user"></param>
|
/// <returns></returns>
|
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.STATUS, q.ACT_START_TIME }).ExecuteCommand();
|
db.Updateable(Batch, user).UpdateColumns(q => new { q.STATUS, q.ACT_START_TIME }).ExecuteCommand();
|
});
|
if (!dbTran.IsSuccess)
|
{
|
//throw dbTran.ErrorException;
|
return false;
|
}
|
}
|
return true;
|
}
|
|
/// <summary>
|
/// 工单暂停
|
/// </summary>
|
/// <param name="user"></param>
|
/// <returns></returns>
|
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;
|
}
|
|
/// <summary>
|
/// 检查工单是否完工,已完工则修改相应状态并记录到数据库
|
/// </summary>
|
/// <param name="user"></param>
|
/// <returns></returns>
|
public async Task<bool> CheckIsComplete(string user)
|
{
|
var woSNs = Biz.Db.Queryable<BIZ_MES_WO_SN>().Where(q => q.WORK_ORDER == OrderNo).ToList();
|
|
//判断当前工单批次是否已完工
|
if (!woSNs.Any(q => q.BATCH_NO == Batch.BATCH_NO && q.STATUS < BIZ_MES_WO_SN.STATUSs.Finished.GetValue()) &&
|
woSNs.Count(q => q.BATCH_NO == Batch.BATCH_NO && q.STATUS == BIZ_MES_WO_SN.STATUSs.Finished.GetValue()) == Batch.PLAN_QTY)
|
{
|
Batch.STATUS = BIZ_MES_WO_BATCH.STATUSs.Closed.GetValue();
|
Batch.ACT_END_TIME = DateTime.Now;
|
}
|
|
//判断当前工单是否已完工
|
if (!woSNs.Any(q => q.STATUS < BIZ_MES_WO_SN.STATUSs.Finished.GetValue()) &&
|
woSNs.Count(q => q.STATUS == BIZ_MES_WO_SN.STATUSs.Finished.GetValue()) == WO.PLAN_QTY)
|
{
|
WO.STATUS = BIZ_MES_WO.STATUSs.Closed.GetValue();
|
WO.ACT_END_TIME = DateTime.Now;
|
}
|
|
//保存数据库
|
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_END_TIME }).ExecuteCommand();
|
db.Updateable(Batch, user).UpdateColumns(q => new { q.UPDATE_TIME, q.UPDATE_USER, q.STATUS, q.ACT_END_TIME }).ExecuteCommand();
|
});
|
if (!dbTran.IsSuccess)
|
{
|
//throw dbTran.ErrorException;
|
Logger.Default.Fatal(dbTran.ErrorException, $"检查工单批次[{Batch.BATCH_NO}]是否完工异常");
|
return false;
|
}
|
//删除缓存
|
if (Batch.STATUS == BIZ_MES_WO_BATCH.STATUSs.Closed.GetValue())
|
{
|
WoContext.WoBatchDic.Remove(Batch.BATCH_NO);
|
}
|
if (WO.STATUS == BIZ_MES_WO.STATUSs.Closed.GetValue())
|
{
|
var list = WoContext.WoBatchDic.Where(q => q.Value.WO.ORDER_NO == OrderNo).Select(q => q.Key);
|
foreach (var item in list)
|
{
|
WoContext.WoBatchDic.Remove(item);
|
}
|
}
|
|
return true;
|
}
|
|
/// <summary>
|
/// 根据岗位编码判断是不是首站
|
/// </summary>
|
/// <param name="postCode"></param>
|
/// <returns></returns>
|
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));
|
}
|
|
/// <summary>
|
/// 根据岗位编码返回工序节点
|
/// </summary>
|
/// <param name="postCode"></param>
|
/// <returns></returns>
|
public MES_WO_NODE GetNode(string postCode)
|
{
|
return Nodes.FirstOrDefault(q => NodePosts.Any(p => p.NODE_ID == q.ID && p.POST_CODE == postCode));
|
}
|
|
/// <summary>
|
/// 根据工序节点返回工序节点设置
|
/// </summary>
|
/// <param name="postCode"></param>
|
/// <returns></returns>
|
public MES_WO_OPER GetNodeSetting(MES_WO_NODE node)
|
{
|
return NodeSets.FirstOrDefault(q => q.NODE_ID == node?.ID);
|
}
|
|
/// <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($"条码[{0}]在工序[{1}]过站扫描错误,请先通过以下工序:{2}");
|
action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (GetNodeSetting(q)?.CAN_SKIP == "Y" ? $"({Biz.T(Biz.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($"工序[{0}]不是条码[{1}]在工序[{2}]维修后可回流的工序,请选择回流到以下工序:{3}");
|
action.LocaleMsg = new("MES.WorkBatch.ReflowToNodeException", nextNode.NODE_NAME, input.SN, curNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (GetNodeSetting(q)?.CAN_SKIP == "Y" ? $"({Biz.T(Biz.L("MES.WorkBatch.Optional"), input.Locale)})" : ""))));
|
return action;
|
}
|
}
|
//正常工序过站
|
else
|
{
|
//不良品入站:如果产品有不良记录且目标工序不是维修工序且不允许不良品入站,则报错
|
if (wipSN.Defects.Any(q => 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($"工序[{0}]不允许不良品入站,条码[{1}]存在不良记录,请先按流程指引操作或者进入维修");
|
action.LocaleMsg = new("MES.WorkBatch.PleaseGotoRepair", curNode.NODE_NAME, input.SN);
|
return action;
|
}
|
|
//添加条码当前工序的下一个可执行工序
|
var nextNodes = GetNextNodes(curNode, wipSN);
|
//如果下一个可执行工序包含目标工序则允许进站
|
if (nextNodes.Any(q => q.ID == nextNode.ID))
|
{
|
action.IsSuccessed = true;
|
return action;
|
}
|
|
//如果当前工序有必须执行的后续工序,则报错
|
if (nextNodes.Any(q => q.OPER_CODE != "EndNode" && 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"));
|
//action.LocaleMsg = new($"条码[{0}]在工序[{1}]过站扫描错误,请先通过以下工序:{2}");
|
action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (GetNodeSetting(q)?.CAN_SKIP == "Y" ? $"({Biz.T(Biz.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.UNBIND_FLAG != "Y" && 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($"条码[{0}]在工序[{1}]过站扫描错误,请先通过以下工序:{2}");
|
action.LocaleMsg = new("MES.WorkBatch.GotoNextNodeException", input.SN, nextNode.NODE_NAME, string.Join(", ", nextList.Select(q => q.NODE_NAME + (GetNodeSetting(q)?.CAN_SKIP == "Y" ? $"({Biz.T(Biz.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.UNBIND_FLAG != "Y" && 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.UNBIND_FLAG != "Y" && 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.UNBIND_FLAG != "Y" && 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>
|
/// <returns></returns>
|
public List<DefectOutput> GetNodeDefects(string postCode)
|
{
|
var result = new List<DefectOutput>();
|
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;
|
}
|
|
/// <summary>
|
/// 从工单中移除正在生产的条码
|
/// </summary>
|
/// <param name="wipList"></param>
|
/// <returns></returns>
|
public ApiAction RemoveWipSn(List<MES_WIP_DATA> wipList, string user)
|
{
|
var action = UnbindWipSnFromWO(WO, wipList, user);
|
//移除成功则处理工单缓存中的数据
|
if (action.IsSuccessed)
|
{
|
Update(true);
|
}
|
|
return action;
|
}
|
|
/// <summary>
|
/// 从工单中解绑正在生产的条码
|
/// </summary>
|
/// <param name="wo"></param>
|
/// <param name="wipList"></param>
|
/// <returns></returns>
|
public static ApiAction UnbindWipSnFromWO(BIZ_MES_WO wo, List<MES_WIP_DATA> wipList, string user)
|
{
|
var action = new ApiAction();
|
|
var wipIDs = wipList.Select(q => q.ID);
|
var batchs = wipList.Where(q => !q.BATCH_NO.IsNullOrEmpty()).GroupBy(q => new { q.BATCH_NO }).Select(g => new {
|
g.Key.BATCH_NO,
|
Qty = g.Count(),
|
OutQty = g.Count(q => MES_WIP_DATA.STATUSs.Output.GetValue() <= q.STATUS && q.STATUS < MES_WIP_DATA.STATUSs.InStorage.GetValue()),
|
});
|
var db = Biz.Db;
|
var dbTran = db.UseTran(() =>
|
{
|
//BIZ_MES_WO
|
wo.INPUT_QTY -= wipList.Count;
|
wo.OUTPUT_QTY -= wipList.Count(q => MES_WIP_DATA.STATUSs.Output.GetValue() <= q.STATUS && q.STATUS < MES_WIP_DATA.STATUSs.InStorage.GetValue());
|
wo.SCRAP_QTY += wipList.Count;
|
wo.STATUS = wo.STATUS.GetEnum<BIZ_MES_WO.STATUSs>() == BIZ_MES_WO.STATUSs.Closed ? BIZ_MES_WO.STATUSs.Working.GetValue() : wo.STATUS;
|
db.Updateable(wo, user).UpdateColumns(q => new { q.INPUT_QTY, q.OUTPUT_QTY, q.SCRAP_QTY, q.UPDATE_USER, q.UPDATE_TIME, q.STATUS }).ExecuteCommand();
|
//BIZ_MES_WO_BATCH
|
var wobatchs = Biz.Db.Queryable<BIZ_MES_WO_BATCH>().Where(q => batchs.Select(x => x.BATCH_NO).Contains(q.BATCH_NO)).ToList();
|
foreach (var batch in wobatchs)
|
{
|
batch.INPUT_QTY -= batchs.Where(q=>q.BATCH_NO == batch.BATCH_NO).First().Qty;
|
batch.OUTPUT_QTY -= batchs.Where(q => q.BATCH_NO == batch.BATCH_NO).First().OutQty;
|
batch.SCRAP_QTY += batchs.Where(q => q.BATCH_NO == batch.BATCH_NO).First().Qty;
|
batch.STATUS = batch.STATUS.GetEnum<BIZ_MES_WO_BATCH.STATUSs>() == BIZ_MES_WO_BATCH.STATUSs.Closed ? BIZ_MES_WO_BATCH.STATUSs.Working.GetValue() : batch.STATUS;
|
}
|
db.Updateable(wobatchs, user).UpdateColumns(q => new { q.INPUT_QTY, q.OUTPUT_QTY, q.SCRAP_QTY, q.UPDATE_USER, q.UPDATE_TIME, q.STATUS }).ExecuteCommand();
|
//BIZ_MES_WO_SN
|
db.Updateable<BIZ_MES_WO_SN>(user)
|
.SetColumns(q => q.STATUS == BIZ_MES_WO_SN.STATUSs.Offline.GetValue())
|
.SetColumns(q => q.TRAY_SN == null).SetColumns(q => q.OUTER_SN == null)
|
.Where(q => q.WORK_ORDER == wo.ORDER_NO && wipIDs.Contains(q.WIP_ID))
|
.ExecuteCommand();
|
//MES_WIP_DATA & MES_WIP_HIS
|
var wipHiss = new List<MES_WIP_HIS>();
|
foreach (var wipSN in wipList) //.Where(q => q.STATUS != MES_WIP_DATA.STATUSs.Offline.GetValue())
|
{
|
wipSN.STATUS = MES_WIP_DATA.STATUSs.Offline.GetValue();
|
wipSN.TRAY_SN = wipSN.INNER_SN = wipSN.CARTON_SN = wipSN.PALLET_SN = wipSN.SHIPPING_ORDER = null;
|
wipSN.FINISHED_FLAG = wipSN.HOLD_FLAG = "N";
|
wipSN.UNBIND_FLAG = "Y";
|
wipSN.NODE_ID = "";
|
wipSN.NODE_NAME = "下线退库";
|
wipSN.OPERATION_TIME = DateTime.Now;
|
var his = new MES_WIP_HIS(wipSN, $"工单[{wipSN.WORK_ORDER}]条码[{wipSN.SN}]下线");
|
wipHiss.Add(his);
|
}
|
db.Storageable(wipList, user).ExecuteCommand();
|
db.Storageable(wipHiss, user).ExecuteCommand();
|
db.Updateable<MES_WIP_HIS>(user)
|
.SetColumns(q => q.UNBIND_FLAG == "Y")
|
.Where(q => q.WORK_ORDER == wo.ORDER_NO && wipIDs.Contains(q.WIP_ID))
|
.ExecuteCommand();
|
//MES_WIP_PKG
|
var curpkg = db.Queryable<MES_WIP_PKG>().Where(q => wipIDs.Contains(q.WIP_ID)).ToList();
|
db.Deleteable(curpkg).ExecuteCommand();
|
var pkgs = new List<MES_WIP_PKG>();
|
do
|
{
|
var parentSns = curpkg.Where(q => !q.PARENT_SN.IsNullOrEmpty()).Select(q => q.PARENT_SN).Distinct();
|
curpkg = db.Queryable<MES_WIP_PKG>().Where(q => parentSns.Contains(q.SN)).ToList();
|
foreach (var pkg in curpkg)
|
{
|
pkg.QTY = db.Queryable<MES_WIP_PKG>().Where(q => q.PARENT_SN == pkg.SN).Sum(q => q.QTY);
|
pkgs.Add(pkg);
|
}
|
}
|
while (curpkg.Any());
|
db.Updateable(pkgs, user).ExecuteCommand();
|
//MES_WIP_DFT,在上仓库装配和维修之前,先把不良记录标记为已处理
|
db.Updateable<MES_WIP_DATA>(user)
|
.SetColumns(q => q.DFT_FLAG == "N")
|
.SetColumns(q => q.DFT_COUNT == 0)
|
.SetColumns(q => q.DFT_CODE == null)
|
.Where(q => q.WORK_ORDER == wo.ORDER_NO && wipIDs.Contains(q.ID))
|
.ExecuteCommand();
|
db.Updateable<MES_WIP_DFT>(user)
|
.SetColumns(q => q.STATUS == MES_WIP_DFT.STATUSs.Resolved.GetValue())
|
.Where(q => q.WORK_ORDER == wo.ORDER_NO && wipIDs.Contains(q.WIP_ID))
|
.ExecuteCommand();
|
});
|
if (!dbTran.IsSuccess)
|
{
|
//抛出异常
|
throw dbTran.ErrorException;
|
}
|
//更新工单池中的工单条码表数据的状态
|
foreach (var item in WoContext.WoBatchDic)
|
{
|
if (item.Value.WO.ORDER_NO == wo.ORDER_NO)
|
{
|
foreach (var woSn in item.Value.WoSNs.Where(q => wipIDs.Contains(q.WIP_ID)))
|
{
|
woSn.STATUS = BIZ_MES_WO_SN.STATUSs.Offline.GetValue();
|
}
|
item.Value.Update();
|
}
|
}
|
return action;
|
}
|
#endregion
|
|
/// <summary>
|
/// 关闭工单
|
/// </summary>
|
/// <returns></returns>
|
public bool Close()
|
{
|
Dispose();
|
return true;
|
}
|
public virtual void Dispose()
|
{
|
WoContext.WoBatchDic.Remove(OrderNo);
|
}
|
}
|
}
|