编辑 | blame | 历史 | 原始文档

质检单 → 质检报告 出件 前端联调方案

后端模块:yudao-module-qcreport 接口前缀:/admin-api/qc-report/instance
上游数据源:yudao-module-mesMesQcReportApi,本模块不直接读 MES 表)
本轮范围:**由 MES 质检单(IQC/IPQC/OQC/RQC)直接出报告**。前端只需选「哪张单据 + 用哪张模板」,
数据全部由后端从 MES 取,**前端不再自己拼 context**。

涉及页面

页面 路由 说明 状态
MES 质检单列表(IQC/IPQC/OQC/RQC 四个页面) /mes/qc/iqcipqcoqcrqc 行操作新增「出报告」:选模板 → 调本接口 → 跳报告实例列表(已带筛选条件) 已落地
报告实例列表页 /qc/report-instance 生成后按 businessType + businessId 反查得到;搜索表单已加「业务单据」字段并支持路由带入 已落地(views/mes/qc/report/instance/index.vue
模板设计器 /qc/report-template 无改动(模板需先在设计器里配好检验项表格并发布) 已落地

qc_report_instance_frontend_integration.md 的分工:那份讲的是「调用方自己准备数据」的通用出件;
本份讲的是「数据从 MES 取」的专用入口。两者最终都落到同一个实例表、同一个列表页。

业务流程与数据带入

  1. 质检单 → 出报告:用户在质检单**列表行操作**里点「出报告」→ 前端弹窗列出可选模板
    (**只列出 reportType 与该单据类型一致的模板**)→ 调 generate-from-qc
  2. 后端六道校验(按业务优先级,只报第一处,不一次抛多条):
    类型合法 → 单据存在 → 已完成检验 → 已填判定 → 模板类型匹配 → 有可用检验项。
  3. 后端取数并出件MesQcReportApi 装配质检单四层结构(单头 / 判定依据 / 样品 / 实测值)
    → 归一成报告上下文 → 走**既有的** generate 流程(版本发布校验、编号生成、快照冻结、判定引擎全部照旧)。
  4. 结果回到前端:弹窗内就地展示结果(reportNo / itemCount / undecidableCount / warnings / errors),
    底部「查看报告列表」跳转报告实例列表页并带上筛选条件。
  5. 反查:报告实例列表页用 businessType=mes_qc_iqc + businessId=1 即可查出该单据出过的所有报告。

带入关系要点

  • qcType + qcId 是主线,从质检单列表行带入。qcType 用字典 mes_qc_type 的**数值**(1/2/3/4)。
  • templateId 由用户在弹窗里选。**前端必须先按 reportType 过滤模板列表**,否则用户会选到后端必然拒绝的组合。
  • version 不传时后端取模板的 currentVersion(要求已发布)。设计器刚存了草稿想立刻出件才需要显式传。
  • reportNo 不传由后端自动生成(QR + 日期 + 4 位流水)。
  • 列表 → 报告实例的反查筛选是路由 query 带入:跳转时带 businessType(表名,如 mes_qc_oqc
    businessId(质检单主键的字符串形式)两个参数,报告实例列表页在挂载时把它们预填进搜索表单,
    用户落地即看到刚出的报告,不需要自己再筛一遍。

UI 入口(已落地)

位置 元素 展示规则 说明
质检单列表行操作 「出报告」按钮 仅当 status === 已完成 时显示 与「编辑」「删除」(仅草稿可见)互斥,避免用户对未完成的单据点
弹窗标题 出报告 - {单据编号} 编号由列表行带入,仅用于展示
弹窗选模板区 单选模板列表 由后端 /qc-report/template/selectable 返回:reportType 与本单类型一致、status=启用、**currentVersion 指向的版本处于「已发布」、且该版本的画布有可渲染的内容** 无可选项时给空状态提示,引导先去模板设计器配好并发布
弹窗结果区 成功提示 + 告警区 生成成功后就地切换,不关弹窗 undecidableCount > 0 用**蓝色信息条**(不是黄色告警):这项数里绝大多数是「过程参数」这类正常情况,标黄会在几乎每份报告上误报
弹窗底部 「查看报告列表」 结果区才出现 关弹窗后跳报告实例列表并带上 businessType + businessId

弹窗标题不带类型中文名(仍保持这个做法):mes_qc_rqc 这一路曾有措辞分歧——
后端枚举、表注释、菜单权限子项都写作「退货检验」,只有菜单父项与 mes_qc_type 字典写作「退料检验」。
现已统一为**「退货检验」**(理由:RQC 的对象同时覆盖生产退料、销售退货、售后退货,
「退货检验」是能覆盖三者的说法;且它本就是多数派)。标题仍不带类型名,少一处需要跟着变的文案。

API

方法 路径 说明 权限码
POST /qc-report/instance/generate-from-qc 由质检单生成报告(数据从 MES 取) qc-report:instance:generate
GET /qc-report/template/selectable 取「可用于出件」的模板列表 qc-report:template:query

qc-report:instance:generate 是**独立权限码**,与 :create:query 都不通用。

GET /qc-report/template/selectable 请求参数:

参数 类型 必填 说明
reportType String 报告类型,即 mes_qc_type 的**数值字符串**,如 "1" / "4"。传 "IQC" 这类英文名会一个都查不到

响应: Template[](**数组,不是分页结构**),字段与模板分页接口一致。

为什么单开这个接口,而不是在前端过滤模板分页结果:
两条过滤条件都落在**版本表**上,分页接口返回的 currentVersion 只是模板表上的一个字符串,看不到版本状态与画布内容。

  1. 版本未发布:停用一个版本**不会**清空模板的 currentVersion,于是会出现「currentVersion 有值、但那个版本已停用」的模板;这种模板选中后出件必然失败(1_070_101_003)。
  2. 画布没有可渲染内容:GrapesJS 把「打开过设计器但一个组件都没放」的项目存成 {"assets":[],"styles":[]}——它非空,所以旧判据会放它过去,渲染时又取不到根组件、body 渲染成空串,**不报错**,用户最终拿到一张白纸 PDF。

两条都是「看起来能选、选了必出问题」,判断它们要读版本行,放在后端做才是单点;
前端拿着分页结果自己推导会得到「看起来对但会漏」的结论。

已补入 system_menuid=1075416,名称「报告实例质检单出件」,类型 3(按钮),
挂在「报告实例」(parent_id=1075410)下,sort=5。非超管角色需授予该菜单后才可见按钮 / 可调用接口
(超管由代码直返全部菜单,不受影响)。

请求参数

参数 类型 必填 说明
qcType Integer 质检类型:1 来料检验(IQC) / 2 过程检验(IPQC) / 3 出货检验(OQC) / 4 退货检验(RQC)
qcId Long 质检单编号(四张表各自的主键)
templateId Long 报告模板编号。**其 reportType 必须与 qcType 一致**
version String 模板版本号,如 v1.0。不传则取模板的 currentVersion
reportNo String 报告编号。传了就用它(撞号会报错),不传由后端自动生成

请求示例

{
  "qcType": 1,
  "qcId": 1,
  "templateId": 1,
  "version": "v1.1"
}

响应字段

字段 类型 说明
id Long 报告实例编号,拿它去查详情 / 跳转
reportNo String 最终生效的报告编号
templateVersion String 本次使用的模板版本号
itemCount Integer 写入报告的检验项条数
undecidableCount Integer 没有判定结论的项数(「无判定规则」+ 规则执行失败的「待判定」),见「业务规则」
warnings Array<String> 软提示:样品的取舍、没成组的分组项、为组头补出来的行、指标已删除、未录入实测值
errors Array<String> 渲染期数据缺口(与通用出件接口同一套机制)

响应示例

{
  "code": 0,
  "data": {
    "id": 38,
    "reportNo": "QR20260919-0001",
    "templateVersion": "v1.1",
    "itemCount": 14,
    "undecidableCount": 5,
    "warnings": [
      "以下 2 个分组项在本单的检验明细里没有自己的行(多半是检验模板只选了子项),报告里为它们各补了一行来显示组名与公式:「水分、酸含量」",
      "以下 1 个指标是指标库里的分组项,但本单没有属于它们的子项,报告里按独立检验项列出、组名不合并:「色度」"
    ],
    "errors": []
  }
}

报告上下文新增字段(设计器可绑定)

出件时后端写进报告的每个检验项上,新增了下面 4 个字段。它们不在这条接口的响应里,而是体现在**报告上下文**中,
供模板在设计器里绑定(「分组检验项表」组件已内置这些绑定,一般不需要手工配):

字段 类型 取值 说明
item.requirement String 组头行 = 该指标的公式;其余行 = 单据上的标准要求 「检测要求」列。与 standardValue 并存不合并:两者来源不同(一个是指标主数据的公式,一个是单据行的标准要求)
item.group.childName String 组内子项 = 自己的名字;组头行与独立项 = 空串 「子项」列
item.group.span Integer 组内第一行 = 该组总行数;同组其余行与独立项 = 1 「检验项目」列的合并行数
item.group.hidden String 组内第一行与独立项 = 空串;同组其余行 = hidden hidden 属性靠「在不在」起作用(写成 hidden="false" 照样隐藏),所以只有「空串 = 这一格可见」这一个可判定的形态

没有分组的报告不会带 group:整个报告一个分组都没有时,item.group 这个键不出现,模板里也就不会多出一层空格子。
一旦报告里有分组,则**每一项都会带 group**(独立项给空串与 1)—— 字段缺席会让合并属性被跳过,反而把该隐藏的格子露出来。

两个字段必须成对使用item.group.spanitem.group.hidden 只作用在「检验项目」这一列上。
不要把它们绑到别的列(如「检测要求」):组内子项各自的检测要求要逐行显示,并进组头格会把它们翻掉。

字段展示规则

字段 展示位置 说明
undecidableCount 生成成功后的提示条 不要当成故障报警。它数的是「没有结论的项」,其中绝大多数是「过程参数」这类正常情况(试样质量 m、称量瓶 m0 —— 只供公式取数,本来就没有可判定的规格)。提示文案建议「本次有 N 项没有判定结论」;**若确实要区分**「正常无规则」与「规则没跑通」,请以报告详情里各检验项的 resultText 为准(无判定规则 / 待判定),不要用这个总数反推
warnings 生成成功后的提示条 建议可折叠;内容已经是完整中文句子,直接原文展示即可,不要改写
errors 同上 数据缺口,与通用出件接口同义
reportNo 成功提示、跳转后详情页头部 业务唯一键,建议带「复制编号」
id 不展示 用来跳转 /qc/report-instance 详情

业务规则说明

场景 规则
谁提供数据 后端。前端**不要**自己拼 context 传进来,本接口不接受 context 参数
类型不合法 qcType 不在 1~4 → 1_070_105_000,报文里列出收到的值与四个合法值
单据不存在 1_070_105_001,报文带单据类型中文名与 ID
单据未完成检验 状态不是「已完成」→ 1_070_105_002,报文带**当前状态中文名**,用户一眼知道卡在哪一步
单据没有判定 已完成但 checkResult 为空 → 1_070_105_003,报文列出四个可选判定
模板类型不符 模板 reportTypeqcType1_070_105_005,报文**同时给出模板类型与单据类型两边的中文名**。**前端应在选模板时就把不符的过滤掉**,这条是兜底
模板没配报告类型 模板 report_type 为空 → 同样 1_070_105_005,但报文改成提示「请先给该模板设置报告类型」
没有可用检验项 1_070_105_006,报文说明具体原因(如「只有分组标题」)
能出多份报告吗 。同一张质检单可以反复出件,每次生成一份独立报告(历史留痕需要)。**不做重复生成拦截**,前端也不必拦
检验项怎么来的 质检单「判定依据行」(max_threshold/min_threshold/标准值)与「实测值明细」(按样品 × 指标)**交叉**展开。**不做任何指标过滤**,全部进报告
多个样品怎么办 同一个(样品 × 指标)展开成**一条**报告检验项。样品编号只在**样品数 > 1** 时折进该项的备注(如「样品 S1」),单一样品时不写,避免污染
样品太多怎么办 样品数超过 3 时,报告头的「样品编号」字段**留空**并发 warnings(列出前 3 个与前 N 总数),不截断明细数据
分组会怎样(本轮改动) 分组项**不再被跳过**。分组父项(如「水分」)作为一行进报告,组内子项紧随其后,组名在报告上纵向合并 —— 报告能看出哪些子项属于哪一组,父项的公式也不再丢
组名与公式放哪 组名放「检验项目」列并向下合并;公式放组头那一行的「检测要求」列。**组内子项各自的检测要求仍逐行保留**,二者不互相覆盖
谁是组头 后端按指标的 parent_id 判归属(不看行序、不看数值类型),并给每行下发 group.span(合并几行)与 group.hidden(这一格要不要让位)。前端**不需要也不会**自己算合并行数
分组项本单没有子项 按独立检验项列出、组名不合并,并把名字收进 warnings(「…是指标库里的分组项,但本单没有属于它们的子项…」)
组头本单没有行 若检验模板只勾了子项,后端**为组头补一行**来放组名与公式,并在 warnings 里说明为哪几个组头补了行。补出来的行实测值为空,判定为「无判定规则」,不影响合格率与结论
指标被删了会怎样 单据行引用了一个已被删除的指标 → 该条跳过,warnings 里报「有 N 条明细所引用的检验指标已被删除」
没录实测值会怎样 该项照常进报告,但 warnings 提示「第 N 项「X」尚未录入实测值」;**组头的锚点行除外**(它按定义没有实测值,值都在子项行上,报出来是误报)
报告行序变了 从此前的「行表顺序」改为按指标的 sort_order 升序(缺失排最后,同值保持行序),组内子项按各自的 sort_order。这样报告顺序与检验单的设计意图一致;默认不需要前端做任何事
哪张画布组件能渲染分组 需要模板里用**「分组检验项表」(GroupedQualityTable)**。用它之外的表(如基础「检验项表格」)不会出现合并效果 —— 数据照常下发,只是模板没画。二者用其一:有分组用前者,纯平层用后者
判定谁算 报告级的 result/resultText/conclusion/passRate 由**后端判定引擎**算(逐项规则 → 规格上下限 → 数据自带结果),与通用出件一致
质检单的原判定去哪了 存进报告的 report.qcResult / report.qcResultText 两个**新字段**,与引擎判定**并存、互不覆盖**。质检单上的判定是人工拍的板(合格/特采/不合格退货/不合格报废),引擎只有三态装不下它,所以分开存
undecidableCount 怎么算 数「快照里 result 为空串的项」。这正是引擎对「待判定」的定义,也顺带把「卡在规则报错上」的项算进去,不另算一遍判定条件
原判定是数字 qcResult 是数字字符串("1"~"4"),qcResultText 才是中文(合格/特采/不合格退货/不合格报废)。**画布上显示 qcResultText**
模板必须已发布 与通用出件一致,草稿版本会被拒(1_070_101_003)。复用既有错误码,本接口不另造
模板已停用 复用 1_070_100_002
报告编号撞号 复用 1_070_102_001。已软删除的编号**不回收**
数据快照冻结 出件时把判定后的数据整份冻结。MES 里的单据后来怎么改,**都不影响这份历史报告**

注意事项

  • engine/context.tsqcResult / qcResultText 已补齐(类型接口 + emptyReport() 空串默认值),
    设计器的绑定面板里现在可以选到这两个字段,画布上也能绑。若要用它们,模板需自行绑到某个文本组件上。
  • 模板列表要按 reportType 过滤:模板的 report_type 存的是字典数值字符串("1"/"2"/"3"/"4"),
    qcType 直接相等比较即可,不要拿 "IQC" 这类英文名去比。
  • qcType 传数值不传字符串名:后端按 1~4 识别,传 "IQC" 会得到 1_070_105_000
  • 不要缓存 warnings:它只在本次响应里出现,**不落库**。事后回看历史报告时看不到当时有哪些提示。
    需要留痕请在出件成功时自己记一份。
  • warningserrors 分工warnings 是「数据取舍的软提示」(跳过、截断、缺值),
    errors 是「模板绑定了但没数据的字段」这类渲染期缺口。**两者都要展示**,不要只显示其中一个。
  • itemCount 不等于质检单的行数:指标被删会被跳过、一个指标多条实测值会展开成多条、
    组头在本单没有行时后端会补一行,所以这个数**只能用来展示**,不要拿它去和质检单行数做断言。
  • 部分字段刻意不带过去:MES 侧检验项上的「检验方法」(checkMethod)与「检验工具」(tool
    在报告上下文里**没有承载位**,本轮未传递。若报告要展示这两列,需要先扩 InspectionItem 契约
    (那是改动前端可见的数据契约,需单独确认后再做)。
  • 质检单上没有「部门」:报告的 department 字段恒为空,不要拿别的字段硬凑。
  • 批次号取哪个字段按类型不同:来料检验取供应商批号,出货/退货取批次号,过程检验两者都没有(只有工单号)。这是后端按业务语义定的,前端不用管。
  • 报告实例落库时 businessType 是表名mes_qc_iqc / mes_qc_ipqc / mes_qc_oqc / mes_qc_rqc),
    与前端路由/字典里的英文枚举同名但与字典数值不同,反查时**直接用这四个字符串**。
  • 失败时不要重试出件:五道校验失败都是「数据或选择不对」,重试同样会失败。按报文提示去改数据或换模板。
  • 失败报文由请求拦截器自动弹出,业务代码不要再弹一次code != 0 时拦截器已把后端 message
    显示成 toast,调用处只写 catch {} 即可,重复提示会让用户看到两条一样的报错。
  • 权限:本接口权限码 qc-report:instance:generate,已补入 system_menuid=1075416)。

错误码

本接口新增(1_070_105_xxx

错误码 报文 触发场景
1_070_105_000 质检类型「9」不合法,只支持 IQC(来料检验)、IPQC(过程检验)、OQC(出货检验)、RQC(退货检验) qcType 不在 1~4
1_070_105_001 来料检验的质检单(ID=999999)不存在或已被删除,请刷新列表后重新选择 qcId 查不到
1_070_105_002 来料检验的质检单「IQC20260919001」当前状态为「草稿」,尚未完成检验,不能生成报告。请先把该单据提交并完成检验判定后再生成 单据状态非「已完成」
1_070_105_003 来料检验的质检单「IQC20260919001」尚未填写检验判定(判定为空),不能生成报告。请先在该单据上填写判定结论(合格 / 特采 / 不合格退货 / 不合格报废)后再生成 已完成但 checkResult
1_070_105_005 报告模板的报告类型是「出货检验」,与本次质检单的类型「来料检验」不一致,无法生成报告。请重新选择一张来料检验的报告模板 模板与单据类型不符(或模板未配类型)
1_070_105_006 来料检验的质检单「IQC20260919001」(ID=1)没有任何检验指标,无法生成报告。请先在该单据上录入检验指标与实测值后再生成 单据没有任何检验指标行。若其中部分明细引用的指标已被删除,报文会在括号里补一句「其中 N 条明细所引用的检验指标已被删除」

复用既有错误码(本接口不另造副本)

错误码 报文 触发场景
1_070_100_000 模板不存在 templateId 找不到
1_070_100_002 该模板已停用,不能生成报告 模板 status=1
1_070_101_000 模板版本不存在 version 传了但查不到
1_070_101_003 该模板版本未发布 version 指向草稿
1_070_101_007 该模板还没有已发布的版本,无法生成报告,请先在设计器中发布一个版本 不传 versioncurrentVersion 为空
1_070_101_008 该模板版本没有画布内容,无法生成报告,请先在设计器中设计并保存模板内容 版本有记录但画布空
1_070_102_001 报告编号「X」已被占用,请换一个编号,或留空由系统按 QR+日期+流水 自动生成 指定了重复编号

与原始方案文档的偏离(已落地口径):方案文档 §7 里列的 105_004(模板不存在)与 105_007(模板无已发布版本)
未实现,改为复用上面的既有码。理由:那几条文案本来就是精确的,再造一遍只会多两条会漂移的副本。