Archived
W05 在途修订:差异评估结论审核件一个候选一张表 · 关键台账数据看板件(ledger-board)· S1 清单不再把人刚填的参数摆成缺口
活动包 `part-substitution-assessment`(case-model / manifest / report / target-style / badge / ledger-board 与两份包内测试)、 目标件「验收工单-01-差异评估结论审核」、`docs/ss02-act-05` 关键台账数据两份、端到端测试记录、e2e 脚本。 逐条口径见同批 CLAUDE.md 快照。2026-08-27 另一会话做的,一直留在工作树没入库;与同批另两条提交互相依赖。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
+51
-1
@@ -42,6 +42,13 @@ const E2E_CLIENT_SECRET = process.env.KEYCLOAK_E2E_CLIENT_SECRET ?? '';
|
||||
* 不设它才走服务账号那条正路(ADR-012)。
|
||||
*/
|
||||
const DIRECT_BEARER = (process.env.JCAI_E2E_BEARER ?? '').replace(/^Bearer\s+/i, '').trim();
|
||||
/**
|
||||
* **要不要落真库**(`JCAI_E2E_LAND_MASTER=1`)—— 默认关:起单接口默认开着(人手点那条路要看见数据真变),
|
||||
* 而 e2e 关它是为了保护「只能测一次」的样例(A1 新客户来函 / W05 候选缺库:主数据一旦真建进去,
|
||||
* 第二次跑那条路就成了老客户 / 都在库)。开着跑一次能验「数据工单确认即入库」真的落到了正式表 ——
|
||||
* 跑完记得把建进去的料号清掉,不然 W05 案例 0/2 从此测不出缺库那条路。
|
||||
*/
|
||||
const LAND_MASTER = process.env.JCAI_E2E_LAND_MASTER === '1';
|
||||
/** access token 只活 5 分钟:超过 4 分钟就换一枚,免得长案例中途 401 */
|
||||
const TOKEN_REFRESH_MS = 4 * 60 * 1000;
|
||||
|
||||
@@ -553,6 +560,8 @@ const SUITES = {
|
||||
rowKey: 'internal_pn',
|
||||
blankCol: 'brand_name',
|
||||
expectGateBlock: true,
|
||||
// 数据工单确认即入库(2026-08-27 用户定)—— 交卷那一刻这张卡的表就该进正式表
|
||||
landsOnConfirm: true,
|
||||
edits: [],
|
||||
// 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了)
|
||||
fillAllBlank: {
|
||||
@@ -580,6 +589,7 @@ const SUITES = {
|
||||
rowKey: 'internal_pn',
|
||||
blankCol: 'document_title',
|
||||
expectGateBlock: true,
|
||||
landsOnConfirm: true,
|
||||
edits: [],
|
||||
fillAllBlank: {
|
||||
doc_seq: '01',
|
||||
@@ -838,6 +848,27 @@ function draftRows(drafts, address) {
|
||||
return (drafts ?? []).filter((d) => d.address === address).map((d) => ({ ...d.key, ...d.patch }));
|
||||
}
|
||||
|
||||
/**
|
||||
* **数据工单确认即入库**(2026-08-27 用户定:「数据工单里填的数据,确认后就应该入库」)——
|
||||
* 交卷那一刻就验,不等放行:
|
||||
* · 痕上必须有一条「数据工单确认入库 / 已确认(试跑」(基座在回传路由里同步落的,交卷返回时已在);
|
||||
* · 落真库时(`JCAI_E2E_LAND_MASTER=1`)正式表里必须查得到件上填的那几颗料;
|
||||
* · 不落真库时痕必须如实写「只留在活动缓存」—— 界面不撒谎那一条也要验。
|
||||
* 回 'landed' / 'held' / 'missing'(没这条痕 = 这道门没开)/ 'absent'(说落了、正式表里没有)。
|
||||
*/
|
||||
async function expectLandedOnConfirm(taskId, stage, rows) {
|
||||
const { body } = await api(`/api/gov/testbed/activity/${taskId}`);
|
||||
const note = [...(body.trail ?? [])].reverse().find((t) => t.kind === 'ledger-write' && /数据工单(确认入库|已确认)/.test(t.title ?? ''));
|
||||
if (note === undefined) return 'missing';
|
||||
if (/试跑|未落正式表/.test(note.title)) return LAND_MASTER ? 'absent' : 'held';
|
||||
if (!LAND_MASTER) return 'landed';
|
||||
const [domain, object] = stage.address.split('.');
|
||||
const formal = await api(`/api/gov/data/${domain}/${object}`);
|
||||
const keys = Array.isArray(stage.rowKey) ? stage.rowKey : [stage.rowKey];
|
||||
const present = (row) => (formal.body.rows ?? []).some((f) => keys.every((k) => String(f[k] ?? '') === String(row[k] ?? '')));
|
||||
return rows.every(present) ? 'landed' : 'absent';
|
||||
}
|
||||
|
||||
/** 交卷执行卡 —— 载荷零字段(执行卡上没有任何输入,填写全在件上) */
|
||||
async function submitExecution(cardId) {
|
||||
await api('/api/desk/work-orders/reply', {
|
||||
@@ -1135,7 +1166,7 @@ async function runCase(suite, kase) {
|
||||
data: sample.input,
|
||||
message: sample.message ?? '',
|
||||
ownerId: '',
|
||||
landMaster: false,
|
||||
landMaster: LAND_MASTER,
|
||||
}),
|
||||
});
|
||||
const taskId = started.taskId;
|
||||
@@ -1147,6 +1178,8 @@ async function runCase(suite, kase) {
|
||||
// 录入卡的已答名单单独记 —— 与 decided 共用会把 decideSeq 按 size 取的序号顶掉(实测:点名驳回被跳过)
|
||||
const entered = new Set();
|
||||
let gateBlocked = false;
|
||||
/** 数据工单确认了却没入库(或说入库了而表里没有)—— 收工判失败 */
|
||||
let landingBroken = false;
|
||||
/** 资料文件那一次检查的结论(在办中验,见下面那段注释) */
|
||||
let fileCheck;
|
||||
let gateLeaked = false;
|
||||
@@ -1331,6 +1364,19 @@ async function runCase(suite, kase) {
|
||||
}
|
||||
console.log(` 交卷执行卡:${card.title}`);
|
||||
replied.add(card.id);
|
||||
if (stage.landsOnConfirm === true) {
|
||||
const verdict = await expectLandedOnConfirm(taskId, stage, tables[stage.address] ?? []);
|
||||
console.log(
|
||||
verdict === 'landed'
|
||||
? ` 数据工单确认即入库:${stage.address} 已进正式表`
|
||||
: verdict === 'held'
|
||||
? ` 数据工单已确认(试跑不落主数据,痕如实写「只留在活动缓存」)`
|
||||
: verdict === 'missing'
|
||||
? ` ⚠️ 数据工单确认了,痕上没有「确认入库」那一条 —— 这道门没开,判失败`
|
||||
: ` ⚠️ 痕说入库了,正式表里查不到件上填的那几行 —— 判失败`,
|
||||
);
|
||||
if (verdict === 'missing' || verdict === 'absent') landingBroken = true;
|
||||
}
|
||||
} else if (card.mode === 'acceptance') {
|
||||
if (kase.acceptEdits && !acceptEdited) {
|
||||
acceptEdited = true;
|
||||
@@ -1379,6 +1425,10 @@ async function runCase(suite, kase) {
|
||||
console.log(' ⚠️ 提交闸没拦住空手交卷 —— 判失败');
|
||||
ok = false;
|
||||
}
|
||||
if (landingBroken) {
|
||||
console.log(' ⚠️ 数据工单确认即入库那道门没兑现 —— 判失败');
|
||||
ok = false;
|
||||
}
|
||||
// 「超限才叫人」是 A4 的要害:该叫的那一单没叫,等于底价门禁是摆设
|
||||
if (missedRows.size > 0) {
|
||||
console.log(` ⚠️ 案例点名要填的行,整例跑完也没在件上出现过(${missedRows.size} 处:${[...missedRows].slice(0, 3).join('、')}…)—— 判失败`);
|
||||
|
||||
Reference in New Issue
Block a user