/** * 统一报价 A4 · 成本定价 —— 活动包代码入口(ADR-004)。 * * flow 活动:下一步由步序声明给定。本文件只做声明装不下的两件事 —— * 视图流控,与「结论不可静默覆写」的呈现:P-02 拦下的条目必须显式呈现给人。 */ import { defineActivity } from '@sdk'; import type { ActivityBase, ActivityOutcome } from '@sdk'; const DRAFT = 'quote_pricing.draft'; export default defineActivity({ name: 'quote-pricing', paradigm: 'flow', async run(base: ActivityBase): Promise { const items = await base.ledger.read(DRAFT); const priced = items.filter((r) => typeof r['quoted_price'] === 'number'); // 底价门禁 P-02 拦下的条目 —— DMN 结论是**约束性输入**,不得静默覆写 const blocked = items.filter((r) => String(r['disposition'] ?? '').includes('拦截')); await base.interaction.pushView([ { t: 'kv', title: '定价概览', items: [ { k: '条目', v: `${items.length} 条` }, { k: '已定价', v: `${priced.length} 条` }, { k: '底价门禁拦下', v: `${blocked.length} 条` }, ], }, { t: 'table', title: '报价草案', cols: ['PN', '拟报单价', '毛利率', '偏离度', '门禁处置'], rows: items.map((r) => [ String(r['pn'] ?? ''), fmt(r['quoted_price']), pct(r['margin']), pct(r['deviation']), String(r['disposition'] ?? '待判'), ]), linkDonut: { col: 4, title: '门禁分布', map: [ { match: '直接放行', label: '直接放行', tone: 'ok' }, { match: 'PM', label: 'PM 附理由', tone: 'warn' }, { match: '拦截', label: '拦截', tone: 'danger' }, ], }, }, ]); if (priced.length < items.length) { await base.interaction.say(`还有 ${items.length - priced.length} 条未定价,继续按利润模型 P-01 求值。`, 'conclusion'); return { kind: 'await', reason: '等待定价步完成' }; } if (blocked.length > 0) { // 超出授权边界 → 走「人 · 决策」,不自行破线 await base.interaction.say( `${blocked.length} 条低于底价 20% 以上,已被底价门禁 P-02 拦下 —— 需要人裁决回调还是注明理由。`, 'criterion', ); return { kind: 'await', reason: '等待超限条目的人裁决' }; } await base.interaction.say(`${items.length} 条已全部定价且过底价门禁,可提交审批。`, 'criterion'); return { kind: 'submit', deliverable: 'quote-draft', rationale: '全条目带偏离度,底价门禁全过' }; }, }); function fmt(value: unknown): string { return typeof value === 'number' ? value.toFixed(2) : '—'; } function pct(value: unknown): string { return typeof value === 'number' ? `${(value * 100).toFixed(1)}%` : '—'; }