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; using Amazon.Runtime; using Magicodes.ExporterAndImporter.Core.Extension; using Volo.Abp.Domain.Repositories; using System.Runtime.CompilerServices; using NUglify.JavaScript; using MathNet.Numerics.Optimization.LineSearch; using MathNet.Numerics; using Volo.Abp.Options; 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; /// /// 领料单 /// private ISqlRepository _nbrMaster; /// /// 领料单明细 /// private ISqlRepository _nbrDetail; /// /// 工单工序开始准备时间记录 /// private ISqlRepository _workOrdSetupTimeRecord; /// /// 工单工序开始准备时间记录 /// private List timeRecords; /// /// 新增工序准备开始时间记录 /// private List inserts = new List(); /// /// 更新工序准备开始时间记录 /// private List updates = new List(); /// /// 生产工单主表 /// private readonly IRepository _mysql_mes_morder; /// /// 工厂id /// private string domain = ""; /// /// 排产任务执行时间 /// private DateTime scheTime; #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, ISqlRepository nbrMaster, IRepository mysql_mes_morder, ISqlRepository workOrdSetupTimeRecord, ISqlRepository nbrDetail ) { _itemMaster = itemMaster; _workOrdMaster = workOrdMaster; _workOrdDetail = workOrdDetail; _workOrdRouting = workOrdRouting; _nbrMaster = nbrMaster; _prodLineDetail = prodLineDetail; _prodLineDetailRunCrew = prodLineDetailRunCrew; _resourceOccupancyTime = resourceOccupancyTime; _periodSequenceDet = periodSequenceDet; _scheduleResultOpMaster = scheduleResultOpMaster; _invMaster = invMaster; _shopCalendarWorkCtr = shopCalendarWorkCtr; _qualityLineWorkDetail = qualityLineWorkDetail; _holidayMaster = holidayMaster; _generalizedCodeMaster = generalizedCodeMaster; _currentTenant = currentTenant; _scheduleExceptionMaster = scheduleExceptionMaster; _mysql_mes_morder = mysql_mes_morder; _workOrdSetupTimeRecord = workOrdSetupTimeRecord; _nbrDetail = nbrDetail; } #endregion /// /// 生产排产-定时任务 /// public async void DoExt() { //定时任务排产:获取工厂id IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build(); domain = configuration.GetConnectionString("Factory_id"); ////获取需要排产的工单(获取四周的工单:正常工单+已审批通过的特殊工单) //DateTime endDate = DateTime.Now.Date.AddDays(28).AddDays(1); //取数开始时间为当前时间 DateTime startTime = DateTime.Now; scheTime = startTime; //尚未开始生产的工单+正在生产的工单(工单需求数量>已完成数量) var workOrds = _workOrdMaster.Select(p => p.IsActive && p.Domain == domain && (p.QtyOrded - p.QtyCompleted) > 0 && p.Status !="C" && p.Status != "c" && !string.IsNullOrEmpty(p.Status) && (string.IsNullOrEmpty(p.Typed) || (!string.IsNullOrEmpty(p.Typed) && p.Typed.ToUpper() != "PW" && p.BusinessID >0 ))).ToList(); if (workOrds.Any()) { await DoProductSchedule(startTime, workOrds, domain, 1); } } /// /// 生产排产 /// /// 排产开始时间 /// 需要重新排产的工单 /// 工单的工厂id /// 排产类型:1-自动排产;2-手动排产 /// public async Task DoProductSchedule(DateTime startTime,List workOrds, string factoryid,int type, List dbWorkOrds=null) { //记录工厂id domain = factoryid; //记录排产开始时间 scheTime = startTime; if (dbWorkOrds == null) { //获取工单领料单 var nbrMasterList = _nbrMaster.Select(x => x.Domain == domain && x.Type.ToUpper() == "SM" && workOrds.Select(s => s.WorkOrd).Contains(x.WorkOrd)).ToList(); //获取领料单明细 var nbrDtlLists = _nbrDetail.Select(x => x.Domain == domain && nbrMasterList.Select(s => s.Nbr).Contains(x.Nbr)).ToList(); workOrds = workOrds.OrderBy(p => p.Priority).ToList(); //记录工单数据 dbWorkOrds = workOrds.Select(p => new WorkPriorityDto { WorkOrd = p.WorkOrd, Priority = p.Priority, QtyOrded = p.QtyOrded }).ToList(); //处理工单优先级:正常投产工单优先级最高,其次是前处理状态为部分完成/全部完成,其他按照工单优先级排 decimal ytc = 1m; decimal ktc = 10000m; decimal bktc = 20000m; for (int i = 0; i < workOrds.Count; i++) { //正在投产 if (!string.IsNullOrEmpty(workOrds[i].Status) && workOrds[i].Status.ToUpper() == "W") { workOrds[i].Priority = ytc; ytc++; continue; } //需要前处理的领料单 var qclNbr = nbrMasterList.FirstOrDefault(p => p.WorkOrd == workOrds[i].WorkOrd && p.TransType == "PrevProcess"); //不需要前处理的领料单 var bqclNbr = nbrMasterList.FirstOrDefault(p => p.WorkOrd == workOrds[i].WorkOrd && p.TransType != "PrevProcess"); List bqclNbrDtls = new List(); if (bqclNbr != null) { bqclNbrDtls = nbrDtlLists.Where(p => p.Nbr == bqclNbr.Nbr).ToList(); } //存在领料单:不需要前处理-领料单明细存在已关闭物料,需要前处理-状态为部分完成或者全部完成 if (qclNbr != null && bqclNbr != null) { if ((qclNbr.PretreatmentState == "50" || qclNbr.PretreatmentState == "completed") && bqclNbrDtls.Exists(p => p.Status.ToUpper() == "C")) { workOrds[i].Priority = ktc; ktc++; continue; } workOrds[i].Priority = bktc; bktc++; continue; } if (qclNbr != null && bqclNbr == null) { if (qclNbr.PretreatmentState == "50" || qclNbr.PretreatmentState == "completed") { workOrds[i].Priority = ktc; ktc++; continue; } workOrds[i].Priority = bktc; bktc++; continue; } if (qclNbr == null && bqclNbr != null) { if (bqclNbrDtls.Exists(p => p.Status.ToUpper() == "C")) { workOrds[i].Priority = ktc; ktc++; continue; } workOrds[i].Priority = bktc; bktc++; continue; } if (qclNbr == null && bqclNbr == null) { workOrds[i].Priority = bktc; bktc++; continue; } } } //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); //修改工序需求数为最大完成数+齐套数量 workOrds.ForEach(w => { w.QtyOrded= w.LocationStock.GetValueOrDefault() + w.OpQtyCompleted.GetValueOrDefault() > w.QtyOrded ? w.QtyOrded : w.LocationStock.GetValueOrDefault() + w.OpQtyCompleted.GetValueOrDefault(); var routings = workOrdRoutings.Where(r => r.WorkOrd == w.WorkOrd).ToList(); routings.ForEach(r => { r.QtyOrded = w.LocationStock + w.OpQtyCompleted > w.QtyOrded ? w.QtyOrded : w.LocationStock + w.OpQtyCompleted; }); }); //获取物料对应的生产线信息:物料、工序对应的生产线 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(); //获取工作日历数据:产线的工作日历+默认的工作日历 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 >= scheTime.Date); //获取工单的准备开始时间记录 timeRecords = _workOrdSetupTimeRecord.Select(p=>p.Domain == domain && workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd)); //获取当天的生产周期数据 List dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate == scheTime.Date && p.Domain == domain && p.IsActive); //获取当前日期往后的排产记录数据 List dbSchedules = _scheduleResultOpMaster.Select(p => lines.Contains(p.Line) && p.WorkDate == scheTime.Date && p.Domain == domain); //排产前校验 List exceptions = BeforeScheduleCheck(workOrds, workOrdRoutings, prodLines,type); //过滤校验未通过的工单 if (exceptions.Any()) { workOrds = workOrds.Where(p => !exceptions.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList(); } //3、排产 //生产周期 List periodSequenceDtls = new List(); //排产记录表 List scheduleMasters = new List(); //记录排产结果 List allResults = new List(); //按照优先级排序 workOrds = workOrds.OrderBy(p=>p.Priority).ToList(); for (int i = 0; i < workOrds.Count; i++) { //记录产线占用情况 allResults.AddRange(scheduleMasters); //当前工单工艺路线主产线的关键工序 var curRoutings = workOrdRoutings.Where(p => p.WorkOrd == workOrds[i].WorkOrd && p.ItemNum == workOrds[i].ItemNum && p.ParentOp == 0 && p.MilestoneOp).OrderBy(p => p.OP).ToList(); //当前工单的产线明细 var curProdLines = prodLines.Where(p => p.Part == workOrds[i].ItemNum).ToList(); //产线排产 LineSchedule(workOrds[i], curRoutings, curProdLines, periodSequenceDtls, scheduleMasters, allResults); } //回写工单优先级 foreach (var item in workOrds) { var curInfo = dbWorkOrds.FirstOrDefault(p => p.WorkOrd == item.WorkOrd); if (curInfo != null) { //item.Priority = curInfo.Priority; item.QtyOrded = curInfo.QtyOrded; } } //更新当天日计划的计划数量,排产数量 foreach (var item in periodSequenceDtls) { //item.UDeci5 = item.OrdQty; //获取历史排产数据 var curDtl = dbPeriodSequences.FirstOrDefault(p =>p.OrdQty > 0 && p.Op == item.Op && p.WorkOrds == item.WorkOrds && p.Line == item.Line && p.PlanDate == item.PlanDate && p.PlanDate == scheTime.Date); //item.OrdQty = curDtl == null ? item.OrdQty : curDtl.OrdQty; item.UDeci5 = curDtl == null ? item.OrdQty : curDtl.OrdQty; } using (TransactionScope scope = new TransactionScope()) { try { //记录排产异常数据 if (exceptions.Any()) { _scheduleExceptionMaster.Insert(exceptions); } //更新工单优先级 _workOrdMaster.Update(workOrds); //添加工单工序准备开始时间记录 if (inserts.Any()) { _workOrdSetupTimeRecord.Insert(inserts); } //更新工单工序准备开始时间记录 if (updates.Any()) { _workOrdSetupTimeRecord.Update(updates); } //已投产的工单,只需要保存当前日期下一天的排产记录 List sczWorkords = workOrds.Where(p => p.Status.ToUpper() == "W" || p.Status.ToUpper() == "S").Select(p=>p.WorkOrd).ToList(); if (sczWorkords.Any()) { //获取计划日期在当前日期之后且已投产的日计划,将其计划开工日期改为当天 List wlSequenceDets = _periodSequenceDet.Select(p => p.Domain == domain && sczWorkords.Contains(p.WorkOrds) && p.PlanDate > scheTime.Date).ToList(); List wlSchedules = _scheduleResultOpMaster.Select(p => p.Domain == domain && sczWorkords.Contains(p.WorkOrd) && p.WorkDate > scheTime.Date).ToList(); //获取当天的日计划 List dtSequenceDets = _periodSequenceDet.Select(p => p.Domain == domain && sczWorkords.Contains(p.WorkOrds) && p.PlanDate == scheTime.Date).ToList(); List dtScheduleResults = _scheduleResultOpMaster.Select(p => p.Domain == domain && sczWorkords.Contains(p.WorkOrd) && p.WorkDate == scheTime.Date).ToList(); //需要更新的日计划 List upSequenceDets = new List(); List upScheduleResults = new List(); //需要删除的日计划 List delSequenceDets = new List(); List delScheduleResults = new List(); //需要新增的日计划 List inSequenceDets = new List(); List inScheduleResults = new List(); PeriodSequenceDet seqInfo = new PeriodSequenceDet(); ScheduleResultOpMaster schInfo = new ScheduleResultOpMaster(); foreach (var gdh in sczWorkords) { //提前投产的日计划 var tcSeqs = wlSequenceDets.Where(p => p.WorkOrds == gdh && (p.Status.ToUpper() == "W" || p.Status.ToUpper() == "S")).ToList(); //当前工单存在未来日期投产的日计划 if (tcSeqs.Any()) { //获取提前投产的工序 List ops = tcSeqs.Select(p => p.Op).Distinct().ToList(); foreach (var op in ops) { //当前工序提前投产的日计划 var opSeqs = tcSeqs.Where(p => p.Op == op).ToList(); //获取当天的日计划 var dtSeq = dtSequenceDets.FirstOrDefault(p=>p.WorkOrds == gdh && p.Op == op); if (dtSeq != null ) { delSequenceDets.Add(dtSeq); } seqInfo = new PeriodSequenceDet(); seqInfo = opSeqs[0]; seqInfo.OrdQty = opSeqs.Sum(p => p.OrdQty) + (dtSeq != null ? dtSeq.OrdQty : 0m); seqInfo.CompQty = opSeqs.Sum(p => p.CompQty) + (dtSeq != null ? dtSeq.CompQty : 0m); //获取提前投产的日计划 var opSchs = wlSchedules.Where(p => p.WorkOrd == gdh && p.Op == op && opSeqs.Select(m => m.PlanDate).Contains(p.WorkDate)).ToList(); if (!opSchs.Any()) continue; schInfo = opSchs[0]; string strScheTime = scheTime.Date.ToString("yyyy-MM-dd"); schInfo.WorkDate = scheTime.Date; schInfo.WorkQty = seqInfo.OrdQty; schInfo.WorkStartTime = Convert.ToDateTime(schInfo.WorkStartTime.ToString("yyyy-MM-dd HH:mm:ss").Replace(schInfo.WorkStartTime.ToString("yyyy-MM-dd"), strScheTime)); schInfo.WorkEndTime = Convert.ToDateTime(schInfo.WorkEndTime.ToString("yyyy-MM-dd HH:mm:ss").Replace(schInfo.WorkEndTime.ToString("yyyy-MM-dd"), strScheTime)); upScheduleResults.Add(schInfo); //获取当天的日计划 var dtSch = dtScheduleResults.FirstOrDefault(p=>p.WorkOrd == gdh && p.Op == op); if (dtSch != null)//当天有日计划 { delScheduleResults.Add(dtSch); } seqInfo.PlanDate = scheTime.Date; upSequenceDets.Add(seqInfo); } } //今天未投产的计划清空 var delp = dtSequenceDets.Where(p => string.IsNullOrEmpty(p.Status) && p.WorkOrds==gdh).ToList(); if (delp.Any()) { //删除今天的 delSequenceDets.AddRange(delp); var delr = dtScheduleResults.Where(p => p.WorkOrd == gdh && delp.Select(d => d.Op).Contains(p.Op)).ToList(); delScheduleResults.AddRange(delr); //同一个工序有下达记录则不进行新增 var pc= dtSequenceDets.Where(p => !string.IsNullOrEmpty(p.Status) && p.WorkOrds == gdh).ToList(); delp = delp.Where(d=> !pc.Select(p=>p.Op).Contains(d.Op)).ToList(); //新增今天的 inSequenceDets.AddRange(periodSequenceDtls.Where(p=>p.WorkOrds==gdh && p.PlanDate== scheTime.Date && delp.Select(d => d.Op).Contains(p.Op))); inScheduleResults.AddRange(scheduleMasters.Where(p => p.WorkOrd == gdh && p.WorkDate == scheTime.Date && delp.Select(d => d.Op).Contains(p.Op))); } //无计划的今天的工序新增 var inss = dtScheduleResults.Where(p => p.WorkOrd == gdh).ToList(); var ins = periodSequenceDtls.Where(p => p.WorkOrds == gdh && p.PlanDate == scheTime.Date && !inss.Select(n=>n.Op).Contains(p.Op)).ToList(); if (ins.Any()) { //新增今天的 inSequenceDets.AddRange(ins); inScheduleResults.AddRange(scheduleMasters.Where(p => p.WorkOrd == gdh && p.WorkDate == scheTime.Date && ins.Select(d => d.Op).Contains(p.Op))); } } //更新提前投产的日计划 if (upSequenceDets.Any()) { _periodSequenceDet.Update(upSequenceDets); } if (upScheduleResults.Any()) { _scheduleResultOpMaster.Update(upScheduleResults); } //删除当天的日计划 if (delSequenceDets.Any()) { _periodSequenceDet.Delete(delSequenceDets); } if (delScheduleResults.Any()) { _scheduleResultOpMaster.Delete(delScheduleResults); } //新增当天的日计划 if (inSequenceDets.Any()) { _periodSequenceDet.Insert(inSequenceDets); } if (inScheduleResults.Any()) { _scheduleResultOpMaster.Insert(inScheduleResults); } //删除当前日期下一天开始的工单排产记录 _periodSequenceDet.Delete(p => sczWorkords.Contains(p.WorkOrds) && p.PlanDate > scheTime.Date && p.Domain == domain); _scheduleResultOpMaster.Delete(p => sczWorkords.Contains(p.WorkOrd) && p.WorkDate > scheTime.Date && p.Domain == domain); //保存下一天的日计划数据 _periodSequenceDet.Insert(periodSequenceDtls.Where(p => sczWorkords.Contains(p.WorkOrds) && p.PlanDate > scheTime.Date).ToList()); _scheduleResultOpMaster.Insert(scheduleMasters.Where(p => sczWorkords.Contains(p.WorkOrd) && p.WorkDate > scheTime.Date).ToList()); } //未投产的工单,当前日期及以后的排产记录全部删除 List wscWorkords = workOrds.Where(p => p.Status.ToUpper() != "W" && p.Status.ToUpper() != "S").Select(p => p.WorkOrd).ToList(); if (wscWorkords.Any()) { //删除当前日期及以后的工单排产记录 _periodSequenceDet.Delete(p => wscWorkords.Contains(p.WorkOrds) && p.PlanDate >= scheTime.Date && p.Domain == domain && p.OrdQty != 0); _scheduleResultOpMaster.Delete(p => wscWorkords.Contains(p.WorkOrd) && p.WorkDate >= scheTime.Date && p.Domain == domain); //保存当前日期及以后的日计划数据 var ins1 = periodSequenceDtls.Where(p => wscWorkords.Contains(p.WorkOrds) && p.PlanDate >= scheTime.Date).ToList(); _periodSequenceDet.Insert(periodSequenceDtls.Where(p => wscWorkords.Contains(p.WorkOrds) && p.PlanDate >= scheTime.Date).ToList()); var ins2 = scheduleMasters.Where(p => wscWorkords.Contains(p.WorkOrd) && p.WorkDate >= scheTime.Date).ToList(); _scheduleResultOpMaster.Insert(scheduleMasters.Where(p => wscWorkords.Contains(p.WorkOrd) && p.WorkDate >= scheTime.Date).ToList()); } scope.Complete(); } catch (Exception ex) { new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductSchedule", "保存排产数据失败:" + ex.Message, _currentTenant.Id.ToString()); scope.Dispose(); return "NO|排产数据保存失败"; } } //领料单重新处理 AdjustNbrDate(workOrds); //日计划自动下达 await ProductionDailyPlanAutoPublish(domain); return "OK"; } /// /// 排产前校验 /// /// 待排产工单 /// 工单工艺路线 /// 产线明细 /// 排产类型:1-自动排产,2-手动排产 /// public List BeforeScheduleCheck(List workOrds,List workOrdRoutings, List prodLines,int type) { //记录操作时间 string optTime = DateTime.Now.ToString("yyyyMMddHHmmss"); List exceptions = new List(); ScheduleExceptionMaster entity; foreach (var item in workOrds) { var curRoutings = workOrdRoutings.Where(p => p.WorkOrd == item.WorkOrd && 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 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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()) + "]对应同一条产线[" + curLines[0].Line +"],请调整后再操作!"; entity.Type = type == 1 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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 ? "自动排产" : "手动排产"; entity.OptTime = optTime; 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(); //产线开工时间默认为当前时间 lineStart.StartTime = scheTime; TimeSpan span = TimeSpan.Zero; //第一层级工序有几个关键工序,就有几条产线 for (int i = 0; i < workOrdRoutings.Count; i++) { //产线实际排产开始时间 if (i == 0)//第一条产线 { lineStart = DealStartTime(workOrd, workOrdRoutings[i], prodLines, allResults); } else { //获取前一产线排产开始时间,通过提前期计算当前产线排产开始时间 lineStart = DealNextStartTime(workOrd, lineStart, workOrdRoutings[i], prodLines, allResults); } //如果当前工序已经生产完成,则跳过 if (lineStart.QtyRemain == 0) { continue; } //当前产线的工作日历 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; //正常工单排产 //正常工单产线已排产数量 decimal sumQty = 0m; //产线准备时间(分钟) decimal sumTimes = 0m; while (sumQty < lineStart.QtyRemain) { //获取当天的产能 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 (sumTimes < lineStart.setupTime * 60) { //当天的可用生产时长能满足提前期 if (dto.EffTime > lineStart.setupTime * 60 - sumTimes) { //当天剩余时长(分钟)=当天可用上产时长-剩余产线准备时间 decimal residueTime = dto.EffTime - (lineStart.setupTime * 60 - sumTimes); //产线提前期安排完之后,需要考虑当前是否存在特殊工单: //1、如果当天是排产的最后一天,则先排完正常工单,再安排特殊工单; //2、如果是排产中间日期,则先安排特殊工单排产 //当天剩余产能 decimal sumAmount = Math.Ceiling(dto.Rate * residueTime / 60); //已排产数量+当天的剩余产能小于工单剩余需排产数量,当天的产能需要全部排产 if (sumQty + sumAmount < lineStart.QtyRemain) { //记录生产周期 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 = lineStart.QtyRemain - sumQty; //剩余数量生产需要时长(分钟) decimal workTime = Math.Ceiling(residueQty / dto.Rate * 60); //处理工作时长:防止相上取整导致时间大于剩余可用时长 workTime = workTime >= residueTime ? residueTime : workTime; //排产结束时间 DateTime workEndTime = workStartTime; var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); //开始时间为凌晨时,改为寻找8点开始的工作时间段 if (curPoint == null) { curPoint=workPoints.Find(p => p.StartPoint >= workStartTime && workStartTime <= p.EndPoint); workStartTime = curPoint.StartPoint; } 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).OrderBy(p => p.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 = lineStart.QtyRemain; //当天剩余可用生产时长 residueTime -= workTime; //处理清场时间,两种场景: //1、residueTime<=lineStart.WaitTime,结束时间加上剩余清场时长 if (residueTime <= lineStart.WaitTime) { //仍需排产的清场时长 decimal otherTime = lineStart.WaitTime - residueTime; workEndTime = dto.EndTime.AddMinutes((double)otherTime); residueTime = 0; } //2、residueTime>lineStart.WaitTime,剩余时长大于清场时长,需要过滤休息时间 else { //清场时长 workTime = lineStart.WaitTime; //获取工单排产结束时间所处时间段 curPoint = workPoints.Find(p => p.StartPoint <= workEndTime && workEndTime <= p.EndPoint); span = curPoint.EndPoint - workEndTime; //当前工作时间段的有效生产时间(分钟) effMins = (decimal)span.TotalMinutes; //当前工作时间段即可清场时长 if (effMins >= workTime) { workEndTime = workEndTime.AddMinutes((double)workTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.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; } } residueTime -= workTime; } //记录生产周期 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 * 60; } //当天可用生产时长不能满足或者刚好满足提前期 else { //当天的可用生产时长不能满足提前期 sumTimes += dto.EffTime; //获取下一个工作日 workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars); } } //产线提前期满足的下一个工作日排产 else { //已排产数量+当天的产能小于工单数量,当天的产能需要全部排产 if (sumQty + dto.ProductQty < lineStart.QtyRemain) { //记录生产周期 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 residueTime = dto.EffTime; //正常工单生产时长 decimal workTime = 0m; DateTime workEndTime = workStartTime; if (sumQty < lineStart.QtyRemain) { //剩余需要排产的数量 decimal residueQty = lineStart.QtyRemain - sumQty; //剩余数量生产需要时长(分钟) workTime = Math.Ceiling(residueQty / dto.Rate * 60); //处理需要生产时长 workTime = workTime >= residueTime ? residueTime : workTime; var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); //开始时间为凌晨时,改为寻找8点开始的工作时间段 if (curPoint == null) { curPoint = workPoints.Find(p => p.StartPoint >= workStartTime && workStartTime <= p.EndPoint); workStartTime = curPoint.StartPoint; } 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).OrderBy(p => p.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 = lineStart.QtyRemain; //剩余可用生产时长 residueTime = (residueTime - workTime) < 0 ? 0 : (residueTime - workTime); //处理清场时间,两种场景: //1、residueTime<=lineStart.WaitTime,结束时间加上剩余清场时长 if (residueTime <= lineStart.WaitTime) { //仍需排产的清场时长 decimal otherTime = lineStart.WaitTime - residueTime; workEndTime = dto.EndTime.AddMinutes((double)otherTime); residueTime = 0; } //2、residueTime>lineStart.WaitTime,剩余时长大于清场时长,需要过滤休息时间 else { //清场时长 workTime = lineStart.WaitTime; //获取工单排产结束时间所处时间段 curPoint = workPoints.Find(p => p.StartPoint <= workEndTime && workEndTime <= p.EndPoint); span = curPoint.EndPoint - workEndTime; //当前工作时间段的有效生产时间(分钟) effMins = (decimal)span.TotalMinutes; //当前工作时间段即可清场时长 if (effMins >= workTime) { workEndTime = workEndTime.AddMinutes((double)workTime); } else { //获取后续生产时间段 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.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; } } //剩余生产时长 residueTime -= workTime; } //记录生产周期 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 }); //处理排产开始时间 workStartTime = workEndTime; curPoint = workPoints.FirstOrDefault(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint); if (curPoint != null && workStartTime == curPoint.EndPoint) { var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1); workStartTime = nextPoint == null ? workStartTime : nextPoint.StartPoint; } } } } } } //记录排产结果 periodsDet.AddRange(curSequences); scheduleResults.AddRange(curScheduleRsts); } /// /// 计算当天排产完成的剩余生产时长 /// /// 排产结束时间 /// 当天的工作时间段 /// public decimal CalcRemainWorkTime(DateTime endTime, List workPoints) { decimal remainTime = 0m; var curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint); TimeSpan span = curPoint.EndPoint - endTime; remainTime = (decimal)span.TotalMinutes; var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList(); foreach (var item in nextPoints) { remainTime += item.WorkMinutes; } return remainTime; } /// /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量 /// /// 排产产线 /// 产线排产开始时间 /// /// /// 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).FirstOrDefault(); //开始时间不在工作时间段 if (curPoint == null) { //获取离开始时间最近的时间段 curPoint = workPoints.Where(p => p.EndPoint < startTime).OrderBy(p => p.Level).LastOrDefault(); if (curPoint == null) { curPoint = workPoints.Where(p => startTime < p.StartPoint).OrderBy(p => p.Level).FirstOrDefault(); } startTime = curPoint.EndPoint; } TimeSpan span = curPoint.EndPoint - startTime; scheduledDto.EffTime = (decimal)span.TotalMinutes; //获取后续工作时间段的有效工作时间 var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.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; //产线当天加班设置 var curOccupyTimes = resourceOccupancyTimes.Where(p => p.Resource == lineStart.Line && p.StartTime.GetValueOrDefault().Date == startTime.Date).OrderBy(p => p.StartTime).ToList(); scheduledDto.EffTime += curOccupyTimes.Sum(p=> Convert.ToDecimal(p.Ufld1) * 60); if (curOccupyTimes.Any()) { var last = curOccupyTimes.Last(); scheduledDto.EndTime = last.StartTime.GetValueOrDefault().AddHours(Convert.ToDouble(last.Ufld1)); } //计算当天的产能(向上取整) scheduledDto.ProductQty = Math.Ceiling(scheduledDto.EffTime / 60 * rate); //计算班次 scheduledDto.Period = 1;//默认一班制 if (shopCal.ShiftsStart2 != 0 && shopCal.ShiftsHours2 != 0) { scheduledDto.Period = 2; } return scheduledDto; } /// /// 计算主产线实际排产开始时间 /// /// 工单 /// 工序 /// 生产线明细 /// 产线占用记录 /// public LineStartDto DealStartTime(WorkOrdMaster workOrd,WorkOrdRouting routing,List prodLines, List allResults) { LineStartDto lineStart = new LineStartDto(); //排产工序 int op = routing.OP; //排产任务执行时间 DateTime actStart = scheTime; if (!string.IsNullOrEmpty(workOrd.JointTyped) && workOrd.JointTyped.ToUpper() == "B") { actStart = workOrd.OrdDate.GetValueOrDefault(); } //获取工序对应的产线,根据优先级排序 var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p => p.Sequence).ToList(); //获取工序开始准备时长记录 var curRecord = timeRecords.FirstOrDefault(p => p.Domain == domain && p.WorkOrd == workOrd.WorkOrd && p.Op == op); //如果产线有准备时间记录且准备开始时间在当前时间之前,说明已在排产中,此时不需要重新获取产线 if (curRecord != null && curRecord.StartTime < actStart) { var curLine = lines.FirstOrDefault(p=>p.Line == curRecord.Line); if (curLine != null) { //计算产线实际开始时间 //产线工作日历:当前产线的工作日历 + 默认工作日历 var curCalendars = calendars.Where(p => p.ProdLine == curLine.Line || string.IsNullOrEmpty(p.ProdLine)).ToList(); var curQtyDtls = qualityLines.Where(p => p.ProdLine == curLine.Line).ToList(); actStart = CalcActStartTime(curLine.Line, actStart, curCalendars, curQtyDtls); lineStart.RecID = curLine.RecID; lineStart.Line = curLine.Line; lineStart.StartTime = actStart; lineStart.setupTime = curLine.SetupTime; lineStart.Rate = curLine.Rate; } } else { //获取第一条产线排产结束时间 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; //循环其他产线 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; } } //当前工序没有开始准备时间记录 if (curRecord == null) { //添加记录 inserts.Add(new WorkOrdSetupTimeRecord { Domain = workOrd.Domain, WorkOrd = workOrd.WorkOrd, ItemNum = workOrd.ItemNum, Op = op, Line = lineStart.Line, StartTime = lineStart.StartTime, CreateTime = DateTime.Now }); //记录工序开始准备时间记录 timeRecords.Add(new WorkOrdSetupTimeRecord { Domain = workOrd.Domain, WorkOrd = workOrd.WorkOrd, ItemNum = workOrd.ItemNum, Op = op, Line = lineStart.Line, StartTime = lineStart.StartTime, CreateTime = DateTime.Now }); } else { //更新准备开始时间记录 timeRecords.ForEach(p => { p.Line = p.WorkOrd == workOrd.WorkOrd && p.Op == curRecord.Op ? lineStart.Line : p.Line; p.StartTime = p.WorkOrd == workOrd.WorkOrd && p.Op == curRecord.Op ? lineStart.StartTime : p.StartTime; }); //存在开始准备时间记录,则更新 curRecord.Line = lineStart.Line; curRecord.StartTime = lineStart.StartTime; updates.Add(curRecord); } } //计算准备开始时间点与当前时间点的有效时长 decimal sumTimes = 0m; if (curRecord != null) { sumTimes = CalcTimeMins(curRecord.StartTime, lineStart.StartTime, lineStart.Line); //如果当前工单状态为生产中"W",但是,调整了日计划开工时间,则需要重新准备 if (workOrd.Status.ToUpper() == "W" && !string.IsNullOrEmpty(workOrd.JointTyped) && workOrd.JointTyped.ToUpper() == "B") { sumTimes = 0m; } } //产线准备完成之后到当前时间点剩余时长(小时) decimal restTime = lineStart.setupTime - sumTimes / 60; lineStart.setupTime = restTime <= 0 ? 0 : restTime; lineStart.Op = op; lineStart.WaitTime = routing.WaitTime * 60; //判断当前产线的UPH var curRunCrews = prodLineDetailRunCrews.Where(p => p.ProdLineDetailRecID == lineStart.RecID).ToList(); //判断当前日期是否配置了UPH var curLevel = curRunCrews.FirstOrDefault(p => p.StartDate.GetValueOrDefault().Date <= lineStart.StartTime.Date && p.EndDate.GetValueOrDefault().Date >= lineStart.StartTime.Date); //当天的实际UPH decimal rate = curLevel == null ? lineStart.Rate : curLevel.Rate; //计算上次报工到当前时间点的预估生产数量,如果没有报工记录,则计算产线准备时间完成到当前时间点的预估生产数量 decimal planQty = 0m; if (routing.Last == null && restTime < 0 && workOrd.Status.ToUpper() == "W") { //工序没有报工,使用产线准备完成之后到当前时间点剩余时长计算预估生产数量 planQty = Math.Floor(Math.Abs(restTime) * rate); } else if(routing.Last != null) { //工序存在报工记录,计算报工时间点到当前时间点的有效生产时长,然后计算预估生产数量 sumTimes = CalcTimeMins(routing.Last.GetValueOrDefault(), lineStart.StartTime, lineStart.Line); planQty = Math.Floor(sumTimes / 60 * rate); } //剩余待排产数量=工单数量-已报工数量-预估数量 decimal restQty = workOrd.QtyOrded - routing.QtyComplete - planQty; lineStart.QtyRemain = restQty < 0 ? (workOrd.QtyOrded - routing.QtyComplete) : restQty; return lineStart; } /// /// 计算两个时间点之间的有效时长 /// /// 开始时间 /// 结束时间 /// 产线 /// public decimal CalcTimeMins(DateTime startTime, DateTime endTime, string line) { decimal sumTimes = 0m; //如果开始时间大于结束时间直接返回0 if (endTime <= startTime) { return sumTimes; } TimeSpan span = TimeSpan.Zero; //产线休息时间段设置 var curQtyDtls = qualityLines.Where(p => p.ProdLine == line).ToList(); //获取产线在开始时间当天的工作时间段 var curCalendar = calendars.FirstOrDefault(p => p.ProdLine == line && p.WeekDay == (int)startTime.DayOfWeek); if (curCalendar == null) { curCalendar = calendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)startTime.DayOfWeek); } List workPoints = DealWorkDayToLevels(line, startTime, curCalendar, curQtyDtls); //开始时间所处时间段 var srtPoint = workPoints.FirstOrDefault(p => p.StartPoint <= startTime && startTime <= p.EndPoint); if (srtPoint == null) { //获取开始时间最近的时间段 //开始时间小于工作开始时间 if (startTime <= workPoints[0].StartPoint) { srtPoint = workPoints[0]; startTime = srtPoint.StartPoint; } else { srtPoint = workPoints.Where(p => p.EndPoint <= startTime).OrderBy(p => p.Level).Last(); startTime = srtPoint.EndPoint; } } //开始时间、结束时间处于同一天 if (startTime.Date == endTime.Date) { //结束时间所处时间段 var lstPoint = workPoints.FirstOrDefault(p => p.StartPoint <= endTime && endTime <= p.EndPoint); //结束时间位于休息时间段 if (lstPoint == null) { //获取结束时间最近的时间段 //lstPoint = workPoints.Where(p => p.EndPoint <= endTime).OrderBy(p => p.Level).Last(); //endTime = lstPoint.EndPoint; //获取开始时间最近的时间段 //结束时间小于工作开始时间 if (endTime <= workPoints[0].StartPoint) { lstPoint = workPoints[0]; endTime = lstPoint.StartPoint; } else { lstPoint = workPoints.Where(p => p.EndPoint <= endTime).OrderBy(p => p.Level).Last(); endTime = lstPoint.EndPoint; } } //开始时间和结束时间位于同一时间段 if (srtPoint.Level == lstPoint.Level) { span = endTime - startTime; sumTimes = (decimal)span.TotalMinutes; return sumTimes; } //开始时间,结束时间位于不同时间段 //计算开始时间所处时间段的有效时长 span = srtPoint.EndPoint - startTime; sumTimes += (decimal)span.TotalMinutes; //计算结束时间所处时间段的有效时长 span = endTime - lstPoint.StartPoint; sumTimes += (decimal)span.TotalMinutes; //计算开始时间,结束时间中间的工作时长 var others = workPoints.Where(p => p.Level > srtPoint.Level && p.Level < lstPoint.Level).ToList(); sumTimes += others.Sum(p => p.WorkMinutes); return sumTimes; } //开始时间、结束时间不处于同一天 //1、计算开始时间所在当天剩余有效工作时长 span = srtPoint.EndPoint - startTime; sumTimes += (decimal)span.TotalMinutes; var nextPoints = workPoints.Where(p => p.Level > srtPoint.Level); sumTimes += nextPoints.Sum(p => p.WorkMinutes); //2、计算结束时间所在当天有效工作时长 curCalendar = calendars.FirstOrDefault(p => p.ProdLine == line && p.WeekDay == (int)endTime.DayOfWeek); if (curCalendar == null) { curCalendar = calendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)endTime.DayOfWeek); } workPoints = DealWorkDayToLevels(line, endTime, curCalendar, curQtyDtls); var curPoint = workPoints.FirstOrDefault(p => p.StartPoint <= endTime && endTime <= p.EndPoint); if (curPoint == null) { //获取结束时间最近的时间段 //curPoint = workPoints.Where(p => p.EndPoint <= endTime).OrderBy(p => p.Level).Last(); //endTime = curPoint.EndPoint; //结束时间小于工作开始时间 if (endTime <= workPoints[0].StartPoint) { curPoint = workPoints[0]; endTime = curPoint.StartPoint; } else { curPoint = workPoints.Where(p => p.EndPoint <= endTime).OrderBy(p => p.Level).Last(); endTime = curPoint.EndPoint; } } span = endTime - curPoint.StartPoint; sumTimes += (decimal)span.TotalMinutes; nextPoints = workPoints.Where(p => p.Level < curPoint.Level); sumTimes += nextPoints.Sum(p => p.WorkMinutes); //3、计算中间天数的有效工作时长 span = endTime.Date - startTime.Date; int days = span.Days; for (int i = 1; i < days; i++) { var newDate = startTime.Date.AddDays(i); //获取产线的工作时间段 curCalendar = calendars.FirstOrDefault(p => p.ProdLine == line && p.WeekDay == (int)newDate.DayOfWeek); if (curCalendar == null) { curCalendar = calendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)newDate.DayOfWeek); } workPoints = DealWorkDayToLevels(line, newDate, curCalendar, curQtyDtls); //当天的有效工作时长 sumTimes += workPoints.Sum(p => p.WorkMinutes); } return sumTimes; } /// /// 计算主产线实际排产开始时间 /// /// 生产线 /// 开始时间 /// 当前产线工作日历+默认工作日历 /// 当前产线休息记录 /// 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.Find(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,WorkOrdRouting routing, List prodLines, List allResults) { //本序工序编码 int op = routing.OP; //获取上序实际开工时间 DateTime preStartTime = lineStart.StartTime; //上序的开始准备时间记录 var preRecord = timeRecords.FirstOrDefault(p=>p.WorkOrd == workOrd.WorkOrd && p.Domain == domain && p.Op == lineStart.Op); if (preRecord != null) { preStartTime = preRecord.StartTime; } LineStartDto startDto = new LineStartDto(); //获取产线 var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p=>p.Sequence).ToList(); //当前工序的开始准备时间记录 var curRecord = timeRecords.FirstOrDefault(p=>p.Domain == domain && p.WorkOrd == workOrd.WorkOrd && p.Op == op); //如果产线有准备时间记录且准备开始时间在当前时间之前,说明已在排产中,此时不需要重新获取产线 if (curRecord != null && curRecord.StartTime < scheTime) { var curLine = lines.FirstOrDefault(p => p.Line == curRecord.Line); if (curLine != null) { //产线工作日历:当前产线的工作日历 + 默认工作日历 var curCalendars = calendars.Where(p => p.ProdLine == curLine.Line || string.IsNullOrEmpty(p.ProdLine)).ToList(); var curQtyDtls = qualityLines.Where(p => p.ProdLine == curLine.Line).ToList(); DateTime startTime = CalcActStartTime(curLine.Line, scheTime, curCalendars, curQtyDtls); startDto.RecID = curLine.RecID; startDto.Line = curLine.Line; startDto.StartTime = startTime; startDto.setupTime = curLine.SetupTime; startDto.Rate = curLine.Rate; } } else { //获取第一条产线排产结束时间 var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault(); DateTime startTime = CalcStartTimeWithSetUpTime(lines[0], preStartTime, 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; //循环剩余产线,找到最早可开工产线 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], preStartTime, 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[i].Rate; } } //当前工序没有开始准备时间记录 if (curRecord == null) { //添加记录 inserts.Add(new WorkOrdSetupTimeRecord { Domain = workOrd.Domain, WorkOrd = workOrd.WorkOrd, ItemNum = workOrd.ItemNum, Op = op, Line = startDto.Line, StartTime = startDto.StartTime, CreateTime = DateTime.Now }); //记录工序开始准备时间记录 timeRecords.Add(new WorkOrdSetupTimeRecord { Domain = workOrd.Domain, WorkOrd = workOrd.WorkOrd, ItemNum = workOrd.ItemNum, Op = op, Line = startDto.Line, StartTime = startDto.StartTime, CreateTime = DateTime.Now }); } else { //更新准备开始时间记录 timeRecords.ForEach(p => { p.Line = p.WorkOrd == workOrd.WorkOrd && p.Op == curRecord.Op ? startDto.Line : p.Line; p.StartTime = p.WorkOrd == workOrd.WorkOrd && p.Op == curRecord.Op ? startDto.StartTime : p.StartTime; }); //存在开始准备时间记录,则更新 curRecord.Line = startDto.Line; curRecord.StartTime = startDto.StartTime; updates.Add(curRecord); } } //计算准备开始时间点与当前时间点的有效时长 decimal sumTimes = 0m; if (curRecord != null) { sumTimes = CalcTimeMins(curRecord.StartTime, startDto.StartTime, startDto.Line); //如果当前工单状态为生产中"W",但是,调整了日计划开工时间,则需要重新准备 if (workOrd.Status.ToUpper() == "W" && !string.IsNullOrEmpty(workOrd.JointTyped) && workOrd.JointTyped.ToUpper() == "B") { sumTimes = 0m; } } //产线准备完成之后到当前时间点剩余时长(小时) decimal restTime = startDto.setupTime - sumTimes / 60; startDto.setupTime = restTime <= 0 ? 0 : restTime; startDto.Op = op; startDto.WaitTime = routing.WaitTime * 60; //判断当前产线的UPH var curRunCrews = prodLineDetailRunCrews.Where(p => p.ProdLineDetailRecID == startDto.RecID).ToList(); //判断当前日期是否配置了UPH var curLevel = curRunCrews.FirstOrDefault(p => p.StartDate.GetValueOrDefault().Date <= startDto.StartTime.Date && p.EndDate.GetValueOrDefault().Date >= startDto.StartTime.Date); //当天的实际UPH decimal rate = curLevel == null ? startDto.Rate : curLevel.Rate; //计算上次报工到当前时间点的预估生产数量,如果没有报工记录,则计算产线准备时间完成到当前时间点的预估生产数量 decimal planQty = 0m; if (routing.Last == null && restTime < 0 && workOrd.Status.ToUpper() == "W") { //工序没有报工,使用产线准备完成之后到当前时间点剩余时长计算预估生产数量 planQty = Math.Floor(Math.Abs(restTime) * rate); } else if (routing.Last != null) { //工序存在报工记录,计算报工时间点到当前时间点的有效生产时长,然后计算预估生产数量 sumTimes = CalcTimeMins(routing.Last.GetValueOrDefault(), startDto.StartTime, startDto.Line); planQty = Math.Floor(sumTimes / 60 * rate); } //剩余待排产数量=工单数量-已报工数量-预估数量 decimal restQty = workOrd.QtyOrded - routing.QtyComplete - planQty; startDto.QtyRemain = restQty < 0 ? (workOrd.QtyOrded - routing.QtyComplete) : restQty; 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(); //如果找不到对应时间段 if (curPoint == null) { //如果开始时间在开始上班之前 if (startTime < workPoints.First().StartPoint) { curPoint = workPoints.First(); startTime = curPoint.StartPoint; } else { //判断开始时间处于哪个空闲时间段 curPoint = workPoints.Where(p => startTime > p.EndPoint).OrderBy(p => p.Level).Last(); startTime = curPoint.EndPoint; } } //当前时间段可用时长 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); //休息时间段开始时间不在工作时间内,不考虑 if (endPoint < dayStartPoint || endPoint > dayEndPoint) { continue; } 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) { //如果加班开始时间在工作时间内,则不考虑 if (item.StartTime.GetValueOrDefault() < dayEndPoint) { continue; } 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(); } /// /// 调整领料单时间 /// /// public void AdjustNbrDate(List workords) { if (workords.Any()) { //找出当前工单的所有领料单,更新领料单的前处理时间和完成日期。 //1.如果未生成领料单,则无需调整。 //2.已生成领料单,但是领料单已出库,也不调整。nbr.Status='C' //3.当领料单未出库,则代表需要调整这个领料单的处理时间。 var nbrlist = _nbrMaster.Select(s => workords.Select(x => x.WorkOrd).Contains(s.WorkOrd) && s.Domain == workords[0].Domain).ToList(); if (nbrlist.Any()) { foreach (var work in workords) { var workNbr = nbrlist.Where(s => s.WorkOrd == work.WorkOrd).ToList(); foreach (var nbr in workNbr) { if (string.IsNullOrEmpty(nbr.Status)) { if (nbr.TransType == "PrevProcess") { //前处理领料单 var ts = nbr.EffDate.GetValueOrDefault() - nbr.Date.GetValueOrDefault(); nbr.EffDate = work.OrdDate.GetValueOrDefault().AddDays(-1); nbr.Date = nbr.EffDate.GetValueOrDefault().AddDays(-ts.Days); } else { nbr.EffDate = work.OrdDate.GetValueOrDefault().AddDays(-1); nbr.Date = nbr.EffDate; } if (nbr.Date.GetValueOrDefault().Date <= DateTime.Now.Date) { nbr.Date = DateTime.Now.AddDays(1).Date; } if (nbr.EffDate.GetValueOrDefault().Date <= DateTime.Now.Date) { nbr.EffDate = DateTime.Now.AddDays(1).Date; } } } } _nbrMaster.Update(nbrlist); } } } /// /// 日计划自动发布 /// public async Task ProductionDailyPlanAutoPublish(string domain) { //取数开始时间为当前日期 DateTime startTime = DateTime.Now.Date; DateTime endDate = DateTime.Now.Date.AddDays(6); var periodList = _periodSequenceDet.Select(x => x.PlanDate >= startTime && x.PlanDate <= endDate && string.IsNullOrEmpty(x.Status) == true && x.Domain == domain).ToList(); if (periodList.Any()) { try { List updateList = new List(); var workords = periodList.Select(s => s.WorkOrds); var workordList = _workOrdMaster.Select(x => x.Domain == domain && x.Status.ToLower() == "r" && workords.Contains(x.WorkOrd)).ToList(); var nbrMasterList = _nbrMaster.Select(x => x.Domain == domain && x.Type == "SM" && workords.Contains(x.WorkOrd)).ToList(); var nbrDetailList = _nbrDetail.Select(x => x.Domain == domain && x.Type == "SM" && workords.Contains(x.WorkOrd)).ToList(); foreach (var work in workordList) { bool flag = false; var nbrlist = nbrMasterList.Where(s => s.WorkOrd == work.WorkOrd).ToList(); if (nbrlist.Any()) { flag = true; foreach (var nbr in nbrlist) { //还有未出库数据 //如果有任意一个出库,则下达 if (!nbrDetailList.Where(x => x.NbrRecID == nbr.RecID && x.Status.ToUpper() == "C").Any()) { flag = false; break; } /*if (nbr.Status.ToLower() != "c") { flag = false; break; }*/ //需要前处理,但是前处理还未完成 if (nbr.TransType == "PrevProcess" && nbr.PretreatmentState != "completed" && nbr.PretreatmentState != "50") { flag = false; break; } } } if (flag) { var publish = periodList.Where(x => x.WorkOrds == work.WorkOrd).ToList(); publish.ForEach(x => { x.Status = "r"; }); updateList.AddRange(publish); } } if (updateList.Any()) { _periodSequenceDet.Update(updateList); } } catch (Exception e) { new NLogHelper("ResourceExamineAppService").WriteLog("ProductionDailyPlanAutoPublish", "日计划自动任务失败:" + e.Message, _currentTenant.Id.ToString()); return "保存失败,请联系管理员。"; } } return "ok"; } } }