liu
昨天 00e4d71bd9d504ed25339595690ebe074146b7e7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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;
 
/**
 * 管理后台 - 管理驾驶舱与统计分析
 * <p>
 * 整合生产、质检、库存、出库、追溯五大维度核心指标,提供可视化图表数据与 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<MesDashboardSummaryRespVO> getSummary() {
        return success(dashboardService.getSummary());
    }
 
    @GetMapping("/production-trend")
    @Operation(summary = "获得生产趋势")
    @Parameter(name = "days", description = "天数", example = "30")
    @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
    public CommonResult<List<MesDashboardProductionTrendRespVO>> 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<MesDashboardQualityRespVO> getQualityStatistics() {
        return success(dashboardService.getQualityStatistics());
    }
 
    @GetMapping("/inventory-statistics")
    @Operation(summary = "获得库存维度统计")
    @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
    public CommonResult<MesDashboardInventoryRespVO> getInventoryStatistics() {
        return success(dashboardService.getInventoryStatistics());
    }
 
    @GetMapping("/outbound-statistics")
    @Operation(summary = "获得出库维度统计")
    @Parameter(name = "days", description = "天数", example = "30")
    @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
    public CommonResult<MesDashboardOutboundRespVO> 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<MesDashboardTraceRespVO> 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<MesDashboardProductionTrendRespVO> 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<MesDashboardOverviewExcelVO> buildOverviewRows(MesDashboardSummaryRespVO summary) {
        List<MesDashboardOverviewExcelVO> 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<MesDashboardProductionExcelVO> buildProductionRows(MesDashboardSummaryRespVO summary,
                                                                    List<MesDashboardProductionTrendRespVO> trend) {
        List<MesDashboardProductionExcelVO> 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<MesDashboardGenericExcelVO> buildQualityRows(MesDashboardQualityRespVO quality) {
        List<MesDashboardGenericExcelVO> 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<MesDashboardGenericExcelVO> buildInventoryRows(MesDashboardInventoryRespVO inventory) {
        List<MesDashboardGenericExcelVO> 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<MesDashboardGenericExcelVO> buildOutboundRows(MesDashboardOutboundRespVO outbound) {
        List<MesDashboardGenericExcelVO> 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<MesDashboardGenericExcelVO> buildTraceRows(MesDashboardTraceRespVO trace) {
        List<MesDashboardGenericExcelVO> 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<MesDashboardGenericExcelVO> rows) {
        if (rows.isEmpty()) {
            rows.add(new MesDashboardGenericExcelVO("提示", "暂无数据", "0"));
        }
    }
 
    private void addOverview(List<MesDashboardOverviewExcelVO> rows, String indicator, Object value) {
        rows.add(new MesDashboardOverviewExcelVO(indicator, value == null ? "0" : value.toString()));
    }
 
    private void addProduction(List<MesDashboardProductionExcelVO> rows, String category, String status, Object count) {
        rows.add(new MesDashboardProductionExcelVO(category, status, count == null ? 0L : ((Number) count).longValue()));
    }
 
    private void addGeneric(List<MesDashboardGenericExcelVO> rows, String category, String name, Object value) {
        rows.add(new MesDashboardGenericExcelVO(category, name, value == null ? "0" : value.toString()));
    }
 
}