package cn.iocoder.yudao.module.mes.controller.admin.dashboard;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcelFactory;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardDailyCountRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardGenericExcelVO;
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.MesDashboardOverviewExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionExcelVO;
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.service.dashboard.MesDashboardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 管理驾驶舱与统计分析
*
* 整合生产、质检、库存、出库、追溯五大维度核心指标,提供可视化图表数据与 Excel 报表导出。
*
* @author 超级管理员
*/
@Tag(name = "管理后台 - MES 管理驾驶舱")
@RestController
@RequestMapping("/mes/dashboard")
@Validated
public class MesDashboardController {
@Resource
private MesDashboardService dashboardService;
@GetMapping("/summary")
@Operation(summary = "获得管理驾驶舱五维度汇总统计")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult getSummary() {
return success(dashboardService.getSummary());
}
@GetMapping("/production-trend")
@Operation(summary = "获得生产趋势")
@Parameter(name = "days", description = "天数", example = "30")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult> getProductionTrend(
@RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
return success(dashboardService.getProductionTrend(days));
}
@GetMapping("/quality-statistics")
@Operation(summary = "获得质检维度统计")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult getQualityStatistics() {
return success(dashboardService.getQualityStatistics());
}
@GetMapping("/inventory-statistics")
@Operation(summary = "获得库存维度统计")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult getInventoryStatistics() {
return success(dashboardService.getInventoryStatistics());
}
@GetMapping("/outbound-statistics")
@Operation(summary = "获得出库维度统计")
@Parameter(name = "days", description = "天数", example = "30")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult getOutboundStatistics(
@RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
return success(dashboardService.getOutboundStatistics(days));
}
@GetMapping("/trace-statistics")
@Operation(summary = "获得追溯维度统计")
@Parameter(name = "days", description = "天数", example = "30")
@PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
public CommonResult getTraceStatistics(
@RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
return success(dashboardService.getTraceStatistics(days));
}
@GetMapping("/export-excel")
@Operation(summary = "导出管理驾驶舱 Excel 报表")
@ApiAccessLog(operateType = EXPORT)
@PreAuthorize("@ss.hasPermission('mes:dashboard:export')")
public void exportExcel(HttpServletResponse response) throws IOException {
// 1. 组装各维度报表数据
MesDashboardSummaryRespVO summary = dashboardService.getSummary();
List productionTrend = dashboardService.getProductionTrend(30);
MesDashboardQualityRespVO quality = dashboardService.getQualityStatistics();
MesDashboardInventoryRespVO inventory = dashboardService.getInventoryStatistics();
MesDashboardOutboundRespVO outbound = dashboardService.getOutboundStatistics(30);
MesDashboardTraceRespVO trace = dashboardService.getTraceStatistics(30);
// 2. 多 sheet 写入
ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream())
.autoCloseStream(false)
.build();
try {
excelWriter.write(buildOverviewRows(summary), FastExcelFactory.writerSheet("驾驶舱总览").build());
excelWriter.write(buildProductionRows(summary, productionTrend), FastExcelFactory.writerSheet("生产统计").build());
excelWriter.write(buildQualityRows(quality), FastExcelFactory.writerSheet("质检统计").build());
excelWriter.write(buildInventoryRows(inventory), FastExcelFactory.writerSheet("库存统计").build());
excelWriter.write(buildOutboundRows(outbound), FastExcelFactory.writerSheet("出库统计").build());
excelWriter.write(buildTraceRows(trace), FastExcelFactory.writerSheet("追溯统计").build());
} finally {
excelWriter.finish();
excelWriter.close();
}
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8("管理驾驶舱统计报表.xls"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
}
// ========== Excel 行组装 ==========
private List buildOverviewRows(MesDashboardSummaryRespVO summary) {
List rows = new ArrayList<>();
addOverview(rows, "批次总数", summary.getBatchTotal());
addOverview(rows, "生产中批次", summary.getBatchProducing());
addOverview(rows, "已完成批次", summary.getBatchFinished());
addOverview(rows, "已作废批次", summary.getBatchCanceled());
addOverview(rows, "袋码总数", summary.getBagCodeTotal());
addOverview(rows, "已导出印刷袋码", summary.getBagCodePrinted());
addOverview(rows, "已作废袋码", summary.getBagCodeCanceled());
addOverview(rows, "托盘码总数", summary.getPalletTotal());
addOverview(rows, "今日扫码入库袋数", summary.getTodayInboundBagCount());
addOverview(rows, "检验记录总数", summary.getQualityTotal());
addOverview(rows, "合格检验记录", summary.getQualityPass());
addOverview(rows, "不合格检验记录", summary.getQualityFail());
addOverview(rows, "检验合格率(%)", summary.getQualityPassRate());
addOverview(rows, "当前库存总量", summary.getStockTotal());
addOverview(rows, "暂存库存量", summary.getStockTemporary());
addOverview(rows, "合格库存量", summary.getStockQualified());
addOverview(rows, "低库存预警数", summary.getLowStockCount());
addOverview(rows, "出库单总数", summary.getOutboundOrderTotal());
addOverview(rows, "出库总量", summary.getOutboundQuantityTotal());
addOverview(rows, "今日出库量", summary.getTodayOutboundQuantity());
addOverview(rows, "扫码总数", summary.getScanTotal());
addOverview(rows, "今日扫码数", summary.getTodayScanCount());
addOverview(rows, "窜货预警总数", summary.getCrossRegionWarningTotal());
addOverview(rows, "待处理窜货预警", summary.getCrossRegionPendingCount());
return rows;
}
private List buildProductionRows(MesDashboardSummaryRespVO summary,
List trend) {
List rows = new ArrayList<>();
addProduction(rows, "批次", "草稿", summary.getBatchDraft());
addProduction(rows, "批次", "生产中", summary.getBatchProducing());
addProduction(rows, "批次", "已完成", summary.getBatchFinished());
addProduction(rows, "批次", "已作废", summary.getBatchCanceled());
addProduction(rows, "袋码", "已生成", summary.getBagCodeGenerated());
addProduction(rows, "袋码", "已导出印刷", summary.getBagCodePrinted());
addProduction(rows, "袋码", "已作废", summary.getBagCodeCanceled());
addProduction(rows, "托盘", "总数", summary.getPalletTotal());
addProduction(rows, "入库", "今日扫码入库袋数", summary.getTodayInboundBagCount());
for (MesDashboardProductionTrendRespVO item : trend) {
addProduction(rows, "趋势-" + item.getDate(), "新增批次", item.getBatchCount());
addProduction(rows, "趋势-" + item.getDate(), "新增袋码", item.getBagCodeCount());
addProduction(rows, "趋势-" + item.getDate(), "新增托盘", item.getPalletCount());
addProduction(rows, "趋势-" + item.getDate(), "入库袋数", item.getInboundBagQuantity() == null ? 0L
: item.getInboundBagQuantity().longValue());
}
return rows;
}
private List buildQualityRows(MesDashboardQualityRespVO quality) {
List rows = new ArrayList<>();
for (MesDashboardNameCountRespVO item : quality.getResultDistribution()) {
addGeneric(rows, "检验结果", item.getName(), item.getCount());
}
for (MesDashboardIndicatorAvgRespVO item : quality.getIndicatorList()) {
addGeneric(rows, "指标均值", item.getIndicatorName() + "(平均值)", item.getAverageValue());
addGeneric(rows, "指标均值", item.getIndicatorName() + "(合格率%)", item.getPassRate());
}
addEmptyGuard(rows);
return rows;
}
private List buildInventoryRows(MesDashboardInventoryRespVO inventory) {
List rows = new ArrayList<>();
for (MesDashboardNameCountRespVO item : inventory.getWarehouseDistribution()) {
addGeneric(rows, "按仓库", item.getName(), item.getCount());
}
for (MesDashboardNameCountRespVO item : inventory.getItemDistribution()) {
addGeneric(rows, "按品种", item.getName(), item.getCount());
}
for (MesDashboardNameCountRespVO item : inventory.getStateDistribution()) {
addGeneric(rows, "按状态", item.getName(), item.getCount());
}
addEmptyGuard(rows);
return rows;
}
private List buildOutboundRows(MesDashboardOutboundRespVO outbound) {
List rows = new ArrayList<>();
for (MesDashboardDailyCountRespVO item : outbound.getTrend()) {
addGeneric(rows, "出库趋势", item.getDate(), item.getCount());
}
for (MesDashboardNameCountRespVO item : outbound.getDealerDistribution()) {
addGeneric(rows, "按经销商", item.getName(), item.getCount());
}
addEmptyGuard(rows);
return rows;
}
private List buildTraceRows(MesDashboardTraceRespVO trace) {
List rows = new ArrayList<>();
for (MesDashboardDailyCountRespVO item : trace.getScanTrend()) {
addGeneric(rows, "扫码趋势", item.getDate(), item.getCount());
}
for (MesDashboardNameCountRespVO item : trace.getProvinceDistribution()) {
addGeneric(rows, "扫码省份", item.getName(), item.getCount());
}
for (MesDashboardNameCountRespVO item : trace.getChannelDistribution()) {
addGeneric(rows, "扫码渠道", item.getName(), item.getCount());
}
if (trace.getCrossRegionSummary() != null) {
addGeneric(rows, "窜货预警", "总数", trace.getCrossRegionSummary().getTotal());
addGeneric(rows, "窜货预警", "待处理", trace.getCrossRegionSummary().getPending());
addGeneric(rows, "窜货预警", "已处理", trace.getCrossRegionSummary().getHandled());
addGeneric(rows, "窜货预警", "误报", trace.getCrossRegionSummary().getFalseAlarm());
addGeneric(rows, "窜货预警", "跨省", trace.getCrossRegionSummary().getCrossProvince());
addGeneric(rows, "窜货预警", "跨市", trace.getCrossRegionSummary().getCrossCity());
}
addEmptyGuard(rows);
return rows;
}
/**
* 空数据兜底:保证每个 sheet 至少有一行,避免 FastExcel 无法推断表头
*/
private void addEmptyGuard(List rows) {
if (rows.isEmpty()) {
rows.add(new MesDashboardGenericExcelVO("提示", "暂无数据", "0"));
}
}
private void addOverview(List rows, String indicator, Object value) {
rows.add(new MesDashboardOverviewExcelVO(indicator, value == null ? "0" : value.toString()));
}
private void addProduction(List rows, String category, String status, Object count) {
rows.add(new MesDashboardProductionExcelVO(category, status, count == null ? 0L : ((Number) count).longValue()));
}
private void addGeneric(List rows, String category, String name, Object value) {
rows.add(new MesDashboardGenericExcelVO(category, name, value == null ? "0" : value.toString()));
}
}