Merge remote-tracking branch 'origin/master'

master
yinq 4 days ago
commit 9e084f61f8

@ -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<MixTraceListVo> list(@RequestParam(required = false) Map<String, Object> 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<String, Object> params,
HttpServletResponse response) {
List<MixTraceListVo> list = mixTraceReportService.queryTraceList(params);
ExcelUtil.exportExcel(list, "密炼追溯报表", MixTraceListVo.class, response);
}
/**
* 9- + +
*/
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/detail/{recipeId}")
public R<MixTraceDetailVo> detail(@PathVariable Long recipeId) {
return R.ok(mixTraceReportService.queryTraceDetail(recipeId));
}
/**
* SPC6-
*/
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/spc/samples")
public TableDataInfo<MixTraceSpcSampleVo> spcSamples(@RequestParam(required = false) Map<String, Object> params,
PageQuery pageQuery) {
return mixTraceReportService.querySpcSamples(params, pageQuery);
}
/**
* SPC7- + + Cp/Cpk/Ppk
*/
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/spc/capability")
public R<MixTraceSpcResultVo> spcCapability(@RequestParam(required = false) Map<String, Object> params) {
return R.ok(mixTraceReportService.calculateSpcCapability(params));
}
/**
* SPC8- + USL/LSL/Target
*/
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/spc/runChart")
public R<MixTraceSpcResultVo> spcRunChart(@RequestParam(required = false) Map<String, Object> params) {
return R.ok(mixTraceReportService.calculateSpcRunChart(params));
}
/**
* SPC10- Xbar-R
*/
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/spc/xbarR")
public R<MixTraceSpcResultVo> spcXbarR(@RequestParam(required = false) Map<String, Object> params) {
return R.ok(mixTraceReportService.calculateSpcXbarR(params));
}
}

@ -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;
/**
* VO9 -
* + +
*
* @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<ProdRecipeWeightVo> weightList;
/** 混炼明细列表(按 mix_id 排序) */
private List<ProdRecipeMixingVo> mixingList;
}

@ -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;
/**
* VO5 -
* + + + /
*
* @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;
}

@ -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;
/**
* SPCVO7/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<BigDecimal> sampleValues;
/** 样本标签(配方编码或序号) */
private List<String> sampleLabels;
// ==================== Xbar-R 控制图数据图10 ====================
/** 子组大小 */
private Integer subgroupSize;
/** 子组均值列表 */
private List<BigDecimal> xbarValues;
/** 子组极差列表 */
private List<BigDecimal> rValues;
/** 子组标签 */
private List<String> 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<Integer> histogramCounts;
/** 直方图区间标签 */
private List<String> histogramBins;
}

@ -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;
/**
* SPCVO6 - 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;
}

@ -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<MixTraceListVo> selectTraceList(@Param("map") Map<String, Object> params,
@Param("page") Page<MixTraceListVo> page);
/**
*
*/
List<MixTraceListVo> selectTraceList(@Param("map") Map<String, Object> params);
/**
* -
*/
MixTraceListVo selectTraceRecipeInfo(@Param("recipeId") Long recipeId);
/**
* - weight_seq
*/
List<ProdRecipeWeightVo> selectWeightListByRecipeId(@Param("recipeId") Long recipeId);
/**
* - mix_id
*/
List<ProdRecipeMixingVo> selectMixingListByRecipeId(@Param("recipeId") Long recipeId);
/**
* SPC
*/
Page<MixTraceSpcSampleVo> selectSpcSamples(@Param("map") Map<String, Object> params,
@Param("page") Page<MixTraceSpcSampleVo> page);
/**
* SPC
*/
List<MixTraceSpcSampleVo> selectSpcSamples(@Param("map") Map<String, Object> params);
}

