diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ProdMixTraceReportController.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ProdMixTraceReportController.java new file mode 100644 index 00000000..4d911807 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/controller/ProdMixTraceReportController.java @@ -0,0 +1,107 @@ +package org.dromara.mes.controller; + +import jakarta.servlet.http.HttpServletResponse; +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.mes.domain.vo.MixTraceDetailVo; +import org.dromara.mes.domain.vo.MixTraceListVo; +import org.dromara.mes.domain.vo.MixTraceSpcResultVo; +import org.dromara.mes.domain.vo.MixTraceSpcSampleVo; +import org.dromara.mes.service.IProdMixTraceReportService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 密炼追溯报表Controller + * 对应图5(追溯列表)、图9(追溯详情)、图4(曲线)、图6(SPC样本)、图7/8/10(SPC图表) + * + * @author Yinq + * @date 2026-02-14 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/mes/mixTrace") +public class ProdMixTraceReportController extends BaseController { + + private final IProdMixTraceReportService mixTraceReportService; + + /** + * 追溯列表(图5)- 分页 + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/list") + public TableDataInfo list(@RequestParam(required = false) Map params, + PageQuery pageQuery) { + return mixTraceReportService.queryTraceList(params, pageQuery); + } + + /** + * 追溯列表导出 + */ + //@SaCheckPermission("mes:mixTrace:export") + @Log(title = "密炼追溯报表导出", businessType = BusinessType.EXPORT) + @RepeatSubmit() + @PostMapping("/export") + public void export(@RequestParam(required = false) Map params, + HttpServletResponse response) { + List list = mixTraceReportService.queryTraceList(params); + ExcelUtil.exportExcel(list, "密炼追溯报表", MixTraceListVo.class, response); + } + + /** + * 追溯详情(图9)- 基础信息 + 称量 + 混炼 + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/detail/{recipeId}") + public R detail(@PathVariable Long recipeId) { + return R.ok(mixTraceReportService.queryTraceDetail(recipeId)); + } + + /** + * SPC样本列表(图6)- 分页 + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/spc/samples") + public TableDataInfo spcSamples(@RequestParam(required = false) Map params, + PageQuery pageQuery) { + return mixTraceReportService.querySpcSamples(params, pageQuery); + } + + /** + * SPC能力分析(图7)- 直方图 + 正态拟合 + Cp/Cpk/Ppk + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/spc/capability") + public R spcCapability(@RequestParam(required = false) Map params) { + return R.ok(mixTraceReportService.calculateSpcCapability(params)); + } + + /** + * SPC运行图(图8)- 点图 + USL/LSL/Target + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/spc/runChart") + public R spcRunChart(@RequestParam(required = false) Map params) { + return R.ok(mixTraceReportService.calculateSpcRunChart(params)); + } + + /** + * SPC均值极差图(图10)- Xbar-R + */ + //@SaCheckPermission("mes:mixTrace:list") + @GetMapping("/spc/xbarR") + public R spcXbarR(@RequestParam(required = false) Map params) { + return R.ok(mixTraceReportService.calculateSpcXbarR(params)); + } +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceDetailVo.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceDetailVo.java new file mode 100644 index 00000000..a3b9a604 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceDetailVo.java @@ -0,0 +1,30 @@ +package org.dromara.mes.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 密炼追溯详情VO(图9 - 追溯详情) + * 包含配方基础信息 + 称量明细 + 混炼明细 + * + * @author Yinq + * @date 2026-02-14 + */ +@Data +public class MixTraceDetailVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 配方基础信息(含关联的机台/物料名称) */ + private MixTraceListVo recipeInfo; + + /** 称量明细列表(按 weight_seq 排序) */ + private List weightList; + + /** 混炼明细列表(按 mix_id 排序) */ + private List mixingList; +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceListVo.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceListVo.java new file mode 100644 index 00000000..625f7f97 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceListVo.java @@ -0,0 +1,106 @@ +package org.dromara.mes.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 密炼追溯列表VO(图5 - 追溯主列表) + * 聚合配方基础信息 + 机台名称 + 物料名称 + 称量/混炼工步数 + * + * @author Yinq + * @date 2026-02-14 + */ +@Data +@ExcelIgnoreUnannotated +public class MixTraceListVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 配方ID */ + @ExcelProperty(value = "配方ID") + private Long recipeId; + + /** 配方编码 */ + @ExcelProperty(value = "配方编码") + private String recipeCode; + + /** 机台ID */ + private Long machineId; + + /** 机台名称(关联查询) */ + @ExcelProperty(value = "机台名称") + private String machineName; + + /** 物料ID */ + private Long materialId; + + /** 物料名称(关联查询) */ + @ExcelProperty(value = "物料名称") + private String materialName; + + /** 版本号 */ + @ExcelProperty(value = "版本号") + private Long edtCode; + + /** 用户版本 */ + @ExcelProperty(value = "用户版本") + private String userEdtCode; + + /** 配方状态(1正用 0停用) */ + @ExcelProperty(value = "配方状态") + private String recipeState; + + /** 配方类型 */ + private Long recipeType; + + /** 配方类型编码 */ + @ExcelProperty(value = "配方类型编码") + private String recipeTypecode; + + /** 胶种类型 */ + @ExcelProperty(value = "胶种类型") + private String rubType; + + /** 胶种编码 */ + @ExcelProperty(value = "胶种编码") + private String rubTypecode; + + /** 配方重量 */ + @ExcelProperty(value = "配方重量") + private BigDecimal totalWeight; + + /** 填充系数 */ + @ExcelProperty(value = "填充系数") + private BigDecimal fillCoefficient; + + /** 操作者 */ + @ExcelProperty(value = "操作者") + private String operCode; + + /** 审核标志 */ + @ExcelProperty(value = "审核标志") + private String auditFlag; + + /** 完成时间 */ + @ExcelProperty(value = "完成时间") + private Long doneTime; + + /** 创建时间(生产时间) */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** 称量工步数 */ + @ExcelProperty(value = "称量工步数") + private Integer weightCount; + + /** 混炼工步数 */ + @ExcelProperty(value = "混炼工步数") + private Integer mixingCount; +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcResultVo.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcResultVo.java new file mode 100644 index 00000000..dc3106ad --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcResultVo.java @@ -0,0 +1,130 @@ +package org.dromara.mes.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 密炼SPC统计结果VO(图7/8/10 - 能力图/运行图/Xbar-R图) + * 包含基本统计、能力指数、控制图数据 + * + * @author Yinq + * @date 2026-02-14 + */ +@Data +public class MixTraceSpcResultVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 分析参数名称(如:mixingTemp、mixingTime等) */ + private String paramName; + + /** 分析参数中文名 */ + private String paramLabel; + + // ==================== 基本统计 ==================== + + /** 样本数 */ + private Integer sampleCount; + + /** 均值 */ + private BigDecimal mean; + + /** 标准差(样本标准差) */ + private BigDecimal sigma; + + /** 最小值 */ + private BigDecimal minValue; + + /** 最大值 */ + private BigDecimal maxValue; + + // ==================== 规格限(来自设定值) ==================== + + /** 规格上限 USL */ + private BigDecimal usl; + + /** 规格下限 LSL */ + private BigDecimal lsl; + + /** 目标值 Target */ + private BigDecimal target; + + // ==================== 能力指数(图7) ==================== + + /** 过程能力指数 Cp */ + private BigDecimal cp; + + /** 过程能力指数 Cpk */ + private BigDecimal cpk; + + /** 上侧能力指数 Cpu */ + private BigDecimal cpu; + + /** 下侧能力指数 Cpl */ + private BigDecimal cpl; + + /** 过程性能指数 Pp */ + private BigDecimal pp; + + /** 过程性能指数 Ppk */ + private BigDecimal ppk; + + // ==================== 运行图数据(图8) ==================== + + /** 样本值列表(按时间排序) */ + private List sampleValues; + + /** 样本标签(配方编码或序号) */ + private List sampleLabels; + + // ==================== Xbar-R 控制图数据(图10) ==================== + + /** 子组大小 */ + private Integer subgroupSize; + + /** 子组均值列表 */ + private List xbarValues; + + /** 子组极差列表 */ + private List rValues; + + /** 子组标签 */ + private List subgroupLabels; + + /** 总均值 Xbarbar */ + private BigDecimal xbarbar; + + /** 平均极差 Rbar */ + private BigDecimal rbar; + + /** Xbar图控制上限 */ + private BigDecimal uclX; + + /** Xbar图中心线 */ + private BigDecimal clX; + + /** Xbar图控制下限 */ + private BigDecimal lclX; + + /** R图控制上限 */ + private BigDecimal uclR; + + /** R图中心线 */ + private BigDecimal clR; + + /** R图控制下限 */ + private BigDecimal lclR; + + // ==================== 直方图数据(图7辅助) ==================== + + /** 直方图各区间计数 */ + private List histogramCounts; + + /** 直方图区间标签 */ + private List histogramBins; +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcSampleVo.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcSampleVo.java new file mode 100644 index 00000000..d4784c5b --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/domain/vo/MixTraceSpcSampleVo.java @@ -0,0 +1,85 @@ +package org.dromara.mes.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 密炼SPC样本VO(图6 - SPC样本列表) + * 每行对应一个配方的某个混炼工步实测值 + * + * @author Yinq + * @date 2026-02-14 + */ +@Data +public class MixTraceSpcSampleVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 配方ID */ + private Long recipeId; + + /** 配方编码 */ + private String recipeCode; + + /** 机台ID */ + private Long machineId; + + /** 机台名称 */ + private String machineName; + + /** 混炼序号(工步号) */ + private Long mixId; + + /** 工步编码 */ + private String termCode; + + /** 动作编码 */ + private String actCode; + + /** 条件编码 */ + private String condCode; + + /** 实测混炼时间 */ + private Long mixingTime; + + /** 实测混炼温度 */ + private BigDecimal mixingTemp; + + /** 实测混炼能量 */ + private BigDecimal mixingEnergy; + + /** 实测混炼功率 */ + private BigDecimal mixingPower; + + /** 实测混炼压力 */ + private BigDecimal mixingPress; + + /** 实测混炼转速 */ + private Long mixingSpeed; + + /** 设定时间 */ + private Long setTime; + + /** 设定温度 */ + private Long setTemp; + + /** 设定能量 */ + private BigDecimal setEnergy; + + /** 设定功率 */ + private BigDecimal setPower; + + /** 设定压力 */ + private BigDecimal setPres; + + /** 设定转速 */ + private Long setRota; + + /** 创建时间(生产时间) */ + private Date createTime; +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/mapper/ProdMixTraceReportMapper.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/mapper/ProdMixTraceReportMapper.java new file mode 100644 index 00000000..71f0b53d --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/mapper/ProdMixTraceReportMapper.java @@ -0,0 +1,57 @@ +package org.dromara.mes.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.mes.domain.vo.MixTraceListVo; +import org.dromara.mes.domain.vo.MixTraceSpcSampleVo; +import org.dromara.mes.domain.vo.ProdRecipeMixingVo; +import org.dromara.mes.domain.vo.ProdRecipeWeightVo; + +import java.util.List; +import java.util.Map; + +/** + * 密炼追溯报表Mapper接口 + * + * @author Yinq + * @date 2026-02-14 + */ +public interface ProdMixTraceReportMapper { + + /** + * 追溯列表(分页) + */ + Page selectTraceList(@Param("map") Map params, + @Param("page") Page page); + + /** + * 追溯列表(导出,不分页) + */ + List selectTraceList(@Param("map") Map params); + + /** + * 追溯详情 - 配方基础信息(含关联名称) + */ + MixTraceListVo selectTraceRecipeInfo(@Param("recipeId") Long recipeId); + + /** + * 追溯详情 - 称量明细(按 weight_seq 排序) + */ + List selectWeightListByRecipeId(@Param("recipeId") Long recipeId); + + /** + * 追溯详情 - 混炼明细(按 mix_id 排序) + */ + List selectMixingListByRecipeId(@Param("recipeId") Long recipeId); + + /** + * SPC样本查询(分页) + */ + Page selectSpcSamples(@Param("map") Map params, + @Param("page") Page page); + + /** + * SPC样本查询(全量,用于统计计算) + */ + List selectSpcSamples(@Param("map") Map params); +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/IProdMixTraceReportService.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/IProdMixTraceReportService.java new file mode 100644 index 00000000..77c5e164 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/IProdMixTraceReportService.java @@ -0,0 +1,55 @@ +package org.dromara.mes.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.mes.domain.vo.MixTraceDetailVo; +import org.dromara.mes.domain.vo.MixTraceListVo; +import org.dromara.mes.domain.vo.MixTraceSpcResultVo; +import org.dromara.mes.domain.vo.MixTraceSpcSampleVo; + +import java.util.List; +import java.util.Map; + +/** + * 密炼追溯报表Service接口 + * + * @author Yinq + * @date 2026-02-14 + */ +public interface IProdMixTraceReportService { + + /** + * 追溯列表(分页)- 图5 + */ + TableDataInfo queryTraceList(Map params, PageQuery pageQuery); + + /** + * 追溯列表(导出) + */ + List queryTraceList(Map params); + + /** + * 追溯详情 - 图9 + */ + MixTraceDetailVo queryTraceDetail(Long recipeId); + + /** + * SPC样本列表(分页)- 图6 + */ + TableDataInfo querySpcSamples(Map params, PageQuery pageQuery); + + /** + * SPC能力分析 - 图7(直方图+正态+Cp/Cpk/Ppk) + */ + MixTraceSpcResultVo calculateSpcCapability(Map params); + + /** + * SPC运行图 - 图8(点图+USL/LSL/Target) + */ + MixTraceSpcResultVo calculateSpcRunChart(Map params); + + /** + * SPC均值极差图 - 图10(Xbar-R) + */ + MixTraceSpcResultVo calculateSpcXbarR(Map params); +} diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/impl/ProdMixTraceReportServiceImpl.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/impl/ProdMixTraceReportServiceImpl.java new file mode 100644 index 00000000..9b2ea5c0 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/service/impl/ProdMixTraceReportServiceImpl.java @@ -0,0 +1,443 @@ +package org.dromara.mes.service.impl; + +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.mes.domain.vo.*; +import org.dromara.mes.mapper.ProdMixTraceReportMapper; +import org.dromara.mes.service.IProdMixTraceReportService; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 密炼追溯报表Service业务层处理 + * + * @author Yinq + * @date 2026-02-14 + */ +@RequiredArgsConstructor +@Service +public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService { + + private final ProdMixTraceReportMapper mixTraceReportMapper; + + /** SPC计算精度 */ + private static final int SCALE = 4; + private static final RoundingMode RM = RoundingMode.HALF_UP; + + /** + * Xbar-R 图常量表(子组大小 n -> A2, D3, D4) + * 仅列出常用子组大小 2~10 + */ + private static final double[][] XBAR_R_CONSTANTS = { + // n=2: A2=1.880, D3=0, D4=3.267 + {1.880, 0.0, 3.267}, + // n=3: A2=1.023, D3=0, D4=2.575 + {1.023, 0.0, 2.575}, + // n=4: A2=0.729, D3=0, D4=2.282 + {0.729, 0.0, 2.282}, + // n=5: A2=0.577, D3=0, D4=2.115 + {0.577, 0.0, 2.115}, + // n=6: A2=0.483, D3=0, D4=2.004 + {0.483, 0.0, 2.004}, + // n=7: A2=0.419, D3=0.076, D4=1.924 + {0.419, 0.076, 1.924}, + // n=8: A2=0.373, D3=0.136, D4=1.864 + {0.373, 0.136, 1.864}, + // n=9: A2=0.337, D3=0.184, D4=1.816 + {0.337, 0.184, 1.816}, + // n=10: A2=0.308, D3=0.223, D4=1.777 + {0.308, 0.223, 1.777} + }; + + // ==================== 追溯列表(图5) ==================== + + @Override + public TableDataInfo queryTraceList(Map params, PageQuery pageQuery) { + Page page = mixTraceReportMapper.selectTraceList(params, pageQuery.build()); + return TableDataInfo.build(page); + } + + @Override + public List queryTraceList(Map params) { + return mixTraceReportMapper.selectTraceList(params); + } + + // ==================== 追溯详情(图9) ==================== + + @Override + public MixTraceDetailVo queryTraceDetail(Long recipeId) { + MixTraceListVo recipeInfo = mixTraceReportMapper.selectTraceRecipeInfo(recipeId); + if (recipeInfo == null) { + return null; + } + MixTraceDetailVo detail = new MixTraceDetailVo(); + detail.setRecipeInfo(recipeInfo); + // 称量明细按 weight_seq 排序 + detail.setWeightList(mixTraceReportMapper.selectWeightListByRecipeId(recipeId)); + // 混炼明细按 mix_id 排序 + detail.setMixingList(mixTraceReportMapper.selectMixingListByRecipeId(recipeId)); + return detail; + } + + // ==================== SPC样本(图6) ==================== + + @Override + public TableDataInfo querySpcSamples(Map params, PageQuery pageQuery) { + Page page = mixTraceReportMapper.selectSpcSamples(params, pageQuery.build()); + return TableDataInfo.build(page); + } + + // ==================== SPC能力分析(图7) ==================== + + @Override + public MixTraceSpcResultVo calculateSpcCapability(Map params) { + // 获取分析参数名称,默认分析混炼温度 + String paramName = getParamName(params); + + // 查询全量样本 + List samples = mixTraceReportMapper.selectSpcSamples(params); + if (samples == null || samples.isEmpty()) { + return buildEmptyResult(paramName); + } + + // 提取指定参数的实测值和设定值 + Function actualExtractor = getActualExtractor(paramName); + Function setExtractor = getSetExtractor(paramName); + + List values = samples.stream() + .map(actualExtractor) + .filter(v -> v != null) + .collect(Collectors.toList()); + + if (values.size() < 2) { + return buildEmptyResult(paramName); + } + + // 基本统计 + MixTraceSpcResultVo result = new MixTraceSpcResultVo(); + result.setParamName(paramName); + result.setParamLabel(getParamLabel(paramName)); + result.setSampleCount(values.size()); + + BigDecimal mean = calcMean(values); + BigDecimal sigma = calcStdDev(values, mean); + BigDecimal min = values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + BigDecimal max = values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + + result.setMean(mean); + result.setSigma(sigma); + result.setMinValue(min); + result.setMaxValue(max); + + // 规格限:从设定值中取平均作为Target,USL/LSL可按 Target ± 容差 或取最大/最小设定值 + List setValues = samples.stream() + .map(setExtractor) + .filter(v -> v != null) + .collect(Collectors.toList()); + + if (!setValues.isEmpty()) { + BigDecimal target = calcMean(setValues); + result.setTarget(target); + BigDecimal setMin = setValues.stream().min(BigDecimal::compareTo).orElse(target); + BigDecimal setMax = setValues.stream().max(BigDecimal::compareTo).orElse(target); + // 若设定值有范围差异,使用最大/最小作为USL/LSL;否则使用 target ± 10% + if (setMax.compareTo(setMin) > 0) { + result.setUsl(setMax); + result.setLsl(setMin); + } else { + BigDecimal tolerance = target.abs().multiply(new BigDecimal("0.10")); + result.setUsl(target.add(tolerance)); + result.setLsl(target.subtract(tolerance)); + } + } + + // 能力指数 + if (sigma.compareTo(BigDecimal.ZERO) > 0 && result.getUsl() != null && result.getLsl() != null) { + BigDecimal sixSigma = sigma.multiply(new BigDecimal("6")); + BigDecimal threeSigma = sigma.multiply(new BigDecimal("3")); + BigDecimal usl = result.getUsl(); + BigDecimal lsl = result.getLsl(); + + result.setCp(usl.subtract(lsl).divide(sixSigma, SCALE, RM)); + result.setCpu(usl.subtract(mean).divide(threeSigma, SCALE, RM)); + result.setCpl(mean.subtract(lsl).divide(threeSigma, SCALE, RM)); + result.setCpk(result.getCpu().min(result.getCpl())); + + // Pp/Ppk 使用总体标准差(此处简化为与Cp/Cpk相同口径) + result.setPp(result.getCp()); + result.setPpk(result.getCpk()); + } + + // 直方图数据 + buildHistogram(result, values); + + // 运行图数据 + result.setSampleValues(values); + List labels = new ArrayList<>(); + for (int i = 0; i < samples.size(); i++) { + MixTraceSpcSampleVo s = samples.get(i); + BigDecimal v = actualExtractor.apply(s); + if (v != null) { + labels.add(s.getRecipeCode() != null ? s.getRecipeCode() : String.valueOf(i + 1)); + } + } + result.setSampleLabels(labels); + + return result; + } + + // ==================== SPC运行图(图8) ==================== + + @Override + public MixTraceSpcResultVo calculateSpcRunChart(Map params) { + // 运行图与能力分析共用数据,直接复用 + return calculateSpcCapability(params); + } + + // ==================== SPC Xbar-R图(图10) ==================== + + @Override + public MixTraceSpcResultVo calculateSpcXbarR(Map params) { + String paramName = getParamName(params); + + List samples = mixTraceReportMapper.selectSpcSamples(params); + if (samples == null || samples.isEmpty()) { + return buildEmptyResult(paramName); + } + + Function actualExtractor = getActualExtractor(paramName); + + List values = samples.stream() + .map(actualExtractor) + .filter(v -> v != null) + .collect(Collectors.toList()); + + // 子组大小,默认5,可通过参数配置 + int subgroupSize = 5; + if (params.containsKey("subgroupSize")) { + try { + subgroupSize = Integer.parseInt(String.valueOf(params.get("subgroupSize"))); + } catch (NumberFormatException ignored) { + } + } + // 子组大小范围限制 2~10 + subgroupSize = Math.max(2, Math.min(10, subgroupSize)); + + if (values.size() < subgroupSize * 2) { + // 样本不足,至少需要2个子组 + MixTraceSpcResultVo result = buildEmptyResult(paramName); + result.setSampleCount(values.size()); + return result; + } + + // 按子组分组 + int groupCount = values.size() / subgroupSize; + List xbarValues = new ArrayList<>(); + List rValues = new ArrayList<>(); + List subgroupLabels = new ArrayList<>(); + + for (int i = 0; i < groupCount; i++) { + int start = i * subgroupSize; + List group = values.subList(start, start + subgroupSize); + BigDecimal groupMean = calcMean(group); + BigDecimal groupMax = group.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + BigDecimal groupMin = group.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO); + BigDecimal groupRange = groupMax.subtract(groupMin); + + xbarValues.add(groupMean); + rValues.add(groupRange); + subgroupLabels.add("G" + (i + 1)); + } + + // 总均值与平均极差 + BigDecimal xbarbar = calcMean(xbarValues); + BigDecimal rbar = calcMean(rValues); + + // 查找常量 A2, D3, D4 + int constIndex = subgroupSize - 2; // 数组从 n=2 开始 + double a2 = XBAR_R_CONSTANTS[constIndex][0]; + double d3 = XBAR_R_CONSTANTS[constIndex][1]; + double d4 = XBAR_R_CONSTANTS[constIndex][2]; + + BigDecimal A2 = BigDecimal.valueOf(a2); + BigDecimal D3 = BigDecimal.valueOf(d3); + BigDecimal D4 = BigDecimal.valueOf(d4); + + // 构建结果 + MixTraceSpcResultVo result = new MixTraceSpcResultVo(); + result.setParamName(paramName); + result.setParamLabel(getParamLabel(paramName)); + result.setSampleCount(values.size()); + result.setSubgroupSize(subgroupSize); + result.setMean(calcMean(values)); + result.setSigma(calcStdDev(values, result.getMean())); + + result.setXbarValues(xbarValues); + result.setRValues(rValues); + result.setSubgroupLabels(subgroupLabels); + result.setXbarbar(xbarbar); + result.setRbar(rbar); + + // Xbar 控制限 + result.setUclX(xbarbar.add(A2.multiply(rbar)).setScale(SCALE, RM)); + result.setClX(xbarbar.setScale(SCALE, RM)); + result.setLclX(xbarbar.subtract(A2.multiply(rbar)).setScale(SCALE, RM)); + + // R 控制限 + result.setUclR(D4.multiply(rbar).setScale(SCALE, RM)); + result.setClR(rbar.setScale(SCALE, RM)); + result.setLclR(D3.multiply(rbar).setScale(SCALE, RM)); + + return result; + } + + // ==================== 工具方法 ==================== + + /** + * 获取分析参数名称,默认 mixingTemp + */ + private String getParamName(Map params) { + Object paramNameObj = params.get("paramName"); + return (paramNameObj != null && !String.valueOf(paramNameObj).isEmpty()) + ? String.valueOf(paramNameObj) : "mixingTemp"; + } + + /** + * 参数名称 -> 中文标签映射 + */ + private String getParamLabel(String paramName) { + switch (paramName) { + case "mixingTemp": return "混炼温度"; + case "mixingTime": return "混炼时间"; + case "mixingEnergy": return "混炼能量"; + case "mixingPower": return "混炼功率"; + case "mixingPress": return "混炼压力"; + case "mixingSpeed": return "混炼转速"; + default: return paramName; + } + } + + /** + * 根据参数名获取实测值提取器 + */ + private Function getActualExtractor(String paramName) { + switch (paramName) { + case "mixingTemp": return MixTraceSpcSampleVo::getMixingTemp; + case "mixingTime": return s -> s.getMixingTime() != null ? BigDecimal.valueOf(s.getMixingTime()) : null; + case "mixingEnergy": return MixTraceSpcSampleVo::getMixingEnergy; + case "mixingPower": return MixTraceSpcSampleVo::getMixingPower; + case "mixingPress": return MixTraceSpcSampleVo::getMixingPress; + case "mixingSpeed": return s -> s.getMixingSpeed() != null ? BigDecimal.valueOf(s.getMixingSpeed()) : null; + default: return MixTraceSpcSampleVo::getMixingTemp; + } + } + + /** + * 根据参数名获取设定值提取器 + */ + private Function getSetExtractor(String paramName) { + switch (paramName) { + case "mixingTemp": return s -> s.getSetTemp() != null ? BigDecimal.valueOf(s.getSetTemp()) : null; + case "mixingTime": return s -> s.getSetTime() != null ? BigDecimal.valueOf(s.getSetTime()) : null; + case "mixingEnergy": return MixTraceSpcSampleVo::getSetEnergy; + case "mixingPower": return MixTraceSpcSampleVo::getSetPower; + case "mixingPress": return MixTraceSpcSampleVo::getSetPres; + case "mixingSpeed": return s -> s.getSetRota() != null ? BigDecimal.valueOf(s.getSetRota()) : null; + default: return s -> s.getSetTemp() != null ? BigDecimal.valueOf(s.getSetTemp()) : null; + } + } + + /** + * 计算均值 + */ + private BigDecimal calcMean(List values) { + if (values == null || values.isEmpty()) { + return BigDecimal.ZERO; + } + BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); + return sum.divide(BigDecimal.valueOf(values.size()), SCALE, RM); + } + + /** + * 计算样本标准差 + */ + private BigDecimal calcStdDev(List values, BigDecimal mean) { + if (values == null || values.size() < 2) { + return BigDecimal.ZERO; + } + BigDecimal sumSq = values.stream() + .map(v -> v.subtract(mean).pow(2)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + // 样本标准差使用 n-1 + BigDecimal variance = sumSq.divide(BigDecimal.valueOf(values.size() - 1), SCALE * 2, RM); + return BigDecimal.valueOf(Math.sqrt(variance.doubleValue())).setScale(SCALE, RM); + } + + /** + * 构建空结果 + */ + private MixTraceSpcResultVo buildEmptyResult(String paramName) { + MixTraceSpcResultVo result = new MixTraceSpcResultVo(); + result.setParamName(paramName); + result.setParamLabel(getParamLabel(paramName)); + result.setSampleCount(0); + return result; + } + + /** + * 构建直方图数据(等宽分箱,默认10个区间) + */ + private void buildHistogram(MixTraceSpcResultVo result, List values) { + if (values == null || values.size() < 2) { + return; + } + int binCount = 10; + BigDecimal min = result.getMinValue(); + BigDecimal max = result.getMaxValue(); + BigDecimal range = max.subtract(min); + + if (range.compareTo(BigDecimal.ZERO) == 0) { + // 所有值相同 + List counts = new ArrayList<>(); + List bins = new ArrayList<>(); + counts.add(values.size()); + bins.add(min.setScale(2, RM).toPlainString()); + result.setHistogramCounts(counts); + result.setHistogramBins(bins); + return; + } + + BigDecimal binWidth = range.divide(BigDecimal.valueOf(binCount), SCALE + 2, RM); + List counts = new ArrayList<>(); + List bins = new ArrayList<>(); + + for (int i = 0; i < binCount; i++) { + BigDecimal lower = min.add(binWidth.multiply(BigDecimal.valueOf(i))); + BigDecimal upper = min.add(binWidth.multiply(BigDecimal.valueOf(i + 1))); + int finalI = i; + long count = values.stream() + .filter(v -> { + if (finalI == binCount - 1) { + return v.compareTo(lower) >= 0 && v.compareTo(upper) <= 0; + } + return v.compareTo(lower) >= 0 && v.compareTo(upper) < 0; + }) + .count(); + counts.add((int) count); + bins.add(lower.setScale(2, RM).toPlainString() + "~" + upper.setScale(2, RM).toPlainString()); + } + + result.setHistogramCounts(counts); + result.setHistogramBins(bins); + } +} diff --git a/ruoyi-modules/hwmom-mes/src/main/resources/mapper/mes/ProdMixTraceReportMapper.xml b/ruoyi-modules/hwmom-mes/src/main/resources/mapper/mes/ProdMixTraceReportMapper.xml new file mode 100644 index 00000000..7b4fd4a2 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/resources/mapper/mes/ProdMixTraceReportMapper.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + +