Skip to content

Case 18: Supply-chain supplier admission

This case corresponds to USE-CASES scenario 18, “Supplier admission rating for supply-chain finance,” in the supply-chain finance domain. It targets factoring admission for upstream suppliers of a core enterprise, automatically producing the supplier rating, admission decision, approved factoring limit, guarantee requirements, and recommended settlement terms.

In supply-chain finance led by a core enterprise, an upstream supplier applying for factoring credit must submit a complete profile: years since business registration, registered capital, qualification level (S/A/B/C/NONE), transaction volume with the core enterprise over the last 12 months, on-time delivery rate, incoming/delivery quality pass rate, contract fulfillment rate, months of cooperation, financial health (HEALTHY/STABLE/WEAK/RISKY), plus two negative flags — dishonest judgment debtor and major contract breach in the last 2 years. The core enterprise and the factoring bank need a consistent admission decision quickly during partnership negotiations or limit renewals.

The decision starts with two one-vote vetoes in the Service: a supplier listed as a dishonest judgment debtor is barred from admission and partnership talks are terminated; a major contract breach in the last 2 years (supply cutoff / quality incident) suspends admission for 24 months. After passing the vetoes, the rule script performs five-dimension scoring: qualification (0-20, axis on qualification level with fine-tuning by registered capital), fulfillment record (0-30, on-time delivery, quality pass, and contract fulfillment rates worth 10 points each), cooperation depth (0-15, 8 points for months of cooperation + 7 points for annual transaction volume), financial status (0-20), and registered years (0-15), totaling 100 points.

The total score maps to grades A/B/C/D: >=85 is grade A (strategic supplier), >=75 is grade B (qualified), >=60 is grade C (restricted), otherwise grade D (eliminated). Grades A/B are APPROVE directly; grade C becomes CONDITIONAL admission — a 10% performance bond or third-party guarantee is required, factoring is reviewed per individual receivable with no revolving line, and a 6-month observation period applies; grade D is rejected. The factoring limit is calculated as annual transaction volume times a grade-based ratio (A 20%, B 12%, C 5%), then capped twice — at 2 million yuan for grade C and at a hard 10-million per-customer ceiling. Recommended settlement terms are 90/60/30 days by grade. When financials are weak (WEAK/RISKY), a clause requiring proceeds to flow into a supervised core-enterprise account is attached; new suppliers with fewer than 6 months of cooperation get their first order reduced by 50% as a trial.

  • Rating criteria scattered across procurement, quality, and finance departments: delivery-rate brackets, qualification-level scores, factoring ratios, and settlement terms live in supplier-management policies and system code, making cross-department consistency hard.
  • Rating and limit policies change with the core enterprise’s strategy: factoring ratios of 20%/12%/5%, the 10M per-customer ceiling, and the grade-C 2M restriction line shift with risk appetite; hard coding means every change requires a release.
  • The three fulfillment dimensions need independent tuning: on-time delivery, quality pass rate, and contract fulfillment are worth 10 points each; the quality department wants to tighten the quality criterion alone, which a single coupled total-score formula cannot support.
  • Conditional-admission terms are numerous and combinatorial: bonds, per-transaction review, observation periods, supervised accounts, and new-supplier trial reductions trigger by rating, financial status, and cooperation months; manual checklists easily miss items.
  • Dishonesty and major breaches must be hard-blocked and auditable: even with perfect scores elsewhere, a dishonest supplier must never enter scoring, and review records must clearly show the veto basis.
  • Domestic-platform and legacy ERP environments at core enterprises cannot adopt heavy rule platforms: supply-chain systems are deeply integrated with existing ERP and SRM on JDK 8, requiring an embeddable rule solution with no standalone middleware.

This case follows the six-piece skeleton: input model + decision result model + rule interface + XML rule script + Service orchestration + 6 test cases covering real business profiles.

