feat(dms): 新增报表功能

- 在 DmsReportMapper 中增加实时报警分页与列表查询接口
- 在 DmsReportMapper.xml 中添加实时报警数据查询 SQL 片段及映射配置
- 实现 service 层对实时报警报表的分页和导出逻辑
- 优化故障类型字段类型转换,确保空值处理一致性- 调整时间字段格式化输出,提升报表可读性

feat(qms): 新增来料检验效率报表功能

- ReportController 增加来料检验效率接口
- ReportService 和 ReportMapper 增加相关 VO 和查询方法
- ReportMapper.xml 中新增复杂聚合查询 SQL,支持检验结果、不合格项

feat(mes): 改进工序进度报表查询逻辑
- 修改 getOrderProcessProgress 方法以支持动态表名查询
- 根据订单派工类型自动识别并查询对应的工序计划表
- 引入工艺路线服务,增强工序数据获取灵活性
hwmom-htk
zangch@mesnac.com 4 months ago
parent 0a75b0a6b4
commit 50abfb434d

@ -0,0 +1,52 @@
package org.dromara.dms.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.dms.domain.bo.RealtimeAlarmReportBo;
import org.dromara.dms.domain.vo.RealtimeAlarmReportVo;
import org.dromara.dms.service.IDmsReportService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/report/realtimeAlarm")
public class DmsRealtimeAlarmReportController extends BaseController {
private final IDmsReportService dmsReportService;
/**
*
*/
@SaCheckPermission("dms:report:realtimeAlarm:list")
@GetMapping("/list")
public TableDataInfo<RealtimeAlarmReportVo> list(RealtimeAlarmReportBo bo, PageQuery pageQuery) {
return dmsReportService.queryRealtimeAlarm(bo, pageQuery);
}
/**
*
*/
@SaCheckPermission("dms:report:realtimeAlarm:export")
@Log(title = "实时报警报表", businessType = BusinessType.EXPORT)
@RepeatSubmit()
@PostMapping("/export")
public void export(RealtimeAlarmReportBo bo, HttpServletResponse response) {
List<RealtimeAlarmReportVo> list = dmsReportService.exportRealtimeAlarm(bo);
ExcelUtil.exportExcel(list, "实时报警报表", RealtimeAlarmReportVo.class, response);
}
}

@ -0,0 +1,67 @@
package org.dromara.dms.domain.bo;
import lombok.Data;
import java.util.Map;
/**
*
*/
@Data
public class RealtimeAlarmReportBo {
/**
* alarm_begin_time
*/
private String startDate;
/**
* alarm_begin_time
*/
private String endDate;
/**
* IDdms_device_mode.device_mode_id
*/
private Long deviceModeId;
/**
*
*/
private String machineCode;
/**
* IDprod_base_machine_info.machine_id base_alarm_info.device_id
*/
private Long machineId;
/**
* ID
*/
private Long alarmLevelId;
/**
* ID
*/
private Long alarmTypeId;
/**
* 012
*/
private String alarmStatus;
/**
* 12
*/
private String alarmMode;
/**
* 13线
*/
private String alarmInfoType;
/**
*
*/
private Map<String, Object> params;
}

@ -0,0 +1,47 @@
package org.dromara.dms.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
/**
*
*/
@Data
public class RealtimeAlarmReportVo {
@ExcelProperty(value = "设备编号")
private String machineCode;
@ExcelProperty(value = "设备名称")
private String machineName;
@ExcelProperty(value = "设备型号")
private String deviceModeName;
@ExcelProperty(value = "报警级别")
private String alarmLevelName;
@ExcelProperty(value = "报警类型")
private String alarmTypeName;
@ExcelProperty(value = "报警状态")
@ExcelDictFormat(dictType = "alarm_status")
private String alarmStatus;
@ExcelProperty(value = "报警方式")
@ExcelDictFormat(dictType = "alarm_mode")
private String alarmMode;
@ExcelProperty(value = "开始时间")
private String alarmBeginTime;
@ExcelProperty(value = "结束时间")
private String alarmEndTime;
@ExcelProperty(value = "持续时长(分钟)")
private String durationMinutes;
@ExcelProperty(value = "报警内容")
private String alarmContent;
}

