|
|
@@ -4,9 +4,12 @@ import com.jeesite.common.entity.Page;
|
|
|
import com.jeesite.common.lang.StringUtils;
|
|
|
import com.jeesite.common.service.CrudService;
|
|
|
import com.jeesite.common.service.ServiceException;
|
|
|
+import com.jeesite.common.mybatis.mapper.query.QueryType;
|
|
|
import com.jeesite.modules.mes.dao.MesErpScheduleDao;
|
|
|
import com.jeesite.modules.mes.entity.MesErpSchedule;
|
|
|
import com.jeesite.modules.mes.entity.MesLine;
|
|
|
+import com.jeesite.modules.mes.entity.MesLineProcess;
|
|
|
+import com.jeesite.modules.mes.entity.MesProductRecord;
|
|
|
import com.jeesite.modules.mes.erp.ErpProductionOrder;
|
|
|
import com.jeesite.modules.mes.erp.ErpProductionOrderService;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
@@ -14,13 +17,23 @@ import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.ObjectUtils;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.DayOfWeek;
|
|
|
import java.time.LocalDate;
|
|
|
+import java.time.YearMonth;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.time.format.DateTimeParseException;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.time.temporal.TemporalAdjusters;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
/**
|
|
|
* ERP排产计划Service
|
|
|
@@ -29,6 +42,7 @@ import java.util.Map;
|
|
|
public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErpSchedule> {
|
|
|
|
|
|
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+ private static final String CRAFT_TOTAL_RESULT = "100000";
|
|
|
private static final Map<String, String> CATEGORY_LINE_SN_MAP;
|
|
|
|
|
|
static {
|
|
|
@@ -41,13 +55,19 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
|
|
|
|
|
|
private final MesErpScheduleDao mesErpScheduleDao;
|
|
|
private final MesLineService mesLineService;
|
|
|
+ private final MesLineProcessService mesLineProcessService;
|
|
|
+ private final MesProductRecordService mesProductRecordService;
|
|
|
private final ErpProductionOrderService erpProductionOrderService;
|
|
|
|
|
|
public MesErpScheduleService(MesErpScheduleDao mesErpScheduleDao,
|
|
|
MesLineService mesLineService,
|
|
|
+ MesLineProcessService mesLineProcessService,
|
|
|
+ MesProductRecordService mesProductRecordService,
|
|
|
ErpProductionOrderService erpProductionOrderService) {
|
|
|
this.mesErpScheduleDao = mesErpScheduleDao;
|
|
|
this.mesLineService = mesLineService;
|
|
|
+ this.mesLineProcessService = mesLineProcessService;
|
|
|
+ this.mesProductRecordService = mesProductRecordService;
|
|
|
this.erpProductionOrderService = erpProductionOrderService;
|
|
|
}
|
|
|
|
|
|
@@ -136,6 +156,292 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
|
|
|
return sum == null ? 0 : sum;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 排产看板数据:按月份返回周历行与计划条
|
|
|
+ */
|
|
|
+ public Map<String, Object> buildBoardDataForMonth(String monthStr) {
|
|
|
+ YearMonth yearMonth = parseYearMonth(monthStr);
|
|
|
+ LocalDate monthStart = yearMonth.atDay(1);
|
|
|
+ LocalDate monthEnd = yearMonth.atEndOfMonth();
|
|
|
+ String startStr = monthStart.format(DATE_FORMAT);
|
|
|
+ String endStr = monthEnd.format(DATE_FORMAT);
|
|
|
+
|
|
|
+ List<MesErpSchedule> plans = mesErpScheduleDao.findListInDateRange(startStr, endStr);
|
|
|
+ if (plans == null) {
|
|
|
+ plans = Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<String> lineIdsWithPlan = new LinkedHashSet<>();
|
|
|
+ for (MesErpSchedule plan : plans) {
|
|
|
+ if (plan != null && StringUtils.isNotBlank(plan.getLineId())) {
|
|
|
+ lineIdsWithPlan.add(plan.getLineId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("month", yearMonth.toString());
|
|
|
+ result.put("monthLabel", yearMonth.getYear() + "\u5e74" + yearMonth.getMonthValue() + "\u6708");
|
|
|
+ result.put("startDate", startStr);
|
|
|
+ result.put("endDate", endStr);
|
|
|
+ result.put("weeks", buildMonthWeeks(monthStart, monthEnd));
|
|
|
+ result.put("lines", buildBoardLines(lineIdsWithPlan));
|
|
|
+ result.put("plans", plans);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 排产详情:产线工位及工艺号100000 OK数量
|
|
|
+ */
|
|
|
+ public Map<String, Object> buildScheduleDetail(MesErpSchedule schedule) {
|
|
|
+ if (schedule == null || StringUtils.isBlank(schedule.getId())) {
|
|
|
+ throw new ServiceException(text("排产计划不存在"));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> stationStats = buildStationProductionStats(schedule);
|
|
|
+ long totalOk = 0;
|
|
|
+ for (Map<String, Object> row : stationStats) {
|
|
|
+ Object okQty = row.get("okQty");
|
|
|
+ if (okQty instanceof Number) {
|
|
|
+ totalOk += ((Number) okQty).longValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("schedule", schedule);
|
|
|
+ result.put("stationStats", stationStats);
|
|
|
+ result.put("totalOkQty", totalOk);
|
|
|
+ result.put("stationCount", stationStats.size());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> buildStationProductionStats(MesErpSchedule schedule) {
|
|
|
+ MesLine line = resolveScheduleLine(schedule);
|
|
|
+ if (line == null || StringUtils.isBlank(line.getId())) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ String lineId = line.getId().trim();
|
|
|
+ String lineSn = StringUtils.trim(line.getSn());
|
|
|
+ schedule.setLineId(lineId);
|
|
|
+ schedule.setLineSn(lineSn);
|
|
|
+ schedule.setLineTitle(line.getTitle());
|
|
|
+
|
|
|
+ List<MesLineProcess> processes = mesLineProcessService.findParentListByLine(lineId, lineSn);
|
|
|
+ if (processes == null || processes.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ String startTime = normalizeDate(schedule.getScheduleStartDate()) + " 00:00:00";
|
|
|
+ String endTime = normalizeDate(schedule.getScheduleEndDate()) + " 23:59:59";
|
|
|
+ Integer scheduleQty = schedule.getScheduleQty();
|
|
|
+ List<Map<String, Object>> rows = new ArrayList<>();
|
|
|
+ for (MesLineProcess process : processes) {
|
|
|
+ if (process == null || StringUtils.isBlank(process.getOprno())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ long okQty = countStationCraftOk(lineSn, process.getOprno(), startTime, endTime);
|
|
|
+ Map<String, Object> row = new LinkedHashMap<>();
|
|
|
+ row.put("oprno", process.getOprno());
|
|
|
+ row.put("title", StringUtils.defaultString(process.getTitle()));
|
|
|
+ row.put("lineTitle", StringUtils.defaultString(process.getLineTitle()));
|
|
|
+ row.put("lineSn", StringUtils.defaultString(process.getLineSn()));
|
|
|
+ row.put("sorts", process.getSorts());
|
|
|
+ row.put("okQty", okQty);
|
|
|
+ row.put("scheduleQty", scheduleQty);
|
|
|
+ row.put("completionRate", calcCompletionRate(okQty, scheduleQty));
|
|
|
+ rows.add(row);
|
|
|
+ }
|
|
|
+ return rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private MesLine resolveScheduleLine(MesErpSchedule schedule) {
|
|
|
+ if (schedule == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ MesLine byId = null;
|
|
|
+ if (StringUtils.isNotBlank(schedule.getLineId())) {
|
|
|
+ byId = mesLineService.get(new MesLine(schedule.getLineId().trim()));
|
|
|
+ }
|
|
|
+ MesLine bySn = null;
|
|
|
+ if (StringUtils.isNotBlank(schedule.getLineSn())) {
|
|
|
+ MesLine query = new MesLine();
|
|
|
+ query.setSn(schedule.getLineSn().trim());
|
|
|
+ query.setStatus(MesLine.STATUS_NORMAL);
|
|
|
+ bySn = mesLineService.findInfo(query);
|
|
|
+ }
|
|
|
+ if (bySn != null) {
|
|
|
+ return bySn;
|
|
|
+ }
|
|
|
+ return byId;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countStationCraftOk(String lineSn, String oprno, String startTime, String endTime) {
|
|
|
+ if (StringUtils.isBlank(lineSn) || StringUtils.isBlank(oprno)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ MesProductRecord query = new MesProductRecord();
|
|
|
+ query.setContent("OK");
|
|
|
+ query.setCraft(CRAFT_TOTAL_RESULT);
|
|
|
+ query.setLineSn(lineSn);
|
|
|
+ query.getSqlMap().getWhere()
|
|
|
+ .and("a.oprno", QueryType.RIGHT_LIKE, oprno.trim())
|
|
|
+ .and("a.update_date", QueryType.GTE, startTime)
|
|
|
+ .and("a.update_date", QueryType.LTE, endTime);
|
|
|
+ Long count = mesProductRecordService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String calcCompletionRate(long okQty, Integer scheduleQty) {
|
|
|
+ if (scheduleQty == null || scheduleQty <= 0) {
|
|
|
+ return "-";
|
|
|
+ }
|
|
|
+ BigDecimal rate = BigDecimal.valueOf(okQty)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(BigDecimal.valueOf(scheduleQty), 1, RoundingMode.HALF_UP);
|
|
|
+ return rate.toPlainString() + "%";
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> buildMonthWeeks(LocalDate monthStart, LocalDate monthEnd) {
|
|
|
+ List<Map<String, Object>> weeks = new ArrayList<>();
|
|
|
+ LocalDate weekStart = monthStart.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
|
|
+ LocalDate lastWeekStart = monthEnd.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
|
|
|
+ while (!weekStart.isAfter(lastWeekStart)) {
|
|
|
+ List<Map<String, Object>> days = new ArrayList<>();
|
|
|
+ boolean weekInMonth = false;
|
|
|
+ for (int i = 0; i < 7; i++) {
|
|
|
+ LocalDate day = weekStart.plusDays(i);
|
|
|
+ boolean inMonth = !day.isBefore(monthStart) && !day.isAfter(monthEnd);
|
|
|
+ if (inMonth) {
|
|
|
+ weekInMonth = true;
|
|
|
+ }
|
|
|
+ Map<String, Object> cell = new HashMap<>();
|
|
|
+ cell.put("date", day.format(DATE_FORMAT));
|
|
|
+ cell.put("day", day.getDayOfMonth());
|
|
|
+ cell.put("inMonth", inMonth);
|
|
|
+ cell.put("weekend", day.getDayOfWeek() == DayOfWeek.SATURDAY
|
|
|
+ || day.getDayOfWeek() == DayOfWeek.SUNDAY);
|
|
|
+ days.add(cell);
|
|
|
+ }
|
|
|
+ if (weekInMonth) {
|
|
|
+ Map<String, Object> week = new HashMap<>();
|
|
|
+ week.put("weekStart", weekStart.format(DATE_FORMAT));
|
|
|
+ week.put("days", days);
|
|
|
+ weeks.add(week);
|
|
|
+ }
|
|
|
+ weekStart = weekStart.plusDays(7);
|
|
|
+ }
|
|
|
+ return weeks;
|
|
|
+ }
|
|
|
+
|
|
|
+ private YearMonth parseYearMonth(String monthStr) {
|
|
|
+ if (StringUtils.isBlank(monthStr)) {
|
|
|
+ return YearMonth.now();
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return YearMonth.parse(monthStr.trim());
|
|
|
+ } catch (Exception ex) {
|
|
|
+ throw new ServiceException(text("月份格式不正确,请使用 yyyy-MM"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 排产看板数据:按日期范围返回产线行与计划条
|
|
|
+ */
|
|
|
+ public Map<String, Object> buildBoardData(String startDate, String endDate) {
|
|
|
+ LocalDate rangeStart = parseDate(startDate, "开始日期");
|
|
|
+ LocalDate rangeEnd = parseDate(endDate, "结束日期");
|
|
|
+ if (rangeEnd.isBefore(rangeStart)) {
|
|
|
+ throw new ServiceException(text("结束日期不能早于开始日期"));
|
|
|
+ }
|
|
|
+ long daySpan = ChronoUnit.DAYS.between(rangeStart, rangeEnd) + 1;
|
|
|
+ if (daySpan > 62) {
|
|
|
+ throw new ServiceException(text("看板日期范围不能超过 {0} 天", "62"));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> dates = new ArrayList<>();
|
|
|
+ for (LocalDate cursor = rangeStart; !cursor.isAfter(rangeEnd); cursor = cursor.plusDays(1)) {
|
|
|
+ dates.add(cursor.format(DATE_FORMAT));
|
|
|
+ }
|
|
|
+
|
|
|
+ String startStr = rangeStart.format(DATE_FORMAT);
|
|
|
+ String endStr = rangeEnd.format(DATE_FORMAT);
|
|
|
+ List<MesErpSchedule> plans = mesErpScheduleDao.findListInDateRange(startStr, endStr);
|
|
|
+ if (plans == null) {
|
|
|
+ plans = Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<String> lineIdsWithPlan = new LinkedHashSet<>();
|
|
|
+ for (MesErpSchedule plan : plans) {
|
|
|
+ if (plan != null && StringUtils.isNotBlank(plan.getLineId())) {
|
|
|
+ lineIdsWithPlan.add(plan.getLineId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, String>> lineRows = buildBoardLines(lineIdsWithPlan);
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("startDate", startStr);
|
|
|
+ result.put("endDate", endStr);
|
|
|
+ result.put("dates", dates);
|
|
|
+ result.put("lines", lineRows);
|
|
|
+ result.put("plans", plans);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, String>> buildBoardLines(Set<String> lineIdsWithPlan) {
|
|
|
+ MesLine query = new MesLine();
|
|
|
+ query.setStatus(MesLine.STATUS_NORMAL);
|
|
|
+ List<MesLine> allLines = mesLineService.findList(query);
|
|
|
+ if (allLines == null) {
|
|
|
+ allLines = Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, MesLine> lineMap = new LinkedHashMap<>();
|
|
|
+ for (MesLine line : allLines) {
|
|
|
+ if (line == null || StringUtils.isBlank(line.getId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String sn = StringUtils.trim(line.getSn());
|
|
|
+ if ("HEVXT".equalsIgnoreCase(sn) || "115XT".equalsIgnoreCase(sn) || lineIdsWithPlan.contains(line.getId())) {
|
|
|
+ lineMap.put(line.getId(), line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (String lineId : lineIdsWithPlan) {
|
|
|
+ if (lineMap.containsKey(lineId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ MesLine line = mesLineService.get(new MesLine(lineId));
|
|
|
+ if (!ObjectUtils.isEmpty(line) && MesLine.STATUS_NORMAL.equals(line.getStatus())) {
|
|
|
+ lineMap.put(line.getId(), line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<MesLine> sorted = new ArrayList<>(lineMap.values());
|
|
|
+ sorted.sort(Comparator.comparingInt(this::boardLineSortOrder).thenComparing(
|
|
|
+ line -> StringUtils.defaultString(line.getSn())));
|
|
|
+ List<Map<String, String>> rows = new ArrayList<>();
|
|
|
+ for (MesLine line : sorted) {
|
|
|
+ Map<String, String> row = new HashMap<>();
|
|
|
+ row.put("id", line.getId());
|
|
|
+ row.put("sn", StringUtils.defaultString(line.getSn()));
|
|
|
+ row.put("title", StringUtils.defaultString(line.getTitle()));
|
|
|
+ rows.add(row);
|
|
|
+ }
|
|
|
+ return rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int boardLineSortOrder(MesLine line) {
|
|
|
+ if (line == null) {
|
|
|
+ return 99;
|
|
|
+ }
|
|
|
+ String sn = StringUtils.trim(line.getSn());
|
|
|
+ if ("HEVXT".equalsIgnoreCase(sn)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if ("115XT".equalsIgnoreCase(sn)) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 10;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional
|
|
|
public void save(MesErpSchedule mesErpSchedule) {
|
|
|
@@ -193,6 +499,25 @@ public class MesErpScheduleService extends CrudService<MesErpScheduleDao, MesErp
|
|
|
if (mesErpSchedule.getScheduleQty() > remainQty) {
|
|
|
throw new ServiceException(text("排产数量不能超过剩余可排产量(剩余 {0})", String.valueOf(remainQty)));
|
|
|
}
|
|
|
+
|
|
|
+ validateLineDateConflict(mesErpSchedule.getLineId(), mesErpSchedule.getScheduleStartDate(),
|
|
|
+ mesErpSchedule.getScheduleEndDate(), mesErpSchedule.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateLineDateConflict(String lineId, String scheduleStartDate, String scheduleEndDate,
|
|
|
+ String excludeId) {
|
|
|
+ if (StringUtils.isBlank(lineId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ MesErpSchedule conflict = mesErpScheduleDao.findLineDateConflict(lineId, scheduleStartDate, scheduleEndDate,
|
|
|
+ excludeId);
|
|
|
+ if (conflict == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ throw new ServiceException(text("该产线在排产日期内已有其他计划({0},{1} ~ {2}),每条产线每天只能有一个计划",
|
|
|
+ StringUtils.defaultString(conflict.getErpBillNo()),
|
|
|
+ StringUtils.defaultString(conflict.getScheduleStartDate()),
|
|
|
+ StringUtils.defaultString(conflict.getScheduleEndDate())));
|
|
|
}
|
|
|
|
|
|
private void fillLineSnapshot(MesErpSchedule mesErpSchedule) {
|