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<WrkAnalysisMapper, WrkAnalysis> implements WrkAnalysisService {
|
|
private static final List<Long> 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<WrkMast>()
|
.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<Integer, WrkAnalysis> mapByWrkNos(Collection<Integer> wrkNos) {
|
Map<Integer, WrkAnalysis> result = new LinkedHashMap<>();
|
if (wrkNos == null || wrkNos.isEmpty()) {
|
return result;
|
}
|
List<WrkAnalysis> 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<String, Object> queryOptions() {
|
Map<String, Object> result = new LinkedHashMap<>();
|
List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> statusList = basWrkStatusService.list(new QueryWrapper<BasWrkStatus>().orderByAsc("wrk_sts"))
|
.stream()
|
.map(item -> option(String.valueOf(item.getWrkSts()), String.valueOf(item.getWrkSts()), item.getWrkDesc(), item.getWrkSts()))
|
.collect(Collectors.toList());
|
List<Map<String, Object>> stations = basStationService.list(new QueryWrapper<BasStation>().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<String, Object> queryList(Integer curr, Integer limit, Map<String, Object> param) {
|
int pageNo = curr == null || curr <= 0 ? 1 : curr;
|
int pageSize = limit == null || limit <= 0 ? 20 : limit;
|
QueryWrapper<WrkMastLog> wrapper = buildHistoryLogWrapper(param);
|
Page<WrkMastLog> page = wrkMastLogService.page(new Page<>(pageNo, pageSize), wrapper);
|
List<WrkMastLog> logList = page.getRecords();
|
List<Integer> wrkNos = logList.stream().map(WrkMastLog::getWrkNo).filter(Objects::nonNull).collect(Collectors.toList());
|
Map<Integer, WrkAnalysis> analysisMap = mapByWrkNos(wrkNos);
|
List<Map<String, Object>> records = new ArrayList<>();
|
for (WrkMastLog log : logList) {
|
records.add(toListItem(log, analysisMap.get(log.getWrkNo())));
|
}
|
Map<String, Object> 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<String, Object> analyze(JSONObject param) {
|
JSONObject request = param == null ? new JSONObject() : param;
|
String mode = upperTrim(request.getString("mode"));
|
if (Cools.isEmpty(mode)) {
|
mode = MODE_TIME;
|
}
|
QueryWrapper<WrkAnalysis> wrapper = buildAnalysisWrapper(request, mode);
|
List<WrkAnalysis> 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, mode, timeField, request);
|
}
|
|
private QueryWrapper<WrkMastLog> buildHistoryLogWrapper(Map<String, Object> param) {
|
QueryWrapper<WrkMastLog> 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<WrkAnalysis> buildAnalysisWrapper(JSONObject request, String mode) {
|
QueryWrapper<WrkAnalysis> 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<Integer> 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<WrkMastLog> wrapper, String deviceType) {
|
if (Cools.isEmpty(deviceType)) {
|
return;
|
}
|
QueryWrapper<WrkAnalysis> 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<Integer> 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<String, Object> buildAnalysisResult(List<WrkAnalysis> list, String mode, String timeField, JSONObject request) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("taskCount", list.size());
|
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, mode, timeField, request));
|
result.put("faultPie", buildFaultPie(list));
|
result.put("faultDuration", buildFaultDuration(list));
|
result.put("detail", buildDetailRows(list));
|
result.put("timeField", timeField);
|
return result;
|
}
|
|
private List<Map<String, Object>> buildDurationCompare(List<WrkAnalysis> list) {
|
return list.stream()
|
.limit(DURATION_COMPARE_LIMIT)
|
.map(this::toDetailItem)
|
.collect(Collectors.toList());
|
}
|
|
private List<Map<String, Object>> buildTrend(List<WrkAnalysis> list, String mode, String timeField, JSONObject request) {
|
List<Map<String, Object>> trend = new ArrayList<>();
|
if (list.isEmpty()) {
|
return trend;
|
}
|
long startMs = Long.MAX_VALUE;
|
long endMs = Long.MIN_VALUE;
|
if (MODE_TIME.equals(mode)) {
|
Long reqStart = parseLong(request.get("startTime"));
|
Long reqEnd = parseLong(request.get("endTime"));
|
if (reqStart != null && reqEnd != null) {
|
startMs = reqStart;
|
endMs = reqEnd;
|
}
|
}
|
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;
|
}
|
boolean hourly = endMs - startMs <= 72L * 60L * 60L * 1000L;
|
SimpleDateFormat keyFormat = new SimpleDateFormat(hourly ? "yyyy-MM-dd HH:00" : "yyyy-MM-dd");
|
keyFormat.setTimeZone(TimeZone.getDefault());
|
Map<String, BucketAccumulator> bucketMap = new LinkedHashMap<>();
|
List<WrkAnalysis> 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;
|
}
|
String key = keyFormat.format(bucketTime);
|
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<String, BucketAccumulator> entry : bucketMap.entrySet()) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("bucketLabel", 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 List<Map<String, Object>> buildFaultPie(List<WrkAnalysis> list) {
|
long fault = list.stream().filter(this::hasFault).count();
|
long normal = Math.max(0, list.size() - fault);
|
List<Map<String, Object>> result = new ArrayList<>();
|
result.add(slice("故障任务", fault));
|
result.add(slice("无故障任务", normal));
|
return result;
|
}
|
|
private List<Map<String, Object>> buildFaultDuration(List<WrkAnalysis> list) {
|
List<Map<String, Object>> result = new ArrayList<>();
|
result.add(slice("单堆垛机", list.stream().map(WrkAnalysis::getCrnFaultDurationMs).filter(Objects::nonNull).reduce(0L, Long::sum)));
|
result.add(slice("双工位堆垛机", list.stream().map(WrkAnalysis::getDualCrnFaultDurationMs).filter(Objects::nonNull).reduce(0L, Long::sum)));
|
result.add(slice("RGV", list.stream().map(WrkAnalysis::getRgvFaultDurationMs).filter(Objects::nonNull).reduce(0L, Long::sum)));
|
return result;
|
}
|
|
private List<Map<String, Object>> buildDetailRows(List<WrkAnalysis> list) {
|
return list.stream().map(this::toDetailItem).collect(Collectors.toList());
|
}
|
|
private Map<String, Object> toListItem(WrkMastLog log, WrkAnalysis analysis) {
|
Map<String, Object> 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<String, Object> toDetailItem(WrkAnalysis item) {
|
Map<String, Object> 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<String, Object> 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<BasCrnpErrLog> crnList = basCrnpErrLogService.list(new QueryWrapper<BasCrnpErrLog>().eq("wrk_no", wrkNo));
|
List<BasDualCrnpErrLog> dualList = basDualCrnpErrLogService.list(new QueryWrapper<BasDualCrnpErrLog>().eq("wrk_no", wrkNo));
|
List<BasRgvErrLog> rgvList = basRgvErrLogService.list(new QueryWrapper<BasRgvErrLog>().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<? extends Object> 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<String, Object> option(String key, String code, String label, Object value) {
|
Map<String, Object> item = new LinkedHashMap<>();
|
item.put("key", key);
|
item.put("code", code);
|
item.put("label", label);
|
item.put("value", value);
|
return item;
|
}
|
|
private Map<String, Object> slice(String name, Object value) {
|
Map<String, Object> 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<WrkAnalysis> list,
|
java.util.function.Predicate<WrkAnalysis> predicate,
|
java.util.function.Function<WrkAnalysis, Long> 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<WrkMastLog> 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<Integer> parseWrkNos(Object value) {
|
List<Integer> 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;
|
}
|
|
}
|