heteng 2 лет назад
Родитель
Сommit
edf320265b

+ 34 - 0
MicroServices/Business/Business.Application.Contracts/Dto/LineScheduledDto.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Business.Dto
+{
+    /// <summary>
+    /// 工单排产Dto
+    /// </summary>
+    public class LineScheduledDto
+    {
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        public DateTime EndTime { get; set; }
+
+        /// <summary>
+        /// 有效生产时长(小时)
+        /// </summary>
+        public decimal EffTime { get; set; }
+
+        /// <summary>
+        /// 生产数量
+        /// </summary>
+        public decimal ProductQty { get; set; }
+    }
+}

+ 34 - 0
MicroServices/Business/Business.Application.Contracts/Dto/LineStartDto.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Business.Dto
+{
+    /// <summary>
+    /// 产线实际排产开始时间
+    /// </summary>
+    public class LineStartDto
+    {
+        /// <summary>
+        /// 层级
+        /// </summary>
+        public int level { get; set; }
+
+        /// <summary>
+        /// 产线
+        /// </summary>
+        public string Line { get; set; }
+
+        /// <summary>
+        /// 工序(当前产线的关键工序)
+        /// </summary>
+        public int Op { get; set; }
+
+        /// <summary>
+        /// 产线排产实际开始日期
+        /// </summary>
+        public DateTime StartTime { get; set; }
+    }
+}

+ 6 - 0
MicroServices/Business/Business.Application.Contracts/Dto/LineWorkPointDto.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -35,5 +36,10 @@ namespace Business.Dto
         /// 结束时间点
         /// </summary>
         public DateTime EndPoint { get; set; }
+
+        /// <summary>
+        /// 当前层级工作时长(分钟)
+        /// </summary>
+        public decimal WorkMinutes { get; set; }
     }
 }

+ 3 - 14
MicroServices/Business/Business.Application.Contracts/Dto/WorkOrdRoutingDto.cs

@@ -12,11 +12,6 @@ namespace Business.Business.Dto
     /// </summary>
     public class WorkOrdRoutingDto
     {
-        /// <summary>
-        /// 父级工序
-        /// </summary>
-        public int ParentOp { get; set; }
-
         /// <summary>
         /// 层级
         /// </summary>
@@ -28,16 +23,10 @@ namespace Business.Business.Dto
         public int Op { get; set; }
 
         /// <summary>
-        /// 标准节拍(小时/个)
+        /// 父级工序
         /// </summary>
-        public List<LineDto> lines { get; set; }
-    }
+        public int ParentOp { get; set; }
 
-    /// <summary>
-    /// 产线Dto
-    /// </summary>
-    public class LineDto 
-    {
         /// <summary>
         /// 产线
         /// </summary>
@@ -49,7 +38,7 @@ namespace Business.Business.Dto
         public decimal Rate { get; set; }
 
         /// <summary>
-        /// 产线提前期
+        /// 产线提前期/提前工作时间不包括休息时间(小时)
         /// </summary>
         public decimal SetupTime { get; set; }
 

+ 361 - 114
MicroServices/Business/Business.Application/Quartz/ProductionScheduleAppService.cs

@@ -8,12 +8,18 @@ using Bussiness.Model.Production;
 using Bussiness.Model.SRM;
 using MongoDB.Driver;
 using MongoDB.Driver.Linq;
+using Quartz;
+using Spire.Pdf.Exporting.XPS.Schema;
+using Spire.Pdf.General.Paper.Font.Rendering.Path;
 using System;
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
+using ThoughtWorks.QRCode.Codec.Util;
+using ThoughtWorks.QRCode.Geom;
 using Volo.Abp.Application.Services;
 using Volo.Abp.Domain.Repositories;
 using Volo.Abp.Validation.Localization;
@@ -169,9 +175,9 @@ namespace Business.Quartz
             //获取物料对应的生产线信息:物料、工序对应的生产线
             List<ProdLineDetail> prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == "1001" && p.IsActive == 1).Result;
             //获取生产周期数据