@ -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<MixTraceListVo> queryTraceList(Map<String, Object> params, PageQuery pageQuery);
/**
*
*/
List<MixTraceListVo> queryTraceList(Map<String, Object> params);
/**
* - 9
*/
MixTraceDetailVo queryTraceDetail(Long recipeId);
/**
* SPC- 6
*/
TableDataInfo<MixTraceSpcSampleVo> querySpcSamples(Map<String, Object> params, PageQuery pageQuery);
/**
* SPC - 7++Cp/Cpk/Ppk
*/
MixTraceSpcResultVo calculateSpcCapability(Map<String, Object> params);
/**
* SPC - 8+USL/LSL/Target
*/
MixTraceSpcResultVo calculateSpcRunChart(Map<String, Object> params);
/**
* SPC - 10Xbar-R
*/
MixTraceSpcResultVo calculateSpcXbarR(Map<String, Object> params);
}

@ -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<MixTraceListVo> queryTraceList(Map<String, Object> params, PageQuery pageQuery) {
Page<MixTraceListVo> page = mixTraceReportMapper.selectTraceList(params, pageQuery.build());
return TableDataInfo.build(page);
}
@Override
public List<MixTraceListVo> queryTraceList(Map<String, Object> 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<MixTraceSpcSampleVo> querySpcSamples(Map<String, Object> params, PageQuery pageQuery) {
Page<MixTraceSpcSampleVo> page = mixTraceReportMapper.selectSpcSamples(params, pageQuery.build());
return TableDataInfo.build(page);
}
// ==================== SPC能力分析图7 ====================
@Override
public MixTraceSpcResultVo calculateSpcCapability(Map<String, Object> params) {
// 获取分析参数名称,默认分析混炼温度
String paramName = getParamName(params);
// 查询全量样本
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
if (samples == null || samples.isEmpty()) {
return buildEmptyResult(paramName);
}
// 提取指定参数的实测值和设定值
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
Function<MixTraceSpcSampleVo, BigDecimal> setExtractor = getSetExtractor(paramName);
List<BigDecimal> 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);
// 规格限从设定值中取平均作为TargetUSL/LSL可按 Target ± 容差 或取最大/最小设定值
List<BigDecimal> 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<String> 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<String, Object> params) {
// 运行图与能力分析共用数据,直接复用
return calculateSpcCapability(params);
}
// ==================== SPC Xbar-R图图10 ====================
@Override
public MixTraceSpcResultVo calculateSpcXbarR(Map<String, Object> params) {
String paramName = getParamName(params);
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
if (samples == null || samples.isEmpty()) {
return buildEmptyResult(paramName);
}
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
List<BigDecimal> 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<BigDecimal> xbarValues = new ArrayList<>();
List<BigDecimal> rValues = new ArrayList<>();
List<String> subgroupLabels = new ArrayList<>();
for (int i = 0; i < groupCount; i++) {
int start = i * subgroupSize;
List<BigDecimal> 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<String, Object> 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<MixTraceSpcSampleVo, BigDecimal> 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<MixTraceSpcSampleVo, BigDecimal> 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<BigDecimal> 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<BigDecimal> 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<BigDecimal> 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<Integer> counts = new ArrayList<>();
List<String> 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<Integer> counts = new ArrayList<>();
List<String> 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);
}
}

