feat(qms): 每周生产测试不良数据分析图表实现

- 新增日报表和周报表功能
- 实现不良品分析报表
- 添加报表控制器和报表服务接口
- 编写报表数据传输对象和视图对象
- 实现报表数据查询和统计逻辑
master
zangch@mesnac.com 3 months ago
parent 0f1fc16f98
commit c9412a2423

@ -0,0 +1,43 @@
package org.dromara.qms.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.qms.domain.vo.report.DefectAnalysisReportVo;
import org.dromara.qms.domain.vo.report.WeeklyTestReportVo;
import org.dromara.qms.service.IReportService;
import org.dromara.common.core.domain.R;
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;
/**
*
*
* @author yuex
* @date 2024-07-29
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/report")
public class ReportController {
private final IReportService reportService;
/**
*
*/
@GetMapping("/weeklyTestReport")
public R<WeeklyTestReportVo> getWeeklyTestReport(@RequestParam String startTime, @RequestParam String endTime) {
WeeklyTestReportVo weeklyTestReport = reportService.getWeeklyTestReport(startTime, endTime);
return R.ok(weeklyTestReport);
}
/**
*
*/
@GetMapping("/defectAnalysisReport")
public R<DefectAnalysisReportVo> getDefectAnalysisReport(@RequestParam String startTime, @RequestParam String endTime) {
DefectAnalysisReportVo defectAnalysisReport = reportService.getDefectAnalysisReport(startTime, endTime);
return R.ok(defectAnalysisReport);
}
}

@ -0,0 +1,34 @@
package org.dromara.qms.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
*
*
* @author yuex
* @date 2024-07-29
*/
@Data
public class DefectAnalysisDto {
/**
*
*/
private String metric;
/**
*
*/
private String bizDate;
/**
*
*/
private String typeName;
/**
*
*/
private BigDecimal value;
}

@ -0,0 +1,32 @@
package org.dromara.qms.domain.dto;
public class DefectDataDto {
private String testDate;
private String defectType;
private Integer defectCount;
// Getters and Setters
public String getTestDate() {
return testDate;
}
public void setTestDate(String testDate) {
this.testDate = testDate;
}
public String getDefectType() {
return defectType;
}
public void setDefectType(String defectType) {
this.defectType = defectType;
}
public Integer getDefectCount() {
return defectCount;
}
public void setDefectCount(Integer defectCount) {
this.defectCount = defectCount;
}
}

@ -0,0 +1,35 @@
package org.dromara.qms.domain.dto;
import java.math.BigDecimal;
import java.util.Date;
public class InspectionCountDto {
private String testDate;
private BigDecimal inspectionQty;
private BigDecimal unqualifiedQty;
// Getters and Setters
public String getTestDate() {
return testDate;
}
public void setTestDate(String testDate) {
this.testDate = testDate;
}
public BigDecimal getInspectionQty() {
return inspectionQty;
}
public void setInspectionQty(BigDecimal inspectionQty) {
this.inspectionQty = inspectionQty;
}
public BigDecimal getUnqualifiedQty() {
return unqualifiedQty;
}
public void setUnqualifiedQty(BigDecimal unqualifiedQty) {
this.unqualifiedQty = unqualifiedQty;
}
}

@ -0,0 +1,16 @@
package org.dromara.qms.domain.vo.report;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Map;
@Data
public class DailyTestReportVo {
private String date;
private String dayOfWeek;
private BigDecimal inspectionQty;
private BigDecimal defectQty;
private String defectRate;
private Map<String, Integer> defectTypeSummary;
}

@ -0,0 +1,51 @@
package org.dromara.qms.domain.vo.report;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
*
*
* @author yuex
* @date 2024-07-29
*/
@Data
public class DefectAnalysisReportVo {
/**
*
*/
private List<Map<String, Object>> tableData;
/**
*
*/
private List<DateColumnInfo> dateColumns;
/**
*
*/
private List<String> inspectionTypes;
/**
*
*/
private ChartData chartData;
@Data
public static class DateColumnInfo {
private String date;
private String dayOfWeek;
private String prop;
}
@Data
public static class ChartData {
private List<String> dates;
private List<BigDecimal> inspectionData;
private List<BigDecimal> defectData;
private List<BigDecimal> defectRateData;
}
}

