9 小时以前 5566783a0b7514d21e76fbd9c60552096308b37d
feat(mes): 新增产品质量等级分类支持

- 在 MesQcOqcCheckResultEnum 枚举中新增 TYPE_3、EXCELLENT、QUALIFIED、UNQUALIFIED 四个产品等级
- 修改 OQC 出货检验逻辑,将可发货等级从 1型/2型扩展为 1型/2型/3型/优等品/合格品
- 更新 SQL 配置文件中 MES 出货检验检测结果的数据字典定义
- 修复 Mermaid 质量流程图中出货判定的条件描述
- 更新相关文档中的产品等级判定规则说明
- 调整 canShip 方法逻辑,排除废品和不合格品两个等级的发货
- 修正数据对象文档中 OQC 出货检验的产品等级描述
已修改7个文件
32 ■■■■■ 文件已修改
docs/project-business/data/flows.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/objects.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/05-quality-flow.mmd 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/config_export_all_20260904.sql 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcOqcCheckResultEnum.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/flows.json
@@ -29,11 +29,11 @@
       {"stage": "联动", "action": "审批通过按工序标记: 倒冲→自动物料消耗扣线边库(允许负库存);质检→产出待检行等 IPQC;关键非检→直接产出入库", "roles": ["系统自动"]},
       {"stage": "完工", "action": "仅末道工序回写工单已生产数量,达标自动完成;MPS 累计判断完成并发事件给销售订单", "roles": ["系统自动"]}]},
    {"id": "quality", "name": "质量流程", "mermaid": "mermaid/05-quality-flow.mmd", "color": "#e06c6c",
     "summary": "四道检验闸口(IQC/IPQC/OQC/RQC,二态 0→4) + 判定四果(合格/特采/退货/报废;OQC 改按产品等级 1型/2型/废品,仅废品判不合格) + NCR 评审处置关闭;AI 提供判定建议;NCR 当前仅台账不触发返工单。支持化工报告范式:分组指标树+公式文本+平行测量批次+采样表头+签字区,浏览器打印检验报告;并可导出产品检验单 Word(纸质检验单版式,判定结论带出检验结果、检验·复核·批准签字带出检测人·复核人·审核人姓名、质检章留空盖章,Word 打印)。",
     "summary": "四道检验闸口(IQC/IPQC/OQC/RQC,二态 0→4) + 判定四果(合格/特采/退货/报废;OQC 改按产品等级 1型/2型/3型/优等品/合格品/废品/不合格品,废品与不合格品判不合格) + NCR 评审处置关闭;AI 提供判定建议;NCR 当前仅台账不触发返工单。支持化工报告范式:分组指标树+公式文本+平行测量批次+采样表头+签字区,浏览器打印检验报告;并可导出产品检验单 Word(纸质检验单版式,判定结论带出检验结果、检验·复核·批准签字带出检测人·复核人·审核人姓名、质检章留空盖章,Word 打印)。",
     "steps": [
       {"stage": "来料", "action": "到货通知/外协入库→IQC,结论回写推进待入库或触发退货", "roles": ["IQC 质检员"]},
       {"stage": "过程", "action": "报工质检工序产出行→IPQC,合格拆行入库、不合格生成 NCR", "roles": ["IPQC 质检员"]},
       {"stage": "出货", "action": "销售出库提交→OQC(检测结果按产品等级 1型/2型/废品必填,1型/2型判行合格,废品判行不合格),全部合格后出库单进入待拣货,出现废品则整单取消", "roles": ["OQC 质检员"]},
       {"stage": "出货", "action": "销售出库提交→OQC(检测结果按产品等级 1型/2型/3型/优等品/合格品/废品/不合格品必填,1型/2型/3型/优等品/合格品判行合格,废品与不合格品判行不合格),全部合格后出库单进入待拣货,出现废品或不合格品则整单取消", "roles": ["OQC 质检员"]},
       {"stage": "退货", "action": "退料/销售退货/售后退货→RQC,结论回调单据与售后", "roles": ["RQC 质检员"]},
       {"stage": "报告", "action": "检验结果按分组树+测量批次录入,/indicator-result/report 聚合后前端打印检验报告(含采样地点/证书编号/执行标准/判定结论·质检专用章/复核·审核签字区;判定结论带出检验结果·质检章留空盖章·无平行测量时列头称测量值);/indicator-result/export-product-word 导出产品检验单Word(无技术指标列·判定结论带出检验结果·检验·复核·批准签字带出检测人/复核人/审核人姓名·质检章留空盖章·平行测量顿号合并·下载后Word打印;质检单详情检测结果Tab可按样品查看只读检测值明细)", "roles": ["质检员", "复核人", "审核人"]},
       {"stage": "不合格", "action": "NCR: 待评审→评审→处置(退货/报废/返工/让步接收/降级)→关闭", "roles": ["质量工程师"]}]},