@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.mes.mapper.ProdMixTraceReportMapper">
<!-- 追溯列表查询图5 -->
<select id="selectTraceList" resultType="org.dromara.mes.domain.vo.MixTraceListVo">
SELECT
ri.recipe_id AS recipeId,
ri.recipe_code AS recipeCode,
ri.machine_id AS machineId,
bm.machine_name AS machineName,
ri.material_id AS materialId,
bmi.material_name AS materialName,
ri.edt_code AS edtCode,
ri.user_edt_code AS userEdtCode,
ri.recipe_state AS recipeState,
ri.recipe_type AS recipeType,
ri.recipe_typecode AS recipeTypecode,
ri.rub_type AS rubType,
ri.rub_typecode AS rubTypecode,
ri.total_weight AS totalWeight,
ri.fill_coefficient AS fillCoefficient,
ri.oper_code AS operCode,
ri.audit_flag AS auditFlag,
ri.done_time AS doneTime,
ri.create_time AS createTime,
(SELECT COUNT(1) FROM prod_recipe_weight w
WHERE w.recipe_id = ri.recipe_id AND w.del_flag = '0') AS weightCount,
(SELECT COUNT(1) FROM prod_recipe_mixing m
WHERE m.recipe_id = ri.recipe_id AND m.del_flag = '0') AS mixingCount
FROM prod_recipe_info ri
LEFT JOIN prod_base_machine_info bm ON bm.machine_id = ri.machine_id
LEFT JOIN base_material_info bmi ON bmi.material_id = ri.material_id
WHERE ri.del_flag = '0'
<if test="map.recipeCode != null and map.recipeCode != ''">
AND ri.recipe_code LIKE CONCAT('%', #{map.recipeCode}, '%')
</if>
<if test="map.machineId != null and map.machineId != ''">
AND ri.machine_id = #{map.machineId}
</if>
<if test="map.materialId != null and map.materialId != ''">
AND ri.material_id = #{map.materialId}
</if>
<if test="map.recipeState != null and map.recipeState != ''">
AND ri.recipe_state = #{map.recipeState}
</if>
<if test="map.rubType != null and map.rubType != ''">
AND ri.rub_type LIKE CONCAT('%', #{map.rubType}, '%')
</if>
<if test="map.rubTypecode != null and map.rubTypecode != ''">
AND ri.rub_typecode LIKE CONCAT('%', #{map.rubTypecode}, '%')
</if>
<if test="map.recipeTypecode != null and map.recipeTypecode != ''">
AND ri.recipe_typecode = #{map.recipeTypecode}
</if>
<if test="map.operCode != null and map.operCode != ''">
AND ri.oper_code LIKE CONCAT('%', #{map.operCode}, '%')
</if>
<if test="map.auditFlag != null and map.auditFlag != ''">
AND ri.audit_flag = #{map.auditFlag}
</if>
<if test="map.materialName != null and map.materialName != ''">
AND bmi.material_name LIKE CONCAT('%', #{map.materialName}, '%')
</if>
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND FORMAT(ri.create_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
ORDER BY ri.create_time DESC
</select>
<!-- 追溯详情 - 配方基础信息(含关联名称) -->
<select id="selectTraceRecipeInfo" resultType="org.dromara.mes.domain.vo.MixTraceListVo">
SELECT
ri.recipe_id AS recipeId,
ri.recipe_code AS recipeCode,
ri.machine_id AS machineId,
bm.machine_name AS machineName,
ri.material_id AS materialId,
bmi.material_name AS materialName,
ri.edt_code AS edtCode,
ri.user_edt_code AS userEdtCode,
ri.recipe_state AS recipeState,
ri.recipe_type AS recipeType,
ri.recipe_typecode AS recipeTypecode,
ri.rub_type AS rubType,
ri.rub_typecode AS rubTypecode,
ri.total_weight AS totalWeight,
ri.fill_coefficient AS fillCoefficient,
ri.oper_code AS operCode,
ri.audit_flag AS auditFlag,
ri.done_time AS doneTime,
ri.create_time AS createTime,
(SELECT COUNT(1) FROM prod_recipe_weight w
WHERE w.recipe_id = ri.recipe_id AND w.del_flag = '0') AS weightCount,
(SELECT COUNT(1) FROM prod_recipe_mixing m
WHERE m.recipe_id = ri.recipe_id AND m.del_flag = '0') AS mixingCount
FROM prod_recipe_info ri
LEFT JOIN prod_base_machine_info bm ON bm.machine_id = ri.machine_id
LEFT JOIN base_material_info bmi ON bmi.material_id = ri.material_id
WHERE ri.del_flag = '0'
AND ri.recipe_id = #{recipeId}
</select>
<!-- 追溯详情 - 称量明细(按 weight_seq 排序) -->
<select id="selectWeightListByRecipeId" resultType="org.dromara.mes.domain.vo.ProdRecipeWeightVo">
SELECT
w.weight_id AS weightId,
w.recipe_id AS recipeId,
w.weight_seq AS weightSeq,
w.machine_id AS machineId,
w.edt_code AS edtCode,
w.weight_type AS weightType,
w.scale_code AS scaleCode,
w.act_code AS actCode,
w.set_weight AS setWeight,
w.error_allow AS errorAllow,
w.father_code AS fatherCode,
w.unit_id AS unitId,
w.child_code AS childCode,
w.if_use_bat AS ifUseBat,
w.max_rate AS maxRate
FROM prod_recipe_weight w
WHERE w.del_flag = '0'
AND w.recipe_id = #{recipeId}
ORDER BY w.weight_seq ASC
</select>
<!-- 追溯详情 - 混炼明细(按 mix_id 排序) -->
<select id="selectMixingListByRecipeId" resultType="org.dromara.mes.domain.vo.ProdRecipeMixingVo">
SELECT
m.mixing_id AS mixingId,
m.recipe_id AS recipeId,
m.mix_id AS mixId,
m.machine_id AS machineId,
m.edt_code AS edtCode,
m.cond_code AS condCode,
m.mixing_time AS mixingTime,
m.mixing_temp AS mixingTemp,
m.mixing_energy AS mixingEnergy,
m.mixing_power AS mixingPower,
m.mixing_press AS mixingPress,
m.mixing_speed AS mixingSpeed,
m.act_code AS actCode,
m.father_code AS fatherCode,
m.child_code AS childCode,
m.term_code AS termCode,
m.set_time AS setTime,
m.set_temp AS setTemp,
m.set_energy AS setEnergy,
m.set_power AS setPower,
m.set_pres AS setPres,
m.set_rota AS setRota
FROM prod_recipe_mixing m
WHERE m.del_flag = '0'
AND m.recipe_id = #{recipeId}
ORDER BY m.mix_id ASC
</select>
<!-- SPC样本查询图6查询多个配方的混炼实测值作为SPC样本 -->
<select id="selectSpcSamples" resultType="org.dromara.mes.domain.vo.MixTraceSpcSampleVo">
SELECT
ri.recipe_id AS recipeId,
ri.recipe_code AS recipeCode,
ri.machine_id AS machineId,
bm.machine_name AS machineName,
rm.mix_id AS mixId,
rm.term_code AS termCode,
rm.act_code AS actCode,
rm.cond_code AS condCode,
rm.mixing_time AS mixingTime,
rm.mixing_temp AS mixingTemp,
rm.mixing_energy AS mixingEnergy,
rm.mixing_power AS mixingPower,
rm.mixing_press AS mixingPress,
rm.mixing_speed AS mixingSpeed,
rm.set_time AS setTime,
rm.set_temp AS setTemp,
rm.set_energy AS setEnergy,
rm.set_power AS setPower,
rm.set_pres AS setPres,
rm.set_rota AS setRota,
ri.create_time AS createTime
FROM prod_recipe_mixing rm
INNER JOIN prod_recipe_info ri ON ri.recipe_id = rm.recipe_id AND ri.del_flag = '0'
LEFT JOIN prod_base_machine_info bm ON bm.machine_id = ri.machine_id
WHERE rm.del_flag = '0'
<if test="map.machineId != null and map.machineId != ''">
AND ri.machine_id = #{map.machineId}
</if>
<if test="map.materialId != null and map.materialId != ''">
AND ri.material_id = #{map.materialId}
</if>
<if test="map.recipeCode != null and map.recipeCode != ''">
AND ri.recipe_code LIKE CONCAT('%', #{map.recipeCode}, '%')
</if>
<if test="map.rubTypecode != null and map.rubTypecode != ''">
AND ri.rub_typecode LIKE CONCAT('%', #{map.rubTypecode}, '%')
</if>
<if test="map.mixId != null and map.mixId != ''">
AND rm.mix_id = #{map.mixId}
</if>
<if test="map.termCode != null and map.termCode != ''">
AND rm.term_code = #{map.termCode}
</if>
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND FORMAT(ri.create_time, 'yyyy-MM-dd') BETWEEN #{map.beginDate} AND #{map.endDate}
</if>
ORDER BY ri.create_time ASC, rm.mix_id ASC
</select>
</mapper>
Loading…
Cancel
Save