服务端的TigerApi 框架,基于.NET6 2024 版本
Rodney Chen
2024-08-06 a7496b3785a33c14079b859c88b91a537c454cd5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
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
    {
        /// <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();
                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).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);
 
                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)
                {
                    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;
            }
 
            /// <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.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;
            }
 
            /// <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="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="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);
                    //进入维修:如果目标工序是维修工序则判断当前条码在本工单有未维修的不良记录且当前工序节点有连线到维修工序,则允许进入维修工序
                    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;
            }
 
            /// <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)))
                    {
                        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="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;
            }
 
            #endregion
 
            /// <summary>
            /// 关闭工单
            /// </summary>
            /// <returns></returns>
            public bool Close()
            {
                Dispose();
                return true;
            }
            public virtual void Dispose()
            {
                WoContext.WoBatchDic.Remove(OrderNo);
            }
        }
    }
}