|
|
@@ -4,6 +4,7 @@ import java.text.DateFormat;
|
|
|
import java.text.DecimalFormat;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
@@ -91,6 +92,8 @@ public class MesProductController extends BaseController {
|
|
|
private MesRepairService mesRepairService;
|
|
|
@Autowired
|
|
|
private FileUploadService fileUploadService;
|
|
|
+ @Autowired
|
|
|
+ private MesPlanService mesPlanService;
|
|
|
|
|
|
|
|
|
/**
|
|
|
@@ -330,6 +333,21 @@ public class MesProductController extends BaseController {
|
|
|
return "modules/mes/mesScreen9";
|
|
|
}
|
|
|
|
|
|
+ @RequestMapping(value = "screen10")
|
|
|
+ public String screen10(Model model) {
|
|
|
+ return "modules/mes/mesScreen10";
|
|
|
+ }
|
|
|
+
|
|
|
+ @RequestMapping(value = "screen11")
|
|
|
+ public String screen11(Model model) {
|
|
|
+ return "modules/mes/mesScreen11";
|
|
|
+ }
|
|
|
+
|
|
|
+ @RequestMapping(value = "screenManage")
|
|
|
+ public String screenManage() {
|
|
|
+ return REDIRECT + adminPath + "/mes/mesScreen/list";
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 返修区大屏数据:汇总统计 + 待处理返修清单(数据来源 mes_repair)
|
|
|
* state: 1=待返修 2=待检查 3=已完成 4=返修NG待处理
|
|
|
@@ -978,4 +996,330 @@ public class MesProductController extends BaseController {
|
|
|
resp.setData(map);
|
|
|
return resp;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生产运营看板:达成率、直通率、OEE、班次报工。
|
|
|
+ * 统计日为当天 08:00 至次日 08:00;8 点前归入上一统计日。
|
|
|
+ * 达成率 = 完工 / 班次计划;直通率 = 工位合格 / (合格 + 不良);
|
|
|
+ * OEE = 时间稼动 × 性能稼动 × 合格率。时间稼动按未关闭报警占在用工位的比例折算。
|
|
|
+ */
|
|
|
+ @RequestMapping(value = "screenKpiData")
|
|
|
+ @ResponseBody
|
|
|
+ public CommonResp screenKpiData() {
|
|
|
+ CommonResp<Map<String, Object>> resp = new CommonResp<>();
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDate day = now.getHour() < 8 ? now.toLocalDate().minusDays(1) : now.toLocalDate();
|
|
|
+ String dayStr = day.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
|
+ String dayStart = dayStr + " 08:00:00";
|
|
|
+ String dayEnd = day.plusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 08:00:00";
|
|
|
+
|
|
|
+ List<MesPlan> todayPlans = loadPlans(dayStr);
|
|
|
+ long plan = sumPlanNums(todayPlans, null);
|
|
|
+ long actual = countFinished(dayStart, dayEnd, null);
|
|
|
+ long repair = countRepair(dayStart, dayEnd);
|
|
|
+ long ok = countCraftResult("OK", dayStart, dayEnd);
|
|
|
+ long ng = countCraftResult("NG", dayStart, dayEnd);
|
|
|
+ double achieve = ratio(actual, plan, false);
|
|
|
+ double fpy = firstPassRate(ok, ng, actual, repair);
|
|
|
+ double performance = plan <= 0 ? 0D : ratio(actual, plan, true);
|
|
|
+ long openAlarm = countOpenAlarm();
|
|
|
+ long stations = countStations();
|
|
|
+ double availability = availabilityRate(openAlarm, stations);
|
|
|
+ double oee = round1(availability * performance * fpy / 10000D);
|
|
|
+
|
|
|
+ MesShiftDataResp curShift = mesShiftService.getCurShift();
|
|
|
+ String curShiftId = curShift == null || curShift.getMesShift() == null ? "" : curShift.getMesShift().getId();
|
|
|
+ String curShiftTitle = curShift == null || curShift.getMesShift() == null ? "未排班" : curShift.getMesShift().getTitle();
|
|
|
+
|
|
|
+ MesShift shiftQuery = new MesShift();
|
|
|
+ shiftQuery.setStatus("0");
|
|
|
+ List<MesShift> shifts = mesShiftService.findList(shiftQuery);
|
|
|
+ if (shifts == null) {
|
|
|
+ shifts = ListUtils.newArrayList();
|
|
|
+ }
|
|
|
+ shifts.sort(Comparator.comparing(item -> hourMinute(item.getStart())));
|
|
|
+
|
|
|
+ List<Map<String, Object>> shiftRows = ListUtils.newArrayList();
|
|
|
+ double curShiftAchieve = 0D;
|
|
|
+ for (MesShift shift : shifts) {
|
|
|
+ String[] window = shiftWindow(day, shift);
|
|
|
+ if (window == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ long shiftPlan = sumPlanNums(todayPlans, shift.getId());
|
|
|
+ long shiftActual = countFinished(window[0], window[1], null);
|
|
|
+ long shiftOk = countCraftResult("OK", window[0], window[1]);
|
|
|
+ long shiftNg = countCraftResult("NG", window[0], window[1]);
|
|
|
+ long shiftRepair = countRepair(window[0], window[1]);
|
|
|
+ double shiftAchieve = ratio(shiftActual, shiftPlan, false);
|
|
|
+ double shiftFpy = firstPassRate(shiftOk, shiftNg, shiftActual, shiftRepair);
|
|
|
+ boolean current = StringUtils.equals(curShiftId, shift.getId());
|
|
|
+ if (current) {
|
|
|
+ curShiftAchieve = shiftAchieve;
|
|
|
+ }
|
|
|
+ Map<String, Object> row = MapUtils.newHashMap();
|
|
|
+ row.put("title", shift.getTitle());
|
|
|
+ row.put("range", window[2]);
|
|
|
+ row.put("plan", shiftPlan);
|
|
|
+ row.put("actual", shiftActual);
|
|
|
+ row.put("ok", shiftOk);
|
|
|
+ row.put("ng", shiftNg);
|
|
|
+ row.put("repair", shiftRepair);
|
|
|
+ row.put("achieve", shiftAchieve);
|
|
|
+ row.put("fpy", shiftFpy);
|
|
|
+ row.put("current", current);
|
|
|
+ shiftRows.add(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> productRows = buildProductRows(todayPlans, dayStart, dayEnd);
|
|
|
+ List<String> hourLabels = ListUtils.newArrayList();
|
|
|
+ List<Long> hourActual = ListUtils.newArrayList();
|
|
|
+ List<Long> hourPlan = ListUtils.newArrayList();
|
|
|
+ long hourTarget = plan <= 0 ? 0 : Math.round(plan / 12D);
|
|
|
+ LocalDateTime bucket = day.atTime(8, 0);
|
|
|
+ DateTimeFormatter hourFmt = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
+ DateTimeFormatter fullFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ for (int i = 0; i < 12; i++) {
|
|
|
+ LocalDateTime bucketEnd = bucket.plusHours(2);
|
|
|
+ hourLabels.add(bucket.format(hourFmt));
|
|
|
+ hourActual.add(countFinished(bucket.format(fullFmt), bucketEnd.format(fullFmt), null));
|
|
|
+ hourPlan.add(hourTarget);
|
|
|
+ bucket = bucketEnd;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> trendLabels = ListUtils.newArrayList();
|
|
|
+ List<Double> trendAchieve = ListUtils.newArrayList();
|
|
|
+ List<Double> trendFpy = ListUtils.newArrayList();
|
|
|
+ List<Double> trendOee = ListUtils.newArrayList();
|
|
|
+ List<Long> trendOutput = ListUtils.newArrayList();
|
|
|
+ List<Long> trendPlanQty = ListUtils.newArrayList();
|
|
|
+ DateTimeFormatter trendFmt = DateTimeFormatter.ofPattern("M/d");
|
|
|
+ for (int i = 6; i >= 0; i--) {
|
|
|
+ LocalDate trendDay = day.minusDays(i);
|
|
|
+ String trendDayStr = trendDay.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
|
|
+ String trendStart = trendDayStr + " 08:00:00";
|
|
|
+ String trendEnd = trendDay.plusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " 08:00:00";
|
|
|
+ long trendPlan = i == 0 ? plan : sumPlanNums(loadPlans(trendDayStr), null);
|
|
|
+ long trendActual = i == 0 ? actual : countFinished(trendStart, trendEnd, null);
|
|
|
+ long trendOk = i == 0 ? ok : countCraftResult("OK", trendStart, trendEnd);
|
|
|
+ long trendNg = i == 0 ? ng : countCraftResult("NG", trendStart, trendEnd);
|
|
|
+ long trendRepair = i == 0 ? repair : countRepair(trendStart, trendEnd);
|
|
|
+ double trendAchieveRate = ratio(trendActual, trendPlan, false);
|
|
|
+ double trendFpyRate = firstPassRate(trendOk, trendNg, trendActual, trendRepair);
|
|
|
+ double trendPerformance = trendPlan <= 0 ? 0D : ratio(trendActual, trendPlan, true);
|
|
|
+ double trendAvailability = i == 0 ? availability : 100D;
|
|
|
+ trendLabels.add(trendDay.format(trendFmt));
|
|
|
+ trendAchieve.add(trendAchieveRate);
|
|
|
+ trendFpy.add(trendFpyRate);
|
|
|
+ trendOee.add(round1(trendAvailability * trendPerformance * trendFpyRate / 10000D));
|
|
|
+ trendOutput.add(trendActual);
|
|
|
+ trendPlanQty.add(trendPlan);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> summary = MapUtils.newHashMap();
|
|
|
+ summary.put("plan", plan);
|
|
|
+ summary.put("actual", actual);
|
|
|
+ summary.put("ok", ok);
|
|
|
+ summary.put("ng", ng);
|
|
|
+ summary.put("repair", repair);
|
|
|
+ summary.put("openAlarm", openAlarm);
|
|
|
+
|
|
|
+ Map<String, Object> kpi = MapUtils.newHashMap();
|
|
|
+ kpi.put("achieve", achieve);
|
|
|
+ kpi.put("fpy", fpy);
|
|
|
+ kpi.put("oee", oee);
|
|
|
+ kpi.put("availability", availability);
|
|
|
+ kpi.put("performance", performance);
|
|
|
+ kpi.put("quality", fpy);
|
|
|
+ kpi.put("shiftAchieve", curShiftAchieve);
|
|
|
+
|
|
|
+ Map<String, Object> hours = MapUtils.newHashMap();
|
|
|
+ hours.put("labels", hourLabels);
|
|
|
+ hours.put("actual", hourActual);
|
|
|
+ hours.put("plan", hourPlan);
|
|
|
+
|
|
|
+ Map<String, Object> trend = MapUtils.newHashMap();
|
|
|
+ trend.put("labels", trendLabels);
|
|
|
+ trend.put("achieve", trendAchieve);
|
|
|
+ trend.put("fpy", trendFpy);
|
|
|
+ trend.put("oee", trendOee);
|
|
|
+ trend.put("output", trendOutput);
|
|
|
+ trend.put("planQty", trendPlanQty);
|
|
|
+
|
|
|
+ Map<String, Object> data = MapUtils.newHashMap();
|
|
|
+ data.put("day", dayStr);
|
|
|
+ data.put("window", "08:00-次日08:00");
|
|
|
+ data.put("shiftTitle", curShiftTitle);
|
|
|
+ data.put("summary", summary);
|
|
|
+ data.put("kpi", kpi);
|
|
|
+ data.put("hours", hours);
|
|
|
+ data.put("trend", trend);
|
|
|
+ data.put("shifts", shiftRows);
|
|
|
+ data.put("products", productRows);
|
|
|
+ resp.setData(data);
|
|
|
+ resp.setResult(Global.TRUE);
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<MesPlan> loadPlans(String day) {
|
|
|
+ MesPlan query = new MesPlan();
|
|
|
+ query.setStatus("0");
|
|
|
+ query.setDay(day);
|
|
|
+ List<MesPlan> plans = mesPlanService.findList(query);
|
|
|
+ return plans == null ? ListUtils.newArrayList() : plans;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long sumPlanNums(List<MesPlan> plans, String shiftId) {
|
|
|
+ long sum = 0L;
|
|
|
+ for (MesPlan plan : plans) {
|
|
|
+ if (plan.getNums() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(shiftId) && !StringUtils.equals(shiftId, plan.getShiftId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ sum += plan.getNums();
|
|
|
+ }
|
|
|
+ return sum;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Map<String, Object>> buildProductRows(List<MesPlan> plans, String start, String end) {
|
|
|
+ Map<String, Map<String, Object>> grouped = new LinkedHashMap<>();
|
|
|
+ for (MesPlan plan : plans) {
|
|
|
+ String key = StringUtils.isNotEmpty(plan.getCateSn()) ? plan.getCateSn() : plan.getCateId();
|
|
|
+ if (StringUtils.isEmpty(key)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map<String, Object> row = grouped.get(key);
|
|
|
+ if (row == null) {
|
|
|
+ row = MapUtils.newHashMap();
|
|
|
+ row.put("title", StringUtils.isNotEmpty(plan.getCateTitle()) ? plan.getCateTitle() : key);
|
|
|
+ row.put("sn", key);
|
|
|
+ row.put("plan", 0L);
|
|
|
+ grouped.put(key, row);
|
|
|
+ }
|
|
|
+ long planNums = row.get("plan") instanceof Number ? ((Number) row.get("plan")).longValue() : 0L;
|
|
|
+ row.put("plan", planNums + (plan.getNums() == null ? 0 : plan.getNums()));
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> rows = ListUtils.newArrayList();
|
|
|
+ for (Map.Entry<String, Map<String, Object>> entry : grouped.entrySet()) {
|
|
|
+ Map<String, Object> row = entry.getValue();
|
|
|
+ long plan = ((Number) row.get("plan")).longValue();
|
|
|
+ long actual = countFinished(start, end, entry.getKey());
|
|
|
+ row.put("actual", actual);
|
|
|
+ row.put("achieve", ratio(actual, plan, false));
|
|
|
+ rows.add(row);
|
|
|
+ if (rows.size() >= 8) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countFinished(String start, String end, String cate) {
|
|
|
+ MesProduct query = new MesProduct();
|
|
|
+ query.setState("1");
|
|
|
+ if (StringUtils.isNotEmpty(cate)) {
|
|
|
+ query.setCate(cate);
|
|
|
+ }
|
|
|
+ query.getSqlMap().getWhere()
|
|
|
+ .and("update_date", QueryType.GTE, start)
|
|
|
+ .and("update_date", QueryType.LT, end);
|
|
|
+ Long count = mesProductService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countCraftResult(String content, String start, String end) {
|
|
|
+ MesProductRecord query = new MesProductRecord();
|
|
|
+ query.setCraft("100000");
|
|
|
+ query.setContent(content);
|
|
|
+ query.getSqlMap().getWhere()
|
|
|
+ .and("update_date", QueryType.GTE, start)
|
|
|
+ .and("update_date", QueryType.LT, end);
|
|
|
+ Long count = mesProductRecordService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countRepair(String start, String end) {
|
|
|
+ MesRepair query = new MesRepair();
|
|
|
+ query.getSqlMap().getWhere()
|
|
|
+ .and("a.create_date", QueryType.GTE, start)
|
|
|
+ .and("a.create_date", QueryType.LT, end);
|
|
|
+ Long count = mesRepairService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countOpenAlarm() {
|
|
|
+ MesProcessAlarm query = new MesProcessAlarm();
|
|
|
+ query.setState("0");
|
|
|
+ Long count = mesProcessAlarmService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private long countStations() {
|
|
|
+ MesLineProcess query = new MesLineProcess();
|
|
|
+ query.setStatus("0");
|
|
|
+ query.getSqlMap().getWhere().and("pid", QueryType.IS_NULL, "");
|
|
|
+ Long count = mesLineProcessService.findCount(query);
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String[] shiftWindow(LocalDate day, MesShift shift) {
|
|
|
+ String startHm = hourMinute(shift.getStart());
|
|
|
+ String endHm = hourMinute(shift.getEnd());
|
|
|
+ if (StringUtils.isEmpty(startHm) || StringUtils.isEmpty(endHm)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ LocalDate endDay = shift.getNext() != null && shift.getNext() == 1 ? day.plusDays(1) : day;
|
|
|
+ String start = day.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " " + startHm + ":00";
|
|
|
+ String end = endDay.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " " + endHm + ":00";
|
|
|
+ return new String[]{start, end, startHm + "-" + endHm};
|
|
|
+ }
|
|
|
+
|
|
|
+ private String hourMinute(String time) {
|
|
|
+ if (StringUtils.isEmpty(time)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ String value = time.trim();
|
|
|
+ return value.length() >= 5 ? value.substring(0, 5) : value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double firstPassRate(long ok, long ng, long actual, long repair) {
|
|
|
+ if (ok + ng > 0) {
|
|
|
+ return ratio(ok, ok + ng, true);
|
|
|
+ }
|
|
|
+ if (actual <= 0) {
|
|
|
+ return 0D;
|
|
|
+ }
|
|
|
+ return ratio(Math.max(0L, actual - repair), actual, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private double availabilityRate(long openAlarm, long stations) {
|
|
|
+ if (stations <= 0 || openAlarm <= 0) {
|
|
|
+ return 100D;
|
|
|
+ }
|
|
|
+ double down = Math.min(100D, openAlarm * 100D / stations);
|
|
|
+ return round1(Math.max(0D, 100D - down));
|
|
|
+ }
|
|
|
+
|
|
|
+ private double ratio(long numerator, long denominator, boolean capAt100) {
|
|
|
+ if (denominator <= 0) {
|
|
|
+ return 0D;
|
|
|
+ }
|
|
|
+ double value = numerator * 100D / denominator;
|
|
|
+ if (value < 0) {
|
|
|
+ value = 0D;
|
|
|
+ }
|
|
|
+ if (capAt100 && value > 100D) {
|
|
|
+ value = 100D;
|
|
|
+ }
|
|
|
+ return round1(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private double round1(double value) {
|
|
|
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
|
|
|
+ return 0D;
|
|
|
+ }
|
|
|
+ return Math.round(value * 10D) / 10D;
|
|
|
+ }
|
|
|
}
|