A2-A7 全部补齐到验收线;顺手把几处「门拦住却不给路」的坑填了

活动:A2 选型 / A3 寻源 / A4 定价 / A6 议价 / A7 归档五件各补成真活动包 ——
契约宽进(input.required 空,归一在活动内)、台账必填声明(含行级 requiredWhen)、
入场哨兵与 L1/L2 判据一律读基座数、两份件(在办可编辑 + 办结静态快照)、
三案例常备、包内自测。e2e 各三案例 released、行动被拒 0、发口正确、提交闸每单真拦。

基座(共享面,改一处影响所有活动,故每处都带回归):
· 重发判断走在站上场之前 —— 人答完那步常已收掉,readyToWork 先抛「不能再往它上面挂活」,
  于是「重发已答过的卡 = 无事发生」这条唯一活路在最需要时够不着,A1 基线当场熔断;
· 决策卡补幂等兜底(与执行工单同一把尺):头两次无事发生,第三次起才算被拒;
· 输出没成形先就地重问一次,原话进工作日志、不再摆上卡面;
· 工具调用读数 tool_<名>_calls —— 「机检只读基座数」这条纪律第一次覆盖到工具;
· 执行卡 steps/standard 补进出参 schema(从前能到卡面全靠厂商 JSON 模式宽松);
· 件的就地更新改逐节点 morph,保存先把新值镜像进只读那份 —— 不闪、不等回声。

A1(基线,全程守住):措辞三档对齐(必填/建议/选填)、禁止拿历史价当客户目标价、
卡骨架从步骤剧本搬进说明书(压成一拍连做时剧本没摊开,会发出没有指导书的裸卡)。

契约两处**新增可选字段**(非破坏):FieldDeclaration.requiredWhen、工单 finalHtml;
api-report 同批更新。docs/adr 与 docs/archive 的 13 份文件在工作树里不见了,
本次予以恢复而非提交删除。

