feat(mes): 扩展混炼追溯报告功能以支持更详细的参数过滤和数据展示

- 在 IProdMixTraceReportService 中为 queryTraceDetail 方法添加参数映射支持
- 向 MixTraceDetailVo 添加追溯汇总、材料追溯树、耗用明细、工步明细、批次明细和历史曲线字段
- 向 MixTraceListVo 添加计划、班次、班组等相关信息字段用于追溯详情展示
- 修改控制器中的 detail 方法以接受并规范化查询参数
- 更新数据库映射器以支持通过参数映射进行配方信息、汇总、耗用、工步和批次查询
- 在 XML 映射文件中实现追溯选取应用逻辑和多维度查询条件支持
- 添加追溯汇总、耗用明细、工步过程和批次明细的数据查询接口实现
- 重构服务实现类中的常量定义和代码注释以提高可读性
master
zangch@mesnac.com 2 months ago
parent 1d42a0f70b
commit 1586a6e853

@ -52,8 +52,9 @@ public class ProdMixTraceReportController extends BaseController {
/** 追溯详情 */
//@SaCheckPermission("mes:mixTrace:list")
@GetMapping("/detail/{recipeId}")
public R<MixTraceDetailVo> detail(@PathVariable Long recipeId) {
return R.ok(mixTraceReportService.queryTraceDetail(recipeId));
public R<MixTraceDetailVo> detail(@PathVariable Long recipeId,
@RequestParam(required = false) Map<String, Object> params) {
return R.ok(mixTraceReportService.queryTraceDetail(recipeId, normalizeParams(params)));
}
/** SPC 样本 */

@ -0,0 +1,29 @@
package org.dromara.mes.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* -
*/
@Data
public class MixTraceBatchVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long batchId;
private String productionBarcode;
private String batchCode;
private String inputBarcode;
private Long materialId;
private String materialName;
private Date instockTime;
private String supplierName;
}

@ -0,0 +1,28 @@
package org.dromara.mes.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* -线
*/
@Data
public class MixTraceCurvePointVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long stepNo;
private Long timelineSecond;
private String xLabel;
private BigDecimal temperature;
private BigDecimal power;
private BigDecimal energy;
private BigDecimal pressure;
private Long speed;
}

@ -27,4 +27,22 @@ public class MixTraceDetailVo implements Serializable {
/** 混炼明细列表(按 mix_id 排序) */
private List<ProdRecipeMixingVo> mixingList;
/** 顶部摘要信息(按 mix.jpg 字段对齐) */
private MixTraceSummaryVo summaryInfo;
/** 左侧耗用追溯树 */
private List<MixTraceMaterialTraceTreeVo> materialTraceTree;
/** 耗用明细(类别/设重/实重/公差/状态) */
private List<MixTraceUsageVo> usageList;
/** 工步明细(时间/温度/能量/功率/动作/压力/转速) */
private List<MixTraceStepVo> mixingStepList;
/** 批次明细(批次/物料/入库时间/供应商) */
private List<MixTraceBatchVo> batchList;
/** 历史曲线(温度/功率/能量/压力/转速) */
private List<MixTraceCurvePointVo> curveSeries;
}

@ -103,4 +103,55 @@ public class MixTraceListVo implements Serializable {
/** 混炼工步数 */
@ExcelProperty(value = "混炼工步数")
private Integer mixingCount;
/** 计划ID */
private Long planId;
/** 计划编号 */
@ExcelProperty(value = "计划编号")
private String planCode;
/** 计划明细ID */
private Long planDetailId;
/** 计划明细编号 */
@ExcelProperty(value = "明细编号")
private String planDetailCode;
/** 生产条码 */
@ExcelProperty(value = "生产条码")
private String productionBarcode;
/** 班次ID */
private Long shiftId;
/** 班次名称 */
@ExcelProperty(value = "班次")
private String shiftName;
/** 班组ID */
private Long classTeamId;
/** 班组名称 */
@ExcelProperty(value = "班组")
private String classTeamName;
/** 计划数量 */
@ExcelProperty(value = "计划数")
private BigDecimal planAmount;
/** 完成数量 */
@ExcelProperty(value = "完成数")
private BigDecimal completeAmount;
/** 车次 */
@ExcelProperty(value = "密炼车次")
private Long trainNumber;
/** 实际开始时间 */
@ExcelProperty(value = "开始生产时间")
private Date realBeginTime;
/** 实际结束时间 */
private Date realEndTime;
}

@ -0,0 +1,32 @@
package org.dromara.mes.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* -
*/
@Data
public class MixTraceMaterialTraceTreeVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String id;
private String label;
private String nodeType;
private Long recipeId;
private Long planDetailId;
private String productionBarcode;
private Long materialId;
private String materialName;
private String batchCode;
private List<MixTraceMaterialTraceTreeVo> children = new ArrayList<>();
}

@ -0,0 +1,43 @@
package org.dromara.mes.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* -
*/
@Data
public class MixTraceStepVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long mixingId;
private Long mixId;
private Long timelineSecond;
private String termCode;
private String termName;
private String condCode;
private String condName;
private String actCode;
private String actName;
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;
}

@ -0,0 +1,57 @@
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;
/**
*
*/
@Data
public class MixTraceSummaryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long recipeId;
private String recipeCode;
private Long machineId;
private String machineName;
private Long materialId;
private String materialName;
private Long planId;
private String planCode;
private Long planDetailId;
private String planDetailCode;
private String productionBarcode;
private Long shiftId;
private String shiftName;
private Long classTeamId;
private String classTeamName;
private BigDecimal planAmount;
private BigDecimal settingWeight;
private BigDecimal completedWeight;
private Long trayCount;
private Long mixingTrainNo;
private Long totalTrainNo;
private String overToleranceAlarm;
private BigDecimal eachCarEnergy;
private BigDecimal dischargeTemp;
private BigDecimal dischargePower;
private BigDecimal dischargeEnergy;
private String mixingStatus;
private Long mixingTime;
private Long consumeTime;
private Long intervalTime;
private Date beginProduceTime;
private Date endProduceTime;
}

@ -0,0 +1,34 @@
package org.dromara.mes.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* -
*/
@Data
public class MixTraceUsageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long usageId;
private Long weightSeq;
private String categoryName;
private String materialName;
private BigDecimal setWeight;
private BigDecimal actualWeight;
private BigDecimal tolerance;
private BigDecimal diffWeight;
private String overToleranceFlag;
private String controlMode;
private String actCode;
private String actName;
}