-            List<PeriodSequenceDet> periodSequences = _periodSequenceDet.Select(p=> workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.PlanDate >= earlist && p.Domain == "1001" && p.IsActive == 1).Result;
+            List<PeriodSequenceDet> dbPeriodSequences = _periodSequenceDet.Select(p=> workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.PlanDate >= earlist && p.Domain == "1001" && p.IsActive == 1).Result;
             //获取当前日期往后的排产记录数据
-            List<ScheduleResultOpMaster> schedules = _scheduleResultOpMaster.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.WorkDate >= earlist && p.Domain == "1001").Result;
+            List<ScheduleResultOpMaster> dbSchedules = _scheduleResultOpMaster.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.WorkDate >= earlist && p.Domain == "1001").Result;
             //获取工作日历数据
             calendars = _shopCalendarWorkCtr.Select(p=>p.Domain == "1001" && p.IsActive == 1).Result;
             //获取产线休息记录数据
@@ -183,18 +189,19 @@ namespace Business.Quartz
             //排产异常记录
             List<ScheduleExceptionMaster> scheduleExceptions = new List<ScheduleExceptionMaster>();
             //生产周期
-            List<PeriodSequenceDet> periodsDet = new List<PeriodSequenceDet>();
+            List<PeriodSequenceDet> periodSequenceDtls = new List<PeriodSequenceDet>();
             //排产记录表
-            List<ScheduleResultOpMaster> scheduleResults = new List<ScheduleResultOpMaster>();
+            List<ScheduleResultOpMaster> scheduleMasters = new List<ScheduleResultOpMaster>();
             foreach (var item in workOrds)
             {
+                //当前工单的排产计划开始时间:年-月-日
                 DateTime planStart = item.OrdDate.GetValueOrDefault().Date;
-                //工序预处理
-                List<WorkOrdRoutingDto> routingDtos = ProcPretreatment(workOrdRoutings, prodLines);
-                
-                //获取主工序对应的产线
-                var first = routingDtos.Where(p => p.level == 1).FirstOrDefault();
-                if (first == null)
+                //当前工单的对应的产线排产记录
+                var curSchedules = dbSchedules.Where(p => p.ItemNum == item.ItemNum).ToList();
+
+                //工序预处理:确定每层级工序对应的产线
+                List<WorkOrdRoutingDto> routingDtos = ProcPretreatment(item, workOrdRoutings.Where(p => p.WorkOrd == item.WorkOrd).ToList(), prodLines, curSchedules);
+                if (routingDtos.Count() == 0)//没有维护主工序
                 {
                     //记录排产异常原因
                     scheduleExceptions.Add(new ScheduleExceptionMaster
@@ -202,12 +209,13 @@ namespace Business.Quartz
                         RecID = help.NextId(),
                         Domain = "1001",
                         WorkOrd = item.WorkOrd,
-                        Remark = "工单没有维护工序数据",
-                        CreatTime = DateTime.Now.Date
-                    }); 
+                        Remark = "工单关联工序数据维护错误",
+                        CreatTime = DateTime.Now
+                    });
                     continue;
                 }
-                if (first.lines.Count() == 0)
+                //校验每层级工序是否都维护了产线
+                if (routingDtos.Exists(p=> string.IsNullOrEmpty(p.Line)))
                 {
                     //记录排产异常原因
                     scheduleExceptions.Add(new ScheduleExceptionMaster
@@ -215,15 +223,14 @@ namespace Business.Quartz
                         RecID = help.NextId(),
                         Domain = "1001",
                         WorkOrd = item.WorkOrd,
-                        Remark = "工单没有维护产线数据",
-                        CreatTime = DateTime.Now.Date
+                        Remark = "工单关联产线数据维护错误",
+                        CreatTime = DateTime.Now
                     });
                     continue;
                 }