Layer Responsibility Files
Domain model Supplier profile fields, admission and factoring-credit decision result (rating / limit / conditions / score breakdown) SupplierProfile.java, SupplierAdmissionDecision.java
Rule set & XML Five-dimension scoring criteria, rating thresholds, factoring ratios and settlement-term mapping SupplierAdmissionRules.java, supplier-admission.xml
Orchestration service Dishonesty/major-breach vetoes, rating-to-decision mapping, dual limit caps, condition assembly SupplierAdmissionService.java
Tests 6 supplier profiles (strategic / qualified / restricted / dishonest / major breach / newly established) SupplierAdmissionServiceTest.java
Rule method Criteria Score/output
scoreQualification Axis on qualification level S/A/B/C, fine-tuned by registered capital at 20M/5M/1M yuan; 0 without a level 0-20
scoreFulfillment On-time delivery, quality pass, and contract fulfillment rates bracketed at >=98/95/90/85%, 10 points each 0-30
scoreCooperation Cooperation months (>=36/18/6/3) 0-8 points + annual transaction volume (50M/20M/5M yuan) 0-7 points 0-15
scoreFinance Financial status: HEALTHY 20, STABLE 15, WEAK 8, otherwise 2 0-20
scoreRegisteredYears Registered years >=8/5/3/1 score 15/12/8/4, otherwise 0 0-15
grade Total score >=85 A, >=75 B, >=60 C, otherwise D A/B/C/D
factoringRatio Factoring limit as a share of annual transaction volume by grade: A 0.20, B 0.12, C 0.05, D 0 Ratio
settlementDays Recommended settlement term by grade: A 90 days, B 60 days, C 30 days, D 0 days Days

