跳转到内容

案例 31:原材料来料验收标准动态适配

本案例对应 JQuick-Java USE-CASES 场景 31(诉求 36:原材料来料验收标准动态适配),属于制造 MES 行业领域,决策内容是对 IQC 来料检验批次做缺陷率分档与多维质量评分,输出接收入库、让步接收或整批拒收结论及处置要求。

离散制造企业的原材料入厂环节,IQC 检验员按批次抽检并记录要素。入参模型 MaterialInspection 承载来料批次号、物料编码、物料类别(ELECTRONIC 电子件 / METAL 金属件 / CHEMICAL 化工 / PLASTIC 塑胶)、抽检样本数,以及三级缺陷计数:致命缺陷(安全/功能零容忍)、严重缺陷、轻微缺陷;同时记录是否随附质保书/COA、外观是否合格、关键尺寸是否合格、RoHS/环保合规是否通过,以及供应商评级 A/B/C。

验收决策分两段执行。第一段是零容忍硬门槛:只要出现致命缺陷、缺少质保书/COA 或环保合规不通过,Service 直接判定整批拒收、综合质量分置 0、标记 hardReject,不再进入评分流程,并逐条给出供应商问责、冻结在途库存等处置说明。第二段是六维质量评分:严重缺陷率 30 分、轻微缺陷率 20 分、质保文件 15 分、外观 15 分、尺寸 10 分、环保合规 10 分,合计 100 分;脚本 inspectionLevel 按 90/70 两条接收线映射 ACCEPT、CONDITIONAL、REJECT。

决策结果由 MaterialInspectionDecision 承载:批次号、综合质量分、处置动作、是否硬拒收,以及评分明细 scoreDetails 与处置说明 dispositions 两张清单。C 级供应商在 Java 侧执行加严验收——只要脚本初判不是 ACCEPT(即质量分未达 90),直接升级为整批退货;让步接收批次附带全检挑选、8D 整改报告、技术会签等可审计的整改要求。

  • 验收阈值随物料类别与供应商频繁变化:电子件、金属件、化工料的 AQL 缺陷率线各不相同,供应商分级调整后加严策略也要跟着变,硬编码进 Java 每次都要走发版流程。
  • 多维检量结果难以独立调参:缺陷率、质保文件、外观、尺寸、环保六个维度权重与分档线耦合在一串 if-else 里,改一个阈值要回归整段验收逻辑。
  • 零容忍规则与评分规则混杂:致命缺陷、缺质保书、RoHS 不合规必须无条件拒收,若埋在评分分支末尾,容易被高分掩盖,产生质量事故放行风险。
  • 处置结果难以审计追溯:退货、挑选、8D 整改、技术会签等处置动作缺少结构化记录,供应商质量索赔与体系外审时拿不出当批次判定依据。
  • 老旧 MES 与信创环境难引入重型平台:车间系统多运行在 JDK 8 甚至更老的国产化配套 JDK 上,无法承载需要独立服务进程和数据库的重量级规则平台。

本案例采用 JQuick-Java 标准六件套:入参模型、决策结果模型、Rules 规则接口、XML 规则脚本、Service 编排服务、JUnit 测试,全部代码在 Oracle JDK 1.8 下实测通过。

层次 职责 对应文件
领域模型 承载 IQC 抽检要素与批次接收决策(评分明细、处置说明) MaterialInspection.java、MaterialInspectionDecision.java
规则层 六个评分维度与质量分初判,AQL 比率线与接收线全部外置 IncomingMaterialRules.java、incoming-material.xml
编排层 零容忍硬否决前置、六维汇总、C 级供应商加严与处置组装 IncomingMaterialService.java
测试 6 组来料批次真实形态 IncomingMaterialServiceTest.java
规则方法 口径 分值/输出
scoreMajor 严重缺陷 0 个满分;缺陷数与样本数交叉相乘比较,比率低于 1% 得 20、低于 3% 得 10,否则 0 0-30
scoreMinor 轻微缺陷 0 个满分;比率低于 3% 得 14、低于 5% 得 7,否则 0 0-20
scoreCertificate 随附质保书/COA 得 15,否则 0 0-15
scoreAppearance 外观检验合格得 15,否则 0 0-15
scoreDimension 关键尺寸合格得 10,否则 0 0-10
scoreEnvironmental RoHS/环保合规通过得 10,否则 0 0-10
inspectionLevel 总分 ≥90 ACCEPT;≥70 CONDITIONAL;否则 REJECT ACCEPT/CONDITIONAL/REJECT