docs/project-business/data/objects.json
@@ -37,7 +37,7 @@
    {"name": "批次", "module": "仓储", "table": "mes_wm_batch", "doc": "关联物料/工单/供应商/客户/质量状态;SN 码含批次 uuid;条码支持18种业务对象", "upstream": ["入库单据"], "downstream": ["库存台账"], "keywords": "批次 batch SN 条码 追溯"},
    {"name": "IQC来料检验", "module": "质量", "table": "mes_qc_iqc", "doc": "到货通知/外协入库触发,草稿0→完成4,结论回写来源单据", "upstream": ["到货通知", "外协入库"], "downstream": ["采购入库", "供应商退货", "NCR"], "keywords": "IQC 来料检验"},
    {"name": "IPQC过程检验", "module": "质量", "table": "mes_qc_ipqc", "doc": "报工质检工序产出行触发,完成回调推进报工与产出入库;表头=来源单据(只读)+产品物料(MDM,必填)+批号(必填)+数量+检验信息,已去掉生产订单/工位/生产任务/工序", "upstream": ["报工"], "downstream": ["产出入库", "NCR"], "keywords": "IPQC 过程检验 巡检 批号"},
    {"name": "OQC出货检验", "module": "质量", "table": "mes_qc_oqc", "doc": "销售出库提交触发,草稿0→完成4;检测结果按产品等级判定(1型/2型/废品,必填),1型/2型判行合格并推进出库单待拣货,废品判行不合格并取消销售出库单;发货数量/出货日期可空", "upstream": ["销售出库单"], "downstream": ["销售出库单(推进或取消)"], "keywords": "OQC 出货检验 出厂检验 1型 2型 废品"},
    {"name": "OQC出货检验", "module": "质量", "table": "mes_qc_oqc", "doc": "销售出库提交触发,草稿0→完成4;检测结果按产品等级判定(1型/2型/3型/优等品/合格品/废品/不合格品,必填),1型/2型/3型/优等品/合格品判行合格并推进出库单待拣货,废品与不合格品判行不合格并取消销售出库单;发货数量/出货日期可空", "upstream": ["销售出库单"], "downstream": ["销售出库单(推进或取消)"], "keywords": "OQC 出货检验 出厂检验 1型 2型 3型 优等品 合格品 废品 不合格品"},
    {"name": "RQC退货检验", "module": "质量", "table": "mes_qc_rqc", "doc": "生产退料/销售退货/售后退货触发,结论回调售后;四类检验单均支持采样地点/证书编号/执行标准表头与复核·审核人,并可打印检验报告", "upstream": ["退料", "退货"], "downstream": ["退货入库", "售后回调", "检验报告打印"], "keywords": "RQC 退货检验"},
    {"name": "检验指标", "module": "质量", "table": "mes_qc_indicator", "doc": "支持两级分组树(itemType录入/分组+parentId)、公式文本展示、单位文本、同级排序;分组项不录值;有子项禁删;同层级名称可重复", "upstream": [], "downstream": ["质检方案", "检验报告打印"], "keywords": "检验指标 indicator 分组 公式 化工"},
    {"name": "检验报告打印", "module": "质量", "table": "mes_qc_indicator_result_detail", "doc": "化工报告范式:平行测量批次(measure_no)+分组矩阵+公式列+签字区;/mes/qc/indicator-result/report 聚合,前端浏览器打印", "upstream": ["IQC来料检验", "IPQC过程检验", "OQC出货检验", "RQC退货检验", "检验指标"], "downstream": [], "keywords": "打印 报告 测量批次 签字 正丁醇 GB/T6027"},
docs/project-business/mermaid/05-quality-flow.mmd
@@ -11,8 +11,8 @@
  R -->|3 不合格退货| Q3[生成NCR·走供应商退货或销售退货]
  R -->|4 不合格报废| Q4[生成NCR·走报废出库]
  A3 --> R2{出货判定 MesQcOqcCheckResultEnum·必填}
  R2 -->|1型/2型| S1[出库行判合格·推进出库单待拣货]
  R2 -->|废品| S2[出库行判不合格·取消销售出库单]
  R2 -->|1型/2型/3型/优等品/合格品| S1[出库行判合格·推进出库单待拣货]
  R2 -->|废品/不合格品| S2[出库行判不合格·取消销售出库单]
  A3 -.-> N5[表头:发货数量/出货日期非必填·检测结果必填]
  A2 -.-> N6[表头:来源单据只读·产品物料(MDM)必填·批号必填·已去掉生产订单/工位/生产任务/工序]
  A2 -->|合格数量| Q5[报工转完成·产出拆行入库]