@ -0,0 +1,16 @@
package org.dromara.qms.domain.vo.report;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Data
public class WeeklyTestReportVo {
private List<DailyTestReportVo> dailyReports;
private BigDecimal totalInspectionQty;
private BigDecimal totalDefectQty;
private String totalDefectRate;
private Map<String, Integer> totalDefectTypeSummary;
}

@ -0,0 +1,26 @@
package org.dromara.qms.mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.qms.domain.dto.DefectAnalysisDto;
import org.dromara.qms.domain.dto.DefectDataDto;
import org.dromara.qms.domain.dto.InspectionCountDto;
import java.util.List;
public interface ReportMapper {
List<InspectionCountDto> selectInspectionCounts(@Param("startTime") String startTime, @Param("endTime") String endTime);
List<DefectDataDto> selectDefectData(@Param("startTime") String startTime, @Param("endTime") String endTime);
/**
*
*/
List<String> selectInspectionTypes();
/**
*
*/
List<DefectAnalysisDto> selectDefectAnalysisData(@Param("startTime") String startTime, @Param("endTime") String endTime);
}

@ -0,0 +1,23 @@
package org.dromara.qms.service;
import org.dromara.qms.domain.vo.report.DefectAnalysisReportVo;
import org.dromara.qms.domain.vo.report.WeeklyTestReportVo;
/**
*
*
* @author yuex
* @date 2024-07-29
*/
public interface IReportService {
/**
*
*/
WeeklyTestReportVo getWeeklyTestReport(String startTime, String endTime);
/**
*
*/
DefectAnalysisReportVo getDefectAnalysisReport(String startTime, String endTime);
}