@ -2,8 +2,12 @@ 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.MixTraceBatchVo;
import org.dromara.mes.domain.vo.MixTraceStepVo;
import org.dromara.mes.domain.vo.MixTraceListVo;
import org.dromara.mes.domain.vo.MixTraceSummaryVo;
import org.dromara.mes.domain.vo.MixTraceSpcSampleVo;
import org.dromara.mes.domain.vo.MixTraceUsageVo;
import org.dromara.mes.domain.vo.ProdRecipeMixingVo;
import org.dromara.mes.domain.vo.ProdRecipeWeightVo;
@ -32,7 +36,7 @@ public interface ProdMixTraceReportMapper {
/**
* -
*/
MixTraceListVo selectTraceRecipeInfo(@Param("recipeId") Long recipeId);
MixTraceListVo selectTraceRecipeInfo(@Param("map") Map<String, Object> params);
/**
* - weight_seq
@ -44,6 +48,26 @@ public interface ProdMixTraceReportMapper {
*/
List<ProdRecipeMixingVo> selectMixingListByRecipeId(@Param("recipeId") Long recipeId);
/**
* -
*/
MixTraceSummaryVo selectTraceSummary(@Param("map") Map<String, Object> params);
/**
* -
*/
List<MixTraceUsageVo> selectTraceUsageList(@Param("map") Map<String, Object> params);
/**
* -
*/
List<MixTraceStepVo> selectTraceStepList(@Param("map") Map<String, Object> params);
/**
* -
*/
List<MixTraceBatchVo> selectTraceBatchList(@Param("map") Map<String, Object> params);
/**
* SPC
*/
@ -54,4 +78,5 @@ public interface ProdMixTraceReportMapper {
* SPC
*/
List<MixTraceSpcSampleVo> selectSpcSamples(@Param("map") Map<String, Object> params);
}

@ -31,7 +31,7 @@ public interface IProdMixTraceReportService {
/**
* - 9
*/
MixTraceDetailVo queryTraceDetail(Long recipeId);
MixTraceDetailVo queryTraceDetail(Long recipeId, Map<String, Object> params);
/**
* SPC- 6

@ -2,25 +2,23 @@ package org.dromara.mes.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
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.tenant.helper.TenantHelper;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Service
* Service
*
* @author Yinq
* @date 2026-02-14
@ -31,44 +29,31 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
private final ProdMixTraceReportMapper mixTraceReportMapper;
/** SPC计算精度 */
/** SPC 数值保留精度 */
private static final int SCALE = 4;
private static final RoundingMode RM = RoundingMode.HALF_UP;
/**
* Xbar-R n -> A2, D3, D4
* 2~10
* Xbar-R n=2~10 A2D3D4
*/
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) {
// 统一把 null 查询参数转为空 Map避免 Mapper XML 中 OGNL 访问空指针。
// 统一参数清洗,避免 Mapper 中空字符串带来的条件误判
Map<String, Object> queryParams = safeParams(params);
// 由 PageQuery 生成分页对象确保前后端分页语义一致pageNum/pageSize
// 分页查询由 MyBatis-Plus 负责总数与分页窗口Service 仅做参数口径统一
Page<MixTraceListVo> page = mixTraceReportMapper.selectTraceList(queryParams, pageQuery.build());
// 统一封装成 TableDataInfo前端可直接读取 rows/total。
return TableDataInfo.build(page);
}
@ -77,67 +62,123 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
return mixTraceReportMapper.selectTraceList(safeParams(params));
}
// ==================== 追溯详情图9 ====================
@Override
public MixTraceDetailVo queryTraceDetail(Long recipeId) {
// 先查主信息;不存在时直接返回 null前端按“无详情”处理。
MixTraceListVo recipeInfo = mixTraceReportMapper.selectTraceRecipeInfo(recipeId);
public MixTraceDetailVo queryTraceDetail(Long recipeId, Map<String, Object> params) {
// 入口参数统一规范后,再注入 recipeId避免前端同名空值覆盖
Map<String, Object> detailParams = safeParams(params);
detailParams.put("recipeId", recipeId);
// 先查主信息,主信息不存在时不继续查询子表,避免无意义 IO
MixTraceListVo recipeInfo = mixTraceReportMapper.selectTraceRecipeInfo(detailParams);
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));
MixTraceSummaryVo summaryInfo = mixTraceReportMapper.selectTraceSummary(detailParams);
detail.setSummaryInfo(summaryInfo);
if (summaryInfo == null && hasDetailScopeFilter(detailParams)) {
// 带“本车范围”筛选但未命中摘要时,直接返回空明细,避免串车
// 这里必须返回空集合而不是 null前端渲染更稳定避免出现 NPE
detail.setUsageList(new ArrayList<>());
detail.setMixingStepList(new ArrayList<>());
detail.setCurveSeries(new ArrayList<>());
detail.setBatchList(new ArrayList<>());
detail.setMaterialTraceTree(new ArrayList<>());
return detail;
}
if (summaryInfo != null) {
// 详情查询优先沿用摘要命中的计划上下文,确保后续子查询口径一致
// 计划主键优先planId/planDetailId 命中后,后续所有明细查询都按同口径收敛
if (detailParams.get("planId") == null && summaryInfo.getPlanId() != null) {
detailParams.put("planId", summaryInfo.getPlanId());
}
if (detailParams.get("planDetailId") == null && summaryInfo.getPlanDetailId() != null) {
detailParams.put("planDetailId", summaryInfo.getPlanDetailId());
}
String planCode = getStringParam(detailParams, "planCode");
if (StringUtils.isBlank(planCode) && StringUtils.isNotBlank(summaryInfo.getPlanCode())) {
// 编码字段只在前端未传时回填,避免覆盖前端明确指定的筛选值
detailParams.put("planCode", summaryInfo.getPlanCode());
}
String planDetailCode = getStringParam(detailParams, "planDetailCode");
if (StringUtils.isBlank(planDetailCode) && StringUtils.isNotBlank(summaryInfo.getPlanDetailCode())) {
detailParams.put("planDetailCode", summaryInfo.getPlanDetailCode());
}
}
// 本车条码优先级:入参 > 摘要命中 > 主信息兜底
// 说明:条码是“本车生产耗用追溯”最强约束条件,优先级必须最高
String productionBarcode = getStringParam(detailParams, "productionBarcode");
if (StringUtils.isBlank(productionBarcode)) {
if (summaryInfo != null && StringUtils.isNotBlank(summaryInfo.getProductionBarcode())) {
detailParams.put("productionBarcode", summaryInfo.getProductionBarcode());
} else if (StringUtils.isNotBlank(recipeInfo.getProductionBarcode())) {
detailParams.put("productionBarcode", recipeInfo.getProductionBarcode());
}
}
// 称量耗用:用于“本车生产耗用追溯”表格
List<MixTraceUsageVo> usageList = mixTraceReportMapper.selectTraceUsageList(detailParams);
detail.setUsageList(usageList);
// 混炼工步:用于工步明细与历史曲线
List<MixTraceStepVo> mixingStepList = mixTraceReportMapper.selectTraceStepList(detailParams);
detail.setMixingStepList(mixingStepList);
detail.setCurveSeries(buildCurveSeries(mixingStepList));
// 批次追溯只保留高选择性条件,优先 productionBarcode 以减少扫描范围
// 注意:批次查询 SQL 内有 TOP 限制,必须用高选择性条件避免误截断有效数据
List<MixTraceBatchVo> batchList = mixTraceReportMapper.selectTraceBatchList(buildBatchQueryParams(detailParams));
detail.setBatchList(batchList);
// 将批次明细组装为“车 -> 物料 -> 批次”树,供左侧树形追溯展示
detail.setMaterialTraceTree(buildMaterialTraceTree(summaryInfo, batchList));
return detail;
}
// ==================== SPC样本图6 ====================
@Override
public TableDataInfo<MixTraceSpcSampleVo> querySpcSamples(Map<String, Object> params, PageQuery pageQuery) {
// SPC 样本分页查询:入参做空安全处理,避免条件拼接异常。
Map<String, Object> queryParams = safeParams(params);
Page<MixTraceSpcSampleVo> page = mixTraceReportMapper.selectSpcSamples(queryParams, pageQuery.build());
return TableDataInfo.build(page);
}
// ==================== SPC能力分析图7 ====================
@Override
public MixTraceSpcResultVo calculateSpcCapability(Map<String, Object> params) {
// 1) 参数归一化:保证后续读取参数时不会出现 null Map。
Map<String, Object> queryParams = safeParams(params);
// 获取分析参数名称,默认分析混炼温度
// paramName 仅允许白名单字段,避免前端传非法参数导致统计口径失控
String paramName = getParamName(queryParams);
// 2) 查询参与统计的全量样本(能力分析必须用全样本,不用分页样本)。
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(queryParams);
if (samples == null || samples.isEmpty()) {
// 无样本直接返回空结构,前端用 sampleCount=0 判断无图表数据。
// 无样本直接返回空结构,前端仍可拿到参数标签用于占位显示
return buildEmptyResult(paramName);
}
// 3) 根据参数名选择“实测值/设定值”的提取器,避免写 if-else 大分支。
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
Function<MixTraceSpcSampleVo, BigDecimal> setExtractor = getSetExtractor(paramName);
// 4) 仅保留非空实测值参与统计,屏蔽脏数据对均值/方差计算的影响。
// 仅保留可参与统计的实际值
// 例如温度/能量为空的工步不参与均值和方差计算
List<BigDecimal> values = samples.stream()
.map(actualExtractor)
.filter(v -> v != null)
.collect(Collectors.toList());
if (values.size() < 2) {
// 标准差至少需要 2 个样本点,少于 2 个无法做能力计算。
return buildEmptyResult(paramName);
// 标准差至少需要 2 个样本,样本不足时只回传样本数
MixTraceSpcResultVo result = buildEmptyResult(paramName);
result.setSampleCount(values.size());
return result;
}
// 5) 计算基础统计量(均值、标准差、极值)。
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
result.setParamName(paramName);
result.setParamLabel(getParamLabel(paramName));
@ -147,26 +188,23 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
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);
// 6) 计算规格限:
// - 优先使用设定值的最小/最大作为 LSL/USL最贴近工艺设定
// - 若设定值没有波动(全相同),退化为 target ± 10% 的经验容差。
// 设定值用于推导目标值与规格线USL/LSL
List<BigDecimal> setValues = samples.stream()
.map(setExtractor)
.filter(v -> v != null)
.collect(Collectors.toList());
if (!setValues.isEmpty()) {
// 规格线优先使用设定值范围;若设定值没有波动则按 target ±10% 兜底
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);
@ -177,36 +215,36 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
}
}
// 7) 计算能力指数(前提:σ>0 且已得到上下规格限)。
if (sigma.compareTo(BigDecimal.ZERO) > 0 && result.getUsl() != null && result.getLsl() != null) {
// Cp=(USL-LSL)/(6σ), Cpu=(USL-μ)/(3σ), Cpl=(μ-LSL)/(3σ), Cpk=min(Cpu,Cpl)
// 仅当“标准差>0 且规格线齐全”时计算能力指数,避免除零和无意义值
BigDecimal sixSigma = sigma.multiply(new BigDecimal("6"));
BigDecimal threeSigma = sigma.multiply(new BigDecimal("3"));
BigDecimal usl = result.getUsl();
BigDecimal lsl = result.getLsl();
// Cp 反映潜在能力Cpu/Cpl 反映均值偏移Cpk 取二者较小值。
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相同口径
// 当前实现按常见近似直接复用 Cp/Cpk 作为 Pp/Ppk
result.setPp(result.getCp());
result.setPpk(result.getCpk());
}
// 8) 构造直方图分箱数据,供前端能力图直接渲染。
buildHistogram(result, values);
// 9) 构造运行图序列数据(值 + 标签)。
// 这里标签优先用配方编码,若缺失则回退为样本序号,保证前端 x 轴稳定。
// 运行图直接复用 sampleValues因此这里先统一填充
result.setSampleValues(values);
// 标签与样本值一一对应,仅记录有效样本的标签
// 标签优先 recipeCode缺失时退化为序号保证前端 x 轴总有文本
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));
MixTraceSpcSampleVo sample = samples.get(i);
BigDecimal value = actualExtractor.apply(sample);
if (value != null) {
labels.add(StringUtils.defaultIfBlank(sample.getRecipeCode(), String.valueOf(i + 1)));
}
}
result.setSampleLabels(labels);
@ -214,61 +252,53 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
return result;
}
// ==================== SPC运行图图8 ====================
@Override
public MixTraceSpcResultVo calculateSpcRunChart(Map<String, Object> params) {
// 运行图复用样本统计结果,但仅返回运行图所需数据,避免语义混用。
// 运行图复用能力分析样本,只清空直方图相关数据
// 这样可避免重复查询数据库,同时保持均值、规格线、能力指数一致
MixTraceSpcResultVo result = calculateSpcCapability(safeParams(params));
if (result != null) {
// 显式清空直方图字段,避免前端误把 runChart 结果当 capability 结果使用。
result.setHistogramBins(null);
result.setHistogramCounts(null);
}
return result;
}
// ==================== SPC Xbar-R图图10 ====================
@Override
public MixTraceSpcResultVo calculateSpcXbarR(Map<String, Object> params) {
// 1) 参数归一化并确定分析参数。
Map<String, Object> queryParams = safeParams(params);
String paramName = getParamName(queryParams);
// 2) 取全量样本Xbar-R 需要连续样本进行分组统计。
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(queryParams);
if (samples == null || samples.isEmpty()) {
return buildEmptyResult(paramName);
}
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
// 仅保留有效实测值,保证子组计算准确。
// Xbar-R 计算同样只使用有效实测值
List<BigDecimal> values = samples.stream()
.map(actualExtractor)
.filter(v -> v != null)
.collect(Collectors.toList());
// 子组大小默认5可通过参数配置
int subgroupSize = 5;
// 子组大小允许前端传入,但必须限制在 2~10对应常数表范围
if (queryParams.containsKey("subgroupSize")) {
try {
subgroupSize = Integer.parseInt(String.valueOf(queryParams.get("subgroupSize")));
} catch (NumberFormatException ignored) {
// 非法子组大小直接忽略,回退默认值 5
}
}
// 子组大小范围限制 2~10
subgroupSize = Math.max(2, Math.min(10, subgroupSize));
// 至少保证 2 个子组,否则 Xbar-R 控制限意义不足
if (values.size() < subgroupSize * 2) {
// 业务约束:至少需要 2 个完整子组,否则控制图没有统计意义。
MixTraceSpcResultVo result = buildEmptyResult(paramName);
result.setSampleCount(values.size());
return result;
}
// 3) 顺序分组:按样本顺序切分为固定大小子组(忽略最后不完整子组)。
int groupCount = values.size() / subgroupSize;
List<BigDecimal> xbarValues = new ArrayList<>();
List<BigDecimal> rValues = new ArrayList<>();
@ -276,33 +306,28 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
for (int i = 0; i < groupCount; i++) {
int start = i * subgroupSize;
// 每个子组按固定窗口切片,不做滑动窗口,便于和传统 SPC 口径对齐
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 groupMax = group.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
BigDecimal groupRange = groupMax.subtract(groupMin);
// 每个子组提取两个关键统计量:均值(Xbar) 和 极差(R)。
xbarValues.add(groupMean);
rValues.add(groupRange);
subgroupLabels.add("G" + (i + 1));
}
// 总均值与平均极差
BigDecimal xbarbar = calcMean(xbarValues);
BigDecimal rbar = calcMean(rValues);
// 4) 按子组大小查控制图常量 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];
// 常数表下标与子组大小映射关系index = n - 2
int constIndex = subgroupSize - 2;
BigDecimal a2 = BigDecimal.valueOf(XBAR_R_CONSTANTS[constIndex][0]);
BigDecimal d3 = BigDecimal.valueOf(XBAR_R_CONSTANTS[constIndex][1]);
BigDecimal d4 = BigDecimal.valueOf(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));
@ -317,118 +342,242 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
result.setXbarbar(xbarbar);
result.setRbar(rbar);
// 5) Xbar 图控制限:
// UCLx = Xbarbar + A2 * Rbar
// CLx = Xbarbar
// LCLx = Xbarbar - A2 * Rbar
result.setUclX(xbarbar.add(A2.multiply(rbar)).setScale(SCALE, RM));
// Xbar 控制限UCLx=Xbarbar+A2*RbarCLx=XbarbarLCLx=Xbarbar-A2*Rbar
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));
result.setLclX(xbarbar.subtract(a2.multiply(rbar)).setScale(SCALE, RM));
// 6) R 图控制限:
// UCLr = D4 * Rbar
// CLr = Rbar
// LCLr = D3 * Rbar
result.setUclR(D4.multiply(rbar).setScale(SCALE, RM));
// R 控制限UCLr=D4*RbarCLr=RbarLCLr=D3*Rbar
result.setUclR(d4.multiply(rbar).setScale(SCALE, RM));
result.setClR(rbar.setScale(SCALE, RM));
result.setLclR(D3.multiply(rbar).setScale(SCALE, RM));
result.setLclR(d3.multiply(rbar).setScale(SCALE, RM));
return result;
}
// ==================== 工具方法 ====================
/**
* mixingTemp
* mixingTemp
*/
private String getParamName(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
// 未传分析参数时默认按混炼温度分析,保持接口可直接调用。
// 默认按温度统计,符合历史页面默认关注项
return "mixingTemp";
}
Object paramNameObj = params.get("paramName");
// 兼容前端传空字符串场景,统一回退默认参数。
return (paramNameObj != null && !String.valueOf(paramNameObj).isEmpty())
? String.valueOf(paramNameObj) : "mixingTemp";
}
private Map<String, Object> safeParams(Map<String, Object> params) {
// Mapper 层统一假设 map 非空,服务层在入口处兜底。
return params == null ? new HashMap<>() : params;
if (paramNameObj == null || StringUtils.isBlank(String.valueOf(paramNameObj))) {
return "mixingTemp";
}
return normalizeParamName(String.valueOf(paramNameObj));
}
/**
* ->
* SQL
*/
private Map<String, Object> safeParams(Map<String, Object> params) {
Map<String, Object> normalized = new HashMap<>();
if (params == null || params.isEmpty()) {
return normalized;
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getKey() == null) {
// 无键值参数直接丢弃,防止后续 put 触发异常
continue;
}
Object value = entry.getValue();
if (value instanceof String) {
String trimmed = StringUtils.trimToNull((String) value);
if (trimmed != null) {
// 仅保留非空字符串,避免 SQL 出现 like '%%' 之类宽查询
normalized.put(entry.getKey(), trimmed);
}
} else {
// 非字符串值Long、Integer、Date 等)原样透传
normalized.put(entry.getKey(), value);
}
}
return normalized;
}
/**
*
*/
private Map<String, Object> buildBatchQueryParams(Map<String, Object> detailParams) {
Map<String, Object> batchParams = new HashMap<>();
if (detailParams == null || detailParams.isEmpty()) {
return batchParams;
}
// 添加租户ID用于多租户过滤
batchParams.put("tenantId", TenantHelper.getTenantId());
String productionBarcode = getStringParam(detailParams, "productionBarcode");
String planDetailId = getStringParam(detailParams, "planDetailId");
String planId = getStringParam(detailParams, "planId");
String planCode = getStringParam(detailParams, "planCode");
if (StringUtils.isNotBlank(productionBarcode)) {
// 命中本车条码后立即返回,避免再放大到计划级别
batchParams.put("productionBarcode", productionBarcode);
return batchParams;
}
if (StringUtils.isNotBlank(planDetailId)) {
// 次优先:计划明细 ID
batchParams.put("planDetailId", planDetailId);
} else if (StringUtils.isNotBlank(planId)) {
// 再次优先:计划主表 ID
batchParams.put("planId", planId);
} else if (StringUtils.isNotBlank(planCode)) {
// 最后兜底:计划编码(模糊匹配)
batchParams.put("planCode", planCode);
}
return batchParams;
}
private String getStringParam(Map<String, Object> params, String key) {
if (params == null || key == null) {
return null;
}
Object value = params.get(key);
return value == null ? null : StringUtils.trimToNull(String.valueOf(value));
}
/**
*
*/
private boolean hasDetailScopeFilter(Map<String, Object> params) {
// 这里列出的字段均属于“本车/本计划”范围字段,只要任一存在即视为强约束查询
return isNotBlankParam(params, "planId")
|| isNotBlankParam(params, "planCode")
|| isNotBlankParam(params, "planDetailId")
|| isNotBlankParam(params, "planDetailCode")
|| isNotBlankParam(params, "productionBarcode")
|| isNotBlankParam(params, "shiftId")
|| isNotBlankParam(params, "classTeamId")
|| isNotBlankParam(params, "shiftName")
|| isNotBlankParam(params, "classTeamName");
}
private boolean isNotBlankParam(Map<String, Object> params, String key) {
if (params == null || key == null) {
return false;
}
Object value = params.get(key);
return value != null && StringUtils.isNotBlank(String.valueOf(value));
}
/**
*
*/
private String getParamLabel(String paramName) {
return paramName;
switch (paramName) {
case "mixingTemp":
return "混炼温度";
case "mixingTime":
return "混炼时间";
case "mixingEnergy":
return "混炼能量";
case "mixingPower":
return "混炼功率";
case "mixingPress":
return "混炼压力";
case "mixingSpeed":
return "混炼转速";
default:
return "混炼温度";
}
}
/**
*
*
*/
private String normalizeParamName(String paramName) {
switch (paramName) {
case "mixingTemp":
case "mixingTime":
case "mixingEnergy":
case "mixingPower":
case "mixingPress":
case "mixingSpeed":
return paramName;
default:
return "mixingTemp";
}
}
/**
*
*/
private Function<MixTraceSpcSampleVo, BigDecimal> getActualExtractor(String paramName) {
// 参数名与“实测字段”一一映射,时间/转速等整型字段转 BigDecimal 统一参与计算。
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;
case "mixingTemp":
return MixTraceSpcSampleVo::getMixingTemp;
case "mixingTime":
return sample -> sample.getMixingTime() == null ? null : BigDecimal.valueOf(sample.getMixingTime());
case "mixingEnergy":
return MixTraceSpcSampleVo::getMixingEnergy;
case "mixingPower":
return MixTraceSpcSampleVo::getMixingPower;
case "mixingPress":
return MixTraceSpcSampleVo::getMixingPress;
case "mixingSpeed":
return sample -> sample.getMixingSpeed() == null ? null : BigDecimal.valueOf(sample.getMixingSpeed());
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;
case "mixingTemp":
return sample -> sample.getSetTemp() == null ? null : BigDecimal.valueOf(sample.getSetTemp());
case "mixingTime":
return sample -> sample.getSetTime() == null ? null : BigDecimal.valueOf(sample.getSetTime());
case "mixingEnergy":
return MixTraceSpcSampleVo::getSetEnergy;
case "mixingPower":
return MixTraceSpcSampleVo::getSetPower;
case "mixingPress":
return MixTraceSpcSampleVo::getSetPres;
case "mixingSpeed":
return sample -> sample.getSetRota() == null ? null : BigDecimal.valueOf(sample.getSetRota());
default:
return sample -> sample.getSetTemp() == null ? null : BigDecimal.valueOf(sample.getSetTemp());
}
}
/**
*
*
*/
private BigDecimal calcMean(List<BigDecimal> values) {
if (values == null || values.isEmpty()) {
return BigDecimal.ZERO;
}
// BigDecimal 全程计算,避免 double 误差影响 SPC 指标。
BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(BigDecimal.valueOf(values.size()), SCALE, RM);
}
/**
*
* n-1
*/
private BigDecimal calcStdDev(List<BigDecimal> values, BigDecimal mean) {
if (values == null || values.size() < 2) {
return BigDecimal.ZERO;
}
// 先算离差平方和,再按 n-1 求样本方差。
// 先算离差平方和,再按样本方差(n-1)开方得到样本标准差
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));
@ -437,21 +586,19 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
}
/**
* 10
* 10
*/
private void buildHistogram(MixTraceSpcResultVo result, List<BigDecimal> values) {
if (values == null || values.size() < 2) {
// 样本过少时不生成分箱,前端直方图区域自然不显示。
return;
}
// 采用固定 10 箱的等宽分箱策略,优先保证可解释性与稳定渲染。
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());
@ -471,11 +618,10 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
int finalI = i;
long count = values.stream()
.filter(v -> {
// 最后一个分箱右闭区间,确保最大值不会因边界比较丢失。
if (finalI == binCount - 1) {
// 最后一个箱包含上边界,避免 max 值丢失
return v.compareTo(lower) >= 0 && v.compareTo(upper) <= 0;
}
// 其余分箱采用左闭右开,避免边界值重复计数。
return v.compareTo(lower) >= 0 && v.compareTo(upper) < 0;
})
.count();
@ -486,4 +632,91 @@ public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService
result.setHistogramCounts(counts);
result.setHistogramBins(bins);
}
private List<MixTraceCurvePointVo> buildCurveSeries(List<MixTraceStepVo> mixingStepList) {
if (mixingStepList == null || mixingStepList.isEmpty()) {
return new ArrayList<>();
}
List<MixTraceCurvePointVo> points = new ArrayList<>();
for (MixTraceStepVo step : mixingStepList.stream()
.sorted(Comparator.comparing(MixTraceStepVo::getMixId, Comparator.nullsLast(Long::compareTo)))
.collect(Collectors.toList())) {
// 先按 mixId 排序,保证曲线时间顺序与工步表一致
MixTraceCurvePointVo point = new MixTraceCurvePointVo();
point.setStepNo(step.getMixId());
point.setTimelineSecond(step.getTimelineSecond());
// 横轴统一使用“步骤N”和工步表格保持一致
point.setXLabel(step.getMixId() == null ? "-" : "步骤" + step.getMixId());
point.setTemperature(step.getMixingTemp());
point.setPower(step.getMixingPower());
point.setEnergy(step.getMixingEnergy());
point.setPressure(step.getMixingPress());
point.setSpeed(step.getMixingSpeed());
points.add(point);
}
return points;
}
private List<MixTraceMaterialTraceTreeVo> buildMaterialTraceTree(MixTraceSummaryVo summaryInfo,
List<MixTraceBatchVo> batchList) {
if (summaryInfo == null) {
return new ArrayList<>();
}
// 根节点代表“本车”
MixTraceMaterialTraceTreeVo root = new MixTraceMaterialTraceTreeVo();
String rootBarcode = StringUtils.defaultString(summaryInfo.getProductionBarcode(), "");
root.setId("root-" + summaryInfo.getRecipeId() + "-" + rootBarcode);
root.setNodeType("car");
root.setRecipeId(summaryInfo.getRecipeId());
root.setPlanDetailId(summaryInfo.getPlanDetailId());
root.setProductionBarcode(summaryInfo.getProductionBarcode());
root.setMaterialId(summaryInfo.getMaterialId());
root.setMaterialName(summaryInfo.getMaterialName());
root.setLabel(StringUtils.defaultIfBlank(summaryInfo.getRecipeCode(), "") + " / " + rootBarcode);
if (batchList == null || batchList.isEmpty()) {
// 没有批次时仍返回根节点,前端可展示“本车无耗用批次”
List<MixTraceMaterialTraceTreeVo> tree = new ArrayList<>();
tree.add(root);
return tree;
}
// 使用 LinkedHashMap 保留插入顺序,前端树节点顺序稳定
Map<String, MixTraceMaterialTraceTreeVo> materialNodeMap = new LinkedHashMap<>();
for (int i = 0; i < batchList.size(); i++) {
MixTraceBatchVo batch = batchList.get(i);
String materialKey = (batch.getMaterialId() == null ? "0" : String.valueOf(batch.getMaterialId()))
+ "_" + StringUtils.defaultString(batch.getMaterialName(), "");
MixTraceMaterialTraceTreeVo materialNode = materialNodeMap.computeIfAbsent(materialKey, key -> {
// 物料层节点按 materialId + materialName 去重
MixTraceMaterialTraceTreeVo node = new MixTraceMaterialTraceTreeVo();
node.setId("mat-" + key);
node.setNodeType("material");
node.setMaterialId(batch.getMaterialId());
node.setMaterialName(batch.getMaterialName());
node.setLabel(StringUtils.defaultIfBlank(batch.getMaterialName(),
batch.getMaterialId() == null ? "" : String.valueOf(batch.getMaterialId())));
return node;
});
MixTraceMaterialTraceTreeVo batchNode = new MixTraceMaterialTraceTreeVo();
batchNode.setId("batch-" + (batch.getBatchId() == null ? i : batch.getBatchId()));
batchNode.setNodeType("batch");
batchNode.setProductionBarcode(batch.getProductionBarcode());
batchNode.setMaterialId(batch.getMaterialId());
batchNode.setMaterialName(batch.getMaterialName());
batchNode.setBatchCode(batch.getBatchCode());
batchNode.setLabel(StringUtils.defaultIfBlank(batch.getBatchCode(),
StringUtils.defaultIfBlank(batch.getInputBarcode(), StringUtils.defaultString(batch.getProductionBarcode(), ""))));
materialNode.getChildren().add(batchNode);
}
root.getChildren().addAll(materialNodeMap.values());
List<MixTraceMaterialTraceTreeVo> tree = new ArrayList<>();
tree.add(root);
return tree;
}
}

