ProductionScheduleAppService.cs 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. using Business.Business.Dto;
  2. using Business.Core.Utilities;
  3. using Business.Domain;
  4. using Business.Dto;
  5. using Business.EntityFrameworkCore.SqlRepositories;
  6. using Business.ResourceExamineManagement.Dto;
  7. using Business.StructuredDB.Production;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Collections.Immutable;
  11. using System.Configuration;
  12. using System.Linq;
  13. using System.Threading.Tasks;
  14. using System.Transactions;
  15. using Volo.Abp.Application.Services;
  16. using Volo.Abp.MultiTenancy;
  17. using Microsoft.Extensions.Configuration;
  18. using MongoDB.Driver.Linq;
  19. using Amazon.Runtime.Internal.Util;
  20. using IdentityModel.Client;
  21. namespace Business.ResourceExamineManagement
  22. {
  23. /// <summary>
  24. /// 生产排产服务
  25. /// </summary>
  26. public class ProductionScheduleAppService : ApplicationService
  27. {
  28. #region 服务
  29. /// <summary>
  30. /// 物料
  31. /// </summary>
  32. private ISqlRepository<ItemMaster> _itemMaster;
  33. /// <summary>
  34. /// 工单
  35. /// </summary>
  36. private ISqlRepository<WorkOrdMaster> _workOrdMaster;
  37. /// <summary>
  38. /// 工单物料明细
  39. /// </summary>
  40. private ISqlRepository<WorkOrdDetail> _workOrdDetail;
  41. /// <summary>
  42. /// 工单工艺路线明细
  43. /// </summary>
  44. private ISqlRepository<WorkOrdRouting> _workOrdRouting;
  45. /// <summary>
  46. /// 库存主数据
  47. /// </summary>
  48. private ISqlRepository<InvMaster> _invMaster;
  49. /// <summary>
  50. /// 生产线明细
  51. /// </summary>
  52. private ISqlRepository<ProdLineDetail> _prodLineDetail;
  53. /// <summary>
  54. /// 生产周期明细
  55. /// </summary>
  56. private ISqlRepository<PeriodSequenceDet> _periodSequenceDet;
  57. /// <summary>
  58. /// 排产结果明细
  59. /// </summary>
  60. private ISqlRepository<ScheduleResultOpMaster> _scheduleResultOpMaster;
  61. /// <summary>
  62. /// 工作日历数据
  63. /// </summary>
  64. private ISqlRepository<ShopCalendarWorkCtr> _shopCalendarWorkCtr;
  65. /// <summary>
  66. /// 产线休息时间记录表
  67. /// </summary>
  68. private ISqlRepository<QualityLineWorkDetail> _qualityLineWorkDetail;
  69. /// <summary>
  70. /// 产线人员配置表
  71. /// </summary>
  72. private ISqlRepository<ProdLineDetailRunCrew> _prodLineDetailRunCrew;
  73. /// <summary>
  74. /// 加班设置表
  75. /// </summary>
  76. private ISqlRepository<ResourceOccupancyTime> _resourceOccupancyTime;
  77. /// <summary>
  78. /// 加班设置表
  79. /// </summary>
  80. private ISqlRepository<GeneralizedCodeMaster> _generalizedCodeMaster;
  81. /// <summary>
  82. /// 节假日记录表
  83. /// </summary>
  84. private ISqlRepository<HolidayMaster> _holidayMaster;
  85. /// <summary>
  86. /// 排产异常记录
  87. /// </summary>
  88. private ISqlRepository<ScheduleExceptionMaster> _scheduleExceptionMaster;
  89. /// <summary>
  90. /// 雪花算法
  91. /// </summary>
  92. SnowFlake help = new SnowFlake();
  93. private readonly ICurrentTenant _currentTenant;
  94. /// <summary>
  95. /// 工作日历数据
  96. /// </summary>
  97. private List<ShopCalendarWorkCtr> calendars;
  98. /// <summary>
  99. /// 产线休息记录数据
  100. /// </summary>
  101. private List<QualityLineWorkDetail> qualityLines;
  102. /// <summary>
  103. /// 节假日记录数据
  104. /// </summary>
  105. private List<HolidayMaster> holidays;
  106. /// <summary>
  107. /// 生产线UPH设置
  108. /// </summary>
  109. private List<ProdLineDetailRunCrew> prodLineDetailRunCrews;
  110. /// <summary>
  111. /// 加班设置
  112. /// </summary>
  113. private List<ResourceOccupancyTime> resourceOccupancyTimes;
  114. /// <summary>
  115. /// 工厂id
  116. /// </summary>
  117. private string domain = "";
  118. #endregion
  119. #region 构造函数
  120. /// <summary>
  121. /// 构造函数
  122. /// </summary>
  123. public ProductionScheduleAppService(
  124. ISqlRepository<ItemMaster> itemMaster,
  125. ISqlRepository<WorkOrdMaster> workOrdMaster,
  126. ISqlRepository<WorkOrdDetail> workOrdDetail,
  127. ISqlRepository<WorkOrdRouting> workOrdRouting,
  128. ISqlRepository<ProdLineDetail> prodLineDetail,
  129. ISqlRepository<ProdLineDetailRunCrew> prodLineDetailRunCrew,
  130. ISqlRepository<ResourceOccupancyTime> resourceOccupancyTime,
  131. ISqlRepository<PeriodSequenceDet> periodSequenceDet,
  132. ISqlRepository<ScheduleResultOpMaster> scheduleResultOpMaster,
  133. ISqlRepository<InvMaster> invMaster,
  134. ISqlRepository<ShopCalendarWorkCtr> shopCalendarWorkCtr,
  135. ISqlRepository<QualityLineWorkDetail> qualityLineWorkDetail,
  136. ISqlRepository<HolidayMaster> holidayMaster,
  137. ISqlRepository<GeneralizedCodeMaster> generalizedCodeMaster,
  138. ICurrentTenant currentTenant,
  139. ISqlRepository<ScheduleExceptionMaster> scheduleExceptionMaster
  140. )
  141. {
  142. _itemMaster = itemMaster;
  143. _workOrdMaster = workOrdMaster;
  144. _workOrdDetail = workOrdDetail;
  145. _workOrdRouting = workOrdRouting;
  146. _prodLineDetail = prodLineDetail;
  147. _prodLineDetailRunCrew = prodLineDetailRunCrew;
  148. _resourceOccupancyTime = resourceOccupancyTime;
  149. _periodSequenceDet = periodSequenceDet;
  150. _scheduleResultOpMaster = scheduleResultOpMaster;
  151. _invMaster = invMaster;
  152. _shopCalendarWorkCtr = shopCalendarWorkCtr;
  153. _qualityLineWorkDetail = qualityLineWorkDetail;
  154. _holidayMaster = holidayMaster;
  155. _generalizedCodeMaster = generalizedCodeMaster;
  156. _currentTenant = currentTenant;
  157. _scheduleExceptionMaster = scheduleExceptionMaster;
  158. }
  159. #endregion
  160. /// <summary>
  161. /// 执行生产排产
  162. /// </summary>
  163. public async void DoExt()
  164. {
  165. //定时任务排产:获取工厂id
  166. IConfiguration configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
  167. domain = configuration.GetConnectionString("Factory_id");
  168. //获取提前期
  169. var generalizedCodeMaster = _generalizedCodeMaster.Select(x => x.FldName == "SystemConfig" && x.Val == "WorkOrderLockPeriod" && x.Domain == domain).FirstOrDefault();
  170. decimal Udecil = 0;
  171. if (generalizedCodeMaster != null)
  172. {
  173. Udecil = generalizedCodeMaster.UDeci1;
  174. }
  175. //排产取4周工单排产
  176. DateTime dateTime = DateTime.Now.AddDays(30);
  177. DateTime date = DateTime.Now.AddDays((double)Udecil);
  178. var workOrds = _workOrdMaster.Select(x => x.IsActive && x.Domain == domain && x.OrdDate < dateTime && x.OrdDate > date && x.Status == "初始").ToList();
  179. await DoProductSchedule(workOrds, domain, 1);
  180. }
  181. /// <summary>
  182. /// 生产排产
  183. /// </summary>
  184. /// <param name="workOrds">工单:定时任务执行时count=0;资源检查调用count>0</param>
  185. /// <param name="factoryid">工单的工厂id</param>
  186. /// <param name="type">排产类型:1-自动排产;2-手动排产</param>
  187. /// <returns></returns>
  188. public async Task DoProductSchedule(List<WorkOrdMaster> workOrds, string factoryid,int type)
  189. {
  190. //记录工厂id
  191. domain = factoryid;
  192. if (workOrds.Count == 0)//没有工单需要排产
  193. {
  194. return;
  195. }
  196. //获取排产工单的最早计划开工日期
  197. DateTime earlist = DateTime.Now.Date.AddDays(1);
  198. //2、获取数据
  199. //获取工单工艺路径数据
  200. List<WorkOrdRouting> workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.ParentOp == 0 && p.Domain == domain && p.Status != "C" && p.IsActive);
  201. //获取物料对应的生产线信息:物料、工序对应的生产线
  202. List<ProdLineDetail> prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == domain && p.IsActive);
  203. List<string> lines = prodLines.Select(p => p.Line).ToList();
  204. //获取非标准产线人员配置
  205. prodLineDetailRunCrews = _prodLineDetailRunCrew.Select(x => prodLines.Select(p => p.RecID).Contains(x.ProdLineDetailRecID) && x.IsActive && x.Domain == domain).ToList();
  206. //获取加班设置
  207. resourceOccupancyTimes = _resourceOccupancyTime.Select(x => prodLines.Select(p => p.Line).Contains(x.Resource) && x.IsActive && x.Domain == domain && x.StartTime.Value > DateTime.Now).ToList();
  208. //获取生产周期数据
  209. List<PeriodSequenceDet> dbPeriodSequences = _periodSequenceDet.Select(p => lines.Contains(p.Line) && p.PlanDate >= earlist && p.Domain == domain && p.IsActive);
  210. //获取当前日期往后的排产记录数据
  211. List<ScheduleResultOpMaster> dbSchedules = _scheduleResultOpMaster.Select(p => lines.Contains(p.Line) && p.WorkDate >= earlist && p.Domain == domain);
  212. //获取工作日历数据:产线的工作日历+默认的工作日历
  213. calendars = _shopCalendarWorkCtr.Select(p => (lines.Contains(p.ProdLine) || string.IsNullOrEmpty(p.ProdLine)) && p.Domain == domain && p.IsActive);
  214. //获取产线休息记录数据
  215. qualityLines = _qualityLineWorkDetail.Select(p => lines.Contains(p.ProdLine) && p.Domain == domain && p.IsActive);
  216. //获取节假日记录数据
  217. holidays = _holidayMaster.Select(p => p.Domain == domain && p.IsActive && p.Dated >= earlist);
  218. //排产前校验
  219. List<ScheduleExceptionMaster> exceptions = BeforeScheduleCheck(workOrds, workOrdRoutings, prodLines,type);
  220. if (exceptions.Any())
  221. {
  222. using (TransactionScope scope = new TransactionScope())
  223. {
  224. try
  225. {
  226. //记录排产异常数据
  227. _scheduleExceptionMaster.Insert(exceptions);
  228. scope.Complete();
  229. }
  230. catch (Exception ex)
  231. {
  232. new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductSchedule", "保存排产异常数据失败:" + ex.Message, _currentTenant.Id.ToString());
  233. scope.Dispose();
  234. }
  235. }
  236. return;
  237. }
  238. //3、排产
  239. //生产周期
  240. List<PeriodSequenceDet> periodSequenceDtls = new List<PeriodSequenceDet>();
  241. //排产记录表
  242. List<ScheduleResultOpMaster> scheduleMasters = new List<ScheduleResultOpMaster>();
  243. //排产结果
  244. List<ScheduleResultOpMaster> allResults = new List<ScheduleResultOpMaster>();
  245. allResults.AddRange(dbSchedules);
  246. foreach (var item in workOrds)
  247. {
  248. //记录产线占用情况
  249. allResults.AddRange(scheduleMasters);
  250. //当前工单工艺路线主产线的关键工序
  251. var curRoutings = workOrdRoutings.Where(p => p.ItemNum == item.ItemNum && p.ParentOp == 0 && p.MilestoneOp).OrderBy(p=>p.OP).ToList();
  252. //当前工单的产线明细
  253. var curProdLines = prodLines.Where(p => p.Part == item.ItemNum).ToList();
  254. //产线排产
  255. LineSchedule(item, curRoutings, curProdLines, periodSequenceDtls, scheduleMasters, allResults);
  256. List<ScheduleResultOpMaster> scheduleList = scheduleMasters.Where(s => s.WorkOrd == item.WorkOrd).ToList();
  257. if (scheduleList.Any())
  258. {
  259. item.OrdDate = scheduleList.Min(s => s.WorkStartTime.Date);
  260. item.DueDate = scheduleList.Max(s => s.WorkEndTime.Date);
  261. }
  262. }
  263. using (TransactionScope scope = new TransactionScope())
  264. {
  265. try
  266. {
  267. //记录排产数据
  268. _workOrdMaster.Update(workOrds);
  269. _periodSequenceDet.Insert(periodSequenceDtls);
  270. _scheduleResultOpMaster.Insert(scheduleMasters);
  271. scope.Complete();
  272. }
  273. catch (Exception ex)
  274. {
  275. new NLogHelper("ProductionScheduleAppService").WriteLog("DoProductSchedule", "保存排产数据失败:" + ex.Message, _currentTenant.Id.ToString());
  276. scope.Dispose();
  277. }
  278. }
  279. }
  280. /// <summary>
  281. /// 排产前校验
  282. /// </summary>
  283. /// <param name="workOrds">待排产工单</param>
  284. /// <param name="workOrdRoutings">工单工艺路线</param>
  285. /// <param name="prodLines">产线明细</param>
  286. /// <param name="type">排产类型:1-自动排产,2-手动排产</param>
  287. /// <returns></returns>
  288. public List<ScheduleExceptionMaster> BeforeScheduleCheck(List<WorkOrdMaster> workOrds,List<WorkOrdRouting> workOrdRoutings, List<ProdLineDetail> prodLines,int type)
  289. {
  290. List<ScheduleExceptionMaster> exceptions = new List<ScheduleExceptionMaster>();
  291. ScheduleExceptionMaster entity;
  292. foreach (var item in workOrds)
  293. {
  294. var curRoutings = workOrdRoutings.Where(p => p.ItemNum == item.ItemNum && p.MilestoneOp).OrderBy(p=>p.OP).Select(p=>p.OP).ToList();
  295. //判断当前工单主产线是否设置了关键工序
  296. if (!curRoutings.Any())//当前工单没有维护关键工序
  297. {
  298. entity = new ScheduleExceptionMaster();
  299. entity.Domain = item.Domain;
  300. entity.WorkOrd = item.WorkOrd;
  301. entity.ItemNum = item.ItemNum;
  302. entity.CreateTime = DateTime.Now;
  303. entity.Remark = "排产异常:工单的工艺路径没有维护关键工序,请维护后再操作!";
  304. entity.Type = type == 1 ? "自动排产" : "手动排产";
  305. exceptions.Add(entity);
  306. continue;
  307. }
  308. //获取当前工单物料对应的产线信息
  309. var lineDetails = prodLines.Where(x => x.Part == item.ItemNum && curRoutings.Contains(x.Op)).ToList();
  310. if (!lineDetails.Any())
  311. {
  312. entity = new ScheduleExceptionMaster();
  313. entity.Domain = item.Domain;
  314. entity.WorkOrd = item.WorkOrd;
  315. entity.ItemNum = item.ItemNum;
  316. entity.CreateTime = DateTime.Now;
  317. entity.Remark = "排产异常:工单的关键工序没有维护产线数据,请维护后再操作!";
  318. entity.Type = type == 1 ? "自动排产" : "手动排产";
  319. exceptions.Add(entity);
  320. continue;
  321. }
  322. //校验关键工序是否维护了产线
  323. List<LineStartDto> lines = new List<LineStartDto>();
  324. foreach (var op in curRoutings)
  325. {
  326. var curLines = lineDetails.Where(p => p.Op == op).ToList();
  327. if (!curLines.Any())//当前Op没有维护产线
  328. {
  329. entity = new ScheduleExceptionMaster();
  330. entity.Domain = item.Domain;
  331. entity.WorkOrd = item.WorkOrd;
  332. entity.ItemNum = item.ItemNum;
  333. entity.CreateTime = DateTime.Now;
  334. entity.Remark = "排产异常:工单的关键工序<"+ op + ">没有维护产线数据,请维护后再操作!";
  335. entity.Type = type == 1 ? "自动排产" : "手动排产";
  336. exceptions.Add(entity);
  337. }
  338. foreach (var line in curLines)
  339. {
  340. lines.Add(new LineStartDto {
  341. Line = line.Line,
  342. Op = op
  343. });
  344. }
  345. }
  346. //校验一条产线是否维护了多个关键工序
  347. List<string> pdLines = lines.Select(p => p.Line).Distinct().ToList();
  348. foreach (var line in pdLines)
  349. {
  350. var curLines = lines.Where(p => p.Line == line).ToList();
  351. if (curLines.Count() > 1)//当前物料的多个关键工序对应一条产线
  352. {
  353. entity = new ScheduleExceptionMaster();
  354. entity.Domain = item.Domain;
  355. entity.WorkOrd = item.WorkOrd;
  356. entity.ItemNum = item.ItemNum;
  357. entity.CreateTime = DateTime.Now;
  358. entity.Remark = "排产异常:工单的多个关键工序<" + string.Join(",",curLines.Select(p=>p.Op).ToList()) + ">对应同一条产线,请调整后再操作!";
  359. entity.Type = type == 1 ? "自动排产" : "手动排产";
  360. exceptions.Add(entity);
  361. }
  362. }
  363. //校验主产线关键工序对应的产线是否维护了工作日历
  364. var curCalendars = calendars.Where(x => lineDetails.Select(p => p.Line).Contains(x.ProdLine) || string.IsNullOrEmpty(x.ProdLine)).ToList();
  365. foreach (var rut in lineDetails)
  366. {
  367. //获取当前产线维护的工作日历
  368. var lineCals = curCalendars.Where(p => p.ProdLine == rut.Line).ToList();
  369. if (!lineCals.Any())//当前产线未维护工作日历
  370. {
  371. lineCals = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine)).ToList();
  372. if (!lineCals.Any())
  373. {
  374. entity = new ScheduleExceptionMaster();
  375. entity.Domain = item.Domain;
  376. entity.WorkOrd = item.WorkOrd;
  377. entity.ItemNum = item.ItemNum;
  378. entity.CreateTime = DateTime.Now;
  379. entity.Remark = "排产异常:产线<"+rut.Line+ ">没有维护工作日历且没有维护标准工作日历,请维护后再操作!";
  380. entity.Type = type == 1 ? "自动排产" : "手动排产";
  381. exceptions.Add(entity);
  382. }
  383. }
  384. else if (lineCals.Select(p => p.WeekDay).Distinct().Count() != 7)//当前产线维护了工作日历,但是没有维护完全
  385. {
  386. entity = new ScheduleExceptionMaster();
  387. entity.Domain = item.Domain;
  388. entity.WorkOrd = item.WorkOrd;
  389. entity.ItemNum = item.ItemNum;
  390. entity.CreateTime = DateTime.Now;
  391. entity.Remark = "排产异常:产线<" + rut.Line + ">工作日历没有维护完整,请维护后再操作!";
  392. entity.Type = type == 1 ? "自动排产" : "手动排产";
  393. exceptions.Add(entity);
  394. }
  395. }
  396. }
  397. return exceptions;
  398. }
  399. /// <summary>
  400. /// 排产
  401. /// </summary>
  402. /// <param name="workOrd">工单</param>
  403. /// <param name="workOrdRoutings">当前工单的工艺路线的关键工序(有几个关键工序就有几条产线)</param>
  404. /// <param name="prodLines">当前工单的产线明细</param>
  405. /// <param name="periodsDet">生产周期</param>
  406. /// <param name="scheduleResults">排产结果</param>
  407. /// <param name="allResults">产线占用记录</param>
  408. public void LineSchedule(WorkOrdMaster workOrd, List<WorkOrdRouting> workOrdRoutings, List<ProdLineDetail> prodLines,List<PeriodSequenceDet> periodsDet, List<ScheduleResultOpMaster> scheduleResults, List<ScheduleResultOpMaster> allResults)
  409. {
  410. //生产周期
  411. List<PeriodSequenceDet> curSequences = new List<PeriodSequenceDet>();
  412. //排产明细
  413. List<ScheduleResultOpMaster> curScheduleRsts = new List<ScheduleResultOpMaster>();
  414. //记录上一产线排产开始时间
  415. LineStartDto lineStart = new LineStartDto();
  416. //第一层级工序有几个关键工序,就有几条产线
  417. for (int i = 0; i < workOrdRoutings.Count; i++)
  418. {
  419. //产线实际排产开始时间
  420. if (i == 0)//第一条产线
  421. {
  422. lineStart = DealStartTime(workOrd, workOrdRoutings[i].OP, prodLines, allResults);
  423. }
  424. else
  425. {
  426. //获取前一产线排产开始时间,通过提前期计算当前产线排产开始时间
  427. lineStart = DealNextStartTime(workOrd, lineStart, workOrdRoutings[i].OP, prodLines, allResults);
  428. }
  429. //当前产线的工作日历
  430. var mLCalendars = calendars.Where(p => p.ProdLine == lineStart.Line || string.IsNullOrEmpty(p.ProdLine)).ToList();
  431. //当前产线的休息时间设置
  432. var mlqtyWorkDtls = qualityLines.Where(p => p.ProdLine == lineStart.Line).ToList();
  433. //产线已排产数量
  434. decimal sumQty = 0m;
  435. //产线准备时间
  436. decimal sumTimes = 0m;
  437. //产线排产开始时间
  438. DateTime workStartTime = lineStart.StartTime;
  439. //排产
  440. while (sumQty < workOrd.QtyOrded)
  441. {
  442. //获取当天的产能
  443. LineScheduledDto dto = GetScheduledPoint(lineStart, workStartTime, mLCalendars, mlqtyWorkDtls);
  444. //排产开始时,需要先减去产线准备时间
  445. if (sumTimes < lineStart.setupTime)
  446. {
  447. //判断当天的可用生产时长能满足提前期
  448. if (dto.EffTime >= lineStart.setupTime - sumTimes)
  449. {
  450. //当天剩余产能
  451. decimal sumAmount = dto.ProductQty - Math.Floor(dto.Rate * (lineStart.setupTime - sumTimes));
  452. //判断已排产数量+当天的产能是否超过工单数量
  453. if (sumQty + sumAmount <= workOrd.QtyOrded)//当天的产能需要全部排产
  454. {
  455. //记录生产周期
  456. curSequences.Add(new PeriodSequenceDet
  457. {
  458. Domain = domain,
  459. Line = lineStart.Line,
  460. ItemNum = workOrd.ItemNum,
  461. PlanDate = workStartTime.Date,
  462. Period = dto.Period,
  463. OrdQty = sumAmount,
  464. WorkOrds = workOrd.WorkOrd,
  465. Op = lineStart.Op,
  466. IsActive = true,
  467. Status = "",
  468. CreateTime = DateTime.Now
  469. });
  470. //记录排产记录
  471. curScheduleRsts.Add(new ScheduleResultOpMaster
  472. {
  473. Domain = domain,
  474. WorkOrd = workOrd.WorkOrd,
  475. Line = lineStart.Line,
  476. ItemNum = workOrd.ItemNum,
  477. Op = lineStart.Op,
  478. WorkDate = workStartTime.Date,
  479. WorkQty = sumAmount,
  480. WorkStartTime = dto.StartTime,
  481. WorkEndTime = dto.EndTime,
  482. CreateTime = DateTime.Now
  483. });
  484. //累计已排产数量
  485. sumQty += sumAmount;
  486. //继续排下一个工作日
  487. workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
  488. }
  489. else// 最后一天的产能只能占用一部分
  490. {
  491. //剩余需要排产的数量
  492. decimal residueQty = workOrd.QtyOrded - sumQty;
  493. //剩余数量生产需要时长(分钟)
  494. decimal workTime = Math.Ceiling(residueQty / dto.Rate * 60);
  495. //获取当天的工作时间段
  496. var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
  497. if (curCalendar == null)
  498. {
  499. curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
  500. }
  501. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(workStartTime, curCalendar, mlqtyWorkDtls);
  502. var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
  503. if (curPoint != null)
  504. {
  505. TimeSpan span = curPoint.EndPoint - workStartTime;
  506. //当天工作时间段的有效生产时间
  507. decimal effMins = (decimal)span.TotalMinutes;
  508. DateTime workEndTime = workStartTime;
  509. if (effMins >= workTime)//当前工作时间段即可满足产能
  510. {
  511. workEndTime = workStartTime.AddMinutes((double)workTime);
  512. }
  513. else
  514. {
  515. //获取后续生产时间段
  516. var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
  517. //剩余需要工作时长
  518. decimal nextMins = workTime - effMins;
  519. foreach (var p in nextPoints)
  520. {
  521. if (p.WorkMinutes >= nextMins)
  522. {
  523. workEndTime = p.StartPoint.AddMinutes((double)nextMins);
  524. break;
  525. }
  526. nextMins -= p.WorkMinutes;
  527. }
  528. }
  529. sumQty = workOrd.QtyOrded;
  530. //记录生产周期
  531. curSequences.Add(new PeriodSequenceDet
  532. {
  533. Domain = domain,
  534. Line = lineStart.Line,
  535. ItemNum = workOrd.ItemNum,
  536. PlanDate = workStartTime.Date,
  537. Period = dto.Period,
  538. OrdQty = residueQty,
  539. WorkOrds = workOrd.WorkOrd,
  540. Op = lineStart.Op,
  541. IsActive = true,
  542. Status = "",
  543. CreateTime = DateTime.Now
  544. });
  545. //记录排产记录
  546. curScheduleRsts.Add(new ScheduleResultOpMaster
  547. {
  548. Domain = domain,
  549. WorkOrd = workOrd.WorkOrd,
  550. Line = lineStart.Line,
  551. ItemNum = workOrd.ItemNum,
  552. Op = lineStart.Op,
  553. WorkDate = workStartTime.Date,
  554. WorkQty = residueQty,
  555. WorkStartTime = workStartTime,
  556. WorkEndTime = workEndTime,
  557. CreateTime = DateTime.Now
  558. });
  559. }
  560. }
  561. sumTimes = lineStart.setupTime;
  562. }
  563. else {
  564. //当天的可用生产时长不能满足提前期
  565. sumTimes += dto.EffTime;
  566. //获取下一个工作日
  567. workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
  568. }
  569. }
  570. else {
  571. //判断已排产数量+当天的产能是否超过工单数量
  572. if (sumQty + dto.ProductQty <= workOrd.QtyOrded)//当天的产能需要全部排产
  573. {
  574. //记录生产周期
  575. curSequences.Add(new PeriodSequenceDet
  576. {
  577. Domain = domain,
  578. Line = lineStart.Line,
  579. ItemNum = workOrd.ItemNum,
  580. PlanDate = workStartTime.Date,
  581. Period = dto.Period,//目前只考虑一班制
  582. OrdQty = dto.ProductQty,
  583. WorkOrds = workOrd.WorkOrd,
  584. Op = lineStart.Op,
  585. IsActive = true,
  586. Status = "",
  587. CreateTime = DateTime.Now
  588. });
  589. //记录排产记录
  590. curScheduleRsts.Add(new ScheduleResultOpMaster
  591. {
  592. Domain = domain,
  593. WorkOrd = workOrd.WorkOrd,
  594. Line = lineStart.Line,
  595. ItemNum = workOrd.ItemNum,
  596. Op = lineStart.Op,
  597. WorkDate = workStartTime.Date,
  598. WorkQty = dto.ProductQty,
  599. WorkStartTime = dto.StartTime,
  600. WorkEndTime = dto.EndTime,
  601. CreateTime = DateTime.Now
  602. });
  603. //累计已排产数量
  604. sumQty += dto.ProductQty;
  605. //继续排下一个工作日
  606. workStartTime = GetNextWorkDay((int)workStartTime.DayOfWeek, workStartTime, mLCalendars);
  607. }
  608. else// 最后一天的产能只能占用一部分
  609. {
  610. //剩余需要排产的数量
  611. decimal residueQty = workOrd.QtyOrded - sumQty;
  612. //剩余数量生产需要时长(分钟)
  613. decimal workTime = Math.Ceiling(residueQty / dto.Rate * 60);
  614. //获取当天的工作时间段
  615. var curCalendar = mLCalendars.FirstOrDefault(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
  616. if (curCalendar == null)
  617. {
  618. curCalendar = mLCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == (int)workStartTime.DayOfWeek);
  619. }
  620. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(workStartTime, curCalendar, mlqtyWorkDtls);
  621. var curPoint = workPoints.Find(p => p.StartPoint <= workStartTime && workStartTime <= p.EndPoint);
  622. if (curPoint != null)
  623. {
  624. TimeSpan span = curPoint.EndPoint - workStartTime;
  625. //当天工作时间段的有效生产时间
  626. decimal effMins = (decimal)span.TotalMinutes;
  627. DateTime workEndTime = workStartTime;
  628. if (effMins >= workTime)//当前工作时间段即可满足产能
  629. {
  630. workEndTime = workStartTime.AddMinutes((double)workTime);
  631. }
  632. else
  633. {
  634. //获取后续生产时间段
  635. var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
  636. //剩余需要工作时长
  637. decimal nextMins = workTime - effMins;
  638. foreach (var p in nextPoints)
  639. {
  640. if (p.WorkMinutes >= nextMins)
  641. {
  642. workEndTime = p.StartPoint.AddMinutes((double)nextMins);
  643. break;
  644. }
  645. nextMins -= p.WorkMinutes;
  646. }
  647. }
  648. sumQty = workOrd.QtyOrded;
  649. //记录生产周期
  650. curSequences.Add(new PeriodSequenceDet
  651. {
  652. Domain = domain,
  653. Line = lineStart.Line,
  654. ItemNum = workOrd.ItemNum,
  655. PlanDate = workStartTime.Date,
  656. Period = dto.Period,//目前只考虑一班制
  657. OrdQty = residueQty,
  658. WorkOrds = workOrd.WorkOrd,
  659. Op = lineStart.Op,
  660. IsActive = true,
  661. Status = "",
  662. CreateTime = DateTime.Now
  663. });
  664. //记录排产记录
  665. curScheduleRsts.Add(new ScheduleResultOpMaster
  666. {
  667. Domain = domain,
  668. WorkOrd = workOrd.WorkOrd,
  669. Line = lineStart.Line,
  670. ItemNum = workOrd.ItemNum,
  671. Op = lineStart.Op,
  672. WorkDate = workStartTime.Date,
  673. WorkQty = residueQty,
  674. WorkStartTime = workStartTime,
  675. WorkEndTime = workEndTime,
  676. CreateTime = DateTime.Now
  677. });
  678. }
  679. }
  680. }
  681. }
  682. }
  683. //记录排产结果
  684. periodsDet.AddRange(curSequences);
  685. scheduleResults.AddRange(curScheduleRsts);
  686. }
  687. /// <summary>
  688. /// 获取产线当天的开工时间,结束时间,有效工作时长,生产数量
  689. /// </summary>
  690. /// <param name="lineStart">排产产线</param>
  691. /// <param name="startTime">产线排产开始时间</param>
  692. /// <param name="curCalendars"></param>
  693. /// <param name="curQtyDtls"></param>
  694. /// <returns></returns>
  695. public LineScheduledDto GetScheduledPoint(LineStartDto lineStart, DateTime startTime, List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
  696. {
  697. LineScheduledDto scheduledDto = new LineScheduledDto();
  698. //当天排产开始时间
  699. scheduledDto.StartTime = startTime;
  700. //开始时间是周几
  701. int weekDay = (int)startTime.DayOfWeek;
  702. //当天的工作日历
  703. var shopCal = curCalendars.Where(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  704. if (shopCal == null)//当前产线当天没有设置工作日历
  705. {
  706. //取默认工作日历
  707. shopCal = curCalendars.Where(p =>string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  708. }
  709. //当前日期的工作时间段
  710. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
  711. //当天排产结束时间
  712. scheduledDto.EndTime = workPoints.Last().EndPoint;
  713. //计算starttime处于那个工作时间段
  714. var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).First();
  715. TimeSpan span = curPoint.EndPoint - startTime;
  716. scheduledDto.EffTime = (decimal)span.TotalHours;
  717. //获取后续工作时间段的有效工作时间
  718. var nextPoints = workPoints.Where(p => p.Level > curPoint.Level).ToList();
  719. foreach (var item in nextPoints)
  720. {
  721. span = item.EndPoint - item.StartPoint;
  722. scheduledDto.EffTime += (decimal)span.TotalHours;
  723. }
  724. //判断产线当天有没有加班
  725. var curOccupyTimes = resourceOccupancyTimes.Where(p=> p.Resource == lineStart.Line && p.StartTime.GetValueOrDefault().Date == startTime.Date).ToList();
  726. scheduledDto.EffTime += curOccupyTimes.Sum(p=> Convert.ToDecimal(p.Ufld1));
  727. //判断当前产线的UPH
  728. var curRunCrews = prodLineDetailRunCrews.Where(p=>p.ProdLineDetailRecID == lineStart.RecID).ToList();
  729. //判断当前日期是否配置了UPH
  730. var curLevel = curRunCrews.FirstOrDefault(p => p.StartDate.GetValueOrDefault().Date <= startTime.Date && p.EndDate.GetValueOrDefault().Date >= startTime.Date);
  731. decimal rate = curLevel == null ? lineStart.Rate : curLevel.Rate;
  732. scheduledDto.Rate = rate;
  733. //计算当天的产能(向下取整)
  734. scheduledDto.ProductQty = Math.Floor(scheduledDto.EffTime * rate);
  735. //计算班次
  736. scheduledDto.Period = 1;//默认一般制
  737. if (shopCal.ShiftsStart2 != 0 && shopCal.ShiftsHours2 != 0)
  738. {
  739. scheduledDto.Period = 2;
  740. }
  741. return scheduledDto;
  742. }
  743. /// <summary>
  744. /// 计算主产线实际排产开始时间
  745. /// </summary>
  746. /// <param name="workOrd">工单</param>
  747. /// <param name="op">工序</param>
  748. /// <param name="prodLines">生产线明细</param>
  749. /// <param name="allResults">产线占用记录</param>
  750. /// <returns></returns>
  751. public LineStartDto DealStartTime(WorkOrdMaster workOrd,int op,List<ProdLineDetail> prodLines, List<ScheduleResultOpMaster> allResults)
  752. {
  753. DateTime actStart = DateTime.Now.Date.AddDays(1);
  754. LineStartDto lineStart = new LineStartDto();
  755. //获取工序对应的产线,根据优先级排序
  756. var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p => p.Sequence).ToList();
  757. //获取第一条产线排产结束时间
  758. var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
  759. actStart = schedule == null ? actStart : (schedule.WorkEndTime >= actStart ? schedule.WorkEndTime : actStart);
  760. //计算实际开工时间
  761. //产线工作日历:当前产线的工作日历+默认工作日历
  762. var curCalendars = calendars.Where(p => p.ProdLine == lines[0].Line || string.IsNullOrEmpty(p.ProdLine)).ToList();
  763. var curQtyDtls = qualityLines.Where(p=>p.ProdLine == lines[0].Line).ToList();
  764. actStart = CalcActStartTime(actStart, curCalendars, curQtyDtls);
  765. lineStart.RecID = lines[0].RecID;
  766. lineStart.Line = lines[0].Line;
  767. lineStart.StartTime = actStart;
  768. lineStart.setupTime = lines[0].SetupTime;
  769. lineStart.Rate = lines[0].Rate;
  770. lineStart.Op = op;
  771. //循环其他产线
  772. for (int i = 1; i < lines.Count; i++)
  773. {
  774. schedule = allResults.Where(p => p.Line == lines[i].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
  775. DateTime StartTime = schedule == null ? actStart : (schedule.WorkEndTime >= actStart ? schedule.WorkEndTime : actStart);
  776. //计算实际开工时间
  777. //产线工作日历:当前产线的工作日历+默认工作日历
  778. curCalendars = calendars.Where(p => p.ProdLine == lines[i].Line || string.IsNullOrEmpty(p.ProdLine)).ToList();
  779. curQtyDtls = qualityLines.Where(p => p.ProdLine == lines[i].Line).ToList();
  780. StartTime = CalcActStartTime(StartTime, curCalendars, curQtyDtls);
  781. if (StartTime < lineStart.StartTime)
  782. {
  783. lineStart.RecID = lines[i].RecID;
  784. lineStart.Line = lines[i].Line;
  785. lineStart.StartTime = StartTime;
  786. lineStart.setupTime = lines[i].SetupTime;
  787. lineStart.Rate = lines[i].Rate;
  788. lineStart.Op = op;
  789. }
  790. }
  791. return lineStart;
  792. }
  793. /// <summary>
  794. /// 计算主产线实际排产开始时间
  795. /// </summary>
  796. /// <param name="startTime">开始时间</param>
  797. /// <param name="curCalendars">当前产线工作日历+默认工作日历</param>
  798. /// <param name="curQtyDtls">当前产线休息记录</param>
  799. /// <returns></returns>
  800. public DateTime CalcActStartTime(DateTime startTime, List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
  801. {
  802. //实际排产开始时间
  803. DateTime actStart = startTime;
  804. //开始时间是周几
  805. int weekDay = (int)startTime.DayOfWeek;
  806. //判断当天是否是工作日
  807. bool isWorkDay = CheckIsWorkDay(startTime);
  808. if (!isWorkDay)//不是工作日
  809. {
  810. //获取下一个工作日开始时间
  811. actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
  812. return actStart;
  813. }
  814. //当天的工作日历
  815. var shopCal = curCalendars.Where(p => !string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  816. if (shopCal == null)
  817. {
  818. //产线没有维护当天的工作日历,则取默认工作日历
  819. shopCal = curCalendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  820. }
  821. //当前日期的工作时间段
  822. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
  823. //计算starttime处于那个工作时间段
  824. var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
  825. if (curPoint == null)//不处于工作时间段
  826. {
  827. //开始时间小于当天工作开始时间
  828. if (startTime < workPoints.First().StartPoint)
  829. {
  830. actStart = workPoints.First().StartPoint;
  831. }
  832. //开始时间大于当前工作结束时间
  833. else if (startTime > workPoints.Last().EndPoint)
  834. {
  835. //获取下一个工作日开始时间
  836. actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
  837. }
  838. else
  839. {//开始时间位于当天的休息时间段
  840. foreach (var item in workPoints)
  841. {
  842. //获取下一个时间段
  843. var next = workPoints.First(p => p.Level == item.Level + 1);
  844. if (item.EndPoint < startTime && startTime < next.StartPoint)
  845. {
  846. actStart = next.StartPoint;
  847. break;
  848. }
  849. }
  850. }
  851. return actStart;
  852. }
  853. if (startTime != curPoint.EndPoint)
  854. {
  855. return actStart;
  856. }
  857. //查询下一时间段的开始时间点
  858. var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
  859. if (nextPoint != null)
  860. {
  861. return nextPoint.StartPoint;
  862. }
  863. //开始时间为今天下班时间,实际排产开始时间为下一个工作日的开始时间
  864. actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
  865. return actStart;
  866. }
  867. /// <summary>
  868. /// 排产开始时间处理为半小时/整点开始--向后取整
  869. /// </summary>
  870. /// <param name="startTime"></param>
  871. /// <returns></returns>
  872. public DateTime CalcStartTimeAfter(DateTime startTime)
  873. {
  874. DateTime rtnTime = startTime;
  875. DateTime curDate = startTime.Date;
  876. //时间转换为分钟
  877. TimeSpan span = rtnTime - curDate;
  878. decimal sumMinutes = (decimal)span.TotalMinutes;
  879. int times = (int)Math.Ceiling(sumMinutes / 30);
  880. rtnTime = curDate.AddMinutes(times * 30);
  881. return rtnTime;
  882. }
  883. /// <summary>
  884. /// 排产开始时间处理为半小时/整点开始--向前取整
  885. /// </summary>
  886. /// <param name="startTime"></param>
  887. /// <returns></returns>
  888. public DateTime CalcStartTimeBefore(DateTime startTime)
  889. {
  890. DateTime rtnTime = startTime;
  891. DateTime curDate = startTime.Date;
  892. //时间转换为分钟
  893. TimeSpan span = rtnTime - curDate;
  894. decimal sumMinutes = (decimal)span.TotalMinutes;
  895. int times = (int)Math.Floor(sumMinutes / 30);
  896. rtnTime = curDate.AddMinutes(times * 30);
  897. return rtnTime;
  898. }
  899. /// <summary>
  900. /// 判断当天是否是工作日
  901. /// </summary>
  902. /// <param name="dateTime"></param>
  903. /// <returns></returns>
  904. public bool CheckIsWorkDay(DateTime dateTime)
  905. {
  906. bool isWorkDay = true;
  907. //周几
  908. int weekDay = (int)dateTime.DayOfWeek;
  909. //判断当天是否是工作日
  910. if (weekDay == 0 || weekDay == 6)//周六或者周日,需要判断是否调班,需要加班
  911. {
  912. if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "调班"))//不是调班
  913. {
  914. isWorkDay = false;
  915. }
  916. return isWorkDay;
  917. }
  918. //不是周六周日,需要判断是不是节假日
  919. if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == dateTime.Date && p.Ufld1 == "休假"))//是节假日
  920. {
  921. isWorkDay = false;
  922. }
  923. return isWorkDay;
  924. }
  925. /// <summary>
  926. /// 计算下一产线实际排产开始时间
  927. /// </summary>
  928. /// <param name="workOrd">工单</param>
  929. /// <param name="lineStart">上一产线开始时间</param>
  930. /// <param name="op">工序</param>
  931. /// <param name="prodLines">产线明细</param>
  932. /// <param name="allResults">产线占用记录</param>
  933. /// <returns></returns>
  934. public LineStartDto DealNextStartTime(WorkOrdMaster workOrd,LineStartDto lineStart,int op, List<ProdLineDetail> prodLines, List<ScheduleResultOpMaster> allResults)
  935. {
  936. LineStartDto startDto = new LineStartDto();
  937. //获取产线
  938. var lines = prodLines.Where(p => p.Part == workOrd.ItemNum && p.Op == op).OrderBy(p=>p.Sequence).ToList();
  939. //获取第一条产线排产结束时间
  940. var schedule = allResults.Where(p => p.Line == lines[0].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
  941. DateTime startTime = CalcStartTimeWithSetUpTime(lines[0], lineStart.StartTime, lineStart.setupTime + lines[0].OverlapTime);
  942. startDto.RecID = lines[0].RecID;
  943. startDto.Line = lines[0].Line;
  944. startDto.setupTime = lines[0].SetupTime;
  945. startDto.StartTime = schedule == null ? startTime : (startTime < schedule.WorkEndTime ? schedule.WorkEndTime : startTime);
  946. startDto.Rate = lines[0].Rate;
  947. startDto.Op = op;
  948. //循环剩余产线,找到最早可开工产线
  949. for (int i = 1; i < lines.Count(); i++)
  950. {
  951. schedule = allResults.Where(p => p.Line == lines[i].Line).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
  952. startTime = CalcStartTimeWithSetUpTime(lines[i], lineStart.StartTime, lineStart.setupTime + lines[i].OverlapTime);
  953. startTime = schedule == null ? startTime : (startTime < schedule.WorkEndTime ? schedule.WorkEndTime : startTime);
  954. if (startTime < startDto.StartTime)
  955. {
  956. startDto.RecID = lines[i].RecID;
  957. startDto.Line = lines[i].Line;
  958. startDto.setupTime = lines[i].SetupTime;
  959. startDto.StartTime = startTime;
  960. startDto.Rate = lines[0].Rate;
  961. startDto.Op = op;
  962. }
  963. }
  964. return startDto;
  965. }
  966. /// <summary>
  967. /// 通过提前期计算产线排产开始时间
  968. /// </summary>
  969. /// <param name="line">产线</param>
  970. /// <param name="startTime">上一产线开始时间</param>
  971. /// <param name="setupTime">上一产线准备时间+当前产线提前期</param>
  972. /// <returns></returns>
  973. public DateTime CalcStartTimeWithSetUpTime(ProdLineDetail line, DateTime startTime, decimal setupTime)
  974. {
  975. //提前期转换成分钟
  976. decimal needMinute = setupTime * 60;
  977. //实际排产开始时间
  978. DateTime actStart = startTime;
  979. //开始时间是周几
  980. int weekDay = (int)startTime.DayOfWeek;
  981. //当天的工作日历
  982. var shopCal = calendars.Where(p => p.ProdLine == line.Line && p.WeekDay == weekDay).FirstOrDefault();
  983. if (shopCal == null)
  984. {
  985. //产线没有维护当天的工作日历,则取默认工作日历
  986. shopCal = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  987. }
  988. //当前产线的休息时间设置
  989. var curQtyDtls = qualityLines.Where(p => p.ProdLine == line.Line).ToList();
  990. //当前日期的工作时间段
  991. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal, curQtyDtls);
  992. //计算starttime处于那个工作时间段
  993. var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
  994. //当前时间段可用时长
  995. TimeSpan span = curPoint.EndPoint - startTime;
  996. decimal curMins = (decimal)span.TotalMinutes;
  997. if (curMins >= needMinute)//当前时间段的可用时长满足提前期
  998. {
  999. actStart = startTime.AddMinutes((double)needMinute);
  1000. return actStart;
  1001. }
  1002. //当前时间段的可用时长不满足
  1003. //剩余提前期
  1004. needMinute -= curMins;
  1005. //获取后层级时间段
  1006. var prePoints = workPoints.Where(p => p.Level > curPoint.Level).OrderBy(p => p.Level).ToList();
  1007. bool flag = true;//标志位
  1008. foreach (var item in prePoints)
  1009. {
  1010. if (item.WorkMinutes >= needMinute)//当前时间段的可用时长满足
  1011. {
  1012. actStart = item.StartPoint.AddMinutes((double)needMinute);
  1013. flag = false;
  1014. break;
  1015. }
  1016. needMinute -= item.WorkMinutes;
  1017. }
  1018. if (!flag)
  1019. {
  1020. return actStart;
  1021. }
  1022. //今天可用时长不够,往后工作日找
  1023. DateTime nextStartTime = startTime;
  1024. while (flag)
  1025. {
  1026. //获取下一个工作日
  1027. nextStartTime = GetNextWorkDay(weekDay, startTime, calendars.Where(p => p.ProdLine == line.Line || string.IsNullOrEmpty(p.ProdLine)).ToList());
  1028. weekDay = (int)nextStartTime.DayOfWeek;
  1029. //获取前一个工作日的工作时间段数据,level顺排
  1030. var curCalendar = calendars.Where(p => p.ProdLine == line.Line && p.WeekDay == weekDay).FirstOrDefault();
  1031. if (curCalendar == null )
  1032. {
  1033. curCalendar = calendars.Where(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == weekDay).FirstOrDefault();
  1034. }
  1035. workPoints = DealWorkDayToLevels(nextStartTime, curCalendar, curQtyDtls).OrderBy(p => p.Level).ToList();
  1036. //当天的工作时长(分钟)
  1037. decimal sumWorkMins = workPoints.Sum(p => p.WorkMinutes);
  1038. if (sumWorkMins >= needMinute)//当天可用提前期满足
  1039. {
  1040. //获取开始时间
  1041. foreach (var item in workPoints)
  1042. {
  1043. if (item.WorkMinutes >= needMinute)//当前时间段满足
  1044. {
  1045. actStart = item.StartPoint.AddMinutes((double)needMinute);
  1046. break;
  1047. }
  1048. needMinute -= item.WorkMinutes;
  1049. }
  1050. flag = false;
  1051. }
  1052. else
  1053. {
  1054. //当天可用提前期不满足
  1055. needMinute -= sumWorkMins;
  1056. }
  1057. }
  1058. return actStart;
  1059. }
  1060. /// <summary>
  1061. /// 获取下一个工作日开始时间
  1062. /// </summary>
  1063. /// <param name="weekDay">当前周几</param>
  1064. /// <param name="startTime">开始时间</param>
  1065. /// <param name="curCalendars">当前产线的工作日历+默认工作日历</param>
  1066. /// <returns></returns>
  1067. public DateTime GetNextWorkDay(int weekDay, DateTime startTime, List<ShopCalendarWorkCtr> curCalendars)
  1068. {
  1069. DateTime rtnData = startTime;
  1070. //下一天
  1071. DateTime nextDate = startTime.Date.AddDays(1);
  1072. //下一天是周几
  1073. int nextWeekDay = (weekDay + 1) % 7;
  1074. //获取工作日历:先获取当前产线的工作日历,若没有维护,则获取默认工作日历
  1075. var calendar = curCalendars.FirstOrDefault(p =>!string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == nextWeekDay);
  1076. if (calendar == null)
  1077. {
  1078. calendar = curCalendars.FirstOrDefault(p => string.IsNullOrEmpty(p.ProdLine) && p.WeekDay == nextWeekDay);
  1079. }
  1080. string strStart = calendar.ShiftsStart1.ToString("0.00").Replace(".", ":");
  1081. //判断下一天是否是工作日
  1082. if (nextWeekDay == 0 || nextWeekDay == 6)//下一天是周六或者周日,需要判断是否调班,需要加班
  1083. {
  1084. if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "调班"))//下一天是周末
  1085. {
  1086. //递归继续找下一个工作日
  1087. rtnData = GetNextWorkDay(nextWeekDay, nextDate, curCalendars);
  1088. return rtnData;
  1089. }
  1090. rtnData = Convert.ToDateTime(nextDate.ToString("yyyy-MM-dd") + " " + strStart);
  1091. return rtnData;
  1092. }
  1093. //下一天不是周六周日,需要判断是不是节假日
  1094. if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "休假"))//是节假日
  1095. {
  1096. //递归继续找下一个工作日
  1097. rtnData = GetNextWorkDay(nextWeekDay, nextDate, curCalendars);
  1098. return rtnData;
  1099. }
  1100. rtnData = Convert.ToDateTime(nextDate.ToString("yyyy-MM-dd") + " " + strStart);
  1101. return rtnData;
  1102. }
  1103. /// <summary>
  1104. /// 处理当前日期的工作时间段
  1105. /// </summary>
  1106. /// <param name="startTime"></param>
  1107. /// <param name="shopCal">当前产线的工作日历-周几</param>
  1108. /// <param name="curQtyDtls">每天休息记录</param>
  1109. /// <returns></returns>
  1110. public List<LineWorkPointDto> DealWorkDayToLevels(DateTime startTime, ShopCalendarWorkCtr shopCal, List<QualityLineWorkDetail> curQtyDtls)
  1111. {
  1112. //年-月-日
  1113. string date = startTime.Date.ToString("yyyy-MM-dd");
  1114. //排产记录结束日期是周几
  1115. int weekDay = (int)startTime.DayOfWeek;
  1116. //工作时间段
  1117. List<LineWorkPointDto> workPoints = new List<LineWorkPointDto>();
  1118. LineWorkPointDto dto = new LineWorkPointDto();
  1119. int level = 1;
  1120. TimeSpan span = TimeSpan.Zero;
  1121. //开始时间
  1122. string strStart = shopCal.ShiftsStart1.ToString("0.00").Replace(".", ":");
  1123. DateTime dayStartPoint = Convert.ToDateTime(date + " " + strStart);
  1124. //结束时间
  1125. DateTime dayEndPoint = dayStartPoint.AddHours((double)(shopCal.ShiftsHours1 + shopCal.ShiftsHours2));
  1126. dto.Level = level;
  1127. dto.Line = shopCal.ProdLine;
  1128. dto.WeekDay = weekDay;
  1129. dto.StartPoint = dayStartPoint;
  1130. //按照产线休息时间切分时间段
  1131. curQtyDtls = curQtyDtls.OrderBy(p => p.RestTimePoint).ToList();
  1132. foreach (var item in curQtyDtls)
  1133. {
  1134. DateTime endPoint = Convert.ToDateTime(date + " " + item.RestTimePoint);
  1135. dto.EndPoint = endPoint;
  1136. span = dto.EndPoint - dto.StartPoint;
  1137. dto.WorkMinutes = (decimal)span.TotalMinutes;
  1138. workPoints.Add(dto);
  1139. level++;
  1140. dto = new LineWorkPointDto();
  1141. dto.Level = level;
  1142. dto.Line = shopCal.ProdLine;
  1143. dto.WeekDay = weekDay;
  1144. dto.StartPoint = endPoint.AddMinutes(item.RestTime);
  1145. }
  1146. dto.EndPoint = dayEndPoint;
  1147. span = dto.EndPoint - dto.StartPoint;
  1148. dto.WorkMinutes = (decimal)span.TotalMinutes;
  1149. workPoints.Add(dto);
  1150. return workPoints.OrderBy(p => p.Level).ToList();
  1151. }
  1152. }
  1153. }