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.
Industry scenario
Section titled “Industry scenario”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.
Business pain points
Section titled “Business pain points”- 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.
Solution architecture
Section titled “Solution architecture”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 breakdown
Section titled “Rule method breakdown”| 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 |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”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.
SupplierProfile.java
Section titled “SupplierProfile.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 核心企业上游供应商档案(供应链金融保理准入要素) */@Data@NoArgsConstructor@AllArgsConstructorpublic 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;}SupplierAdmissionDecision.java
Section titled “SupplierAdmissionDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 供应商准入与保理授信决策结果 */@Datapublic 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); }}SupplierAdmissionRules.java
Section titled “SupplierAdmissionRules.java”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);}supplier-admission.xml
Section titled “supplier-admission.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.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>SupplierAdmissionService.java
Section titled “SupplierAdmissionService.java”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(); }}SupplierAdmissionServiceTest.java
Section titled “SupplierAdmissionServiceTest.java”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 & verified results
Section titled “Run & verified results”Run from the project root:
mvn test -Dtest=SupplierAdmissionServiceTestTest coverage
Section titled “Test coverage”| 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 |
Technical highlights
Section titled “Technical highlights”- 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.
@Parammulti-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.