package cn.iocoder.yudao.module.mes.service.dashboard; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardDailyCountRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardIndicatorAvgRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardInventoryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardNameCountRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOutboundRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionTrendRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardQualityRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardSummaryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardTraceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO; import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO; import cn.iocoder.yudao.module.mes.dal.mysql.dashboard.MesDashboardMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; import cn.iocoder.yudao.module.mes.service.trace.consumer.MesTraceConsumerScanStatisticsService; import cn.iocoder.yudao.module.mes.service.trace.crossregion.MesTraceCrossRegionService; import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 管理驾驶舱统计 Service 实现类 *

* 整合生产、质检、库存、出库、追溯五大维度核心指标。 * * @author 超级管理员 */ @Service public class MesDashboardServiceImpl implements MesDashboardService { /** 指标编码 -> 指标名称 */ private static final Map INDICATOR_NAME_MAP = Map.of( "purity", "纯度", "cleanliness", "净度", "germination_rate", "发芽率", "moisture", "水分"); @Resource private MesDashboardMapper dashboardMapper; @Resource private MesTraceConsumerScanStatisticsService traceScanStatisticsService; @Resource private MesTraceCrossRegionService traceCrossRegionService; @Resource private MesWmStockWarningService stockWarningService; @Override public MesDashboardSummaryRespVO getSummary() { MesDashboardSummaryRespVO summary = new MesDashboardSummaryRespVO(); // 1. 生产维度 List> batchStats = dashboardMapper.selectBatchCountGroupByStatus(); summary.setBatchTotal(getTotalCount(batchStats)); summary.setBatchDraft(getCountByStatus(batchStats, MesProBatchStatusEnum.DRAFT.getStatus())); summary.setBatchProducing(getCountByStatus(batchStats, MesProBatchStatusEnum.PRODUCING.getStatus())); summary.setBatchFinished(getCountByStatus(batchStats, MesProBatchStatusEnum.FINISHED.getStatus())); summary.setBatchCanceled(getCountByStatus(batchStats, MesProBatchStatusEnum.CANCELED.getStatus())); List> bagCodeStats = dashboardMapper.selectBagCodeCountGroupByStatus(); summary.setBagCodeTotal(getTotalCount(bagCodeStats)); summary.setBagCodeGenerated(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.GENERATED.getStatus())); summary.setBagCodePrinted(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.PRINTED.getStatus())); summary.setBagCodeCanceled(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.CANCELED.getStatus())); summary.setPalletTotal(getLong(dashboardMapper.selectPalletTotal())); summary.setTodayInboundBagCount(getLong(dashboardMapper.selectTodayInboundBagCount(LocalDateTimeUtils.getToday()))); // 2. 质检维度 List> qualityStats = dashboardMapper.selectQualityCountGroupByResult(); long qualityPending = getCountByResult(qualityStats, 0); long qualityPass = getCountByResult(qualityStats, 1); long qualityFail = getCountByResult(qualityStats, 2); summary.setQualityTotal(qualityPending + qualityPass + qualityFail); summary.setQualityPending(qualityPending); summary.setQualityPass(qualityPass); summary.setQualityFail(qualityFail); summary.setQualityPassRate(calcPercent(qualityPass, qualityPass + qualityFail)); // 3. 库存维度 List> stockStateStats = dashboardMapper.selectStockGroupByState(); BigDecimal stockTemporary = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.TEMP.getState()); BigDecimal stockQualified = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.QUALIFIED.getState()); BigDecimal stockOutbound = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.OUT.getState()); summary.setStockTemporary(stockTemporary); summary.setStockQualified(stockQualified); summary.setStockOutbound(stockOutbound); summary.setStockTotal(stockTemporary.add(stockQualified).add(stockOutbound)); summary.setLowStockCount((long) stockWarningService.getCurrentWarnings().size()); // 4. 出库维度 Map outboundSummary = dashboardMapper.selectOutboundSummary(LocalDateTimeUtils.getToday()); summary.setOutboundOrderTotal(getLong(outboundSummary == null ? null : outboundSummary.get("orderTotal"))); summary.setOutboundQuantityTotal(getDecimal(outboundSummary == null ? null : outboundSummary.get("quantityTotal"))); summary.setTodayOutboundQuantity(getDecimal(outboundSummary == null ? null : outboundSummary.get("todayQuantity"))); // 5. 追溯维度 MesTraceConsumerScanSummaryRespVO scanAll = traceScanStatisticsService.getSummary(null, null); MesTraceConsumerScanSummaryRespVO scanToday = traceScanStatisticsService.getSummary(LocalDateTimeUtils.getToday(), null); MesTraceCrossRegionSummaryRespVO crossRegion = traceCrossRegionService.getWarningSummary(null, null); summary.setScanTotal(scanAll == null ? 0L : getLong(scanAll.getTotal())); summary.setScanSuccessCount(scanAll == null ? 0L : getLong(scanAll.getSuccess())); summary.setTodayScanCount(scanToday == null ? 0L : getLong(scanToday.getTotal())); summary.setCrossRegionWarningTotal(crossRegion == null ? 0L : getLong(crossRegion.getTotal())); summary.setCrossRegionPendingCount(crossRegion == null ? 0L : getLong(crossRegion.getPending())); return summary; } @Override public List getProductionTrend(Integer days) { LocalDate endDate = LocalDate.now().plusDays(1); LocalDate startDate = endDate.minusDays(days); LocalDateTime beginTime = startDate.atStartOfDay(); LocalDateTime endTime = endDate.atStartOfDay(); Map> batchMap = toDateMap(dashboardMapper.selectDailyBatchCount(beginTime, endTime)); Map> bagCodeMap = toDateMap(dashboardMapper.selectDailyBagCodeCount(beginTime, endTime)); Map> palletMap = toDateMap(dashboardMapper.selectDailyPalletCount(beginTime, endTime)); Map> inboundMap = toDateMap(dashboardMapper.selectDailyInboundBagQuantity(beginTime, endTime)); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); List result = new ArrayList<>(); for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) { String dateStr = d.format(fmt); result.add(new MesDashboardProductionTrendRespVO( dateStr, getLongFromMap(batchMap.get(dateStr), "count"), getLongFromMap(bagCodeMap.get(dateStr), "count"), getLongFromMap(palletMap.get(dateStr), "count"), getDecimalFromMap(inboundMap.get(dateStr), "quantity"))); } return result; } @Override public MesDashboardQualityRespVO getQualityStatistics() { MesDashboardQualityRespVO vo = new MesDashboardQualityRespVO(); // 1. 检验结果分布 List resultDistribution = new ArrayList<>(); for (Map stat : dashboardMapper.selectQualityCountGroupByResult()) { int result = ((Number) stat.get("result")).intValue(); resultDistribution.add(new MesDashboardNameCountRespVO(qualityResultName(result), getLong(stat.get("count")))); } vo.setResultDistribution(resultDistribution); // 2. 四项指标均值与合格率 List indicatorList = new ArrayList<>(); for (Map stat : dashboardMapper.selectIndicatorStatistics()) { MesDashboardIndicatorAvgRespVO item = new MesDashboardIndicatorAvgRespVO(); item.setIndicatorCode((String) stat.get("indicatorCode")); item.setIndicatorName(indicatorName(item.getIndicatorCode())); item.setCheckCount(getLong(stat.get("checkCount"))); item.setAverageValue(getDecimal(stat.get("averageValue"))); item.setPassRate(getDecimal(stat.get("passRate"))); indicatorList.add(item); } vo.setIndicatorList(indicatorList); return vo; } @Override public MesDashboardInventoryRespVO getInventoryStatistics() { MesDashboardInventoryRespVO vo = new MesDashboardInventoryRespVO(); vo.setWarehouseDistribution(toNameCountList(dashboardMapper.selectStockGroupByWarehouse())); vo.setItemDistribution(toNameCountList(dashboardMapper.selectStockGroupByItem())); // 库存状态分布 List stateDistribution = new ArrayList<>(); for (Map stat : dashboardMapper.selectStockGroupByState()) { int stockState = ((Number) stat.get("stockState")).intValue(); stateDistribution.add(new MesDashboardNameCountRespVO(stockStateName(stockState), getDecimal(stat.get("quantity")).longValue())); } vo.setStateDistribution(stateDistribution); return vo; } @Override public MesDashboardOutboundRespVO getOutboundStatistics(Integer days) { LocalDate endDate = LocalDate.now().plusDays(1); LocalDate startDate = endDate.minusDays(days); LocalDateTime beginTime = startDate.atStartOfDay(); LocalDateTime endTime = endDate.atStartOfDay(); MesDashboardOutboundRespVO vo = new MesDashboardOutboundRespVO(); // 出库按日趋势(日补零) Map> trendMap = toDateMap(dashboardMapper.selectOutboundDailyQuantity(beginTime, endTime)); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); List trend = new ArrayList<>(); for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) { String dateStr = d.format(fmt); trend.add(new MesDashboardDailyCountRespVO(dateStr, getDecimalFromMap(trendMap.get(dateStr), "quantity"))); } vo.setTrend(trend); // 经销商出库分布 vo.setDealerDistribution(toNameCountList(dashboardMapper.selectOutboundGroupByClient())); return vo; } @Override public MesDashboardTraceRespVO getTraceStatistics(Integer days) { LocalDate endDate = LocalDate.now().plusDays(1); LocalDate startDate = endDate.minusDays(days); LocalDateTime beginTime = startDate.atStartOfDay(); LocalDateTime endTime = endDate.atStartOfDay(); MesDashboardTraceRespVO vo = new MesDashboardTraceRespVO(); // 1. 扫码按日趋势 List trend = traceScanStatisticsService.getTrend(beginTime, endTime); Map trendMap = new LinkedHashMap<>(); if (trend != null) { for (MesTraceConsumerScanTrendRespVO item : trend) { trendMap.put(item.getDate(), getLong(item.getTotal())); } } DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); List scanTrend = new ArrayList<>(); for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) { String dateStr = d.format(fmt); scanTrend.add(new MesDashboardDailyCountRespVO(dateStr, new BigDecimal(trendMap.getOrDefault(dateStr, 0L)))); } vo.setScanTrend(scanTrend); // 2. 省份分布 vo.setProvinceDistribution(toDistribution(traceScanStatisticsService.getDistribution( buildDistributionReqVO("province", beginTime, endTime)))); // 3. 渠道分布 vo.setChannelDistribution(toDistribution(traceScanStatisticsService.getDistribution( buildDistributionReqVO("channel", beginTime, endTime)))); // 4. 窜货预警汇总 vo.setCrossRegionSummary(traceCrossRegionService.getWarningSummary(null, null)); return vo; } // ========== 工具方法 ========== private MesTraceConsumerScanDistributionReqVO buildDistributionReqVO(String groupBy, LocalDateTime beginTime, LocalDateTime endTime) { MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO(); reqVO.setGroupBy(groupBy); reqVO.setStartTime(beginTime); reqVO.setEndTime(endTime); return reqVO; } private List toDistribution(List distribution) { List result = new ArrayList<>(); if (distribution == null) { return result; } for (MesTraceConsumerScanDistributionRespVO item : distribution) { result.add(new MesDashboardNameCountRespVO(item.getName(), getLong(item.getCount()))); } return result; } private List toNameCountList(List> stats) { List result = new ArrayList<>(); if (stats == null) { return result; } for (Map stat : stats) { result.add(new MesDashboardNameCountRespVO((String) stat.get("name"), getDecimal(stat.get("quantity")).longValue())); } return result; } private Map> toDateMap(List> stats) { Map> map = new LinkedHashMap<>(); if (stats == null) { return map; } for (Map stat : stats) { map.put((String) stat.get("date"), stat); } return map; } private Long getCountByStatus(List> stats, Integer status) { if (stats == null) { return 0L; } for (Map stat : stats) { if (((Number) stat.get("status")).intValue() == status) { return getLong(stat.get("count")); } } return 0L; } private Long getCountByResult(List> stats, Integer result) { if (stats == null) { return 0L; } for (Map stat : stats) { if (((Number) stat.get("result")).intValue() == result) { return getLong(stat.get("count")); } } return 0L; } private BigDecimal getStockQuantityByState(List> stats, Integer stockState) { if (stats == null) { return BigDecimal.ZERO; } for (Map stat : stats) { if (((Number) stat.get("stockState")).intValue() == stockState) { return getDecimal(stat.get("quantity")); } } return BigDecimal.ZERO; } private Long getTotalCount(List> stats) { long total = 0L; if (stats != null) { for (Map stat : stats) { total += getLong(stat.get("count")); } } return total; } private Long getLongFromMap(Map map, String key) { return map == null ? 0L : getLong(map.get(key)); } private BigDecimal getDecimalFromMap(Map map, String key) { return map == null ? BigDecimal.ZERO : getDecimal(map.get(key)); } private Long getLong(Object obj) { if (obj == null) { return 0L; } if (obj instanceof Number) { return ((Number) obj).longValue(); } return Long.valueOf(obj.toString()); } private BigDecimal getDecimal(Object obj) { if (obj == null) { return BigDecimal.ZERO; } if (obj instanceof BigDecimal) { return (BigDecimal) obj; } if (obj instanceof Number) { return BigDecimal.valueOf(((Number) obj).doubleValue()); } return new BigDecimal(obj.toString()); } private BigDecimal calcPercent(long pass, long total) { if (total == 0) { return BigDecimal.ZERO; } return BigDecimal.valueOf(pass) .multiply(BigDecimal.valueOf(100)) .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP); } private String indicatorName(String code) { return code == null ? "未知指标" : INDICATOR_NAME_MAP.getOrDefault(code, code); } private String qualityResultName(int result) { return switch (result) { case 0 -> "处理中"; case 1 -> "合格"; case 2 -> "不合格"; default -> "未知"; }; } private String stockStateName(int stockState) { return switch (stockState) { case 10 -> "暂存库"; case 20 -> "合格库"; case 30 -> "已出库"; default -> "未知状态"; }; } }