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