From 1dc09186fef021d2d7a0e493a1a838ce955c6f16 Mon Sep 17 00:00:00 2001 From: zch Date: Fri, 5 Jun 2026 09:26:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(dms):=20=E6=96=B0=E5=A2=9EOEE=E7=BB=BC?= =?UTF-8?q?=E5=90=88=E6=95=88=E7=8E=87=E6=8A=A5=E8=A1=A8=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增完整的OEE报表功能: 1. 新增OEE相关BO、VO、实体类扩展字段 2. 新增OEE报表服务接口、实现类与Mapper 3. 新增OEE报表控制器与前端接口 4. 实现Java版OEE计算引擎替代存储过程 5. 补充五态时长采集、数据聚合与报表导出功能 6. 修复反向追溯控制器括号格式问题 --- .../controller/DmsReportOeeController.java | 108 +++++ .../dms/domain/DmsReportDeviceEfficiency.java | 47 +- .../bo/DmsReportDeviceEfficiencyBo.java | 23 + .../dromara/dms/domain/bo/DmsReportOeeBo.java | 53 +++ .../vo/DmsReportDeviceEfficiencyVo.java | 50 ++ .../dms/domain/vo/DmsReportOeeKpiVo.java | 37 ++ .../domain/vo/DmsReportOeeThresholdVo.java | 29 ++ .../dms/domain/vo/DmsReportOeeTrendVo.java | 33 ++ .../dromara/dms/domain/vo/DmsReportOeeVo.java | 106 +++++ .../dms/domain/vo/MachineDayOeeData.java | 69 +++ .../dms/mapper/DmsReportOeeMapper.java | 110 +++++ .../dms/service/IDmsReportOeeService.java | 51 ++ .../service/impl/DmsReportOeeServiceImpl.java | 449 ++++++++++++++++++ .../mapper/dms/DmsReportOeeMapper.xml | 396 +++++++++++++++ .../controller/ReverseTraceController.java | 3 +- 15 files changed, 1562 insertions(+), 2 deletions(-) create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportOeeController.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportOeeBo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeKpiVo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeThresholdVo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeTrendVo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeVo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/MachineDayOeeData.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportOeeMapper.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportOeeService.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportOeeServiceImpl.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportOeeMapper.xml diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportOeeController.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportOeeController.java new file mode 100644 index 00000000..8c14d551 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportOeeController.java @@ -0,0 +1,108 @@ +package org.dromara.dms.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.Pattern; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +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.DmsReportOeeBo; +import org.dromara.dms.domain.vo.DmsReportOeeVo; +import org.dromara.dms.domain.vo.DmsReportOeeKpiVo; +import org.dromara.dms.domain.vo.DmsReportOeeThresholdVo; +import org.dromara.dms.domain.vo.DmsReportOeeTrendVo; +import org.dromara.dms.service.IDmsReportOeeService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * OEE 综合效率报表(只读看板) + * 前端访问路由地址:/report/oee + * + * 为什么独立 Controller: + * 1) 与 /dmsReportDeviceEfficiency 的 CRUD 语义不同,这是只读看板,职责单一 + * 2) 独立 RequestMapping 便于后续配置 dms:report:oee:* 权限点和网关限流 + * + * @author zch + * @date 2026-04-27 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/report/oee") +public class DmsReportOeeController extends BaseController { + + private final IDmsReportOeeService oeeService; + + /** + * 分页查询 OEE 明细(读预聚合表,毫秒级响应) + */ + // @SaCheckPermission("dms:report:oee:list") + @GetMapping("/list") + public TableDataInfo list(DmsReportOeeBo bo, PageQuery pageQuery) { + return oeeService.queryPageList(bo, pageQuery); + } + + /** + * 导出 OEE 明细 + */ + // @SaCheckPermission("dms:report:oee:export") + @Log(title = "OEE综合效率报表", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(DmsReportOeeBo bo, HttpServletResponse response) { + List list = oeeService.queryList(bo); + ExcelUtil.exportExcel(list, "OEE综合效率报表", DmsReportOeeVo.class, response); + } + + /** + * 顶部 KPI 汇总卡片(轻量聚合,一次请求返回 6 标量) + */ + @GetMapping("/kpi") + public R kpi(DmsReportOeeBo bo) { + return R.ok(oeeService.queryKpi(bo)); + } + + /** + * 趋势图数据;granularity 白名单:day/week/month,默认 day + * 白名单校验在 Service 层完成,确保 SQL 注入防护 + */ + @GetMapping("/trend") + public R> trend(DmsReportOeeBo bo, + @RequestParam(defaultValue = "day") String granularity) { + return R.ok(oeeService.queryTrend(bo, granularity)); + } + + /** + * 获取 OEE 等级阈值(红黄线),供前端趋势图参考线绘制。 + * 为什么独立端点:阈值由字典驱动,前端不再硬编码,字典修改后趋势图同步生效 + */ + @GetMapping("/thresholds") + public R thresholds() { + return R.ok(oeeService.queryThresholds()); + } + + /** + * 运维重算:@RepeatSubmit + Service 层 @Transactional 双保险 + * 为什么没有 Redisson 锁:DMS 模块未引入 redis 依赖; + * @RepeatSubmit 同 session + 同 statDate 的重复请求在 5 秒内会被拦截 + */ + // @SaCheckPermission("dms:report:oee:recalc") + @RepeatSubmit + @Log(title = "OEE重算", businessType = BusinessType.UPDATE) + @PostMapping("/recalc") + public R recalc(@RequestParam + @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", + message = "日期格式必须为 yyyy-MM-dd") + String statDate) { + oeeService.recalcByDate(statDate); + return R.ok(); + } +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/DmsReportDeviceEfficiency.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/DmsReportDeviceEfficiency.java index b9cb2f39..471775f7 100644 --- a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/DmsReportDeviceEfficiency.java +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/DmsReportDeviceEfficiency.java @@ -115,7 +115,8 @@ public class DmsReportDeviceEfficiency extends TenantEntity { private BigDecimal outputMachineHours; /** - * 合计时长(小时)= 运行时长 + 待机时长 + 故障时长 + * 合计时长(小时)= 运行 + 待机 + 故障 + 停机 + 调试(五态之和,SEMI 标准)。 + * 注意:OEE 重算后为五态总和;原始 SP 仅含三态(运行+待机+故障),历史数据口径不同。 */ private BigDecimal totalHours; @@ -139,5 +140,49 @@ public class DmsReportDeviceEfficiency extends TenantEntity { */ private String remark; + // ========== OEE 综合效率扩展字段(ALTER TABLE 追加,允许 NULL)========== + + /** + * 合格品数量(件)。OEE 合格品率 Q 的分子,来自 qc_inspection_main.qualified_qty 聚合 + */ + private BigDecimal qualifiedQty; + + /** + * 理论节拍(秒/件)。OEE 性能开动率 P 的因子,来自 prod_base_process_info.theoretical_cycle_time。 + * 允许 NULL:节拍缺失时 P 降级为 1,避免未维护主数据的设备被算作"零效率" + */ + private BigDecimal idealCycleTimeSec; + + /** + * 时间开动率 A = 运行时长 / 全时长(五态:运行+待机+故障+停机+调试) + */ + private BigDecimal availabilityRate; + + /** + * 性能开动率 P = (理论节拍 × 产量) / (运行时长 × 3600) + */ + private BigDecimal performanceRate; + + /** + * 合格品率 Q = 合格数 / 产量 + */ + private BigDecimal qualityRate; + + /** + * OEE = A × P × Q + */ + private BigDecimal oeeRate; + + /** + * TEEP = OEE × (合计时长 / 24) + */ + private BigDecimal teepRate; + + /** + * OEE 等级:red / yellow / green。由 Service 按字典阈值动态计算,不落库。 + * 阈值调整不污染历史数据,所有页面同步生效 + */ + @TableField(exist = false) + private String oeeLevel; } diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportDeviceEfficiencyBo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportDeviceEfficiencyBo.java index 9a101fb7..00b06e85 100644 --- a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportDeviceEfficiencyBo.java +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportDeviceEfficiencyBo.java @@ -161,5 +161,28 @@ public class DmsReportDeviceEfficiencyBo extends BaseEntity { @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) private String remark; + // ========== OEE 综合效率扩展字段(SP 计算,非必填)========== + + /** 合格品数量(件) */ + private BigDecimal qualifiedQty; + + /** 理论节拍(秒/件) */ + private BigDecimal idealCycleTimeSec; + + /** 时间开动率 A */ + private BigDecimal availabilityRate; + + /** 性能开动率 P */ + private BigDecimal performanceRate; + + /** 合格品率 Q */ + private BigDecimal qualityRate; + + /** OEE = A × P × Q */ + private BigDecimal oeeRate; + + /** TEEP = OEE × (合计时长/24) */ + private BigDecimal teepRate; + } diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportOeeBo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportOeeBo.java new file mode 100644 index 00000000..93b150a5 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/DmsReportOeeBo.java @@ -0,0 +1,53 @@ +package org.dromara.dms.domain.bo; + +import lombok.Data; + +import java.util.Map; + +/** + * OEE 综合效率报表查询条件 + * 为什么独立 BO:OEE 看板的查询维度(车间/班组/班次/设备/OEE等级)与老 CRUD 的 + * Add/Edit 语义不同,拆分后职责单一,避免 AddGroup/EditGroup 校验干扰查询 + * + * @author zch + * @date 2026-04-27 + */ +@Data +public class DmsReportOeeBo { + + /** 统计日期起(YYYY-MM-DD) */ + private String startDate; + + /** 统计日期止(YYYY-MM-DD) */ + private String endDate; + + /** 车间ID */ + private Long workshopId; + + /** 车间名称(模糊查询,解决前端 name 搜索参数与后端 ID 筛选不匹配的问题) */ + private String workshopName; + + /** 班组ID */ + private Long classTeamId; + + /** 班组名称(模糊查询) */ + private String classTeamName; + + /** 班次ID */ + private Long shiftId; + + /** 班次名称(模糊查询) */ + private String shiftName; + + /** 设备ID */ + private Long machineId; + + /** 设备名称(模糊查询) */ + private String machineName; + + /** OEE 等级筛选:red / yellow / green */ + private String oeeLevel; + + /** 预留扩展参数 */ + private Map params; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportDeviceEfficiencyVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportDeviceEfficiencyVo.java index 52f4b63a..caea5fb1 100644 --- a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportDeviceEfficiencyVo.java +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportDeviceEfficiencyVo.java @@ -182,5 +182,55 @@ public class DmsReportDeviceEfficiencyVo implements Serializable { @ExcelProperty(value = "备注") private String remark; + // ========== OEE 综合效率扩展字段 ========== + + /** + * 合格品数量(件) + */ + @ExcelProperty(value = "合格品数量") + private BigDecimal qualifiedQty; + + /** + * 理论节拍(秒/件) + */ + @ExcelProperty(value = "理论节拍(秒/件)") + private BigDecimal idealCycleTimeSec; + + /** + * 时间开动率 A + */ + @ExcelProperty(value = "时间开动率") + private BigDecimal availabilityRate; + + /** + * 性能开动率 P + */ + @ExcelProperty(value = "性能开动率") + private BigDecimal performanceRate; + + /** + * 合格品率 Q + */ + @ExcelProperty(value = "合格品率") + private BigDecimal qualityRate; + + /** + * OEE 综合效率 + */ + @ExcelProperty(value = "OEE综合效率") + private BigDecimal oeeRate; + + /** + * TEEP 设备综合效率 + */ + @ExcelProperty(value = "TEEP") + private BigDecimal teepRate; + + /** + * OEE 等级标签(red/yellow/green/-),不落库 + */ + @ExcelProperty(value = "OEE等级") + private String oeeLevel; + } diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeKpiVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeKpiVo.java new file mode 100644 index 00000000..6346718c --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeKpiVo.java @@ -0,0 +1,37 @@ +package org.dromara.dms.domain.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * OEE 顶部 KPI 卡片 VO(一次查询返回 6 个标量) + * 为什么不用 List:6 个标量语义独立,共用一个对象减少网络往返 + * + * @author zch + * @date 2026-04-27 + */ +@Data +public class DmsReportOeeKpiVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 设备数(去重 machine_id) */ + private Long deviceCount; + + /** 平均 OEE */ + private BigDecimal avgOee; + + /** 平均时间开动率 A */ + private BigDecimal avgA; + + /** 平均性能开动率 P */ + private BigDecimal avgP; + + /** 平均合格品率 Q */ + private BigDecimal avgQ; + + /** 红灯数(OEE 低于 red 阈值的设备数) */ + private Long redCount; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeThresholdVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeThresholdVo.java new file mode 100644 index 00000000..e7d6112d --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeThresholdVo.java @@ -0,0 +1,29 @@ +package org.dromara.dms.domain.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * OEE 阈值响应 VO(供前端趋势图参考线使用)。 + * 为什么独立 VO:阈值与 KPI/趋势/明细语义不同,拆开后接口职责单一 + * + * @author zch + * @date 2026-05-25 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DmsReportOeeThresholdVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 红灯阈值上限(OEE < red 即为红灯) */ + private BigDecimal red; + + /** 黄灯阈值上限(red <= OEE < yellow 即为黄灯) */ + private BigDecimal yellow; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeTrendVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeTrendVo.java new file mode 100644 index 00000000..1089168f --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeTrendVo.java @@ -0,0 +1,33 @@ +package org.dromara.dms.domain.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * OEE 趋势图数据点 VO(按日/周/月聚合的 A/P/Q/OEE 均值) + * + * @author zch + * @date 2026-04-27 + */ +@Data +public class DmsReportOeeTrendVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 时间粒度标签(如 "2026-04-27" / "2026-17" / "2026-04") */ + private String periodLabel; + + /** 平均时间开动率 A */ + private BigDecimal availabilityRate; + + /** 平均性能开动率 P */ + private BigDecimal performanceRate; + + /** 平均合格品率 Q */ + private BigDecimal qualityRate; + + /** 平均 OEE */ + private BigDecimal oeeRate; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeVo.java new file mode 100644 index 00000000..574e3470 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/DmsReportOeeVo.java @@ -0,0 +1,106 @@ +package org.dromara.dms.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * OEE 综合效率报表明细行 VO + * 为什么拆 3 个 VO:KPI/趋势/明细语义不同,共用一个对象会产生 undefined 噪音 + * + * @author zch + * @date 2026-04-27 + */ +@Data +@ExcelIgnoreUnannotated +public class DmsReportOeeVo implements Serializable { + + private static final long serialVersionUID = 1L; + + @ExcelProperty(value = "主键") + private Long reportId; + + @ExcelProperty(value = "统计日期") + private String statDate; + + @ExcelProperty(value = "车间") + private String workshopName; + + @ExcelProperty(value = "班组") + private String classTeamName; + + @ExcelProperty(value = "班次") + private String shiftName; + + @ExcelProperty(value = "设备") + private String machineName; + + @ExcelProperty(value = "车间ID") + private Long workshopId; + + @ExcelProperty(value = "班组ID") + private Long classTeamId; + + @ExcelProperty(value = "班次ID") + private Long shiftId; + + @ExcelProperty(value = "设备ID") + private Long machineId; + + @ExcelProperty(value = "产量(件)") + private BigDecimal outputQty; + + @ExcelProperty(value = "合格数(件)") + private BigDecimal qualifiedQty; + + @ExcelProperty(value = "理论节拍(秒/件)") + private BigDecimal idealCycleTimeSec; + + @ExcelProperty(value = "运行时长(h)") + private BigDecimal runHours; + + @ExcelProperty(value = "待机时长(h)") + private BigDecimal standbyHours; + + @ExcelProperty(value = "故障时长(h)") + private BigDecimal faultHours; + + @ExcelProperty(value = "关机时长(h)") + private BigDecimal shutdownHours; + + @ExcelProperty(value = "调试时长(h)") + private BigDecimal debugHours; + + @ExcelProperty(value = "合计时长(h)") + private BigDecimal totalHours; + + @ExcelProperty(value = "开机率") + private BigDecimal uptimeRate; + + @ExcelProperty(value = "综合效率") + private BigDecimal overallEfficiency; + + @ExcelProperty(value = "运行效率") + private BigDecimal runtimeEfficiency; + + @ExcelProperty(value = "时间开动率(A)") + private BigDecimal availabilityRate; + + @ExcelProperty(value = "性能开动率(P)") + private BigDecimal performanceRate; + + @ExcelProperty(value = "合格品率(Q)") + private BigDecimal qualityRate; + + @ExcelProperty(value = "OEE") + private BigDecimal oeeRate; + + @ExcelProperty(value = "TEEP") + private BigDecimal teepRate; + + @ExcelProperty(value = "OEE等级") + private String oeeLevel; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/MachineDayOeeData.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/MachineDayOeeData.java new file mode 100644 index 00000000..4060b903 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/MachineDayOeeData.java @@ -0,0 +1,69 @@ +package org.dromara.dms.domain.vo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.math.BigDecimal; + +/** + * OEE 计算引擎内部 DTO:单个设备单日的数据容器。 + * 为什么用独立 DTO 而非 Map:类型安全 + 字段可追踪 + 避免魔法字符串。 + * 数据采集层逐步骤填充,计算层统一消费。 + * + * @author zch + * @date 2026-04-30 + */ +@Data +@Accessors(chain = true) +public class MachineDayOeeData { + + // ========== 维度标识 ========== + /** 设备ID */ + private Long machineId; + /** 车间ID */ + private Long workshopId; + /** 班组ID(暂不可用,plan_slice 未实现) */ + private Long classTeamId; + /** 班次ID(暂不可用,plan_slice 未实现) */ + private Long shiftId; + + // ========== 冗余名称(落库直接取用,免后续 JOIN)========== + private String machineName; + private String workshopName; + private String classTeamName; + private String shiftName; + + // ========== 五态时长(小时)========== + private BigDecimal runHours = BigDecimal.ZERO; + private BigDecimal standbyHours = BigDecimal.ZERO; + private BigDecimal faultHours = BigDecimal.ZERO; + private BigDecimal shutdownHours = BigDecimal.ZERO; + private BigDecimal debugHours = BigDecimal.ZERO; + + // ========== 产量数据 ========== + /** 产出台数(来自 prod_output_scan_info) */ + private BigDecimal outputQty = BigDecimal.ZERO; + /** 合格品数量(来自 qc_inspection_main,经 plan_detail → plan_info 链路归属到机台) */ + private BigDecimal qualifiedQty = BigDecimal.ZERO; + + // ========== 工序参数 ========== + /** 工序标准机时(小时/件),来自 prod_base_process_info.production_time / 3600 */ + private BigDecimal processStdMachineHours = BigDecimal.ZERO; + /** 理论生产节拍(秒/件),来自 prod_base_process_info.theoretical_cycle_time。 + * NULL 表示未维护,后续 P 值降级为 1 */ + private BigDecimal idealCycleTimeSec; + + // ========== 中间计算列(与原 SP 字段对齐)========== + private BigDecimal totalHours = BigDecimal.ZERO; + private BigDecimal outputMachineHours = BigDecimal.ZERO; + private BigDecimal uptimeRate = BigDecimal.ZERO; + private BigDecimal overallEfficiency = BigDecimal.ZERO; + private BigDecimal runtimeEfficiency = BigDecimal.ZERO; + + // ========== OEE 核心指标 ========== + private BigDecimal availabilityRate = BigDecimal.ZERO; + private BigDecimal performanceRate = BigDecimal.ZERO; + private BigDecimal qualityRate = BigDecimal.ZERO; + private BigDecimal oeeRate = BigDecimal.ZERO; + private BigDecimal teepRate = BigDecimal.ZERO; +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportOeeMapper.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportOeeMapper.java new file mode 100644 index 00000000..3bdd77a6 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportOeeMapper.java @@ -0,0 +1,110 @@ +package org.dromara.dms.mapper; + +import com.baomidou.mybatisplus.annotation.InterceptorIgnore; +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.DmsReportOeeBo; +import org.dromara.dms.domain.vo.DmsReportOeeVo; +import org.dromara.dms.domain.vo.DmsReportOeeKpiVo; +import org.dromara.dms.domain.vo.DmsReportOeeTrendVo; +import org.dromara.dms.domain.vo.MachineDayOeeData; + +import java.util.List; +import java.util.Map; + +/** + * OEE 综合效率报表 Mapper + * 为什么用原始 MyBatis 而非 BaseMapperPlus: + * KPI/趋势查询涉及聚合函数、CASE WHEN、GROUP BY 粒度切换, + * MPJ 表达力不够,手写 XML 更清晰可控 + * + * @author zch + * @date 2026-04-27 + */ +public interface DmsReportOeeMapper { + + /** + * 分页查询 OEE 明细(读预聚合表,毫秒级响应) + */ + IPage selectOeePage(Page page, @Param("bo") DmsReportOeeBo bo); + + /** + * 导出 OEE 明细(不分页) + */ + List selectOeeList(@Param("bo") DmsReportOeeBo bo); + + /** + * KPI 卡片聚合(一次扫描 6 标量) + */ + DmsReportOeeKpiVo selectOeeKpi(@Param("bo") DmsReportOeeBo bo); + + /** + * 趋势图数据(按日/周/月聚合) + * @param granularity 白名单值:day/week/month,Service 层已校验 + */ + List selectOeeTrend(@Param("bo") DmsReportOeeBo bo, + @Param("granularity") String granularity); + + /** + * 检查 OEE 扩展列是否已完成数据库升级。 + * 为什么放在 Mapper:SQL Server 元数据检查必须靠数据库完成,Service 只负责 Fail Fast。 + * 为什么跳过租户拦截:sys.columns 是 SQL Server 系统表,不存在 tenant_id。 + */ + @InterceptorIgnore(tenantLine = "true") + List selectMissingOeeColumns(); + + /** + * 调用存储过程重算指定日期的 OEE(已废弃,保留向后兼容) + * @deprecated 替换为 Java 计算引擎 recalcByDate() + */ + @Deprecated + void callSpCalcOee(@Param("statDate") String statDate); + + // ========== OEE 数据采集查询(方案 C:XML 数据采集 + Java 计算引擎)========== + + /** + * 查询设备五态时长(LEAD 窗口函数),返回每设备每日的状态小时数 + * 结束边界在 SQL 内用 DATEADD(DAY, 1, #{start}) 计算,无需传 end + */ + List> selectStateHours(@Param("tenantId") String tenantId, + @Param("start") String start); + + /** + * 查询产出扫描台数,按机台聚合 + */ + List> selectOutputQty(@Param("tenantId") String tenantId, + @Param("start") String start, + @Param("end") String end); + + /** + * 查询工序参数:理论节拍 + 标准机时,按机台聚合 + */ + List> selectProcessInfo(@Param("tenantId") String tenantId); + + /** + * 查询合格品数量,按机台聚合(需处理分表——通过 tableSuffix 指定车间后缀) + */ + List> selectQualifiedQtyByWorkshop(@Param("tenantId") String tenantId, + @Param("start") String start, + @Param("end") String end, + @Param("tableSuffix") String tableSuffix); + + /** + * 查询所有激活车间,用于解析 qc_inspection_main.workshop → workshop_id + */ + List> selectActiveWorkshops(); + + /** + * 删除指定日期的 OEE 报表数据(幂等:重算前清理) + */ + void deleteOeeReportByDate(@Param("tenantId") String tenantId, + @Param("statDate") String statDate); + + /** + * 批量插入 OEE 报表数据(含 7 个 OEE 列) + */ + void insertOeeReport(@Param("list") List list, + @Param("tenantId") String tenantId, + @Param("statDate") String statDate); +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportOeeService.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportOeeService.java new file mode 100644 index 00000000..84791b78 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportOeeService.java @@ -0,0 +1,51 @@ +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.DmsReportOeeBo; +import org.dromara.dms.domain.vo.DmsReportOeeVo; +import org.dromara.dms.domain.vo.DmsReportOeeKpiVo; +import org.dromara.dms.domain.vo.DmsReportOeeTrendVo; +import org.dromara.dms.domain.vo.DmsReportOeeThresholdVo; + +import java.util.List; + +/** + * OEE 综合效率报表服务接口 + * + * @author zch + * @date 2026-04-27 + */ +public interface IDmsReportOeeService { + + /** + * 分页查询 OEE 明细(含 OEE 等级标签打标) + */ + TableDataInfo queryPageList(DmsReportOeeBo bo, PageQuery pageQuery); + + /** + * 查询 OEE 明细列表(导出用,不分页) + */ + List queryList(DmsReportOeeBo bo); + + /** + * KPI 卡片聚合查询(设备数/平均OEE/平均A/平均P/平均Q/红灯数) + */ + DmsReportOeeKpiVo queryKpi(DmsReportOeeBo bo); + + /** + * 趋势图数据查询(按日/周/月聚合 A/P/Q/OEE 均值) + * @param granularity 白名单值:day/week/month + */ + List queryTrend(DmsReportOeeBo bo, String granularity); + + /** + * 查询 OEE 等级阈值(从字典 dms_oee_level 动态获取) + */ + DmsReportOeeThresholdVo queryThresholds(); + + /** + * 调用存储过程重算指定日期的 OEE 数据 + */ + void recalcByDate(String statDate); +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportOeeServiceImpl.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportOeeServiceImpl.java new file mode 100644 index 00000000..3f25ce98 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportOeeServiceImpl.java @@ -0,0 +1,449 @@ +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 lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.dms.domain.bo.DmsReportOeeBo; +import org.dromara.dms.domain.vo.DmsReportOeeVo; +import org.dromara.dms.domain.vo.DmsReportOeeKpiVo; +import org.dromara.dms.domain.vo.DmsReportOeeThresholdVo; +import org.dromara.dms.domain.vo.DmsReportOeeTrendVo; +import org.dromara.dms.domain.vo.MachineDayOeeData; +import org.dromara.dms.mapper.DmsReportOeeMapper; +import org.dromara.dms.service.IDmsReportOeeService; +import org.dromara.system.api.RemoteDictService; +import org.dromara.system.api.domain.vo.RemoteDictDataVo; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; + +/** + * OEE 综合效率报表服务实现(方案 C:XML 数据采集 + Java 计算引擎) + * + * 架构分层: + * 1. XML 数据采集层 — LEAD 窗口函数、GROUP BY 聚合、跨表 JOIN + * 2. Java 计算引擎 — A/P/Q/OEE/TEEP 公式 + 零值保护 + 降级策略 + * 3. 持久化层 — 批量 DELETE + INSERT + * + * @author zch + * @date 2026-04-27 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class DmsReportOeeServiceImpl implements IDmsReportOeeService { + + private final DmsReportOeeMapper oeeMapper; + private final RemoteDictService remoteDictService; + + /** OEE 等级字典类型 */ + private static final String DICT_TYPE_OEE_LEVEL = "dms_oee_level"; + + /** 默认阈值:红 < 0.6 / 黄 0.6~0.85 / 绿 >= 0.85 */ + private static final BigDecimal DEFAULT_RED_MAX = new BigDecimal("0.6"); + private static final BigDecimal DEFAULT_YELLOW_MAX = new BigDecimal("0.85"); + + /** 一天的小时数 */ + private static final BigDecimal HOURS_PER_DAY = new BigDecimal("24"); + + /** 小时转秒因子 */ + private static final BigDecimal SECONDS_PER_HOUR = new BigDecimal("3600"); + + // ==================== 读取方法(不变) ==================== + + @Override + public TableDataInfo queryPageList(DmsReportOeeBo bo, PageQuery pageQuery) { + ensureOeeSchemaReady(); + Page page = pageQuery.build(); + IPage iPage = oeeMapper.selectOeePage(page, bo); + attachOeeLevel(iPage.getRecords(), bo.getOeeLevel()); + return TableDataInfo.build(iPage); + } + + @Override + public List queryList(DmsReportOeeBo bo) { + ensureOeeSchemaReady(); + List list = oeeMapper.selectOeeList(bo); + attachOeeLevel(list, bo.getOeeLevel()); + return list; + } + + @Override + public DmsReportOeeKpiVo queryKpi(DmsReportOeeBo bo) { + ensureOeeSchemaReady(); + return oeeMapper.selectOeeKpi(bo); + } + + @Override + public List queryTrend(DmsReportOeeBo bo, String granularity) { + ensureOeeSchemaReady(); + String g = switch (granularity == null ? "day" : granularity) { + case "week" -> "week"; + case "month" -> "month"; + default -> "day"; + }; + return oeeMapper.selectOeeTrend(bo, g); + } + + // ==================== OEE 计算引擎(方案 C 核心) ==================== + + /** + * 重算指定日期的 OEE 数据。 + * 为什么不用 SP:SP 部署依赖 DBA,修改需走变更流程;Java 计算引擎可单测、可版本管理。 + * 为什么用 @Transactional:DELETE + INSERT 需在同一事务内完成,保证幂等。 + */ + @Override + public DmsReportOeeThresholdVo queryThresholds() { + BigDecimal red = getDictThreshold("red", DEFAULT_RED_MAX); + BigDecimal yellow = getDictThreshold("yellow", DEFAULT_YELLOW_MAX); + return new DmsReportOeeThresholdVo(red, yellow); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void recalcByDate(String statDate) { + ensureOeeSchemaReady(); + // 防重提交由 Controller 层 @RepeatSubmit 保证(会话级去重,5s 窗口) + String tenantId = LoginHelper.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + throw new ServiceException("无法获取当前租户ID,请检查登录状态"); + } + + // 构造时间窗:statDate 零点 → 次日零点 + String start = statDate + " 00:00:00"; + String end = statDate + " 23:59:59.999"; + + // ===== 第 1 步:采集数据 ===== + Map dataMap = collectStateHours(tenantId, start); + collectOutputQty(dataMap, tenantId, start, end); + collectProcessInfo(dataMap, tenantId); + collectQualifiedQty(dataMap, tenantId, start, end); + resolveMachineNames(dataMap); + + if (dataMap.isEmpty()) { + return; // 无设备活动数据,不写入空行 + } + + // ===== 第 2 步:计算 OEE ===== + computeOee(dataMap); + + // ===== 第 3 步:持久化(DELETE + INSERT 原子操作) ===== + oeeMapper.deleteOeeReportByDate(tenantId, statDate); + oeeMapper.insertOeeReport(new ArrayList<>(dataMap.values()), tenantId, statDate); + } + + /** + * OEE 依赖 dms_report_device_efficiency 的扩展列。 + * 数据库未升级时主动阻断,比让 SQL Server 抛"列名无效"更利于现场定位。 + */ + private void ensureOeeSchemaReady() { + List missingColumns = oeeMapper.selectMissingOeeColumns(); + if (missingColumns != null && !missingColumns.isEmpty()) { + throw new ServiceException("OEE报表数据库结构未升级,dms_report_device_efficiency 缺少字段:" + + String.join("、", missingColumns) + + "。请先执行 hwmom/sql/dms_oee_upgrade.sql 后再访问 OEE 看板。"); + } + } + + // ==================== 数据采集步骤 ==================== + + /** + * 采集设备五态时长(XML 中 LEAD 窗口函数计算) + */ + private Map collectStateHours(String tenantId, String start) { + Map dataMap = new LinkedHashMap<>(); + List> rows = oeeMapper.selectStateHours(tenantId, start); + + for (Map row : rows) { + Long machineId = toLong(row.get("machine_id")); + MachineDayOeeData d = dataMap.computeIfAbsent(machineId, k -> new MachineDayOeeData()); + d.setMachineId(machineId); + d.setWorkshopId(toLong(row.get("workshop_id"))); + d.setRunHours(toDecimal(row.get("run_hours"))); + d.setStandbyHours(toDecimal(row.get("standby_hours"))); + d.setFaultHours(toDecimal(row.get("fault_hours"))); + d.setShutdownHours(toDecimal(row.get("shutdown_hours"))); + d.setDebugHours(toDecimal(row.get("debug_hours"))); + } + return dataMap; + } + + /** + * 采集产出扫描台数(OEE 标准口径:产出而非投料) + */ + private void collectOutputQty(Map dataMap, + String tenantId, String start, String end) { + List> rows = oeeMapper.selectOutputQty(tenantId, start, end); + for (Map row : rows) { + Long machineId = toLong(row.get("machine_id")); + MachineDayOeeData d = dataMap.computeIfAbsent(machineId, k -> new MachineDayOeeData()); + d.setMachineId(machineId); + d.setOutputQty(toDecimal(row.get("output_qty"))); + } + } + + /** + * 采集工序参数:理论节拍 + 标准机时 + */ + private void collectProcessInfo(Map dataMap, String tenantId) { + List> rows = oeeMapper.selectProcessInfo(tenantId); + for (Map row : rows) { + Long machineId = toLong(row.get("machine_id")); + MachineDayOeeData d = dataMap.computeIfAbsent(machineId, k -> new MachineDayOeeData()); + d.setMachineId(machineId); + // 理论节拍允许 NULL(未维护主数据),后续 P 降级为 1 + Object cycleTime = row.get("ideal_cycle_time_sec"); + d.setIdealCycleTimeSec(cycleTime == null ? null : toDecimal(cycleTime)); + d.setProcessStdMachineHours(toDecimal(row.get("process_std_machine_hours"))); + } + } + + /** + * 采集合格品数量(QC→机台 归属链路,处理分表) + * + * 为什么在 Java 层处理分表循环而非 XML 中 UNION: + * 车间数量运行时可变,UNION 需要硬编码所有车间, + * Java 循环 + 参数化 tableSuffix 更灵活 + */ + private void collectQualifiedQty(Map dataMap, + String tenantId, String start, String end) { + // 第 1 步:获取所有激活车间的 ID 列表 + List> workshops = oeeMapper.selectActiveWorkshops(); + if (workshops.isEmpty()) { + return; + } + + Set tableSuffixes = new LinkedHashSet<>(); + for (Map ws : workshops) { + Object id = ws.get("workshop_id"); + if (id != null) { + tableSuffixes.add(String.valueOf(toLong(id))); + } + } + + // 第 2 步:遍历每个车间分表,查询合格品 → 机台映射 + for (String suffix : tableSuffixes) { + // 为什么显式校验:tableSuffix 来自数据库但作为 SQL 动态表名拼接, + // 加白名单确保只允许纯数字 suffix,防止注入风险 + if (!suffix.matches("^\\d+$")) { + log.warn("非法的分表后缀已拒绝: {}", suffix); + continue; + } + try { + List> rows = + oeeMapper.selectQualifiedQtyByWorkshop(tenantId, start, end, suffix); + for (Map row : rows) { + Long machineId = toLong(row.get("machine_id")); + if (machineId != null) { + MachineDayOeeData d = dataMap.computeIfAbsent(machineId, k -> new MachineDayOeeData()); + d.setMachineId(machineId); + BigDecimal qty = toDecimal(row.get("qualified_qty")); + // 多车间同一机台的情况极端少见,使用累加 + d.setQualifiedQty(d.getQualifiedQty().add(qty)); + } + } + } catch (Exception e) { + // 区分"表不存在"(新车间未建表,可跳过)与"运行时异常"(不可跳过) + String msg = e.getMessage(); + if (msg != null && (msg.contains("Invalid object name") + || msg.contains("不是有效的对象名") + || msg.contains("doesn't exist"))) { + log.info("分表不存在,跳过车间 suffix={}:{}", suffix, msg); + } else { + log.error("合格品查询异常 suffix={} date={}", suffix, start, e); + throw new ServiceException("OEE数据采集失败:合格品查询异常,车间=" + suffix + + "," + (msg != null ? msg : "")); + } + } + } + } + + /** + * 补充设备/车间名称(来自 prod_base_machine_info) + * 为什么单独步骤:名称数据量大时不应混入聚合查询,按需单查更清晰 + */ + private void resolveMachineNames(Map dataMap) { + // TODO: 批量查询 prod_base_machine_info + prod_base_workshop_info 获取名称 + // 当前占位:名称字段留空,前端可根据 machineId 做二次查询或显示 "—" + // 后续通过 DmsBaseMachineInfoMapper 批量补充 + } + + // ==================== OEE 计算引擎 ==================== + + /** + * 遍历每台设备,计算 OEE 指标。 + * 为什么公式中包含多重 NULL/零值保护: + * 制造业数据质量参差不齐——未维护节拍、未质检、产量为零都是常态, + * 计算引擎需优雅降级而非除零报错 + */ + private void computeOee(Map dataMap) { + for (MachineDayOeeData d : dataMap.values()) { + // 五态总时长(用于 A 的分母和 TEEP 计算) + // 用户确认:SEMI 标准,分母包含全部五种状态 + BigDecimal allHours = d.getRunHours() + .add(d.getStandbyHours()) + .add(d.getFaultHours()) + .add(d.getShutdownHours()) + .add(d.getDebugHours()); + d.setTotalHours(allHours); + + // 产出机时 = 产量 × 工序标准机时 + d.setOutputMachineHours(d.getOutputQty().multiply(d.getProcessStdMachineHours())); + + // 开机率 = (运行 + 调试) / 全时长 + BigDecimal uptimeNumerator = d.getRunHours().add(d.getDebugHours()); + d.setUptimeRate(safeDivide(uptimeNumerator, allHours)); + + // 综合效率 = 产出机时 / 全时长 + d.setOverallEfficiency(safeDivide(d.getOutputMachineHours(), allHours)); + + // 运行效率 = 产出机时 / 运行时长 + d.setRuntimeEfficiency(safeDivide(d.getOutputMachineHours(), d.getRunHours())); + + // ===== OEE 核心指标 ===== + + // A = 运行时长 / 全时长(含待机、故障、停机、调试) + d.setAvailabilityRate(safeDivide(d.getRunHours(), allHours)); + + // P = (节拍 × 产量) / (运行秒数) + // 节拍缺失或运行时长为 0 时降级为 1 + d.setPerformanceRate(computePerformance(d)); + + // Q = 合格数 / 产量 + // 产量为零时 Q=0,无合格记录时 Q=1(免检工序不惩罚) + d.setQualityRate(computeQuality(d)); + + // OEE = A × P × Q + d.setOeeRate(d.getAvailabilityRate() + .multiply(d.getPerformanceRate()) + .multiply(d.getQualityRate())); + + // TEEP = OEE × (全时长 / 24) + d.setTeepRate(d.getOeeRate() + .multiply(safeDivide(allHours, HOURS_PER_DAY))); + } + } + + /** + * P 值计算(性能开动率) + * 公式:P = (理论节拍 × 产量) / (运行时长 × 3600) + * 降级策略:节拍 NULL 或运行时长为 0 → P = 1(不惩罚未维护主数据的设备) + */ + private BigDecimal computePerformance(MachineDayOeeData d) { + if (d.getIdealCycleTimeSec() == null + || d.getIdealCycleTimeSec().compareTo(BigDecimal.ZERO) <= 0 + || d.getRunHours().compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ONE; // 降级:无节拍数据或未运行 + } + // (节拍 × 产量) / (运行小时 × 3600) + BigDecimal numerator = d.getIdealCycleTimeSec().multiply(d.getOutputQty()); + BigDecimal denominator = d.getRunHours().multiply(SECONDS_PER_HOUR); + return safeDivide(numerator, denominator); + } + + /** + * Q 值计算(合格品率) + * 降级策略:产量为零 → Q = 0(无产出不谈合格);无合格记录 → Q = 1(免检工序不惩罚) + */ + private BigDecimal computeQuality(MachineDayOeeData d) { + if (d.getOutputQty().compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO; + } + // 无合格数记录 → 免检,Q = 1(配置项 dms_check_skip_process 未来可驱动此策略) + if (d.getQualifiedQty().compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ONE; + } + return safeDivide(d.getQualifiedQty(), d.getOutputQty()); + } + + /** + * 安全除法:分母为 0 或 null 时返回 0 + */ + private BigDecimal safeDivide(BigDecimal numerator, BigDecimal denominator) { + if (denominator == null || denominator.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO; + } + if (numerator == null) { + return BigDecimal.ZERO; + } + return numerator.divide(denominator, 8, RoundingMode.HALF_UP); + } + + // ==================== 类型转换工具 ==================== + + private BigDecimal toDecimal(Object val) { + if (val == null) { + return BigDecimal.ZERO; + } + if (val instanceof BigDecimal bd) { + return bd; + } + try { + return new BigDecimal(val.toString()); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } + + private Long toLong(Object val) { + if (val == null) { + return null; + } + if (val instanceof Long l) { + return l; + } + if (val instanceof Integer i) { + return i.longValue(); + } + try { + return Long.valueOf(val.toString()); + } catch (NumberFormatException e) { + return null; + } + } + + // ==================== OEE 等级打标逻辑(不变) ==================== + + private void attachOeeLevel(List rows, String filterLevel) { + BigDecimal redMax = getDictThreshold("red", DEFAULT_RED_MAX); + BigDecimal yellowMax = getDictThreshold("yellow", DEFAULT_YELLOW_MAX); + + rows.removeIf(vo -> { + if (vo.getOeeRate() == null) { + vo.setOeeLevel("-"); + } else if (vo.getOeeRate().compareTo(redMax) < 0) { + vo.setOeeLevel("red"); + } else if (vo.getOeeRate().compareTo(yellowMax) < 0) { + vo.setOeeLevel("yellow"); + } else { + vo.setOeeLevel("green"); + } + return StringUtils.isNotBlank(filterLevel) && !filterLevel.equals(vo.getOeeLevel()); + }); + } + + private BigDecimal getDictThreshold(String label, BigDecimal defaultValue) { + try { + List dictList = remoteDictService.selectDictDataByType(DICT_TYPE_OEE_LEVEL); + if (dictList != null) { + for (RemoteDictDataVo dict : dictList) { + if (label.equals(dict.getDictLabel()) && StringUtils.isNotBlank(dict.getDictValue())) { + return new BigDecimal(dict.getDictValue()); + } + } + } + } catch (Exception e) { + // 字典服务不可用时降级到默认阈值,不阻断业务 + } + return defaultValue; + } +} diff --git a/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportOeeMapper.xml b/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportOeeMapper.xml new file mode 100644 index 00000000..5edc80d7 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportOeeMapper.xml @@ -0,0 +1,396 @@ + + + + + + + + + + AND t.stat_date BETWEEN #{bo.startDate} AND #{bo.endDate} + + + AND t.workshop_id = #{bo.workshopId} + + + AND t.workshop_name LIKE '%' + #{bo.workshopName} + '%' + + + AND t.class_team_id = #{bo.classTeamId} + + + AND t.class_team_name LIKE '%' + #{bo.classTeamName} + '%' + + + AND t.shift_id = #{bo.shiftId} + + + AND t.shift_name LIKE '%' + #{bo.shiftName} + '%' + + + AND t.machine_id = #{bo.machineId} + + + AND t.machine_name LIKE '%' + #{bo.machineName} + '%' + + + + + + + + + + + + + + + + + + + + + + {call sp_calc_dms_report_device_efficiency(#{statDate,jdbcType=DATE})} + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM dms_report_device_efficiency + WHERE stat_date = #{statDate} + AND tenant_id = #{tenantId} + + + + + INSERT INTO dms_report_device_efficiency ( + tenant_id, stat_date, workshop_id, class_team_id, shift_id, machine_id, + workshop_name, class_team_name, shift_name, machine_name, + output_qty, process_std_machine_hours, + debug_hours, run_hours, standby_hours, fault_hours, shutdown_hours, + output_machine_hours, total_hours, uptime_rate, overall_efficiency, runtime_efficiency, + qualified_qty, ideal_cycle_time_sec, + availability_rate, performance_rate, quality_rate, oee_rate, teep_rate, + create_time + ) VALUES + + ( + #{tenantId}, #{statDate}, + #{d.workshopId}, #{d.classTeamId}, #{d.shiftId}, #{d.machineId}, + #{d.workshopName}, #{d.classTeamName}, #{d.shiftName}, #{d.machineName}, + #{d.outputQty}, #{d.processStdMachineHours}, + #{d.debugHours}, #{d.runHours}, #{d.standbyHours}, #{d.faultHours}, #{d.shutdownHours}, + #{d.outputMachineHours}, #{d.totalHours}, #{d.uptimeRate}, #{d.overallEfficiency}, #{d.runtimeEfficiency}, + #{d.qualifiedQty}, #{d.idealCycleTimeSec}, + #{d.availabilityRate}, #{d.performanceRate}, #{d.qualityRate}, #{d.oeeRate}, #{d.teepRate}, + GETDATE() + ) + + + + diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ReverseTraceController.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ReverseTraceController.java index 2a478b65..9bf6bf37 100644 --- a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ReverseTraceController.java +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ReverseTraceController.java @@ -74,7 +74,8 @@ public class ReverseTraceController extends BaseController { @GetMapping("/workOrder/materialInputs") public R> listMaterialInputs( @RequestParam Long planId, - @RequestParam String industryType) { + @RequestParam String industryType) + { List list = reverseTraceService.listMaterialInputs(planId, industryType); return R.ok(list); }