案例 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 |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
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 |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将两个模型类放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口放入 demo/,Service 放入 demo/service/,XML 脚本放入 src/test/resources/demo/,测试类放入 demo/,包名保持源码原样。工程根目录执行 mvn test -Dtest=IncomingMaterialServiceTest 即可复现全部 6 个用例。
MaterialInspection.java
Section titled “MaterialInspection.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 来料检验(IQC)抽检结果要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic 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;}MaterialInspectionDecision.java
Section titled “MaterialInspectionDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 来料批次接收决策 */@Datapublic 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); }}IncomingMaterialRules.java
Section titled “IncomingMaterialRules.java”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);}incoming-material.xml
Section titled “incoming-material.xml”<?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>IncomingMaterialService.java
Section titled “IncomingMaterialService.java”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("外观检验不合格,转外观挑选后复检"); } }}IncomingMaterialServiceTest.java
Section titled “IncomingMaterialServiceTest.java”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; }}在工程根目录执行:
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 环境可直接嵌入。