using Business.Business.Dto; using Business.Core.Utilities; using Business.Domain; using Business.Dto; using Business.EntityFrameworkCore.SqlRepositories; using Business.ResourceExamineManagement.Dto; using Business.StructuredDB.Production; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Configuration; using System.Linq; using System.Threading.Tasks; using System.Transactions; using Volo.Abp.Application.Services; using Volo.Abp.MultiTenancy; using Microsoft.Extensions.Configuration; using MongoDB.Driver.Linq; using Amazon.Runtime.Internal.Util; using IdentityModel.Client; 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 _prodLineDetailRunCrew; /// /// 加班设置表 /// private ISqlRepository _resourceOccupancyTime; /// /// 加班设置表 /// private ISqlRepository _generalizedCodeMaster; /// /// 节假日记录表 /// private ISqlRepository _holidayMaster; /// /// 排产异常记录 /// private ISqlRepository _scheduleExceptionMaster; /// /// 雪花算法 /// SnowFlake help = new SnowFlake(); private readonly ICurrentTenant _currentTenant; /// /// 工作日历数据 /// private List calendars; /// /// 产线休息记录数据 /// private List qualityLines; /// /// 节假日记录数据 /// private List holidays; /// /// 生产线UPH设置 /// private List prodLineDetailRunCrews; /// /// 加班设置 /// private List resourceOccupancyTimes; /// /// 工厂id /// private string domain = ""; #endregion #region 构造函数 /// /// 构造函数 /// public ProductionScheduleAppService( ISqlRepository itemMaster, ISqlRepository workOrdMaster, ISqlRepository workOrdDetail, ISqlRepository workOrdRouting, ISqlRepository prodLineDetail, ISqlRepository prodLineDetailRunCrew, ISqlRepository resourceOccupancyTime, ISqlRepository periodSequenceDet, ISqlRepository scheduleResultOpMaster, ISqlRepository invMaster, ISqlRepository shopCalendarWorkCtr, ISqlRepository qualityLineWorkDetail, ISqlRepository holidayMaster, ISqlRepository generalizedCodeMaster, ICurrentTenant currentTenant, ISqlRepository scheduleExceptionMaster ) { _itemMaster = itemMaster; _workOrdMaster = workOrdMaster; _workOrdDetail = workOrdDetail; _workOrdRouting = workOrdRouting; _prodLineDetail = prodLineDetail; _prodLineDetailRunCrew = prodLineDetailRunCrew; _resourceOccupancyTime = resourceOccupancyTime; _periodSequenceDet = periodSequenceDet; _scheduleResultOpMaster = scheduleResultOpMaster; _invMaster = invMaster; _shopCalendarWorkCtr = shopCalendarWorkCtr; _qualityLineWorkDetail = qualityLineWorkDetail; _holidayMaster = holidayMaster; _generalizedCodeMaster = generalizedCodeMaster; _currentTenant = currentTenant; _scheduleExceptionMaster = scheduleExceptionMaster; } #endregion /// /// 生产排产-定时任务 /// public async void DoExt() { //定时任务排产:获取工厂id IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build(); domain = configuration.GetConnectionString("Factory_id"); //获取排产锁定期 var generalizedCodeMaster = _generalizedCodeMaster.Select(p => p.FldName == "SystemConfig" && p.Val == "WorkOrderLockPeriod" && p.Domain == domain && p.IsActive).FirstOrDefault(); decimal lockDays = generalizedCodeMaster != null ? generalizedCodeMaster.UDeci1 : 0; //获取需要排产的工单(获取四周的工单:正常工单+已审批通过的特殊工单) DateTime endDate = DateTime.Now.Date.AddWeeks(4).AddDays(1); DateTime startDate = DateTime.Now.Date; var workOrds = _workOrdMaster.Select(p => p.IsActive && p.Domain == domain && p.OrdDate < endDate && p.OrdDate >= startDate && string.IsNullOrEmpty(p.Status) && (string.IsNullOrEmpty(p.Typed) || (!string.IsNullOrEmpty(p.Typed) && p.BusinessID >0 ))).ToList(); await DoProductSchedule(workOrds, domain, 1); } /// /// 生产排产 /// /// 工单 /// 工单的工厂id /// 排产类型:1-自动排产;2-手动排产 /// public async Task DoProductSchedule(List workOrds, string factoryid,int type) { //记录工厂id domain = factoryid; if (workOrds.Count == 0)//没有工单需要排产 { return; } //工单排产的最早日期 DateTime earlist = DateTime.Now.Date.AddDays(1); //特殊工单指定时间开工,不需要计算产能,只需要计算时长即可 var tsWorkOrds = workOrds.Where(p => !string.IsNullOrEmpty(p.Typed)).ToList(); //正常工单 var zcWorkOrds = workOrds.Where(p => string.IsNullOrEmpty(p.Typed)).ToList(); //2、获取数据 //获取工单工艺路径数据 List workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.ParentOp == 0 && p.Domain == domain && p.Status != "C" && p.IsActive); //特殊工单的工艺路线 List tsWoRoutings = workOrdRoutings.Where(p => tsWorkOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList(); //获取物料对应的生产线信息:物料、工序对应的生产线 List prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == domain && p.IsActive); List lines = prodLines.Select(p => p.Line).ToList(); //获取非标准产线人员配置 prodLineDetailRunCrews = _prodLineDetailRunCrew.Select(x => prodLines.Select(p => p.RecID).Contains(x.ProdLineDetailRecID) && x.IsActive && x.Domain == domain).ToList(); //获取加班设置 resourceOccupancyTimes = _resourceOccupancyTime.Select(x => prodLines.Select(p => p.Line).Contains(x.Resource) && x.IsActive && x.Domain == domain && x.StartTime.Value > DateTime.Now).ToList(); //获取生产周期数据,TODO:需要过滤锁定期之外的工单 List dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate >= earlist && p.Domain == domain && p.IsActive); //获取当前日期往后的排产记录数据,TODO:需要过滤锁定期之外的工单 List dbSchedules = _scheduleResultOpMaster.Select(p => lines.Contains(p.Line) && p.WorkDate >= earlist && p.Domain == domain); //获取工作日历数据:产线的工作日历+默认的工作日历 calendars = _shopCalendarWorkCtr.Select(p => (lines.Contains(p.ProdLine) || string.IsNullOrEmpty(p.ProdLine)) && p.Domain == domain && p.IsActive); //获取产线休息记录数据 qualityLines = _qualityLineWorkDetail.Select(p => lines.Contains(p.ProdLine) && p.Domain == domain && p.IsActive); //获取节假日记录数据 holidays = _holidayMaster.Select(p => p.Domain == domain && p.IsActive && p.Dated >= earlist); //排产前校验 List exceptions = BeforeScheduleCheck(workOrds, workOrdRoutings, prodLines,type); if (exceptions.Any()) { using (TransactionScope scope = new TransactionScope()) { try { //记录排产异常数据 _scheduleExceptionMaster.Insert(exceptions); scope.Complete(); } catch (Exception ex) { new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductSchedule", "保存排产异常数据失败:" + ex.Message, _currentTenant.Id.ToString()); scope.Dispose(); } } return; } //3、排产 //生产周期 List periodSequenceDtls = new List(); //排产记录表 List scheduleMasters = new List(); //排产结果 List allResults = new List(); allResults.AddRange(dbSchedules); //记录特殊工单排产结果 List ypcWorkOrds = new List(); foreach (var item in zcWorkOrds) { //记录产线占用情况 allResults.AddRange(scheduleMasters); //当前工单工艺路线主产线的关键工序 var curRoutings = workOrdRoutings.Where(p => p.ItemNum == item.ItemNum && p.ParentOp == 0 && p.MilestoneOp).OrderBy(p=>p.OP).ToList(); //当前工单的产线明细 var curProdLines = prodLines.Where(p => p.Part == item.ItemNum).ToList(); //产线排产 LineSchedule(item, curRoutings, curProdLines, periodSequenceDtls, scheduleMasters, allResults, tsWorkOrds, ypcWorkOrds, tsWoRoutings); List scheduleList = scheduleMasters.Where(s => s.WorkOrd == item.WorkOrd).ToList(); if (scheduleList.Any()) { item.OrdDate = scheduleList.Min(s => s.WorkStartTime); item.DueDate = scheduleList.Max(s => s.WorkEndTime); } } //判断是否有特殊工单没有排产,如果有,则对剩余特殊工单排产 var notSchedules = tsWorkOrds.Where(p => !ypcWorkOrds.Contains(p.WorkOrd)).ToList(); List workDtos = new List(); foreach (var item in notSchedules) { int op = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First().OP; workDtos.Add(new WorkOrdMstDto { WorkOrd = item.WorkOrd, ItemNum = item.ItemNum, QtyOrded = item.QtyOrded, OrdDate = item.OrdDate.GetValueOrDefault(), ProdLine = item.ProdLine, LbrVar = item.LbrVar * 60, Worked = 0, QtyWorked = 0, Op = op }); } TsLineSchedule(workDtos, periodSequenceDtls, scheduleMasters); //处理特殊工单计划开始日期、计划结束日期 foreach (var item in tsWorkOrds) { List scheduleList = scheduleMasters.Where(s => s.WorkOrd == item.WorkOrd).ToList(); if (scheduleList.Any()) { item.OrdDate = scheduleList.Min(s => s.WorkStartTime); item.DueDate = scheduleList.Max(s => s.WorkEndTime); } } using (TransactionScope scope = new TransactionScope()) { try { //记录排产数据 _workOrdMaster.Update(workOrds); _periodSequenceDet.Insert(periodSequenceDtls); _scheduleResultOpMaster.Insert(scheduleMasters); scope.Complete(); } catch (Exception ex) { new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductSchedule", "保存排产数据失败:" + ex.Message, _currentTenant.Id.ToString()); scope.Dispose(); } } } /// /// 排产前校验 /// /// 待排产工单 /// 工单工艺路线 /// 产线明细 /// 排产类型:1-自动排产,2-手动排产 /// public List BeforeScheduleCheck(List workOrds,List workOrdRoutings, List prodLines,int type) { List exceptions = new List(); ScheduleExceptionMaster entity; foreach (var item in workOrds) { if (!string.IsNullOrEmpty(item.Typed))//特殊工单 { //只需要校验有没有维护工艺路线即可 var routings = workOrdRoutings.Where(p=>p.ItemNum == item.ItemNum).ToList(); if (!routings.Any()) { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:工单没有维护工艺路线,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); } continue; } var curRoutings = workOrdRoutings.Where(p => p.ItemNum == item.ItemNum && p.MilestoneOp).OrderBy(p=>p.OP).Select(p=>p.OP).ToList(); //判断当前工单主产线是否设置了关键工序 if (!curRoutings.Any())//当前工单没有维护关键工序 { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:工单的工艺路径没有维护关键工序,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); continue; } //获取当前工单物料对应的产线信息 var lineDetails = prodLines.Where(x => x.Part == item.ItemNum && curRoutings.Contains(x.Op)).ToList(); if (!lineDetails.Any()) { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:工单的关键工序没有维护产线数据,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); continue; } //校验关键工序是否维护了产线 List lines = new List(); foreach (var op in curRoutings) { var curLines = lineDetails.Where(p => p.Op == op).ToList(); if (!curLines.Any())//当前Op没有维护产线 { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:工单的关键工序["+ op + "]没有维护产线数据,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); } foreach (var line in curLines) { lines.Add(new LineStartDto { Line = line.Line, Op = op }); } } //校验一条产线是否维护了多个关键工序 List pdLines = lines.Select(p => p.Line).Distinct().ToList(); foreach (var line in pdLines) { var curLines = lines.Where(p => p.Line == line).ToList(); if (curLines.Count() > 1)//当前物料的多个关键工序对应一条产线 { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:工单的多个关键工序[" + string.Join("、",curLines.Select(p=>p.Op).ToList()) + "]对应同一条产线,请调整后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); } } //校验主产线关键工序对应的产线是否维护了工作日历 var curCalendars = calendars.Where(x => lineDetails.Select(p => p.Line).Contains(x.ProdLine) || string.IsNullOrEmpty(x.ProdLine)).ToList(); foreach (var rut in lineDetails) { //获取当前产线维护的工作日历 var lineCals = curCalendars.Where(p => p.ProdLine == rut.Line).ToList(); if (!lineCals.Any())//当前产线未维护工作日历 { lineCals = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine)).ToList(); if (!lineCals.Any() || lineCals.Select(p => p.WeekDay).Distinct().Count() != 7) { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:产线["+rut.Line+ "]没有维护工作日历且标准工作日历未维护完全,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); } } else if (lineCals.Select(p => p.WeekDay).Distinct().Count() != 7)//当前产线维护了工作日历,但是没有维护完全 { entity = new ScheduleExceptionMaster(); entity.Domain = item.Domain; entity.WorkOrd = item.WorkOrd; entity.ItemNum = item.ItemNum; entity.CreateTime = DateTime.Now; entity.Remark = "排产异常:产线[" + rut.Line + "]工作日历没有维护完整,请维护后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; exceptions.Add(entity); } } } return exceptions; } /// /// 排产 /// /// 工单 /// 当前工单的工艺路线的关键工序(有几个关键工序就有几条产线) /// 当前工单的产线明细 /// 生产周期 /// 排产结果 /// 产线占用记录 /// 特殊工单 /// 已排产特殊工单的工单编号 /// 特殊工单的工艺路线 public void LineSchedule(WorkOrdMaster workOrd, List workOrdRoutings, List prodLines,List periodsDet, List scheduleResults, List allResults,List tsWorkOrds,List ypcWorkOrds, List tsWoRoutings) { //生产周期 List curSequences = new List(); //排产明细 List curScheduleRsts = new List(); //记录上一产线排产开始时间 LineStartDto lineStart = new LineStartDto(); TimeSpan span = TimeSpan.Zero; //第一层级工序有几个关键工序,就有几条产线 for (int i = 0; i < workOrdRoutings.Count; i++) { //产线实际排产开始时间 if (i == 0)//第一条产线 { lineStart = DealStartTime(workOrd, workOrdRoutings[i].OP, prodLines, allResults); } else { //获取前一产线排产开始时间,通过提前期计算当前产线排产开始时间 lineStart = DealNextStartTime(workOrd, lineStart, workOrdRoutings[i].OP, prodLines, allResults); } //当前产线的工作日历 var mLCalendars = calendars.Where(p => p.ProdLine == lineStart.Line || string.IsNullOrEmpty(p.ProdLine)).ToList(); //当前产线的休息时间设置 var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == lineStart.Line).ToList(); //产线排产开始时间 DateTime workStartTime = lineStart.StartTime; //记录特殊工单:获取第一天是否有特殊工单 List fstWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.OrdDate).ToList(); List workDtos = new List(); foreach (var item in fstWOMasters) { int op = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First().OP; workDtos.Add(new WorkOrdMstDto{ WorkOrd = item.WorkOrd, ItemNum = item.ItemNum, QtyOrded = item.QtyOrded, LbrVar = item.LbrVar * 60, Worked = 0, QtyWorked = 0, Op = op }); } //记录排产开始第二天及以后安排的特殊工单 var secWOMasters = new List(); //特殊工单工作时长(分钟) decimal sumTsTimes = fstWOMasters.Sum(p => p.LbrVar) * 60; while (sumTsTimes > 0) //产线排产开始当天安排了特殊工单,则先安排特殊工单 { secWOMasters.Clear(); //获取当天的产能 LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls); //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); //如果当天的可用产能大于特殊工单生产时长,此时需要考虑当天是否安排了其他的特殊工单 if (dto.EffTime > sumTsTimes) { //获取特殊工单 secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.OrdDate).ToList(); //此时需要过滤掉第一天的工单 secWOMasters = secWOMasters.Where(p=> !workDtos.Select(m=>m.WorkOrd).Contains(p.WorkOrd)).ToList(); //记录新增待排产特殊工单 foreach (var item in secWOMasters) { int op = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First().OP; workDtos.Add(new WorkOrdMstDto { WorkOrd = item.WorkOrd, ItemNum = item.ItemNum, QtyOrded = item.QtyOrded, LbrVar = item.LbrVar * 60, Worked = 0, QtyWorked = 0, Op = op }); } //特殊工单待排产时长增加新增的特殊工单生产时长 sumTsTimes += secWOMasters.Sum(p => p.LbrVar) * 60; } if (dto.EffTime >= sumTsTimes)//当天的可用产能满足特殊工单生产时长 { DateTime beginTime = workStartTime; DateTime endTime = dto.EndTime; foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产,跳过 { continue; } //当前工单还需工作时长 decimal needTime = item.LbrVar - item.Worked; //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当天工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); //下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间 curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint)//结束时间位于工作区间结尾 { //获取后续生产时间段 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null)//存在后续工作区间 { endTime = nextPoint.StartPoint; } } beginTime = endTime; //工单排产完成,排产时长=工单工作时长 item.Worked = item.LbrVar; sumTsTimes -= needTime; } if (dto.EffTime == sumTsTimes)//当天产能完全占用 { sumTsTimes = 0;//排产完毕,特殊工单时长置0 //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); //特殊工单排产完成,占用了当天的全部产能,则需要判断下一个工作日是否存在特殊工单,如果存在,则需要继续排特殊工单 secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.OrdDate).ToList(); //此时需要过滤掉已排产的工单 secWOMasters = secWOMasters.Where(p => !workDtos.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList(); //记录新增待排产特殊工单 foreach (var item in secWOMasters) { int op = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First().OP; workDtos.Add(new WorkOrdMstDto { WorkOrd = item.WorkOrd, ItemNum = item.ItemNum, QtyOrded = item.QtyOrded, LbrVar = item.LbrVar * 60, Worked = 0, QtyWorked = 0, Op = op }); } //特殊工单待排产时长增加新增带排产特殊工单生产时长 sumTsTimes += secWOMasters.Sum(p => p.LbrVar) * 60; } else { sumTsTimes = 0;//排产完毕,特殊工单时长置0 //处理workStartTime workStartTime = endTime; var curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint) { var nextPoint = workPoints.Find(p => p.Level == curPoint.Level + 1); workStartTime = nextPoint.StartPoint; } } } else//当天的可用产能不满足特殊工单生产时长 { decimal residueTime = dto.EffTime;//当天产能剩余产能(分钟) DateTime beginTime = workStartTime;//排产开始时间 DateTime endTime = dto.EndTime;//排产结束时间 foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产 { continue; } //当前工单剩余待排产时长(分钟) decimal needTime = item.LbrVar - item.Worked; if (residueTime >= needTime)//当天剩余产能满足当前工单的剩余待排产时长 { //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当前工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); beginTime = endTime; //当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量 item.Worked = item.LbrVar; item.QtyWorked = item.QtyWorked; //当天剩余产能 residueTime -= needTime; if (residueTime == 0) { break; } } else//当天剩余产能不满足当前工单的剩余待排产时长 { //计算生产数量 decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = qty, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); item.Worked += residueTime; item.QtyWorked += qty; residueTime = 0; break; } } //特殊工单剩余待排产时长(分钟) sumTsTimes -= dto.EffTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); } } //记录已排产特殊工单 ypcWorkOrds.AddRange(workDtos.Select(p => p.WorkOrd).ToList()); //正常工单排产 //正常工单产线已排产数量 decimal sumQty = 0m; //产线准备时间(分钟) decimal sumTimes = 0m; fstWOMasters.Clear(); workDtos.Clear(); sumTsTimes = 0m; //正常工单排产第一天标识 bool isFstDay = true; while (sumQty < workOrd.QtyOrded || sumTsTimes > 0) { secWOMasters.Clear(); //获取当天的产能 LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls); //正常工单排产第一天没有特殊工单,第一天之后才需要考虑是否有特殊工单 if (!isFstDay) { //获取特殊工单 secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.OrdDate).ToList(); foreach (var item in secWOMasters) { int op = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First().OP; workDtos.Add(new WorkOrdMstDto { WorkOrd = item.WorkOrd, ItemNum = item.ItemNum, QtyOrded = item.QtyOrded, LbrVar = item.LbrVar * 60, Worked = 0, QtyWorked = 0, Op = op }); } //记录特殊工单剩余生产时长 sumTsTimes += secWOMasters.Sum(p => p.LbrVar) * 60; } //排产开始时,需要先减去产线准备时间 if (sumTimes < lineStart.setupTime * 60) { //当天的可用生产时长能满足提前期 if (dto.EffTime > lineStart.setupTime * 60 - sumTimes) { //当天剩余时长(分钟) decimal residueTime = dto.EffTime - lineStart.setupTime * 60 - sumTimes; //产线提前期安排完之后,需要考虑当前是否存在特殊工单: //1、如果当天是排产的最后一天,则先排完正常工单,再安排特殊工单; //2、如果是排产中间日期,则先安排特殊工单排产 //当天剩余产能 decimal sumAmount = dto.ProductQty - Math.Floor(dto.Rate * (lineStart.setupTime - sumTimes / 60)); //判断已排产数量+当天的产能是否超过工单数量 if (sumQty + sumAmount <= workOrd.QtyOrded)//当天的产能需要全部排产 { if (sumTsTimes > 0)//当天有特殊工单,先排特殊工单 { DateTime beginTime = workStartTime;//排产开始时间 DateTime endTime = dto.EndTime;//排产结束时间 //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); if (residueTime > sumTsTimes)//当天的可用产能满足特殊工单生产时长 { //特殊工单排产 foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产,跳过 { continue; } //当前工单还需工作时长 decimal needTime = item.LbrVar - item.Worked; //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当天工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); //下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间 curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint)//结束时间位于工作区间结尾 { //获取后续生产时间段 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null)//存在后续工作区间 { endTime = nextPoint.StartPoint; } } beginTime = endTime; //工单排产完成,排产时长=工单工作时长 item.Worked = item.LbrVar; item.QtyWorked = item.QtyOrded; sumTsTimes -= needTime; } //处理正常工单开工时间 workStartTime = endTime; var point = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == point.EndPoint) { var nextPoint = workPoints.Find(p => p.Level == point.Level + 1); workStartTime = nextPoint.StartPoint; } //剩余产能继续排正常工单 sumAmount = dto.ProductQty - Math.Floor(dto.Rate * (lineStart.setupTime - sumTimes / 60 - sumTsTimes / 60)); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = sumAmount, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.Op, WorkDate = workStartTime.Date, WorkQty = sumAmount, WorkStartTime = workStartTime, WorkEndTime = dto.EndTime, CreateTime = DateTime.Now }); //累计已排产数量 sumQty += sumAmount; //继续排下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; //排产完毕,特殊工单时长置0 sumTsTimes = 0; } else//当天的可用产能不满足或者刚好满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产 { continue; } //当前工单剩余待排产时长(分钟) decimal needTime = item.LbrVar - item.Worked; if (residueTime >= needTime)//当天剩余产能满足当前工单的剩余待排产时长 { //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当前工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); beginTime = endTime; //当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量 item.Worked = item.LbrVar; item.QtyWorked = item.QtyWorked; //当天剩余产能 residueTime -= needTime; if (residueTime == 0) { break; } } else//当天剩余产能不满足当前工单的剩余待排产时长 { //计算生产数量 decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = qty, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); item.Worked += residueTime; item.QtyWorked += qty; break; } } //特殊工单剩余待排产时长(分钟) sumTsTimes -= residueTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; } } else { //当天没有特殊工单 #region 正常工单排产-开始 //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = sumAmount, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.Op, WorkDate = workStartTime.Date, WorkQty = sumAmount, WorkStartTime = dto.StartTime, WorkEndTime = dto.EndTime, CreateTime = DateTime.Now }); //累计已排产数量 sumQty += sumAmount; //继续排下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; #endregion 正常工单排产-结束 } } else// 当天的产能正常工单只能占用一部分 { #region 正常工单排产-开始 //剩余需要排产的数量 decimal residueQty = workOrd.QtyOrded - sumQty; //剩余数量生产需要时长(分钟) decimal workTime = Math.Ceiling(residueQty / dto.Rate * 60); //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); 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 = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = residueQty, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.Op, WorkDate = workStartTime.Date, WorkQty = residueQty, WorkStartTime = workStartTime, WorkEndTime = workEndTime, CreateTime = DateTime.Now }); #endregion 正常工单排产-结束 #region 特殊工单排产-开始 //当天剩余产能 decimal restTime = residueTime - workTime; if (sumTsTimes > 0) { DateTime beginTime = workEndTime;//排产开始时间 DateTime endTime = dto.EndTime;//排产结束时间 if (restTime >= sumTsTimes)//当天的可用产能满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产,跳过 { continue; } //当前工单还需工作时长 decimal needTime = item.LbrVar - item.Worked; //计算工单排产结束时间 curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当天工作时间段的有效生产时间(分钟) effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); //下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间 curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint)//结束时间位于工作区间结尾 { //获取后续生产时间段 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null)//存在后续工作区间 { endTime = nextPoint.StartPoint; } } beginTime = endTime; //工单排产完成,排产时长=工单工作时长 item.Worked = item.LbrVar; item.QtyWorked = item.QtyOrded; sumTsTimes -= needTime; } sumTsTimes = 0;//排产完毕,特殊工单时长置0 } else//当天的可用产能不满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产 { continue; } //当前工单剩余待排产时长(分钟) decimal needTime = item.LbrVar - item.Worked; if (restTime >= needTime)//当天剩余产能满足当前工单的剩余待排产时长 { //计算工单排产结束时间 curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当前工作时间段的有效生产时间(分钟) effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); beginTime = endTime; //当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量 item.Worked = item.LbrVar; item.QtyWorked = item.QtyWorked; //当天剩余产能 restTime -= needTime; if (restTime == 0) { break; } } else//当天剩余产能不满足当前工单的剩余待排产时长 { //计算生产数量 decimal qty = Math.Ceiling(restTime / item.LbrVar * item.QtyOrded); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = qty, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); item.Worked += restTime; item.QtyWorked += qty; break; } } //特殊工单剩余待排产时长(分钟) sumTsTimes -= restTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; } } #endregion 特殊工单排产-结束 } //记录提前期 sumTimes = lineStart.setupTime * 60; } else//当天可用生产时长不能满足或者刚好满足提前期 { //当天的可用生产时长不能满足提前期 sumTimes += dto.EffTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; } } else { //判断已排产数量+当天的产能是否超过工单数量 if (sumQty + dto.ProductQty <= workOrd.QtyOrded)//当天的产能需要全部排产 { if (sumTsTimes > 0)//当天有特殊工单,先排特殊工单 { //当天剩余产能(分钟) decimal residueTime = dto.EffTime; DateTime beginTime = workStartTime;//排产开始时间 DateTime endTime = dto.EndTime;//排产结束时间 //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); if (residueTime > sumTsTimes)//当天的可用产能满足特殊工单生产时长 { //特殊工单排产 foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产,跳过 { continue; } //当前工单还需工作时长 decimal needTime = item.LbrVar - item.Worked; //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当天工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); //下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间 curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint)//结束时间位于工作区间结尾 { //获取后续生产时间段 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null)//存在后续工作区间 { endTime = nextPoint.StartPoint; } } beginTime = endTime; //工单排产完成,排产时长=工单工作时长 item.Worked = item.LbrVar; item.QtyWorked = item.QtyOrded; sumTsTimes -= needTime; } //处理正常工单开工时间 workStartTime = endTime; var point = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == point.EndPoint) { var nextPoint = workPoints.Find(p => p.Level == point.Level + 1); workStartTime = nextPoint.StartPoint; } //剩余产能继续排正常工单 decimal sumAmount = dto.ProductQty - Math.Floor(dto.Rate * (dto.EffTime - sumTsTimes) / 60); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = sumAmount, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.Op, WorkDate = workStartTime.Date, WorkQty = sumAmount, WorkStartTime = workStartTime, WorkEndTime = dto.EndTime, CreateTime = DateTime.Now }); //累计已排产数量 sumQty += sumAmount; //继续排下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; //排产完毕,特殊工单时长置0 sumTsTimes = 0; } else//当天的可用产能不满足或者刚好满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产 { continue; } //当前工单剩余待排产时长(分钟) decimal needTime = item.LbrVar - item.Worked; if (residueTime >= needTime)//当天剩余产能满足当前工单的剩余待排产时长 { //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当前工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); beginTime = endTime; //当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量 item.Worked = item.LbrVar; item.QtyWorked = item.QtyWorked; //当天剩余产能 residueTime -= needTime; if (residueTime == 0) { break; } } else//当天剩余产能不满足当前工单的剩余待排产时长 { //计算生产数量 decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = qty, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); item.Worked += residueTime; item.QtyWorked += qty; break; } } //特殊工单剩余待排产时长(分钟) sumTsTimes -= residueTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; } } else {//不存在特殊工单 //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = dto.ProductQty, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.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); isFstDay = false; } } else// 当天的产能正常工单只能占用一部分 { decimal workTime = 0m; DateTime workEndTime = workStartTime; //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); if (sumQty < workOrd.QtyOrded) { #region 正常工单排产-开始 //剩余需要排产的数量 decimal residueQty = workOrd.QtyOrded - sumQty; //剩余数量生产需要时长(分钟) workTime = Math.Ceiling(residueQty / dto.Rate * 60); var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); span = curPoint.EndPoint - workStartTime; //当天工作时间段的有效生产时间 decimal effMins = (decimal)span.TotalMinutes; 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 = domain, Line = lineStart.Line, ItemNum = workOrd.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = residueQty, WorkOrds = workOrd.WorkOrd, Op = lineStart.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = workOrd.WorkOrd, Line = lineStart.Line, ItemNum = workOrd.ItemNum, Op = lineStart.Op, WorkDate = workStartTime.Date, WorkQty = residueQty, WorkStartTime = workStartTime, WorkEndTime = workEndTime, CreateTime = DateTime.Now }); #endregion 正常工单排产-结束 } #region 特殊工单排产-开始 //当天剩余产能(分钟) decimal restTime = dto.EffTime - workTime; if (sumTsTimes > 0)//存在特殊工单 { DateTime beginTime = workEndTime;//排产开始时间 DateTime endTime = dto.EndTime;//排产结束时间 if (restTime >= sumTsTimes)//当天的可用产能满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产,跳过 { continue; } //当前工单还需工作时长 decimal needTime = item.LbrVar - item.Worked; //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当天工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); //下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间 curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (endTime == curPoint.EndPoint)//结束时间位于工作区间结尾 { //获取后续生产时间段 var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault(); if (nextPoint != null)//存在后续工作区间 { endTime = nextPoint.StartPoint; } } beginTime = endTime; //工单排产完成,排产时长=工单工作时长 item.Worked = item.LbrVar; item.QtyWorked = item.QtyOrded; sumTsTimes -= needTime; } sumTsTimes = 0;//排产完毕,特殊工单时长置0 } else//当天的可用产能不满足特殊工单生产时长 { foreach (var item in workDtos) { if (item.LbrVar == item.Worked)//当前工单已排产 { continue; } //当前工单剩余待排产时长(分钟) decimal needTime = item.LbrVar - item.Worked; if (restTime >= needTime)//当天剩余产能满足当前工单的剩余待排产时长 { //计算工单排产结束时间 var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint); span = curPoint.EndPoint - beginTime; //当前工作时间段的有效生产时间(分钟) decimal effMins = (decimal)span.TotalMinutes; if (effMins >= needTime)//当前工作时间段即可满足产能 { endTime = beginTime.AddMinutes((double)needTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); //剩余需要工作时长 decimal nextMins = needTime - effMins; foreach (var p in nextPoints) { if (p.WorkMinutes >= nextMins) { endTime = p.StartPoint.AddMinutes((double)nextMins); break; } nextMins -= p.WorkMinutes; } } //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); beginTime = endTime; //当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量 item.Worked = item.LbrVar; item.QtyWorked = item.QtyWorked; //当天剩余产能 restTime -= needTime; if (restTime == 0) { break; } } else//当天剩余产能不满足当前工单的剩余待排产时长 { //计算生产数量 decimal qty = Math.Ceiling(restTime / item.LbrVar * item.QtyOrded); //记录生产周期 curSequences.Add(new PeriodSequenceDet { Domain = domain, Line = lineStart.Line, ItemNum = item.ItemNum, PlanDate = beginTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 curScheduleRsts.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = lineStart.Line, ItemNum = item.ItemNum, Op = item.Op, WorkDate = beginTime.Date, WorkQty = qty, WorkStartTime = beginTime, WorkEndTime = endTime, CreateTime = DateTime.Now }); item.Worked += restTime; item.QtyWorked += qty; break; } } //特殊工单剩余待排产时长(分钟) sumTsTimes -= restTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); isFstDay = false; } } #endregion 特殊工单排产-结束 } } } //记录已排产特殊工单 ypcWorkOrds.AddRange(workDtos.Select(p=>p.WorkOrd).ToList()); } //记录排产结果 periodsDet.AddRange(curSequences); scheduleResults.AddRange(curScheduleRsts); } /// /// 特殊工单排产:和正常工单不共用产线的特殊工单 /// /// 特殊工单 /// 生产周期 /// 排产结果 public void TsLineSchedule(List workOrds, List periodsDet, List scheduleResults) { foreach (var item in workOrds) { //工单的生产时长(分钟) decimal sumTimes = item.LbrVar; //工单的排产开始时间 DateTime workStartTime = item.OrdDate; //当前产线的工作日历 var mLCalendars = calendars.Where(p => p.ProdLine == item.ProdLine || string.IsNullOrEmpty(p.ProdLine)).ToList(); //当前产线的休息时间设置 var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == item.ProdLine).ToList(); LineStartDto lineStart = new LineStartDto(); lineStart.Line = item.ProdLine; lineStart.Op = item.Op; lineStart.StartTime = workStartTime; lineStart.Rate = 0; while (sumTimes > 0) { //获取当天的工作时间段 var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); if (curCalendar == null) { curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls); //获取当天的产能 LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls); //当天的可用生产时长不能满足特殊工单生产时长 if (dto.EffTime <= sumTimes) { decimal qty = Math.Ceiling(dto.EffTime / item.LbrVar * item.QtyOrded); //记录生产周期 periodsDet.Add(new PeriodSequenceDet { Domain = domain, Line = item.ProdLine, ItemNum = item.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = qty, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 scheduleResults.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = item.ProdLine, ItemNum = item.ItemNum, Op = item.Op, WorkDate = workStartTime.Date, WorkQty = qty, WorkStartTime = dto.StartTime, WorkEndTime = dto.EndTime, CreateTime = DateTime.Now }); //剩余待排产时长 sumTimes -= dto.EffTime; //已排产数量 item.QtyWorked += qty; //继续排下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); } else// 最后一天的产能只能占用一部分 { //剩余生产时长(分钟) decimal workTime = sumTimes; var curPoint = workPoints.Find(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; } } //记录生产周期 periodsDet.Add(new PeriodSequenceDet { Domain = domain, Line = item.ProdLine, ItemNum = item.ItemNum, PlanDate = workStartTime.Date, Period = dto.Period, OrdQty = item.QtyOrded - item.QtyWorked, WorkOrds = item.WorkOrd, Op = item.Op, IsActive = true, Status = "", CreateTime = DateTime.Now }); //记录排产记录 scheduleResults.Add(new ScheduleResultOpMaster { Domain = domain, WorkOrd = item.WorkOrd, Line = item.ProdLine, ItemNum = item.ItemNum, Op = item.Op, WorkDate = workStartTime.Date, WorkQty = item.QtyOrded - item.QtyWorked, WorkStartTime = workStartTime, WorkEndTime = workEndTime, CreateTime = DateTime.Now }); sumTimes = 0; item.QtyWorked = item.QtyOrded; } } } } /// /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量 /// /// 排产产线 /// 产线排产开始时间 /// /// /// public LineScheduledDto GetScheduledPoint(LineStartDto lineStart, DateTime startTime, List curCalendars, List curQtyDtls) { LineScheduledDto scheduledDto = new LineScheduledDto(); //当天排产开始时间 scheduledDto.StartTime = startTime; //开始时间是周几 int weekDay = (int)startTime.DayOfWeek; //当天的工作日历 var shopCal = curCalendars.Where(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); if (shopCal == null)//当前产线当天没有设置工作日历 { //取默认工作日历 shopCal = curCalendars.Where(p =>string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); } //当前日期的工作时间段 List workPoints = DealWorkDayToLevels(lineStart.Line, 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.TotalMinutes; //获取后续工作时间段的有效工作时间 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList(); foreach (var item in nextPoints) { span = item.EndPoint - item.StartPoint; scheduledDto.EffTime += (decimal)span.TotalMinutes; } //判断当前产线的UPH var curRunCrews = prodLineDetailRunCrews.Where(p=>p.ProdLineDetailRecID == lineStart.RecID).ToList(); //判断当前日期是否配置了UPH var curLevel = curRunCrews.FirstOrDefault(p => p.StartDate.GetValueOrDefault().Date <= startTime.Date && p.EndDate.GetValueOrDefault().Date >= startTime.Date); decimal rate = curLevel == null ? lineStart.Rate : curLevel.Rate; scheduledDto.Rate = rate; //计算当天的产能(向下取整) scheduledDto.ProductQty = Math.Floor(scheduledDto.EffTime / 60 * rate); //计算班次 scheduledDto.Period = 1;//默认一班制 if (shopCal.ShiftsStart2 != 0 && shopCal.ShiftsHours2 != 0) { scheduledDto.Period = 2; } return scheduledDto; } /// /// 计算主产线实际排产开始时间 /// /// 工单 /// 工序 /// 生产线明细 /// 产线占用记录 /// public LineStartDto DealStartTime(WorkOrdMaster workOrd,int op,List prodLines, List allResults) { DateTime actStart = DateTime.Now.Date.AddDays(1); LineStartDto lineStart = new LineStartDto(); //获取工序对应的产线,根据优先级排序 var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p => p.Sequence).ToList(); //获取第一条产线排产结束时间 var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault(); actStart = schedule == null ? actStart : (schedule.WorkEndTime >= actStart ? schedule.WorkEndTime : actStart); //计算实际开工时间 //产线工作日历:当前产线的工作日历+默认工作日历 var curCalendars = calendars.Where(p => p.ProdLine == lines[0].Line || string.IsNullOrEmpty(p.ProdLine)).ToList(); var curQtyDtls = qualityLines.Where(p=>p.ProdLine == lines[0].Line).ToList(); actStart = CalcActStartTime(lines[0].Line, actStart, curCalendars, curQtyDtls); lineStart.RecID = lines[0].RecID; lineStart.Line = lines[0].Line; lineStart.StartTime = actStart; lineStart.setupTime = lines[0].SetupTime; lineStart.Rate = lines[0].Rate; lineStart.Op = op; //循环其他产线 for (int i = 1; i < lines.Count; i++) { schedule = allResults.Where(p => p.Line == lines[i].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault(); DateTime StartTime = schedule == null ? actStart : (schedule.WorkEndTime >= actStart ? schedule.WorkEndTime : actStart); //计算实际开工时间 //产线工作日历:当前产线的工作日历+默认工作日历 curCalendars = calendars.Where(p => p.ProdLine == lines[i].Line || string.IsNullOrEmpty(p.ProdLine)).ToList(); curQtyDtls = qualityLines.Where(p => p.ProdLine == lines[i].Line).ToList(); StartTime = CalcActStartTime(lines[0].Line, StartTime, curCalendars, curQtyDtls); if (StartTime < lineStart.StartTime) { lineStart.RecID = lines[i].RecID; lineStart.Line = lines[i].Line; lineStart.StartTime = StartTime; lineStart.setupTime = lines[i].SetupTime; lineStart.Rate = lines[i].Rate; lineStart.Op = op; } } return lineStart; } /// /// 计算主产线实际排产开始时间 /// /// 生产线 /// 开始时间 /// 当前产线工作日历+默认工作日历 /// 当前产线休息记录 /// public DateTime CalcActStartTime(string prodLine,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; } //当天的工作日历 var shopCal = curCalendars.Where(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); if (shopCal == null) { //产线没有维护当天的工作日历,则取默认工作日历 shopCal = curCalendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); } //当前日期的工作时间段 List workPoints = DealWorkDayToLevels(prodLine, 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 LineStartDto DealNextStartTime(WorkOrdMaster workOrd,LineStartDto lineStart,int op, List prodLines, List allResults) { LineStartDto startDto = new LineStartDto(); //获取产线 var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p=>p.Sequence).ToList(); //获取第一条产线排产结束时间 var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault(); DateTime startTime = CalcStartTimeWithSetUpTime(lines[0], lineStart.StartTime, lineStart.setupTime + lines[0].OverlapTime); startDto.RecID = lines[0].RecID; startDto.Line = lines[0].Line; startDto.setupTime = lines[0].SetupTime; startDto.StartTime = schedule == null ? startTime : (startTime < schedule.WorkEndTime ? schedule.WorkEndTime : startTime); startDto.Rate = lines[0].Rate; startDto.Op = op; //循环剩余产线,找到最早可开工产线 for (int i = 1; i < lines.Count(); i++) { schedule = allResults.Where(p => p.Line == lines[i].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault(); startTime = CalcStartTimeWithSetUpTime(lines[i], lineStart.StartTime, lineStart.setupTime + lines[i].OverlapTime); startTime = schedule == null ? startTime : (startTime < schedule.WorkEndTime ? schedule.WorkEndTime : startTime); if (startTime < startDto.StartTime) { startDto.RecID = lines[i].RecID; startDto.Line = lines[i].Line; startDto.setupTime = lines[i].SetupTime; startDto.StartTime = startTime; startDto.Rate = lines[0].Rate; startDto.Op = op; } } return startDto; } /// /// 通过提前期计算产线排产开始时间 /// /// 产线 /// 上一产线开始时间 /// 上一产线准备时间+当前产线提前期 /// public DateTime CalcStartTimeWithSetUpTime(ProdLineDetail line, DateTime startTime, decimal setupTime) { //提前期转换成分钟 decimal needMinute = setupTime * 60; //实际排产开始时间 DateTime actStart = startTime; //开始时间是周几 int weekDay = (int)startTime.DayOfWeek; //当天的工作日历 var shopCal = calendars.Where(p => p.ProdLine == line.Line && p.WeekDay == weekDay).FirstOrDefault(); if (shopCal == null) { //产线没有维护当天的工作日历,则取默认工作日历 shopCal = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); } //当前产线的休息时间设置 var curQtyDtls = qualityLines.Where(p => p.ProdLine == line.Line).ToList(); //当前日期的工作时间段 List workPoints = DealWorkDayToLevels(line.Line, startTime, shopCal, curQtyDtls); //计算starttime处于那个工作时间段 var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault(); //当前时间段可用时长 TimeSpan span = curPoint.EndPoint - startTime; 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).OrderBy(p => p.Level).ToList(); bool flag = true;//标志位 foreach (var item in prePoints) { if (item.WorkMinutes >= needMinute)//当前时间段的可用时长满足 { actStart = item.StartPoint.AddMinutes((double)needMinute); flag = false; break; } needMinute -= item.WorkMinutes; } if (!flag) { return actStart; } //今天可用时长不够,往后工作日找 DateTime nextStartTime = startTime; while (flag) { //获取下一个工作日 nextStartTime = GetNextWorkDay(weekDay, startTime, calendars.Where(p => p.ProdLine == line.Line || string.IsNullOrEmpty(p.ProdLine)).ToList()); weekDay = (int)nextStartTime.DayOfWeek; //获取前一个工作日的工作时间段数据,level顺排 var curCalendar = calendars.Where(p => p.ProdLine == line.Line && p.WeekDay == weekDay).FirstOrDefault(); if (curCalendar == null ) { curCalendar = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault(); } workPoints = DealWorkDayToLevels(line.Line, nextStartTime, curCalendar, curQtyDtls).OrderBy(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.StartPoint.AddMinutes((double)needMinute); break; } needMinute -= item.WorkMinutes; } flag = false; } else { //当天可用提前期不满足 needMinute -= sumWorkMins; } } 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 =>!string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == nextWeekDay); if (calendar == null) { calendar = curCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && 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 List DealWorkDayToLevels(string prodLine, DateTime startTime, ShopCalendarWorkCtr shopCal, List curQtyDtls) { //年-月-日 string date = startTime.Date.ToString("yyyy-MM-dd"); //排产记录结束日期是周几 int weekDay = (int)startTime.DayOfWeek; //工作时间段 List workPoints = new List(); LineWorkPointDto dto = new LineWorkPointDto(); int level = 1; TimeSpan span = TimeSpan.Zero; //开始时间 string strStart = shopCal.ShiftsStart1.ToString("0.00").Replace(".", ":"); DateTime dayStartPoint = Convert.ToDateTime(date + " " + strStart); //结束时间 DateTime dayEndPoint = dayStartPoint.AddHours((double)(shopCal.ShiftsHours1 + shopCal.ShiftsHours2)); dto.Level = level; dto.Line = prodLine; dto.WeekDay = weekDay; dto.StartPoint = dayStartPoint; //按照产线休息时间切分时间段 curQtyDtls = curQtyDtls.OrderBy(p => p.RestTimePoint).ToList(); 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 = 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); //产线当天加班设置 var curOccupyTimes = resourceOccupancyTimes.Where(p => p.Resource == prodLine && p.StartTime.GetValueOrDefault().Date == startTime.Date).OrderBy(p=>p.StartTime).ToList(); foreach (var item in curOccupyTimes) { level++; dto = new LineWorkPointDto(); dto.Level = level; dto.Line = prodLine; dto.WeekDay = weekDay; dto.StartPoint = item.StartTime.GetValueOrDefault(); dto.EndPoint = dto.StartPoint.AddHours(Convert.ToDouble(item.Ufld1)); dto.WorkMinutes = Convert.ToDecimal(item.Ufld1) * 60; workPoints.Add(dto); } return workPoints.OrderBy(p => p.Level).ToList(); } } }