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;
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;
///
/// 雪花算法
///
SnowFlake help = new SnowFlake();
private readonly ICurrentTenant _currentTenant;
///
/// 工作日历数据
///
private List calendars;
///
/// 产线休息记录数据
///
private List qualityLines;
///
/// 节假日记录数据
///
private List holidays;
///
/// 工厂id
///
private string domain = "";
#endregion
#region 构造函数
///
/// 构造函数
///
public ProductionScheduleAppService(
ISqlRepository itemMaster,
ISqlRepository workOrdMaster,
ISqlRepository workOrdDetail,
ISqlRepository workOrdRouting,
ISqlRepository prodLineDetail,
ISqlRepository prodLineDetailRunCrew,
ISqlRepository resourceOccupancyTime,
ISqlRepository periodSequenceDet,
ISqlRepository scheduleResultOpMaster,
ISqlRepository invMaster,
ISqlRepository shopCalendarWorkCtr,
ISqlRepository qualityLineWorkDetail,
ISqlRepository holidayMaster,
ISqlRepository generalizedCodeMaster,
ICurrentTenant currentTenant
)
{
_itemMaster = itemMaster;
_workOrdMaster = workOrdMaster;
_workOrdDetail = workOrdDetail;
_workOrdRouting = workOrdRouting;
_prodLineDetail = prodLineDetail;
_prodLineDetailRunCrew = prodLineDetailRunCrew;
_resourceOccupancyTime = resourceOccupancyTime;
_periodSequenceDet = periodSequenceDet;
_scheduleResultOpMaster = scheduleResultOpMaster;
_invMaster = invMaster;
_shopCalendarWorkCtr = shopCalendarWorkCtr;
_qualityLineWorkDetail = qualityLineWorkDetail;
_holidayMaster = holidayMaster;
_generalizedCodeMaster = generalizedCodeMaster;
_currentTenant = currentTenant;
}
#endregion
///
/// 执行生产排产
///
public async void DoExt()
{
//定时任务排产:获取工厂id
IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
domain = configuration.GetConnectionString("Factory_id");
//获取提前期
var generalizedCodeMaster = _generalizedCodeMaster.Select(x => x.FldName == "SystemConfig" && x.Val == "WorkOrderLockPeriod" && x.Domain == domain).FirstOrDefault();
decimal Udecil = 0;
if (generalizedCodeMaster != null)
{
Udecil = generalizedCodeMaster.UDeci1;
}
//排产取4周工单排产
DateTime dateTime = DateTime.Now.AddDays(30);
DateTime date = DateTime.Now.AddDays((double)Udecil);
var workOrds = _workOrdMaster.Select(x => x.IsActive && x.Domain == domain && x.OrdDate < dateTime && x.OrdDate > date && x.Status == "初始").ToList();
await DoProductShcedule(workOrds, domain);
}
///
/// 生产排产
///
/// 工单:定时任务执行时count=0;资源检查调用count>0
/// 工单的工厂id
///
public async Task DoProductShcedule(List workOrds, string factoryid)
{
//记录工厂id
domain = factoryid;
if (workOrds.Count == 0)//没有工单需要排产
{
return;
}
//获取排产工单的最早计划开工日期
DateTime earlist = DateTime.Now.Date.AddDays(1);
//2、获取数据
//获取工单工艺路径数据
List workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.ParentOp == 0 && p.Domain == domain && p.Status != "C" && p.IsActive);
//获取物料对应的生产线信息:物料、工序对应的生产线
List prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == domain && p.IsActive);
List lines = prodLines.Select(p => p.Line).ToList();
//获取非标准产线人员配置
List prodLineDetailRunCrews = _prodLineDetailRunCrew.Select(x => prodLines.Select(p => p.RecID).Contains(x.ProdLineDetailRecID) && x.IsActive && x.Domain == domain).ToList();
//获取加班设置
List resourceOccupancyTimes = _resourceOccupancyTime.Select(x => prodLines.Select(p => p.Line).Contains(x.Resource) && x.IsActive && x.Domain == domain).ToList();
//获取生产周期数据
List dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate >= earlist && p.Domain == domain && p.IsActive);
//获取当前日期往后的排产记录数据
List dbSchedules = _scheduleResultOpMaster.Select(p => lines.Contains(p.Line) && p.WorkDate >= earlist && p.Domain == domain);
//获取工作日历数据:产线的工作日历+默认的工作日历
calendars = _shopCalendarWorkCtr.Select(p => (lines.Contains(p.ProdLine) || string.IsNullOrEmpty(p.ProdLine)) && p.Domain == domain && p.IsActive);
//获取产线休息记录数据
qualityLines = _qualityLineWorkDetail.Select(p => lines.Contains(p.ProdLine) && p.Domain == domain && p.IsActive);
//获取节假日记录数据
holidays = _holidayMaster.Select(p => p.Domain == domain && p.IsActive && p.Dated >= earlist);
//3、排产
//生产周期
List periodSequenceDtls = new List();
//排产记录表
List scheduleMasters = new List();
//排产结果
List allResults = new List();
allResults.AddRange(dbSchedules);
foreach (var item in workOrds)
{
//记录产线占用情况
allResults.AddRange(scheduleMasters);
//当前工单工艺路线主产线的关键工序
var curRoutings = workOrdRoutings.Where(p => p.ItemNum == item.ItemNum && p.ParentOp == 0 && p.MilestoneOp).OrderBy(p=>p.OP).ToList();
//当前工单的产线明细
var curProdLines = prodLines.Where(p => p.Part == item.ItemNum).ToList();
#region 校验
//工单工艺多产线关键工序、物料对应的生产线信息:物料,工序对应的生产线、工作日历数据
var workOrdRouting = workOrdRoutings.Where(x => x.WorkOrd == item.WorkOrd && x.ParentOp == 0).ToList();
if (workOrdRouting.Count == 0)
{
//记录排产异常原因
new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的工单工艺流程数据维护为空", _currentTenant.Id.ToString());
continue;
}
//TODO:多个关键工序校验
//物料对应生产线校验
var ProdLineDetails = prodLines.Where(x => x.Part == item.ItemNum).ToList();
if (ProdLineDetails.Count == 0)
{
//记录排产异常原因
new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的生产线物料数据维护为空", _currentTenant.Id.ToString());
continue;
}
var calendarsList = calendars.Where(x => ProdLineDetails.Select(p => p.Line).Contains(x.ProdLine) || string.IsNullOrEmpty(x.ProdLine)).ToList();
//校验每个层级是否维护了工作日历
bool flag = false;
foreach (var rut in ProdLineDetails)
{
var lineCals = calendarsList.Where(p => p.ProdLine == rut.Line).ToList();
//当前产线未配置工作日历取标准无产线工作日历使用
if (lineCals.Count == 0)
{
lineCals = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine)).ToList();
if (lineCals.Count == 0)
{
break;
}
}
if (lineCals.Select(p => p.WeekDay).Distinct().Count() != 7)
{
flag = true;
break;
}
}
if (flag)
{
//记录排产异常原因
new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "工单<" + item.WorkOrd + ">的<" + item.ProdLine + ">工作日历数据维护错误", _currentTenant.Id.ToString());
continue;
}
#endregion
//产线排产
LineSchedule(item, curRoutings, curProdLines, periodSequenceDtls, scheduleMasters,allResults);
List scheduleList = scheduleMasters.Where(s => s.WorkOrd == item.WorkOrd).ToList();
if (scheduleList.Any())
{
item.OrdDate = scheduleList.Min(s => s.WorkStartTime.Date);
item.DueDate = scheduleList.Max(s => s.WorkEndTime.Date);
}
}
using (TransactionScope scope = new TransactionScope())
{
try
{
//记录排产数据
_workOrdMaster.Update(workOrds);
_periodSequenceDet.Insert(periodSequenceDtls);
_scheduleResultOpMaster.Insert(scheduleMasters);
scope.Complete();
}
catch (Exception ex)
{
new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductShcedule", "记录排产数据失败:" + ex.Message, _currentTenant.Id.ToString());
scope.Dispose();
}
}
}
///
/// 排产
///
/// 工单
/// 当前工单的工艺路线的关键工序(有几个关键工序就有几条产线)
/// 当前工单的产线明细
/// 生产周期
/// 排产结果
/// 产线占用记录
public void LineSchedule(WorkOrdMaster workOrd, List workOrdRoutings, List prodLines,List periodsDet, List scheduleResults, List allResults)
{
//生产周期
List curSequences = new List();
//排产明细
List curScheduleRsts = new List();
//记录上一产线排产开始时间
LineStartDto lineStart = new LineStartDto();
//第一层级工序有几个关键工序,就有几条产线
for (int i = 0; i < workOrdRoutings.Count; i++)
{
//产线实际排产开始时间
if (i == 0)//第一条产线
{
lineStart = DealStartTime(workOrd, workOrdRoutings[i].OP, prodLines, allResults);
}
else
{
//获取前一产线排产开始时间,通过提前期计算当前产线排产开始时间
lineStart = DealNextStartTime(workOrd,lineStart, workOrdRoutings[i].OP, prodLines, allResults);
}
//当前产线的工作日历
var mLCalendars = calendars.Where(p => p.ProdLine == lineStart.Line || string.IsNullOrEmpty(p.ProdLine)).ToList();
//当前产线的休息时间设置
var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == lineStart.Line).ToList();
//产线已排产数量
decimal sumQty = 0m;
//产线准备时间
decimal sumTimes = 0m;
//产线排产开始时间
DateTime workStartTime = lineStart.StartTime;
//排产
while (sumQty < workOrd.QtyOrded)
{
//获取当天的产能
LineScheduledDto dto = GetScheduledPoint(lineStart.Rate, workStartTime, mLCalendars, mlqtyWorkDtls);
//排产开始时,需要先减去产线准备时间
if (sumTimes < lineStart.setupTime)
{
//判断当天的可用生产时长能满足提前期
if (dto.EffTime >= lineStart.setupTime)
{
sumTimes = lineStart.setupTime;
//当天剩余产能
decimal sumAmount = dto.ProductQty - Math.Floor(lineStart.Rate * lineStart.setupTime);
//判断已排产数量+当天的产能是否超过工单数量
if (sumQty + sumAmount <= workOrd.QtyOrded)//当天的产能需要全部排产
{
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = sumAmount,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = sumAmount,
WorkStartTime = dto.StartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += sumAmount;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
}
else// 最后一天的产能只能占用一部分
{
//剩余需要排产的数量
decimal residueQty = workOrd.QtyOrded - sumQty;
//剩余数量生产需要时长(分钟)
decimal workTime = Math.Ceiling(residueQty / lineStart.Rate * 60);
//获取当天的工作时间段
var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
if (curCalendar == null)
{
curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
}
List workPoints = DealWorkDayToLevels(workStartTime, curCalendar, mlqtyWorkDtls);
var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
if (curPoint != null)
{
TimeSpan span = curPoint.EndPoint - workStartTime;
//当天工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
DateTime workEndTime = workStartTime;
if (effMins >= workTime)//当前工作时间段即可满足产能
{
workEndTime = workStartTime.AddMinutes((double)workTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
//剩余需要工作时长
decimal nextMins = workTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
workEndTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
sumQty = workOrd.QtyOrded;
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = residueQty,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = residueQty,
WorkStartTime = workStartTime,
WorkEndTime = workEndTime,
CreateTime = DateTime.Now
});
}
}
}
else {
//当天的可用生产时长不能满足提前期
sumTimes += dto.EffTime;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
}
}
else {
//判断已排产数量+当天的产能是否超过工单数量
if (sumQty + dto.ProductQty <= workOrd.QtyOrded)//当天的产能需要全部排产
{
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,//目前只考虑一班制
OrdQty = dto.ProductQty,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = dto.ProductQty,
WorkStartTime = dto.StartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += dto.ProductQty;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
}
else// 最后一天的产能只能占用一部分
{
//剩余需要排产的数量
decimal residueQty = workOrd.QtyOrded - sumQty;
//剩余数量生产需要时长(分钟)
decimal workTime = Math.Ceiling(residueQty / lineStart.Rate * 60);
//获取当天的工作时间段
var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
if (curCalendar == null)
{
curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
}
List workPoints = DealWorkDayToLevels(workStartTime, curCalendar, mlqtyWorkDtls);
var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
if (curPoint == null)
{
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 = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,//目前只考虑一班制
OrdQty = residueQty,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = residueQty,
WorkStartTime = workStartTime,
WorkEndTime = workEndTime,
CreateTime = DateTime.Now
});
}
}
}
}
//记录排产结果
periodsDet.AddRange(curSequences);
scheduleResults.AddRange(curScheduleRsts);
}
///
/// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量
///
/// 产线UPH
/// 产线排产开始时间
///
///
///
public LineScheduledDto GetScheduledPoint(decimal rate, DateTime startTime, List curCalendars, List curQtyDtls)
{
LineScheduledDto scheduledDto = new LineScheduledDto();
//当天排产开始时间
scheduledDto.StartTime = startTime;
//开始时间是周几
int weekDay = (int)startTime.DayOfWeek;
//当天的工作日历
var shopCal = curCalendars.Where(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
if (shopCal == null)//当前产线当天没有设置工作日历
{
//取默认工作日历
shopCal = curCalendars.Where(p =>string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
}
//当前日期的工作时间段
List workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
//当天排产结束时间
scheduledDto.EndTime = workPoints.Last().EndPoint;
//计算starttime处于那个工作时间段
var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).First();
TimeSpan span = curPoint.EndPoint - startTime;
scheduledDto.EffTime = (decimal)span.TotalHours;
//获取后续工作时间段的有效工作时间
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
foreach (var item in nextPoints)
{
span = item.EndPoint - item.StartPoint;
scheduledDto.EffTime += (decimal)span.TotalHours;
}
//计算当天的产能
scheduledDto.ProductQty = Math.Floor(scheduledDto.EffTime * rate);
//计算班次
scheduledDto.Period = 1;//默认一般制
if (shopCal.ShiftsStart2 != 0 && shopCal.ShiftsHours2 != 0)
{
scheduledDto.Period = 2;
}
return scheduledDto;
}
///
/// 计算主产线实际排产开始时间
///
/// 工单
/// 工序
/// 生产线明细
/// 产线占用记录
///
public LineStartDto DealStartTime(WorkOrdMaster workOrd,int op,List prodLines, List allResults)
{
DateTime actStart = DateTime.Now.Date.AddDays(1);
LineStartDto lineStart = new LineStartDto();
//获取工序对应的产线,根据优先级排序
var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p => p.Sequence).ToList();
//获取第一条产线排产结束时间
var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
actStart = schedule == null ? actStart : schedule.WorkEndTime;
//计算实际开工时间
var curCalendars = calendars.Where(p => p.ProdLine == lines[0].Line).ToList();
var curQtyDtls = qualityLines.Where(p=>p.ProdLine == lines[0].Line).ToList();
actStart = CalcActStartTime(actStart, curCalendars, curQtyDtls);
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;
//计算实际开工时间
curCalendars = calendars.Where(p => p.ProdLine == lines[i].Line).ToList();
curQtyDtls = qualityLines.Where(p => p.ProdLine == lines[i].Line).ToList();
StartTime = CalcActStartTime(StartTime, curCalendars, curQtyDtls);
if (StartTime < lineStart.StartTime)
{
lineStart.Line = lines[i].Line;
lineStart.StartTime = StartTime;
lineStart.setupTime = lines[i].SetupTime;
lineStart.Rate = lines[i].Rate;
}
}
return lineStart;
}
///
/// 计算主产线实际排产开始时间
///
/// 开始时间
/// 当前产线工作日历
/// 当前产线休息记录
///
public DateTime CalcActStartTime(DateTime startTime, List curCalendars, List curQtyDtls)
{
//实际排产开始时间
DateTime actStart = startTime;
//开始时间是周几
int weekDay = (int)startTime.DayOfWeek;
//判断当天是否是工作日
bool isWorkDay = CheckIsWorkDay(startTime);
if (!isWorkDay)//不是工作日
{
//获取下一个工作日开始时间
actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
return actStart;
}
//当天的工作日历
var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First();
//当前日期的工作时间段
List workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
//计算starttime处于那个工作时间段
var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
if (curPoint == null)//不处于工作时间段
{
//开始时间小于当天工作开始时间
if (startTime < workPoints.First().StartPoint)
{
actStart = workPoints.First().StartPoint;
}
//开始时间大于当前工作结束时间
else if (startTime > workPoints.Last().EndPoint)
{
//获取下一个工作日开始时间
actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
}
else
{//开始时间位于当天的休息时间段
foreach (var item in workPoints)
{
//获取下一个时间段
var next = workPoints.First(p => p.Level == item.Level + 1);
if (item.EndPoint < startTime && startTime < next.StartPoint)
{
actStart = next.StartPoint;
break;
}
}
}
return actStart;
}
if (startTime != curPoint.EndPoint)
{
return actStart;
}
//查询下一时间段的开始时间点
var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
if (nextPoint != null)
{
return nextPoint.StartPoint;
}
//开始时间为今天下班时间,实际排产开始时间为下一个工作日的开始时间
actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
return actStart;
}
///
/// 排产开始时间处理为半小时/整点开始--向后取整
///
///
///
public DateTime CalcStartTimeAfter(DateTime startTime)
{
DateTime rtnTime = startTime;
DateTime curDate = startTime.Date;
//时间转换为分钟
TimeSpan span = rtnTime - curDate;
decimal sumMinutes = (decimal)span.TotalMinutes;
int times = (int)Math.Ceiling(sumMinutes / 30);
rtnTime = curDate.AddMinutes(times * 30);
return rtnTime;
}
///
/// 排产开始时间处理为半小时/整点开始--向前取整
///
///
///
public DateTime CalcStartTimeBefore(DateTime startTime)
{
DateTime rtnTime = startTime;
DateTime curDate = startTime.Date;
//时间转换为分钟
TimeSpan span = rtnTime - curDate;
decimal sumMinutes = (decimal)span.TotalMinutes;
int times = (int)Math.Floor(sumMinutes / 30);
rtnTime = curDate.AddMinutes(times * 30);
return rtnTime;
}
///
/// 判断当天是否是工作日
///
///
///
public bool CheckIsWorkDay(DateTime dateTime)
{
bool isWorkDay = true;
//周几
int weekDay = (int)dateTime.DayOfWeek;
//判断当天是否是工作日
if (weekDay == 0 || weekDay == 6)//周六或者周日,需要判断是否调班,需要加班
{
if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "调班"))//不是调班
{
isWorkDay = false;
}
return isWorkDay;
}
//不是周六周日,需要判断是不是节假日
if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "休假"))//是节假日
{
isWorkDay = false;
}
return isWorkDay;
}
///
/// 计算下一产线实际排产开始时间
///
/// 工单
/// 上一产线开始时间
/// 工序
/// 产线明细
/// 产线占用记录
///
public LineStartDto DealNextStartTime(WorkOrdMaster workOrd,LineStartDto lineStart,int op, List prodLines, List allResults)
{
LineStartDto startDto = new LineStartDto();
//获取产线
var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p=>p.Sequence).ToList();
//获取第一条产线排产结束时间
var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
DateTime startTime = CalcStartTimeWithSetUpTime(lines[0], lineStart.StartTime, lineStart.setupTime + lines[0].OverlapTime);
startDto.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], lineStart.StartTime, lineStart.setupTime + lines[i].OverlapTime);
startTime = schedule == null ? startTime : (startTime < schedule.WorkEndTime ? schedule.WorkEndTime : startTime);
if (startTime < startDto.StartTime)
{
startDto.Line = lines[i].Line;
startDto.setupTime = lines[i].SetupTime;
startDto.StartTime = startTime;
startDto.Rate = lines[0].Rate;
}
}
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).First();
//当前产线的休息时间设置
var curQtyDtls = qualityLines.Where(p => p.ProdLine == line.Line).ToList();
//当前日期的工作时间段
List workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
//计算starttime处于那个工作时间段
var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
//当前时间段可用时长
TimeSpan span = curPoint.EndPoint - startTime;
decimal curMins = (decimal)span.TotalMinutes;
if (curMins >= needMinute)//当前时间段的可用时长满足提前期
{
actStart = startTime.AddMinutes((double)needMinute);
return actStart;
}
//当前时间段的可用时长不满足
//剩余提前期
needMinute -= curMins;
//获取后层级时间段
var prePoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
bool flag = true;//标志位
foreach (var item in prePoints)
{
if (item.WorkMinutes >= needMinute)//当前时间段的可用时长满足
{
actStart = item.StartPoint.AddMinutes((double)needMinute);
flag = false;
break;
}
needMinute -= item.WorkMinutes;
}
if (!flag)
{
return actStart;
}
//今天可用时长不够,往后工作日找
DateTime nextStartTime = startTime;
while (flag)
{
//获取下一个工作日
nextStartTime = GetNextWorkDay(weekDay, startTime, calendars.Where(p => p.ProdLine == line.Line).ToList());
weekDay = (int)nextStartTime.DayOfWeek;
//获取前一个工作日的工作时间段数据,level顺排
workPoints = DealWorkDayToLevels(nextStartTime, calendars.Where(p=>p.ProdLine == line.Line && p.WeekDay == weekDay).First(), 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 DateTime GetPreWorkDay(DateTime startTime, List curCalendars)
{
DateTime rtnData = startTime;
//前一天
DateTime preDate = startTime.Date.AddDays(-1);
//前一天是周几
int preWeekDay = (int)preDate.DayOfWeek;
var calendar = curCalendars.FirstOrDefault(p => p.WeekDay == preWeekDay);
string strStart = calendar.ShiftsStart1.ToString("0.00").Replace(".", ":");
//判断前一天是否是工作日
if (preWeekDay == 0 || preWeekDay == 6)//前一天是周六或者周日,需要判断是否调班,需要加班
{
if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "调班"))//前一天是非工作日
{
//递归继续找上一个工作日
rtnData = GetPreWorkDay(preDate, curCalendars);
return rtnData;
}
rtnData = Convert.ToDateTime(preDate.ToString("yyyy-MM-dd") + " " + strStart);
return rtnData;
}
//前一天不是周六周日,需要判断是不是节假日
if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == preDate && p.Ufld1 == "休假"))//是节假日
{
//递归继续找前一个工作日
rtnData = GetPreWorkDay(preDate, curCalendars);
return rtnData;
}
rtnData = Convert.ToDateTime(preDate.ToString("yyyy-MM-dd") + " " + strStart);
return rtnData;
}
///
/// 处理当前日期的工作时间段
///
///
/// 当前产线的工作日历-周几
/// 每天休息记录
///
public List DealWorkDayToLevels(DateTime startTime, ShopCalendarWorkCtr shopCal, List curQtyDtls)
{
//年-月-日
string date = startTime.Date.ToString("yyyy-MM-dd");
//排产记录结束日期是周几
int weekDay = (int)startTime.DayOfWeek;
//工作时间段
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 = CalcEndPoint(startTime, shopCal);
dto.Level = level;
dto.Line = shopCal.ProdLine;
dto.WeekDay = weekDay;
dto.StartPoint = dayStartPoint;
//按照产线休息时间切分时间段
curQtyDtls = curQtyDtls.OrderBy(p => p.RestTimePoint).ToList();
foreach (var item in curQtyDtls)
{
DateTime endPoint = Convert.ToDateTime(date + " " + item.RestTimePoint);
dto.EndPoint = endPoint;
span = dto.EndPoint - dto.StartPoint;
dto.WorkMinutes = (decimal)span.TotalMinutes;
workPoints.Add(dto);
level++;
dto = new LineWorkPointDto();
dto.Level = level;
dto.Line = shopCal.ProdLine;
dto.WeekDay = weekDay;
dto.StartPoint = endPoint.AddMinutes(item.RestTime);
}
dto.EndPoint = dayEndPoint;
span = dto.EndPoint - dto.StartPoint;
dto.WorkMinutes = (decimal)span.TotalMinutes;
workPoints.Add(dto);
return workPoints.OrderBy(p => p.Level).ToList();
}
///
/// 计算当前班次结束时间
///
///
///
///
public DateTime CalcEndPoint(DateTime startTime, ShopCalendarWorkCtr shopCal)
{
//年-月-日
string date = startTime.Date.ToString("yyyy-MM-dd");
string strStart = "";
DateTime dayStartPoint = DateTime.Now;
DateTime dayEndPoint = DateTime.Now;
if (shopCal.ShiftsStart2 != 0 && shopCal.ShiftsHours2 != 0)//设置了第二班
{
strStart = shopCal.ShiftsStart2.ToString("0.00").Replace(".", ":");
dayStartPoint = Convert.ToDateTime(date + " " + strStart);
dayEndPoint = dayStartPoint.AddHours(Convert.ToDouble(shopCal.ShiftsHours2));
return dayEndPoint;
}
strStart = shopCal.ShiftsStart1.ToString("0.00").Replace(".", ":");
dayStartPoint = Convert.ToDateTime(date + " " + strStart);
dayEndPoint = dayStartPoint.AddHours(Convert.ToDouble(shopCal.ShiftsHours1));
return dayEndPoint;
}
///
/// 计算第一条产线开始时间
///
/// 工单
/// 关键工序
/// 产线明细
/// 排产结果
///
public LineStartDto CalcLineStart(WorkOrdMaster workOrd, int op, List prodLines, List scheduleResults)
{
LineStartDto dto = new LineStartDto();
//获取工序对应的产线,根据优先级排序
var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p=>p.Sequence).ToList();
//第一条产线:使用工单OrdDate计算开始排产时间,不需要考虑提前期
//获取当前产线排产结束时间
var schedule = scheduleResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
dto.Line = lines[0].Line;
dto.Op = op;
dto.StartTime = schedule == null ? workOrd.OrdDate.GetValueOrDefault() : (schedule.WorkEndTime <= workOrd.OrdDate ? workOrd.OrdDate.GetValueOrDefault() : schedule.WorkEndTime);
//循环其他产线
for (int i = 1; i < lines.Count; i++)
{
schedule = scheduleResults.Where(p => p.Line == lines[i].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
DateTime StartTime = schedule == null ? workOrd.OrdDate.GetValueOrDefault() : (schedule.WorkEndTime <= workOrd.OrdDate ? workOrd.OrdDate.GetValueOrDefault() : schedule.WorkEndTime);
if (StartTime < dto.StartTime)
{
dto.Line = lines[i].Line;
dto.StartTime = StartTime;
}
}
return dto;
}
}
}