docs/sql/config_export_all_20260904.sql
@@ -988,7 +988,7 @@
('2046', 'MES 维修工单状态', 'mes_dv_repair_status', '0', 'MES 设备管理 - 维修工单状态(10=待维修,20=维修中,30=已完成,40=已验收)', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', X'00', NULL),
('2047', 'MES 维修结果', 'mes_dv_repair_result', '0', 'MES 设备管理 - 维修结果(1=修复成功,2=报废)', '1', '2026-02-20 10:56:24', '1', '2026-02-20 10:56:24', X'00', NULL),
('2049', 'MES 检测结果', 'mes_qc_check_result', '0', '来料检验的最终结果判定', '1', '2026-02-20 11:23:35', '1', '2026-02-20 11:23:35', X'00', NULL),
('1065400', 'MES 出货检验检测结果', 'mes_oqc_check_result', '0', '出货检验单(OQC)判定结果:1型/2型为可发货等级,废品不放行', '1', '2026-09-22 00:00:00', '1', '2026-09-22 00:00:00', X'00', NULL),
('1065400', 'MES 出货检验检测结果', 'mes_oqc_check_result', '0', '出货检验单(OQC)判定结果:1型/2型/3型/优等品/合格品为可发货等级,废品与不合格品不放行', '1', '2026-09-22 00:00:00', '1', '2026-09-22 00:00:00', X'00', NULL),
('2050', 'MES 来源单据类型', 'mes_qc_source_doc_type', '0', 'IQC 来料检验的来源单据类型', '1', '2026-02-20 11:23:35', '1', '2026-02-20 11:23:35', X'00', NULL),
('2051', 'MES 安灯处置状态', 'mes_pro_andon_status', '0', 'MES 生产管理 - 安灯处置状态(0=未处置,1=已处置)', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', X'00', NULL),
('2052', 'MES 安灯级别', 'mes_pro_andon_level', '0', 'MES 生产管理 - 安灯级别(1=一级,2=二级,3=三级)', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', X'00', NULL),
@@ -1826,6 +1826,10 @@
('1065401', '1', '1型', '1', 'mes_oqc_check_result', '0', 'success', '', '', '1', '2026-09-22 00:00:00', '1', '2026-09-22 00:00:00', X'00'),
('1065402', '2', '2型', '2', 'mes_oqc_check_result', '0', 'primary', '', '', '1', '2026-09-22 00:00:00', '1', '2026-09-22 00:00:00', X'00'),
('1065403', '3', '废品', '3', 'mes_oqc_check_result', '0', 'danger', '', '', '1', '2026-09-22 00:00:00', '1', '2026-09-22 00:00:00', X'00'),
('1065404', '4', '3型', '4', 'mes_oqc_check_result', '0', 'success', '', '', '1', '2026-09-24 00:00:00', '1', '2026-09-24 00:00:00', X'00'),
('1065405', '5', '优等品', '5', 'mes_oqc_check_result', '0', 'success', '', '', '1', '2026-09-24 00:00:00', '1', '2026-09-24 00:00:00', X'00'),
('1065406', '6', '合格品', '6', 'mes_oqc_check_result', '0', 'primary', '', '', '1', '2026-09-24 00:00:00', '1', '2026-09-24 00:00:00', X'00'),
('1065407', '7', '不合格品', '7', 'mes_oqc_check_result', '0', 'danger', '', '', '1', '2026-09-24 00:00:00', '1', '2026-09-24 00:00:00', X'00'),
('3166', '0', '未处置', '0', 'mes_pro_andon_status', '0', 'danger', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', X'00'),
('3167', '1', '已处置', '1', 'mes_pro_andon_status', '0', 'success', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', X'00'),
('3168', '1', '一级', '1', 'mes_pro_andon_level', '0', 'danger', '', '', '1', '2026-02-21 00:08:38', '1', '2026-02-21 00:08:38', X'00'),
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java
@@ -31,7 +31,7 @@
    @Schema(description = "出厂检验单号(OQC)", example = "OQC20250801001")
    private String oqcCode;
    @Schema(description = "OQC 检验结论(字典 mes_qc_check_result)", example = "1")
    @Schema(description = "OQC 检验结论(字典 mes_oqc_check_result)", example = "1")
    private Integer checkResult;
    @Schema(description = "物料 ID", example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcOqcCheckResultEnum.java
@@ -18,7 +18,11 @@
    TYPE_1(1, "1型"),
    TYPE_2(2, "2型"),
    SCRAP(3, "废品");
    SCRAP(3, "废品"),
    TYPE_3(4, "3型"),
    EXCELLENT(5, "优等品"),
    QUALIFIED(6, "合格品"),
    UNQUALIFIED(7, "不合格品");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcOqcCheckResultEnum::getType).toArray(Integer[]::new);
@@ -39,13 +43,15 @@
    /**
     * 判断是否可以放行发货
     *
     * <p>1 型、2 型均为可发货等级,仅废品不放行</p>
     * <p>废品、不合格品不放行,其余等级(1 型、2 型、3 型、优等品、合格品)均可发货</p>
     *
     * @param type 检测结果
     * @return true 可以放行
     */
    public static boolean canShip(Integer type) {
        return type != null && !SCRAP.getType().equals(type);
        return type != null
                && !SCRAP.getType().equals(type)
                && !UNQUALIFIED.getType().equals(type);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -232,7 +232,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) {
        // 1. 更新行的 oqcId + qualityStatus(1型/2型放行,废品判不合格)
        // 1. 更新行的 oqcId + qualityStatus(1型/2型/3型/优等品/合格品放行,废品与不合格品判不合格)
        Integer qualityStatus = MesQcOqcCheckResultEnum.canShip(checkResult)
                ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus();
        productSalesLineMapper.updateById(new MesWmProductSalesLineDO()