-                //确定主工序排产产线以及排产开始时间
-                LineDto line = ConfirmLine(first.Op, planStart, first.lines, schedules);
+
                 //产线排产
-                LineSchedule(item, line, routingDtos, periodsDet, scheduleResults, scheduleExceptions);
+                LineSchedule(item, routingDtos.OrderBy(p=>p.level).ToList(), periodSequenceDtls, scheduleMasters, scheduleExceptions);
             }
         }
 
@@ -231,48 +238,201 @@ namespace Business.Quartz
         /// 排产
         /// </summary>
         /// <param name="workOrd">工单</param>
-        /// <param name="line">主工序对应的产线</param>
-        /// <param name="routingDtos">每层级工序对应的产线信息</param>
+        /// <param name="routingDtos">每层级工序对应的产线信息,从小到大排序</param>
         /// <param name="periodsDet">生产周期</param>
         /// <param name="scheduleResults">排产结果</param>
         /// <param name="scheduleExceptions">排产异常记录</param>
-        public void LineSchedule(WorkOrdMaster workOrd, LineDto line, List<WorkOrdRoutingDto> routingDtos,List<PeriodSequenceDet> periodsDet, List<ScheduleResultOpMaster> scheduleResults, List<ScheduleExceptionMaster> scheduleExceptions)
+        public void LineSchedule(WorkOrdMaster workOrd,List<WorkOrdRoutingDto> routingDtos,List<PeriodSequenceDet> periodsDet, List<ScheduleResultOpMaster> scheduleResults, List<ScheduleExceptionMaster> scheduleExceptions)
         {
             //生产周期
             List<PeriodSequenceDet> curSequences = new List<PeriodSequenceDet>();
             //排产明细
-            List< ScheduleResultOpMaster > curScheduleRsts = new List<ScheduleResultOpMaster >();
-            //获取当前产线的工作日历
-            var curCalendars = calendars.Where(p => p.ProdLine == line.Line).ToList();
-            //获取当前产线的休息记录
-            var curQtyDtls = qualityLines.Where(p => p.ProdLine == line.Line).OrderBy(m => m.Line).ToList();
-            if (curCalendars.Count() == 0 || curCalendars.Select(p=>p.WeekDay).Distinct().Count() != 7)
+            List<ScheduleResultOpMaster> curScheduleRsts = new List<ScheduleResultOpMaster>();
+            //产线排产开始时间
+            List<LineStartDto> lineStarts = new List<LineStartDto>();
+            
+            //循环产线,排产
+            foreach (var item in routingDtos)
             {
-                //记录排产异常原因
-                scheduleExceptions.Add(new ScheduleExceptionMaster
+                //当前产线的工作日历
+                var mLCalendars = calendars.Where(p => p.ProdLine == item.Line).ToList();
+                //当前产线的每天休息时间记录
+                var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == item.Line).ToList();
+
+                //产线已排产数量
+                decimal sumQty = 0m;
+                //产线实际排产开始时间
+                DateTime workStartTime;
+                if (item.level == 1)//主产线
+                {
+                    workStartTime = DealStartTime(item.StartTime, mLCalendars, mlqtyWorkDtls);
+                }
+                else
                 {
-                    RecID = help.NextId(),
-                    Domain = "1001",
-                    WorkOrd = workOrd.WorkOrd,
-                    Remark = "工单对应产线工作日历数据错误",
-                    CreatTime = DateTime.Now.Date
+                    //子产线获取实际排产开始日期
+                    //获取父级排产开始时间
+                    DateTime parentStartTime = lineStarts.First(p => p.Op == item.ParentOp).StartTime;
+                    workStartTime = DealChildStartTime(parentStartTime, item.SetupTime, mLCalendars, mlqtyWorkDtls);
+                }
+                //记录产线排产开始时间
+                lineStarts.Add(new LineStartDto { 
+                    level = item.level,
+                    Line = item.Line,
+                    Op= item.Op,
+                    StartTime = workStartTime
                 });
-                return;
+                //排产
+                while (sumQty < workOrd.QtyOrded)
+                {
+                    //获取当天的产能
+                    LineScheduledDto dto = GetScheduledPoint(item, workStartTime, mLCalendars, mlqtyWorkDtls);
+                    //判断已排产数量+当天的产能是否超过工单数量
+                    if (sumQty + dto.ProductQty <= workOrd.QtyOrded)//当天的产能需要全部排产
+                    {
+                        //记录生产周期
+                        curSequences.Add(new PeriodSequenceDet
+                        {
+                            Domain = "1001",
+                            Line = item.Line,
+                            ItemNum = workOrd.ItemNum,
+                            PlanDate = workStartTime.Date,
+                            Period = 1,//目前只考虑一班制
+                            OrdQty = dto.ProductQty,
+                            WorkOrds = workOrd.WorkOrd,
+                            IsActive = 1
+                        });
+                        //记录排产记录
+                        curScheduleRsts.Add(new ScheduleResultOpMaster
+                        {
+                            Domain = "1001",
+                            WorkOrd = workOrd.WorkOrd,
+                            Line = item.Line,
+                            ItemNum = workOrd.ItemNum,
+                            Op = item.Op,
+                            WorkDate = workStartTime.Date,
+                            WorkQty = dto.ProductQty,
+                            WorkStartTime = dto.StartTime,
+                            WorkEndTime = dto.EndTime,
+                            CreatTime = DateTime.Now
+                        });
+                        //累计已排产数量
+                        sumQty += dto.ProductQty;
+                        //继续排下一个工作日
+                        workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
+                    }
+                    else// 最后一天的产能只能占用一部分
+                    {
+                        //剩余需要排产的数量
+                        decimal residueQty = workOrd.QtyOrded - sumQty;
+                        //剩余数量生产需要时长(分钟)
+                        decimal workTime = residueQty / item.Rate * 60;
+                        //获取当天的工作时间段
+                        List<LineWorkPointDto> workPoints = DealWorkDayToLevels(workStartTime, mLCalendars.First(p => p.WeekDay == (int)workStartTime.DayOfWeek), mlqtyWorkDtls);
+                        var curPoint = workPoints.First(p => p.StartPoint >= workStartTime && workStartTime <= p.EndPoint);
+                        TimeSpan span = curPoint.EndPoint - workStartTime;
+                        //当天工作时间段的有效生产时间
+                        decimal effMins = (decimal)span.TotalMinutes;
+                        DateTime workEndTime = workStartTime;
+                        if (effMins >= workTime)//当前工作时间段即可满足产能
+                        {
+                            workEndTime = workStartTime.AddMinutes((double)workTime);
+                        }
+                        else
+                        {
+                            //获取后续生产时间段
+                            var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
+                            //剩余需要工作时长
+                            decimal nextMins = workTime - effMins;
+                            foreach (var p in nextPoints)
+                            {
+                                if (p.WorkMinutes >= nextMins)
+                                {
+                                    workEndTime = p.StartPoint.AddMinutes((double)nextMins);
+                                    break;
+                                }
+                                nextMins -= p.WorkMinutes;
+                            }
+                        }
+                        //记录生产周期
+                        curSequences.Add(new PeriodSequenceDet
+                        {
+                            Domain = "1001",
+                            Line = item.Line,
+                            ItemNum = workOrd.ItemNum,
+                            PlanDate = workStartTime.Date,
+                            Period = 1,//目前只考虑一班制
+                            OrdQty = residueQty,
+                            WorkOrds = workOrd.WorkOrd,
+                            IsActive = 1
+                        });
+                        //记录排产记录
+                        curScheduleRsts.Add(new ScheduleResultOpMaster
+                        {
+                            Domain = "1001",
+                            WorkOrd = workOrd.WorkOrd,
+                            Line = item.Line,
+                            ItemNum = workOrd.ItemNum,
+                            Op = item.Op,
+                            WorkDate = workStartTime.Date,
+                            WorkQty = residueQty,
+                            WorkStartTime = workStartTime,
+                            WorkEndTime = workEndTime,
+                            CreatTime = DateTime.Now
+                        });
+                    }
+                }
             }
-            //产线可排产开始日期
-            DateTime workStartTime = DealStartTime(line.StartTime, curCalendars, curQtyDtls);
 
+            periodsDet.AddRange(curSequences);
+            scheduleResults.AddRange(curScheduleRsts);
         }