@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.dromara.dms.domain.bo.FaultTraceReportBo;
import org.dromara.dms.domain.bo.RealtimeAlarmReportBo;
import org.dromara.dms.domain.vo.FaultTraceReportVo;
import org.dromara.dms.domain.vo.RealtimeAlarmReportVo;
import java.util.List;
@ -13,4 +15,8 @@ public interface DmsReportMapper {
IPage<FaultTraceReportVo> selectFaultTracePage(Page<?> page, @Param("bo") FaultTraceReportBo bo);
List<FaultTraceReportVo> selectFaultTraceList(@Param("bo") FaultTraceReportBo bo);
IPage<RealtimeAlarmReportVo> selectRealtimeAlarmPage(Page<?> page, @Param("bo") RealtimeAlarmReportBo bo);
List<RealtimeAlarmReportVo> selectRealtimeAlarmList(@Param("bo") RealtimeAlarmReportBo bo);
}

@ -4,14 +4,20 @@ package org.dromara.dms.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.dms.domain.bo.FaultTraceReportBo;
import org.dromara.dms.domain.bo.RealtimeAlarmReportBo;
import org.dromara.dms.domain.vo.FaultTraceReportVo;
import org.dromara.dms.domain.vo.RealtimeAlarmReportVo;
import java.util.List;
public interface IDmsReportService {
public TableDataInfo<FaultTraceReportVo> queryFaultTrace(FaultTraceReportBo bo, PageQuery pageQuery);
TableDataInfo<FaultTraceReportVo> queryFaultTrace(FaultTraceReportBo bo, PageQuery pageQuery);
public List<FaultTraceReportVo> exportFaultTrace(FaultTraceReportBo bo);
List<FaultTraceReportVo> exportFaultTrace(FaultTraceReportBo bo);
TableDataInfo<RealtimeAlarmReportVo> queryRealtimeAlarm(RealtimeAlarmReportBo bo, PageQuery pageQuery);
List<RealtimeAlarmReportVo> exportRealtimeAlarm(RealtimeAlarmReportBo bo);
}

@ -1,11 +1,14 @@
package org.dromara.dms.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.dms.domain.bo.FaultTraceReportBo;
import org.dromara.dms.domain.bo.RealtimeAlarmReportBo;
import org.dromara.dms.domain.vo.FaultTraceReportVo;
import org.dromara.dms.domain.vo.RealtimeAlarmReportVo;
import org.dromara.dms.mapper.DmsReportMapper;
import org.dromara.dms.service.IDmsReportService;
import org.springframework.stereotype.Service;
@ -20,12 +23,25 @@ public class DmsReportServiceImpl implements IDmsReportService {
@Override
public TableDataInfo<FaultTraceReportVo> queryFaultTrace(FaultTraceReportBo bo, PageQuery pageQuery) {
IPage<FaultTraceReportVo> page = dmsReportMapper.selectFaultTracePage(pageQuery.build(), bo);
return TableDataInfo.build(page);
Page<FaultTraceReportVo> page = pageQuery.build();
IPage<FaultTraceReportVo> iPage = dmsReportMapper.selectFaultTracePage(page, bo);
return TableDataInfo.build(iPage);
}
@Override
public List<FaultTraceReportVo> exportFaultTrace(FaultTraceReportBo bo) {
return dmsReportMapper.selectFaultTraceList(bo);
}
@Override
public TableDataInfo<RealtimeAlarmReportVo> queryRealtimeAlarm(RealtimeAlarmReportBo bo, PageQuery pageQuery) {
Page<RealtimeAlarmReportVo> page = pageQuery.build();
IPage<RealtimeAlarmReportVo> iPage = dmsReportMapper.selectRealtimeAlarmPage(page, bo);
return TableDataInfo.build(iPage);
}
@Override
public List<RealtimeAlarmReportVo> exportRealtimeAlarm(RealtimeAlarmReportBo bo) {
return dmsReportMapper.selectRealtimeAlarmList(bo);
}
}

