diff --git a/activities/part-substitution-assessment/__tests__/activity.test.ts b/activities/part-substitution-assessment/__tests__/activity.test.ts index 9589f97..2cd42c2 100644 --- a/activities/part-substitution-assessment/__tests__/activity.test.ts +++ b/activities/part-substitution-assessment/__tests__/activity.test.ts @@ -2,8 +2,9 @@ * ACT-WORK-05 差异评估的包内自测 —— **只准 import @sdk / @contracts / @sfeel**。 * 案例活动没有 `run`,自测不跑回合;测的是清单本身与授权面对齐,结构照 E04 / I01。 * - * 六组:清单本身合规 / 台账声明 / 案例模型与清单的对齐 / 报告模版:编辑面与只读面各就各位 / - * 徽标别名与值域相关的坑(2026-08-27 实测账,回归网)/ 活动入口。 + * 七组:清单本身合规 / 台账声明 / 案例模型与清单的对齐 / 报告模版:编辑面与只读面各就各位 / + * 徽标别名与值域相关的坑(2026-08-27 实测账,回归网)/ 可编辑件的主键与必填列 + * (2026-08-27 复审,回归网)/ 活动入口。 */ import { describe, expect, it } from 'vitest'; import { activityManifestSchema } from '@contracts'; @@ -304,6 +305,56 @@ describe('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () }); }); +/* + * 2026-08-27 定向复审逮住的两条 Critical(都是这一轮版面重做新引入的回归,不是原有的坑): + * ① `sub_eval.input_gap` 的「键」(`item_key`)那一格丢了 `data-col` —— 一单 11 行,`item_key` + * 逐行不同,人保存**任何一行**时服务端 `resolveRowKey` 凑不出主键(它既不在件上、 + * 又不是「全表一致」的那种案号型列),整格落不下去; + * ② `sub_eval.input_gap.status` 声明 `required: true`,件上又有「+ 加一行」—— + * 降成只读徽标后,人新加的行永远没有格子能把这一列填上,`_missing` 恒为正、 + * 提交闸永远拦着,这是「墙不配路」的死路形态。 + * 两条各配一张回归网,防的是同一类事故再出现在别的字段上。 + */ +describe('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归)', () => { + /** 三张真能编辑的件与它们各自绑定的台账对象 */ + const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [ + { view: 'p08-master-create', domain: 'parts', object: 'profile' }, + { view: 'p10-document-create', domain: 'parts', object: 'document' }, + { view: 'eval-input-list', domain: D, object: 'input_gap' }, + ]; + + it('每一个主键列都有 data-col —— 它不是编辑面,是行的钥匙,丢了就整行保存不下去', () => { + for (const { view, domain, object } of EDITABLE) { + const table = byAddress(domain, object); + /* + * `caseKey` 声明的那一列(如 `eval_ref`)是例外:它是「一单一号,全表当然一致」的 + * 案号型主键(与 I01 的 `round_ref` 同款),件上压根不摆,服务端 `resolveRowKey` + * 按「全表一致」自动补上(`apps/api/src/app/routes/desk.ts` 注释里点名的那条路)—— + * 摆出来给人填反而是错的(那会让人以为能按行改案号)。真正逐行不同、必须挂 + * `data-col` 的,是主键里除 caseKey 之外的那几列(如 `item_key`)。 + */ + const pk = (table?.fields.filter((f) => f.primaryKey).map((f) => f.name) ?? []).filter((name) => name !== table?.caseKey); + expect(pk.length, `${domain}.${object} 没有非 caseKey 的主键列`).toBeGreaterThan(0); + const html = viewHtml(view); + for (const col of pk) { + expect(html, `${view}: 主键列 ${domain}.${object}.${col} 缺 data-col`).toContain(`data-col="${col}"`); + } + } + }); + + it('可编辑件上,每一个 required:true 的列都有真能填的控件(data-col)—— 必填但填不了是墙不配路', () => { + for (const { view, domain, object } of EDITABLE) { + const table = byAddress(domain, object); + const required = table?.fields.filter((f) => f.required === true).map((f) => f.name) ?? []; + expect(required.length, `${domain}.${object} 没有声明 required 列`).toBeGreaterThan(0); + const html = viewHtml(view); + for (const col of required) { + expect(html, `${view}: 必填列 ${domain}.${object}.${col} 没有可填的控件`).toContain(`data-col="${col}"`); + } + } + }); +}); + describe('活动入口', () => { it('没有 run —— 行为全在清单里', () => { expect(activity.run).toBeUndefined(); diff --git a/activities/part-substitution-assessment/src/manifest.ts b/activities/part-substitution-assessment/src/manifest.ts index 320fe38..b3d2d8f 100644 --- a/activities/part-substitution-assessment/src/manifest.ts +++ b/activities/part-substitution-assessment/src/manifest.ts @@ -84,7 +84,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'ACT-WORK-05 · 差异评估', - packageVersion: '1.1.0', + packageVersion: '1.1.1', category: '需求分析与方案匹配', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-part-substitution-assessment', diff --git a/activities/part-substitution-assessment/src/views/report.ts b/activities/part-substitution-assessment/src/views/report.ts index 4983aaa..115ab82 100644 --- a/activities/part-substitution-assessment/src/views/report.ts +++ b/activities/part-substitution-assessment/src/views/report.ts @@ -436,6 +436,7 @@ const PN_CHECK_TABLE = `
const GAP_TABLE = `
+ @@ -444,9 +445,10 @@ const GAP_TABLE = `
#键 输入项 来源 状态
+ - + @@ -473,7 +475,7 @@ export const EVAL_INPUT_LIST = doc({ gapbar(`${D}_input_gap`, '补充内容') + editGate(`${D}.input_gap`, `还有 项待补 / 待确认,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) + GAP_TABLE + - addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新加行的「输入项」与「补充内容」为必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') + + addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新行的「键」必须写(它是这一行的钥匙,写一个英文短名,如 thermal_note);「输入项」与「补充内容」也是必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') + flow( '审批 / 提交:本工单只承载内容;「提交」请在对话区的【执行卡】中进行。提交后去向:进入步骤 2 · AI 读取与标准化,仍缺失的项将按 UNKNOWN 进入差异评估。注意:启动参数如有误,需退回触发方修订后重新触发,本单不可改。', ),
${cell('item_key', 'mono')} ${cell('input_item')} ${cell('source_ledger', 'mono')}${badge('st', 'status', true)}${select('status', [['gap', '待补'], ['default', '默认·可改'], ['ok', '齐备']], true)}${badge('st', 'status')} ${cell('detail')} ${cell('supplement')}