@ -16,6 +16,93 @@
)
</sql>
<sql id="tracePickApply">
OUTER APPLY (
SELECT TOP 1
p.plan_id AS planId,
p.plan_code AS planCode,
p.plan_amount AS planAmount,
p.complete_amount AS planCompleteAmount,
p.real_begin_time AS planRealBeginTime,
p.real_end_time AS planRealEndTime,
ppd.plan_detail_id AS planDetailId,
ppd.plan_detail_code AS planDetailCode,
ppd.complete_amount AS detailCompleteAmount,
ppd.real_begin_time AS detailRealBeginTime,
ppd.real_end_time AS detailRealEndTime,
ppd.train_number AS trainNumber,
ppd.alarm_flag AS alarmFlag,
ppd.plan_detail_status AS planDetailStatus,
COALESCE(ppd.shift_id, p.shift_id) AS shiftId,
COALESCE(ppd.class_team_id, p.class_team_id) AS classTeamId,
bsi.shift_name AS shiftName,
bcti.team_name AS classTeamName,
COALESCE(NULLIF(LTRIM(RTRIM(ppd.return_barcode)), ''), NULLIF(LTRIM(RTRIM(ppd.material_barcode)), '')) AS productionBarcode
FROM prod_plan_info_1 p
LEFT JOIN prod_product_plan_detail_1 ppd ON ppd.plan_id = p.plan_id AND ISNULL(ppd.del_flag, '0') = '0'
LEFT JOIN base_shift_info bsi ON bsi.shift_id = COALESCE(ppd.shift_id, p.shift_id)
LEFT JOIN base_class_team_info bcti ON bcti.class_team_id = COALESCE(ppd.class_team_id, p.class_team_id)
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (
p.release_type IS NULL
OR p.release_type &lt;&gt; '1'
OR p.release_id = ri.machine_id
)
<if test="map.planId != null and map.planId != ''">
AND p.plan_id = #{map.planId}
</if>
<if test="map.planCode != null and map.planCode != ''">
AND p.plan_code LIKE CONCAT('%', #{map.planCode}, '%')
</if>
<if test="map.planDetailId != null and map.planDetailId != ''">
AND ppd.plan_detail_id = #{map.planDetailId}
</if>
<if test="map.planDetailCode != null and map.planDetailCode != ''">
AND ppd.plan_detail_code LIKE CONCAT('%', #{map.planDetailCode}, '%')
</if>
<if test="map.shiftId != null and map.shiftId != ''">
AND COALESCE(ppd.shift_id, p.shift_id) = #{map.shiftId}
</if>
<if test="map.classTeamId != null and map.classTeamId != ''">
AND COALESCE(ppd.class_team_id, p.class_team_id) = #{map.classTeamId}
</if>
<if test="map.shiftName != null and map.shiftName != ''">
AND bsi.shift_name LIKE CONCAT('%', #{map.shiftName}, '%')
</if>
<if test="map.classTeamName != null and map.classTeamName != ''">
AND bcti.team_name LIKE CONCAT('%', #{map.classTeamName}, '%')
</if>
<if test="map.productionBarcode != null and map.productionBarcode != ''">
AND (
LTRIM(RTRIM(COALESCE(ppd.return_barcode, ''))) = LTRIM(RTRIM(#{map.productionBarcode}))
OR LTRIM(RTRIM(COALESCE(ppd.material_barcode, ''))) = LTRIM(RTRIM(#{map.productionBarcode}))
)
</if>
ORDER BY
CASE
WHEN #{map.planDetailId} IS NOT NULL
AND LTRIM(RTRIM(CONVERT(VARCHAR(64), #{map.planDetailId}))) &lt;&gt; ''
AND CONVERT(VARCHAR(64), ppd.plan_detail_id) = CONVERT(VARCHAR(64), #{map.planDetailId}) THEN 0
ELSE 1
END,
CASE
WHEN #{map.productionBarcode} IS NOT NULL
AND (
LTRIM(RTRIM(COALESCE(ppd.return_barcode, ''))) = LTRIM(RTRIM(#{map.productionBarcode}))
OR LTRIM(RTRIM(COALESCE(ppd.material_barcode, ''))) = LTRIM(RTRIM(#{map.productionBarcode}))
) THEN 0
ELSE 1
END,
COALESCE(ppd.real_begin_time, ppd.create_time, p.real_begin_time, p.create_time) DESC
) tp
</sql>
<!-- trace list -->
<select id="selectTraceList" resultType="org.dromara.mes.domain.vo.MixTraceListVo">
SELECT
@ -41,10 +128,25 @@
(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
WHERE m.recipe_id = ri.recipe_id AND m.del_flag = '0') AS mixingCount,
tp.planId AS planId,
tp.planCode AS planCode,
tp.planDetailId AS planDetailId,
tp.planDetailCode AS planDetailCode,
tp.productionBarcode AS productionBarcode,
tp.shiftId AS shiftId,
tp.shiftName AS shiftName,
tp.classTeamId AS classTeamId,
tp.classTeamName AS classTeamName,
tp.planAmount AS planAmount,
COALESCE(tp.detailCompleteAmount, tp.planCompleteAmount) AS completeAmount,
tp.trainNumber AS trainNumber,
COALESCE(tp.detailRealBeginTime, tp.planRealBeginTime) AS realBeginTime,
COALESCE(tp.detailRealEndTime, tp.planRealEndTime) AS realEndTime
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
<include refid="tracePickApply"/>
WHERE ri.del_flag = '0'
<if test="map.recipeCode != null and map.recipeCode != ''">
AND ri.recipe_code LIKE CONCAT('%', #{map.recipeCode}, '%')
@ -83,6 +185,17 @@
AND ri.create_time &gt;= CAST(CONCAT(#{map.beginDate}, ' 00:00:00') AS DATETIME)
AND ri.create_time &lt; DATEADD(DAY, 1, CAST(#{map.endDate} AS DATE))
</if>
<if test="(map.planId != null and map.planId != '')
or (map.planCode != null and map.planCode != '')
or (map.planDetailId != null and map.planDetailId != '')
or (map.planDetailCode != null and map.planDetailCode != '')
or (map.productionBarcode != null and map.productionBarcode != '')
or (map.shiftId != null and map.shiftId != '')
or (map.classTeamId != null and map.classTeamId != '')
or (map.shiftName != null and map.shiftName != '')
or (map.classTeamName != null and map.classTeamName != '')">
AND tp.planId IS NOT NULL
</if>
ORDER BY ri.create_time DESC
</select>
@ -111,12 +224,148 @@
(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
WHERE m.recipe_id = ri.recipe_id AND m.del_flag = '0') AS mixingCount,
tp.planId AS planId,
tp.planCode AS planCode,
tp.planDetailId AS planDetailId,
tp.planDetailCode AS planDetailCode,
tp.productionBarcode AS productionBarcode,
tp.shiftId AS shiftId,
tp.shiftName AS shiftName,
tp.classTeamId AS classTeamId,
tp.classTeamName AS classTeamName,
tp.planAmount AS planAmount,
COALESCE(tp.detailCompleteAmount, tp.planCompleteAmount) AS completeAmount,
tp.trainNumber AS trainNumber,
COALESCE(tp.detailRealBeginTime, tp.planRealBeginTime) AS realBeginTime,
COALESCE(tp.detailRealEndTime, tp.planRealEndTime) AS realEndTime
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
<include refid="tracePickApply"/>
WHERE ri.del_flag = '0'
AND ri.recipe_id = #{recipeId}
AND ri.recipe_id = #{map.recipeId}
<if test="(map.planId != null and map.planId != '')
or (map.planCode != null and map.planCode != '')
or (map.planDetailId != null and map.planDetailId != '')
or (map.planDetailCode != null and map.planDetailCode != '')
or (map.productionBarcode != null and map.productionBarcode != '')
or (map.shiftId != null and map.shiftId != '')
or (map.classTeamId != null and map.classTeamId != '')
or (map.shiftName != null and map.shiftName != '')
or (map.classTeamName != null and map.classTeamName != '')">
AND tp.planId IS NOT NULL
</if>
</select>
<!-- trace detail summary -->
<select id="selectTraceSummary" resultType="org.dromara.mes.domain.vo.MixTraceSummaryVo">
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,
tp.planId AS planId,
tp.planCode AS planCode,
tp.planDetailId AS planDetailId,
tp.planDetailCode AS planDetailCode,
COALESCE(
NULLIF(LTRIM(RTRIM(CONVERT(VARCHAR(128), #{map.productionBarcode}))), ''),
tp.productionBarcode
) AS productionBarcode,
tp.shiftId AS shiftId,
tp.shiftName AS shiftName,
tp.classTeamId AS classTeamId,
tp.classTeamName AS classTeamName,
tp.planAmount AS planAmount,
ri.total_weight AS settingWeight,
COALESCE(tp.detailCompleteAmount, tp.planCompleteAmount) AS completedWeight,
ri.shelf_num AS trayCount,
tp.trainNumber AS mixingTrainNo,
train_stat.totalTrainNo AS totalTrainNo,
tp.alarmFlag AS overToleranceAlarm,
CASE
WHEN train_stat.totalTrainNo IS NOT NULL AND train_stat.totalTrainNo &gt; 0
AND mix_sum.totalMixingEnergy IS NOT NULL
THEN CAST(mix_sum.totalMixingEnergy / train_stat.totalTrainNo AS DECIMAL(18,4))
ELSE NULL
END AS eachCarEnergy,
mix_last.dischargeTemp AS dischargeTemp,
mix_last.dischargePower AS dischargePower,
mix_last.dischargeEnergy AS dischargeEnergy,
tp.planDetailStatus AS mixingStatus,
mix_sum.totalMixingTime AS mixingTime,
CASE
WHEN tp.detailRealBeginTime IS NOT NULL AND tp.detailRealEndTime IS NOT NULL
THEN DATEDIFF(SECOND, tp.detailRealBeginTime, tp.detailRealEndTime)
WHEN tp.planRealBeginTime IS NOT NULL AND tp.planRealEndTime IS NOT NULL
THEN DATEDIFF(SECOND, tp.planRealBeginTime, tp.planRealEndTime)
ELSE NULL
END AS consumeTime,
CASE
WHEN tp.detailRealBeginTime IS NOT NULL AND prev_stat.prevRealEndTime IS NOT NULL
THEN DATEDIFF(SECOND, prev_stat.prevRealEndTime, tp.detailRealBeginTime)
ELSE NULL
END AS intervalTime,
COALESCE(tp.detailRealBeginTime, tp.planRealBeginTime, ri.create_time) AS beginProduceTime,
COALESCE(tp.detailRealEndTime, tp.planRealEndTime) AS endProduceTime
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
<include refid="tracePickApply"/>
OUTER APPLY (
SELECT
SUM(ISNULL(m.mixing_time, 0)) AS totalMixingTime,
SUM(ISNULL(m.mixing_energy, 0)) AS totalMixingEnergy
FROM prod_recipe_mixing m
WHERE m.del_flag = '0'
AND m.recipe_id = ri.recipe_id
) mix_sum
OUTER APPLY (
SELECT TOP 1
m.mixing_temp AS dischargeTemp,
m.mixing_power AS dischargePower,
m.mixing_energy AS dischargeEnergy
FROM prod_recipe_mixing m
WHERE m.del_flag = '0'
AND m.recipe_id = ri.recipe_id
ORDER BY m.mix_id DESC
) mix_last
OUTER APPLY (
SELECT CAST(COUNT(1) AS BIGINT) AS totalTrainNo
FROM prod_product_plan_detail_1 d
WHERE tp.planId IS NOT NULL
AND d.plan_id = tp.planId
AND ISNULL(d.del_flag, '0') = '0'
) train_stat
OUTER APPLY (
SELECT TOP 1 d.real_end_time AS prevRealEndTime
FROM prod_product_plan_detail_1 d
WHERE tp.planId IS NOT NULL
AND tp.planDetailId IS NOT NULL
AND d.plan_id = tp.planId
AND ISNULL(d.del_flag, '0') = '0'
AND d.plan_detail_id &lt;&gt; tp.planDetailId
AND d.real_end_time IS NOT NULL
AND tp.detailRealBeginTime IS NOT NULL
AND d.real_end_time &lt;= tp.detailRealBeginTime
ORDER BY d.real_end_time DESC
) prev_stat
WHERE ri.del_flag = '0'
AND ri.recipe_id = #{map.recipeId}
<if test="(map.planId != null and map.planId != '')
or (map.planCode != null and map.planCode != '')
or (map.planDetailId != null and map.planDetailId != '')
or (map.planDetailCode != null and map.planDetailCode != '')
or (map.productionBarcode != null and map.productionBarcode != '')
or (map.shiftId != null and map.shiftId != '')
or (map.classTeamId != null and map.classTeamId != '')
or (map.shiftName != null and map.shiftName != '')
or (map.classTeamName != null and map.classTeamName != '')">
AND tp.planId IS NOT NULL
</if>
</select>
<!-- trace detail weight list -->
@ -157,6 +406,87 @@
ORDER BY w.weight_seq ASC
</select>
<!-- trace detail usage list -->
<select id="selectTraceUsageList" resultType="org.dromara.mes.domain.vo.MixTraceUsageVo">
<include refid="routerMasterDataCte"/>
SELECT
w.weight_id AS usageId,
w.weight_seq AS weightSeq,
RTRIM(LTRIM(COALESCE(w.weight_type, ''))) AS categoryName,
COALESCE(cm.material_name, fm.material_name, act_detail.data_detail_name, RTRIM(LTRIM(COALESCE(w.act_code, '')))) AS materialName,
w.set_weight AS setWeight,
trace_used.actualWeight AS actualWeight,
w.error_allow AS tolerance,
CASE
WHEN trace_used.actualWeight IS NULL OR w.set_weight IS NULL THEN NULL
ELSE trace_used.actualWeight - w.set_weight
END AS diffWeight,
CASE
WHEN trace_used.actualWeight IS NULL OR w.set_weight IS NULL OR w.error_allow IS NULL THEN NULL
WHEN ABS(trace_used.actualWeight - w.set_weight) > w.error_allow THEN '1'
ELSE '0'
END AS overToleranceFlag,
RTRIM(LTRIM(COALESCE(w.if_use_bat, ''))) AS controlMode,
RTRIM(LTRIM(COALESCE(w.act_code, ''))) AS actCode,
CASE
WHEN act_detail.data_detail_name IS NULL OR LTRIM(RTRIM(act_detail.data_detail_name)) = '' THEN RTRIM(LTRIM(COALESCE(w.act_code, '')))
ELSE RTRIM(LTRIM(act_detail.data_detail_name))
END AS actName
FROM prod_recipe_weight w
CROSS JOIN router_master_data rmd
LEFT JOIN base_material_info cm ON cm.material_id = w.child_code
LEFT JOIN base_material_info fm ON fm.material_id = w.father_code
OUTER APPLY (
SELECT TOP 1 d.data_detail_name
FROM sys_master_data_detail d
WHERE d.del_flag = '0'
AND d.active_flag = '1'
AND d.master_data_id = rmd.act_master_data_id
AND RTRIM(LTRIM(COALESCE(d.data_detail_code, ''))) = RTRIM(LTRIM(COALESCE(w.act_code, '')))
ORDER BY d.master_data_detail_id DESC
) act_detail
<if test="map.productionBarcode != null and map.productionBarcode != ''">
OUTER APPLY (
SELECT
CASE
WHEN ISNULL(SUM(CASE WHEN t.materiel_id = w.child_code THEN 1 ELSE 0 END), 0) = 0
AND ISNULL(SUM(CASE
WHEN w.father_code IS NOT NULL
AND w.father_code &lt;&gt; w.child_code
AND t.materiel_id = w.father_code THEN 1
ELSE 0
END), 0) = 0
THEN NULL
ELSE
ISNULL(SUM(CASE WHEN t.materiel_id = w.child_code THEN ISNULL(t.used_amount, 0) ELSE 0 END), 0)
+ ISNULL(SUM(CASE
WHEN w.father_code IS NOT NULL
AND w.father_code &lt;&gt; w.child_code
AND t.materiel_id = w.father_code
THEN ISNULL(t.used_amount, 0)
ELSE 0
END), 0)
END AS actualWeight
FROM prod_trace_cur_info t
WHERE t.production_barcode = #{map.productionBarcode}
AND (
t.materiel_id = w.child_code
OR (
w.father_code IS NOT NULL
AND w.father_code &lt;&gt; w.child_code
AND t.materiel_id = w.father_code
)
)
) trace_used
</if>
<if test="map.productionBarcode == null or map.productionBarcode == ''">
OUTER APPLY (SELECT CAST(NULL AS DECIMAL(18, 4)) AS actualWeight) trace_used
</if>
WHERE w.del_flag = '0'
AND w.recipe_id = #{map.recipeId}
ORDER BY w.weight_seq ASC
</select>
<!-- trace detail mixing list -->
<select id="selectMixingListByRecipeId" resultType="org.dromara.mes.domain.vo.ProdRecipeMixingVo">
<include refid="routerMasterDataCte"/>
@ -226,6 +556,140 @@
ORDER BY m.mix_id ASC
</select>
<!-- trace detail step list -->
<select id="selectTraceStepList" resultType="org.dromara.mes.domain.vo.MixTraceStepVo">
<include refid="routerMasterDataCte"/>
SELECT
m.mixing_id AS mixingId,
m.mix_id AS mixId,
SUM(ISNULL(m.mixing_time, 0)) OVER (ORDER BY m.mix_id ROWS UNBOUNDED PRECEDING) AS timelineSecond,
RTRIM(LTRIM(COALESCE(m.term_code, ''))) AS termCode,
CASE
WHEN term_detail.data_detail_name IS NULL OR LTRIM(RTRIM(term_detail.data_detail_name)) = '' THEN RTRIM(LTRIM(COALESCE(m.term_code, '')))
ELSE RTRIM(LTRIM(term_detail.data_detail_name))
END AS termName,
RTRIM(LTRIM(COALESCE(m.cond_code, ''))) AS condCode,
CASE
WHEN cond_detail.data_detail_name IS NULL OR LTRIM(RTRIM(cond_detail.data_detail_name)) = '' THEN RTRIM(LTRIM(COALESCE(m.cond_code, '')))
ELSE RTRIM(LTRIM(cond_detail.data_detail_name))
END AS condName,
RTRIM(LTRIM(COALESCE(m.act_code, ''))) AS actCode,
CASE
WHEN act_detail.data_detail_name IS NULL OR LTRIM(RTRIM(act_detail.data_detail_name)) = '' THEN RTRIM(LTRIM(COALESCE(m.act_code, '')))
ELSE RTRIM(LTRIM(act_detail.data_detail_name))
END AS actName,
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.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
CROSS JOIN router_master_data rmd
OUTER APPLY (
SELECT TOP 1 d.data_detail_name
FROM sys_master_data_detail d
WHERE d.del_flag = '0'
AND d.active_flag = '1'
AND d.master_data_id = rmd.term_master_data_id
AND RTRIM(LTRIM(COALESCE(d.data_detail_code, ''))) = RTRIM(LTRIM(COALESCE(m.term_code, '')))
ORDER BY d.master_data_detail_id DESC
) term_detail
OUTER APPLY (
SELECT TOP 1 d.data_detail_name
FROM sys_master_data_detail d
WHERE d.del_flag = '0'
AND d.active_flag = '1'
AND d.master_data_id = rmd.term_master_data_id
AND RTRIM(LTRIM(COALESCE(d.data_detail_code, ''))) = RTRIM(LTRIM(COALESCE(m.cond_code, '')))
ORDER BY d.master_data_detail_id DESC
) cond_detail
OUTER APPLY (
SELECT TOP 1 d.data_detail_name
FROM sys_master_data_detail d
WHERE d.del_flag = '0'
AND d.active_flag = '1'
AND d.master_data_id = rmd.act_master_data_id
AND RTRIM(LTRIM(COALESCE(d.data_detail_code, ''))) = RTRIM(LTRIM(COALESCE(m.act_code, '')))
ORDER BY d.master_data_detail_id DESC
) act_detail
WHERE m.del_flag = '0'
AND m.recipe_id = #{map.recipeId}
ORDER BY m.mix_id ASC
</select>
<!-- trace detail batch list -->
<select id="selectTraceBatchList" resultType="org.dromara.mes.domain.vo.MixTraceBatchVo">
WITH target_barcodes AS (
SELECT #{map.productionBarcode} AS production_barcode,
#{map.tenantId} AS tenant_id
WHERE #{map.productionBarcode} IS NOT NULL
AND #{map.productionBarcode} &lt;&gt; ''
<if test="(map.productionBarcode == null or map.productionBarcode == '')
and (
(map.planDetailId != null and map.planDetailId != '')
or (map.planId != null and map.planId != '')
or (map.planCode != null and map.planCode != '')
)">
UNION
SELECT DISTINCT d.return_barcode,
#{map.tenantId} AS tenant_id
FROM prod_product_plan_detail_1 d
WHERE ISNULL(d.del_flag, '0') = '0'
AND d.return_barcode IS NOT NULL
AND d.return_barcode &lt;&gt; ''
<if test="map.planDetailId != null and map.planDetailId != ''">
AND d.plan_detail_id = #{map.planDetailId}
</if>
<if test="map.planId != null and map.planId != ''">
AND d.plan_id = #{map.planId}
</if>
<if test="map.planCode != null and map.planCode != ''">
AND d.plan_code LIKE CONCAT('%', #{map.planCode}, '%')
</if>
UNION
SELECT DISTINCT d.material_barcode,
#{map.tenantId} AS tenant_id
FROM prod_product_plan_detail_1 d
WHERE ISNULL(d.del_flag, '0') = '0'
AND d.material_barcode IS NOT NULL
AND d.material_barcode &lt;&gt; ''
<if test="map.planDetailId != null and map.planDetailId != ''">
AND d.plan_detail_id = #{map.planDetailId}
</if>
<if test="map.planId != null and map.planId != ''">
AND d.plan_id = #{map.planId}
</if>
<if test="map.planCode != null and map.planCode != ''">
AND d.plan_code LIKE CONCAT('%', #{map.planCode}, '%')
</if>
</if>
)
SELECT TOP 200
pis.prod_input_scan_info_id AS batchId,
pis.production_barcode AS productionBarcode,
pis.input_barcode AS batchCode,
pis.input_barcode AS inputBarcode,
pis.materiel_id AS materialId,
bmi.material_name AS materialName,
pis.feeding_time AS instockTime,
pis.user_name AS supplierName
FROM target_barcodes tb
INNER JOIN prod_input_scan_info pis ON pis.production_barcode = tb.production_barcode
AND pis.tenant_id = tb.tenant_id
LEFT JOIN base_material_info bmi ON bmi.material_id = pis.materiel_id
AND bmi.tenant_id = tb.tenant_id
WHERE tb.production_barcode IS NOT NULL
AND tb.production_barcode &lt;&gt; ''
ORDER BY pis.feeding_time DESC, pis.prod_input_scan_info_id DESC
</select>
<!-- spc samples -->
<select id="selectSpcSamples" resultType="org.dromara.mes.domain.vo.MixTraceSpcSampleVo">
<include refid="routerMasterDataCte"/>
@ -316,6 +780,125 @@
<if test="map.termCode != null and map.termCode != ''">
AND rm.term_code = #{map.termCode}
</if>
<if test="map.planId != null and map.planId != ''">
AND EXISTS (
SELECT 1 FROM prod_plan_info_1 p
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND p.plan_id = #{map.planId}
)
</if>
<if test="map.planCode != null and map.planCode != ''">
AND EXISTS (
SELECT 1 FROM prod_plan_info_1 p
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND p.plan_code LIKE CONCAT('%', #{map.planCode}, '%')
)
</if>
<if test="map.planDetailId != null and map.planDetailId != ''">
AND EXISTS (
SELECT 1
FROM prod_plan_info_1 p
INNER JOIN prod_product_plan_detail_1 d ON d.plan_id = p.plan_id AND ISNULL(d.del_flag, '0') = '0'
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND d.plan_detail_id = #{map.planDetailId}
)
</if>
<if test="map.planDetailCode != null and map.planDetailCode != ''">
AND EXISTS (
SELECT 1
FROM prod_plan_info_1 p
INNER JOIN prod_product_plan_detail_1 d ON d.plan_id = p.plan_id AND ISNULL(d.del_flag, '0') = '0'
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND d.plan_detail_code LIKE CONCAT('%', #{map.planDetailCode}, '%')
)
</if>
<if test="map.shiftId != null and map.shiftId != ''">
AND EXISTS (
SELECT 1
FROM prod_plan_info_1 p
LEFT JOIN prod_product_plan_detail_1 d ON d.plan_id = p.plan_id AND ISNULL(d.del_flag, '0') = '0'
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND COALESCE(d.shift_id, p.shift_id) = #{map.shiftId}
)
</if>
<if test="map.classTeamId != null and map.classTeamId != ''">
AND EXISTS (
SELECT 1
FROM prod_plan_info_1 p
LEFT JOIN prod_product_plan_detail_1 d ON d.plan_id = p.plan_id AND ISNULL(d.del_flag, '0') = '0'
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND COALESCE(d.class_team_id, p.class_team_id) = #{map.classTeamId}
)
</if>
<if test="map.productionBarcode != null and map.productionBarcode != ''">
AND (
EXISTS (
SELECT 1
FROM prod_plan_info_1 p
INNER JOIN prod_product_plan_detail_1 d ON d.plan_id = p.plan_id AND ISNULL(d.del_flag, '0') = '0'
WHERE (
p.recipe_id = ri.recipe_id
OR (
ISNULL(p.recipe_id, 0) = 0
AND p.material_id = ri.material_id
)
)
AND (p.release_type IS NULL OR p.release_type &lt;&gt; '1' OR p.release_id = ri.machine_id)
AND (
RTRIM(LTRIM(COALESCE(d.return_barcode, ''))) = RTRIM(LTRIM(#{map.productionBarcode}))
OR RTRIM(LTRIM(COALESCE(d.material_barcode, ''))) = RTRIM(LTRIM(#{map.productionBarcode}))
)
)
OR EXISTS (
SELECT 1
FROM prod_input_scan_info pis
WHERE pis.machine_id = ri.machine_id
AND RTRIM(LTRIM(COALESCE(pis.production_barcode, ''))) = RTRIM(LTRIM(#{map.productionBarcode}))
)
)
</if>
<if test="map.beginDate != null and map.beginDate != '' and map.endDate != null and map.endDate != ''">
AND ri.create_time &gt;= CAST(CONCAT(#{map.beginDate}, ' 00:00:00') AS DATETIME)
AND ri.create_time &lt; DATEADD(DAY, 1, CAST(#{map.endDate} AS DATE))

Loading…
Cancel
Save