@ -132,8 +132,8 @@ where mp2.machine_id = fb.machine_id - 同一机台
dateadd(week, datediff(week, datefromparts(year(fb.apply_time), month(fb.apply_time), 1), fb.apply_time),
datefromparts(year(fb.apply_time), month(fb.apply_time), 1)))
) as period_hours,
-- 故障类型:直接使用原始
coalesce(af.fault_type, '') as fault_type,
-- 故障类型:确保转换为字符串类型并处理空
cast(coalesce(af.fault_type, '') as varchar(10)) as fault_type,
cast(case
when fb.real_begin_time is not null and fb.real_end_time is not null and fb.real_end_time >= fb.real_begin_time
then datediff(second, fb.real_begin_time, fb.real_end_time) / 3600.0
@ -374,7 +374,7 @@ where mp2.machine_id = fb.machine_id - 同一机台
)
select
a.period_label,
a.period_start,
format(a.period_start, 'yyyy-MM-dd') as period_start,
a.device_type,
a.machine_code,
a.fault_type,
@ -386,7 +386,7 @@ where mp2.machine_id = fb.machine_id - 同一机台
a.total_order_delay_count as delay_count,
coalesce(ca.top_cause, N'') as top_cause,
coalesce(tr.top_resolution, N'') as top_resolution,
tr.top_resolution_time
format(tr.top_resolution_time, 'yyyy-MM-dd HH:mm:ss') as top_resolution_time
from agg a
left join top_cause ca
on ca.period_start = a.period_start
@ -411,4 +411,71 @@ where mp2.machine_id = fb.machine_id - 同一机台
<include refid="FaultTraceMain"/>
</select>
<!-- 实时报警公共查询片段 -->
<sql id="RealtimeAlarmMain">
select
m.machine_code as machine_code,
m.machine_name as machine_name,
coalesce(dm.device_mode_name, N'') as device_mode_name,
coalesce(al.alarm_level_name, N'') as alarm_level_name,
coalesce(at.alarm_type_name, N'') as alarm_type_name,
coalesce(ai.alarm_status, '') as alarm_status,
coalesce(ai.alarm_mode, '') as alarm_mode,
format(ai.alarm_begin_time, 'yyyy-MM-dd HH:mm:ss') as alarm_begin_time,
format(ai.alarm_end_time, 'yyyy-MM-dd HH:mm:ss') as alarm_end_time,
cast(datediff(minute, ai.alarm_begin_time, coalesce(ai.alarm_end_time, getdate())) as varchar(32)) as duration_minutes,
coalesce(ai.alarm_content, N'') as alarm_content
from base_alarm_info ai
join prod_base_machine_info m
on m.machine_id = ai.device_id
left join dms_device_mode dm
on dm.device_mode_id = m.device_mode_id
left join base_alarm_level al
on al.alarm_level_id = ai.alarm_level_id
left join base_alarm_type at
on at.alarm_type_id = ai.alarm_type_id
where 1=1
<if test="bo.startDate != null">
and ai.alarm_begin_time &gt;= #{bo.startDate}
</if>
<if test="bo.endDate != null">
and ai.alarm_begin_time &lt; #{bo.endDate}
</if>
<if test="bo.machineId != null">
and ai.device_id = #{bo.machineId}
</if>
<if test="bo.machineCode != null and bo.machineCode != ''">
and m.machine_code like concat('%', #{bo.machineCode}, '%')
</if>
<if test="bo.deviceModeId != null">
and m.device_mode_id = #{bo.deviceModeId}
</if>
<if test="bo.alarmLevelId != null">
and ai.alarm_level_id = #{bo.alarmLevelId}
</if>
<if test="bo.alarmTypeId != null">
and ai.alarm_type_id = #{bo.alarmTypeId}
</if>
<if test="bo.alarmStatus != null and bo.alarmStatus != ''">
and ai.alarm_status = #{bo.alarmStatus}
</if>
<if test="bo.alarmMode != null and bo.alarmMode != ''">
and ai.alarm_mode = #{bo.alarmMode}
</if>
<if test="bo.alarmInfoType != null and bo.alarmInfoType != ''">
and ai.alarm_info_type = #{bo.alarmInfoType}
</if>
order by ai.alarm_begin_time desc, m.machine_code
</sql>
<!-- 实时报警分页查询 -->
<select id="selectRealtimeAlarmPage" resultType="org.dromara.dms.domain.vo.RealtimeAlarmReportVo">
<include refid="RealtimeAlarmMain"/>
</select>
<!-- 实时报警导出(不分页) -->
<select id="selectRealtimeAlarmList" resultType="org.dromara.dms.domain.vo.RealtimeAlarmReportVo">
<include refid="RealtimeAlarmMain"/>
</select>
</mapper>

@ -121,5 +121,5 @@ public interface ProdReportMapper {
/**
*
*/
List<ProcessProgressVo> getOrderProcessProgress(@Param("productOrderId") Long productOrderId);
List<ProcessProgressVo> getOrderProcessProgress(@Param("productOrderId") Long productOrderId, @Param("planTableName") String planTableName);
}

@ -12,8 +12,12 @@ import org.dromara.mes.domain.vo.WorkHourReportVo;
import org.dromara.mes.domain.vo.TeamWorkReportVo;
import org.dromara.mes.domain.vo.WipTrackingReportVo;
import org.dromara.mes.domain.vo.ProcessProgressVo;
import org.dromara.mes.domain.vo.ProdBaseRouteProcessVo;
import org.dromara.mes.domain.bo.ProdBaseRouteProcessBo;
import org.dromara.mes.mapper.ProdReportMapper;
import org.dromara.mes.mapper.ProdOrderInfoMapper;
import org.dromara.mes.service.IProdReportService;
import org.dromara.mes.service.IProdBaseRouteProcessService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
@ -31,6 +35,8 @@ import java.util.Map;
public class ProdReportServiceImpl implements IProdReportService {
private final ProdReportMapper prodReportMapper;
private final ProdOrderInfoMapper prodOrderInfoMapper;
private final IProdBaseRouteProcessService prodBaseRouteProcessService;
/**
*
@ -64,11 +70,11 @@ public class ProdReportServiceImpl implements IProdReportService {
* @return
*/
public String getPlanInfoTableNameByProcessId(Long processId) {
String tableName = null;
String tableName;
if (StringUtils.isNull(processId)) {
return null;
}
if (processId == 17L) {
// 默认返回半制品表
tableName = DatabaseConstants.TABLE_NAME_PROD_PLAN_INFO_PREFIX + "_2";
} else if (processId == 17L) {
tableName = DatabaseConstants.TABLE_NAME_PROD_PLAN_INFO_PREFIX + "_3";
} else if (processId == 18L) {
tableName = DatabaseConstants.TABLE_NAME_PROD_PLAN_INFO_PREFIX + "_4";
@ -197,7 +203,7 @@ public class ProdReportServiceImpl implements IProdReportService {
// 为每个订单获取工序进度详情
for (WipTrackingReportVo vo : page.getRecords()) {
if (vo.getProductOrderId() != null) {
List<ProcessProgressVo> processProgress = prodReportMapper.getOrderProcessProgress(vo.getProductOrderId());
List<ProcessProgressVo> processProgress = getAllOrderProcessProgress(vo.getProductOrderId());
vo.setProcessProgressList(processProgress);
}
}
@ -215,7 +221,7 @@ public class ProdReportServiceImpl implements IProdReportService {
// 为每个订单获取工序进度详情
for (WipTrackingReportVo vo : list) {
if (vo.getProductOrderId() != null) {
List<ProcessProgressVo> processProgress = prodReportMapper.getOrderProcessProgress(vo.getProductOrderId());
List<ProcessProgressVo> processProgress = getAllOrderProcessProgress(vo.getProductOrderId());
vo.setProcessProgressList(processProgress);
}
}
@ -229,6 +235,80 @@ public class ProdReportServiceImpl implements IProdReportService {
*/
@Override
public List<ProcessProgressVo> getOrderProcessProgress(Long productOrderId) {
return prodReportMapper.getOrderProcessProgress(productOrderId);
// 默认查询半制品表
String planTableName = getPlanInfoTableNameByProcessId(null);
return prodReportMapper.getOrderProcessProgress(productOrderId, planTableName);
}
/**
*
* @param productOrderId ID
* @return
*/
private List<ProcessProgressVo> getAllOrderProcessProgress(Long productOrderId) {
List<ProcessProgressVo> allProcessProgress = new java.util.ArrayList<>();
try {
// 1. 获取订单信息
var orderInfo = prodOrderInfoMapper.selectById(productOrderId);
if (orderInfo == null) {
return allProcessProgress;
}
// 2. 根据派工类型获取工序列表
List<Long> processIds = new java.util.ArrayList<>();
if ("2".equals(orderInfo.getDispatchType())) {
// 工艺路线派工通过工艺路线ID获取工序列表
Long routeId = orderInfo.getDispatchId();
if (routeId != null) {
ProdBaseRouteProcessBo bo = new ProdBaseRouteProcessBo();
bo.setRouteId(routeId);
List<ProdBaseRouteProcessVo> routeProcessList = prodBaseRouteProcessService.queryList(bo);
for (ProdBaseRouteProcessVo routeProcess : routeProcessList) {
if (routeProcess.getProcessId() != null) {
processIds.add(routeProcess.getProcessId());
}
}
}
} else if ("3".equals(orderInfo.getDispatchType())) {
// 单工序派工直接使用dispatchId作为工序ID
if (orderInfo.getDispatchId() != null) {
processIds.add(orderInfo.getDispatchId());
}
} else {
// 产线派工或其他情况,查询所有可能的工序表
// 根据业务规则,查询半制品(16)、成型(17)、硫化(18)工序
processIds.addAll(List.of(16L, 17L, 18L));
}
// 3. 根据工序ID列表查询对应的表
for (Long processId : processIds) {
try {
String planTableName = getPlanInfoTableNameByProcessId(processId);
if (planTableName != null) {
List<ProcessProgressVo> processProgress = prodReportMapper.getOrderProcessProgress(productOrderId, planTableName);
if (processProgress != null && !processProgress.isEmpty()) {
allProcessProgress.addAll(processProgress);
}
}
} catch (Exception e) {
// 忽略单个表的查询错误,继续查询其他表
}
}
// 4. 按工序顺序排序
allProcessProgress.sort((a, b) -> {
if (a.getProcessOrder() == null) return 1;
if (b.getProcessOrder() == null) return -1;
return a.getProcessOrder().compareTo(b.getProcessOrder());
});
} catch (Exception e) {
// 静默处理异常,返回空列表
}
return allProcessProgress;
}
}

@ -184,11 +184,11 @@
bmi.material_name AS productName,
pproc.process_name AS processName,
CAST(ppd.complete_amount AS DECIMAL(18,4)) AS productionQuantity,
CAST(ISNULL(ppd.ok_amount, 0) AS DECIMAL(18,4)) AS qualifiedQuantity,
CAST(ISNULL(ppd.ng_amount, 0) AS DECIMAL(18,4)) AS unqualifiedQuantity,
CAST(ISNULL(qc.qualified_qty, 0) AS DECIMAL(18,4)) AS qualifiedQuantity,
CAST(ISNULL(qc.unqualified_qty, 0) AS DECIMAL(18,4)) AS unqualifiedQuantity,
CAST(
CASE WHEN ISNULL(ppd.complete_amount,0) > 0
THEN (ISNULL(ppd.ok_amount,0) + 0.0) / (ppd.complete_amount + 0.0) * 100
THEN (ISNULL(qc.qualified_qty,0) + 0.0) / (ppd.complete_amount + 0.0) * 100
ELSE NULL END
AS DECIMAL(18,4)
) AS qualifiedRate,
@ -202,14 +202,23 @@
LEFT JOIN prod_base_station_info AS pbsi ON pbsi.station_id = bct.station_id
LEFT JOIN prod_base_process_info AS pproc ON pproc.process_id = ppi.process_id
LEFT JOIN base_material_info AS bmi ON bmi.material_id = ppi.material_id
LEFT JOIN (
SELECT
plan_detail_id,
SUM(ISNULL(qualified_qty, 0)) AS qualified_qty,
SUM(ISNULL(unqualified_qty, 0)) AS unqualified_qty
FROM qc_inspection_main
WHERE del_flag = '0'
GROUP BY plan_detail_id
) AS qc ON qc.plan_detail_id = ppd.plan_detail_id
<where>
ppd.real_begin_time IS NOT NULL AND ppd.real_end_time IS NOT NULL
-- ppd.real_begin_time IS NOT NULL AND ppd.real_end_time IS NOT NULL
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND FORMAT(ppd.real_begin_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
<if test="map.processId != null and map.processId != ''">
AND ppi.process_id = #{map.processId}
FORMAT(ppd.create_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
<!-- <if test="map.processId != null and map.processId != ''">-->
<!-- AND ppi.process_id = #{map.processId}-->
<!-- </if>-->
<if test="map.machineId != null and map.machineId != ''">
AND ppi.release_id = #{map.machineId}
</if>
@ -260,13 +269,14 @@
LEFT JOIN prod_base_process_info AS pproc ON pproc.process_id = ppi.process_id
LEFT JOIN base_material_info AS bmi ON bmi.material_id = ppi.material_id
<where>
ppd.real_begin_time IS NOT NULL AND ppd.real_end_time IS NOT NULL
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND FORMAT(ppd.real_begin_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
<if test="map.processId != null and map.processId != ''">
<!-- ppd.real_begin_time IS NOT NULL AND ppd.real_end_time IS NOT NULL -->
<!-- ppd.real_begin_time IS NOT NULL AND ppd.real_end_time IS NOT NULL -->
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND FORMAT(ppd.create_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
<!-- <if test="map.processId != null and map.processId != ''">
AND ppi.process_id = #{map.processId}
</if>
</if>-->
<if test="map.machineId != null and map.machineId != ''">
AND ppi.release_id = #{map.machineId}
</if>
@ -364,7 +374,7 @@
ELSE '未知'
END AS statusDesc,
CASE WHEN p.plan_status = '3' THEN 1 ELSE 0 END AS isCompleted
FROM prod_plan_info p
FROM ${planTableName} p
LEFT JOIN prod_base_process_info pr ON pr.process_id = p.process_id
WHERE p.product_order_id = #{productOrderId}
ORDER BY p.process_order

@ -1,15 +1,18 @@
package org.dromara.qms.controller;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.qms.domain.vo.report.DefectAnalysisReportVo;
import org.dromara.qms.domain.vo.report.IqcEfficiencyReportVo;
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;
import java.util.List;
/**
*
*
@ -40,4 +43,9 @@ public class ReportController {
DefectAnalysisReportVo defectAnalysisReport = reportService.getDefectAnalysisReport(startTime, endTime);
return R.ok(defectAnalysisReport);
}
@GetMapping("/incoming")
public R<List<IqcEfficiencyReportVo>> getIncomingInspectionEfficiency(@RequestParam String startTime, @RequestParam String endTime) {
return R.ok(reportService.getIncomingInspectionEfficiency(startTime, endTime));
}
}

@ -4,6 +4,7 @@ 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 org.dromara.qms.domain.vo.report.IqcEfficiencyReportVo;
import java.util.List;
@ -13,14 +14,12 @@ public interface ReportMapper {
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);
}
/** 获取来料检验效率报表数据 */
List<IqcEfficiencyReportVo> selectIqcEfficiencyReport(@Param("startTime") String startTime, @Param("endTime") String endTime);
}

@ -2,6 +2,8 @@ package org.dromara.qms.service;
import org.dromara.qms.domain.vo.report.DefectAnalysisReportVo;
import org.dromara.qms.domain.vo.report.WeeklyTestReportVo;
import org.dromara.qms.domain.vo.report.IqcEfficiencyReportVo;
import java.util.List;
/**
*
@ -20,4 +22,6 @@ public interface IReportService {
*
*/
DefectAnalysisReportVo getDefectAnalysisReport(String startTime, String endTime);
}
List<IqcEfficiencyReportVo> getIncomingInspectionEfficiency(String startTime, String endTime);
}

@ -7,6 +7,7 @@ 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.domain.vo.report.IqcEfficiencyReportVo;
import org.dromara.qms.mapper.ReportMapper;
import org.dromara.qms.service.IReportService;
import org.springframework.stereotype.Service;
@ -25,59 +26,89 @@ public class ReportServiceImpl implements IReportService {
private final ReportMapper reportMapper;
/**
*
*
* @param startTime
* @param endTime
* @return
*/
@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));
// 设置检测数量如果为空则设为0
dailyReport.setInspectionQty(inspectionCount.getInspectionQty() != null ? inspectionCount.getInspectionQty() : BigDecimal.ZERO);
// 设置不合格数量如果为空则设为0
dailyReport.setDefectQty(inspectionCount.getUnqualifiedQty() != null ? inspectionCount.getUnqualifiedQty() : BigDecimal.ZERO);
// 初始化缺陷类型统计Map
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) {
// 计算每日缺陷率:不合格数量/检测数量*100%保留4位小数进行计算最后格式化为百分比字符串
if (report.getInspectionQty().compareTo(BigDecimal.ZERO) > 0) {
// 缺陷率 = 不合格数量 / 检测数量 * 100%
BigDecimal defectRate = report.getDefectQty().divide(report.getInspectionQty(), 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
report.setDefectRate(String.format("%.2f%%", defectRate));
} else {
// 如果检测数量为0则缺陷率为0%
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) {
// 总缺陷率 = 总不合格数量 / 总检测数量 * 100%
BigDecimal totalDefectRate = totalDefectQty.divide(totalInspectionQty, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
weeklyReport.setTotalDefectRate(String.format("%.2f%%", totalDefectRate));
} else {
// 如果总检测数量为0则总缺陷率为0%
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)));
@ -87,55 +118,80 @@ public class ReportServiceImpl implements IReportService {
return weeklyReport;
}
/**
*
*
* @param startTime
* @param endTime
* @return
*/
@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;
}
/**
*
*
* @param startTime
* @param endTime
* @return
*/
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,
/**
*
*
* @param analysisData
* @param inspectionTypes
* @param dateRange
* @return
*/
private List<Map<String, Object>> buildTableData(List<DefectAnalysisDto> analysisData,
List<String> inspectionTypes,
List<String> dateRange) {
List<Map<String, Object>> tableData = new ArrayList<>();
// 按指标类型分组数据
// 按指标类型分组数据metric -> bizDate -> typeName -> value
// 将原始数据按指标、日期、检测类型进行多级分组,便于后续数据查询和计算
Map<String, Map<String, Map<String, BigDecimal>>> groupedData = analysisData.stream()
.collect(Collectors.groupingBy(
DefectAnalysisDto::getMetric,
@ -149,63 +205,79 @@ public class ReportServiceImpl implements IReportService {
)
));
// 构建检验数量行
// 构建检验数量行,统计每日各类检测的总检验数量
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);
// 不良率 = 不良数量 / 检验数量 * 100%,保留两位小数
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 {
// 如果检验数量为0则不良率为0
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));
@ -214,21 +286,27 @@ public class ReportServiceImpl implements IReportService {
}
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);
}
@ -236,44 +314,68 @@ public class ReportServiceImpl implements IReportService {
return tableData;
}
/**
*
*
* @param dateRange
* @return
*/
private List<DefectAnalysisReportVo.DateColumnInfo> buildDateColumns(List<String> dateRange) {
return dateRange.stream().map(date -> {
DefectAnalysisReportVo.DateColumnInfo columnInfo = new DefectAnalysisReportVo.DateColumnInfo();
// 格式化日期显示M月d日例如"9月15日"
columnInfo.setDate(LocalDate.parse(date).format(DateTimeFormatter.ofPattern("M月d日")));
// 设置星期几的中文名称
columnInfo.setDayOfWeek(getDayOfWeek(date));
// 设置属性名,用于前端绑定
columnInfo.setProp(date);
return columnInfo;
}).collect(Collectors.toList());
}
/**
*
*
* @param analysisData
* @param dateRange
* @return
*/
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));
@ -281,17 +383,29 @@ public class ReportServiceImpl implements IReportService {
defectRateData.add(BigDecimal.ZERO);
}
}
// 设置图表数据
chartData.setDates(dates);
chartData.setInspectionData(inspectionData);
chartData.setDefectData(defectData);
chartData.setDefectRateData(defectRateData);
return chartData;
}
/**
*
*
* @param dateStr (: yyyy-MM-dd)
* @return
*/
private String getDayOfWeek(String dateStr) {
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE);
}
@Override
public List<IqcEfficiencyReportVo> getIncomingInspectionEfficiency(String startTime, String endTime) {
return reportMapper.selectIqcEfficiencyReport(startTime, endTime);
}
}

