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 实现类
|
* <p>
|
* 整合生产、质检、库存、出库、追溯五大维度核心指标。
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
public class MesDashboardServiceImpl implements MesDashboardService {
|
|
/** 指标编码 -> 指标名称 */
|
private static final Map<String, String> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> 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<MesDashboardProductionTrendRespVO> getProductionTrend(Integer days) {
|
LocalDate endDate = LocalDate.now().plusDays(1);
|
LocalDate startDate = endDate.minusDays(days);
|
LocalDateTime beginTime = startDate.atStartOfDay();
|
LocalDateTime endTime = endDate.atStartOfDay();
|
|
Map<String, Map<String, Object>> batchMap = toDateMap(dashboardMapper.selectDailyBatchCount(beginTime, endTime));
|
Map<String, Map<String, Object>> bagCodeMap = toDateMap(dashboardMapper.selectDailyBagCodeCount(beginTime, endTime));
|
Map<String, Map<String, Object>> palletMap = toDateMap(dashboardMapper.selectDailyPalletCount(beginTime, endTime));
|
Map<String, Map<String, Object>> inboundMap = toDateMap(dashboardMapper.selectDailyInboundBagQuantity(beginTime, endTime));
|
|
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
List<MesDashboardProductionTrendRespVO> 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<MesDashboardNameCountRespVO> resultDistribution = new ArrayList<>();
|
for (Map<String, Object> 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<MesDashboardIndicatorAvgRespVO> indicatorList = new ArrayList<>();
|
for (Map<String, Object> 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<MesDashboardNameCountRespVO> stateDistribution = new ArrayList<>();
|
for (Map<String, Object> 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<String, Map<String, Object>> trendMap = toDateMap(dashboardMapper.selectOutboundDailyQuantity(beginTime, endTime));
|
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
List<MesDashboardDailyCountRespVO> 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<MesTraceConsumerScanTrendRespVO> trend = traceScanStatisticsService.getTrend(beginTime, endTime);
|
Map<String, Long> 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<MesDashboardDailyCountRespVO> 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<MesDashboardNameCountRespVO> toDistribution(List<MesTraceConsumerScanDistributionRespVO> distribution) {
|
List<MesDashboardNameCountRespVO> 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<MesDashboardNameCountRespVO> toNameCountList(List<Map<String, Object>> stats) {
|
List<MesDashboardNameCountRespVO> result = new ArrayList<>();
|
if (stats == null) {
|
return result;
|
}
|
for (Map<String, Object> stat : stats) {
|
result.add(new MesDashboardNameCountRespVO((String) stat.get("name"), getDecimal(stat.get("quantity")).longValue()));
|
}
|
return result;
|
}
|
|
private Map<String, Map<String, Object>> toDateMap(List<Map<String, Object>> stats) {
|
Map<String, Map<String, Object>> map = new LinkedHashMap<>();
|
if (stats == null) {
|
return map;
|
}
|
for (Map<String, Object> stat : stats) {
|
map.put((String) stat.get("date"), stat);
|
}
|
return map;
|
}
|
|
private Long getCountByStatus(List<Map<String, Object>> stats, Integer status) {
|
if (stats == null) {
|
return 0L;
|
}
|
for (Map<String, Object> stat : stats) {
|
if (((Number) stat.get("status")).intValue() == status) {
|
return getLong(stat.get("count"));
|
}
|
}
|
return 0L;
|
}
|
|
private Long getCountByResult(List<Map<String, Object>> stats, Integer result) {
|
if (stats == null) {
|
return 0L;
|
}
|
for (Map<String, Object> stat : stats) {
|
if (((Number) stat.get("result")).intValue() == result) {
|
return getLong(stat.get("count"));
|
}
|
}
|
return 0L;
|
}
|
|
private BigDecimal getStockQuantityByState(List<Map<String, Object>> stats, Integer stockState) {
|
if (stats == null) {
|
return BigDecimal.ZERO;
|
}
|
for (Map<String, Object> stat : stats) {
|
if (((Number) stat.get("stockState")).intValue() == stockState) {
|
return getDecimal(stat.get("quantity"));
|
}
|
}
|
return BigDecimal.ZERO;
|
}
|
|
private Long getTotalCount(List<Map<String, Object>> stats) {
|
long total = 0L;
|
if (stats != null) {
|
for (Map<String, Object> stat : stats) {
|
total += getLong(stat.get("count"));
|
}
|
}
|
return total;
|
}
|
|
private Long getLongFromMap(Map<String, Object> map, String key) {
|
return map == null ? 0L : getLong(map.get(key));
|
}
|
|
private BigDecimal getDecimalFromMap(Map<String, Object> 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 -> "未知状态";
|
};
|
}
|
|
}
|