-        
+
+        /// <summary>
+        /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量
+        /// </summary>
+        /// <param name="routingDto">产线</param>
+        /// <param name="startTime"></param>
+        /// <param name="curCalendars"></param>
+        /// <param name="curQtyDtls"></param>
+        /// <returns></returns>
+        public LineScheduledDto GetScheduledPoint(WorkOrdRoutingDto routingDto,DateTime startTime, List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
+        { 
+            LineScheduledDto scheduledDto = new LineScheduledDto();
+            //当天排产开始时间
+            scheduledDto.StartTime = startTime;
+            //开始时间是周几
+            int weekDay = (int)startTime.DayOfWeek;
+            //当天的工作日历
+            var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First();
+            //当前日期的工作时间段
+            List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
+            //当天排产结束时间
+            scheduledDto.EndTime = workPoints.Last().EndPoint;
+            //计算starttime处于那个工作时间段
+            var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).First();
+            TimeSpan span = curPoint.EndPoint - startTime;
+            scheduledDto.EffTime = (decimal)span.TotalHours;
+            //获取后续工作时间段的有效工作时间
+            var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
+            foreach (var item in nextPoints)
+            {
+                span = item.EndPoint - item.StartPoint;
+                scheduledDto.EffTime += (decimal)span.TotalHours;
+            }
+            //计算当天的产能
+            scheduledDto.ProductQty = scheduledDto.EffTime * routingDto.Rate;
+            return scheduledDto;
+        }
+
         /// <summary>
         /// 计算主产线实际排产开始时间
         /// </summary>
