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(x => x.FldName == "SystemConfig" && x.Val == "WorkOrderLockPeriod" && x.Domain == domain).FirstOrDefault(); decimal Udecil = 0; if (generalizedCodeMaster != null) { Udecil = generalizedCodeMaster.UDeci1; } //排产取4周工单排产 DateTime dateTime = DateTime.Now.AddDays(30); DateTime date = DateTime.Now.AddDays((double)Udecil); var workOrds = _workOrdMaster.Select(x => x.IsActive && x.Domain == domain && x.OrdDate < dateTime && x.OrdDate > date && x.Status == "初始").ToList(); await DoProductSchedule(workOrds, domain, 1); } /// /// 生产排产 /// /// 工单:定时任务执行时count=0;资源检查调用count>0 /// 工单的工厂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); //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 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(); //获取生产周期数据 List dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate >= earlist && p.Domain == domain && p.IsActive); //获取当前日期往后的排产记录数据 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); foreach (var item in workOrds) { //记录产线占用情况 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); 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); } } 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) { 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()) { 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 curSequences = new List(); //排产明细 List curScheduleRsts = new List(); //记录上一产线排产开始时间 LineStartDto lineStart = new LineStartDto(); //第一层级工序有几个关键工序,就有几条产线 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(); //产线已排产数量 decimal sumQty = 0m; //产线准备时间 decimal sumTimes = 0m; //产线排产开始时间 DateTime workStartTime = lineStart.StartTime; //排产 while (sumQty < workOrd.QtyOrded) { //获取当天的产能 LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls); //排产开始时,需要先减去产线准备时间 if (sumTimes < lineStart.setupTime) { //判断当天的可用生产时长能满足提前期 if (dto.EffTime >= lineStart.setupTime - sumTimes) { //当天剩余产能 decimal sumAmount = dto.ProductQty - Math.Floor(dto.Rate * (lineStart.setupTime - sumTimes)); //判断已排产数量+当天的产能是否超过工单数量 if (sumQty + sumAmount <= workOrd.QtyOrded)//当天的产能需要全部排产 { //记录生产周期 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); } else// 最后一天的产能只能占用一部分 { //剩余需要排产的数量 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(workStartTime, curCalendar, mlqtyWorkDtls); var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); if (curPoint != null) { 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 = 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 }); } } sumTimes = lineStart.setupTime; } else { //当天的可用生产时长不能满足提前期 sumTimes += dto.EffTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); } } else { //判断已排产数量+当天的产能是否超过工单数量 if (sumQty + dto.ProductQty <= workOrd.QtyOrded)//当天的产能需要全部排产 { //记录生产周期 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); } else// 最后一天的产能只能占用一部分 { //剩余需要排产的数量 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(workStartTime, curCalendar, mlqtyWorkDtls); var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); if (curPoint != null) { 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 = 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 }); } } } } } //记录排产结果 periodsDet.AddRange(curSequences); scheduleResults.AddRange(curScheduleRsts); } /// /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量 /// /// 排产产线 /// 产线排产开始时间 /// /// /// 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(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; } //判断产线当天有没有加班 var curOccupyTimes = resourceOccupancyTimes.Where(p=> p.Resource == lineStart.Line && p.StartTime.GetValueOrDefault().Date == startTime.Date).ToList(); scheduledDto.EffTime += curOccupyTimes.Sum(p=> Convert.ToDecimal(p.Ufld1)); //判断当前产线的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 * 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(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(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(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(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(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(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(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 = shopCal.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 = 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(); } } }