@ -0,0 +1,297 @@
package org.dromara.qms.service.impl;
import lombok.RequiredArgsConstructor;
import org.dromara.qms.domain.dto.DefectAnalysisDto;
import org.dromara.qms.domain.dto.DefectDataDto;
import org.dromara.qms.domain.dto.InspectionCountDto;
import org.dromara.qms.domain.vo.report.DailyTestReportVo;
import org.dromara.qms.domain.vo.report.DefectAnalysisReportVo;
import org.dromara.qms.domain.vo.report.WeeklyTestReportVo;
import org.dromara.qms.mapper.ReportMapper;
import org.dromara.qms.service.IReportService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class ReportServiceImpl implements IReportService {
private final ReportMapper reportMapper;
@Override
public WeeklyTestReportVo getWeeklyTestReport(String startTime, String endTime) {
List<InspectionCountDto> dailyInspectionCount = reportMapper.selectInspectionCounts(startTime, endTime);
List<DefectDataDto> dailyDefectData = reportMapper.selectDefectData(startTime, endTime);
Map<String, DailyTestReportVo> dailyReportMap = new HashMap<>();
for (InspectionCountDto inspectionCount : dailyInspectionCount) {
String date = inspectionCount.getTestDate();
DailyTestReportVo dailyReport = dailyReportMap.computeIfAbsent(date, k -> new DailyTestReportVo());
dailyReport.setDate(date);
dailyReport.setDayOfWeek(getDayOfWeek(date));
dailyReport.setInspectionQty(inspectionCount.getInspectionQty() != null ? inspectionCount.getInspectionQty() : BigDecimal.ZERO);
dailyReport.setDefectQty(inspectionCount.getUnqualifiedQty() != null ? inspectionCount.getUnqualifiedQty() : BigDecimal.ZERO);
dailyReport.setDefectTypeSummary(new HashMap<>());
}
for (DefectDataDto defectData : dailyDefectData) {
String date = defectData.getTestDate();
DailyTestReportVo dailyReport = dailyReportMap.get(date);
if (dailyReport != null && defectData.getDefectType() != null) {
dailyReport.getDefectTypeSummary().put(defectData.getDefectType(), defectData.getDefectCount());
}
}
List<DailyTestReportVo> dailyReports = new ArrayList<>(dailyReportMap.values());
for (DailyTestReportVo report : dailyReports) {
if (report.getInspectionQty().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal defectRate = report.getDefectQty().divide(report.getInspectionQty(), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
report.setDefectRate(String.format("%.2f%%", defectRate));
} else {
report.setDefectRate("0.00%");
}
}
dailyReports.sort(Comparator.comparing(DailyTestReportVo::getDate));
WeeklyTestReportVo weeklyReport = new WeeklyTestReportVo();
weeklyReport.setDailyReports(dailyReports);
BigDecimal totalInspectionQty = dailyReports.stream().map(DailyTestReportVo::getInspectionQty).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalDefectQty = dailyReports.stream().map(DailyTestReportVo::getDefectQty).reduce(BigDecimal.ZERO, BigDecimal::add);
weeklyReport.setTotalInspectionQty(totalInspectionQty);
weeklyReport.setTotalDefectQty(totalDefectQty);
if (totalInspectionQty.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal totalDefectRate = totalDefectQty.divide(totalInspectionQty, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
weeklyReport.setTotalDefectRate(String.format("%.2f%%", totalDefectRate));
} else {
weeklyReport.setTotalDefectRate("0.00%");
}
Map<String, Integer> totalDefectTypeSummary = dailyReports.stream()
.flatMap(r -> r.getDefectTypeSummary().entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(Map.Entry::getValue)));
weeklyReport.setTotalDefectTypeSummary(totalDefectTypeSummary);
return weeklyReport;
}
@Override
public DefectAnalysisReportVo getDefectAnalysisReport(String startTime, String endTime) {
// 获取所有检测类型
List<String> inspectionTypes = reportMapper.selectInspectionTypes();
// 获取不良品分析数据
List<DefectAnalysisDto> analysisData = reportMapper.selectDefectAnalysisData(startTime, endTime);
// 生成日期范围
List<String> dateRange = generateDateRange(startTime, endTime);
// 构建表格数据
List<Map<String, Object>> tableData = buildTableData(analysisData, inspectionTypes, dateRange);
// 构建日期列信息
List<DefectAnalysisReportVo.DateColumnInfo> dateColumns = buildDateColumns(dateRange);
// 构建图表数据
DefectAnalysisReportVo.ChartData chartData = buildChartData(analysisData, dateRange);
DefectAnalysisReportVo reportVo = new DefectAnalysisReportVo();
reportVo.setTableData(tableData);
reportVo.setDateColumns(dateColumns);
reportVo.setInspectionTypes(inspectionTypes);
reportVo.setChartData(chartData);
return reportVo;
}
private List<String> generateDateRange(String startTime, String endTime) {
List<String> dateRange = new ArrayList<>();
LocalDate start = LocalDate.parse(startTime);
LocalDate end = LocalDate.parse(endTime);
LocalDate current = start;
while (!current.isAfter(end)) {
dateRange.add(current.toString());
current = current.plusDays(1);
}
return dateRange;
}
private List<Map<String, Object>> buildTableData(List<DefectAnalysisDto> analysisData,
List<String> inspectionTypes,
List<String> dateRange) {
List<Map<String, Object>> tableData = new ArrayList<>();
// 按指标类型分组数据
Map<String, Map<String, Map<String, BigDecimal>>> groupedData = analysisData.stream()
.collect(Collectors.groupingBy(
DefectAnalysisDto::getMetric,
Collectors.groupingBy(
DefectAnalysisDto::getBizDate,
Collectors.toMap(
DefectAnalysisDto::getTypeName,
DefectAnalysisDto::getValue,
(v1, v2) -> v1
)
)
));
// 构建检验数量行
Map<String, Object> inspectionRow = new HashMap<>();
inspectionRow.put("metric", "检验数量 (单位: 个)");
BigDecimal totalInspection = BigDecimal.ZERO;
for (String date : dateRange) {
BigDecimal dayTotal = BigDecimal.ZERO;
Map<String, Map<String, BigDecimal>> dateData = groupedData.get("检验数量(单位:个)");
if (dateData != null && dateData.containsKey(date)) {
for (String type : inspectionTypes) {
BigDecimal value = dateData.get(date).getOrDefault(type, BigDecimal.ZERO);
dayTotal = dayTotal.add(value);
}
}
inspectionRow.put(date, dayTotal);
totalInspection = totalInspection.add(dayTotal);
}
inspectionRow.put("total", totalInspection);
tableData.add(inspectionRow);
// 构建不良数量行
Map<String, Object> defectRow = new HashMap<>();
defectRow.put("metric", "不良数量 (单位: 个)");
BigDecimal totalDefect = BigDecimal.ZERO;
for (String date : dateRange) {
BigDecimal dayTotal = BigDecimal.ZERO;
Map<String, Map<String, BigDecimal>> dateData = groupedData.get("不合格数(单位:个)");
if (dateData != null && dateData.containsKey(date)) {
for (String type : inspectionTypes) {
BigDecimal value = dateData.get(date).getOrDefault(type, BigDecimal.ZERO);
dayTotal = dayTotal.add(value);
}
}
defectRow.put(date, dayTotal);
totalDefect = totalDefect.add(dayTotal);
}
defectRow.put("total", totalDefect);
tableData.add(defectRow);
// 构建不良率行
Map<String, Object> rateRow = new HashMap<>();
rateRow.put("metric", "不良率 (单位: %)");
for (String date : dateRange) {
BigDecimal inspectionQty = (BigDecimal) inspectionRow.get(date);
BigDecimal defectQty = (BigDecimal) defectRow.get(date);
if (inspectionQty.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal rate = defectQty.divide(inspectionQty, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
rateRow.put(date, rate.setScale(2, RoundingMode.HALF_UP));
} else {
rateRow.put(date, BigDecimal.ZERO);
}
}
// 计算总不良率
if (totalInspection.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal totalRate = totalDefect.divide(totalInspection, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
rateRow.put("total", totalRate.setScale(2, RoundingMode.HALF_UP));
} else {
rateRow.put("total", BigDecimal.ZERO);
}
tableData.add(rateRow);
// 构建各检测类型行
for (String type : inspectionTypes) {
Map<String, Object> typeRow = new HashMap<>();
typeRow.put("metric", type + " (单位: 个)");
BigDecimal typeTotal = BigDecimal.ZERO;
for (String date : dateRange) {
BigDecimal value = BigDecimal.ZERO;
Map<String, Map<String, BigDecimal>> dateData = groupedData.get("检验数量(单位:个)");
if (dateData != null && dateData.containsKey(date)) {
value = dateData.get(date).getOrDefault(type, BigDecimal.ZERO);
}
typeRow.put(date, value);
typeTotal = typeTotal.add(value);
}
typeRow.put("total", typeTotal);
tableData.add(typeRow);
}
return tableData;
}
private List<DefectAnalysisReportVo.DateColumnInfo> buildDateColumns(List<String> dateRange) {
return dateRange.stream().map(date -> {
DefectAnalysisReportVo.DateColumnInfo columnInfo = new DefectAnalysisReportVo.DateColumnInfo();
columnInfo.setDate(LocalDate.parse(date).format(DateTimeFormatter.ofPattern("M月d日")));
columnInfo.setDayOfWeek(getDayOfWeek(date));
columnInfo.setProp(date);
return columnInfo;
}).collect(Collectors.toList());
}
private DefectAnalysisReportVo.ChartData buildChartData(List<DefectAnalysisDto> analysisData, List<String> dateRange) {
DefectAnalysisReportVo.ChartData chartData = new DefectAnalysisReportVo.ChartData();
List<String> dates = dateRange.stream()
.map(date -> LocalDate.parse(date).format(DateTimeFormatter.ofPattern("M月d日")))
.collect(Collectors.toList());
List<BigDecimal> inspectionData = new ArrayList<>();
List<BigDecimal> defectData = new ArrayList<>();
List<BigDecimal> defectRateData = new ArrayList<>();
// 按日期和指标分组数据
Map<String, Map<String, BigDecimal>> dailyData = new HashMap<>();
for (DefectAnalysisDto data : analysisData) {
dailyData.computeIfAbsent(data.getBizDate(), k -> new HashMap<>())
.merge(data.getMetric(), data.getValue(), BigDecimal::add);
}
for (String date : dateRange) {
Map<String, BigDecimal> dayData = dailyData.getOrDefault(date, new HashMap<>());
BigDecimal inspection = dayData.getOrDefault("检验数量(单位:个)", BigDecimal.ZERO);
BigDecimal defect = dayData.getOrDefault("不合格数(单位:个)", BigDecimal.ZERO);
inspectionData.add(inspection);
defectData.add(defect);
if (inspection.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal rate = defect.divide(inspection, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
defectRateData.add(rate.setScale(2, RoundingMode.HALF_UP));
} else {
defectRateData.add(BigDecimal.ZERO);
}
}
chartData.setDates(dates);
chartData.setInspectionData(inspectionData);
chartData.setDefectData(defectData);
chartData.setDefectRateData(defectRateData);
return chartData;
}
private String getDayOfWeek(String dateStr) {
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE);
}
}

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.qms.mapper.ReportMapper">
<select id="selectInspectionCounts" resultType="org.dromara.qms.domain.dto.InspectionCountDto">
SELECT
CONVERT(varchar, create_time, 23) AS testDate,
SUM(inspection_qty) AS inspectionQty,
SUM(unqualified_qty) AS unqualifiedQty
FROM
qc_inspection_main
WHERE
create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY
CONVERT(varchar, create_time, 23)
</select>
<select id="selectDefectData" resultType="org.dromara.qms.domain.dto.DefectDataDto">
SELECT
CONVERT(varchar, qim.create_time, 23) AS testDate,
qurc.item_name AS defectType,
COUNT(qurc.record_id) AS defectCount
FROM
qc_inspection_main qim
LEFT JOIN
qc_unqualified_review qur ON qim.inspection_no = qur.inspection_no
LEFT JOIN
qc_unqualified_record qurc ON qur.review_id = qurc.review_id
WHERE
qim.create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY
CONVERT(varchar, qim.create_time, 23), qurc.item_name
</select>
<!-- 获取所有检测类型 -->
<select id="selectInspectionTypes" resultType="java.lang.String">
SELECT type_name
FROM qc_inspection_type
WHERE ISNULL(del_flag, '0') = '0'
ORDER BY type_id
</select>
<!-- 不良品分析报表数据查询 -->
<select id="selectDefectAnalysisData" resultType="org.dromara.qms.domain.dto.DefectAnalysisDto">
SELECT metric, biz_date, type_name, value
FROM (
/* 1) 每日各检测类型-检验数量(来自 qc_inspection_main */
SELECT '检验数量(单位:个)' AS metric,
CONVERT(varchar, m.inspection_start_time, 23) AS biz_date,
it.type_name,
SUM(ISNULL(m.inspection_qty, 0)) AS value
FROM qc_inspection_main m
INNER JOIN qc_inspection_type it ON it.type_id = m.inspection_type
WHERE m.inspection_start_time IS NOT NULL
AND ISNULL(m.del_flag, '0') = '0'
AND ISNULL(it.del_flag, '0') = '0'
AND CONVERT(varchar, m.inspection_start_time, 23) BETWEEN #{startTime} AND #{endTime}
GROUP BY CONVERT(varchar, m.inspection_start_time, 23), it.type_name
UNION ALL
/* 2) 每日各检测类型-不合格数(来自 qc_inspection_main */
SELECT '不合格数(单位:个)' AS metric,
CONVERT(varchar, m.inspection_start_time, 23) AS biz_date,
it.type_name,
SUM(ISNULL(m.unqualified_qty, 0)) AS value
FROM qc_inspection_main m
INNER JOIN qc_inspection_type it ON it.type_id = m.inspection_type
WHERE m.inspection_start_time IS NOT NULL
AND ISNULL(m.del_flag, '0') = '0'
AND ISNULL(it.del_flag, '0') = '0'
AND CONVERT(varchar, m.inspection_start_time, 23) BETWEEN #{startTime} AND #{endTime}
GROUP BY CONVERT(varchar, m.inspection_start_time, 23), it.type_name
UNION ALL
/* 3) 每日各检测类型-不合格记录数(来自 qc_unqualified_record + qc_unqualified_review */
SELECT '不合格记录数(单位:个)' AS metric,
CONVERT(varchar, rv.production_date, 23) AS biz_date,
it.type_name,
COUNT(1) AS value
FROM qc_unqualified_record ur
INNER JOIN qc_unqualified_review rv ON rv.review_id = ur.review_id
INNER JOIN qc_inspection_type it ON it.type_id = ur.type_id
WHERE rv.production_date IS NOT NULL
AND ISNULL(rv.del_flag, '0') = '0'
AND ISNULL(ur.del_flag, '0') = '0'
AND ISNULL(it.del_flag, '0') = '0'
AND CONVERT(varchar, rv.production_date, 23) BETWEEN #{startTime} AND #{endTime}
GROUP BY CONVERT(varchar, rv.production_date, 23), it.type_name
) s
ORDER BY biz_date, metric, type_name
</select>
</mapper>
Loading…
Cancel
Save