@ -94,4 +94,95 @@
ORDER BY biz_date, metric, type_name
</select>
<!-- 来料检验效率报表(按主表一行,聚合项目/方法/不合格项) -->
<select id="selectIqcEfficiencyReport" resultType="org.dromara.qms.domain.vo.report.IqcEfficiencyReportVo">
SELECT
m.inspection_no AS inspectionNo,
m.production_order AS purchaseOrderNo,
CONVERT(varchar, m.inspection_start_time, 23) AS inspectionDate,
CONVERT(varchar, m.inspection_start_time, 20) AS inspectionStartTime,
CONVERT(varchar, m.inspection_end_time, 20) AS inspectionEndTime,
m.material_code AS materialCode,
COALESCE(m.material_name, bmi.material_name) AS materialName,
bmi.material_spec AS materialSpec,
bmi.material_unit AS materialUnit,
m.supplier_name AS supplierName,
m.inspection_qty AS inspectionQty,
m.qualified_qty AS qualifiedQty,
m.unqualified_qty AS unqualifiedQty,
CASE
WHEN ur.review_result IS NOT NULL THEN
CASE ur.review_result
WHEN '5' THEN N'让步接收合格'
WHEN '2' THEN N'退货'
WHEN '1' THEN N'返工'
WHEN '0' THEN N'报废'
WHEN '4' THEN N'流转'
ELSE N'其他' END
ELSE
CASE m.result
WHEN '0' THEN N'合格'
WHEN '1' THEN N'不合格'
ELSE N'其他' END
END AS inspectionResult,
it.type_name AS inspectionTypeName,
su.user_name AS inspectorName,
ag.items AS items,
ag.methods AS methods,
ag.unqualified_items AS unqualifiedItems,
CASE WHEN ur.review_result = '2'
THEN N'退货原因:' + COALESCE(NULLIF(LTRIM(RTRIM(ur.remark)),''), N'退货')
ELSE NULL END AS returnReason,
LTRIM(RTRIM(CONCAT(
COALESCE(ag.unqualified_items, N''),
CASE WHEN ag.unqualified_items IS NOT NULL AND ur.review_result = '2' THEN N'' ELSE N'' END,
CASE WHEN ur.review_result = '2' THEN COALESCE(N'退货原因:' + COALESCE(NULLIF(LTRIM(RTRIM(ur.remark)),''), N'退货'), N'') ELSE N'' END
))) AS unqualifiedSummary,
m.remark AS remark
FROM qc_inspection_main m
LEFT JOIN qc_inspection_type it ON it.type_id = m.inspection_type AND ISNULL(it.del_flag,'0')='0'
LEFT JOIN sys_user su ON su.user_id = m.create_by AND ISNULL(su.del_flag,'0')='0'
LEFT JOIN base_material_info bmi ON bmi.material_code = m.material_code AND ISNULL(bmi.del_flag,'0')='0'
CROSS APPLY (
SELECT
(SELECT REPLACE(STRING_AGG(x.item_name, ';'), ';', N'')
FROM (
SELECT DISTINCT r2.item_name
FROM qc_inspection_result r2
WHERE r2.inspection_id = m.inspection_id AND ISNULL(r2.del_flag,'0')='0'
) x
) AS items,
(SELECT REPLACE(STRING_AGG(y.method_name, ';'), ';', N'')
FROM (
SELECT DISTINCT
CASE ii2.method WHEN '0' THEN N'目视' WHEN '1' THEN N'千分尺' ELSE N'其他' END AS method_name
FROM qc_inspection_result r3
INNER JOIN qc_inspection_item ii2 ON ii2.item_id = r3.item_id AND ISNULL(ii2.del_flag,'0')='0'
WHERE r3.inspection_id = m.inspection_id AND ISNULL(r3.del_flag,'0')='0'
) y
) AS methods,
(SELECT REPLACE(STRING_AGG(z.unq_item, ';'), ';', N'')
FROM (
SELECT DISTINCT
LTRIM(RTRIM(r4.item_name)) +
CASE WHEN r4.problem_detail IS NOT NULL AND LTRIM(RTRIM(r4.problem_detail)) &lt;&gt; ''
THEN N'' + r4.problem_detail + N'' ELSE N'' END AS unq_item
FROM qc_inspection_result r4
WHERE r4.inspection_id = m.inspection_id AND ISNULL(r4.del_flag,'0')='0'
AND (r4.detect_result = '1' OR (r4.problem_detail IS NOT NULL AND LTRIM(RTRIM(r4.problem_detail)) &lt;&gt; ''))
) z
) AS unqualified_items
) ag
OUTER APPLY (
SELECT TOP 1 ur.*
FROM qc_unqualified_review ur
WHERE ur.inspection_no = m.inspection_no AND ISNULL(ur.del_flag,'0')='0'
ORDER BY COALESCE(ur.review_end_time, ur.update_time, ur.review_start_time, ur.create_time) DESC, ur.review_id DESC
) ur
WHERE ISNULL(m.del_flag,'0')='0'
AND m.inspection_start_time IS NOT NULL
AND CONVERT(varchar, m.inspection_start_time, 23) BETWEEN #{startTime} AND #{endTime}
ORDER BY m.inspection_start_time DESC, m.inspection_no
</select>
</mapper>

Loading…
Cancel
Save