-        /// <param name="startTime">开始时间</param>
+        /// <param name="startTime">工单排产开始时间</param>
         /// <param name="curCalendars">当前产线工作日历</param>
         /// <param name="curQtyDtls">当前产线休息记录</param>
         /// <returns></returns>
-        public DateTime DealStartTime(DateTime startTime,List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
-        { 
+        public DateTime DealStartTime(DateTime startTime, List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
+        {
             //实际排产开始时间
             DateTime actStart = startTime;
             //开始时间是周几
@@ -280,12 +440,12 @@ namespace Business.Quartz
             //当天的工作日历
             var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First();
             //当前日期的工作时间段
-            List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal,curQtyDtls);
+            List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
             //计算starttime处于那个工作时间段
             var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
             if (startTime != curPoint.EndPoint)
             {
-                return startTime;
+                return actStart;
             }
             //查询下一时间段的开始时间点
             var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
@@ -298,6 +458,82 @@ namespace Business.Quartz
             return actStart;
         }
 
+        /// <summary>
+        /// 计算子产线实际排产开始时间
+        /// </summary>
+        /// <param name="startTime">父级工单排产开始时间</param>
+        /// <param name="setupTime">当前产线提前期(小时)-需要提前生产时长,不包括休息时间</param>
+        /// <param name="curCalendars">当前产线工作日历</param>
+        /// <param name="curQtyDtls">当前产线休息记录</param>
+        /// <returns></returns>
+        public DateTime DealChildStartTime(DateTime startTime,decimal setupTime, List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
+        {
+            //提前期转换成分钟
+            decimal needMinute = setupTime * 60;
+            //实际排产开始时间
+            DateTime actStart = startTime;
+            //开始时间是周几
+            int weekDay = (int)startTime.DayOfWeek;
+            //当天的工作日历
+            var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First();
+            //当前日期的工作时间段
+            List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
+            //计算starttime处于那个工作时间段
+            var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
+            //当前时间段可用提前期
+            TimeSpan span = startTime - curPoint.StartPoint;
+            decimal curMins = (decimal)span.TotalMinutes;
+            if (curMins >= needMinute)//当前时间段的可用提前期满足
+            {
+                actStart = startTime.AddMinutes((double)-needMinute);
+                return actStart;
+            }
+            //当前时间段的可用提前期不满足
+            //剩余提前期
+            needMinute -= curMins;
+            //获取前层级时间段
+            var prePoints = workPoints.Where(p => p.Level < curPoint.Level).ToList();
+            foreach (var item in prePoints)
+            {
+                if (item.WorkMinutes >= needMinute)//当前时间段的可用提前期满足
+                {
+                    actStart = item.EndPoint.AddMinutes((double)-needMinute);
+                    break;
+                }
+                needMinute -= item.WorkMinutes;
+            }
+            //今天可用提前期不够,往前工作日找
+            DateTime perStartTime = startTime;
+            bool flag = true;//标志位
+            while (flag)
+            {
+                //获取前一个工作日
+                perStartTime = GetPreWorkDay(perStartTime, curCalendars);
+                //获取前一个工作日的工作时间段数据,倒序排
+                workPoints = DealWorkDayToLevels(perStartTime, shopCal, curQtyDtls).OrderByDescending(p=>p.Level).ToList();
+                //当天的工作时长(分钟)
+                decimal sumWorkMins = workPoints.Sum(p => p.WorkMinutes);
+                if (sumWorkMins >= needMinute)//当天可用提前期满足
+                {
+                    //获取开始时间
+                    foreach (var item in workPoints)
+                    {
+                        if (item.WorkMinutes >= needMinute)//当前时间段满足
+                        {
+                            actStart = item.EndPoint.AddMinutes((double)-needMinute);
+                            break;
+                        }
+                        needMinute -= item.WorkMinutes;
+                    }
+                    flag = false;
+                }
+                //当天可用提前期不满足
+                needMinute -= sumWorkMins;
+            }
+
+            return actStart;
+        }
+
         /// <summary>
         /// 获取下一个工作日开始时间
         /// </summary>
@@ -334,6 +570,41 @@ namespace Business.Quartz
             return rtnData;
         }
 
+        /// <summary>
+        /// 获取上一个工作日开始时间
+        /// </summary>
+        /// <param name="startTime">开始时间</param>
+        /// <param name="curCalendars">当前产线的工作日历</param>
+        /// <returns></returns>
+        public DateTime GetPreWorkDay(DateTime startTime, List<ShopCalendarWorkCtr> curCalendars)
+        {
+            DateTime rtnData = startTime;
+            //前一天
+            DateTime preDate = startTime.Date.AddDays(-1);
+            //前一天是周几
+            int preWeekDay = (int)preDate.DayOfWeek;
+            var calendar = curCalendars.FirstOrDefault(p => p.WeekDay == preWeekDay);
+            //判断前一天是否是工作日
+            if (preWeekDay == 0 || preWeekDay == 6)//前一天是周六或者周日,需要判断是否调休,需要加班
+            {
+                if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "调休"))//前一天是非工作日
+                {
+                    //递归继续找下一个工作日
+                    GetPreWorkDay(preDate, curCalendars);
+                }
+                rtnData = preDate.AddHours((double)calendar.ShiftsStart1);
+                return rtnData;
+            }
+            //前一天不是周六周日,需要判断是不是节假日
+            if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "休假"))//是节假日
+            {
+                //递归继续找前一个工作日
+                GetPreWorkDay(preDate, curCalendars);
+            }
+            rtnData = preDate.AddHours((double)calendar.ShiftsStart1);
+            return rtnData;
+        }
+
         /// <summary>
         /// 处理当前日期的工作时间段
         /// </summary>
