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 _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
)
{
_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;
}
#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)
{
//记录工厂id
domain = factoryid;
//记录排产开始时间
scheTime = startTime;
//获取工单领料单
var nbrMasterList = _nbrMaster.Select(x => x.Domain == domain && x.Type.ToUpper() == "SM" && workOrds.Select(s => s.WorkOrd).Contains(x.WorkOrd)).ToList();
workOrds = workOrds.OrderBy(p => p.Priority).ToList();
//记录工单数据
List dbWorkOrds = workOrds.Select(p => new WorkPriorityDto {
WorkOrd = p.WorkOrd,
Priority = p.Priority
}).ToList();
//处理工单优先级:正常投产工单优先级最高,其次是前处理状态为部分完成/全部完成,其他按照工单优先级排
decimal ytc = 1m;
decimal ktc = 10000m;
decimal bktc = 20000m;
for (int i = 0; i < workOrds.Count; i++)
{
//正在投产
if (workOrds[i].Status.ToUpper() == "W")
{
workOrds[i].Priority = ytc;
ytc++;
continue;
}
var curNbrs = nbrMasterList.Where(p => p.WorkOrd == workOrds[i].WorkOrd).ToList();
//存在领料单:不需要前处理-领料单已关闭,需要前处理-状态为部分完成或者全部完成
if (curNbrs.Any() && !curNbrs.Exists(p => (p.TransType != "PrevProcess" && p.Status.ToUpper() != "C") || (p.TransType == "PrevProcess" && p.Ufld1 != "completed" && p.Ufld1 != "50")))
{
workOrds[i].Priority = ktc;
ktc++;
continue;
}
//不存在领料单或者领料单未关闭或者前处理状态不为部分完成/全部完成
if (!curNbrs.Any() || curNbrs.Exists(p => (p.TransType != "PrevProcess" && p.Status.ToUpper() != "C") || (p.TransType == "PrevProcess" && p.Ufld1 != "completed" && p.Ufld1 != "50")))
{
workOrds[i].Priority = bktc;
bktc++;
continue;
}
}
//特殊工单指定时间开工,不需要计算产能,只需要计算时长即可
var tsWorkOrds = workOrds.Where(p => !string.IsNullOrEmpty(p.Typed)).ToList();
//正常工单
var zcWorkOrds = workOrds.Where(p => string.IsNullOrEmpty(p.Typed)).ToList();
//先删除计划开工时间在当前日期之前且状态不为"w"的工单日计划
List delWorks = workOrds.Where(p => p.OrdDate < scheTime && p.Status.ToUpper() != "W").ToList();
if (delWorks.Any())
{
_periodSequenceDet.Delete(p => delWorks.Select(m=>m.WorkOrd).Contains(p.WorkOrds));
_scheduleResultOpMaster.Delete(p => delWorks.Select(m => m.WorkOrd).Contains(p.WorkOrd));
_workOrdSetupTimeRecord.Delete(p=> delWorks.Select(m => m.WorkOrd).Contains(p.WorkOrd));
}
//2、获取数据
//获取工单工艺路径数据
List workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.ParentOp == 0 && p.Domain == domain && p.Status != "C" && p.IsActive);
//特殊工单的工艺路线
List tsWoRoutings = workOrdRoutings.Where(p => tsWorkOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList();
//获取物料对应的生产线信息:物料、工序对应的生产线
List prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == domain && p.IsActive);
List lines = prodLines.Select(p => p.Line).ToList();
//获取非标准产线人员配置
prodLineDetailRunCrews = _prodLineDetailRunCrew.Select(x => prodLines.Select(p => p.RecID).Contains(x.ProdLineDetailRecID) && x.IsActive && x.Domain == domain).ToList();
//获取加班设置
resourceOccupancyTimes = _resourceOccupancyTime.Select(x => prodLines.Select(p => p.Line).Contains(x.Resource) && x.IsActive && x.Domain == domain && x.StartTime.Value > DateTime.Now).ToList();
//获取工作日历数据:产线的工作日历+默认的工作日历
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())
{
zcWorkOrds = zcWorkOrds.Where(p => !exceptions.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList();
tsWorkOrds = tsWorkOrds.Where(p => !exceptions.Select(m => m.WorkOrd).Contains(p.WorkOrd)).ToList();
}
//3、排产
//生产周期
List periodSequenceDtls = new List();
//排产记录表
List scheduleMasters = new List();
//记录排产结果
List allResults = new List();
//记录特殊工单排产结果
List ypcWorkOrds = new List();
//按照优先级排序
zcWorkOrds = zcWorkOrds.OrderBy(p=>p.Priority).ToList();
for (int i = 0; i < zcWorkOrds.Count; i++)
{
//记录产线占用情况
allResults.AddRange(scheduleMasters);
//当前工单工艺路线主产线的关键工序
var curRoutings = workOrdRoutings.Where(p => p.WorkOrd == zcWorkOrds[i].WorkOrd && p.ItemNum == zcWorkOrds[i].ItemNum && p.ParentOp == 0 && p.MilestoneOp).OrderBy(p => p.OP).ToList();
//当前工单的产线明细
var curProdLines = prodLines.Where(p => p.Part == zcWorkOrds[i].ItemNum).ToList();
//产线排产
LineSchedule(zcWorkOrds[i], curRoutings, curProdLines, periodSequenceDtls, scheduleMasters, allResults, tsWorkOrds, ypcWorkOrds, tsWoRoutings);
}
//判断是否有特殊工单没有排产,如果有,则对剩余特殊工单排产
var notSchedules = tsWorkOrds.Where(p => !ypcWorkOrds.Contains(p.WorkOrd)).ToList();
List workDtos = new List();
foreach (var item in notSchedules)
{
//注:特殊工单暂时只考虑连续生产,不考虑暂停
//特殊工单工序
var curOp = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First();
//特殊工单排产开始时间
DateTime tStartTime = item.OrdDate.GetValueOrDefault() < scheTime ? scheTime : item.OrdDate.GetValueOrDefault();
//获取特殊工单开始排产时间记录
var curRecord = timeRecords.FirstOrDefault(p=>p.WorkOrd == item.WorkOrd && p.Op == curOp.OP);
if (curRecord == null)
{
//添加记录
inserts.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = tStartTime,
CreateTime = DateTime.Now
});
//记录工序开始准备时间记录
timeRecords.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = tStartTime,
CreateTime = DateTime.Now
});
}
//如果产线有准备时间记录且准备开始时间在当前时间之前,说明已在排产中,此时开始排产日期为当前时间
else if (curRecord != null && curRecord.StartTime < scheTime)
{
tStartTime = scheTime;
}
//计算开始排产时间或者上次报工时间到当前时间点的预估产能
decimal sumTimes = 0m;
//工单没有报工记录
if (curOp.Last == null && item.Status.ToUpper() == "W")
{
if (curRecord == null)
{
//工单没有开始时间记录,取预计开工时间到当前时间的有效时长
sumTimes = CalcTimeMins(tStartTime, scheTime, item.ProdLine);
}
else
{
//工单有开始时间记录,取开始时间到当前时间的有效时长
sumTimes = CalcTimeMins(curRecord.StartTime, scheTime, item.ProdLine);
}
}
else if(curOp.Last != null)
{
//工单有报工记录,取最新报工时间到当前时间的有效时长
sumTimes = CalcTimeMins(curOp.Last.GetValueOrDefault(), scheTime, item.ProdLine);
}
//计算预估数量
decimal planQty = Math.Ceiling(sumTimes / (item.LbrVar * 60) * item.QtyOrded);
//剩余排产数量 = 工单数量 - 已报工数量 - 计划数量
decimal qtyNeed = item.QtyOrded - curOp.QtyComplete - planQty;
qtyNeed = qtyNeed < 0 ? 0 : qtyNeed;
//剩余工作时长(分钟)
decimal usedTime = Math.Ceiling(qtyNeed / item.QtyOrded * item.LbrVar * 60);
workDtos.Add(new WorkOrdMstDto
{
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
QtyOrded = qtyNeed,
OrdDate = tStartTime,
ProdLine = item.ProdLine,
LbrVar = usedTime,
Worked = 0,
QtyWorked = 0,
Op = curOp.OP,
WaitTime = curOp.WaitTime * 60
});
}
TsLineSchedule(workDtos, periodSequenceDtls, scheduleMasters);
//回写工单优先级
foreach (var item in workOrds)
{
var curInfo = dbWorkOrds.FirstOrDefault(p => p.WorkOrd == item.WorkOrd);
if (curInfo != null)
{
item.Priority = curInfo.Priority;
}
}
//取数开始时间为当前日期
DateTime beginDate = DateTime.Now.Date;
DateTime endDate = DateTime.Now.Date.AddDays(4);
//取三天内的日计划
var thePeriods = periodSequenceDtls.Where(p => p.PlanDate >= beginDate && p.PlanDate <= endDate).ToList();
var workordList = workOrds.Where(x => (x.Status.ToLower() == "r" || x.Status.ToLower() == "w") && thePeriods.Select(s => s.WorkOrds).Contains(x.WorkOrd)).ToList();
var theNbrMasters = nbrMasterList.Where(p => thePeriods.Select(s => s.WorkOrds).Contains(p.WorkOrd));
//更新当天日计划的计划数量,排产数量
foreach (var item in periodSequenceDtls)
{
item.UDeci5 = item.OrdQty.GetValueOrDefault();
//获取历史排产数据
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;
//未来三天的日计划(工单下达,且前处理完成)状态设为r
if (item.PlanDate >= beginDate && item.PlanDate <= endDate && workordList.Exists(p => p.WorkOrd == item.WorkOrds))
{
//工单不需要前处理的领料单全部出库且工单需要前处理的领料单前处理完成,则日计划自动下达
var curNbrs = theNbrMasters.Where(p => p.WorkOrd == item.WorkOrds).ToList();
if (curNbrs.Any() && !curNbrs.Exists(p=>(p.TransType != "PrevProcess" && p.Status.ToLower() != "c") || (p.TransType == "PrevProcess" && p.Ufld1 != "completed" && p.Ufld1 !="50")))
{
item.Status = "r";
}
}
}
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").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();
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").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.PlanDate = scheTime.Date;
seqInfo.OrdQty = opSeqs.Sum(p => p.OrdQty) + (dtSeq != null ? dtSeq.OrdQty : 0m);
seqInfo.CompQty = opSeqs.Sum(p=>p.CompQty) + (dtSeq != null ? dtSeq.CompQty : 0m);
upSequenceDets.Add(seqInfo);
//获取提前投产的日计划
var opSchs = wlSchedules.Where(p => p.WorkOrd == gdh && p.Op == op && opSeqs.Select(m => m.PlanDate).Contains(p.WorkDate)).ToList();
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);
}
}
}
}
//更新提前投产的日计划
if (upSequenceDets.Any())
{
_periodSequenceDet.Update(upSequenceDets);
}
if (upScheduleResults.Any())
{
_scheduleResultOpMaster.Update(upScheduleResults);
}
//删除当天的日计划
if (delSequenceDets.Any())
{
_periodSequenceDet.Delete(delSequenceDets);
}
if (delScheduleResults.Any())
{
_scheduleResultOpMaster.Update(delScheduleResults);
}
//删除当前日期下一天开始的工单排产记录
_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").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);
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)
{
if (!string.IsNullOrEmpty(item.Typed))//特殊工单
{
//校验有没有维护工艺路线即可
var routings = workOrdRoutings.Where(p=>p.WorkOrd == item.WorkOrd && p.ItemNum == item.ItemNum).ToList();
if (!routings.Any())
{
entity = new ScheduleExceptionMaster();
entity.Domain = item.Domain;
entity.WorkOrd = item.WorkOrd;
entity.ItemNum = item.ItemNum;
entity.CreateTime = DateTime.Now;
entity.Remark = "排产异常:工单没有维护工艺路线,请维护后再操作!";
entity.Type = type == 1 ? "自动排产" : "手动排产";
entity.OptTime = optTime;
exceptions.Add(entity);
}
//获取当前产线维护的工作日历
var lineCals = calendars.Where(p => p.ProdLine == item.ProdLine).ToList();
//获取默认工作日历
var mrCals = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine)).ToList();
if (!lineCals.Any())//当前产线未维护工作日历
{
if (!mrCals.Any() || mrCals.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 = "排产异常:产线[" + item.ProdLine + "]没有维护工作日历且标准工作日历未维护完全,请维护后再操作!";
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 = "排产异常:产线[" + item.ProdLine + "]工作日历没有维护完整,请维护后再操作!";
entity.Type = type == 1 ? "自动排产" : "手动排产";
entity.OptTime = optTime;
exceptions.Add(entity);
}
continue;
}
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 tsWorkOrds,List ypcWorkOrds, List tsWoRoutings)
{
//生产周期
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;
//记录特殊工单:获取第一天是否有特殊工单
List fstWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.Priority).ToList();
List workDtos = new List();
//记录排产开始第二天及以后安排的特殊工单
var secWOMasters = new List();
//特殊工单总清场时长(分钟)
decimal sumCleanTimes = 0m;
//最后一个特殊工单的清场时长(分钟)
decimal lstCleanTime = 0m;
//新增特殊工单最后一个清场时长
decimal secCleanTime = 0m;
//特殊工单排产需要考虑的时长
decimal sumTsTimes = 0m;
if (fstWOMasters.Any())
{
foreach (var item in fstWOMasters)
{
//注:特殊工单暂时只考虑连续生产,不考虑暂停
DateTime tsStartTime = workStartTime;
var first = fstWOMasters.First();
if (item != first)
{
tsStartTime = item.OrdDate.GetValueOrDefault() < scheTime ? scheTime : item.OrdDate.GetValueOrDefault();
}
//特殊工单工序
var curOp = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First();
//获取特殊工单开始排产时间记录
var curRecord = timeRecords.FirstOrDefault(p => p.WorkOrd == item.WorkOrd && p.Op == curOp.OP);
//如果产线有准备时间记录且准备开始时间在当前时间之前,说明已在排产中,此时不需要重新获取产线
if (curRecord != null && curRecord.StartTime < tsStartTime)
{
tsStartTime = workStartTime;
}
else
{
if (curRecord == null)
{
//添加记录
inserts.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = tsStartTime,
CreateTime = DateTime.Now
});
//记录工序开始准备时间记录
timeRecords.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = tsStartTime,
CreateTime = DateTime.Now
});
}
else
{
//更新准备开始时间记录
timeRecords.ForEach(p =>
{
p.StartTime = p.WorkOrd == curRecord.WorkOrd && p.Op == curRecord.Op ? tsStartTime : p.StartTime;
});
//存在开始准备时间记录,则更新
curRecord.StartTime = tsStartTime;
updates.Add(curRecord);
}
}
//计算开始排产时间或者上次报工时间到当前时间点的预估产能
decimal sumWorkTimes = 0m;
//工单没有报工记录
if (curOp.Last == null && item.Status.ToUpper() == "W")
{
if (curRecord == null)
{
//工单没有开始时间记录,取预计开工时间到当前时间的有效时长
sumWorkTimes = 0m;
}
else
{
//工单有开始时间记录,取开始时间到当前时间的有效时长
sumWorkTimes = CalcTimeMins(curRecord.StartTime, workStartTime, item.ProdLine);
}
}
else if(curOp.Last != null)
{
//工单有报工记录,取最新报工时间到当前时间的有效时长
sumWorkTimes = CalcTimeMins(curOp.Last.GetValueOrDefault(), workStartTime, item.ProdLine);
}
//计算预估数量
decimal planQty = Math.Ceiling(sumWorkTimes / (item.LbrVar * 60) * item.QtyOrded);
//剩余排产数量 = 工单数量 - 已报工数量 - 计划数量
decimal qtyNeed = item.QtyOrded - curOp.QtyComplete - planQty;
qtyNeed = qtyNeed < 0 ? 0 : qtyNeed;
//剩余工作时长(分钟)
decimal usedTime = Math.Ceiling(qtyNeed / item.QtyOrded * item.LbrVar * 60);
workDtos.Add(new WorkOrdMstDto
{
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
QtyOrded = qtyNeed,
LbrVar = usedTime,
Worked = 0,
QtyWorked = 0,
Op = curOp.OP,
WaitTime = curOp.WaitTime * 60,
Priority = item.Priority
});
sumCleanTimes += curOp.WaitTime * 60;
}
//获取最后一个特殊工单的清场时长
var last = fstWOMasters.Last();
lstCleanTime = tsWoRoutings.Where(p => p.WorkOrd == last.WorkOrd).OrderByDescending(p => p.OP).First().WaitTime * 60;
//特殊工单排产需要考虑的时长=特殊工单工作时长(分钟)+除最后一个特殊工单之外的清场时长
sumTsTimes = workDtos.Sum(p => p.LbrVar) + sumCleanTimes - lstCleanTime;
}
//工单排产第一天标识
bool isFstDay = true;
//产线排产开始当天安排了特殊工单,则先安排特殊工单
while (sumTsTimes > 0)
{
secWOMasters.Clear();
//获取当天的产能
LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls);
//获取当天的工作时间段
var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
if (curCalendar == null)
{
curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
}
List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls);
//特殊工单排产第一天之后:如果当天的可用产能大于特殊工单生产时长,此时需要考虑当天是否安排了其他的特殊工单
if (dto.EffTime > sumTsTimes && !isFstDay)
{
//获取特殊工单
secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.Priority).ToList();
if (secWOMasters.Any())
{
sumCleanTimes = 0m;
//新增特殊工单最后一个清场时长
secCleanTime = 0m;
//生产时长
decimal sumLbrVar = 0m;
//记录新增待排产特殊工单
foreach (var item in secWOMasters)
{
//注:特殊工单暂时只考虑连续生产,不考虑暂停
var curOp = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First();
//获取特殊工单开始排产时间记录
var curRecord = timeRecords.FirstOrDefault(p => p.WorkOrd == item.WorkOrd && p.Op == curOp.OP);
if (curRecord == null)
{
//添加记录
inserts.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
//记录工序开始准备时间记录
timeRecords.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
}
//剩余排产数量
decimal qtyNeed = item.QtyOrded - curOp.QtyComplete;
//剩余工作时长(分钟)
decimal usedTime = Math.Ceiling(qtyNeed / item.QtyOrded * item.LbrVar * 60);
workDtos.Add(new WorkOrdMstDto
{
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
QtyOrded = qtyNeed,
LbrVar = usedTime,
Worked = 0,
QtyWorked = 0,
Op = curOp.OP,
WaitTime = curOp.WaitTime * 60,
Priority = item.Priority
});
sumLbrVar += usedTime;
sumCleanTimes += curOp.WaitTime * 60;
}
//获取最后一个特殊工单的清场时长
var last = secWOMasters.Last();
secCleanTime = tsWoRoutings.Where(p => p.WorkOrd == last.WorkOrd).OrderByDescending(p => p.OP).First().WaitTime * 60;
//特殊工单待排产时长=前一天特殊工单到最后一个清场时长+新增的特殊工单生产时长+新增特殊工单除最后一个清场时长之外的清场时长之和
sumTsTimes += (lstCleanTime + sumLbrVar + sumCleanTimes - secCleanTime);
lstCleanTime = secCleanTime;
}
}
//特殊工单排序
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
//当天的可用生产时长满足特殊工单生产时长+清场时间(最后一个工单清场时间除外)
if (dto.EffTime >= sumTsTimes)
{
//可用生产时长(分钟)
decimal remainTime = dto.EffTime;
DateTime beginTime = workStartTime;
DateTime endTime = dto.EndTime;
//获取最后一个工单
var lastWork = workDtos.Last();
//开始或结束时间所处时间段
var curPoint = new LineWorkPointDto();
//特殊工单排产
foreach (var item in workDtos)
{
//当前工单已排产,跳过
if (item.LbrVar == item.Worked)
{
continue;
}
//当前工单排产需要时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长
if (remainTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
remainTime -= effMins;
}
remainTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - remainTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
remainTime = 0;
}
//最后一个工单的清场时长
lstCleanTime = 0m;
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else {
//当前工单还需工作时长+工单的清场时长
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
remainTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.FirstOrDefault(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
curPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
//存在后续工作区间
if (curPoint != null)
{
endTime = curPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
//当天产能完全占用,且最后一个工单的清场时间刚好下班或者在下班后
if (endTime >= dto.EndTime)
{
sumTsTimes = 0;//排产完毕,特殊工单时长置0
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = true;
//特殊工单排产完成,占用了当天的全部产能,则需要判断下一个工作日是否存在特殊工单,如果存在,则需要继续排特殊工单
secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.Priority).ToList();
//记录新增待排产特殊工单
if (secWOMasters.Any())
{
sumCleanTimes = 0m;
//新增特殊工单最后一个清场时长(分钟)
secCleanTime = 0m;
//生产时长
decimal sumLbrVar = 0m;
foreach (var item in secWOMasters)
{
var curOp = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First();
//获取特殊工单开始排产时间记录
var curRecord = timeRecords.FirstOrDefault(p => p.WorkOrd == item.WorkOrd && p.Op == curOp.OP);
if (curRecord == null)
{
//添加记录
inserts.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
//记录工序开始准备时间记录
timeRecords.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
}
//剩余排产数量
decimal qtyNeed = item.QtyOrded - curOp.QtyComplete;
//剩余工作时长(分钟)
decimal usedTime = Math.Ceiling(qtyNeed / item.QtyOrded * item.LbrVar * 60);
workDtos.Add(new WorkOrdMstDto
{
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
QtyOrded = qtyNeed,
LbrVar = usedTime,
Worked = 0,
QtyWorked = 0,
Op = curOp.OP,
WaitTime = curOp.WaitTime * 60,
Priority = item.Priority
});
sumCleanTimes += curOp.WaitTime * 60;
sumLbrVar += usedTime;
}
//获取最后一个特殊工单的清场时长
var last = secWOMasters.Last();
secCleanTime = tsWoRoutings.Where(p => p.WorkOrd == last.WorkOrd).OrderByDescending(p => p.OP).First().WaitTime * 60;
//特殊工单待排产时长=前一天特殊工单到最后一个清场时长+新增的特殊工单生产时长+新增特殊工单除最后一个清场时长之外的清场时长之和
sumTsTimes += (lstCleanTime + sumLbrVar + sumCleanTimes - secCleanTime);
lstCleanTime = secCleanTime;
}
}
//当天可用生产时长大于特殊工单生产时长+最后一个工单的清场时长
else
{
//排产完毕,特殊工单时长置0
sumTsTimes = 0;
//处理workStartTime
workStartTime = endTime;
}
}
//当天的可用产能不满足特殊工单生产时长,则当天产能全部排特殊工单(至少最后一个特殊工单的生产时长不能满足)
else
{
decimal residueTime = dto.EffTime;//当天产能剩余产能(分钟)
DateTime beginTime = workStartTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长(则当前工单的清场时间必定排完)
if (residueTime >= needTime)
{
//计算工单排产结束时间
var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
residueTime -= needTime;
//处理清场时间,两种场景:
//1、residueTime<=item.WaitTime,结束时间加上剩余清场时长
if (residueTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
//当天可用生产时长还有剩余
if (residueTime > 0)
{
//获取结束时间所处时间段
curPoint = workPoints.FirstOrDefault(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
endTime = nextPoint == null ? endTime : nextPoint.StartPoint;
}
beginTime = endTime;
}
//当天可用产能已全部用完
else {
break;
}
}
//当天剩余产能不满足当前工单的剩余待排产时长
else
{
//计算生产数量
decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
item.Worked += residueTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= residueTime;
residueTime = 0;
break;
}
}
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//记录已排产特殊工单
ypcWorkOrds.AddRange(workDtos.Select(p => p.WorkOrd).ToList());
//正常工单排产
//正常工单产线已排产数量
decimal sumQty = 0m;
//产线准备时间(分钟)
decimal sumTimes = 0m;
fstWOMasters.Clear();
workDtos.Clear();
sumTsTimes = 0m;
lstCleanTime = 0m;
//工单排产第一天标识
isFstDay = true;
//前一天是否有特殊工单未排产完全
bool isFullPC = true;
while (sumQty < lineStart.QtyRemain || sumTsTimes > 0)
{
secWOMasters.Clear();
sumCleanTimes = 0m;
secCleanTime = 0m;
//获取当天的产能
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 (!isFstDay)
{
//获取特殊工单
secWOMasters = tsWorkOrds.Where(p => p.ProdLine == lineStart.Line && p.OrdDate.Value.Date == workStartTime.Date).OrderBy(p => p.Priority).ToList();
if (secWOMasters.Any())
{
//生产时长
decimal sumLbrVar = 0m;
foreach (var item in secWOMasters)
{
var curOp = tsWoRoutings.Where(p => p.WorkOrd == item.WorkOrd).OrderByDescending(p => p.OP).First();
//获取特殊工单开始排产时间记录
var curRecord = timeRecords.FirstOrDefault(p => p.WorkOrd == item.WorkOrd && p.Op == curOp.OP);
if (curRecord == null)
{
//添加记录
inserts.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
//记录工序开始准备时间记录
timeRecords.Add(new WorkOrdSetupTimeRecord
{
Domain = item.Domain,
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
Op = curOp.OP,
Line = item.ProdLine,
StartTime = item.OrdDate.GetValueOrDefault(),
CreateTime = DateTime.Now
});
}
//剩余排产数量
decimal qtyNeed = item.QtyOrded - curOp.QtyComplete;
//剩余工作时长(分钟)
decimal usedTime = Math.Ceiling(qtyNeed / item.QtyOrded * item.LbrVar * 60);
workDtos.Add(new WorkOrdMstDto
{
WorkOrd = item.WorkOrd,
ItemNum = item.ItemNum,
QtyOrded = qtyNeed,
LbrVar = usedTime,
Worked = 0,
QtyWorked = 0,
Op = curOp.OP,
WaitTime = curOp.WaitTime * 60,
Priority = item.Priority
});
sumLbrVar += usedTime;
sumCleanTimes += curOp.WaitTime * 60;
}
//获取最后一个特殊工单的清场时长
var last = secWOMasters.Last();
secCleanTime = tsWoRoutings.Where(p => p.WorkOrd == last.WorkOrd).OrderByDescending(p => p.OP).First().WaitTime * 60;
//特殊工单待排产时长=前一天特殊工单到最后一个清场时长+新增的特殊工单生产时长+新增特殊工单除最后一个清场时长之外的清场时长之和
sumTsTimes += (lstCleanTime + sumLbrVar + sumCleanTimes - secCleanTime);
lstCleanTime = secCleanTime;
}
}
//排产开始时,需要先减去产线准备时间(产线提前期满足的当天排产)
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)
{
//当天有特殊工单,且当天的产能不能满足工单剩余需排产数量,则优先排特殊工单
if (sumTsTimes > 0)
{
//特殊工单排序
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
DateTime beginTime = workStartTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
//当天的可用生产时长满足特殊工单生产时长+清场时长(最后一个特殊工单的清场时长除外)
if (residueTime >= sumTsTimes)
{
//获取最后一个工单
var lastWork = workDtos.Last();
//开始或结束时间所处时间段
var curPoint = new LineWorkPointDto();
//特殊工单排产
foreach (var item in workDtos)
{
//当前工单已排产,跳过
if (item.LbrVar == item.Worked)
{
continue;
}
//当前工单排产需要时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长,需要过滤掉休息时间
if (residueTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
residueTime -= effMins;
}
//剩余可用生产时长
residueTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
residueTime = 0;
}
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else
{
//当前工单还需工作时长+清场时长
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
curPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
//存在后续工作区间
if (curPoint != null)
{
endTime = curPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
//剩余生产时长大于0,继续排正常工单(剩余产能<工单剩余待排产数量,当天不需要考虑该工单的清场时间)
if (residueTime >0)
{
//处理正常工单开工时间
workStartTime = endTime;
//剩余产能继续排正常工单
sumAmount = Math.Ceiling(dto.Rate * residueTime / 60);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = sumAmount,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = sumAmount,
WorkStartTime = workStartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += sumAmount;
}
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
//排产完毕,特殊工单时长置0
sumTsTimes = 0;
isFullPC = true;
//最后一个工单的清场时长
lstCleanTime = 0m;
}
//当天的可用产能不满足特殊工单生产时长(最后一个工单的生产时长必定不满足)
else
{
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长,则当前工单的清场时长必定排完
if (residueTime >= needTime)
{
//计算工单排产结束时间
var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
residueTime -= needTime;
//处理清场时间,两种场景:
//1、residueTime<=item.WaitTime,结束时间加上剩余清场时长
if (residueTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//剩余生产时长
residueTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
//当天可用生产时长还有剩余
if (residueTime > 0)
{
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
endTime = nextPoint == null ? endTime : nextPoint.StartPoint;
}
beginTime = endTime;
}
//当天剩余产能排完
else {
break;
}
}
//当天剩余产能不满足当前工单的剩余待排产时长
else
{
//计算生产数量
decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
item.Worked += residueTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= residueTime;
residueTime = 0m;
break;
}
}
//当天产能不能满足特殊工单排产
isFullPC = false;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//当天没有特殊工单,直接进行正常工单排产(当天产能<工单剩余待排产数量,当天不需要考虑该工单的清场时间)
else
{
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = sumAmount,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = sumAmount,
WorkStartTime = dto.StartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += sumAmount;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//当天的剩余产能可满足正常工单剩余待排产数量,则先排正常工单,需要考虑清场时间,如果有特殊工单,则后排
else
{
#region 正常工单排产-开始
//剩余需要排产的数量
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);
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
});
#endregion 正常工单排产-结束
//当天排完正常工单后的剩余生产时长(分钟)
decimal restTime = residueTime < 0 ? 0 : residueTime;
//当天存在特殊工单
if (sumTsTimes > 0)
{
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
//当天剩余生产时长大于0,特殊工单排产
if (restTime > 0)
{
//处理正常工单排产结束时间<=>特殊工单排产开始时间
curPoint = workPoints.Find(p => p.StartPoint <= workEndTime && workEndTime <= p.EndPoint);
if (workEndTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
if (nextPoint != null)
{
workEndTime = nextPoint.StartPoint;
}
}
#region 特殊工单排产-开始
DateTime beginTime = workEndTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
//当天的剩余生产时长满足特殊工单生产时长+清场时长(最后一个工单清场时长除外)
if (restTime >= sumTsTimes)
{
//获取最后一个工单
var lastWork = workDtos.Last();
//特殊工单排产
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单还需工作时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长
if (restTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
restTime -= effMins;
}
restTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - restTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
restTime = 0;
}
//最后一个工单的清场时长
lstCleanTime = 0m;
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else
{
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
restTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
curPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
if (curPoint != null)//存在后续工作区间
{
endTime = curPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
sumTsTimes = 0;//排产完毕,特殊工单时长置0
isFullPC = true;
}
//当天的剩余生产时长不满足特殊工单生产时长(至少最后一个特殊工单的生产时长不能满足)
else
{
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长(则当前工单的清场时长必定满足)
if (restTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
restTime -= needTime;
//处理清场时间,两种场景:
//1、restTime<=item.WaitTime,结束时间加上剩余清场时长
if (restTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - restTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
restTime = 0;
}
//2、restTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
restTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
if (restTime > 0)
{
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint )
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
if (nextPoint != null)
{
endTime = nextPoint.StartPoint;
}
beginTime = endTime;
}
}
//当天产能已全部用完
else
{
break;
}
}
//当天剩余产能不满足当前工单的剩余待排产时长
else
{
//计算生产数量
decimal qty = Math.Ceiling(restTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
item.Worked += restTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= restTime;
restTime = 0m;
break;
}
}
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
//当天特殊工单未排产完全
isFullPC = false;
}
#endregion 特殊工单排产-结束
}
//当天剩余生产时长为0,由于存在特殊工单,则循环下一个工作日进行排产
else
{
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
//当天特殊工单未排产完全
isFullPC = false;
}
}
}
//记录提前期
sumTimes = lineStart.setupTime * 60;
}
//当天可用生产时长不能满足或者刚好满足提前期
else
{
//当天的可用生产时长不能满足提前期
sumTimes += dto.EffTime;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//产线提前期满足的下一个工作日排产
else
{
//如果前一天的特殊工单没排产完,则先排特殊工单,如果当天还安排有特殊工单,则将特殊工单提前
if (!isFullPC)
{
//当天可用生产时长(分钟)
decimal residueTime = dto.EffTime;
DateTime beginTime = workStartTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
//当天的可用生产时长满足特殊工单生产时长+清场时长(最后一个工单清场时间除外)
if (residueTime >= sumTsTimes)
{
//获取最后一个工单
var lastWork = workDtos.Last();
//开始或结束时间所处时间段
var curPoint = new LineWorkPointDto();
//特殊工单排产
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产,跳过
{
continue;
}
//当前工单还需工作时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长
if (residueTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
residueTime -= effMins;
}
residueTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
residueTime = 0;
}
//最后一个工单的清场时长
lstCleanTime = 0m;
break;
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else
{
//当前工单还需工作时长+清场时长
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//剩余可用时长
residueTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
//存在后续工作区间
if (nextPoint != null)
{
endTime = nextPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
//排产完毕,特殊工单时长置0
sumTsTimes = 0;
//当天特殊工单全部排产完成
isFullPC = true;
//特殊工单排产完成,最后一个工单的清场时长置0
lstCleanTime = 0m;
//剩余可用生产时长
residueTime = residueTime < 0 ? 0 : residueTime;
//特殊工单处理完成,剩余生产时长大于0且还有正常工单待排产,则继续排正常工单
if (residueTime > 0 && sumQty < lineStart.QtyRemain)
{
workStartTime = beginTime;//排产开始时间
//剩余产能继续排正常工单
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 = workStartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += sumAmount;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
//剩余产能满足正常工单待排产数量,此时需要排清场时长
else
{
//剩余需要排产的数量
decimal residueQty = lineStart.QtyRemain - sumQty;
//剩余数量生产需要时长(分钟)
decimal workTime = Math.Ceiling(residueQty / dto.Rate * 60);
//处理剩余需要生产时长:防止向上取整导致超过residueTime
workTime = workTime > residueTime ? residueTime : workTime;
curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
span = curPoint.EndPoint - workStartTime;
//当天工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= workTime)
{
endTime = 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)
{
endTime = 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;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>lineStart.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
workTime = lineStart.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= workTime)
{
endTime = endTime.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)
{
endTime = 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 = endTime,
CreateTime = DateTime.Now
});
}
}
else {
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//当天的可用产能不满特殊工单生产时长,则当天产能全部排特殊工单(至少最后一个特殊工单的生产时长不能满足)
else
{
//特殊工单排产
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长(则当前工单的清场时间必定排完)
if (residueTime >= needTime)
{
//计算工单排产结束时间
var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可满足产能
if (effMins >= needTime)
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
residueTime -= needTime;
//处理清场时间,两种场景:
//1、residueTime<=item.WaitTime,结束时间加上剩余清场时长
if (residueTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
//当天可用生产时长还有剩余
if (residueTime > 0)
{
//获取结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
endTime = nextPoint == null ? endTime : nextPoint.StartPoint;
}
beginTime = endTime;
}
//当天可用产能已全部用完
else
{
break;
}
}
//当天剩余产能不满足当前工单的剩余待排产时长
else
{
//计算生产数量
decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
item.Worked += residueTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= residueTime;
residueTime = 0m;
break;
}
}
//当天特殊工单未排产完成
isFullPC = false;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//当天之前的特殊工单全部排产完毕
else {
//已排产数量+当天的产能小于工单数量,当天的产能需要全部排产
if (sumQty + dto.ProductQty < lineStart.QtyRemain)
{
//当天有特殊工单,先排特殊工单
if (sumTsTimes > 0)
{
//当天剩余产能(分钟)
decimal residueTime = dto.EffTime;
DateTime beginTime = workStartTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
//当天的可用产能满足特殊工单生产时长+清场时长(最后一个工单清场时间除外)
if (residueTime >= sumTsTimes)
{
var lastWork = workDtos.Last();
//开始或结束时间所处时间段
var curPoint = new LineWorkPointDto();
//特殊工单排产
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单排产需要时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长
if (residueTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
residueTime -= effMins;
}
residueTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
residueTime = 0;
}
//最后一个工单的清场时长
lstCleanTime = 0m;
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else
{
//当前工单还需工作时长+工单的清场时长
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
curPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
//存在后续工作区间
if (curPoint != null)
{
endTime = curPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
//剩余可用生产时长
residueTime = residueTime < 0 ? 0 : residueTime;
//特殊工单处理完成,剩余生产时长大于0继续排正常工单
if (residueTime > 0)
{
//处理正常工单开工时间
workStartTime = endTime;
//剩余产能继续排正常工单
decimal sumAmount = Math.Ceiling(dto.Rate * residueTime / 60);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = sumAmount,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = sumAmount,
WorkStartTime = workStartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += sumAmount;
}
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
//排产完毕,特殊工单时长置0
sumTsTimes = 0;
//当天的特殊工单排产完成
isFullPC = true;
//最后一个特殊工单清场时长置0
lstCleanTime = 0m;
}
//当天的可用产能不满足特殊工单生产时长,则当天产能全部排特殊工单(至少最后一个特殊工单的生产时长不能满足)
else
{
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长(则当前工单的清场时间必定排完)
if (residueTime >= needTime)
{
//计算工单排产结束时间
var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
residueTime -= needTime;
//处理清场时间,两种场景:
//1、residueTime<=item.WaitTime,结束时间加上剩余清场时长
if (residueTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
if (residueTime > 0)
{
//获取结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
endTime = nextPoint == null ? endTime : nextPoint.StartPoint;
}
beginTime = endTime;
}
//当天可用产能已全部用完
else
{
break;
}
}
//当天剩余产能不满足当前工单的剩余待排产时长
else
{
//计算生产数量
decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
item.Worked += residueTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= residueTime;
residueTime = 0;
break;
}
}
//当天的特殊工单未排产完成
isFullPC = false;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
}
//不存在特殊工单(此时不想需要考虑清场时间)
else
{
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = dto.ProductQty,
WorkOrds = workOrd.WorkOrd,
Op = lineStart.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = workOrd.WorkOrd,
Line = lineStart.Line,
ItemNum = workOrd.ItemNum,
Op = lineStart.Op,
WorkDate = workStartTime.Date,
WorkQty = dto.ProductQty,
WorkStartTime = dto.StartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//累计已排产数量
sumQty += dto.ProductQty;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
isFullPC = true;
}
}
//剩余产能满足正常工单待排产数量,此时需要排清场时长
else
{
//当天可用生产时长
decimal residueTime = dto.EffTime;
//正常工单生产时长
decimal workTime = 0m;
DateTime workEndTime = workStartTime;
if (sumQty < lineStart.QtyRemain)
{
#region 正常工单排产-开始
//剩余需要排产的数量
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);
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;
}
#endregion 正常工单排产-结束
}
//当天剩余产能(分钟)
residueTime = residueTime < 0 ? 0 : residueTime;
if (sumTsTimes > 0)//存在特殊工单
{
if (residueTime > 0)
{
#region 特殊工单排产-开始
DateTime beginTime = workStartTime;//排产开始时间
DateTime endTime = dto.EndTime;//排产结束时间
workDtos = workDtos.OrderBy(p => p.Priority).ToList();
//当天的可用产能满足特殊工单生产时长+清场时长(最后一个工单清场时间除外)
if (residueTime >= sumTsTimes)
{
//最后一个工单
var lastWork = workDtos.Last();
//开始或结束时间所处时间段
var curPoint = new LineWorkPointDto();
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单排产需要时长
decimal needTime = 0m;
//最后一个特殊工单
if (item.WorkOrd == lastWork.WorkOrd)
{
//最后一个工单的生产时长+清场时长
needTime = lastWork.LbrVar - lastWork.Worked + lastWork.WaitTime;
//剩余可用工作时长满足最后一个特殊工单的生产时长+清场时长
if (residueTime >= needTime)
{
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
//获取排产结束时间所处以及之后的生产时间段
var nextPoints = workPoints.Where(p => p.Level >= curPoint.Level).OrderBy(p => p.Level).ToList();
//处理第一个时间段的开始时间
nextPoints[0].StartPoint = beginTime;
foreach (var p in nextPoints)
{
span = p.EndPoint - p.StartPoint;
//当前工作时间段的有效生产时间
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)
{
endTime = p.StartPoint.AddMinutes((double)needTime);
break;
}
needTime -= effMins;
residueTime -= effMins;
}
//剩余可用时长
residueTime -= needTime;
}
//剩余可用工作时长不能满足清场时长
else
{
//还需排产的清场时间
decimal qcTime = needTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)qcTime);
residueTime = 0;
}
//最后一个工单的清场时长
lstCleanTime = 0m;
}
//最后一个特殊工单之前的工单:剩余排产时长和清场时间全部排产
else
{
//当前工单还需工作时长+工单的清场时长
needTime = item.LbrVar - item.Worked + item.WaitTime;
//计算工单排产结束时间
curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当天工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//下一工单开始时间=当前工单结束时间,如果位于工作区间结尾,则为下一工作区间开始时间
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
//结束时间位于工作区间结尾
if (curPoint != null && endTime == curPoint.EndPoint)
{
//获取后续生产时间段
var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
if (nextPoint != null)//存在后续工作区间
{
endTime = nextPoint.StartPoint;
}
}
beginTime = endTime;
//工单排产完成,排产时长=工单工作时长
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
}
//排产完毕,特殊工单时长置0
sumTsTimes = 0;
//当天的特殊工单以排产完成
isFullPC = true;
//最后一个工单的清场时间置0
lstCleanTime = 0m;
}
//当天的可用产能不满足特殊工单生产时长,则当天产能全部排特殊工单(至少最后一个特殊工单的生产时长不能满足)
else
{
foreach (var item in workDtos)
{
if (item.LbrVar == item.Worked)//当前工单已排产
{
continue;
}
//当前工单剩余待排产时长(分钟)
decimal needTime = item.LbrVar - item.Worked;
//当天剩余产能满足当前工单的剩余待排产时长(则当前工单的清场时间必定排完)
if (residueTime >= needTime)
{
//计算工单排产结束时间
var curPoint = workPoints.Find(p => p.StartPoint <= beginTime && beginTime <= p.EndPoint);
span = curPoint.EndPoint - beginTime;
//当前工作时间段的有效生产时间(分钟)
decimal effMins = (decimal)span.TotalMinutes;
if (effMins >= needTime)//当前工作时间段即可满足产能
{
endTime = beginTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
//当天剩余可用生产时长
residueTime -= needTime;
//处理清场时间,两种场景:
//1、residueTime<=item.WaitTime,结束时间加上剩余清场时长
if (residueTime <= item.WaitTime)
{
//仍需排产的清场时长
decimal otherTime = item.WaitTime - residueTime;
endTime = dto.EndTime.AddMinutes((double)otherTime);
residueTime = 0;
}
//2、residueTime>item.WaitTime,剩余时长大于清场时长,需要过滤休息时间
else
{
//清场时长
needTime = item.WaitTime;
//获取工单排产结束时间所处时间段
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
span = curPoint.EndPoint - endTime;
//当前工作时间段的有效生产时间(分钟)
effMins = (decimal)span.TotalMinutes;
//当前工作时间段即可清场时长
if (effMins >= needTime)
{
endTime = endTime.AddMinutes((double)needTime);
}
else
{
//获取后续生产时间段
var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
//剩余需要工作时长
decimal nextMins = needTime - effMins;
foreach (var p in nextPoints)
{
if (p.WorkMinutes >= nextMins)
{
endTime = p.StartPoint.AddMinutes((double)nextMins);
break;
}
nextMins -= p.WorkMinutes;
}
}
residueTime -= needTime;
}
//处理特殊工单剩余时长减去当前工单待排产时长和清场时长
sumTsTimes -= (item.LbrVar - item.Worked + item.WaitTime);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = beginTime,
WorkEndTime = endTime,
CreateTime = DateTime.Now
});
//当前工单已排产完成,已排产时间=工单生产时长,已排产数量=工单数量
item.Worked = item.LbrVar;
item.QtyWorked = item.QtyOrded;
beginTime = endTime;
if (residueTime > 0)
{
curPoint = workPoints.Find(p => p.StartPoint <= endTime && endTime <= p.EndPoint);
if (endTime == curPoint.EndPoint)
{
var nextPoint = workPoints.FirstOrDefault(p => p.Level == curPoint.Level + 1);
endTime = nextPoint == null ? endTime : nextPoint.StartPoint;
beginTime = endTime;
}
}
//当天可用产能已全部用完
else
{
break;
}
}
else//当天剩余产能不满足当前工单的剩余待排产时长
{
//计算生产数量
decimal qty = Math.Ceiling(residueTime / item.LbrVar * item.QtyOrded);
//记录生产周期
curSequences.Add(new PeriodSequenceDet
{
Domain = domain,
Line = lineStart.Line,
ItemNum = item.ItemNum,
PlanDate = beginTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
curScheduleRsts.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = lineStart.Line,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = beginTime.Date,
WorkQty = qty,
WorkStartTime = beginTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
item.Worked += residueTime;
item.QtyWorked += qty;
//特殊工单剩余待排产时长(分钟)
sumTsTimes -= residueTime;
residueTime = 0;
break;
}
}
//当天的特殊工单未排产完成
isFullPC = false;
//获取下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
}
#endregion 特殊工单排产-结束
}
else
{
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
isFstDay = false;
//当天的特殊工单未排产完成
isFullPC = false;
}
}
}
}
}
}
//记录已排产特殊工单
ypcWorkOrds.AddRange(workDtos.Select(p=>p.WorkOrd).ToList());
}
//记录排产结果
periodsDet.AddRange(curSequences);
scheduleResults.AddRange(curScheduleRsts);
}
///
/// 特殊工单排产:和正常工单不共用产线的特殊工单
///
/// 特殊工单
/// 生产周期
/// 排产结果
public void TsLineSchedule(List workOrds, List periodsDet, List scheduleResults)
{
foreach (var item in workOrds)
{
//工单生产时长(分钟)
decimal sumTimes = item.LbrVar;
//工单的排产开始时间
DateTime workStartTime = item.OrdDate;
//当前产线的工作日历
var mLCalendars = calendars.Where(p => p.ProdLine == item.ProdLine || string.IsNullOrEmpty(p.ProdLine)).ToList();
//当前产线的休息时间设置
var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == item.ProdLine).ToList();
//计算产线实际开始时间
workStartTime = CalcActStartTime(item.ProdLine, workStartTime, mLCalendars, mlqtyWorkDtls);
LineStartDto lineStart = new LineStartDto();
lineStart.RecID = 0;
lineStart.Line = item.ProdLine;
lineStart.Op = item.Op;
lineStart.StartTime = workStartTime;
lineStart.Rate = 0;
while (sumTimes > 0)
{
//获取当天的工作时间段
var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
if (curCalendar == null)
{
curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
}
List workPoints = DealWorkDayToLevels(lineStart.Line, workStartTime, curCalendar, mlqtyWorkDtls);
//获取当天的产能
LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls);
//当天的可用生产时长不能满足特殊工单生产时长
if (dto.EffTime < sumTimes)
{
decimal qty = Math.Ceiling(dto.EffTime / item.LbrVar * item.QtyOrded);
//记录生产周期
periodsDet.Add(new PeriodSequenceDet
{
Domain = domain,
Line = item.ProdLine,
ItemNum = item.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = qty,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
scheduleResults.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = item.ProdLine,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = workStartTime.Date,
WorkQty = qty,
WorkStartTime = dto.StartTime,
WorkEndTime = dto.EndTime,
CreateTime = DateTime.Now
});
//剩余待排产时长
sumTimes -= dto.EffTime;
//已排产数量
item.QtyWorked += qty;
item.Worked += dto.EffTime;
//继续排下一个工作日
workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
}
//最后一天的产能全部占用或只能占用一部分
else
{
//结束时间
DateTime workEndTime = workStartTime;
//需要排产时长=剩余生产时长(分钟) + 清场时长
decimal workTime = sumTimes + item.WaitTime;
//当天可用时长满足生产时长+清场时长
if (dto.EffTime > workTime)
{
//排产开始时间位于哪个时间段
var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
TimeSpan 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;
}
}
}
//当天可用时长满足生产时长,但是不满足清场时长
else {
//剩余清场时长
decimal needTime = workTime - dto.EffTime;
workEndTime = dto.EndTime.AddMinutes((double)needTime);
}
//记录生产周期
periodsDet.Add(new PeriodSequenceDet
{
Domain = domain,
Line = item.ProdLine,
ItemNum = item.ItemNum,
PlanDate = workStartTime.Date,
Period = dto.Period,
OrdQty = item.QtyOrded - item.QtyWorked,
WorkOrds = item.WorkOrd,
Op = item.Op,
IsActive = true,
Status = "",
CreateTime = DateTime.Now
});
//记录排产记录
scheduleResults.Add(new ScheduleResultOpMaster
{
Domain = domain,
WorkOrd = item.WorkOrd,
Line = item.ProdLine,
ItemNum = item.ItemNum,
Op = item.Op,
WorkDate = workStartTime.Date,
WorkQty = item.QtyOrded - item.QtyWorked,
WorkStartTime = workStartTime,
WorkEndTime = workEndTime,
CreateTime = DateTime.Now
});
sumTimes = 0;
item.QtyWorked = item.QtyOrded;
item.Worked = item.LbrVar;
}
}
}
}
///
/// 计算当天排产完成的剩余生产时长
///
/// 排产结束时间
/// 当天的工作时间段
///
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);
}
//剩余待排产数量=工单数量-已报工数量-预估数量
lineStart.QtyRemain = workOrd.QtyOrded - routing.QtyComplete - planQty;
lineStart.QtyRemain = lineStart.QtyRemain < 0 ? 0 : lineStart.QtyRemain;
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();
scheTime = CalcActStartTime(curLine.Line, scheTime, curCalendars, curQtyDtls);
startDto.RecID = curLine.RecID;
startDto.Line = curLine.Line;
startDto.StartTime = scheTime;
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);
}
//剩余待排产数量=工单数量-已报工数量-预估数量
startDto.QtyRemain = workOrd.QtyOrded - routing.QtyComplete - planQty;
startDto.QtyRemain = startDto.QtyRemain < 0 ? 0 : startDto.QtyRemain;
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);
}
}
}
}
}