Place the following files into the test directory of a JQuick-Java project following the source package structure: the two models go to src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class go to src/test/java/com/github/paohaijiao/demo/, the Service goes to src/test/java/com/github/paohaijiao/demo/service/, and the XML script goes to src/test/resources/demo/. Keep the package names exactly as in the source (com.github.paohaijiao.demo / .model / .service). Then run mvn test -Dtest=SupplierAdmissionServiceTest to execute all 6 cases.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 核心企业上游供应商档案(供应链金融保理准入要素)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SupplierProfile {
/**
* 供应商编号
*/
private String supplierId;
/**
* 供应商名称
*/
private String supplierName;
/**
* 工商注册年限
*/
private int registeredYears;
/**
* 注册资本(万元)
*/
private int registeredCapitalWan;
/**
* 资质等级:S / A / B / C / NONE(未取得体系认证)
*/
private String qualificationLevel;
/**
* 近 12 个月与核心企业交易额(元)
*/
private double annualOrderAmount;
/**
* 准时交付率(百分比,可含一位小数,如 97.5)
*/
private double onTimeDeliveryRate;
/**
* 来料/交付质量合格率(百分比)
*/
private double qualityPassRate;
/**
* 合同履约率(百分比)
*/
private double contractFulfillRate;
/**
* 与核心企业合作月数
*/
private int cooperationMonths;
/**
* 财务健康状况:HEALTHY / STABLE / WEAK / RISKY
*/
private String financeStatus;
/**
* 是否失信被执行人
*/
private boolean dishonest;
/**
* 近 2 年是否发生重大合同违约
*/
private boolean majorBreach;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 供应商准入与保理授信决策结果
*/
@Data
public class SupplierAdmissionDecision {
/**
* 供应商编号
*/
private String supplierId;
/**
* 准入结论:APPROVE-准入 / CONDITIONAL-附条件准入 / REJECT-淘汰
*/
private String decision;
/**
* 供应商评级:A-战略 / B-合格 / C-限制 / D-淘汰
*/
private String grade;
/**
* 综合评分(0-100)
*/
private int totalScore;
/**
* 核定保理额度(元)
*/
private double factoringLimit;
/**
* 是否要求追加担保/保证金
*/
private boolean requireGuarantee;
/**
* 建议结算账期(天)
*/
private int settlementDays;
/**
* 评分明细
*/
private List<String> scoreDetails = new ArrayList<String>();
/**
* 拒绝原因
*/
private List<String> rejectReasons = new ArrayList<String>();
/**
* 风险提示与附加条件
*/
private List<String> conditions = new ArrayList<String>();
public void addScoreDetail(String detail) {
this.scoreDetails.add(detail);
}
public void addRejectReason(String reason) {
this.rejectReasons.add(reason);
}
public void addCondition(String condition) {
this.conditions.add(condition);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 18(生产级样板):供应链金融供应商准入规则集
* <p>
* 五维评分(企业资质 / 履约记录 / 合作深度 / 财务状况 / 注册年限),
* 再映射评级、保理额度比例、担保要求与账期;失信与重大违约由 Service 一票否决。
* 规则脚本见 {@code src/test/resources/demo/supplier-admission.xml}。
*/
public interface SupplierAdmissionRules {
/**
* 企业资质评分(0-20):资质等级 + 注册资本
*/
int scoreQualification(@Param("qualificationLevel") String qualificationLevel,
@Param("registeredCapitalWan") int registeredCapitalWan);
/**
* 履约记录评分(0-30):准时交付率、质量合格率、合同履约率各 10 分
*/
int scoreFulfillment(@Param("onTimeDeliveryRate") double onTimeDeliveryRate,
@Param("qualityPassRate") double qualityPassRate,
@Param("contractFulfillRate") double contractFulfillRate);
/**
* 合作深度评分(0-15):合作月数 + 年度交易额
*/
int scoreCooperation(@Param("cooperationMonths") int cooperationMonths,
@Param("annualOrderAmount") double annualOrderAmount);
/**
* 财务状况评分(0-20)
*/
int scoreFinance(@Param("financeStatus") String financeStatus);
/**
* 注册年限评分(0-15)
*/
int scoreRegisteredYears(@Param("registeredYears") int registeredYears);
/**
* 供应商评级:A / B / C / D
*/
String grade(@Param("totalScore") int totalScore);
/**
* 保理额度占年交易额比例
*/
double factoringRatio(@Param("grade") String grade);
/**
* 建议结算账期(天)
*/
int settlementDays(@Param("grade") String grade);
}
<?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.SupplierAdmissionRules">
<!-- 企业资质评分:0-20 -->
<java name="scoreQualification" returnClass="int">
<![CDATA[
int def scoreQualification(str:qualificationLevel, int:registeredCapitalWan) {
if (qualificationLevel == "S") {
if (registeredCapitalWan >= 2000) {
return 20;
} else if (registeredCapitalWan >= 500) {
return 18;
} else {
return 16;
}
} else if (qualificationLevel == "A") {
if (registeredCapitalWan >= 2000) {
return 16;
} else if (registeredCapitalWan >= 500) {
return 14;
} else {
return 12;
}
} else if (qualificationLevel == "B") {
if (registeredCapitalWan >= 500) {
return 10;
} else if (registeredCapitalWan >= 100) {
return 8;
} else {
return 6;
}
} else if (qualificationLevel == "C") {
return 4;
} else {
return 0;
}
}
]]>
</java>
<!-- 履约记录评分:0-30,三项比率各 10 分 -->
<java name="scoreFulfillment" returnClass="int">
<![CDATA[
int def scoreFulfillment(double:onTimeDeliveryRate, double:qualityPassRate,
double:contractFulfillRate) {
int total = 0;
if (onTimeDeliveryRate >= 98.0) {
total = total + 10;
} else if (onTimeDeliveryRate >= 95.0) {
total = total + 8;
} else if (onTimeDeliveryRate >= 90.0) {
total = total + 5;
} else if (onTimeDeliveryRate >= 85.0) {
total = total + 3;
} else {
total = total + 1;
}
if (qualityPassRate >= 98.0) {
total = total + 10;
} else if (qualityPassRate >= 95.0) {
total = total + 8;
} else if (qualityPassRate >= 90.0) {
total = total + 5;
} else if (qualityPassRate >= 85.0) {
total = total + 3;
} else {
total = total + 1;
}
if (contractFulfillRate >= 98.0) {
total = total + 10;
} else if (contractFulfillRate >= 95.0) {
total = total + 8;
} else if (contractFulfillRate >= 90.0) {
total = total + 5;
} else if (contractFulfillRate >= 85.0) {
total = total + 3;
} else {
total = total + 1;
}
return total;
}
]]>
</java>
<!-- 合作深度评分:0-15 -->
<java name="scoreCooperation" returnClass="int">
<![CDATA[
int def scoreCooperation(int:cooperationMonths, double:annualOrderAmount) {
int monthsScore = 0;
if (cooperationMonths >= 36) {
monthsScore = 8;
} else if (cooperationMonths >= 18) {
monthsScore = 6;
} else if (cooperationMonths >= 6) {
monthsScore = 4;
} else if (cooperationMonths >= 3) {
monthsScore = 2;
}
int orderScore = 0;
if (annualOrderAmount >= 50000000.0) {
orderScore = 7;
} else if (annualOrderAmount >= 20000000.0) {
orderScore = 5;
} else if (annualOrderAmount >= 5000000.0) {
orderScore = 3;
} else {
orderScore = 1;
}
return monthsScore + orderScore;
}
]]>
</java>
<!-- 财务状况评分:0-20 -->
<java name="scoreFinance" returnClass="int">
<![CDATA[
int def scoreFinance(str:financeStatus) {
if (financeStatus == "HEALTHY") {
return 20;
} else if (financeStatus == "STABLE") {
return 15;
} else if (financeStatus == "WEAK") {
return 8;
} else {
return 2;
}
}
]]>
</java>
<!-- 注册年限评分:0-15 -->
<java name="scoreRegisteredYears" returnClass="int">
<![CDATA[
int def scoreRegisteredYears(int:registeredYears) {
if (registeredYears >= 8) {
return 15;
} else if (registeredYears >= 5) {
return 12;
} else if (registeredYears >= 3) {
return 8;
} else if (registeredYears >= 1) {
return 4;
} else {
return 0;
}
}
]]>
</java>
<!-- 评级映射 -->
<java name="grade" returnClass="java.lang.String">
<![CDATA[
str def grade(int:totalScore) {
if (totalScore >= 85) {
return "A";
} else if (totalScore >= 75) {
return "B";
} else if (totalScore >= 60) {
return "C";
} else {
return "D";
}
}
]]>
</java>
<!-- 保理额度比例 -->
<java name="factoringRatio" returnClass="double">
<![CDATA[
double def factoringRatio(str:grade) {
if (grade == "A") {
return 0.20;
} else if (grade == "B") {
return 0.12;
} else if (grade == "C") {
return 0.05;
} else {
return 0.0;
}
}
]]>
</java>
<!-- 建议账期 -->
<java name="settlementDays" returnClass="int">
<![CDATA[
int def settlementDays(str:grade) {
if (grade == "A") {
return 90;
} else if (grade == "B") {
return 60;
} else if (grade == "C") {
return 30;
} else {
return 0;
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.SupplierAdmissionRules;
import com.github.paohaijiao.demo.model.SupplierAdmissionDecision;
import com.github.paohaijiao.demo.model.SupplierProfile;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 场景 18:供应链金融供应商准入服务
* <p>
* 规则侧(XML):五维评分口径、评级阈值、保理比例与账期。
* Java 侧:失信/重大违约一票否决、评级到准入结论映射、保理额度封顶与附加条件组装。
*/
public class SupplierAdmissionService {
/**
* 单户保理额度绝对上限(元):1000 万
*/
private static final double FACTORING_CEILING = 10000000.0;
/**
* C 级(限制类)保理额度上限(元):200 万
*/
private static final double GRADE_C_CEILING = 2000000.0;
private final SupplierAdmissionRules rules;
public SupplierAdmissionService(SupplierAdmissionRules rules) {
this.rules = rules;
}
public SupplierAdmissionDecision evaluate(SupplierProfile supplier) {
SupplierAdmissionDecision decision = new SupplierAdmissionDecision();
decision.setSupplierId(supplier.getSupplierId());
if (supplier.isDishonest()) {
reject(decision, "供应商为失信被执行人,禁止准入并终止合作洽谈");
return decision;
}
if (supplier.isMajorBreach()) {
reject(decision, "近 2 年发生重大合同违约(断供/质量事故),暂停准入 24 个月");
return decision;
}
int qualificationScore = rules.scoreQualification(
supplier.getQualificationLevel(), supplier.getRegisteredCapitalWan());
int fulfillmentScore = rules.scoreFulfillment(
supplier.getOnTimeDeliveryRate(), supplier.getQualityPassRate(),
supplier.getContractFulfillRate());
int cooperationScore = rules.scoreCooperation(
supplier.getCooperationMonths(), supplier.getAnnualOrderAmount());
int financeScore = rules.scoreFinance(supplier.getFinanceStatus());
int yearsScore = rules.scoreRegisteredYears(supplier.getRegisteredYears());
int totalScore = qualificationScore + fulfillmentScore + cooperationScore
+ financeScore + yearsScore;
if (totalScore > 100) {
totalScore = 100;
}
decision.addScoreDetail("企业资质评分: " + qualificationScore + " / 20");
decision.addScoreDetail("履约记录评分: " + fulfillmentScore + " / 30");
decision.addScoreDetail("合作深度评分: " + cooperationScore + " / 15");
decision.addScoreDetail("财务状况评分: " + financeScore + " / 20");
decision.addScoreDetail("注册年限评分: " + yearsScore + " / 15");
decision.addScoreDetail("汇总评分: " + totalScore);
decision.setTotalScore(totalScore);
String grade = rules.grade(totalScore);
decision.setGrade(grade);
decision.setSettlementDays(rules.settlementDays(grade));
if ("A".equals(grade) || "B".equals(grade)) {
decision.setDecision("APPROVE");
} else if ("C".equals(grade)) {
decision.setDecision("CONDITIONAL");
} else {
decision.setDecision("REJECT");
decision.addRejectReason("综合评分 " + totalScore + " 低于准入线 60,评为 D 级供应商");
return decision;
}
double ratio = rules.factoringRatio(grade);
double limit = supplier.getAnnualOrderAmount() * ratio;
if ("C".equals(grade) && limit > GRADE_C_CEILING) {
limit = GRADE_C_CEILING;
}
if (limit > FACTORING_CEILING) {
limit = FACTORING_CEILING;
}
decision.setFactoringLimit(round(limit));
if ("C".equals(grade)) {
decision.setRequireGuarantee(true);
decision.addCondition("附条件准入:需缴纳 10% 履约保证金或引入第三方担保");
decision.addCondition("保理业务按单笔应收账款逐笔审核,不授循环额度");
decision.addCondition("观察期 6 个月,履约指标连续达标后可申请升为 B 级");
}
if ("WEAK".equals(supplier.getFinanceStatus()) || "RISKY".equals(supplier.getFinanceStatus())) {
decision.addCondition("财务状况偏弱,保理回款须进入核心企业监管专户封闭运行");
}
if (supplier.getCooperationMonths() < 6) {
decision.addCondition("与核心企业合作不足 6 个月,首笔业务降额 50% 试单");
}
return decision;
}
private void reject(SupplierAdmissionDecision decision, String reason) {
decision.setDecision("REJECT");
decision.setGrade("D");
decision.setFactoringLimit(0.0);
decision.addRejectReason(reason);
}
private double round(double value) {
return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.SupplierAdmissionDecision;
import com.github.paohaijiao.demo.model.SupplierProfile;
import com.github.paohaijiao.demo.service.SupplierAdmissionService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 18 生产级样板:供应链金融上游供应商准入评级
* <p>
* 覆盖 6 组供应商形态:S 级战略(额度触顶 1000 万)、A/B 级合格、
* C 级附条件(担保+监管专户)、失信、重大违约、资质不全新供应商。
*/
public class SupplierAdmissionServiceTest {
private SupplierAdmissionService service;
@Before
public void setUp() {
SupplierAdmissionRules rules = JQuickJava.create()
.createApi(SupplierAdmissionRules.class, "demo/supplier-admission.xml");
this.service = new SupplierAdmissionService(rules);
}
/**
* S 级战略供应商:100 分 A 级,理论保理 1600 万触单户上限核 1000 万、账期 90 天
*/
@Test
public void shouldAdmitStrategicSupplierWithCeiling() {
SupplierProfile supplier = new SupplierProfile(
"SUP001", "中盛新材料股份有限公司", 8, 3000, "S",
80000000.0, 99.0, 99.2, 99.5, 60, "HEALTHY", false, false);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals(100, decision.getTotalScore());
Assert.assertEquals("A", decision.getGrade());
Assert.assertEquals("APPROVE", decision.getDecision());
Assert.assertEquals(10000000.0, decision.getFactoringLimit(), 0.001);
Assert.assertFalse(decision.isRequireGuarantee());
Assert.assertEquals(90, decision.getSettlementDays());
Assert.assertTrue(decision.getScoreDetails().contains("履约记录评分: 30 / 30"));
}
/**
* 合格供应商:78 分 B 级准入,保理 360 万、账期 60 天
*/
@Test
public void shouldAdmitQualifiedSupplier() {
SupplierProfile supplier = new SupplierProfile(
"SUP002", "广联五金制品有限公司", 5, 800, "A",
30000000.0, 97.0, 98.0, 96.0, 24, "STABLE", false, false);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals(78, decision.getTotalScore());
Assert.assertEquals("B", decision.getGrade());
Assert.assertEquals("APPROVE", decision.getDecision());
Assert.assertEquals(3600000.0, decision.getFactoringLimit(), 0.001);
Assert.assertEquals(60, decision.getSettlementDays());
Assert.assertTrue(decision.getConditions().isEmpty());
}
/**
* 限制类供应商:62 分 C 级附条件准入,需保证金、监管专户、账期缩至 30 天
*/
@Test
public void shouldAdmitConditionalSupplierWithGuarantee() {
SupplierProfile supplier = new SupplierProfile(
"SUP003", "鑫泰包装材料厂", 5, 800, "B",
20000000.0, 96.0, 96.0, 94.0, 24, "WEAK", false, false);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals(62, decision.getTotalScore());
Assert.assertEquals("C", decision.getGrade());
Assert.assertEquals("CONDITIONAL", decision.getDecision());
Assert.assertEquals(1000000.0, decision.getFactoringLimit(), 0.001);
Assert.assertTrue(decision.isRequireGuarantee());
Assert.assertEquals(30, decision.getSettlementDays());
Assert.assertTrue(decision.getConditions().contains(
"附条件准入:需缴纳 10% 履约保证金或引入第三方担保"));
Assert.assertTrue(decision.getConditions().contains(
"财务状况偏弱,保理回款须进入核心企业监管专户封闭运行"));
}
/**
* 失信被执行人:一票否决
*/
@Test
public void shouldRejectDishonestSupplier() {
SupplierProfile supplier = new SupplierProfile(
"SUP004", "失信供应商", 10, 5000, "A",
60000000.0, 98.0, 98.0, 98.0, 48, "HEALTHY", true, false);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals("REJECT", decision.getDecision());
Assert.assertEquals("D", decision.getGrade());
Assert.assertEquals(0.0, decision.getFactoringLimit(), 0.001);
Assert.assertTrue(decision.getRejectReasons().contains(
"供应商为失信被执行人,禁止准入并终止合作洽谈"));
}
/**
* 近 2 年重大违约:暂停准入 24 个月
*/
@Test
public void shouldRejectMajorBreachSupplier() {
SupplierProfile supplier = new SupplierProfile(
"SUP005", "曾断供供应商", 7, 600, "B",
15000000.0, 92.0, 90.0, 88.0, 30, "STABLE", false, true);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals("REJECT", decision.getDecision());
Assert.assertTrue(decision.getRejectReasons().contains(
"近 2 年发生重大合同违约(断供/质量事故),暂停准入 24 个月"));
}
/**
* 资质不全的新设供应商:20 分 D 级淘汰
*/
@Test
public void shouldRejectUnqualifiedNewSupplier() {
SupplierProfile supplier = new SupplierProfile(
"SUP006", "某个体户加工厂", 1, 50, "NONE",
2000000.0, 88.0, 86.0, 85.0, 4, "RISKY", false, false);
SupplierAdmissionDecision decision = service.evaluate(supplier);
Assert.assertEquals(18, decision.getTotalScore());
Assert.assertEquals("D", decision.getGrade());
Assert.assertEquals("REJECT", decision.getDecision());
Assert.assertTrue(decision.getRejectReasons().contains("综合评分 18 低于准入线 60,评为 D 级供应商"));
Assert.assertEquals(0, decision.getSettlementDays());
}
}

Run from the project root:

Terminal window
mvn test -Dtest=SupplierAdmissionServiceTest
Test method Constructed business profile Key assertions / expected decision
shouldAdmitStrategicSupplierWithCeiling SUP001: 8 years registered, 30M capital, grade-S qualification, 80M annual transactions, all three rates around 99%, 60 months of cooperation, HEALTHY 100 points, grade A, APPROVE; theoretical 16M capped at the per-customer ceiling of 10M; no guarantee required, 90-day settlement; fulfillment score 30 / 30
shouldAdmitQualifiedSupplier SUP002: 5 years, 8M capital, grade-A qualification, 30M annual transactions, 24 months of cooperation, STABLE 78 points, grade B, APPROVE; factoring 30M x 12% = 3.6M; 60-day settlement; no conditions
shouldAdmitConditionalSupplierWithGuarantee SUP003: grade-B qualification, 20M annual transactions, three rates at 94-96%, WEAK 62 points, grade C, CONDITIONAL; factoring 20M x 5% = 1M (below the grade-C 2M line); guarantee required, 30-day settlement; conditions include the 10% bond and the supervised-account clause
shouldRejectDishonestSupplier Excellent metrics but dishonest=true REJECT, grade D, factoring limit 0, reason contains “supplier is a dishonest judgment debtor; admission barred and partnership talks terminated”
shouldRejectMajorBreachSupplier Supplier with a prior supply cutoff, majorBreach=true REJECT, reason contains “admission suspended for 24 months”
shouldRejectUnqualifiedNewSupplier Individual-workshop processor: 1 year registered, 500K capital, qualification NONE, 4 months of cooperation, RISKY 18 points, grade D, REJECT; reason contains “total score 18 is below the admission line of 60; rated a grade-D supplier”; recommended settlement 0 days
  • Rating and limit policies fully externalized to XML: the five dimension brackets (qualification / fulfillment / cooperation / finance / years), the 85/75/60 rating lines, factoring ratios, and settlement days all live in the XML; the core enterprise can adjust supplier policy without touching the ERP/SRM main program.
  • Three fulfillment dimensions accumulate sub-scores inside one method; one method per criterion is auditable: on-time delivery, quality pass rate, and contract fulfillment are worth 10 points each with their own brackets, so the quality department can retune its bracket independently; the score breakdown is output item by item.
  • Service-side dual one-vote vetoes up front + grade-D short-circuit: dishonesty and major breaches are rejected before scoring; grade D returns immediately without computing the limit; the aggregated score is clamped to 100.
  • Dual caps on the factoring limit: the theoretical limit is first calculated from the grade ratio, grade C is compressed below 2M, and then everything is bounded by the hard 10M per-customer ceiling — a clearly layered risk control.
  • Automatic assembly of conditional-admission terms: grade-C bond / per-transaction review / observation period, the supervised account for WEAK/RISKY financials, and the trial reduction for cooperation under 6 months are appended to the condition list by trigger condition, eliminating manual omissions.
  • @Param multi-parameter name binding + proxy reuse + ASM call chain: 8 rule methods (including the three-rate and two-axis cooperation-depth methods) share a single dynamic proxy; Java 8 with no heavy dependencies, embeddable into existing supply-chain systems and domestic-platform environments.