package com.zy.asrs.service.impl; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.core.common.Cools; import com.core.common.DateUtils; import com.zy.asrs.entity.*; import com.zy.asrs.mapper.WrkAnalysisMapper; import com.zy.asrs.service.*; import com.zy.core.enums.WrkIoType; import com.zy.core.enums.WrkStsType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @Service("wrkAnalysisService") public class WrkAnalysisServiceImpl extends ServiceImpl implements WrkAnalysisService { private static final List DEFAULT_FINAL_STATUSES = Arrays.asList( WrkStsType.COMPLETE_INBOUND.sts, WrkStsType.SETTLE_INBOUND.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts, WrkStsType.COMPLETE_LOC_MOVE.sts ); private static final String MODE_TASK = "TASK"; private static final String MODE_TIME = "TIME"; private static final String TIME_FIELD_FINISH = "finish_time"; private static final String TIME_FIELD_APPE = "appe_time"; private static final String DEVICE_TYPE_CRN = "CRN"; private static final String DEVICE_TYPE_DUAL_CRN = "DUAL_CRN"; private static final String DEVICE_TYPE_RGV = "RGV"; private static final int DURATION_COMPARE_LIMIT = 20; private final WrkMastLogService wrkMastLogService; private final BasCrnpErrLogService basCrnpErrLogService; private final BasDualCrnpErrLogService basDualCrnpErrLogService; private final BasRgvErrLogService basRgvErrLogService; private final BasStationService basStationService; private final BasWrkStatusService basWrkStatusService; private final WrkMastService wrkMastService; public WrkAnalysisServiceImpl(WrkMastLogService wrkMastLogService, BasCrnpErrLogService basCrnpErrLogService, BasDualCrnpErrLogService basDualCrnpErrLogService, BasRgvErrLogService basRgvErrLogService, BasStationService basStationService, BasWrkStatusService basWrkStatusService, WrkMastService wrkMastService) { this.wrkMastLogService = wrkMastLogService; this.basCrnpErrLogService = basCrnpErrLogService; this.basDualCrnpErrLogService = basDualCrnpErrLogService; this.basRgvErrLogService = basRgvErrLogService; this.basStationService = basStationService; this.basWrkStatusService = basWrkStatusService; this.wrkMastService = wrkMastService; } @Override @Transactional(rollbackFor = Exception.class) public void initForTask(WrkMast wrkMast) { if (wrkMast == null || wrkMast.getWrkNo() == null) { return; } WrkAnalysis entity = this.getById(wrkMast.getWrkNo()); Date now = new Date(); if (entity == null) { entity = new WrkAnalysis(); entity.setWrkNo(wrkMast.getWrkNo()); entity.setCreateTime(now); } syncBaseFields(entity, wrkMast); entity.setHasFault(defaultInt(entity.getHasFault())); entity.setFaultCount(defaultInt(entity.getFaultCount())); entity.setFaultDurationMs(defaultLong(entity.getFaultDurationMs())); entity.setCrnFaultCount(defaultInt(entity.getCrnFaultCount())); entity.setCrnFaultDurationMs(defaultLong(entity.getCrnFaultDurationMs())); entity.setDualCrnFaultCount(defaultInt(entity.getDualCrnFaultCount())); entity.setDualCrnFaultDurationMs(defaultLong(entity.getDualCrnFaultDurationMs())); entity.setRgvFaultCount(defaultInt(entity.getRgvFaultCount())); entity.setRgvFaultDurationMs(defaultLong(entity.getRgvFaultDurationMs())); entity.setMetricCompleteness(METRIC_PARTIAL); entity.setUpdateTime(now); this.saveOrUpdate(entity); } @Override @Transactional(rollbackFor = Exception.class) public void markInboundStationStart(WrkMast wrkMast, Date operateTime) { WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(operateTime); entity.setStationStartTime(time); entity.setSourceStaNo(wrkMast.getSourceStaNo()); entity.setStaNo(wrkMast.getStaNo()); entity.setFinalWrkSts(WrkStsType.INBOUND_STATION_RUN.sts); entity.setUpdateTime(time); this.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void markOutboundStationStart(WrkMast wrkMast, Date operateTime) { WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(operateTime); entity.setStationStartTime(time); entity.setFinalWrkSts(WrkStsType.STATION_RUN.sts); entity.setUpdateTime(time); this.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public boolean completeInboundStationRun(WrkMast wrkMast, Date operateTime) { if (wrkMast == null || wrkMast.getWrkNo() == null || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)) { return false; } Date now = safeDate(operateTime); boolean updated = wrkMast.getWrkNo() != null && wrkMastService.update(null, new UpdateWrapper() .set("wrk_sts", WrkStsType.INBOUND_STATION_RUN_COMPLETE.sts) .set("io_time", now) .set("modi_time", now) .eq("wrk_no", wrkMast.getWrkNo()) .eq("wrk_sts", WrkStsType.INBOUND_STATION_RUN.sts)); if (!updated) { return false; } WrkAnalysis entity = ensureRecord(wrkMast); if (entity != null) { entity.setFinalWrkSts(WrkStsType.INBOUND_STATION_RUN_COMPLETE.sts); entity.setStationEndTime(now); if (entity.getStationStartTime() != null) { entity.setStationDurationMs(durationMs(entity.getStationStartTime(), now)); } entity.setUpdateTime(now); this.updateById(entity); } wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN_COMPLETE.sts); wrkMast.setIoTime(now); wrkMast.setModiTime(now); return true; } @Override @Transactional(rollbackFor = Exception.class) public void markOutboundStationComplete(WrkMast wrkMast, Date operateTime) { WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(operateTime); entity.setFinalWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts); entity.setStationEndTime(time); if (entity.getStationStartTime() != null) { entity.setStationDurationMs(durationMs(entity.getStationStartTime(), time)); } entity.setUpdateTime(time); this.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void markCraneStart(WrkMast wrkMast, Date operateTime) { WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(operateTime); entity.setCraneStartTime(time); entity.setCrnNo(wrkMast.getCrnNo()); entity.setDualCrnNo(wrkMast.getDualCrnNo()); entity.setRgvNo(wrkMast.getRgvNo()); entity.setFinalWrkSts(wrkMast.getWrkSts()); entity.setUpdateTime(time); if (Objects.equals(wrkMast.getIoType(), WrkIoType.LOC_MOVE.id) && entity.getStationDurationMs() == null) { entity.setStationDurationMs(0L); } this.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void markCraneComplete(WrkMast wrkMast, Date operateTime, Long finalWrkSts) { WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(operateTime); entity.setCraneEndTime(time); if (entity.getCraneStartTime() != null) { entity.setCraneDurationMs(durationMs(entity.getCraneStartTime(), time)); } entity.setFinalWrkSts(finalWrkSts); entity.setUpdateTime(time); this.updateById(entity); } @Override @Transactional(rollbackFor = Exception.class) public void finishTask(WrkMast wrkMast, Date finishTime) { if (wrkMast == null || wrkMast.getWrkNo() == null) { return; } WrkAnalysis entity = ensureRecord(wrkMast); if (entity == null) { return; } Date time = safeDate(finishTime); syncBaseFields(entity, wrkMast); entity.setFinishTime(time); if (entity.getAppeTime() != null) { entity.setTotalDurationMs(durationMs(entity.getAppeTime(), time)); } if (Objects.equals(wrkMast.getIoType(), WrkIoType.LOC_MOVE.id) && entity.getStationDurationMs() == null) { entity.setStationDurationMs(0L); } FaultSummary faultSummary = buildFaultSummary(wrkMast.getWrkNo(), time); entity.setHasFault(faultSummary.hasFault); entity.setFaultCount(faultSummary.totalCount); entity.setFaultDurationMs(faultSummary.totalDurationMs); entity.setCrnFaultCount(faultSummary.crnCount); entity.setCrnFaultDurationMs(faultSummary.crnDurationMs); entity.setDualCrnFaultCount(faultSummary.dualCount); entity.setDualCrnFaultDurationMs(faultSummary.dualDurationMs); entity.setRgvFaultCount(faultSummary.rgvCount); entity.setRgvFaultDurationMs(faultSummary.rgvDurationMs); entity.setMetricCompleteness(resolveMetricCompleteness(wrkMast, entity)); entity.setUpdateTime(time); this.saveOrUpdate(entity); } @Override public Map mapByWrkNos(Collection wrkNos) { Map result = new LinkedHashMap<>(); if (wrkNos == null || wrkNos.isEmpty()) { return result; } List list = this.listByIds(new LinkedHashSet<>(wrkNos)); for (WrkAnalysis item : list) { if (item != null && item.getWrkNo() != null) { result.put(item.getWrkNo(), item); } } return result; } @Override public Map queryOptions() { Map result = new LinkedHashMap<>(); List> ioTypes = new ArrayList<>(); ioTypes.add(option("1", "IN", "入库", WrkIoType.IN.id)); ioTypes.add(option("2", "OUT", "出库", WrkIoType.OUT.id)); ioTypes.add(option("3", "LOC_MOVE", "移库", WrkIoType.LOC_MOVE.id)); List> timeFields = new ArrayList<>(); timeFields.add(option(TIME_FIELD_FINISH, TIME_FIELD_FINISH, "完成时间", TIME_FIELD_FINISH)); timeFields.add(option(TIME_FIELD_APPE, TIME_FIELD_APPE, "创建时间", TIME_FIELD_APPE)); List> deviceTypes = new ArrayList<>(); deviceTypes.add(option(DEVICE_TYPE_CRN, DEVICE_TYPE_CRN, "单堆垛机", DEVICE_TYPE_CRN)); deviceTypes.add(option(DEVICE_TYPE_DUAL_CRN, DEVICE_TYPE_DUAL_CRN, "双工位堆垛机", DEVICE_TYPE_DUAL_CRN)); deviceTypes.add(option(DEVICE_TYPE_RGV, DEVICE_TYPE_RGV, "RGV", DEVICE_TYPE_RGV)); List> statusList = basWrkStatusService.list(new QueryWrapper().orderByAsc("wrk_sts")) .stream() .map(item -> option(String.valueOf(item.getWrkSts()), String.valueOf(item.getWrkSts()), item.getWrkDesc(), item.getWrkSts())) .collect(Collectors.toList()); List> stations = basStationService.list(new QueryWrapper().orderByAsc("station_id")) .stream() .map(item -> { String label = item.getStationAlias(); if (Cools.isEmpty(label)) { label = "站点" + item.getStationId(); } else { label = item.getStationId() + " - " + label; } return option(String.valueOf(item.getStationId()), String.valueOf(item.getStationId()), label, item.getStationId()); }) .collect(Collectors.toList()); result.put("ioTypes", ioTypes); result.put("timeFields", timeFields); result.put("deviceTypes", deviceTypes); result.put("statuses", statusList); result.put("stations", stations); return result; } @Override public Map queryList(Integer curr, Integer limit, Map param) { int pageNo = curr == null || curr <= 0 ? 1 : curr; int pageSize = limit == null || limit <= 0 ? 20 : limit; QueryWrapper wrapper = buildHistoryLogWrapper(param); Page page = wrkMastLogService.page(new Page<>(pageNo, pageSize), wrapper); List logList = page.getRecords(); List wrkNos = logList.stream().map(WrkMastLog::getWrkNo).filter(Objects::nonNull).collect(Collectors.toList()); Map analysisMap = mapByWrkNos(wrkNos); List> records = new ArrayList<>(); for (WrkMastLog log : logList) { records.add(toListItem(log, analysisMap.get(log.getWrkNo()))); } Map data = new LinkedHashMap<>(); data.put("records", records); data.put("total", page.getTotal()); data.put("size", page.getSize()); data.put("current", page.getCurrent()); return data; } @Override public Map analyze(JSONObject param) { JSONObject request = param == null ? new JSONObject() : param; String mode = upperTrim(request.getString("mode")); if (Cools.isEmpty(mode)) { mode = MODE_TIME; } QueryWrapper wrapper = buildAnalysisWrapper(request, mode); List list = this.list(wrapper); list.sort(Comparator.comparing(WrkAnalysis::getFinishTime, Comparator.nullsLast(Date::compareTo)) .thenComparing(WrkAnalysis::getWrkNo, Comparator.nullsLast(Integer::compareTo))); Collections.reverse(list); String timeField = TIME_FIELD_FINISH; if (TIME_FIELD_APPE.equalsIgnoreCase(request.getString("timeField"))) { timeField = TIME_FIELD_APPE; } return buildAnalysisResult(list, timeField); } private QueryWrapper buildHistoryLogWrapper(Map param) { QueryWrapper wrapper = new QueryWrapper<>(); String keyword = stringValue(param.get("keyword")); if (!Cools.isEmpty(keyword)) { wrapper.and(w -> w.like("wrk_no", keyword) .or().like("wms_wrk_no", keyword) .or().like("loc_no", keyword) .or().like("source_loc_no", keyword) .or().like("barcode", keyword)); } Integer ioType = parseInteger(param.get("ioType")); if (ioType != null) { wrapper.eq("io_type", ioType); } Long finalWrkSts = parseLong(param.get("finalWrkSts")); if (finalWrkSts != null) { wrapper.eq("wrk_sts", finalWrkSts); } else { wrapper.in("wrk_sts", DEFAULT_FINAL_STATUSES); } Integer sourceStaNo = parseInteger(param.get("sourceStaNo")); if (sourceStaNo != null) { wrapper.eq("source_sta_no", sourceStaNo); } Integer staNo = parseInteger(param.get("staNo")); if (staNo != null) { wrapper.eq("sta_no", staNo); } applyDeviceTypeFilter(wrapper, upperTrim(stringValue(param.get("deviceType")))); applyRange(wrapper, "appe_time", stringValue(param.get("appeTimeRange"))); applyRange(wrapper, "modi_time", stringValue(param.get("finishTimeRange"))); wrapper.orderByDesc("modi_time", "wrk_no"); return wrapper; } private QueryWrapper buildAnalysisWrapper(JSONObject request, String mode) { QueryWrapper wrapper = new QueryWrapper<>(); Integer ioType = parseInteger(request.get("ioType")); if (ioType != null) { wrapper.eq("io_type", ioType); } Long finalWrkSts = parseLong(request.get("finalWrkSts")); if (finalWrkSts != null) { wrapper.eq("final_wrk_sts", finalWrkSts); } else { wrapper.in("final_wrk_sts", DEFAULT_FINAL_STATUSES); } Integer sourceStaNo = parseInteger(request.get("sourceStaNo")); if (sourceStaNo != null) { wrapper.eq("source_sta_no", sourceStaNo); } Integer staNo = parseInteger(request.get("staNo")); if (staNo != null) { wrapper.eq("sta_no", staNo); } String deviceType = upperTrim(request.getString("deviceType")); if (DEVICE_TYPE_CRN.equals(deviceType)) { wrapper.isNotNull("crn_no"); } else if (DEVICE_TYPE_DUAL_CRN.equals(deviceType)) { wrapper.isNotNull("dual_crn_no"); } else if (DEVICE_TYPE_RGV.equals(deviceType)) { wrapper.isNotNull("rgv_no"); } if (MODE_TASK.equals(mode)) { List wrkNos = parseWrkNos(request.get("wrkNos")); if (wrkNos.isEmpty()) { wrapper.eq("wrk_no", -1); return wrapper; } wrapper.in("wrk_no", wrkNos); } else { String timeField = TIME_FIELD_APPE.equalsIgnoreCase(request.getString("timeField")) ? TIME_FIELD_APPE : TIME_FIELD_FINISH; Long startTime = parseLong(request.get("startTime")); Long endTime = parseLong(request.get("endTime")); if (startTime != null && endTime != null) { wrapper.ge(timeField, new Date(startTime)); wrapper.le(timeField, new Date(endTime)); } else { wrapper.eq("wrk_no", -1); return wrapper; } } return wrapper; } private void applyDeviceTypeFilter(QueryWrapper wrapper, String deviceType) { if (Cools.isEmpty(deviceType)) { return; } QueryWrapper analysisWrapper = new QueryWrapper<>(); analysisWrapper.select("wrk_no"); if (DEVICE_TYPE_CRN.equals(deviceType)) { analysisWrapper.isNotNull("crn_no"); } else if (DEVICE_TYPE_DUAL_CRN.equals(deviceType)) { analysisWrapper.isNotNull("dual_crn_no"); } else if (DEVICE_TYPE_RGV.equals(deviceType)) { analysisWrapper.isNotNull("rgv_no"); } else { return; } List wrkNos = this.list(analysisWrapper).stream() .map(WrkAnalysis::getWrkNo) .filter(Objects::nonNull) .collect(Collectors.toList()); if (wrkNos.isEmpty()) { wrapper.eq("wrk_no", -1); return; } wrapper.in("wrk_no", wrkNos); } private Map buildAnalysisResult(List list, String timeField) { Map result = new LinkedHashMap<>(); Map summary = new LinkedHashMap<>(); Date taskStartTime = list.stream() .map(WrkAnalysis::getAppeTime) .filter(Objects::nonNull) .min(Date::compareTo) .orElse(null); Date taskEndTime = list.stream() .map(WrkAnalysis::getFinishTime) .filter(Objects::nonNull) .max(Date::compareTo) .orElse(null); summary.put("taskCount", list.size()); summary.put("taskStartTime", taskStartTime); summary.put("taskStartTime$", formatDate(taskStartTime)); summary.put("taskEndTime", taskEndTime); summary.put("taskEndTime$", formatDate(taskEndTime)); summary.put("taskDurationMs", taskStartTime == null || taskEndTime == null ? null : durationMs(taskStartTime, taskEndTime)); summary.put("avgTotalDurationMs", average(list, item -> item.getTotalDurationMs() != null, WrkAnalysis::getTotalDurationMs)); summary.put("avgStationDurationMs", average(list, item -> !METRIC_PARTIAL.equals(item.getMetricCompleteness()) && item.getStationDurationMs() != null, WrkAnalysis::getStationDurationMs)); summary.put("avgCraneDurationMs", average(list, item -> !METRIC_PARTIAL.equals(item.getMetricCompleteness()) && item.getCraneDurationMs() != null, WrkAnalysis::getCraneDurationMs)); summary.put("faultTaskCount", list.stream().filter(this::hasFault).count()); summary.put("faultDurationMs", list.stream().map(WrkAnalysis::getFaultDurationMs).filter(Objects::nonNull).reduce(0L, Long::sum)); summary.put("partialTaskCount", list.stream().filter(item -> METRIC_PARTIAL.equals(item.getMetricCompleteness())).count()); result.put("summary", summary); result.put("durationCompare", buildDurationCompare(list)); result.put("trend", buildTrend(list, timeField)); result.put("faultPie", buildFaultPie(list)); result.put("faultDuration", buildFaultDuration(list)); result.put("detail", buildDetailRows(list)); result.put("timeField", timeField); return result; } private List> buildDurationCompare(List list) { return list.stream() .limit(DURATION_COMPARE_LIMIT) .map(this::toDetailItem) .collect(Collectors.toList()); } private List> buildTrend(List list, String timeField) { List> trend = new ArrayList<>(); if (list.isEmpty()) { return trend; } long startMs = Long.MAX_VALUE; long endMs = Long.MIN_VALUE; if (startMs == Long.MAX_VALUE || endMs == Long.MIN_VALUE) { for (WrkAnalysis item : list) { Date date = resolveBucketTime(item, timeField); if (date == null) { continue; } long time = date.getTime(); startMs = Math.min(startMs, time); endMs = Math.max(endMs, time); } } if (startMs == Long.MAX_VALUE || endMs == Long.MIN_VALUE) { return trend; } TrendBucketType bucketType = resolveTrendBucketType(startMs, endMs); SimpleDateFormat keyFormat = new SimpleDateFormat(resolveTrendBucketPattern(bucketType)); keyFormat.setTimeZone(TimeZone.getDefault()); Map bucketMap = new LinkedHashMap<>(); List sorted = new ArrayList<>(list); sorted.sort(Comparator.comparing(item -> resolveBucketTime(item, timeField), Comparator.nullsLast(Date::compareTo))); for (WrkAnalysis item : sorted) { Date bucketTime = resolveBucketTime(item, timeField); if (bucketTime == null) { continue; } Date bucketStart = truncateBucketTime(bucketTime, bucketType); long key = bucketStart.getTime(); BucketAccumulator accumulator = bucketMap.computeIfAbsent(key, k -> new BucketAccumulator()); accumulator.taskCount++; if (item.getTotalDurationMs() != null) { accumulator.totalDurationMs += item.getTotalDurationMs(); accumulator.totalDurationCount++; } if (!METRIC_PARTIAL.equals(item.getMetricCompleteness()) && item.getStationDurationMs() != null) { accumulator.stationDurationMs += item.getStationDurationMs(); accumulator.stationDurationCount++; } if (!METRIC_PARTIAL.equals(item.getMetricCompleteness()) && item.getCraneDurationMs() != null) { accumulator.craneDurationMs += item.getCraneDurationMs(); accumulator.craneDurationCount++; } } for (Map.Entry entry : bucketMap.entrySet()) { Map item = new LinkedHashMap<>(); item.put("bucketLabel", keyFormat.format(new Date(entry.getKey()))); item.put("taskCount", entry.getValue().taskCount); item.put("avgTotalDurationMs", entry.getValue().totalDurationCount == 0 ? null : entry.getValue().totalDurationMs / entry.getValue().totalDurationCount); item.put("avgStationDurationMs", entry.getValue().stationDurationCount == 0 ? null : entry.getValue().stationDurationMs / entry.getValue().stationDurationCount); item.put("avgCraneDurationMs", entry.getValue().craneDurationCount == 0 ? null : entry.getValue().craneDurationMs / entry.getValue().craneDurationCount); trend.add(item); } return trend; } private TrendBucketType resolveTrendBucketType(long startMs, long endMs) { long spanMs = Math.max(0L, endMs - startMs); if (spanMs <= 6L * 60L * 60L * 1000L) { return TrendBucketType.MINUTE; } if (spanMs <= 72L * 60L * 60L * 1000L) { return TrendBucketType.HOUR; } return TrendBucketType.DAY; } private String resolveTrendBucketPattern(TrendBucketType bucketType) { if (bucketType == TrendBucketType.MINUTE) { return "yyyy-MM-dd HH:mm"; } if (bucketType == TrendBucketType.HOUR) { return "yyyy-MM-dd HH:00"; } return "yyyy-MM-dd"; } private Date truncateBucketTime(Date time, TrendBucketType bucketType) { Calendar calendar = Calendar.getInstance(); calendar.setTime(time); if (bucketType == TrendBucketType.DAY) { calendar.set(Calendar.HOUR_OF_DAY, 0); } if (bucketType == TrendBucketType.DAY || bucketType == TrendBucketType.HOUR) { calendar.set(Calendar.MINUTE, 0); } calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } private List> buildFaultPie(List list) { long fault = list.stream().filter(this::hasFault).count(); long normal = Math.max(0, list.size() - fault); List> result = new ArrayList<>(); result.add(slice("故障任务", fault)); result.add(slice("无故障任务", normal)); return result; } private List> buildFaultDuration(List list) { Map durationMap = new LinkedHashMap<>(); for (WrkAnalysis item : list) { addDeviceFaultDuration(durationMap, "单堆垛机", item.getCrnNo(), item.getCrnFaultDurationMs()); addDeviceFaultDuration(durationMap, "双工位堆垛机", item.getDualCrnNo(), item.getDualCrnFaultDurationMs()); addDeviceFaultDuration(durationMap, "RGV", item.getRgvNo(), item.getRgvFaultDurationMs()); } List> result = new ArrayList<>(); durationMap.forEach((name, durationMs) -> result.add(slice(name, durationMs))); return result; } private void addDeviceFaultDuration(Map durationMap, String deviceLabel, Integer deviceNo, Long durationMs) { long value = defaultLong(durationMs); if (value <= 0L) { return; } String key = deviceNo == null ? deviceLabel : deviceLabel + deviceNo; durationMap.merge(key, value, Long::sum); } private List> buildDetailRows(List list) { return list.stream().map(this::toDetailItem).collect(Collectors.toList()); } private Map toListItem(WrkMastLog log, WrkAnalysis analysis) { Map item = new LinkedHashMap<>(); item.put("wrkNo", log.getWrkNo()); item.put("wmsWrkNo", log.getWmsWrkNo()); item.put("wrkSts", log.getWrkSts()); item.put("wrkSts$", log.getWrkSts$()); item.put("ioType", log.getIoType()); item.put("ioType$", log.getIoType$()); item.put("sourceStaNo", log.getSourceStaNo()); item.put("staNo", log.getStaNo()); item.put("sourceLocNo", log.getSourceLocNo()); item.put("locNo", log.getLocNo()); item.put("barcode", log.getBarcode()); item.put("appeTime", log.getAppeTime()); item.put("appeTime$", formatDate(log.getAppeTime())); item.put("finishTime", analysis == null ? null : analysis.getFinishTime()); item.put("finishTime$", analysis == null ? "" : formatDate(analysis.getFinishTime())); fillMetrics(item, analysis); return item; } private Map toDetailItem(WrkAnalysis item) { Map result = new LinkedHashMap<>(); result.put("wrkNo", item.getWrkNo()); result.put("wmsWrkNo", item.getWmsWrkNo()); result.put("ioType", item.getIoType()); result.put("ioType$", resolveIoTypeDesc(item.getIoType())); result.put("finalWrkSts", item.getFinalWrkSts()); result.put("finalWrkSts$", resolveWrkStsDesc(item.getFinalWrkSts())); result.put("sourceStaNo", item.getSourceStaNo()); result.put("staNo", item.getStaNo()); result.put("sourceLocNo", item.getSourceLocNo()); result.put("locNo", item.getLocNo()); result.put("appeTime", item.getAppeTime()); result.put("appeTime$", formatDate(item.getAppeTime())); result.put("finishTime", item.getFinishTime()); result.put("finishTime$", formatDate(item.getFinishTime())); fillMetrics(result, item); return result; } private void fillMetrics(Map target, WrkAnalysis analysis) { target.put("totalDurationMs", analysis == null ? null : analysis.getTotalDurationMs()); target.put("stationDurationMs", analysis == null ? null : analysis.getStationDurationMs()); target.put("craneDurationMs", analysis == null ? null : analysis.getCraneDurationMs()); target.put("faultCount", analysis == null ? 0 : defaultInt(analysis.getFaultCount())); target.put("faultDurationMs", analysis == null ? 0L : defaultLong(analysis.getFaultDurationMs())); target.put("hasFault", analysis == null ? 0 : defaultInt(analysis.getHasFault())); target.put("crnFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getCrnFaultDurationMs())); target.put("dualCrnFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getDualCrnFaultDurationMs())); target.put("rgvFaultDurationMs", analysis == null ? 0L : defaultLong(analysis.getRgvFaultDurationMs())); target.put("metricCompleteness", analysis == null ? METRIC_PARTIAL : analysis.getMetricCompleteness()); } private FaultSummary buildFaultSummary(Integer wrkNo, Date finishTime) { FaultSummary summary = new FaultSummary(); if (wrkNo == null) { return summary; } List crnList = basCrnpErrLogService.list(new QueryWrapper().eq("wrk_no", wrkNo)); List dualList = basDualCrnpErrLogService.list(new QueryWrapper().eq("wrk_no", wrkNo)); List rgvList = basRgvErrLogService.list(new QueryWrapper().eq("task_no", wrkNo)); summary.crnCount = crnList.size(); summary.crnDurationMs = durationMs(crnList, finishTime); summary.dualCount = dualList.size(); summary.dualDurationMs = durationMs(dualList, finishTime); summary.rgvCount = rgvList.size(); summary.rgvDurationMs = durationMs(rgvList, finishTime); summary.totalCount = summary.crnCount + summary.dualCount + summary.rgvCount; summary.totalDurationMs = summary.crnDurationMs + summary.dualDurationMs + summary.rgvDurationMs; summary.hasFault = summary.totalCount > 0 ? 1 : 0; return summary; } private Long durationMs(List list, Date finishTime) { long total = 0L; for (Object item : list) { Date startTime = null; Date endTime = null; if (item instanceof BasCrnpErrLog) { startTime = ((BasCrnpErrLog) item).getStartTime(); endTime = ((BasCrnpErrLog) item).getEndTime(); } else if (item instanceof BasDualCrnpErrLog) { startTime = ((BasDualCrnpErrLog) item).getStartTime(); endTime = ((BasDualCrnpErrLog) item).getEndTime(); } else if (item instanceof BasRgvErrLog) { startTime = ((BasRgvErrLog) item).getStartTime(); endTime = ((BasRgvErrLog) item).getEndTime(); } if (startTime == null) { continue; } total += durationMs(startTime, endTime == null ? finishTime : endTime); } return total; } private String resolveMetricCompleteness(WrkMast wrkMast, WrkAnalysis entity) { if (wrkMast == null) { return METRIC_PARTIAL; } if (Objects.equals(wrkMast.getIoType(), WrkIoType.LOC_MOVE.id)) { return entity.getCraneStartTime() != null && entity.getCraneEndTime() != null ? METRIC_COMPLETE : METRIC_PARTIAL; } return entity.getStationStartTime() != null && entity.getStationEndTime() != null && entity.getCraneStartTime() != null && entity.getCraneEndTime() != null ? METRIC_COMPLETE : METRIC_PARTIAL; } private WrkAnalysis ensureRecord(WrkMast wrkMast) { if (wrkMast == null || wrkMast.getWrkNo() == null) { return null; } WrkAnalysis entity = this.getById(wrkMast.getWrkNo()); if (entity != null) { syncBaseFields(entity, wrkMast); return entity; } initForTask(wrkMast); return this.getById(wrkMast.getWrkNo()); } private void syncBaseFields(WrkAnalysis entity, WrkMast wrkMast) { entity.setWrkNo(wrkMast.getWrkNo()); entity.setWmsWrkNo(wrkMast.getWmsWrkNo()); entity.setIoType(wrkMast.getIoType()); entity.setFinalWrkSts(wrkMast.getWrkSts()); entity.setSourceStaNo(wrkMast.getSourceStaNo()); entity.setStaNo(wrkMast.getStaNo()); entity.setSourceLocNo(wrkMast.getSourceLocNo()); entity.setLocNo(wrkMast.getLocNo()); entity.setCrnNo(wrkMast.getCrnNo()); entity.setDualCrnNo(wrkMast.getDualCrnNo()); entity.setRgvNo(wrkMast.getRgvNo()); entity.setAppeTime(wrkMast.getAppeTime()); } private Date resolveBucketTime(WrkAnalysis item, String timeField) { if (TIME_FIELD_APPE.equals(timeField)) { return item.getAppeTime(); } return item.getFinishTime(); } private Map option(String key, String code, String label, Object value) { Map item = new LinkedHashMap<>(); item.put("key", key); item.put("code", code); item.put("label", label); item.put("value", value); return item; } private Map slice(String name, Object value) { Map item = new LinkedHashMap<>(); item.put("name", name); item.put("value", value); return item; } private String formatDate(Date date) { if (date == null) { return ""; } return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); } private String resolveWrkStsDesc(Long wrkSts) { if (wrkSts == null) { return ""; } BasWrkStatus basWrkStatus = basWrkStatusService.getById(wrkSts); if (basWrkStatus != null && !Cools.isEmpty(basWrkStatus.getWrkDesc())) { return basWrkStatus.getWrkDesc(); } try { return WrkStsType.query(wrkSts).desc; } catch (Exception ignore) { return String.valueOf(wrkSts); } } private String resolveIoTypeDesc(Integer ioType) { if (ioType == null) { return ""; } if (Objects.equals(ioType, WrkIoType.IN.id)) { return "入库"; } if (Objects.equals(ioType, WrkIoType.OUT.id)) { return "出库"; } if (Objects.equals(ioType, WrkIoType.LOC_MOVE.id)) { return "移库"; } return String.valueOf(ioType); } private boolean hasFault(WrkAnalysis item) { return item != null && (defaultInt(item.getHasFault()) > 0 || defaultInt(item.getFaultCount()) > 0); } private Long average(List list, java.util.function.Predicate predicate, java.util.function.Function valueFn) { long total = 0L; long count = 0L; for (WrkAnalysis item : list) { if (!predicate.test(item)) { continue; } Long value = valueFn.apply(item); if (value == null) { continue; } total += value; count++; } return count == 0 ? null : total / count; } private void applyRange(QueryWrapper wrapper, String column, String rawValue) { if (Cools.isEmpty(rawValue) || !rawValue.contains("~")) { return; } String[] parts = rawValue.split("~"); if (parts.length != 2) { return; } wrapper.ge(column, DateUtils.convert(parts[0].trim())); wrapper.le(column, DateUtils.convert(parts[1].trim())); } private List parseWrkNos(Object value) { List result = new ArrayList<>(); if (value == null) { return result; } if (value instanceof JSONArray) { JSONArray array = (JSONArray) value; for (int i = 0; i < array.size(); i++) { Integer item = parseInteger(array.get(i)); if (item != null) { result.add(item); } } return result; } if (value instanceof Collection) { for (Object item : (Collection) value) { Integer parsed = parseInteger(item); if (parsed != null) { result.add(parsed); } } return result; } String text = String.valueOf(value).trim(); if (text.startsWith("[") && text.endsWith("]")) { return parseWrkNos(JSONArray.parseArray(text)); } if (!Cools.isEmpty(text)) { for (String part : text.split(",")) { Integer parsed = parseInteger(part); if (parsed != null) { result.add(parsed); } } } return result; } private Integer parseInteger(Object value) { if (value == null || Cools.isEmpty(value)) { return null; } try { return Integer.valueOf(String.valueOf(value).trim()); } catch (Exception ignore) { return null; } } private Long parseLong(Object value) { if (value == null || Cools.isEmpty(value)) { return null; } try { return Long.valueOf(String.valueOf(value).trim()); } catch (Exception ignore) { return null; } } private String stringValue(Object value) { return value == null ? null : String.valueOf(value).trim(); } private String upperTrim(String value) { return value == null ? null : value.trim().toUpperCase(Locale.ROOT); } private Date safeDate(Date date) { return date == null ? new Date() : date; } private long durationMs(Date startTime, Date endTime) { if (startTime == null || endTime == null) { return 0L; } return Math.max(0L, endTime.getTime() - startTime.getTime()); } private Integer defaultInt(Integer value) { return value == null ? 0 : value; } private Long defaultLong(Long value) { return value == null ? 0L : value; } private static class FaultSummary { private int hasFault; private int totalCount; private long totalDurationMs; private int crnCount; private long crnDurationMs; private int dualCount; private long dualDurationMs; private int rgvCount; private long rgvDurationMs; } private static class BucketAccumulator { private long taskCount; private long totalDurationMs; private long totalDurationCount; private long stationDurationMs; private long stationDurationCount; private long craneDurationMs; private long craneDurationCount; } private enum TrendBucketType { MINUTE, HOUR, DAY } }