后端模块:
yudao-module-qcreport接口前缀:/admin-api/qc-report/instance
上游数据源:yudao-module-mes(MesQcReportApi,本模块不直接读 MES 表)
本轮范围:**由 MES 质检单(IQC/IPQC/OQC/RQC)直接出报告**。前端只需选「哪张单据 + 用哪张模板」,
数据全部由后端从 MES 取,**前端不再自己拼context**。
| 页面 | 路由 | 说明 | 状态 |
|---|---|---|---|
| MES 质检单列表(IQC/IPQC/OQC/RQC 四个页面) | /mes/qc/iqc、ipqc、oqc、rqc |
行操作新增「出报告」:选模板 → 调本接口 → 跳报告实例列表(已带筛选条件) | 已落地 |
| 报告实例列表页 | /qc/report-instance |
生成后按 businessType + businessId 反查得到;搜索表单已加「业务单据」字段并支持路由带入 |
已落地(views/mes/qc/report/instance/index.vue) |
| 模板设计器 | /qc/report-template |
无改动(模板需先在设计器里配好检验项表格并发布) | 已落地 |
与
qc_report_instance_frontend_integration.md的分工:那份讲的是「调用方自己准备数据」的通用出件;
本份讲的是「数据从 MES 取」的专用入口。两者最终都落到同一个实例表、同一个列表页。
reportType 与该单据类型一致的模板**)→ 调 generate-from-qc。MesQcReportApi 装配质检单四层结构(单头 / 判定依据 / 样品 / 实测值)generate 流程(版本发布校验、编号生成、快照冻结、判定引擎全部照旧)。reportNo / itemCount / undecidableCount / warnings / errors),businessType=mes_qc_iqc + businessId=1 即可查出该单据出过的所有报告。带入关系要点
qcType + qcId 是主线,从质检单列表行带入。qcType 用字典 mes_qc_type 的**数值**(1/2/3/4)。templateId 由用户在弹窗里选。**前端必须先按 reportType 过滤模板列表**,否则用户会选到后端必然拒绝的组合。version 不传时后端取模板的 currentVersion(要求已发布)。设计器刚存了草稿想立刻出件才需要显式传。reportNo 不传由后端自动生成(QR + 日期 + 4 位流水)。businessType(表名,如 mes_qc_oqc)businessId(质检单主键的字符串形式)两个参数,报告实例列表页在挂载时把它们预填进搜索表单,| 位置 | 元素 | 展示规则 | 说明 |
|---|---|---|---|
| 质检单列表行操作 | 「出报告」按钮 | 仅当 status === 已完成 时显示 |
与「编辑」「删除」(仅草稿可见)互斥,避免用户对未完成的单据点 |
| 弹窗标题 | 出报告 - {单据编号} |
— | 编号由列表行带入,仅用于展示 |
| 弹窗选模板区 | 单选模板列表 | 由后端 /qc-report/template/selectable 返回:reportType 与本单类型一致、status=启用、**currentVersion 指向的版本处于「已发布」、且该版本的画布有可渲染的内容** |
无可选项时给空状态提示,引导先去模板设计器配好并发布 |
| 弹窗结果区 | 成功提示 + 告警区 | 生成成功后就地切换,不关弹窗 | undecidableCount > 0 用**蓝色信息条**(不是黄色告警):这项数里绝大多数是「过程参数」这类正常情况,标黄会在几乎每份报告上误报 |
| 弹窗底部 | 「查看报告列表」 | 结果区才出现 | 关弹窗后跳报告实例列表并带上 businessType + businessId |
弹窗标题不带类型中文名(仍保持这个做法):
mes_qc_rqc这一路曾有措辞分歧——
后端枚举、表注释、菜单权限子项都写作「退货检验」,只有菜单父项与mes_qc_type字典写作「退料检验」。
现已统一为**「退货检验」**(理由:RQC 的对象同时覆盖生产退料、销售退货、售后退货,
「退货检验」是能覆盖三者的说法;且它本就是多数派)。标题仍不带类型名,少一处需要跟着变的文案。
| 方法 | 路径 | 说明 | 权限码 |
|---|---|---|---|
| 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 只是模板表上的一个字符串,看不到版本状态与画布内容。
currentVersion,于是会出现「currentVersion 有值、但那个版本已停用」的模板;这种模板选中后出件必然失败(1_070_101_003)。{"assets":[],"styles":[]}——它非空,所以旧判据会放它过去,渲染时又取不到根组件、body 渲染成空串,**不报错**,用户最终拿到一张白纸 PDF。两条都是「看起来能选、选了必出问题」,判断它们要读版本行,放在后端做才是单点;
前端拿着分页结果自己推导会得到「看起来对但会漏」的结论。
已补入
system_menu:id=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.span 与 item.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,报文列出四个可选判定 |
| 模板类型不符 | 模板 reportType ≠ qcType → 1_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.ts 的 qcResult / qcResultText 已补齐(类型接口 + emptyReport() 空串默认值),reportType 过滤:模板的 report_type 存的是字典数值字符串("1"/"2"/"3"/"4"),qcType 直接相等比较即可,不要拿 "IQC" 这类英文名去比。qcType 传数值不传字符串名:后端按 1~4 识别,传 "IQC" 会得到 1_070_105_000。warnings:它只在本次响应里出现,**不落库**。事后回看历史报告时看不到当时有哪些提示。warnings 与 errors 分工:warnings 是「数据取舍的软提示」(跳过、截断、缺值),errors 是「模板绑定了但没数据的字段」这类渲染期缺口。**两者都要展示**,不要只显示其中一个。itemCount 不等于质检单的行数:指标被删会被跳过、一个指标多条实测值会展开成多条、checkMethod)与「检验工具」(tool)InspectionItem 契约department 字段恒为空,不要拿别的字段硬凑。businessType 是表名(mes_qc_iqc / mes_qc_ipqc / mes_qc_oqc / mes_qc_rqc),code != 0 时拦截器已把后端 messagecatch {} 即可,重复提示会让用户看到两条一样的报错。qc-report:instance:generate,已补入 system_menu(id=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 |
该模板还没有已发布的版本,无法生成报告,请先在设计器中发布一个版本 | 不传 version 且 currentVersion 为空 |
1_070_101_008 |
该模板版本没有画布内容,无法生成报告,请先在设计器中设计并保存模板内容 | 版本有记录但画布空 |
1_070_102_001 |
报告编号「X」已被占用,请换一个编号,或留空由系统按 QR+日期+流水 自动生成 | 指定了重复编号 |
与原始方案文档的偏离(已落地口径):方案文档 §7 里列的
105_004(模板不存在)与105_007(模板无已发布版本)
未实现,改为复用上面的既有码。理由:那几条文案本来就是精确的,再造一遍只会多两条会漂移的副本。