using Business.Business.Dto; using Business.Core.Utilities; using Business.Dto; using Business.EntityFrameworkCore; using Business.EntityFrameworkCore.SqlRepositories; using Business.Model.MES.IC; using Business.Model.Production; using EFCore.BulkExtensions; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Threading.Tasks; using System.Transactions; using Volo.Abp.Application.Services; using Volo.Abp.MultiTenancy; namespace Business.ResourceExamineManagement { /// /// 生产排产服务 /// public class ProductionScheduleAppService : ApplicationService { #region 服务 /// /// 物料 /// private ISqlRepository _itemMaster; /// /// 工单 /// private ISqlRepository _workOrdMaster; /// /// 工单物料明细 /// private ISqlRepository _workOrdDetail; /// /// 工单工艺路线明细 /// private ISqlRepository _workOrdRouting; /// /// 库存主数据 /// private ISqlRepository _invMaster; /// /// 生产线明细 /// private ISqlRepository _prodLineDetail; /// /// 生产周期明细 /// private ISqlRepository _periodSequenceDet; /// /// 排产结果明细 /// private ISqlRepository _scheduleResultOpMaster; /// /// 工作日历数据 /// private ISqlRepository _shopCalendarWorkCtr; /// /// 产线休息时间记录表 /// private ISqlRepository _qualityLineWorkDetail; /// /// 节假日记录表 /// private ISqlRepository _holidayMaster; /// /// 雪花算法 /// SnowFlake help = new SnowFlake(); private readonly ICurrentTenant _currentTenant; /// /// 工作日历数据 /// private List calendars; /// /// 产线休息记录数据 /// private List qualityLines; /// /// 节假日记录数据 /// private List holidays; #endregion #region 构造函数 /// /// 构造函数 /// public ProductionScheduleAppService( ISqlRepository itemMaster, ISqlRepository workOrdMaster, ISqlRepository workOrdDetail, ISqlRepository workOrdRouting, ISqlRepository prodLineDetail, ISqlRepository periodSequenceDet, ISqlRepository scheduleResultOpMaster, ISqlRepository invMaster, ISqlRepository shopCalendarWorkCtr, ISqlRepository qualityLineWorkDetail, ISqlRepository holidayMaster, ICurrentTenant currentTenant ) { _itemMaster = itemMaster; _workOrdMaster = workOrdMaster; _workOrdDetail = workOrdDetail; _workOrdRouting = workOrdRouting; _prodLineDetail = prodLineDetail; _periodSequenceDet = periodSequenceDet; _scheduleResultOpMaster = scheduleResultOpMaster; _invMaster = invMaster; _shopCalendarWorkCtr = shopCalendarWorkCtr; _qualityLineWorkDetail = qualityLineWorkDetail; _holidayMaster = holidayMaster; _currentTenant = currentTenant; } #endregion /// /// 执行生产排产 /// public async void DoExt() { List workOrds = new List(); await DoProductShcedule(workOrds); } /// /// 生产排产 /// /// 工单:定时任务执行时count=0;资源检查调用count>0 /// public async Task DoProductShcedule(List workOrds) { if (workOrds.Count == 0)//定时任务调用时,需要获取工单数据 { //1、获取需要排产的工单:Status为空且IsActive==1 workOrds = _workOrdMaster.Select(p => string.IsNullOrEmpty(p.Status) && p.IsActive); } if (workOrds.Count == 0)//没有工单需要排产 { return; } //获取排产工单的最早计划开工日期 DateTime earlist; if (workOrds.Min(p => p.OrdDate) == null) { earlist = DateTime.Now.Date.AddDays(7); } else { earlist = workOrds.Min(p => p.OrdDate.GetValueOrDefault()).Date; } //2、获取数据 //获取工单工艺路径数据 List workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.Domain == "1001" && p.Status != "C" && p.IsActive); //获取物料对应的生产线信息:物料、工序对应的生产线 List prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == "1001" && p.IsActive); List lines = prodLines.Select(p => p.Line).ToList(); //获取生产周期数据 List dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate >= earlist && p.Domain == "1001" && p.IsActive); //获取当前日期往后的排产记录数据 List dbSchedules = _scheduleResultOpMaster.Select(p => lines.Contains(p.Line) && p.WorkDate >= earlist && p.Domain == "1001"); //获取工作日历数据 calendars = _shopCalendarWorkCtr.Select(p => p.Domain == "1001" && p.IsActive); //获取产线休息记录数据 qualityLines = _qualityLineWorkDetail.Select(p => p.Domain == "1001" && p.IsActive); //获取节假日记录数据 holidays = _holidayMaster.Select(p => p.Domain == "1001" && p.IsActive && p.Dated >= earlist); //3、排产 //生产周期 List periodSequenceDtls = new List(); //排产记录表 List scheduleMasters = new List(); //排产结果 List allResults = new List(); allResults.AddRange(dbSchedules); foreach (var item in workOrds) { ////当前工单的排产计划开始时间:年-月-日 //DateTime planStart = item.OrdDate.GetValueOrDefault().Date; ////当前工单对应的产线数据 //var curLines = prodLines.Where(p => p.Part == item.ItemNum).Select(m=>m.Line).Distinct().ToList(); ////当前工单的对应的产线排产记录 //var curSchedules = dbSchedules.Where(p => curLines.Contains(p.Line)).ToList(); allResults.AddRange(scheduleMasters); //工序预处理:确定每层级工序对应的产线 List routingDtos = ProcPretreatment(item, workOrdRoutings.Where(p => p.WorkOrd == item.WorkOrd).ToList(), prodLines, allResults); //排产前的数据校验 if (routingDtos.Count == 0)//没有维护主工序 { //记录排产异常原因 new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的工序数据维护错误", _currentTenant.Id.ToString()); continue; } //校验每层级工序是否都维护了产线 if (routingDtos.Exists(p => string.IsNullOrEmpty(p.Line))) { //记录排产异常原因 new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的产线数据维护错误", _currentTenant.Id.ToString()); continue; } //校验每个层级是否维护了工作日历 bool flag = false; foreach (var rut in routingDtos) { var lineCals = calendars.Where(p => p.ProdLine == rut.Line).ToList(); if (lineCals.Select(p => p.WeekDay).Distinct().Count() != 7) { flag = true; break; } } if (flag) { //记录排产异常原因 new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的产线工作日历数据维护错误", _currentTenant.Id.ToString()); continue; } //产线排产 LineSchedule(item, routingDtos.OrderBy(p => p.level).ToList(), periodSequenceDtls, scheduleMasters); List scheduleList = scheduleMasters.Where(s => s.WorkOrd == item.WorkOrd).ToList(); if (scheduleList.Any()) { item.OrdDate = scheduleList.Min(s => s.WorkStartTime.Date); item.DueDate = scheduleList.Max(s => s.WorkEndTime.Date); } /*//更新工单表 item.Status = "r";*/ } using (TransactionScope scope = new TransactionScope()) { try { //记录排产数据 _workOrdMaster.Update(workOrds); _periodSequenceDet.Insert(periodSequenceDtls); _scheduleResultOpMaster.Insert(scheduleMasters); scope.Complete(); } catch (Exception ex) { new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "记录排产数据失败:" + ex.Message, _currentTenant.Id.ToString()); scope.Dispose(); } } } /// /// 排产 /// /// 工单 /// 每层级工序对应的产线信息,从小到大排序 /// 生产周期 /// 排产结果 public void LineSchedule(WorkOrdMaster workOrd, List routingDtos, List periodsDet, List scheduleResults) { //生产周期 List curSequences = new List(); //排产明细 List curScheduleRsts = new List(); //产线排产开始时间 List lineStarts = new List(); //循环产线,排产 foreach (var item in routingDtos) { //当前产线的工作日历 var mLCalendars = calendars.Where(p => p.ProdLine == item.Line).ToList(); //当前产线的每天休息时间记录 var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == item.Line).OrderBy(p => p.Line).ToList(); //产线已排产数量 decimal sumQty = 0m; //产线实际排产开始时间 DateTime workStartTime; if (item.level == 1)//主产线 { workStartTime = DealStartTime(item.StartTime, mLCalendars, mlqtyWorkDtls); } else { //子产线获取实际排产开始日期 //获取父级排产开始时间 DateTime parentStartTime = lineStarts.First(p => p.ChdParentOps.Contains(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, ChdParentOps = item.ChdParentOps }); //排产 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, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 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, CreateTime = DateTime.Now }); //累计已排产数量 sumQty += dto.ProductQty; //继续排下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); } else// 最后一天的产能只能占用一部分 { //剩余需要排产的数量 decimal residueQty = workOrd.QtyOrded - sumQty; //剩余数量生产需要时长(分钟) decimal workTime = Math.Ceiling(residueQty / item.Rate * 60); //获取当天的工作时间段 List workPoints = DealWorkDayToLevels(workStartTime, mLCalendars.First(p => p.WeekDay == (int)workStartTime.DayOfWeek), mlqtyWorkDtls); var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); if (curPoint == null) { continue; } 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; } } sumQty = workOrd.QtyOrded; //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = "1001", Line = item.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = 1,//目前只考虑一班制 OrdQty = residueQty, WorkOrds = workOrd.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 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, CreateTime = DateTime.Now }); } } } //记录排产结果 periodsDet.AddRange(curSequences); scheduleResults.AddRange(curScheduleRsts); } /// /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量 /// /// 产线 /// /// /// /// public LineScheduledDto GetScheduledPoint(WorkOrdRoutingDto routingDto, DateTime startTime, List curCalendars, List curQtyDtls) { LineScheduledDto scheduledDto = new LineScheduledDto(); //当天排产开始时间 scheduledDto.StartTime = startTime; //开始时间是周几 int weekDay = (int)startTime.DayOfWeek; //当天的工作日历 var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First(); //当前日期的工作时间段 List 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 = Math.Floor(scheduledDto.EffTime * routingDto.Rate); return scheduledDto; } /// /// 计算主产线实际排产开始时间 /// /// 工单排产开始时间 /// 当前产线工作日历 /// 当前产线休息记录 /// public DateTime DealStartTime(DateTime startTime, List curCalendars, List curQtyDtls) { //实际排产开始时间 DateTime actStart = startTime; //开始时间是周几 int weekDay = (int)startTime.DayOfWeek; //判断当天是否是工作日 bool isWorkDay = CheckIsWorkDay(startTime); if (!isWorkDay)//不是工作日 { //获取下一个工作日开始时间 actStart = GetNextWorkDay(weekDay, startTime, curCalendars); return actStart; } //开始时间往后推到半小时/整点节点 actStart = CalcStartTimeAfter(actStart); //当天的工作日历 var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First(); //当前日期的工作时间段 List workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls); //计算starttime处于那个工作时间段 var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault(); if (curPoint == null)//不处于工作时间段 { //开始时间小于当天工作开始时间 if (startTime < workPoints.First().StartPoint) { actStart = workPoints.First().StartPoint; } //开始时间大于当前工作结束时间 else if (startTime > workPoints.Last().EndPoint) { //获取下一个工作日开始时间 actStart = GetNextWorkDay(weekDay, startTime, curCalendars); } else {//开始时间位于当天的休息时间段 foreach (var item in workPoints) { //获取下一个时间段 var next = workPoints.First(p => p.Level == item.Level + 1); if (item.EndPoint < startTime && startTime < next.StartPoint) { actStart = next.StartPoint; break; } } } return actStart; } if (startTime != curPoint.EndPoint) { return actStart; } //查询下一时间段的开始时间点 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null) { return nextPoint.StartPoint; } //开始时间为今天下班时间,实际排产开始时间为下一个工作日的开始时间 actStart = GetNextWorkDay(weekDay, startTime, curCalendars); return actStart; } /// /// 排产开始时间处理为半小时/整点开始--向后取整 /// /// /// public DateTime CalcStartTimeAfter(DateTime startTime) { DateTime rtnTime = startTime; DateTime curDate = startTime.Date; //时间转换为分钟 TimeSpan span = rtnTime - curDate; decimal sumMinutes = (decimal)span.TotalMinutes; int times = (int)Math.Ceiling(sumMinutes / 30); rtnTime = curDate.AddMinutes(times * 30); return rtnTime; } /// /// 排产开始时间处理为半小时/整点开始--向前取整 /// /// /// public DateTime CalcStartTimeBefore(DateTime startTime) { DateTime rtnTime = startTime; DateTime curDate = startTime.Date; //时间转换为分钟 TimeSpan span = rtnTime - curDate; decimal sumMinutes = (decimal)span.TotalMinutes; int times = (int)Math.Floor(sumMinutes / 30); rtnTime = curDate.AddMinutes(times * 30); return rtnTime; } /// /// 判断当天是否是工作日 /// /// /// public bool CheckIsWorkDay(DateTime dateTime) { bool isWorkDay = true; //周几 int weekDay = (int)dateTime.DayOfWeek; //判断当天是否是工作日 if (weekDay == 0 || weekDay == 6)//周六或者周日,需要判断是否调班,需要加班 { if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "调班"))//不是调班 { isWorkDay = false; } return isWorkDay; } //不是周六周日,需要判断是不是节假日 if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "休假"))//是节假日 { isWorkDay = false; } return isWorkDay; } /// /// 计算子产线实际排产开始时间 /// /// 父级工单排产开始时间 /// 当前产线提前期(小时)-需要提前生产时长,不包括休息时间 /// 当前产线工作日历 /// 当前产线休息记录 /// public DateTime DealChildStartTime(DateTime startTime, decimal setupTime, List curCalendars, List curQtyDtls) { //提前期转换成分钟 decimal needMinute = setupTime * 60; //实际排产开始时间 DateTime actStart = startTime; //开始时间是周几 int weekDay = (int)startTime.DayOfWeek; //当天的工作日历 var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First(); //当前日期的工作时间段 List 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); //开始时间往前推到半小时/整点节点 actStart = CalcStartTimeBefore(actStart); return actStart; } //当前时间段的可用提前期不满足 //剩余提前期 needMinute -= curMins; //获取前层级时间段 var prePoints = workPoints.Where(p => p.Level < curPoint.Level).OrderByDescending(p => p.Level).ToList(); bool flag = true;//标志位 foreach (var item in prePoints) { if (item.WorkMinutes >= needMinute)//当前时间段的可用提前期满足 { actStart = item.EndPoint.AddMinutes((double)-needMinute); flag = false; break; } needMinute -= item.WorkMinutes; } if (!flag) { //开始时间往前推到半小时/整点节点 actStart = CalcStartTimeBefore(actStart); return actStart; } //今天可用提前期不够,往前工作日找 DateTime perStartTime = startTime; 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; } else { //当天可用提前期不满足 needMinute -= sumWorkMins; } } //开始时间往前推到半小时/整点节点 actStart = CalcStartTimeBefore(actStart); return actStart; } /// /// 获取下一个工作日开始时间 /// /// 当前周几 /// 开始时间 /// 当前产线的工作日历 /// public DateTime GetNextWorkDay(int weekDay, DateTime startTime, List curCalendars) { DateTime rtnData = startTime; //下一天 DateTime nextDate = startTime.Date.AddDays(1); //下一天是周几 int nextWeekDay = (weekDay + 1) % 7; var calendar = curCalendars.FirstOrDefault(p => p.WeekDay == nextWeekDay); string strStart = calendar.ShiftsStart1.ToString("0.00").Replace(".", ":"); //判断下一天是否是工作日 if (nextWeekDay == 0 || nextWeekDay == 6)//下一天是周六或者周日,需要判断是否调班,需要加班 { if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "调班"))//下一天是周末 { //递归继续找下一个工作日 rtnData = GetNextWorkDay(nextWeekDay, nextDate, curCalendars); return rtnData; } rtnData = Convert.ToDateTime(nextDate.ToString("yyyy-MM-dd") + " " + strStart); return rtnData; } //下一天不是周六周日,需要判断是不是节假日 if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "休假"))//是节假日 { //递归继续找下一个工作日 rtnData = GetNextWorkDay(nextWeekDay, nextDate, curCalendars); return rtnData; } rtnData = Convert.ToDateTime(nextDate.ToString("yyyy-MM-dd") + " " + strStart); return rtnData; } /// /// 获取上一个工作日开始时间 /// /// 开始时间 /// 当前产线的工作日历 /// public DateTime GetPreWorkDay(DateTime startTime, List curCalendars) { DateTime rtnData = startTime; //前一天 DateTime preDate = startTime.Date.AddDays(-1); //前一天是周几 int preWeekDay = (int)preDate.DayOfWeek; var calendar = curCalendars.FirstOrDefault(p => p.WeekDay == preWeekDay); string strStart = calendar.ShiftsStart1.ToString("0.00").Replace(".", ":"); //判断前一天是否是工作日 if (preWeekDay == 0 || preWeekDay == 6)//前一天是周六或者周日,需要判断是否调班,需要加班 { if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "调班"))//前一天是非工作日 { //递归继续找上一个工作日 rtnData = GetPreWorkDay(preDate, curCalendars); return rtnData; } rtnData = Convert.ToDateTime(preDate.ToString("yyyy-MM-dd") + " " + strStart); return rtnData; } //前一天不是周六周日,需要判断是不是节假日 if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "休假"))//是节假日 { //递归继续找前一个工作日 rtnData = GetPreWorkDay(preDate, curCalendars); return rtnData; } rtnData = Convert.ToDateTime(preDate.ToString("yyyy-MM-dd") + " " + strStart); return rtnData; } /// /// 处理当前日期的工作时间段 /// /// /// 当前产线的工作日历-周几 /// 每天休息记录 /// public List DealWorkDayToLevels(DateTime startTime, ShopCalendarWorkCtr shopCal, List curQtyDtls) { //年-月-日 string date = startTime.Date.ToString("yyyy-MM-dd"); //排产记录结束日期是周几 int weekDay = (int)startTime.DayOfWeek; //计算当天的开工时间点,停工时间点 string strStart = shopCal.ShiftsStart1.ToString("0.00").Replace(".", ":"); DateTime dayStartPoint = Convert.ToDateTime(date + " " + strStart); DateTime dayEndPoint = dayStartPoint.AddHours(Convert.ToDouble(shopCal.ShiftsHours1)); //工作时间段 List workPoints = new List(); LineWorkPointDto dto = new LineWorkPointDto(); dto.Level = 1; dto.Line = shopCal.ProdLine; 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(); dto.Level = level; dto.Line = shopCal.ProdLine; dto.WeekDay = weekDay; dto.StartPoint = endPoint.AddMinutes(item.RestTime); } dto.EndPoint = dayEndPoint; span = dto.EndPoint - dto.StartPoint; dto.WorkMinutes = (decimal)span.TotalMinutes; workPoints.Add(dto); return workPoints.OrderBy(p => p.Level).ToList(); } /// /// 工单工艺路线预处理 /// /// 工单 /// 当前工单对应的工序 /// 产线 /// 当前工单对应产品的排产记录 /// public List ProcPretreatment(WorkOrdMaster workOrd, List woRuntings, List prodLines, List schedules) { List routingDtos = new List(); //当前工单计划开始时间(默认加两天) DateTime planStart = workOrd.OrdDate.GetValueOrDefault().AddDays(2); //取主工序(第一层级工序) var firsts = woRuntings.Where(p => p.ParentOp == 0).OrderByDescending(p => p.OP).ToList(); if (firsts.Count == 0) { return routingDtos; } WorkOrdRoutingDto dto = new WorkOrdRoutingDto(); //主工序按照Op排序,取最大Op var lastOp = firsts.First(); dto.ParentOp = lastOp.ParentOp; dto.level = 1; dto.Op = lastOp.OP; dto.ChdParentOps = new List(); //获取当前层级工序中有子级的工序集合 var childs = woRuntings.Where(p => firsts.Select(m => m.OP).Contains(p.ParentOp)).Select(m => m.ParentOp).Distinct().ToList(); if (childs.Count > 0) { dto.ChdParentOps = childs; } //主工序对应的产线(目前只考虑一个产品对应一条产线的情况) 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); return routingDtos; } /// /// 递归处理工序 /// /// 工单工序 /// 上-层级工序 /// 层级 /// 返回结果 /// 产线 public void RecursionProc(List woRuntings, List preLevels, int level, List routingDtos, List prodLines) { //获取当前层级工序 var curLevels = woRuntings.Where(p => preLevels.Select(m => m.OP).Contains(p.ParentOp)).ToList(); if (curLevels.Count == 0) { return; } //获取父级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) { continue; } dto.Op = lastOp.OP; dto.ParentOp = lastOp.ParentOp; dto.level = level + 1; dto.ChdParentOps = new List(); //获取当前层级工序中有子级的工序集合 var childs = woRuntings.Where(p => curLevels.Where(p => p.ParentOp == item).Select(m => m.OP).Contains(p.ParentOp)).Select(m => m.ParentOp).Distinct().ToList(); if (childs.Count > 0) { dto.ChdParentOps = childs; } //当前层级工序对应的产线 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); } } }