# 采购主链 RPA 一期设计文档(技术设计 / HLD+LLD) > 用途:把《采购主链RPA一期需求文档_三方会审版》(业务规则)和《采购主链RPA一期开发规划》(排期与阶段)落成**可建库、可对接口、可分工**的技术设计底稿。 > 关系说明:本文取代早期《一期开发任务清单_表结构_API草案.md》的**架构与数据模型部分**——该草案工程框架扎实,但建立在"合同文件入池→OCR→映射"的错误骨架上,缺**订单驱动主线、产品对码、三来源分流**。本文保留其状态机/幂等/证据/待办思想,按订单驱动重排实体与节点。 > 术语:状态中心 = 我们自研的编排与审计中台;执行器 = RPA 平台(影刀/来也等);对码 = 电商药品匹配到 M5 货品ID/编码。 --- ## 一、设计目标与非目标 ### 1.1 设计目标 1. **订单驱动**:以电商订单(医药城/小药药)导出为入口,线下手工单为 OCR 分支,贯穿到 M5 录入待保存。 2. **分层解耦**:业务规则、状态机、审计、对码口径掌握在状态中心;RPA 平台仅做页面执行,可替换。 3. **对码可迭代**:对码表版本化、累积迭代,未命中一对多转人工,命中率可统计。 4. **不可逆保护**:一期末端"保存/签订/生成收货通知单"由人工执行,自动化做成配置开关。 5. **可追溯**:订单号→合同号→返利总单号→M5单号→收货通知单号全链路可查,每节点留证。 ### 1.2 非目标(一期不做) - 招财平台接入。 - M5 自动保存/签订(阶段三才放开,本期只留开关)。 - 加单/拆单的全自动重做(父订单号先记录,处理转人工,待确认)。 - 返利中心重复订单的全自动重建(规则未讲完,先转人工)。 --- ## 二、系统架构 ### 2.1 分层架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 人工协同层 │ │ 企业微信应用消息/群机器人 + H5待办页(Vue/React) │ │ 待办推送 / 一对多对码选择 / 人工检查入口 / 修改留痕 │ └───────────────▲─────────────────────────────▲───────────────┘ │ 待办/回执 │ OAuth身份 ┌───────────────┴─────────────────────────────┴───────────────┐ │ 状态中心(自研,核心资产) │ │ ┌──────────┬──────────┬──────────┬──────────┬────────────┐ │ │ │ 建单入池 │ 状态机 │ 对码引擎 │ 待办路由 │ 审计/证据 │ │ │ │ 幂等去重 │ 编排调度 │ mapping │ 角色分派 │ 事件流 │ │ │ └──────────┴──────────┴──────────┴──────────┴────────────┘ │ │ 统一节点契约 / 回调接口 / 执行平台路由 / 查询审计 API │ └──────▲───────────────▲──────────────────▲───────────────────┘ │ HTTP回调 │ OCR调用 │ 平台路由 ┌──────┴──────┐ ┌──────┴──────┐ ┌───────┴─────────────────┐ │ RPA执行器 │ │ OCR服务 │ │ 对象存储(MinIO/NAS) │ │ 影刀/来也 │ │ PaddleOCR │ │ 合同PDF/截图/OCR JSON │ │ 登录/导出/ │ │ (本地优先) │ │ 证据目录 │ │ 建单/导入/ │ └─────────────┘ └─────────────────────────┘ │ 截图/上传 │ └──────▲──────┘ │ 操作 ┌──────┴───────────────────────────────────────────────────────┐ │ 外部业务系统(RPA 通过 UI 操作,非 API 集成) │ │ 医药城 │ 小药药 │ 合同系统 contract.gzmpc.com │ 返利中心 │ │ │ │ │ flgzmpc.com │ │ M5(INCA) 采购订单管理 1055 │ └───────────────────────────────────────────────────────────────┘ ``` ### 2.2 组件职责边界 | 组件 | 职责 | 明确不做 | | --- | --- | --- | | **状态中心** | job 生命周期、状态机流转、对码、幂等、待办路由、证据审计、平台路由、统一查询 | 不直接操作外部系统页面 | | **RPA 执行器** | 登录/导出Excel/建合同/PDF合并上传/返利导入/M5录入/截图/文件读写/HTTP回调 | 不做流程主状态判断、不做业务规则决策 | | **OCR 服务** | 线下手工单图片→结构化字段 JSON + 置信度 | 仅线下单,线上走 Excel 不经 OCR | | **企业微信/H5** | 待办通知、一对多选择、人工确认、修改留痕、身份识别 | 不直接写业务结果,须经状态中心标准动作 | | **对象存储** | 合同PDF、截图、OCR JSON、日志等证据落盘 | — | **关键设计原则**:RPA 平台"半节点执行过程中不跨平台接力";支持"按新单切换平台""按节点配置平台";切换前跑统一 POC 用例。 ### 2.3 技术选型(推荐,可依公司栈调整) | 层 | 采用 | 备选 | | --- | --- | --- | | 状态中心 | Python 3 + FastAPI(开发快、与 OCR/对码脚本同栈、Pydantic 校验回调契约方便) | Java + Spring Boot | | 数据库 | MySQL 8(状态中心自有库,只存流程状态+累积别名+证据) | PostgreSQL | | 状态机 | 轻量自实现(NODE_ORDER 顺序驱动 + 7 状态枚举) | — | | 对象存储 | MinIO / 公司现有 NAS | 本地文件系统 | | RPA | 影刀 / 来也UiBot / 艺赛旗 择一 POC | — | | OCR | PaddleOCR 本地部署(无信息安全顾虑) | 阿里/腾讯云OCR | | 前端 | Vue/React + 企业微信 OAuth | — | > 注:状态中心确定采用 **Python 3 + FastAPI + MySQL 8**,骨架代码在 `state_center/`(建库脚本、对码引擎、三回调、整单查询已跑通)。若后续团队要平移到 Java+Spring Boot,数据模型与接口契约保持不变即可。 ### 2.4 部署拓扑 ``` 公司内网(与业务系统同网段,便于 RPA 访问) ├── 状态中心服务(容器/虚机) ── MySQL主 ── 对象存储MinIO ├── OCR服务(本地GPU/CPU) [仅阶段2.2线下单启用] ├── RPA运行机(独立虚机, 保持登录态, 定时/7×24) └── 企业微信应用(内网可信域名 + H5静态站) ``` 安全边界: - RPA 回调接口用 `X-Agent-Token`;内部服务用 `Bearer service-token`;H5 用企业微信身份换 JWT。 - 合同/OCR 证据按角色可见(`visible_roles`),敏感文件脱敏标记 `is_masked`。 - **网络暴露提醒**:状态中心所有写接口须鉴权,回调接口须校验 token + 幂等键,禁止无认证暴露。 --- ## 三、数据模型设计 > 按订单驱动重排。核心实体:`job / job_node / order_item / product_match / user_task / mapping_config / executor_binding_config / job_artifact / job_event`。 > 相比早期草案新增:`order_item`(订单明细行)、`product_match`(对码结果,核心难点落库);`job` 主键由"合同文件"改为"订单批次"。 ### 3.1 实体关系 ``` job (一次流程实例 = 同供应商同日一批订单) ├─1:N─ order_item (订单明细行, 对码前的原始行) ├─1:N─ product_match (对码结果, 命中/未命中/一对多候选) ├─1:N─ job_node (每节点每次尝试) ├─1:N─ user_task (人工待办) ├─1:N─ job_artifact (证据) └─1:N─ job_event (事件流) mapping_config (对码表/供应商对照/承付结算映射, 版本化) executor_binding_config (节点→平台路由) ``` ### 3.2 `job`(流程实例) 一次流程实例 = **同一供应商 + 同一订单日期 + 同一来源** 的一批订单(对应一条合同)。 | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | job_id | varchar(64) unique | 对外流程单号 | | business_key | varchar(160) | 业务唯一键:`来源+供应商+订单日期`(见§5.2) | | parent_job_id | varchar(64) null | 重跑/加单来源 | | source_type | varchar(16) | `YYC`(医药城)/`XYY`(小药药)/`OFFLINE`(线下单) | | order_date | date | 订单日期(=合同签约日期) | | supplier_name | varchar(128) | 供应商/商家 | | order_no_list_json | json | 该批订单号数组(贯穿追溯) | | contract_template | varchar(16) null | `FORMAT`(格式)/`NON_FORMAT`(非格式承兑) | | need_pdf_upload | tinyint | 医药城=1,小药药=0 | | planner_name | varchar(64) null | 计划员 | | current_node_code | varchar(64) | 当前节点 | | status | varchar(24) | 见§4.1 | | mapping_version | varchar(64) null | 绑定的对码表版本 | | executor_platform | varchar(16) null | 默认执行平台 | | contract_system_no | varchar(64) null | 合同系统编号 | | rebate_order_no | varchar(64) null | 返利中心总单号 | | m5_order_no | varchar(64) null | M5 采购订单号 | | receipt_notice_no | varchar(64) null | 收货通知单号 | | current_role | varchar(16) null | 当前责任角色 | | current_assignee | varchar(64) null | 当前责任人 | | auto_save_enabled | tinyint | M5自动保存开关(阶段一=0) | | created_by | varchar(64) | | | created_at / updated_at / finished_at | datetime | | 索引:`uniq_job_business_open(business_key, status)`、`idx_supplier_date(supplier_name, order_date)`、`idx_status`、`idx_created_at`。 ### 3.3 `order_item`(订单明细行,对码前原始数据) | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | job_id | varchar(64) | | | order_no | varchar(64) | 订单号 | | parent_order_no | varchar(64) null | 父订单号(加单/拆单) | | raw_product_name | varchar(255) | 平台品名 | | platform_product_code | varchar(64) null | 平台产品编码 | | spec | varchar(128) null | 规格(对码痛点:15×2 vs 30片) | | manufacturer | varchar(255) null | 厂家(含进口英文) | | approval_no | varchar(64) null | 国药准字/批准文号 | | qty | decimal(14,2) | 数量 | | unit_price | decimal(14,4) null | 单价 | | source_row_no | int | 原始 Excel 行号(回溯) | | created_at | datetime | | 索引:`idx_job(job_id)`、`idx_order_no`。 ### 3.4 `product_match`(对码结果,核心难点落库) | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | job_id | varchar(64) | | | order_item_id | bigint | 对应订单行 | | match_key | varchar(512) | 四要素拼接键:通用名+批准文号+规格+厂家 | | match_status | varchar(16) | `EXACT`(精确命中)/`FUZZY`(模糊)/`MULTI`(一对多)/`NONE`(未命中)/`MANUAL`(人工确认) | | goods_id | varchar(64) null | 命中的 M5 货品ID | | goods_code | varchar(64) null | 货品编码 | | base_unit | varchar(32) null | 基本单位 | | candidates_json | json null | 一对多候选列表(供H5选择) | | source_add_needed | tinyint | 是否需 M5 新增货源 | | confirmed_by | varchar(64) null | 人工确认人 | | confirmed_at | datetime null | | | mapping_version | varchar(64) | 使用的对码表版本 | | created_at | datetime | | 索引:`idx_job(job_id)`、`idx_status(match_status)`。 ### 3.5 `job_node`(节点执行) | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | job_id | varchar(64) | | | node_code | varchar(64) | 见§4.2 | | attempt_no | int | 第几次尝试(不覆盖历史) | | status | varchar(24) | 节点状态 | | executor_platform | varchar(16) null | 实际执行平台 | | executor_run_id | varchar(64) null | 平台侧任务号 | | started_at / ended_at | datetime null | | | duration_ms | bigint null | | | error_code | varchar(64) null | | | error_summary | varchar(500) null | | | role_at_failure | varchar(16) null | | | output_json | json null | 节点输出(业务编号等) | | callback_idempotency_key | varchar(128) null | 回调幂等键 | | created_at | datetime | | 索引:`uniq_job_node_attempt(job_id, node_code, attempt_no)`、`idx_status`。 ### 3.6 `user_task`(人工待办) 沿用早期草案结构,`reason_type` 扩展订单驱动场景。 | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | task_id | varchar(64) unique | | | job_id | varchar(64) | | | node_code | varchar(64) | | | role_code | varchar(16) | `PLANNER`/`PURCHASER`/`QC`/`OPS` | | assignee_user_id | varchar(64) null | | | title | varchar(255) | | | reason_type | varchar(32) | `MATCH_MULTI`/`MATCH_NONE`/`SOURCE_ADD`/`OCR_LOW_CONF`/`REBATE_DUP`/`QC_MISSING`/`CONTRACT_DISABLED`/... | | reason_user_friendly | varchar(500) | 人话说明 | | why_me_text | varchar(255) | 为什么派给你 | | editable_fields_json | json | 白名单字段 | | action_schema_json | json | 允许动作 | | comparison_snapshot_json | json null | 原值/建议/当前对比 | | candidates_json | json null | 一对多对码候选 | | status | varchar(16) | `OPEN`/`SUBMITTED`/`CANCELLED`/`EXPIRED` | | submit_token | varchar(64) | 防重复 | | submitted_action | varchar(24) null | | | submitted_payload_json | json null | | | submitted_by / submitted_at | | | | due_at | datetime null | 超时升级 | | created_at | datetime | | ### 3.7 `mapping_config`(映射/对码,版本化) 统一映射表,`config_type` 区分用途。对码表是其中 `PRODUCT` 类型的累积数据。 | 字段 | 类型 | 说明 | | --- | --- | --- | | id | bigint PK | | | config_type | varchar(24) | `PRODUCT`(对码)/`SUPPLIER`(供应商对照)/`SETTLEMENT`/`PAYMENT_TERM`/`PLANNER_ROUTE` | | config_key | varchar(512) | 对码时=四要素拼接键 | | source_value | varchar(512) | 来源值 | | target_value | varchar(512) | 目标值(对码=货品ID+编码+单位JSON) | | extra_json | json null | 扩展 | | version_no | varchar(64) | 版本号 | | is_active | tinyint | | | effective_from / effective_to | datetime | | | updated_by / updated_at | | | 索引:`idx_type_key(config_type, config_key)`、`idx_active(is_active)`。 ### 3.8 `executor_binding_config` / `job_artifact` / `job_event` 沿用早期草案结构(见《一期开发任务清单_表结构_API草案.md》§4.5/4.6/4.7)。要点: - `executor_binding_config`:`node_code → preferred_platform/fallback_platform + required_capabilities_json + route_policy`。 - `job_artifact`:`artifact_type ∈ {SOURCE_EXCEL, SOURCE_IMAGE, OCR_JSON, CONTRACT_PDF, MERGED_PDF, SCREENSHOT, TEMPLATE, LOG}`;证据目录 `job_id/node_code/attempt_no/`。 - `job_event`:`event_type ∈ {JOB_CREATED, NODE_STARTED, NODE_SUCCESS, NODE_FAILED, TASK_CREATED, TASK_SUBMITTED, MANUAL_HANDOFF, JOB_FINISHED}`;`operator_type ∈ {SYSTEM, ROBOT, USER}`。 ### 3.9 关键 DDL 片段(新增表) ```sql CREATE TABLE job ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_id VARCHAR(64) NOT NULL UNIQUE, business_key VARCHAR(160) NOT NULL, parent_job_id VARCHAR(64) NULL, source_type VARCHAR(16) NOT NULL, -- YYC/XYY/OFFLINE order_date DATE NOT NULL, supplier_name VARCHAR(128) NOT NULL, order_no_list_json JSON NULL, contract_template VARCHAR(16) NULL, -- FORMAT/NON_FORMAT need_pdf_upload TINYINT NOT NULL DEFAULT 0, planner_name VARCHAR(64) NULL, current_node_code VARCHAR(64) NOT NULL, status VARCHAR(24) NOT NULL, mapping_version VARCHAR(64) NULL, executor_platform VARCHAR(16) NULL, contract_system_no VARCHAR(64) NULL, rebate_order_no VARCHAR(64) NULL, m5_order_no VARCHAR(64) NULL, receipt_notice_no VARCHAR(64) NULL, current_role VARCHAR(16) NULL, current_assignee VARCHAR(64) NULL, auto_save_enabled TINYINT NOT NULL DEFAULT 0, created_by VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, finished_at DATETIME NULL, KEY idx_supplier_date (supplier_name, order_date), KEY idx_status (status), KEY idx_created_at (created_at) ); CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_id VARCHAR(64) NOT NULL, order_no VARCHAR(64) NOT NULL, parent_order_no VARCHAR(64) NULL, raw_product_name VARCHAR(255) NOT NULL, platform_product_code VARCHAR(64) NULL, spec VARCHAR(128) NULL, manufacturer VARCHAR(255) NULL, approval_no VARCHAR(64) NULL, qty DECIMAL(14,2) NOT NULL, unit_price DECIMAL(14,4) NULL, source_row_no INT NULL, created_at DATETIME NOT NULL, KEY idx_job (job_id), KEY idx_order_no (order_no) ); CREATE TABLE product_match ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_id VARCHAR(64) NOT NULL, order_item_id BIGINT NOT NULL, match_key VARCHAR(512) NOT NULL, match_status VARCHAR(16) NOT NULL, -- EXACT/FUZZY/MULTI/NONE/MANUAL goods_id VARCHAR(64) NULL, goods_code VARCHAR(64) NULL, base_unit VARCHAR(32) NULL, candidates_json JSON NULL, source_add_needed TINYINT NOT NULL DEFAULT 0, confirmed_by VARCHAR(64) NULL, confirmed_at DATETIME NULL, mapping_version VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL, KEY idx_job (job_id), KEY idx_status (match_status) ); ``` --- ## 四、状态机与节点设计 ### 4.1 job 主状态机(7 状态) ``` PENDING ──► RUNNING ──► SUCCESS │ │ ▲ │ │ └──── (重试 attempt_no+1) │ ├──► WAITING_USER ──► RUNNING (人工处理后继续) │ ├──► FAILED_RETRYABLE ──► RUNNING │ └──► FAILED_MANUAL (转人工手工做,如M5质管缺失) └──────────────► CANCELLED ``` | 状态 | 含义 | 触发 | | --- | --- | --- | | PENDING | 已建单待调度 | 建单成功 | | RUNNING | 节点执行中 | 调度启动节点 | | WAITING_USER | 等人工(对码未命中/一对多/货源新增/OCR确认) | 节点转人工 | | SUCCESS | 全链完成(一期到 M5 待保存+人工完成后) | 末节点成功 | | FAILED_RETRYABLE | 可自动重试(网页卡顿/登录失败) | 系统类错误 | | FAILED_MANUAL | 停止转人工手工做(M5质管资料缺失) | 业务决策终止 | | CANCELLED | 取消 | 人工终止 | **重试机制**:节点失败不覆盖历史,`attempt_no+1` 新建 `job_node` 行;系统类自动重试 1-2 次,超时升级。 ### 4.2 节点编码字典(订单驱动重排) | 顺序 | 节点编码 | 内容 | 转人工场景 | 能力标签 | | --- | --- | --- | --- | --- | | 1 | `ORDER_FETCH` | 登录平台,选前一天,导出订单Excel(状态过滤【待确认】) | 登录失败→OPS | desktop_ui, file_download | | 1b | `OCR_EXTRACT` | 仅线下单:图片→字段 | 低置信度→PLANNER | ocr, http_callback | | 2 | `PRODUCT_MATCH` | 四要素对码→货品ID/编码 | 未命中/一对多→PLANNER | (状态中心内算) | | 2b | `SOURCE_ADD` | 未采购过品种→通知M5新增货源 | 缺货源→业务 | (通知) | | 3 | `CONTRACT_CREATE` | 合同建单,选模板,签约日=订单日;医药城合并PDF上传 | 合同禁掉失效→采购邮件 | desktop_ui, file_upload, pdf_merge | | 4 | `CONTRACT_APPROVE_WAIT` | 送审后等半小时查审核 | 未审完→等待 | http_callback, timer | | 5 | `REBATE_HEADER` | 按合同号查总单,填必填项保存 | — | desktop_ui | | 6 | `REBATE_IMPORT` | 细单模板导入(备注填订单号,特定补偿=0) | 重复编码→采购【待确认】 | excel_template, file_upload | | 7 | `REBATE_SUBMIT` | 提交送审(决定M5能否带出协议)⭐ | 未送审→阻断 | desktop_ui | | 8 | `M5_CREATE_ORDER` | M5-1055新增订单+细单,备注填订单号,选正常保管账,**不点保存** | 质管资料缺失→FAILED_MANUAL | desktop_ui | | 9 | `M5_MANUAL_CONFIRM` | 人工检查→保存→签订(阶段一人工,开关控制) | — | (人工/可配自动) | | 10 | `M5_RECEIPT_NOTICE` | 生成收货通知单 | — | (人工/可配自动) | | 11 | `ARCHIVE_JOB` | 归档业务编号链 | — | (系统) | **关键卡点**:`REBATE_SUBMIT` 未送审则 M5 带不出协议,是硬阻断点。 ### 4.3 节点转移规则(示例) - `PRODUCT_MATCH`:全 EXACT/已确认 → 进 `CONTRACT_CREATE`;有 MULTI/NONE → `WAITING_USER` 建 `user_task`。 - `CONTRACT_APPROVE_WAIT`:定时器半小时后查;未审完保持等待并重查,超阈值升级 OPS。 - `M5_CREATE_ORDER` 报质管错 → 直接 `FAILED_MANUAL`(不重试、不等质管),通知业务手工做。 --- ## 五、幂等、追溯与证据设计 ### 5.1 幂等三层 1. **入池幂等**:源文件哈希 `source_file_hash` + `business_key`,同批订单重复导入不生成多条未完成 job。 2. **回调幂等**:`job_id + node_code + attempt_no` 唯一键;重复回调按幂等键去重。 3. **人工提交幂等**:`task_id + submit_token`,防止 H5 重复点击重复推进。 4. **执行前检索**:RPA 在返利/M5 建单前先检索已有单号,避免重复建单。 ### 5.2 business_key 生成 `business_key = source_type + "|" + supplier_name + "|" + order_date` (同供应商同日同来源 = 一条合同 = 一个 job)。加单场景父订单号记录在 order_item,重做逻辑待确认。 ### 5.3 订单号贯穿追溯 | 位置 | 填什么 | 给谁看 | | --- | --- | --- | | 电商平台 | 原始订单号 ZQD... | 源头 | | 返利中心细单备注 | 订单号 | 物流部软件 | | M5 订单备注 | 订单号 | 采购结算(打结算勾对账) | 归档链:订单号→合同编号→返利总单号→M5单号→收货通知单号,写入 `job` 各字段 + `job_event`。 ### 5.4 证据存储 目录 `对象存储/{job_id}/{node_code}/{attempt_no}/`,含截图、日志、OCR JSON、合同PDF/合并PDF。`job_artifact` 记录元数据、哈希、可见角色、脱敏标记。 --- ## 六、接口契约设计 > 所有写操作经状态中心,RPA/H5/企业微信不得直连数据库。鉴权:内部 `Bearer`,RPA `X-Agent-Token`,H5 企业微信身份换 JWT,写接口带 `X-Idempotency-Key`。 ### 6.1 统一节点回调契约 节点开始 `POST /api/jobs/{jobId}/nodes/{nodeCode}/start` ```json { "attemptNo": 1, "executorPlatform": "YINGDAO", "operator": "agent-01", "startedAt": "2026-08-07T22:00:00+08:00" } ``` 节点成功 `POST /api/jobs/{jobId}/nodes/{nodeCode}/complete` ```json { "attemptNo": 1, "executorRunId": "YD-RUN-001", "endedAt": "2026-08-07T22:03:00+08:00", "businessRefs": { "contractSystemNo": "HT...", "rebateOrderNo": null, "m5OrderNo": null, "receiptNoticeNo": null }, "summary": { "importedItemCount": 18, "failedItemCount": 0 }, "artifacts": [ { "artifactType": "SCREENSHOT", "storagePath": "/data/jobs/.../success.png" } ] } ``` 约束:节点要求业务编号却未回传,不得记 SUCCESS。 节点失败 `POST /api/jobs/{jobId}/nodes/{nodeCode}/fail` ```json { "attemptNo": 1, "errorCode": "REBATE_DUP_CODE", "errorSummary": "存在重复的编码", "retryable": false, "routeRole": "PURCHASER", "artifacts": [ { "artifactType": "SCREENSHOT", "storagePath": "/data/jobs/.../error.png" } ] } ``` ### 6.2 错误码分层 | 层 | 前缀 | 处理 | 例 | | --- | --- | --- | --- | | 系统类 | `SYS_` | 自动重试1-2次 | SYS_LOGIN_FAIL, SYS_PAGE_TIMEOUT | | 业务类 | `BIZ_` | 转对应角色 | BIZ_MATCH_NONE, REBATE_DUP_CODE, M5_QC_MISSING | | 配置类 | `CFG_` | 转OPS/管理员 | CFG_TEMPLATE_MISSING | | 页面变化类 | `UI_` | 转OPS | UI_ELEMENT_NOT_FOUND | | 能力不满足 | `CAP_` | 换平台/转OPS | CAP_PDF_MERGE_UNSUPPORTED | 特例:`M5_QC_MISSING` → 不重试,直接 `FAILED_MANUAL`。 ### 6.3 待办与查询接口 - `GET /api/tasks/my?roleCode=PLANNER&status=open` - `POST /api/tasks/{taskId}/submit`:动作 `CONFIRM_AND_CONTINUE`/`SELECT_CANDIDATE`(对码一对多)/`RETRY_CURRENT_NODE`/`TRANSFER_MANUAL`/`TERMINATE_JOB`;返回即时回执 `receiptMessage`/`nextExpectedAction`。 - `POST /api/jobs/{jobId}/retry`、`POST /api/jobs/{jobId}/handoff` - `GET /api/jobs/{jobId}`:job基础+节点轨迹+待办+对码摘要+业务编号+证据列表。 - `POST /api/wecom/callback`:只接动作/身份,转标准动作后调内部逻辑。 ### 6.4 对码专用接口(新增) - `POST /api/jobs/{jobId}/match/run`:触发对码引擎,返回命中率与未命中清单。 - `GET /api/jobs/{jobId}/match/unmatched`:未命中/一对多候选(供H5)。 - `POST /api/match/{matchId}/confirm`:人工确认选择候选或新增,回写 `mapping_config`(累积迭代)。 --- ## 七、对码引擎设计(核心难点) ### 7.1 匹配流程 ``` order_item ──► 规范化(去空格/全半角/规格标准化) ──► 拼接 match_key(通用名+批准文号+规格+厂家) │ ├─ 精确命中 mapping_config(PRODUCT) ──► EXACT, 填 goods_id/code ├─ 模糊命中(规格/厂家容错规则) ─────► FUZZY(标记待复核) ├─ 同商品名多规格 ─────────────────► MULTI, 生成 candidates └─ 无命中 ─────────────────────────► NONE, 建 user_task 转人工 ``` ### 7.2 规格标准化规则(针对痛点) - `15×2 ↔ 30片`(乘积展开)、`干一瓶 ↔ ×1瓶`、进口厂家英文名容错(大小写/标点/B.V. S.r.l.)。 - 规则表化,可迭代补充;未覆盖的进 MULTI/NONE 人工。 ### 7.3 迭代闭环 人工确认结果回写 `mapping_config`(新版本 `version_no`),下次自动命中。命中率统计写入 `job_event`,支撑阶段0/阶段3的准确率报告。 --- ## 八、关键时序图 ### 8.1 医药城端到端(正向) ``` 定时器 → 状态中心: 触发建单(前一天医药城订单) 状态中心 → RPA: 启动 ORDER_FETCH RPA → 医药城: 登录/导出Excel RPA → 状态中心: complete(ORDER_FETCH, artifacts=Excel) 状态中心 → 对码引擎: PRODUCT_MATCH(解析order_item, 匹配) 对码引擎 → 状态中心: 全命中 状态中心 → RPA: 启动 CONTRACT_CREATE(need_pdf_upload=1) RPA → 合同系统: 建单/选格式模板/改签约日/合并PDF上传/送审 RPA → 状态中心: complete(合同编号) 状态中心: 定时器等30分钟 → RPA: CONTRACT_APPROVE_WAIT 查审核 状态中心 → RPA: REBATE_HEADER(按合同号查总单填必填) 状态中心 → RPA: REBATE_IMPORT(细单模板,备注=订单号,特定补偿0) 状态中心 → RPA: REBATE_SUBMIT(送审) ⭐ 状态中心 → RPA: M5_CREATE_ORDER(新增订单+细单,备注订单号,正常保管账,不保存) RPA → 状态中心: complete(M5待保存) 状态中心 → 企业微信: 推送人工检查待办 计划员 → H5: 检查→保存→签订→生成收货通知单(阶段一人工) 状态中心: ARCHIVE_JOB 归档编号链 ``` ### 8.2 对码未命中(一对多) ``` 对码引擎 → 状态中心: MATCH_MULTI(candidates) 状态中心 → user_task: 建待办(reason=MATCH_MULTI, candidates_json) 状态中心 → 企业微信: 推送"对码待确认" 计划员 → H5: 选择候选/新增 H5 → 状态中心: submit(SELECT_CANDIDATE) 状态中心 → mapping_config: 回写(累积迭代) 状态中心: job WAITING_USER → RUNNING, 继续 CONTRACT_CREATE ``` ### 8.3 M5 质管资料缺失(转人工,不等质管) ``` RPA → M5: 新增订单保存时报错(供应商年报过期) RPA → 状态中心: fail(M5_QC_MISSING, retryable=false) 状态中心: job → FAILED_MANUAL (不重试/不等质管) 状态中心 → 企业微信: 通知业务"此单转人工手工做" 业务: 次日质管维护后自行在M5手工完成 ``` --- ## 九、模块设计(状态中心内部) | 模块 | 职责 | 关键接口/服务 | | --- | --- | --- | | intake_service | 建单入池、哈希/业务键去重、Excel解析入 order_item | POST /api/jobs | | workflow_service | 状态机流转、节点调度、转移规则 | node start/complete/fail | | match_service | 对码引擎、规格标准化、候选生成、迭代回写 | match/run, match/confirm | | retry_service | 系统类自动重试、超时扫描升级 | 定时任务 | | task_service | 待办生成、角色路由、提交幂等、回执 | tasks/my, tasks/submit | | executor_router | 节点→平台路由(FIXED/PREFERRED/FALLBACK) | 内部 | | ocr_service | 线下单OCR适配、置信度阈值 | OCR_EXTRACT | | artifact_service | 证据落盘、可见角色、脱敏 | 内部 | | wecom_service | 企业微信消息、卡片更新、身份 | wecom/callback | | audit/query | 事件流、整单查询、审计导出 | GET /api/jobs/{id} | --- ## 十、与早期草案的设计差异(供评审) | # | 早期草案 | 本设计 | 原因 | | --- | --- | --- | --- | | 1 | job=一份合同文件的流程 | job=同供应商同日一批订单 | 订单驱动,合同是产物非入口 | | 2 | 无 order_item/product_match 表 | 新增两表 | 对码是核心难点须落库 | | 3 | 节点以合同/OCR为主线 | 节点订单驱动重排(ORDER_FETCH起) | 骨架方向纠正 | | 4 | 送审在合同系统 | 关键送审 REBATE_SUBMIT | 卡点位置纠正 | | 5 | 质管缺料转质管重试 | M5_QC_MISSING→FAILED_MANUAL | 业务明确否决重试 | | 6 | 无自动化开关 | job.auto_save_enabled 分阶段 | 分阶段上线诉求 | | 7 | 技术栈未定 | 确定 Python+FastAPI+MySQL,骨架已跑通 | 与对码/OCR同栈,开发快 | | 8 | OCR为全流程核心 | OCR仅线下单分支 | 范围纠正 | --- ## 十一、待确认的设计输入(阻塞项) 1. **OCR信息安全**:线上是否获批 / 本地部署预算 → 决定 `OCR_EXTRACT` 节点部署方式。 2. **返利中心重复订单**完整规则 → 决定 `REBATE_DUP_CODE` 是否可自动重建总单。 3. **医药城订单状态过滤**:只取"待收货"还是含其他 → `ORDER_FETCH` 过滤条件。 4. **加单场景**:父订单号关联与重做逻辑 → `parent_job_id/parent_order_no` 处理策略。 5. **测试账号/对码表(Oracle连接串+表名列名)/供应商对照表/邮箱/计划员表**到位时间 → 影响联调。 > 技术栈已确认:状态中心 = Python 3 + FastAPI + MySQL 8(骨架见 `state_center/`),不再作为阻塞项。 --- ## 附录:联调顺序建议 1. 状态中心单独跑通建单/状态流转/待办。 2. 假OCR+假RPA回调打通全流程。 3. 接真实对码引擎(复用阶段0成果)。 4. 接真实企业微信+H5身份。 5. 接真实RPA(先医药城单来源)。 6. 返利中心/M5端到端联调。