@@ -358,9 +629,13 @@ namespace Business.Quartz
             dto.WeekDay = weekDay;
             dto.StartPoint = dayStartPoint;
             int level = 1;
+            TimeSpan span = TimeSpan.Zero;
             foreach (var item in curQtyDtls)
             {
                 DateTime endPoint = Convert.ToDateTime(date + " " + item.RestTimePoint);
+                dto.EndPoint= endPoint;
+                span = dto.EndPoint - dto.StartPoint;
+                dto.WorkMinutes = (decimal)span.TotalMinutes;
                 workPoints.Add(dto);
                 level++;
                 dto = new LineWorkPointDto();
@@ -370,79 +645,50 @@ namespace Business.Quartz
                 dto.StartPoint = endPoint.AddMinutes(item.RestTime);
             }
             dto.EndPoint = dayEndPoint;
+            span = dto.EndPoint - dto.StartPoint;
+            dto.WorkMinutes = (decimal)span.TotalMinutes;
             workPoints.Add(dto);
 
-            return workPoints;
-        }
-
-        /// <summary>
-        /// 确定工序排产对应的产线
-        /// </summary>
-        /// <param name="op">主产线的最后一道工序</param>
-        /// <param name="planStart">当前工单计划开始时间</param>
-        /// <param name="lines">主工序对应的产线</param>
-        /// <param name="scheduleResults"></param>
-        /// <returns></returns>
-        public LineDto ConfirmLine(int op, DateTime planStart, List<LineDto> lines, List<ScheduleResultOpMaster> scheduleResults)
-        {
-            LineDto rtn = new LineDto();
-            //多条产线确认最优产线:按开工时间升序,产能倒叙排序
-            foreach (var item in lines)
-            {
-                //获取当前产线的排产最晚结束时间
-                var schedule = scheduleResults.Where(p => p.Line == item.Line && p.Op == op).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
-                //当前产线没有排产记录,开始日期= planStart;
-                //当前产线有排产记录,如果产线排产最晚结束时间≤planStart 开始日期=planStart,反之 开始日期 = WorkEndTime
-                var latest = schedule == null ? planStart : (schedule.WorkEndTime <= planStart ? planStart : schedule.WorkEndTime);
-                if (rtn == null)
-                {
-                    rtn.Line = item.Line;
-                    rtn.Rate = item.Rate;
-                    rtn.StartTime = latest;
-                }
-                if (latest < rtn.StartTime || (latest == rtn.StartTime && item.Rate > rtn.Rate))
-                {//当前产线最早开工时间早于之前的产线或者产线开工时间相同且当前产线产能大于之前的产线,则当前产线为优先选择产线
-                    rtn.Line = item.Line;
-                    rtn.Rate = item.Rate;
-                    rtn.StartTime = latest;
-                }
-            }
-            return rtn;
+            return workPoints.OrderBy(p => p.Level).ToList();
         }
 
         /// <summary>
         /// 工单工艺路线预处理
         /// </summary>