门禁:typecheck / lint / depcruise / test(1366)全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 12:28:31 +08:00
co-authored by Claude Opus 5
parent 85466e815f
commit ade0fa5a94
90 changed files with 17969 additions and 2316 deletions
@@ -11,7 +11,7 @@
*/
import { describe, expect, it } from 'vitest';
import { evaluateCondition } from '@sfeel';
import { INTAKE_PHASES, INTAKE_STEPS } from '../src/case-model.ts';
import { INTAKE_PHASES, INTAKE_STEPS, NARRATIVE } from '../src/case-model.ts';
import { CMMN_XML } from '../src/case-model.ts';
import { manifest } from '../src/manifest.ts';
@@ -152,8 +152,23 @@ describe('doneWhen 是「成果已存在」的判据,不是「该不该做」的
const confirm = INTAKE_STEPS.find((s) => s.item === 'S3_confirm');
expect(confirm, '受理需求交人审定这一步不见了').toBeDefined();
expect(confirm?.view).toBe('intake-overview');
// 卡上那张表绑台账整份铺开 —— 人改的是九行,不是缺的那一两行
expect(confirm?.guide).toContain('"ledger":"quote_intake.record"');
/*
* **执行卡零输入**(2026-08-02 用户定:填写全去工作区)——
* 卡上不绑表、不摆格子;清单在工作区那份件上就地改(editGate + 孪生件),
* 提交闸回落到必交付物的台账声明照样把必填看住。
* 从前这儿断言卡里带 `"ledger":"quote_intake.record"` 的 blocks —— 那一版
* 同一张表摆两处(卡上一份、件上一份),人不知道改哪一份算数。
*/
/*
* 骨架已搬进说明书(2026-08-03:模型常在「拆表+收步+派卡」同一拍发这张卡,
* 那一拍 S3 的剧本没摊开 —— 骨架只放剧本里就会发出裸卡,实测指导书整个是空的)。
* 剧本里留的是指路;零输入纪律与骨架本体都在说明书里,每一拍都看得见。
*/
expect(confirm?.guide).toContain('骨架在说明书里');
expect(NARRATIVE).toContain('受理需求审定卡的骨架');
expect(NARRATIVE).toContain('卡上不摆表、不摆格子');
expect(NARRATIVE).toContain('"itemId":"S3_confirm"');
expect(confirm?.guide).not.toContain('"blocks"');
expect(confirm?.guide).toContain('confirmed_total');
});
+86 -54
View File
@@ -50,6 +50,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
(连跑十轮里三轮死在这)。自报的两个数照旧写,它们是给报告和人看的。
-->
<caseFileItem id="quote_intake_record_missing" name="缺必填的行数(基座现数)"/>
<caseFileItem id="quote_intake_record_rows" name="受理记录行数(基座现数)"/>
<caseFileItem id="missing_key_total" name="缺必填字段条数(PN/年用量)"/>
<caseFileItem id="missing_minor_total" name="缺选填字段条数(品牌/备注)"/>
<caseFileItem id="asked_total" name="已就缺口问过人的次数"/>
@@ -74,10 +75,17 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!--
② 拆完了 —— 交人审定那一步的入场前提。只看「有没有拆出行来」:
缺不缺字段不影响该不该给人看,缺口是**卡上要说清的事**,不是拦住这张卡的理由。
读**基座数的行数**,不读自报的 line_total(2026-08-02 用户实测送来的单,
「机检只读基座数」第四次被违反现形):模型把 record 三行在 S1 拍就写了،
却没记 line_total(那本是 S2 的账),S2 空手收步 —— 这道门于是永远不开,
引擎报「依赖断点」,模型被推去发补全卡绕路,两张多余的卡把 4 次人环
预算烧光,整单 exhausted。行落了没有,基座数得出来;门不该等一个
要模型自己记的数。
-->
<sentry id="s_parsed">
<onPart id="op_ps" caseFileItemRef="line_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_ps"><condition>line_total &gt; 0</condition></ifPart>
<onPart id="op_ps" caseFileItemRef="quote_intake_record_rows"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_ps"><condition>quote_intake_record_rows &gt; 0</condition></ifPart>
</sentry>
<!--
③ **人审定过了** —— 后面查历史价/打标的入场前提。
@@ -126,7 +134,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!--
① 定位客户信息 —— 拿发件邮箱去客户主数据里认人。
认得出就取它的客户编码;认不出说明这是个**新客户**,发补全卡请人建档
认得出就取它的客户编码;认不出说明这是个**新客户**,发数据录入卡请人建档
(公司名、客户编码、联系人)—— 建档是人的事,活动不替他编一个客户出来。
-->
<humanTask id="S1_customer" name="定位客户信息:按发件邮箱认客户,认不出请人建档">
@@ -135,7 +143,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!--
② 归一客户 RFQ 输入 —— 解析邮件正文,逐条落成受理记录。
PN / 年用量**必填**,其余**选填**;缺哪几格照实记进两个缺口数。
PN / 年用量**必填**;客户料号 / 品类 / 制造商 / 目标价是**建议追问**;其余**选填**。缺哪几格照实记进两个缺口数。
**这一步不叫人**:拆料是 AI 的活,拆完交给下一步给人看。
-->
<humanTask id="S2_normalize" name="归一客户RFQ输入:解析正文逐条落表">
@@ -226,6 +234,14 @@ S5 逐条定状况标 → S6 定去向与派单 → 提交放行。
「LM358DR 有近期成交价可以直接报;NE555P 半年多没动过要重新核;TL072CDR 停产了,得找替代。」
· S6 定完去向 → view = intake-release。**说这单接下来往哪走、谁接手**。
**交代里说缺什么,用件上那三个词**(必填 / 建议 / 选填),而且**只说已发生的事**:
客户没给的就说「客户没给」并留空 —— 估价、垫数这类事你没有权限做,更不许在交代里
声称做过(见「三种分量」那段:目标价尤其如此)。历史价、三分类、去向是**审定之后**
那几步的事:这一拍它们连数都还没有,一个字都不要提。
派审定卡那一拍的交代照这个样子说(数字与料号换成本单的):
「三颗料拆好了,交期用量都齐;LM358DR、TL072CDR 客户给了目标价,NE555P 没给
(建议追问,不拦单)。整份受理需求请你审定,哪一格不对在工作区的件上就地改。」
别每拍一个腔调复读(「已完成X,现在进行Y」)。事情不同,话就该不同 ——
调工具那一拍说「我去查这三颗的历史价」,拿到结果那一拍说查出了什么,
两句话该是不一样的语气。有风险、要他拿主意、或者你打算叫人,**先说出来**,
@@ -240,6 +256,31 @@ S5 逐条定状况标 → S6 定去向与派单 → 提交放行。
发卡 / 提交会挂起(它们只能是串尾)。**这一单常路 5-7 拍就该收口** ——
每多拆一拍,就是多烧一遍完整上下文。
【受理需求审定卡的骨架 —— 哪一拍发都照这个,一个字段别少】
拆完受理记录就要派这张卡(S3_confirm)。**骨架放在说明书里正是因为你常在
「拆表 + 收步 + 派卡」同一拍里发它** —— 那一拍 S3 的剧本还没摊开,凭记忆拼出来的
就是一张只有一句话的裸卡(实测:人收到的指导书整个是空的)。照抄:
{"kind":"human","itemId":"S3_confirm","workOrder":"execution","card":{
"title":"受理需求审定:<单号> 共<N>条","ask":"<照当下情形写,见下>",
"steps":["型号 PN 与年用量 EAU 必须有 —— 缺一条这一单就走不到放行;客户没给的去问,问不到也要在备注里写清「客户未提供」",
"客户料号、品类、制造商、目标价这四格是建议追问 —— 不填也走得到放行,但那一问不能省;尤其目标价,有它才谈得上「报得进不进得去」,客户不肯给就问一句「贵司这颗现在的成交价区间」",
"客户没给的一律留空,由你判断要不要追问 —— 系统查到的历史价是分类依据,不是客户的目标价",
"改在工作区的件上:抽错的就地改,多出来的行按行首 × 撤掉,漏掉的用表尾「加一行」补上;改完保存,回这张卡提交"],
"standard":"必填(PN / 年用量)一条不缺;建议档(客户料号 / 品类 / 制造商 / 目标价)该问的都问过了 —— 问到的填上,问不到的留空并在备注里写明,放行时由你决定先受理还是退回。",
"basis":[{"k":"<缺格的 PN>","v":"缺年用量(必填,须补)"},
{"k":"<缺格的 PN>","v":"缺制造商(建议追问,可先受理)"},
{"k":"<缺格的 PN>","v":"客户未给目标价(建议追问,不拦单)"}]}}
· steps 与 standard 是**作业指导书**,卡上照原样摆给人看 —— 这一段每一单都一样,照抄别重写;
**卡面是纯文本**:里面别写星号那类记号,它们不会变粗体,只会原样摆在人眼前(实测过)。
· ask 照当下情形写:**有必填缺**写「有几条缺 PN/年用量,须向客户问清后填回」;
**只缺建议档**写「有几条缺建议追问的格子(客户料号/品类/制造商/目标价),可先受理也可去问」;
**都齐**写「请核对确认」。三档的词照件上那三个词说,别混用。
· basis 逐条写**谁缺什么、是必填还是建议还是选填**;一格不缺就写这一单的要点
(几条料、客户点名的口径要求)。
· **卡上不摆表、不摆格子**(填写全在工作区):整份清单就在工作区那份件上,
每一格能就地改、能增删行,缺哪几格件顶上的红带点着名。别往卡里塞 blocks 或 fields ——
塞了是把同一张表摆两处,人不知道改哪一份算数。
§Z 结案:BD 在验收单上签字,系统把草稿合并成正式记录并发出 intake 口的载荷。`;
/**
@@ -261,7 +302,7 @@ S5 逐条定状况标 → S6 定去向与派单 → 提交放行。
export const INTAKE_STEPS = [
{
item: 'S1_customer',
intent: '按发件邮箱认客户;认不出就发补全卡请人建档,建完写进客户主数据',
intent: '按发件邮箱认客户;认不出就发数据录入卡请人建档,建完写进客户主数据',
writes: ['customer.profile', 'customer.contact', 'quote_intake.verdict'],
/* 认客户就靠这两张 —— 没声明的话它们会被整表段的截断刀砍掉(踩过) */
reads: ['customer.contact', 'customer.profile'],
@@ -320,7 +361,7 @@ export const INTAKE_STEPS = [
③ **记账两个数,引擎按它们收步**(别绕过去,绕不过去):
· customer_new_total —— 第①步认出来了记 0;认不出、要建档记 1;
· customer_asked_total —— 发过建档补全卡就记 1。
· customer_asked_total —— 发过建档卡就记 1。
**新客户没问过人,这一步收不了** —— 实测有过一次:模型从邮箱域名
编出 \`CUST-SZ-MACH\` 直接把档建了,人根本没被问过一句,而那个编码是错的。
客户编码是主数据的主键,编错一次后面每一单都跟着错。
@@ -406,14 +447,29 @@ export const INTAKE_STEPS = [
【一张卡别铺太长】缺口卡的 basis 与表格**只列真正缺的那几行**,不要把全部条目铺进去:
整份回复有长度上限,铺太长会在半途被截断 —— 截断的 JSON 解析不了,
这一拍作废、还会弹一张「需要你判断下一步」把人叫来(实测)。缺几条就写几条。
【两种分量 —— **必填只有两项,别的一个都不是**】
【三种分量 —— 与件上的空占位、执行卡的指导书**逐字同一套话**】
台账声明里怎么标,件上就是什么颜色,你说话就得用哪个词。三档只有这三个词:
· **必填(只有这两项)**:\`pn\`、\`eau\` 年用量 —— 缺了这一单没法报价,
**这是验收标准**,不齐不许放行,记进 missing_key_total。
· **选填(除上面两项之外的一切)**:客户料号、品类、制造商、**目标价**、交期、品质要求、备注……
缺了照样往下走,只当提醒,记进 missing_minor_total。
**目标价尤其常缺** —— 客户不给目标价是常态,不是缺陷:实测栽过一次,
一颗料只缺目标价却被记成必填缺、还派了执行卡去问客户,而按 F-01 它根本不该拦单。
拿不准某个字段算哪一档,就问自己一句:**它是不是 PN 或年用量?** 不是就是选填。
**这是验收标准**,不齐不许放行(件上标红「必填」),记进 missing_key_total。
· **建议(值得追问,但不拦单)**:\`customer_pn\` 客户料号 · \`category\` 品类 ·
\`brand\` 制造商 · \`target_price\` 目标价 —— 声明里标了 \`expect\`,件上标橘「建议」。
缺了照样往下走,但**那一问不能省**:执行卡上要写清「这几格建议追问」。
· **选填(其余一切)**:交期、品质要求、备注…… 件上标灰「选填」,缺了只当提醒。
建议档与选填档一并记进 missing_minor_total。
**别把「建议」说成「选填」**(2026-08-03 用户当场指出):同一件事,工单上标着「建议追问」,
交代里若换成另一档的叫法,人会以为这两处说的不是一回事 —— 而它们就是一回事。
**目标价客户没给就留空,不许估**:它是**客户的意向价**,不是我们算得出来的数。
S4 查到的历史价是**判 A/B/C 的依据**(距上次报价几天、行情稳不稳),
**不是客户的目标价** —— 拿它填进 \`target_price\` 就是替客户表了态,
下游定价、议价全建在这个假数上。也不许只在**交代里声称**估过价、垫过数
(实测栽过一次:台账留空是对的,嘴上却说按历史价估了 —— 而那一步连历史价都还没查,
纯属编造,人读了就以为表里躺着一个估出来的数)。留空、在件上标橘「建议」、
在执行卡上写明建议追问,这三件事合起来才是对的处置。
**目标价常缺是常态,不是缺陷** —— 实测栽过一次:一颗料只缺目标价却被记成必填缺、
还派了执行卡去问客户,而按 F-01 它根本不该拦单。
拿不准某个字段算哪一档,就问自己一句:**它是不是 PN 或年用量?** 不是就不许拦单。
【怎么拆】逐条写进 \`quote_intake.record\`,一条需求一行(同一 PN 不同数量档算两行)。
列名照台账字典写,别自己起名:
@@ -473,7 +529,7 @@ export const INTAKE_STEPS = [
* **不给 doneWhen** —— 这一步触达世界(把件交到人手上),给了就等于给引擎
* 一把替人签字的钥匙。而它的成果按定义也判不出来:「人看过、改过、认了」
* 不是台账里任何一个数能自己变出来的。人工激活也一并给上:
* 引擎不会替你按下这一步,你得先 start 再发卡。
* 引擎不会替你按下这一步,也不会替你收 —— 发卡就算启动,收要你自己 finish。
*/
guide: `拆好的九条摆在那儿了,**这一单现在要经过人的手**。
@@ -485,39 +541,15 @@ export const INTAKE_STEPS = [
人一眼没看过受理需求,下一次被叫到已经是终局验收 —— 用户当场说
「我都没确认需求 它就让我验收了」。
【怎么发】先 start 这一步(它是人工激活的),再照这个骨架发,别少字段:
{"kind":"human","itemId":"S3_confirm","workOrder":"execution","card":{
"title":"…","ask":"这是从来件与附件 BOM 归一出来的受理需求,请核对确认;哪一格不对就地改",
"steps":["型号 PN 与年用量 EAU 必须有 —— 缺一条这一单就走不到放行;客户没给的去问,问不到也要在备注里写清「客户未提供」",
"目标价尽量试探出来 —— 有它才谈得上「报得进不进得去」;客户不肯给,问一句「贵司这颗现在的成交价区间」也常有答案",
"抽错的就地改,多出来的行按行首 × 撤掉,漏掉的用表尾「加一行」补上;核完回这张卡提交"],
· **卡面是纯文本**:\`steps\` 与 \`standard\` 里**别写星号那类记号** —— 它们不会变成粗体,
只会原样摆在人眼前(实测过一次)。要强调就靠句子本身的次序与用词。
"standard":"必填(PN / 年用量)一条不缺;选填该问的都问过了 —— 问到的填上,问不到的留空并在备注里写明,放行时由你决定先受理还是退回。",
"basis":[{"k":"DAG2400L321504XZB01-R0","v":"缺年用量(必填,须补)"},
{"k":"UMW AO6604","v":"缺制造商(选填,可先受理)"}],
"blocks":[{"t":"table","title":"有效 PN 清单(可就地改)",
"ledger":"quote_intake.record","editable":true,
"cols":["PN","客户料号","品类","年用量","制造商","目标价","备注"],
"fields":["pn","customer_pn","category","eau","brand","target_price","remark"]}]}}
**\`steps\` 与 \`standard\` 是作业指导书** —— 卡上照原样摆给人看:要核什么、核到什么程度算完。
照上面抄即可(这一步每一单都一样,不必每次重写)。少了它,人拿到的只有
「请核对确认」四个字,不知道该核什么。
· \`ask\` 照当下情形写:**有必填缺**就写「有几条缺 PN/年用量,须向客户问清后填回」;
**只有选填缺**写「有几条缺选填项,可先受理也可去问」;**都齐**写「请核对确认」。
· **别自己抄行**:绑了 \`ledger\` + \`fields\`,基座发卡那一刻就把这一单**整份清单**
从草稿层铺进去 —— 人看到的是完整的九行,哪一格不对都能就地改,不只是缺的那一两行。
你抄一遍的下场实测过两次:回复有长度上限,铺长了半途截断、整拍作废;
抄的时候还把 BOM 原样的 30 填了进去(台账里已经是 30000)。
· \`fields\` 与 \`cols\` **一一对应**,列名照台账字典写。
· \`basis\` 逐条写清**谁缺什么、是必填还是选填** —— 分量不同,人心里要有数;
一格不缺就写这一单的要点(几条料、几条本轮不报、客户点名的口径要求)。
【怎么发】**卡的骨架在说明书里(「受理需求审定卡的骨架」那一段),逐字段照抄** ——
不管你在哪一拍发它,骨架都在说明书里等着,不要凭记忆拼一张裸卡。
这一步人工激活:发卡就算启动,不必单发 start;引擎也不会替你收 —— 人交回后要你自己 finish。
发完卡把 **asked_total 加 1**(记账用:它证明这一问没省)。
【人提交之后 —— **表里的值基座已经替你写回了**】
那张表绑着台账,人按下提交那一刻基座就把改动落进了草稿层(与数据录入卡同一条规矩:
该由人亲手落下的,不经你的手)。所以**别再 write 一遍那几行** ——
值没变的写入会被「写了等于没写」拒回(实测)。
【人提交之后 —— **件上的改动基座已经替你写回了**】
人在件上按「保存」那一刻,改动就落进了草稿层(与数据录入卡同一条规矩:
该由人亲手落下的,不经你的手);他按「提交」只是把这张卡交回来。
所以**别再 write 一遍那几行** —— 值没变的写入会被「写了等于没写」拒回(实测)。
你这一拍只做三件事,然后 finish:
① **把 missing_key_total / missing_minor_total 重算** —— 照台账里现在的值,
@@ -526,14 +558,14 @@ export const INTAKE_STEPS = [
③ answered_total 加 1(答案已经处理过了)。
漏掉①的下场实测过:值明明补上了,汇总列还写着「缺 1 条」,提交被判据一次次拦回来,
连拒五拍熔断 —— 而你只要把那个数改对就过去了。
回填时**按卡上那一列的 PN 对行**:卡上的 PN 就是台账里那一行的主键,
真要补写某一行时**按台账里那一行的 PN 对行**:PN 是主键,
换一个写法就是**新开一行**(实测:补写时把 PN 补全成 \`…-R0\`,于是同一颗料在表里成了两行,
条目数说 9 而表里躺着 10)。真发现第一次的 PN 抄错了,在 say 里说明并交给人,
别自己用新 PN 再写一行。
【**这张卡一单只发一次**】人交回来之后,不论还缺什么,**都不许再发同一张卡**。
必填(PN / 年用量)有提交闸兜着:缺一条他根本按不下提交,所以卡交回来时必填一定是齐的;
选填缺口不拦单,留着交放行的人定。
建议档的缺口不拦单,留着交放行的人定。
实测栽过多次(2026-08-02 那一单连撞四拍):从前这儿写着「人交回来还缺必填,就再发一张卡」——
而提交闸上线后那条路已经不存在了,模型照它办,每次都撞在
「这件事刚问过,人也答过了」上,白烧四拍。
@@ -542,7 +574,7 @@ export const INTAKE_STEPS = [
这时候要做的**不是再问一遍**(基座会当场拒:同一件事不许问第二遍),而是:
· confirmed_total 记 1、answered_total 记 1(人已经处理过了);
· 缺口数**照实留着**(缺就是缺,别改小);
· 这一步就算办完了 —— 选填缺不拦单,放行时由人定「先受理还是退回」。
· 这一步就算办完了 —— 建议档的缺口不拦单,放行时由人定「先受理还是退回」。
实测栽过一次:人留空交回,模型又发一张一模一样的卡,被拒;
转头把同一行原样再写一遍,又被「写了等于没写」拒;连拒五拍熔断,一单就这么停了。`,
},
@@ -664,19 +696,19 @@ A2 / A3 拿到之后会按自己的口径归一需要的数据。你替它们先
tools: [],
// 放行触达世界,必须人按一下 —— 引擎不自动收它(剧本里也不给 doneWhen)
guide: `line_total > 0、missing_key_total = 0、untyped_total = 0、两枚旗已定 ——
**先 start T_release**(它标了人工激活:放行会触达世界,必须按一下)。
可以走放行了(T_release 人工激活:动它就算启动,不必单发 start)。
**start 之后、submit 之前,把清单里**每一条**裁量判据(L3/L4)都裁掉**
(同一拍里排在 start 后面即可)。**一条都不能漏** —— 漏一条,submit 当场被拒,
**submit 之前,把清单里**每一条**裁量判据(L3/L4)都裁掉**。
**一条都不能漏** —— 漏一条,submit 当场被拒,
而拒回来的理由里就写着漏的是哪一条:照它原样抄 criterion 再裁一次即可,
**别原样重提同一个 submit**(实测连提七次,每次都被同一条拒回来)。
眼下**只剩一条**,照名字逐字抄(2026-08-02 起「吃不准降 B」那条已从清单里删掉,
别再裁它 —— 裁一个不存在的判据会被拒):
· judge criterion="选填缺口(客户料号/品类/制造商/目标价)已补齐,或由你放行时确认可以先受理" ——
**只看 \`quote_intake_record_gaps\` 这一个数**(基座现数的:还有几行缺选填;
· judge criterion="建议追问的四格(客户料号/品类/制造商/目标价)已补齐,或由你放行时确认可以先受理" ——
**只看 \`quote_intake_record_gaps\` 这一个数**(基座现数的:还有几行缺**建议追问**的格子;
\`..._gaps_keys\` 写着缺在哪几行的哪一格)。就两种写法,没有第三种:
· **= 0** → satisfied=true,rationale 写「选填已补齐」;
· **= 0** → satisfied=true,rationale 写「建议追问的那几格都补齐了」;
· **> 0** → satisfied=**false**,rationale 照 \`..._gaps_keys\` 逐条写清缺在哪儿
(例:「DAG2400L321504XZB01-R0 缺目标价」),交放行的人定「先受理还是退回」。
+13 -6
View File
@@ -73,7 +73,7 @@ export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'A1 · 询价受理',
packageVersion: '42.0.0',
packageVersion: '49.5.0',
category: '统一报价',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-quote-intake',
@@ -192,15 +192,23 @@ export const manifest: ActivityManifest = {
* 等于把一个可回放的判定换成一次不可回放的手感。
*/
ports: [
/*
* ── 这里**去掉了 `altRequests` / `rfqRequests` 两个载荷字段**(@43,2026-08-02)──
*
* `alt_request` / `rfq_request` 两张台账表同日已退休(见 declarations 那段墓志铭),
* 这两个键从那天起就是**空壳**:载荷装配对数组属性一律塞「本单全部落地行」
* (不加区分),于是它们的内容与 `records` 完全相同 —— 挂着一个名字叫
* 「替代评估请求」而里面装的是受理记录的键,比没有更糟:下游照名字用,用到的
* 是另一样东西。下游(A2/A3)自己按 `records` 里的状况标归一,契约照实说。
*/
{
name: 'to-alternative',
payload: {
type: 'object',
required: ['rfqRef', 'records', 'altRequests'],
required: ['rfqRef', 'records'],
properties: {
rfqRef: { type: 'string', title: '询价单号(单标识 —— 下游台账的主键)' },
records: { type: 'array', title: '询价受理记录(已打标)' },
altRequests: { type: 'array', title: '替代评估请求' },
},
},
description: '有 C 类要替代 —— 去选型',
@@ -211,11 +219,10 @@ export const manifest: ActivityManifest = {
name: 'to-sourcing',
payload: {
type: 'object',
required: ['rfqRef', 'records', 'rfqRequests'],
required: ['rfqRef', 'records'],
properties: {
rfqRef: { type: 'string', title: '询价单号(单标识 —— 下游台账的主键)' },
records: { type: 'array', title: '询价受理记录(已打标)' },
rfqRequests: { type: 'array', title: '询价请求' },
},
},
description: '无选型但有 B 类 —— 去寻源',
@@ -365,7 +372,7 @@ export const manifest: ActivityManifest = {
*/
{
key: 'optional-asked',
label: '选填缺口(客户料号/品类/制造商/目标价)已补齐,或由你放行时确认可以先受理',
label: '建议追问的四格(客户料号/品类/制造商/目标价)已补齐,或由你放行时确认可以先受理',
level: 'L4',
predicate: 'quote_intake_record_gaps = 0',
},
+21 -6
View File
@@ -55,13 +55,27 @@ import { PRD_CSS, PRD_SPRITE } from './prd-style.ts';
* 这里由基座把值消毒成 `v-*` 追加进 class(`data-class-of`)—— 判断在样式表里,不在填数里。
*/
const BIND_CSS = `
/* ① 空格子自己说话 —— 值真的空才轮到这两条(基座 data-blank) */
/* ① 空格子自己说话 —— 值真的空才轮到这三条(基座 data-blank)。
* 占位三级(2026-08-03 补齐,用户:「目标价是建议」):必填红 / 建议橘 / 选填灰,
* 档位跟台账声明走(required → req,expect → sug,其余 opt),颜色与 A2 同一套。 */
/* 胶囊与同一行的数字坐同一条线(2026-08-03 用户:「没有和数字对齐」)——
* 裸 inline-flex 的胶囊按 baseline 挂:盒子(10px 字 + 上下 3px 内距 + 边框 = 18px)比
* 字的基线深好几像素,视觉上整枚往下坠。治法:空格子自己撑起文字那份盒高(12px×1.5),
* 胶囊在里面垂直居中 —— 中线对中线,行高也不再忽高忽低。
* :not(.on) 不能省(2026-08-03 用户:「编辑态太拥挤」):这条比「编辑态藏起只读份」
* 的那条更特异,不圈住的话编辑态里胶囊会盖过 display:none 冒出来,54 个输入框旁边
* 各站一枚 —— 编辑态里格子该不该填由闸与红带说,胶囊只属于看的那份。 */
.proto-root .jc-tbl-in:not(.on) td.req > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.sug > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.opt > .jc-vcell:empty { display: flex; align-items: center; min-height: 18px; }
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after {
display: inline-flex; align-items: center; font-style: normal; font-size: 10px; font-weight: 700;
line-height: 1; padding: 3px 7px; border-radius: 5px; border: 1px dashed;
}
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after { content: "必填"; color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3; }
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after { content: "建议"; color: #b45309; background: #fff7ed; border-color: #fcd9a8; }
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after { content: "选填"; color: var(--ink-4); background: var(--soft); border-color: var(--line); }
/* 归档件里空格子就是「客户没给」,不再摆「你还能填」的胶囊 */
.proto-root .jc-tbl-in td.dimempty > .jc-vcell:empty::after { content: "—"; color: var(--ink-4); }
@@ -388,7 +402,7 @@ const listTable = (mode: 'draft' | 'final'): string => {
* 待处理那一屏可就地改;写的是**台账列名**,不是表头文字 ——
* 表头改个说法不该让写回错位。归档件不挂:那时它是件,不是表单。
*/
const cell = (key: string, head: string, kind: 'req' | 'opt', style = ''): string => {
const cell = (key: string, head: string, kind: 'req' | 'sug' | 'opt', style = ''): string => {
const cls = mode === 'final' ? 'dimempty' : kind;
return `<td class="${cls}" data-h="${head}"><span class="jc-vcell ${style}" data-slot="${key}" data-col="${key}" data-blank></span></td>`;
};
@@ -414,10 +428,10 @@ const listTable = (mode: 'draft' | 'final'): string => {
${del}
${mode === 'final' ? '<td class="jc-ty" data-h="分类"><span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag" data-col="type_tag"></span></td>' : ''}
${cell('pn', 'PN', 'req', 'mono')}
${cell('category', '品类', 'opt')}
${cell('category', '品类', 'sug')}
${cell('eau', '年用量', 'req', 'mono')}
${cell('brand', '制造商', 'opt')}
${cell('target_price', '目标价', 'opt', 'num')}
${cell('brand', '制造商', 'sug')}
${cell('target_price', '目标价', 'sug', 'num')}
${cell('remark', '备注', 'opt')}
</tr></tbody>
</table></div>`;
@@ -537,7 +551,8 @@ export const INTAKE_OVERVIEW = doc({
addRowBar('quote_intake.record') +
`<div class="jc-drop">${ico('info', 13)}
<span>带 * 的两列是<b>必填</b>(PN / 年用量):缺任一按 F-01 退回补全,不许放行;
其余列缺了标灰「选填」—— 先受理、后追问,<b>不为它们拦单</b>,但那一问不能省。
品类 / 制造商 / 目标价缺了标橘「<b>建议</b>」—— 值得追问、尽量补上,备注缺了标灰「选填」。
三档都<b>不为它们拦单</b>,先受理、后追问,但那一问不能省。
<b>原文没写的一律不补</b>:受理阶段编出来的数会一路流到定价和报价单上,越往后越难发现。</span>
</div>`,
foot: sign('AI 拟稿,你审定 —— 要改哪一格,在「待处理」那张单的需求表上就地改', '统一报价 · A1 受理归一'),