将两个模型类放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口放入 demo/,Service 放入 demo/service/,XML 脚本放入 src/test/resources/demo/,测试类放入 demo/,包名保持源码原样。工程根目录执行 mvn test -Dtest=IncomingMaterialServiceTest 即可复现全部 6 个用例。

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 来料检验(IQC)抽检结果要素
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MaterialInspection {
/**
* 来料批次号
*/
private String batchNo;
/**
* 物料编码
*/
private String materialCode;
/**
* 物料类别:ELECTRONIC 电子件 / METAL 金属件 / CHEMICAL 化工 / PLASTIC 塑胶
*/
private String materialCategory;
/**
* 抽检样本数
*/
private int sampleSize;
/**
* 致命缺陷数(安全/功能零容忍)
*/
private int criticalDefects;
/**
* 严重缺陷数
*/
private int majorDefects;
/**
* 轻微缺陷数
*/
private int minorDefects;
/**
* 是否随附质保书/COA
*/
private boolean certificateProvided;
/**
* 外观检验是否合格
*/
private boolean appearancePass;
/**
* 关键尺寸检验是否合格
*/
private boolean dimensionPass;
/**
* RoHS/环保合规是否通过
*/
private boolean environmentalCompliant;
/**
* 供应商评级:A / B / C
*/
private String supplierGrade;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 来料批次接收决策
*/
@Data
public class MaterialInspectionDecision {
/**
* 批次号
*/
private String batchNo;
/**
* 综合质量分(硬拒收时为 0)
*/
private int totalScore;
/**
* 处置动作:ACCEPT 接收入库 / CONDITIONAL 让步接收或挑选 / REJECT 整批拒收
*/
private String action;
/**
* 是否命中零容忍硬规则
*/
private boolean hardReject;
/**
* 评分明细
*/
private List<String> scoreDetails = new ArrayList<String>();
/**
* 处置说明与整改要求
*/
private List<String> dispositions = new ArrayList<String>();
public void addDetail(String detail) {
this.scoreDetails.add(detail);
}
public void addDisposition(String disposition) {
this.dispositions.add(disposition);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 31:来料检验缺陷率分档与单据/外观尺寸评分
*/
public interface IncomingMaterialRules {
/**
* 严重缺陷率得分(0-30):0 个满分;比率与阈值交叉相乘
*/
int scoreMajor(@Param("major") int major,
@Param("sample") int sample);
/**
* 轻微缺陷率得分(0-20)
*/
int scoreMinor(@Param("minor") int minor,
@Param("sample") int sample);
/**
* 质保书/COA 得分(0-15)
*/
int scoreCertificate(@Param("provided") boolean provided);
/**
* 外观得分(0-15)
*/
int scoreAppearance(@Param("pass") boolean pass);
/**
* 尺寸得分(0-10)
*/
int scoreDimension(@Param("pass") boolean pass);
/**
* 环保合规得分(0-10)
*/
int scoreEnvironmental(@Param("compliant") boolean compliant);
/**
* 按综合质量分给出初判
*/
String inspectionLevel(@Param("totalScore") int totalScore);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-java.dtd">
<javas namespace="com.github.paohaijiao.demo.IncomingMaterialRules">
<java name="scoreMajor" returnClass="int">
<![CDATA[
int def scoreMajor(int:major, int:sample) {
if (major == 0) {
return 30;
} else if (major * 100 < sample * 1) {
return 20;
} else if (major * 100 < sample * 3) {
return 10;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreMinor" returnClass="int">
<![CDATA[
int def scoreMinor(int:minor, int:sample) {
if (minor == 0) {
return 20;
} else if (minor * 100 < sample * 3) {
return 14;
} else if (minor * 100 < sample * 5) {
return 7;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreCertificate" returnClass="int">
<![CDATA[
int def scoreCertificate(boolean:provided) {
if (provided) {
return 15;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreAppearance" returnClass="int">
<![CDATA[
int def scoreAppearance(boolean:pass) {
if (pass) {
return 15;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreDimension" returnClass="int">
<![CDATA[
int def scoreDimension(boolean:pass) {
if (pass) {
return 10;
} else {
return 0;
}
}
]]>
</java>
<java name="scoreEnvironmental" returnClass="int">
<![CDATA[
int def scoreEnvironmental(boolean:compliant) {
if (compliant) {
return 10;
} else {
return 0;
}
}
]]>
</java>
<java name="inspectionLevel" returnClass="java.lang.String">
<![CDATA[
str def inspectionLevel(int:totalScore) {
if (totalScore >= 90) {
return "ACCEPT";
} else if (totalScore >= 70) {
return "CONDITIONAL";
} else {
return "REJECT";
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.IncomingMaterialRules;
import com.github.paohaijiao.demo.model.MaterialInspection;
import com.github.paohaijiao.demo.model.MaterialInspectionDecision;
/**
* 场景 31:来料检验批次接收判定
* <p>
* 致命缺陷、缺质保书、环保不合规零容忍直接拒收;其余按六维质量分入库/让步/退货,
* C 级供应商执行加严验收。
*/
public class IncomingMaterialService {
private final IncomingMaterialRules rules;
public IncomingMaterialService(IncomingMaterialRules rules) {
this.rules = rules;
}
public MaterialInspectionDecision inspect(MaterialInspection m) {
MaterialInspectionDecision d = new MaterialInspectionDecision();
d.setBatchNo(m.getBatchNo());
if (m.getCriticalDefects() > 0 || !m.isCertificateProvided()
|| !m.isEnvironmentalCompliant()) {
d.setTotalScore(0);
d.setAction("REJECT");
d.setHardReject(true);
if (m.getCriticalDefects() > 0) {
d.addDisposition("发现 " + m.getCriticalDefects()
+ " 个致命缺陷,违反零容忍要求,整批拒收并启动供应商问责");
}
if (!m.isCertificateProvided()) {
d.addDisposition("缺少质保书/COA 随附文件,批次不予接收");
}
if (!m.isEnvironmentalCompliant()) {
d.addDisposition("RoHS/环保合规验证不通过,整批拒收并冻结同批次在途库存");
}
return d;
}
int majorScore = rules.scoreMajor(m.getMajorDefects(), m.getSampleSize());
int minorScore = rules.scoreMinor(m.getMinorDefects(), m.getSampleSize());
int certScore = rules.scoreCertificate(m.isCertificateProvided());
int appearanceScore = rules.scoreAppearance(m.isAppearancePass());
int dimensionScore = rules.scoreDimension(m.isDimensionPass());
int envScore = rules.scoreEnvironmental(m.isEnvironmentalCompliant());
int total = majorScore + minorScore + certScore + appearanceScore + dimensionScore + envScore;
d.addDetail("严重缺陷率: " + majorScore + " / 30");
d.addDetail("轻微缺陷率: " + minorScore + " / 20");
d.addDetail("质保文件: " + certScore + " / 15");
d.addDetail("外观: " + appearanceScore + " / 15");
d.addDetail("尺寸: " + dimensionScore + " / 10");
d.addDetail("环保合规: " + envScore + " / 10");
d.setTotalScore(total);
String level = rules.inspectionLevel(total);
if ("C".equals(m.getSupplierGrade()) && !"ACCEPT".equals(level)) {
d.setAction("REJECT");
d.addDisposition("供应商评级为 C,执行加严验收,质量分 " + total
+ " 未达 90 分接收线,整批退货");
collectDefectItems(d, m, majorScore, minorScore);
return d;
}
d.setAction(level);
if ("ACCEPT".equals(level)) {
d.addDisposition("抽检符合接收质量限,准予办理入库手续");
} else if ("CONDITIONAL".equals(level)) {
d.addDisposition("质量分 " + total + ",作让步接收处理,须挑选/复检后上线");
collectDefectItems(d, m, majorScore, minorScore);
} else {
d.addDisposition("质量分 " + total + ",低于 70 分接收底线,整批退货");
collectDefectItems(d, m, majorScore, minorScore);
}
return d;
}
private void collectDefectItems(MaterialInspectionDecision d, MaterialInspection m,
int majorScore, int minorScore) {
if (majorScore == 0) {
d.addDisposition("严重缺陷率超过 AQL 加严线,按退货流程处理");
} else if (m.getMajorDefects() > 0) {
d.addDisposition("存在 " + m.getMajorDefects() + " 个严重缺陷,上线前须全检挑选");
}
if (minorScore == 0) {
d.addDisposition("轻微缺陷率过高,不予让步接收");
} else if (minorScore <= 7 && m.getMinorDefects() > 0) {
d.addDisposition("轻微缺陷率达到加控线,要求供应商提交 8D 整改报告");
}
if (!m.isDimensionPass()) {
d.addDisposition("关键尺寸检验不合格,批次不得上线,须技术会签");
}
if (!m.isAppearancePass()) {
d.addDisposition("外观检验不合格,转外观挑选后复检");
}
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.MaterialInspection;
import com.github.paohaijiao.demo.model.MaterialInspectionDecision;
import com.github.paohaijiao.demo.service.IncomingMaterialService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 31:原材料来料验收标准动态适配
* <p>
* 6 组批次形态:满分接收入库、轻微缺陷让步接收、缺陷超标退货、
* 致命缺陷零容忍、缺质保书且环保不合规、C 级供应商加严拒收(与 A 级对比)。
*/
public class IncomingMaterialServiceTest {
private IncomingMaterialService service;
@Before
public void setUp() {
IncomingMaterialRules rules = JQuickJava.create()
.createApi(IncomingMaterialRules.class, "demo/incoming-material.xml");
this.service = new IncomingMaterialService(rules);
}
/**
* 50 件全检合格、文件齐全 → 100 分接收
*/
@Test
public void shouldAcceptPerfectBatch() {
MaterialInspection m = base("BAT001", "A");
m.setSampleSize(50);
m.setMajorDefects(0);
m.setMinorDefects(0);
MaterialInspectionDecision d = service.inspect(m);
Assert.assertEquals(100, d.getTotalScore());
Assert.assertEquals("ACCEPT", d.getAction());
Assert.assertFalse(d.isHardReject());
Assert.assertTrue(d.getDispositions().contains("抽检符合接收质量限,准予办理入库手续"));
}
/**
* 50 件中 2 个轻微缺陷(4%)→ 87 分让步接收并要求 8D
*/
@Test
public void shouldConditionallyAcceptMinorDefects() {
MaterialInspection m = base("BAT002", "A");
m.setSampleSize(50);
m.setMinorDefects(2);
MaterialInspectionDecision d = service.inspect(m);
Assert.assertEquals(87, d.getTotalScore());
Assert.assertEquals("CONDITIONAL", d.getAction());
Assert.assertTrue(d.getScoreDetails().contains("轻微缺陷率: 7 / 20"));
Assert.assertTrue(d.getDispositions().contains(
"轻微缺陷率达到加控线,要求供应商提交 8D 整改报告"));
}
/**
* 3 个严重缺陷 + 4 个轻微缺陷 + 尺寸不合格 → 40 分退货
*/
@Test
public void shouldRejectBatchWithExcessiveDefects() {
MaterialInspection m = base("BAT003", "B");
m.setSampleSize(50);
m.setMajorDefects(3);
m.setMinorDefects(4);
m.setDimensionPass(false);
MaterialInspectionDecision d = service.inspect(m);
Assert.assertEquals(40, d.getTotalScore());
Assert.assertEquals("REJECT", d.getAction());
Assert.assertTrue(d.getDispositions().contains("严重缺陷率超过 AQL 加严线,按退货流程处理"));
Assert.assertTrue(d.getDispositions().contains("关键尺寸检验不合格,批次不得上线,须技术会签"));
}
/**
* 其余全合格但出现 1 个致命缺陷 → 零容忍整批拒收
*/
@Test
public void shouldHardRejectCriticalDefect() {
MaterialInspection m = base("BAT004", "A");
m.setSampleSize(50);
m.setCriticalDefects(1);
MaterialInspectionDecision d = service.inspect(m);
Assert.assertEquals(0, d.getTotalScore());
Assert.assertEquals("REJECT", d.getAction());
Assert.assertTrue(d.isHardReject());
Assert.assertTrue(d.getDispositions().get(0).contains("致命缺陷,违反零容忍要求"));
}
/**
* 无质保书且环保验证不通过 → 双硬规则拒收
*/
@Test
public void shouldHardRejectMissingCertificateAndRoHS() {
MaterialInspection m = base("BAT005", "B");
m.setSampleSize(50);
m.setCertificateProvided(false);
m.setEnvironmentalCompliant(false);
MaterialInspectionDecision d = service.inspect(m);
Assert.assertEquals("REJECT", d.getAction());
Assert.assertTrue(d.isHardReject());
Assert.assertTrue(d.getDispositions().contains("缺少质保书/COA 随附文件,批次不予接收"));
Assert.assertTrue(d.getDispositions().contains(
"RoHS/环保合规验证不通过,整批拒收并冻结同批次在途库存"));
}
/**
* 74 分批次:A 级供应商让步接收,C 级供应商加严拒收
*/
@Test
public void shouldTightenAcceptanceForGradeCSupplier() {
MaterialInspection gradeA = base("BAT006", "A");
gradeA.setSampleSize(50);
gradeA.setMajorDefects(1);
gradeA.setMinorDefects(1);
MaterialInspectionDecision d1 = service.inspect(gradeA);
Assert.assertEquals(74, d1.getTotalScore());
Assert.assertEquals("CONDITIONAL", d1.getAction());
Assert.assertFalse(d1.isHardReject());
MaterialInspection gradeC = base("BAT007", "C");
gradeC.setSampleSize(50);
gradeC.setMajorDefects(1);
gradeC.setMinorDefects(1);
MaterialInspectionDecision d2 = service.inspect(gradeC);
Assert.assertEquals(74, d2.getTotalScore());
Assert.assertEquals("REJECT", d2.getAction());
Assert.assertTrue(d2.getDispositions().get(0).contains("供应商评级为 C,执行加严验收"));
}
private MaterialInspection base(String batchNo, String grade) {
MaterialInspection m = new MaterialInspection();
m.setBatchNo(batchNo);
m.setMaterialCode("MAT-8001");
m.setMaterialCategory("ELECTRONIC");
m.setSampleSize(50);
m.setCriticalDefects(0);
m.setMajorDefects(0);
m.setMinorDefects(0);
m.setCertificateProvided(true);
m.setAppearancePass(true);
m.setDimensionPass(true);
m.setEnvironmentalCompliant(true);
m.setSupplierGrade(grade);
return m;
}
}

在工程根目录执行:

Terminal window
mvn test -Dtest=IncomingMaterialServiceTest

测试覆盖形态(6 个 @Test 方法,覆盖满分接收、让步、退货、零容忍、双硬规则、供应商加严对比):

测试方法 业务形态 关键断言/期望决策
shouldAcceptPerfectBatch 50 件样本零缺陷、文件齐全、外观尺寸环保全过(A 级供应商 BAT001) 总分 100、ACCEPT、hardReject=false,处置含“抽检符合接收质量限,准予办理入库手续”
shouldConditionallyAcceptMinorDefects 50 件中 2 个轻微缺陷(4%,落入 3%-5% 档) 总分 87、CONDITIONAL,明细含“轻微缺陷率: 7 / 20”,处置含“要求供应商提交 8D 整改报告”
shouldRejectBatchWithExcessiveDefects 3 个严重缺陷 + 4 个轻微缺陷 + 尺寸不合格(B 级 BAT003) 总分 40、REJECT,处置含“严重缺陷率超过 AQL 加严线,按退货流程处理”和“关键尺寸检验不合格,批次不得上线,须技术会签”
shouldHardRejectCriticalDefect 其余全合格但出现 1 个致命缺陷(BAT004) 总分 0、REJECT、hardReject=true,首条处置含“致命缺陷,违反零容忍要求”
shouldHardRejectMissingCertificateAndRoHS 无质保书且 RoHS 不通过(BAT005) REJECT、hardReject=true,两条处置分别含“缺少质保书/COA 随附文件”和“冻结同批次在途库存”
shouldTightenAcceptanceForGradeCSupplier 同一 74 分批次分别走 A 级(BAT006)与 C 级(BAT007)供应商 A 级 74 分 CONDITIONAL 且非硬拒收;C 级同为 74 分却 REJECT,首条处置含“供应商评级为 C,执行加严验收”
  • 验收标准 XML 外置、按物料动态适配:缺陷率 1%/3%/5% 分档线与 90/70 接收线都写在 incoming-material.xml 中,电子件、金属件、化工料可各维护一份 XML,重建代理即热更新,无需 MES 主程序发版。
  • 一方法一口径,独立调参与审计:六个评分维度各是一个规则方法,scoreDetails 逐维输出“严重缺陷率: 10 / 30”式明细,供应商质量争议与体系外审可直接回溯每一档得分依据。
  • 硬否决前置 + C 级加严在 Java 侧兜底:致命缺陷、缺质保书、环保不合规三类零容忍在 Service 入口短路,评分再高也不能翻案;C 级供应商加严线(90 分)由 Java 二次收紧,脚本初判与供应商策略分层清晰。
  • 交叉相乘规避脚本整数除法陷阱:缺陷率比较写成 major * 100 < sample * 3,不在脚本里做浮点比率运算,JDK 8 下口径稳定、无精度歧义。
  • @Param 按名绑定 + ASM 动态代理:接口参数经 @Param 按名注入脚本,代理对象可单例复用;底层 ASM 生成调用器并带并发缓存,无独立进程、无重型依赖,老旧 MES 与信创 JDK 环境可直接嵌入。