Archived
基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
+56
-140
@@ -211,7 +211,7 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
const pkg = processPackageSchema.parse({
|
||||
id: 'unified-quote',
|
||||
name: '统一报价',
|
||||
version: 1,
|
||||
version: 2,
|
||||
state: 'draft',
|
||||
callInterface: {
|
||||
input: {
|
||||
@@ -221,44 +221,15 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
},
|
||||
output: { type: 'object', properties: { landingRate: { type: 'number' } } },
|
||||
},
|
||||
/*
|
||||
* **当前只展开第一个活动**(v2)。
|
||||
* 后续活动一个一个加回来:每加一个,补它的节点、边映射与出口口载荷,
|
||||
* 版本随之上抬 —— 在途的旧单仍跑各自起单时那一版,不受影响。
|
||||
*/
|
||||
nodes: [
|
||||
{ id: 'start', kind: 'start', label: '客户RFQ询价', layout: { row: 0, col: 0 } },
|
||||
activityNode('A1', 'A1 RFQ需求分析', 'quote-intake', 'BD', 'terminate', 1),
|
||||
// 已裁定 ②:G2 料号分流 = **并行网关**,三道并行、每边载荷按 Type 过滤
|
||||
{ id: 'G2', kind: 'gateway', label: 'G2 料号分流', gateway: 'parallel', mode: 'fork', layout: { row: 0, col: 2 } },
|
||||
// 某类条目为空的分支按「容忍」处置 —— A4 汇合处的必填映射不得依赖可空分支
|
||||
activityNode('A3', 'A3 渠道寻源', 'quote-sourcing', 'PRM', 'tolerate', 3, -1),
|
||||
activityNode('A2', 'A2 替代选型', 'quote-alternative', 'FAE', 'tolerate', 3, 1),
|
||||
{ id: 'G2J', kind: 'gateway', label: 'G2 汇合', gateway: 'parallel', mode: 'join', layout: { row: 0, col: 4 } },
|
||||
activityNode('A4', 'A4 成本定价', 'quote-pricing', 'BD', 'terminate', 5),
|
||||
// 已裁定 ①:A5 定价审批 = **审批点**;G3 审批结果并入驳回走向(驳回 → 回 A4 重开新实例)
|
||||
{
|
||||
id: 'AP',
|
||||
kind: 'approval',
|
||||
label: 'AP 定价审批',
|
||||
approver: { position: 'PM', strategy: 'sole' },
|
||||
// 驳回走向的载荷映射不能空:重开的是**新的 A4 实例**,它照样要过 A4 的入参契约。
|
||||
// 空映射 = 重开必失败,等于把「驳回回 A4」写成了一句空话。
|
||||
reject: {
|
||||
kind: 'reopen',
|
||||
nodeId: 'A4',
|
||||
mapping: [
|
||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
||||
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||
{ target: 'materialType', source: { from: 'output-port', port: 'intake', path: 'type_tag' } },
|
||||
{ target: 'customerGrade', source: { from: 'constant', value: '普通客户' } },
|
||||
{ target: 'volumeTier', source: { from: 'constant', value: '大批量' } },
|
||||
],
|
||||
},
|
||||
nudge: { deadline: 'PT30M', steps: [{ after: 'PT20M', action: 'nudge' }] },
|
||||
layout: { row: 0, col: 6 },
|
||||
},
|
||||
activityNode('A6', 'A6 报价议价', 'quote-negotiate', 'BD', 'terminate', 7),
|
||||
// G4 议价结果 = 异或网关:成交 / 有异议(回边 A4)/ 拒绝超时
|
||||
{ id: 'G4', kind: 'gateway', label: 'G4 议价结果', gateway: 'exclusive', mode: 'fork', layout: { row: 0, col: 8 } },
|
||||
activityNode('A7', 'A7 情报归档', 'quote-archive', 'BD', 'terminate', 9),
|
||||
{ id: 'end', kind: 'end', label: '归档闭环', layout: { row: 0, col: 10 } },
|
||||
activityNode('A1', '客户RFQ受理', 'quote-intake', 'BD', 'terminate', 1),
|
||||
{ id: 'end', kind: 'end', label: '受理闭环', layout: { row: 0, col: 2 } },
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
@@ -274,107 +245,7 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
],
|
||||
reopen: false,
|
||||
},
|
||||
{ id: 'e-a1-g2', from: 'A1', to: 'G2', mapping: [], label: '统一受理记录', reopen: false },
|
||||
|
||||
// 三道并行:TYPE-A 直通定价 / TYPE-B 寻源 / TYPE-C 选型
|
||||
// TYPE-A 直通:不经活动,直接进汇合 —— A4 因此只有一条并发入边,来源无歧义
|
||||
{ id: 'e-g2-j', from: 'G2', to: 'G2J', label: 'TYPE-A 直通定价', mapping: [], reopen: false },
|
||||
{
|
||||
id: 'e-g2-a3',
|
||||
from: 'G2',
|
||||
to: 'A3',
|
||||
label: 'TYPE-B 转寻源',
|
||||
mapping: [
|
||||
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||
{ target: 'scenario', source: { from: 'constant', value: 'BOM后期' } },
|
||||
],
|
||||
reopen: false,
|
||||
},
|
||||
{
|
||||
id: 'e-g2-a2',
|
||||
from: 'G2',
|
||||
to: 'A2',
|
||||
label: 'TYPE-C 转选型',
|
||||
mapping: [{ target: 'originalPn', source: { from: 'output-port', port: 'intake', path: 'pn' } }],
|
||||
reopen: false,
|
||||
},
|
||||
|
||||
// 汇合:容忍分支的输出**不进 A4 的必填映射**(静态校验会拦)
|
||||
{ id: 'e-a3-j', from: 'A3', to: 'G2J', mapping: [], reopen: false },
|
||||
{ id: 'e-a2-j', from: 'A2', to: 'G2J', mapping: [], reopen: false },
|
||||
{
|
||||
id: 'e-j-a4',
|
||||
from: 'G2J',
|
||||
to: 'A4',
|
||||
mapping: [
|
||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
||||
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
||||
// 逐条办的料号:MVP 单条 RFQ,取受理归一出的那一条
|
||||
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||
// P-01 利润模型的三个因子:物料类型就是 A1 打的标(A/B/C),真实取值;
|
||||
// 客户等级与数量档在 MVP 取默认档 —— 接 CRM 后改为读 shared.customer 与按 EAU 分档,
|
||||
// P-01 决策表本就有兜底行('-' = 任意),默认档不会让求值失真。
|
||||
{ target: 'materialType', source: { from: 'output-port', port: 'intake', path: 'type_tag' } },
|
||||
{ target: 'customerGrade', source: { from: 'constant', value: '普通客户' } },
|
||||
{ target: 'volumeTier', source: { from: 'constant', value: '大批量' } },
|
||||
],
|
||||
reopen: false,
|
||||
},
|
||||
|
||||
{ id: 'e-a4-ap', from: 'A4', to: 'AP', mapping: [], label: '报价草案提审', reopen: false },
|
||||
{
|
||||
id: 'e-ap-a6',
|
||||
from: 'AP',
|
||||
to: 'A6',
|
||||
label: '通过',
|
||||
mapping: [
|
||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||
{ target: 'items', source: { from: 'output-port', port: 'draft', path: 'items' } },
|
||||
],
|
||||
reopen: false,
|
||||
},
|
||||
|
||||
{ id: 'e-a6-g4', from: 'A6', to: 'G4', mapping: [], reopen: false },
|
||||
{ id: 'e-g4-end', from: 'G4', to: 'end', condition: 'outcome = "won"', label: '成交 → 转订单履约', reopen: false },
|
||||
// 已裁定 ④:议价回环 = **流程层回边**,人裁决后重开新任务实例
|
||||
{
|
||||
id: 'e-g4-a4',
|
||||
from: 'G4',
|
||||
to: 'A4',
|
||||
condition: 'outcome = "revise"',
|
||||
label: '有异议 → 返回调整',
|
||||
mapping: [
|
||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
||||
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
||||
// 逐条办的料号:MVP 单条 RFQ,取受理归一出的那一条
|
||||
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||
// P-01 利润模型的三个因子:物料类型就是 A1 打的标(A/B/C),真实取值;
|
||||
// 客户等级与数量档在 MVP 取默认档 —— 接 CRM 后改为读 shared.customer 与按 EAU 分档,
|
||||
// P-01 决策表本就有兜底行('-' = 任意),默认档不会让求值失真。
|
||||
{ target: 'materialType', source: { from: 'output-port', port: 'intake', path: 'type_tag' } },
|
||||
{ target: 'customerGrade', source: { from: 'constant', value: '普通客户' } },
|
||||
{ target: 'volumeTier', source: { from: 'constant', value: '大批量' } },
|
||||
],
|
||||
reopen: true,
|
||||
},
|
||||
{
|
||||
id: 'e-g4-a7',
|
||||
from: 'G4',
|
||||
to: 'A7',
|
||||
condition: 'outcome = "lost"',
|
||||
label: '拒绝/超时 → 情报归档',
|
||||
mapping: [
|
||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||
{ target: 'quoteNo', source: { from: 'initiation', path: 'rfqRef' } },
|
||||
// 归档写的是 (pn, quoted_at) 这一行 —— 两个主键都得传到,它自己造不出来
|
||||
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||
{ target: 'quotedAt', source: { from: 'initiation', path: 'quotedAt' } },
|
||||
],
|
||||
reopen: false,
|
||||
},
|
||||
{ id: 'e-a7-end', from: 'A7', to: 'end', mapping: [], reopen: false },
|
||||
{ id: 'e-a1-end', from: 'A1', to: 'end', mapping: [], label: '统一受理记录', reopen: false },
|
||||
],
|
||||
variables: [],
|
||||
});
|
||||
@@ -385,7 +256,7 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
log(' 闭合校验未过,不得发布:' + report.violations.map((v) => v.message).join('; '));
|
||||
} else {
|
||||
await container.process.publish(pkg.id, pkg.version);
|
||||
log('④ 流程包「统一报价」v1 已发布 —— 7 节点 + 3 网关 + 1 审批点,边映射闭合校验通过');
|
||||
log(`④ 流程包「统一报价」v${pkg.version} 已发布 —— ${pkg.nodes.length} 节点,边映射闭合校验通过`);
|
||||
}
|
||||
|
||||
/* 事件触发器「客户RFQ询价」 */
|
||||
@@ -409,7 +280,13 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
*/
|
||||
{ target: 'quotedAt', source: { from: 'expression', sfeel: 'occurredAt' } },
|
||||
],
|
||||
spec: { kind: 'event', rule: 'contains(subject, "询价") or contains(subject, "RFQ")', eventType: 'data' },
|
||||
// 按**通道**收口:只有投进「公司询价邮箱」的才自动起单;
|
||||
// 同一封信投到「询价待确认邮箱」不命中,落人工确认那条路
|
||||
spec: {
|
||||
kind: 'event',
|
||||
rule: 'channelId = "ch-rfq-mail" and (contains(subject, "询价") or contains(subject, "RFQ"))',
|
||||
eventType: 'data',
|
||||
},
|
||||
});
|
||||
await container.trigger.upsert(trigger);
|
||||
log(' 事件触发器「客户RFQ询价」已配置');
|
||||
@@ -433,4 +310,43 @@ export async function seedQuote(container: Container): Promise<void> {
|
||||
addressWindowMs: 7 * 86_400_000,
|
||||
});
|
||||
log(' 邮件通道实例「公司询价邮箱」已配置(默认认领岗位:OP)');
|
||||
|
||||
/*
|
||||
* ⑥ 人工确认通道 + 登记触发器 —— 三类触发里的**登记触发**(OS 3.1)。
|
||||
*
|
||||
* 同一封 RFQ,两条起单路径:
|
||||
* · 走「公司询价邮箱」→ 事件触发器命中 → **自动起单**;
|
||||
* · 走「询价待确认邮箱」→ 无触发器命中 → 按「宁交人,不错投」落**消息兜底**到
|
||||
* 默认认领岗位(PM)→ 由人确认后**登记起单**,「登记人即来源」。
|
||||
* 后者是给需要人过一道目的场景用的:同样的信,起不起单由人定。
|
||||
*/
|
||||
const pm = positions.find((p) => p.code === 'PM');
|
||||
await container.channel.upsert({
|
||||
id: 'ch-rfq-review',
|
||||
name: '询价待确认邮箱',
|
||||
kind: 'mail',
|
||||
address: 'rfq-review@we.com',
|
||||
enabled: true,
|
||||
// 归属键规则与正式通道一致:识别照做,只是不自动起单
|
||||
extract: [
|
||||
{ key: 'customerCode', regex: '\\bC\\d{3}\\b', source: 'both' },
|
||||
{ key: 'rfqRef', regex: 'RFQ-[A-Z0-9]+', source: 'both' },
|
||||
],
|
||||
mapping: [],
|
||||
...(pm === undefined ? {} : { fallbackPosition: pm.id }),
|
||||
addressWindowMs: 7 * 86_400_000,
|
||||
});
|
||||
|
||||
await container.trigger.upsert(
|
||||
triggerConfigSchema.parse({
|
||||
id: 'trg-rfq-manual',
|
||||
name: '人工登记起单(统一报价)',
|
||||
enabled: true,
|
||||
processPackageId: 'unified-quote',
|
||||
// 登记触发以**登记表单**为输入,不做字段映射:表单由调用接口 schema 自动渲染
|
||||
mapping: [],
|
||||
spec: { kind: 'registration' },
|
||||
}),
|
||||
);
|
||||
log(' 人工确认通道「询价待确认邮箱」+ 登记触发器已配置(默认认领岗位:PM)');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user