This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/activities/quote-negotiate/src/case-model.ts
T
ChaiHaoandClaude Opus 5 ade0fa5a94 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>
2026-08-03 12:28:31 +08:00

442 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* A6 报价议价的案例模型 —— **双表示同版本**:CMMN XML(机器面)与案例说明书(叙事面)。
*
* 业务出处:`docs/MVP示例工作流.html` §1.4(S1 发报留档 → S2 跟进议价 → S3 定案交接),
* 按 A4 同一条形状建模(2026-08-03 重写 @3.0.0):
*
* 上游(A5 批准的报价草案,A4 `draft` 口的载荷 `{rfqRef, customerCode, items}`)进来 →
* 归一成**正式报价函**底表(逐条条款齐备 + 按 P-04 注明有效期)→ **一张执行单**
* 交 BD:把报价函发给客户,回音回来后逐条落单(客户表态那一列 AI 一格都不写)→
* 按议价结果 N-01 逐条定案(接受 / 还价 / 不成交)→ **只有还价那几条才叫人拍板**
* (决策卡:照客户价成交 / 退回定价重批 / 守住现价)→ 放行,去向由三口的谓词算出来。
*
* 上一版(@2.1.0)四处不合现行口径,这一版整个置换:
* ① 入参必填 `rfqRef + customerCode + items` —— 而宽进严出是全线口径,归一在活动内做;
* ② 三条 L1/L2 判据读的是活动自报的 `missing_field_total` / `validity_days` /
* `followup_total` —— 「机检只读基座数得出来的数」违反史第六类;
* ③ 台账一个 `required` 都没有 —— 执行卡的闸没牙,人不填也能交卷;
* ④ 三个出口没有 `when`:往哪走要模型自己选,而**去向该是算出来的**。
*
* 【为什么没有 `listen` 动作】等客户回音这一段落在**执行工单**上,不落 `listen`:
* `listen` 在引擎里恒挂起(status: 'awaiting'),要外部事件才唤得醒;而这一单里
* 「客户回了什么」的落单人本来就是 BD(他从自己的邮箱收信,再把结论录进系统)——
* MVP 示例工作流 §1.4 的 S1「生成发出」与 S2「记录反馈」写的也都是**人工任务**。
* 时限那一侧照旧硬约束:`declarations.listeners` 三条静态监听(D+3 催 / D+7 到期 /
* 客户回音)由基座在开案那一刻注册,时限必配 —— 「无来源又无时限的等待」一条都没有。
*/
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
<case id="negotiate">
<!--
案卷项的 id **就是任务视图里的键名**。上游载荷(items)与本活动台账列都摊平到
任务视图顶层,照它们命名;quote_negotiate_* 那几个是**基座读数**
(行数 / 缺必填 / 按值现数),由基座现算,模型碰不着。
-->
<caseFileModel>
<caseFileItem id="items" name="上游报价草案条目(已批准,流内开案即有)"/>
<caseFileItem id="line_total" name="报价函条数(自报)"/>
<caseFileItem id="composed_total" name="已排版落表的条数(排版走完的凭据)"/>
<caseFileItem id="replied_total" name="已落客户回音的条数(人环走完的凭据)"/>
<caseFileItem id="route_decided_total" name="已定案"/>
<caseFileItem id="quote_negotiate_line_rows" name="报价函行数(基座现数)"/>
<caseFileItem id="quote_negotiate_line_missing" name="缺客户表态的行数(基座现数)"/>
<caseFileItem id="quote_negotiate_line_deal_status_haggle" name="还价了但还没拍板的条数(基座现数)"/>
<caseFileItem id="quote_negotiate_line_reply_type_counter" name="客户还了价的条数(基座现数 —— 客户自己那一格)"/>
</caseFileModel>
<casePlanModel id="plan" name="报价议价" autoComplete="true">
<!--
① 排版完了 —— 交 BD 发出那一步的入场前提。
行数两边对上(自报 = 基座现数),且排版那一轮走完(composed_total 是排完之后
显式写下的数;「大于等于 0」对「写过的 0」为真、对「没写过」为假)。
-->
<sentry id="s_composed">
<onPart id="op_cp" caseFileItemRef="composed_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_cp"><condition>line_total &gt; 0 and quote_negotiate_line_rows = line_total and composed_total &gt;= 0</condition></ifPart>
</sentry>
<!--
② 回音齐了 —— 逐条定案那一步的入场前提。
replied_total 只在人交回执行单之后才产生,它就是「经过人了」的凭据;
缺表态的行数由基座按 required 现数(与执行卡的提交闸、放行门 L1 同一个数)。
-->
<sentry id="s_replied">
<onPart id="op_rp" caseFileItemRef="replied_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_rp"><condition>replied_total &gt; 0 and quote_negotiate_line_rows &gt; 0 and quote_negotiate_line_missing = 0</condition></ifPart>
</sentry>
<!--
③ **还价才叫人** —— 这个活动的要害(与 A4「超限才叫人」同一形状)。
它读的是**客户自己那一格**(reply_type = COUNTER 的条数,基座按取值域现数),
不是模型判出来的 deal_status。两版都栽过,病根是同一个 ——
**「该不该叫人」不能由被叫的那一方说了算**:
· 一版读 deal_status = HAGGLE,而 revise 那条路让「定案不动」,人点完了那个数
还在,哨兵照旧成立,模型读同一段做法就再发一张卡(同一张连发六次);
· 修成「拍板必须清 HAGGLE」之后,模型改在定案那一步**直接写 REVISE** ——
HAGGLE 一条没有,这道门整个不开,单子一路 released 而没人拍过板
(实测:决策卡 0 张,让步梯度成了摆设)。
现在钉在客户填的那一格上:客户还了价,这一步就必须入场,模型绕不过去。
-->
<sentry id="s_haggle">
<onPart id="op_hg" caseFileItemRef="quote_negotiate_line_reply_type_counter"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_hg"><condition>quote_negotiate_line_reply_type_counter &gt; 0 and replied_total &gt; 0</condition></ifPart>
</sentry>
<!--
── 放行门的入场条件:**认「办完了没有」,且读基座数**────────────────
· 每条都有客户表态(quote_negotiate_line_missing 由基座按 required 现数);
· 定案逐条落定且守恒(四档相加 = 行数,两边都是基座按值现数);
· **没有还压在 HAGGLE 上的条目**(还价的要么接、要么退回重批、要么守价不让);
· 定案过了(route_decided_total 是定案那一步显式写下的记账数)。
-->
<sentry id="s_ready">
<onPart id="op_rd" caseFileItemRef="route_decided_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_rd"><condition>route_decided_total &gt; 0 and quote_negotiate_line_rows &gt; 0 and quote_negotiate_line_missing = 0 and quote_negotiate_line_deal_status_haggle = 0 and quote_negotiate_line_deal_status_won + quote_negotiate_line_deal_status_haggle + quote_negotiate_line_deal_status_revise + quote_negotiate_line_deal_status_lost = quote_negotiate_line_rows</condition></ifPart>
</sentry>
<!-- ① 归一报价函:逐条条款齐备 + 按 P-04 注明有效期(能算的不问人) -->
<humanTask id="S1_compose" name="落正式报价函:逐条条款齐备,按报价有效期注明到期日">
<itemControl><repetitionRule id="r_s1"/></itemControl>
</humanTask>
<!--
② 发出去并把回音落回来 —— **一张执行单**(触达客户的那一下由人做)。
manualActivation + 不给 doneWhen(发卡触达真人,引擎不替人按)。
-->
<humanTask id="S2_send" name="发出报价函并落客户回音:工作单上逐条落表态">
<entryCriterion id="ec_s2" sentryRef="s_composed"/>
<itemControl><manualActivationRule id="m_s2"/></itemControl>
</humanTask>
<!--
③ 逐条定案 —— SOP 里的 S3,一件独立的事:按客户表态判成交 / 还价 / 不成交,
还价的再按让步梯度 N-02 给出处置建议。它不触达世界,所以给 doneWhen,
引擎自己收 —— 从前这一段藏在「等回音」的收尾里,模型写完 replied_total
就把那一项收了,定案一格没写,放行门当场判「定案没落定」而拒回只会说
「数不对/行不对」——两扇打不开的门(A4 实测 8 次拒回熔断)。
-->
<humanTask id="S3_settle" name="逐条定案:按客户表态判成交/待拍板/不成交,还价的给让步处置">
<entryCriterion id="ec_s3" sentryRef="s_replied"/>
<itemControl><repetitionRule id="r_s3"/></itemControl>
</humanTask>
<!-- ④ 异议拍板:让价触达公司利益的那一下,必须人工激活;只在还有还价条目时入场 -->
<humanTask id="T_haggle" name="异议拍板:客户还的价接不接,要人定">
<entryCriterion id="ec_hg" sentryRef="s_haggle"/>
<itemControl><manualActivationRule id="m_hg"/><repetitionRule id="r_hg"/></itemControl>
</humanTask>
<!-- 议价结果放行:终局唯放行 —— 触达世界,必须人工激活 -->
<humanTask id="T_release" name="议价结果验收放行">
<entryCriterion id="ec_rel" sentryRef="s_ready"/>
<itemControl><manualActivationRule id="m_rl"/></itemControl>
</humanTask>
</casePlanModel>
</case>
</definitions>`;
/**
* 案例说明书 —— **只写整单一以贯之的事**(逐步做法归剧本,两处不重复写同一件事)。
*/
export const NARRATIVE = `§0 开案:**一份已批准的报价要发出去,并谈出一个结果**。来路两种,形状都收(宽进):
· **流内转来**:任务数据里有 \`items\` 数组 —— A4 的报价草案条目(每条带拟报单价与门禁处置),
A5 已经批过;这是最全的一种来路;
· **单飞**:只有一段需求文字,说了要给谁发报价、几颗料什么价。
**成品只有一样**:一份**正式报价函** —— 整份条目都在、**每一条都有客户表态**,
而且每一条都定了案:成交 / 退回重批 / 不成交。人签的不是「发出去了」,
是**这一单谈成了什么**。它决定整条流往哪走。
(还价那一档 HAGGLE 是过渡态:**客户还了价、还没拍板**。放行前它必须清零 ——
拍板那一下把它换成成交、退回重批或不成交,三条路都是结论,都算办完。)
**能算的不问人,该客户说的不代说**:议价结果 N-01(表态 → 定案)、让步梯度 N-02
(还价幅度 → 处置建议),两支判定表说了算,你不临场拍;而**客户表态那一列是人落的,
你一格都不写** —— 客户回了什么只有收信的人知道,系统不替客户表态。
**四步走**:归一排版报价函 → 交 BD 发出并落回音 → 逐条定案 → 拍板定案放行。
**这一单要经过人一到两次**:BD 在议价工作单上逐条落客户表态(那张单绑着整份报价函,
表态缺一条按不下提交 —— 基座的闸看着,他填的格子直接落账);
**只有客户还了价的那几条才另外叫人拍板**(一张决策卡)。每条都叫人,人就成了橡皮图章;
一条都不叫,授权红线就是摆设。最后 BD 放行签字。
**去向是算出来的,不是你选的**:有条目判了退回重批 → 退回定价重批;有成交 → 转订单履约;
全都没成 → 转情报归档。三口各带谓词,放行那一刻基座按序求值,你只要把定案落准。
【这一单只有一个岔路口,它长这样 —— 无论哪一拍都照这个发,别自己另编一张】
客户还了价要人拍板,那张决策卡**三处写死**:
· \`"itemId":"T_haggle"\` —— 英文计划项号。**别写中文名「异议拍板」**:中文名不是 id,
写了当场被拒(【目标缺口】那一段摆的就是中文名,别照它抄);
· \`"ask":"<N> 条客户还了价,按哪条路办?"\` —— 只把 N 换成条数,**别的字一个都不改**。
换了问法,系统就认不出这是同一张卡:人已经点过的那张会被你再发一遍;
· 三个 \`value\`:\`accept-encounter\` 不对,是 \`accept-counter\`(照客户还的价成交)、
\`revise\`(退回成本定价重批)、\`hold\`(守住现价不让)。label 与 consequence 可以按
本单的数改写,**value 不行**。
**REVISE(退回重批)这个码只有拍板那一步写得出来** —— 定案那一步遇到客户还价一律落
HAGGLE,替他写了 REVISE,那张卡就永远不会发,让步梯度成了摆设。
拍板之前**别急着发卡也别绕路** —— 定案那一步先把三档写完、finish;
下一拍引擎会把拍板那一步的做法摊开给你,那时再发。
【一路上要说话】负责人正看着这一单。**每一拍都带一句 say** —— 说业务的话:
报价函排了几条、有效期到哪天、客户回了什么、哪几条要拍板;不说系统词。
到了要找人的那一拍,先说清这一轮办完了什么、为什么现在需要他。
【怎么把一项干完 · 一拍连做】write 和 tool 都不会自动收步 —— 做完用 finish 宣告。
动作排进同一拍的 actions 串一口气给;只有发卡 / 提交(挂起)必须停拍。
**这一单常路 6-8 拍就该收口**。
§Z 结案:BD 在验收单上签字,系统把草稿合并成正式报价函并按定案发出对应那一口的载荷 ——
成交转订单履约,有异议退回成本定价,不成交转情报归档。`;
/**
* 步骤剧本 —— 一个计划项一条;`doneWhen` 是「成果已存在」的判据,不是「该不该做」。
*
* 三处不给 `doneWhen`:`S2_send`(发卡触达真人)、`T_haggle`(要人拍板)、
* `T_release`(签下去触达世界)—— 给了等于给引擎一把替人签字的钥匙。
*/
export const NEGOTIATE_STEPS = [
{
item: 'S1_compose',
intent: '整份归一成正式报价函:逐条抄齐条款、补 SPQ/MOQ/贸易条款、按 P-04 注明有效期',
writes: ['quote_negotiate.line', 'quote_negotiate.verdict'],
/* 上游载荷数组不声明就会被整表段的截断刀砍掉(砍掉的恰是要归一的行) */
reads: ['items'],
view: 'negotiate-worksheet',
tools: [],
/*
* 归一 + 排版走完才算办完:行数两边对上(一边是基座现数),
* 且 composed_total 写过(S-FEEL 未绑定的名字参与比较为假,所以「没写过」进不来)。
*/
doneWhen: 'line_total > 0 and quote_negotiate_line_rows = line_total and composed_total >= 0',
guide: `第一件事:把这一单的**正式报价函**落出来 —— 一拍连做,别拆成两拍。
【条目从哪儿来】
· 流内转来(\`items\` 里的行带 quoted_price / disposition):照抄 —— **报出去的价就是
上游批准的那个价**,一个数字都别改;带 quote_line_total 这类总数的是结论行,不是料,
别落进报价函。
· 单飞:照正文归一,列了几颗落几行;正文里说的批准价就是报出去的价。
**按 pn 归并,一颗料一行**;pn 一个字符不许改(它是主键,改一个字就是新开一行)。
【写 \`quote_negotiate.line\`,一颗料一行】(rows 收数组)
rfq_ref(入参有就原样抄;没有从主题认;认不出才自己造一个,**造出来的必须以 \`NEG-\` 开头**
(\`NEG-<主题或第一颗料的型号>\`)。两条硬规矩:
**① 整单只有一个单号,不是一颗料一个** —— 它是这一单的案号,报价函所有行、verdict 那一行
用的都得是同一个串(实测栽过:两颗料造出两个号,一单裂成两单);
**② 只从本单来件取材** —— 台账里躺着的 RFQ-xxxx 是别人的单,借它的号等于把两单的行
混进同一个案卷(实测栽过一轮))、pn、customer_pn、category、brand、eau、lead_time、
remark(客户约束/急料,一个字别丢)、type_tag(照上游现标;认不出按 B)、
**quoted_price**(上游批准的报价,原样抄)。
条款三列**照这个口径补齐**(它们是报价函的必备条款,不是可选项):
· spq —— 最小包装量:上游给了就抄;没给按 eau 的量级给一个常规值(千位以下 100,
千到万 1000,万以上 5000)并在 remark 里注明是按常规包装估的;
· moq —— 最小起订量:同上,没给就取 spq;
· trade_terms —— 贸易条款:统一写 \`EXW 深圳\`,客户来件另有约定的照客户的写。
· valid_until —— 有效期到期日:按**报价有效期 P-04:7 个日历天**从今天算,写成
\`YYYY-MM-DD\`。整单同一个日子,不是一行一个。
【**这三列你一格都不写**:reply_type / counter_price / reply_note】
客户表态、客户还的价、客户原话 —— 这三样只有收信的人知道,**系统不替客户说话**。
它们是 BD 在工作单上落的;deal_status / concession / final_price 三列要等表态回来
才判得了,这一步也别写。
这不是客气话:一条要你分辨「这行能不能替客户填」的规矩会走岔;
**「这几列不写」没有分支可走岔。**
【同一拍写 \`quote_negotiate.verdict\`(一行)】
rfq_ref、**quote_no**(报价单号:\`Q-\` 开头 + 本单案号的尾段,整单一个)、
customer_code / customer_name(顶层有就抄)、**line_total** = 报价函行数、
**valid_days** = 7、**composed_total** = 这一拍排版落表的条数(0 也要写)、
summary 一句人话(几条料、报给谁、有效期到哪天)。
写完 finish 这一项。`,
},
{
item: 'S2_send',
intent: '一张执行单交 BD:把报价函发给客户,回音回来后逐条落客户表态',
writes: ['quote_negotiate.verdict'],
reads: ['quote_negotiate.line'],
view: 'negotiate-worksheet',
tools: [],
/* 不给 doneWhen —— 发卡触达真人(manualActivation),引擎不替人按 */
guide: `报价函排好了,**该 BD 出场了** —— 这是本活动唯一的一张执行单。
触达客户的那一下由人做:他把报价函发出去、按 D+3/D+7 催问、拿回客户的话。
工作区摆的是**议价工作单**:逐颗料一张报价档案卡(条款、报价、客户约束),
让步梯度与议价纪律挂在上面,填回区长在同一屏 —— 他在「客户表态」那一列逐行落。
这一步标了人工激活:发卡就算启动,不必单发 start;引擎也不会替你收 ——
人交回后要你自己 finish。
【照这个骨架发,别少字段】(卡面是纯文本,steps/standard 里别写星号记号)
{"kind":"human","itemId":"S2_send","workOrder":"execution","card":{
"title":"报价函发出并跟进:<单号> 共 <N> 条待回音",
"ask":"报价函已按批准价排好版,条款与有效期都在工作单上。请把它发给客户,按 D+3/D+7 的节奏催问;客户回话之后,在工作单的「客户表态」一列逐行落:接受 / 还价 / 拒绝 / 无回复,还价的把客户报的那个价也填上。",
"steps":["发出去之前先核一遍条款:单价、交期、SPQ、MOQ、贸易条款、有效期 —— 报价函一旦发出,这几项就是对客户的承诺",
"客户表态每一行都要有 —— 缺一行这张单按不下提交;客户只回了一部分料的,没回的那几行按无回复落,别替客户猜",
"客户还价的行:把他报的价填进「客户还价」那一格,再把他的原话记进备注 —— 争的是价格、交期还是 MOQ,后面拍板全看这一句",
"D+3 第一轮催问确认收到探态度,D+7 最后一催;催到有效期过仍无回复的,按无回复定案,不要一直挂着"],
"standard":"整份报价函每一条都有客户表态,还价的那几条填了客户报的价并记下他的原话 —— 缺一条这张单按不下提交(系统的闸看着)。",
"basis":[{"k":"<PN>","v":"<报价 · 交期 · 有效期到 YYYY-MM-DD>"}]}}
· \`basis\` 逐条列本单的料:PN + 报价与有效期。几颗写几条,别铺全表。
· **卡上不摆表、不摆格子**(执行卡零输入):整份报价函就在工作单那份件上,
客户表态、客户还价、备注三列都能就地改、行能撤 —— **别往卡里塞 \`blocks\` 或 \`fields\`**。
· **这张卡带提交闸**:客户表态(必填列)还空着,他按不下提交 —— 缺几格、缺在哪行,
闸上与件上的红带都写着(与放行门 L1 同一个数同一套算法)。
发完卡这一拍就结束了(发卡挂起)。
【人交回之后 —— 他落的表态基座已经替你写回台账了】这一步你只做两件事,然后 finish:
① 写 verdict:**replied_total** = 已落表态的条数(照报价函行数记);
他在件上撤掉了行的话,**line_total 跟着改成撤后的实数**。
② finish 这一项。
**别在这一拍判定案** —— 逐条定案是下一步的活(它有自己的一段做法),这里判了也白判。
报价函行你一列都不用重写:他落的值基座已经落进台账了,重写会被「写了等于没写」拒回。
【这张卡一单只发一次】人交回来之后不许再发同一张卡 —— 必填有提交闸兜着,
卡回来时客户表态一定是齐的(或那几行已被他撤掉)。`,
},
{
item: 'S3_settle',
intent: '逐条定案:按客户表态判成交/待拍板/不成交,还价的按让步梯度给处置建议',
writes: ['quote_negotiate.line', 'quote_negotiate.verdict'],
reads: ['quote_negotiate.line'],
view: 'negotiate-worksheet',
tools: [],
/*
* **收步判据与验收判据同源**:四档定案的条数加起来等于行数(两边都是基座现数)——
* 判完了才算办完。`_rows > 0` 那一半不能省:开案时行数是 0,而 0+0+0+0 = 0
* 会让这一步开局即收(未绑定名字的坑之外,还有「全零相等」这一坑)。
*/
doneWhen:
'quote_negotiate_line_rows > 0 and quote_negotiate_line_deal_status_haggle = quote_negotiate_line_reply_type_counter and quote_negotiate_line_deal_status_won + quote_negotiate_line_deal_status_haggle + quote_negotiate_line_deal_status_revise + quote_negotiate_line_deal_status_lost = quote_negotiate_line_rows',
guide: `客户的话都落回来了,**现在才轮到定案** —— 这一步是纯查表,一拍连做:
① **逐行按客户表态 reply_type 判 deal_status**,按议价结果 N-01,
只许写这四个大写码之一,一个字母不许差:
· ACCEPT(客户接受)→ **WON**,同一行 final_price 记 quoted_price(成交价就是报价);
· COUNTER(客户还价)→ **HAGGLE**,final_price **留空**(还没谈定,别替客户成交);
· REJECT(客户拒绝)→ **LOST**,final_price 留空;
· NOREPLY(催到有效期过仍无回复)→ **LOST**,final_price 留空。
**第四个码 REVISE「退回重批」这一步一格都不写** —— 它是人在决策卡上拍出来的,
不是你判出来的。你替他写了,那张卡就永远不会发,让步梯度成了摆设(实测栽过一轮:
一单从头到尾 released 而没有人拍过板)。客户还了价 = 落 **HAGGLE**,句号。
**两个数要对上,都是基座现数,你改不动它们**:
· 待拍板条数 quote_negotiate_line_deal_status_haggle
**必须等于**客户还价条数 quote_negotiate_line_reply_type_counter ——
对不上这一步收不掉(把写错的那行改回 HAGGLE 就对上了);
· 四档定案的条数加起来必须等于 quote_negotiate_line_rows(基座数的行数),一行都不能漏。
现在四档都是 0,就是一行都还没判 —— 这一步没办完,放行门开不了。
② **还价的那几行(且只有那几行)再算一格 concession**:让步梯度 N-02 看还价幅度
gap =(quoted_price − counter_price)/ quoted_price,只许写这三个大写码之一:
· gap ≤ 0.03(三个点以内)→ **ACCEPT_COUNTER**(意思意思的压价,接了拿单);
· 0.03 < gap ≤ 0.10(三到十个点)→ **NEGOTIATE**(在授权内回旋一轮,让一半再报回去);
· gap > 0.10(超过一成)→ **ESCALATE**(超出授权,退回成本定价重批,不私自破线)。
不是还价的行 concession 给空串 \`""\`,**不许写 0 也不许编一个码** ——
没还价就没有让步幅度这回事。
③ 看这一拍写完之后的**待拍板条数**(deal_status = HAGGLE 的行):
· 一条都没有 → 同一拍把 verdict 写全:**outcome**(全按 N-01 的结果算:
有 WON 就写 \`won\`,一条 WON 都没有就写 \`lost\`)、**route_decided_total 记 1**、
**settled_total** = 定案条数、reason(不成交时写清缘由:客户拒绝还是超时无回复)、
summary 补一句人话,然后 finish 这一项;
· 还有 → **别写 outcome 也别写 route_decided_total**,只写 settled_total 再 finish ——
下一拍会有一步专门叫人拍板。
【finish 之后这一拍到此为止】**别在这一拍顺手发决策卡** —— 那张卡属于下一步
(itemId 要写 T_haggle),这一步收掉之后再往它上面挂任何行动都会被拒
(实测:同一串里 write / write / finish / decide,最后那个 decide 挂着 S3_settle,当场被拒)。
下一拍引擎会把「异议拍板」那一步的做法摊开给你,那时再发。`,
},
{
item: 'T_haggle',
intent: '客户还的价接不接要人定 —— 发一张决策卡,照他选的那条路把定案改掉',
writes: ['quote_negotiate.line', 'quote_negotiate.verdict'],
reads: ['quote_negotiate.line'],
view: 'negotiate-roster',
tools: [],
/* 人工激活 + 要人拍板 —— 不给 doneWhen,引擎不许替人收 */
guide: `**这一单只发一张决策卡。动手之前先在轨迹里找一行 \`[card-reply]\` 带「人选了:」:**
· **找到了** —— 跳到下面【人点完之后】那一段,照他选的那条路把那几行的 deal_status
从 HAGGLE 改掉、写 outcome 与 route_decided_total、finish。**一个 decide 都不许再发**
(实测栽过:人点完了,而这一步又从头念了一遍「发一张决策卡」,同一张卡连发六次,
前两次无事发生、后四次被拒 —— 他点过的那一下就白点了);
· **没找到** —— 才轮到下面【发一张决策卡】那一段。
【这一步办完了没有,看一个数】待拍板条数 quote_negotiate_line_deal_status_haggle:
**它归零就是这一步办完了**。HAGGLE 是过渡态(客户还了价、还没拍板),
拍板那一下必须把它换成 WON / REVISE / LOST 之一 —— 三条路都是结论。
这个数不归零,放行门开不了;它归零,这一步也就不会再找你。
这一步只在**客户还过价**(基座现数 quote_negotiate_line_reply_type_counter > 0)时入场 ——
读的是客户自己填的那一格,不是你判的定案:该不该叫人,不由被叫的那一方说了算。
它触达公司利益,标了人工激活:发卡就算启动,不必单发 start。
工作区这时摆的是**成交定案清单**,整份报价函与定案分布都在上面,他看得见全局再拍板。
【发一张决策卡,照这个骨架】(选项少于两个基座拒发)
**三处一个字都不许改**(与说明书里那一段是同一份):
· \`itemId\` 只写英文计划项号 \`T_haggle\` —— **别写中文名「异议拍板」**,
中文名不是 id,写了当场被拒(实测栽过两拍:「计划项不存在:异议拍板」);
【目标缺口】那一段摆的是中文名,它不是 id,别照它抄。
· \`ask\` 照抄「<N> 条客户还了价,按哪条路办?」,**只把 N 换成条数** ——
换了问法系统就认不出这是同一张卡,人已经点过的那张会被你再发一遍(实测发出去两张)。
· 三个 \`value\` 照抄 \`accept-counter\` / \`revise\` / \`hold\` —— 它们是这一单三条路的
代号,换了名字下一段的对应关系就对不上,你自己也不知道人点的是哪条。
label 与 consequence 可以按本单的数改写,value 不行。
{"kind":"decide","itemId":"T_haggle","ask":"<N> 条客户还了价,按哪条路办?",
"options":[
{"value":"accept-counter","label":"照客户还的价成交","consequence":"当场拿下这几条,毛利让到客户的价位上,让出去的部分不再回来"},
{"value":"revise","label":"退回成本定价重批","consequence":"这几条超出让价授权,回 A4 重新算价再谈 —— 这一单本轮不成交,客户要多等一轮"},
{"value":"hold","label":"守住现价不让","consequence":"报价不动,客户不接受即视为不成交,这几条转情报归档"}],
"basis":[{"k":"<PN>","v":"报价 <X> · 客户还价 <Y> · 让幅 <Z>% · 让步梯度判 <码>"}]}
· basis 逐条摆还价的那几颗料:报价、客户还价、让幅、N-02 的处置码 —— 裸卡基座拒发。
· **recommended 按让步梯度 N-02 标**,标在与处置码对应的那一项上,只标一项:
ACCEPT_COUNTER → accept-counter;NEGOTIATE → accept-counter;ESCALATE → revise。
几条料的码不一致时,按**最靠后那一档**标(有一条要 ESCALATE 就标 revise)。
· 发完这一拍就结束了(发卡挂起)。
【人点完之后】轨迹上那一行长这样:\`[card-reply] …人选了:xxx\`。**照那条路把定案改掉,
别再发第二张卡**,一拍连做 —— 三条路**都要动 deal_status**,一条都不许留在 HAGGLE:
· 选 accept-counter:那几行 deal_status 改 **WON**、**final_price 记该行的
counter_price**(那个数不是你定的,是客户报的、人点头认的,你只是替他落账)、
reply_note 补一句「按客户还价成交,由负责人拍板」;verdict 的 outcome 写 \`won\`;
· 选 revise:那几行 deal_status 改 **REVISE**、final_price 留空,
reply_note 补一句「让幅超出授权,退回成本定价重批」;verdict 的 outcome 写 \`revise\`、
reason 写清超了多少;
· 选 hold:那几行 deal_status 改 **LOST**、final_price 留空,
reply_note 补一句「守价不让,客户未接受」;verdict 的 outcome 写 \`lost\`、
reason 写「客户还价未达成,守价不让」。
然后把 verdict 写全:**route_decided_total 记 1**、settled_total = 定案条数、
summary 补一句拍板结果,finish 这一项。
**outcome 那一格照上面三条写死的对应关系填,别自己另造词** —— 下游三条边读的就是它。
改完之后待拍板条数就是 0 了,这一步再也不会找你。`,
},
{
item: 'T_release',
intent: '定案已落,提交放行交 BD 验收',
writes: [],
reads: [],
view: 'negotiate-roster',
tools: [],
/* 放行触达世界,必须人按一下 —— 引擎不自动收它(剧本里也不给 doneWhen) */
guide: `每条都有客户表态、定案守恒、待拍板清零、定案已记 —— 可以走放行了
(T_release 人工激活:动它就算启动,不必单发 start)。先把裁量判据裁掉,然后 submit,同一拍连做:
· judge criterion="议价过程说得清:报价函条款齐备、每一轮客户反馈与让价理由逐条可溯,BD 对这个结果负责" ——
看报价函里每一行的条款、客户原话与让价理由:说得出来路就 satisfied=true;
有行说不出的(比如还价成交却没记客户的话),satisfied=**false**,rationale 逐行写清,
交签字的人定。**裁成不满足照样能提交** —— 理由连着卡一起交给他。
criterion 照判据名**整句原样抄**,写别的名字对不上。
**去向不必你选**:三口各带谓词,放行那一刻基座按序求值 ——
有条目判了退回重批就走「退回调整」,有成交就走「转订单履约」,全都没成就走「转情报归档」。
你要做的只是把定案落准,别在提交里手选出口。
终局唯放行:你不能自己宣布完成。`,
},
];
/**
* 阶段 —— 进度条的刻度,只给人看。除放行外每个计划项恰属一个阶段;
* 放行那一格由基座统一加,人看到的是 x/5。
*/
export const NEGOTIATE_PHASES = [
{ name: '排版报价函', items: ['S1_compose'] },
{ name: '发出并等回音', items: ['S2_send'] },
{ name: '逐条定案', items: ['S3_settle'] },
{ name: '异议拍板', items: ['T_haggle'] },
];