ProductionScheduleAppService.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. using Business.Business.Dto;
  2. using Business.Core.Utilities;
  3. using Business.Dto;
  4. using Business.ResourceExamineManagement.Dto;
  5. using Bussiness.EntityFrameworkCore.SqlRepositories;
  6. using Bussiness.Model.MES.IC;
  7. using Bussiness.Model.Production;
  8. using Bussiness.Model.SRM;
  9. using MongoDB.Driver;
  10. using MongoDB.Driver.Linq;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Collections.Immutable;
  14. using System.Linq;
  15. using System.Text;
  16. using System.Threading.Tasks;
  17. using Volo.Abp.Application.Services;
  18. using Volo.Abp.Domain.Repositories;
  19. using Volo.Abp.Validation.Localization;
  20. namespace Business.Quartz
  21. {
  22. /// <summary>
  23. /// 生产排产服务
  24. /// </summary>
  25. public class ProductionScheduleAppService : ApplicationService
  26. {
  27. #region 服务
  28. /// <summary>
  29. /// 物料
  30. /// </summary>
  31. private ISqlRepository<ItemMaster> _itemMaster;
  32. /// <summary>
  33. /// 工单
  34. /// </summary>
  35. private ISqlRepository<WorkOrdMaster> _workOrdMaster;
  36. /// <summary>
  37. /// 工单物料明细
  38. /// </summary>
  39. private ISqlRepository<WorkOrdDetail> _workOrdDetail;
  40. /// <summary>
  41. /// 工单工艺路线明细
  42. /// </summary>
  43. private ISqlRepository<WorkOrdRouting> _workOrdRouting;
  44. /// <summary>
  45. /// 库存主数据
  46. /// </summary>
  47. private ISqlRepository<InvMaster> _invMaster;
  48. /// <summary>
  49. /// 生产线明细
  50. /// </summary>
  51. private ISqlRepository<ProdLineDetail> _prodLineDetail;
  52. /// <summary>
  53. /// 生产周期明细
  54. /// </summary>
  55. private ISqlRepository<PeriodSequenceDet> _periodSequenceDet;
  56. /// <summary>
  57. /// 排产结果明细
  58. /// </summary>
  59. private ISqlRepository<ScheduleResultOpMaster> _scheduleResultOpMaster;
  60. /// <summary>
  61. /// 工作日历数据
  62. /// </summary>
  63. private ISqlRepository<ShopCalendarWorkCtr> _shopCalendarWorkCtr;
  64. /// <summary>
  65. /// 排产异常记录表
  66. /// </summary>
  67. private ISqlRepository<ScheduleExceptionMaster> _scheduleExceptionMaster;
  68. /// <summary>
  69. /// 产线休息时间记录表
  70. /// </summary>
  71. private ISqlRepository<QualityLineWorkDetail> _qualityLineWorkDetail;
  72. /// <summary>
  73. /// 节假日记录表
  74. /// </summary>
  75. private ISqlRepository<HolidayMaster> _holidayMaster;
  76. /// <summary>
  77. /// 雪花算法
  78. /// </summary>
  79. SnowFlake help = new SnowFlake();
  80. /// <summary>
  81. /// 工作日历数据
  82. /// </summary>
  83. private List<ShopCalendarWorkCtr> calendars;
  84. /// <summary>
  85. /// 产线休息记录数据
  86. /// </summary>
  87. private List<QualityLineWorkDetail> qualityLines;
  88. /// <summary>
  89. /// 节假日记录数据
  90. /// </summary>
  91. private List<HolidayMaster> holidays;
  92. #endregion
  93. #region 构造函数
  94. /// <summary>
  95. /// 构造函数
  96. /// </summary>
  97. public ProductionScheduleAppService(
  98. ISqlRepository<ItemMaster> itemMaster,
  99. ISqlRepository<WorkOrdMaster> workOrdMaster,
  100. ISqlRepository<WorkOrdDetail> workOrdDetail,
  101. ISqlRepository<WorkOrdRouting> workOrdRouting,
  102. ISqlRepository<ProdLineDetail> prodLineDetail,
  103. ISqlRepository<PeriodSequenceDet> periodSequenceDet,
  104. ISqlRepository<ScheduleResultOpMaster> scheduleResultOpMaster,
  105. ISqlRepository<InvMaster> invMaster,
  106. ISqlRepository<ShopCalendarWorkCtr> shopCalendarWorkCtr,
  107. ISqlRepository<ScheduleExceptionMaster> scheduleExceptionMaster,
  108. ISqlRepository<QualityLineWorkDetail> qualityLineWorkDetail,
  109. ISqlRepository<HolidayMaster> holidayMaster
  110. )
  111. {
  112. _itemMaster= itemMaster;
  113. _workOrdMaster= workOrdMaster;
  114. _workOrdDetail= workOrdDetail;
  115. _workOrdRouting= workOrdRouting;
  116. _prodLineDetail = prodLineDetail;
  117. _periodSequenceDet = periodSequenceDet;
  118. _scheduleResultOpMaster= scheduleResultOpMaster;
  119. _invMaster= invMaster;
  120. _shopCalendarWorkCtr= shopCalendarWorkCtr;
  121. _scheduleExceptionMaster= scheduleExceptionMaster;
  122. _qualityLineWorkDetail= qualityLineWorkDetail;
  123. _holidayMaster = holidayMaster;
  124. }
  125. #endregion
  126. /// <summary>
  127. /// 执行生产排产
  128. /// </summary>
  129. public async void DoExt()
  130. {
  131. await DoProductShcedule();
  132. }
  133. /// <summary>
  134. /// 生产排产
  135. /// </summary>
  136. public async Task DoProductShcedule()
  137. {
  138. //1、获取需要排产的工单:Status为空且IsActive==1
  139. List<WorkOrdMaster> workOrds = _workOrdMaster.Select(p => string.IsNullOrEmpty(p.Status) && p.IsActive == 1).Result;
  140. if (workOrds.Count == 0)
  141. {
  142. return;
  143. }
  144. //获取排产工单的最早计划开工日期
  145. DateTime earlist = workOrds.Min(p => p.OrdDate.GetValueOrDefault()).Date;
  146. //2、获取数据
  147. //获取工单工艺路径数据
  148. List<WorkOrdRouting> workOrdRoutings = _workOrdRouting.Select(p => workOrds.Select(m => m.WorkOrd).Contains(p.WorkOrd) && p.Domain == "1001" && p.Status != "C" && p.IsActive == 1).Result;
  149. //获取物料对应的生产线信息:物料、工序对应的生产线
  150. List<ProdLineDetail> prodLines = _prodLineDetail.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.Part) && p.Domain == "1001" && p.IsActive == 1).Result;
  151. //获取生产周期数据
  152. List<PeriodSequenceDet> periodSequences = _periodSequenceDet.Select(p=> workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.PlanDate >= earlist && p.Domain == "1001" && p.IsActive == 1).Result;
  153. //获取当前日期往后的排产记录数据
  154. List<ScheduleResultOpMaster> schedules = _scheduleResultOpMaster.Select(p => workOrds.Select(m => m.ItemNum).Contains(p.ItemNum) && p.WorkDate >= earlist && p.Domain == "1001").Result;
  155. //获取工作日历数据
  156. calendars = _shopCalendarWorkCtr.Select(p=>p.Domain == "1001" && p.IsActive == 1).Result;
  157. //获取产线休息记录数据
  158. qualityLines = _qualityLineWorkDetail.Select(p => p.Domain == "1001" && p.IsActive == 1).Result;
  159. //获取节假日记录数据
  160. holidays = _holidayMaster.Select(p => p.Domain == "1001" && p.IsActive == 1 && p.Dated >= earlist).Result;
  161. //3、排产
  162. //排产异常记录
  163. List<ScheduleExceptionMaster> scheduleExceptions = new List<ScheduleExceptionMaster>();
  164. //生产周期
  165. List<PeriodSequenceDet> periodsDet = new List<PeriodSequenceDet>();
  166. //排产记录表
  167. List<ScheduleResultOpMaster> scheduleResults = new List<ScheduleResultOpMaster>();
  168. foreach (var item in workOrds)
  169. {
  170. DateTime planStart = item.OrdDate.GetValueOrDefault().Date;
  171. //工序预处理
  172. List<WorkOrdRoutingDto> routingDtos = ProcPretreatment(workOrdRoutings, prodLines);
  173. //获取主工序对应的产线
  174. var first = routingDtos.Where(p => p.level == 1).FirstOrDefault();
  175. if (first == null)
  176. {
  177. //记录排产异常原因
  178. scheduleExceptions.Add(new ScheduleExceptionMaster
  179. {
  180. RecID = help.NextId(),
  181. Domain = "1001",
  182. WorkOrd = item.WorkOrd,
  183. Remark = "工单没有维护工序数据",
  184. CreatTime = DateTime.Now.Date
  185. });
  186. continue;
  187. }
  188. if (first.lines.Count() == 0)
  189. {
  190. //记录排产异常原因
  191. scheduleExceptions.Add(new ScheduleExceptionMaster
  192. {
  193. RecID = help.NextId(),
  194. Domain = "1001",
  195. WorkOrd = item.WorkOrd,
  196. Remark = "工单没有维护产线数据",
  197. CreatTime = DateTime.Now.Date
  198. });
  199. continue;
  200. }
  201. //确定主工序排产产线以及排产开始时间
  202. LineDto line = ConfirmLine(first.Op, planStart, first.lines, schedules);
  203. //产线排产
  204. LineSchedule(item, line, routingDtos, periodsDet, scheduleResults, scheduleExceptions);
  205. }
  206. }
  207. /// <summary>
  208. /// 排产
  209. /// </summary>
  210. /// <param name="workOrd">工单</param>
  211. /// <param name="line">主工序对应的产线</param>
  212. /// <param name="routingDtos">每层级工序对应的产线信息</param>
  213. /// <param name="periodsDet">生产周期</param>
  214. /// <param name="scheduleResults">排产结果</param>
  215. /// <param name="scheduleExceptions">排产异常记录</param>
  216. public void LineSchedule(WorkOrdMaster workOrd, LineDto line, List<WorkOrdRoutingDto> routingDtos,List<PeriodSequenceDet> periodsDet, List<ScheduleResultOpMaster> scheduleResults, List<ScheduleExceptionMaster> scheduleExceptions)
  217. {
  218. //生产周期
  219. List<PeriodSequenceDet> curSequences = new List<PeriodSequenceDet>();
  220. //排产明细
  221. List< ScheduleResultOpMaster > curScheduleRsts = new List<ScheduleResultOpMaster >();
  222. //获取当前产线的工作日历
  223. var curCalendars = calendars.Where(p => p.ProdLine == line.Line).ToList();
  224. //获取当前产线的休息记录
  225. var curQtyDtls = qualityLines.Where(p => p.ProdLine == line.Line).OrderBy(m => m.Line).ToList();
  226. if (curCalendars.Count() == 0 || curCalendars.Select(p=>p.WeekDay).Distinct().Count() != 7)
  227. {
  228. //记录排产异常原因
  229. scheduleExceptions.Add(new ScheduleExceptionMaster
  230. {
  231. RecID = help.NextId(),
  232. Domain = "1001",
  233. WorkOrd = workOrd.WorkOrd,
  234. Remark = "工单对应产线工作日历数据错误",
  235. CreatTime = DateTime.Now.Date
  236. });
  237. return;
  238. }
  239. //产线可排产开始日期
  240. DateTime workStartTime = DealStartTime(line.StartTime, curCalendars, curQtyDtls);
  241. }
  242. /// <summary>
  243. /// 计算主产线实际排产开始时间
  244. /// </summary>
  245. /// <param name="startTime">开始时间</param>
  246. /// <param name="curCalendars">当前产线工作日历</param>
  247. /// <param name="curQtyDtls">当前产线休息记录</param>
  248. /// <returns></returns>
  249. public DateTime DealStartTime(DateTime startTime,List<ShopCalendarWorkCtr> curCalendars, List<QualityLineWorkDetail> curQtyDtls)
  250. {
  251. //实际排产开始时间
  252. DateTime actStart = startTime;
  253. //开始时间是周几
  254. int weekDay = (int)startTime.DayOfWeek;
  255. //当天的工作日历
  256. var shopCal = curCalendars.Where(p => p.WeekDay == weekDay).First();
  257. //当前日期的工作时间段
  258. List<LineWorkPointDto> workPoints = DealWorkDayToLevels(startTime, shopCal,curQtyDtls);
  259. //计算starttime处于那个工作时间段
  260. var curPoint = workPoints.Where(p => startTime >= p.StartPoint && startTime <= p.EndPoint).FirstOrDefault();
  261. if (startTime != curPoint.EndPoint)
  262. {
  263. return startTime;
  264. }
  265. //查询下一时间段的开始时间点
  266. var nextPoint = workPoints.Where(p => p.Level == curPoint.Level + 1).FirstOrDefault();
  267. if (nextPoint != null)
  268. {
  269. return nextPoint.StartPoint;
  270. }
  271. //开始时间为今天下班时间,实际排产开始时间为下一个工作日的开始时间
  272. actStart = GetNextWorkDay(weekDay, startTime, curCalendars);
  273. return actStart;
  274. }
  275. /// <summary>
  276. /// 获取下一个工作日开始时间
  277. /// </summary>
  278. /// <param name="weekDay">当前周几</param>
  279. /// <param name="startTime">开始时间</param>
  280. /// <param name="curCalendars">当前产线的工作日历</param>
  281. /// <returns></returns>
  282. public DateTime GetNextWorkDay(int weekDay, DateTime startTime, List<ShopCalendarWorkCtr> curCalendars)
  283. {
  284. DateTime rtnData = startTime;
  285. //下一天
  286. DateTime nextDate = startTime.Date.AddDays(1);
  287. //下一天是周几
  288. int nextWeekDay = (weekDay + 1) % 7;
  289. var calendar = curCalendars.FirstOrDefault(p=>p.WeekDay == nextWeekDay);
  290. //判断下一天是否是工作日
  291. if (nextWeekDay == 0 || nextWeekDay == 6)//下一天是周六或者周日,需要判断是否调休,需要加班
  292. {
  293. if (!holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "调休"))//下一天是周末
  294. {
  295. //递归继续找下一个工作日
  296. GetNextWorkDay(nextWeekDay, nextDate, curCalendars);
  297. }
  298. rtnData = nextDate.AddHours((double)calendar.ShiftsStart1);
  299. return rtnData;
  300. }
  301. //下一天不是周六周日,需要判断是不是节假日
  302. if (holidays.Exists(p => p.Dated.GetValueOrDefault().Date == nextDate && p.Ufld1 == "休假"))//是节假日
  303. {
  304. //递归继续找下一个工作日
  305. GetNextWorkDay(nextWeekDay, nextDate, curCalendars);
  306. }
  307. rtnData = nextDate.AddHours((double)calendar.ShiftsStart1);
  308. return rtnData;
  309. }
  310. /// <summary>
  311. /// 处理当前日期的工作时间段
  312. /// </summary>
  313. /// <param name="startTime"></param>
  314. /// <param name="shopCal">当前产线的工作日历-周几</param>
  315. /// <param name="curQtyDtls">每天休息记录</param>
  316. /// <returns></returns>
  317. public List<LineWorkPointDto> DealWorkDayToLevels(DateTime startTime, ShopCalendarWorkCtr shopCal, List<QualityLineWorkDetail> curQtyDtls)
  318. {
  319. //年-月-日
  320. string date = startTime.Date.ToString("yyyy-MM-dd");
  321. //排产记录结束日期是周几
  322. int weekDay = (int)startTime.DayOfWeek;
  323. //计算当天的开工时间点,停工时间点
  324. DateTime dayStartPoint = startTime.Date.AddHours(Convert.ToDouble(shopCal.ShiftsStart1));
  325. DateTime dayEndPoint = dayStartPoint.AddHours(Convert.ToDouble(shopCal.ShiftsHours1));
  326. //工作时间段
  327. List<LineWorkPointDto> workPoints = new List<LineWorkPointDto>();
  328. LineWorkPointDto dto = new LineWorkPointDto();
  329. dto.Level = 1;
  330. dto.Line = shopCal.ProdLine;
  331. dto.WeekDay = weekDay;
  332. dto.StartPoint = dayStartPoint;
  333. int level = 1;
  334. foreach (var item in curQtyDtls)
  335. {
  336. DateTime endPoint = Convert.ToDateTime(date + " " + item.RestTimePoint);
  337. workPoints.Add(dto);
  338. level++;
  339. dto = new LineWorkPointDto();
  340. dto.Level = level;
  341. dto.Line = shopCal.ProdLine;
  342. dto.WeekDay = weekDay;
  343. dto.StartPoint = endPoint.AddMinutes(item.RestTime);
  344. }
  345. dto.EndPoint = dayEndPoint;
  346. workPoints.Add(dto);
  347. return workPoints;
  348. }
  349. /// <summary>
  350. /// 确定工序排产对应的产线
  351. /// </summary>
  352. /// <param name="op">主产线的最后一道工序</param>
  353. /// <param name="planStart">当前工单计划开始时间</param>
  354. /// <param name="lines">主工序对应的产线</param>
  355. /// <param name="scheduleResults"></param>
  356. /// <returns></returns>
  357. public LineDto ConfirmLine(int op, DateTime planStart, List<LineDto> lines, List<ScheduleResultOpMaster> scheduleResults)
  358. {
  359. LineDto rtn = new LineDto();
  360. //多条产线确认最优产线:按开工时间升序,产能倒叙排序
  361. foreach (var item in lines)
  362. {
  363. //获取当前产线的排产最晚结束时间
  364. var schedule = scheduleResults.Where(p => p.Line == item.Line && p.Op == op).OrderByDescending(p => p.WorkEndTime).FirstOrDefault();
  365. //当前产线没有排产记录,开始日期= planStart;
  366. //当前产线有排产记录,如果产线排产最晚结束时间≤planStart 开始日期=planStart,反之 开始日期 = WorkEndTime
  367. var latest = schedule == null ? planStart : (schedule.WorkEndTime <= planStart ? planStart : schedule.WorkEndTime);
  368. if (rtn == null)
  369. {
  370. rtn.Line = item.Line;
  371. rtn.Rate = item.Rate;
  372. rtn.StartTime = latest;
  373. }
  374. if (latest < rtn.StartTime || (latest == rtn.StartTime && item.Rate > rtn.Rate))
  375. {//当前产线最早开工时间早于之前的产线或者产线开工时间相同且当前产线产能大于之前的产线,则当前产线为优先选择产线
  376. rtn.Line = item.Line;
  377. rtn.Rate = item.Rate;
  378. rtn.StartTime = latest;
  379. }
  380. }
  381. return rtn;
  382. }
  383. /// <summary>
  384. /// 工单工艺路线预处理
  385. /// </summary>
  386. /// <param name="woRuntings">工单工序</param>
  387. /// <param name="prodLines">产线</param>
  388. /// <returns></returns>
  389. public List<WorkOrdRoutingDto> ProcPretreatment(List<WorkOrdRouting> woRuntings, List<ProdLineDetail> prodLines)
  390. {
  391. List<WorkOrdRoutingDto> routingDtos = new List<WorkOrdRoutingDto>();
  392. //取主工序(第一层级工序)
  393. var firsts = woRuntings.Where(p =>p.ParentOp == 0).ToList();
  394. if (firsts.Count == 0)
  395. {
  396. return routingDtos;
  397. }
  398. WorkOrdRoutingDto dto = new WorkOrdRoutingDto();
  399. //当前层级的关键工序/报工工序(当前层级工序Op最大的工序)
  400. var lastOp = firsts.OrderByDescending(p => p.OP).FirstOrDefault();
  401. if (lastOp != null) {
  402. dto.ParentOp = lastOp.ParentOp;
  403. dto.level = 1;
  404. dto.Op = lastOp.OP;
  405. //当前层级工序对应的产线
  406. var curLines = prodLines.Where(p => p.Part == lastOp.WorkOrd && p.Op == lastOp.OP).Select(m => new LineDto
  407. {
  408. Line = m.Line,
  409. Rate = m.Rate,
  410. SetupTime = m.SetupTime
  411. }).AsQueryable<LineDto>().ToList();
  412. dto.lines = curLines;
  413. routingDtos.Add(dto);
  414. }
  415. //递归处理其他层级工序
  416. RecursionProc(woRuntings, firsts, 1, routingDtos, prodLines);
  417. return routingDtos;
  418. }
  419. /// <summary>
  420. /// 递归处理工序
  421. /// </summary>
  422. /// <param name="woRuntings">工单工序</param>
  423. /// <param name="preLevels">上-层级工序</param>
  424. /// <param name="level">层级</param>
  425. /// <param name="routingDtos">返回结果</param>
  426. /// <param name="prodLines">产线</param>
  427. public void RecursionProc(List<WorkOrdRouting> woRuntings, List<WorkOrdRouting> preLevels, int level, List<WorkOrdRoutingDto> routingDtos, List<ProdLineDetail> prodLines)
  428. {
  429. //获取当前层级工序
  430. var curLevels = woRuntings.Where(p => preLevels.Select(m=>m.OP).Contains(p.ParentOp)).ToList();
  431. if (curLevels.Count() == 0)
  432. {
  433. return;
  434. }
  435. //获取父级Op
  436. var parentOps = curLevels.Select(m => m.ParentOp).Distinct().ToList();
  437. foreach (var item in parentOps)
  438. {
  439. var dto = new WorkOrdRoutingDto();
  440. var lastOp = curLevels.Where(p=>p.ParentOp == item).OrderByDescending(m=>m.OP).FirstOrDefault();
  441. if (lastOp != null){
  442. dto.Op = lastOp.OP;
  443. dto.ParentOp = lastOp.ParentOp;
  444. dto.level = level + 1;
  445. //当前层级工序对应的产线
  446. var curLines = prodLines.Where(p => p.Part == lastOp.WorkOrd && p.Op == lastOp.OP).Select(m => new LineDto
  447. {
  448. Line = m.Line,
  449. Rate = m.Rate,
  450. SetupTime = m.SetupTime
  451. }).AsQueryable<LineDto>().ToList();
  452. dto.lines = curLines;
  453. routingDtos.Add(dto);
  454. }
  455. }
  456. //递归
  457. RecursionProc(woRuntings, curLevels, level + 1, routingDtos,prodLines);
  458. }
  459. }
  460. }