-        /// <param name="woRuntings">工单工序</param>
+        /// <param name="workOrd">工单</param>
+        /// <param name="woRuntings">当前工单对应的工序</param>
         /// <param name="prodLines">产线</param>
+        /// <param name="schedules">当前工单对应产品的排产记录</param>
         /// <returns></returns>
-        public List<WorkOrdRoutingDto> ProcPretreatment(List<WorkOrdRouting> woRuntings, List<ProdLineDetail> prodLines)
+        public List<WorkOrdRoutingDto> ProcPretreatment(WorkOrdMaster workOrd,List<WorkOrdRouting> woRuntings, List<ProdLineDetail> prodLines, List<ScheduleResultOpMaster> schedules)
         {
             List<WorkOrdRoutingDto> routingDtos = new List<WorkOrdRoutingDto>();
+            //当前工单计划开始时间
+            DateTime planStart = workOrd.OrdDate.GetValueOrDefault();
             //取主工序(第一层级工序)
-            var firsts = woRuntings.Where(p =>p.ParentOp == 0).ToList();
-            if (firsts.Count == 0)
+            var firsts = woRuntings.Where(p =>p.ParentOp == 0).OrderByDescending(p => p.OP).ToList();
+            if (firsts.Count() == 0)
             {
                 return routingDtos;
             }
             WorkOrdRoutingDto dto = new WorkOrdRoutingDto();
-            //当前层级的关键工序/报工工序(当前层级工序Op最大的工序)
-            var lastOp = firsts.OrderByDescending(p => p.OP).FirstOrDefault();
-            if (lastOp != null) {
-                dto.ParentOp = lastOp.ParentOp;
-                dto.level = 1;
-                dto.Op = lastOp.OP;
-                //当前层级工序对应的产线
-                var curLines = prodLines.Where(p => p.Part == lastOp.WorkOrd && p.Op == lastOp.OP).Select(m => new LineDto
-                {
-                    Line = m.Line,
-                    Rate = m.Rate,
-                    SetupTime = m.SetupTime
-                }).AsQueryable<LineDto>().ToList();
-                dto.lines = curLines;
-                routingDtos.Add(dto);
+            //主工序按照Op排序,取最大Op
+            var lastOp = firsts.First();
+            dto.ParentOp = lastOp.ParentOp;
+            dto.level = 1;
+            dto.Op = lastOp.OP;
+            //主工序对应的产线
+            var line = prodLines.Where(p => p.Part == lastOp.ItemNum && p.Op == lastOp.OP).FirstOrDefault();
+            if (line != null) {
+                dto.Line = line.Line;
+                dto.Rate = line.Rate;
+                dto.SetupTime = 0;
+                //获取产线占用结束时间
+                var schedule = schedules.Where(p => p.Line == line.Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
+                dto.StartTime = schedule == null ? planStart : (schedule.WorkEndTime <= planStart ? planStart : schedule.WorkEndTime);
             }
-            
+            routingDtos.Add(dto);
+
             //递归处理其他层级工序
             RecursionProc(woRuntings, firsts, 1, routingDtos, prodLines);
 
@@ -465,26 +711,27 @@ namespace Business.Quartz
             {
                 return;
             }
-            //获取父级Op
+            //获取父级Op-当前层级有几条子产线
             var parentOps = curLevels.Select(m => m.ParentOp).Distinct().ToList();
             foreach (var item in parentOps)
             {
                 var dto = new WorkOrdRoutingDto();
                 var lastOp = curLevels.Where(p=>p.ParentOp == item).OrderByDescending(m=>m.OP).FirstOrDefault();
-                if (lastOp != null){
-                    dto.Op = lastOp.OP;
-                    dto.ParentOp = lastOp.ParentOp;
-                    dto.level = level + 1;
-                    //当前层级工序对应的产线
-                    var curLines = prodLines.Where(p => p.Part == lastOp.WorkOrd && p.Op == lastOp.OP).Select(m => new LineDto
-                    {
-                        Line = m.Line,
-                        Rate = m.Rate,
-                        SetupTime = m.SetupTime
-                    }).AsQueryable<LineDto>().ToList();
-                    dto.lines = curLines;
-                    routingDtos.Add(dto);
+                if (lastOp == null){
+                    continue;
+                }
+                dto.Op = lastOp.OP;
+                dto.ParentOp = lastOp.ParentOp;
+                dto.level = level + 1;
+                //当前层级工序对应的产线,子产线有多条,则取产能最大的那一条
+                var maxRateLine = prodLines.Where(p => p.Part == lastOp.ItemNum && p.Op == lastOp.OP).OrderByDescending(p => p.Rate).FirstOrDefault();
+                if (maxRateLine != null)
+                {
+                    dto.Line = maxRateLine.Line;
+                    dto.Rate = maxRateLine.Rate;
+                    dto.SetupTime = maxRateLine.SetupTime;
                 }
+                routingDtos.Add(dto);
             }
             //递归
             RecursionProc(woRuntings, curLevels, level + 1, routingDtos,prodLines);