608 lines
31 KiB
Markdown
608 lines
31 KiB
Markdown
# 采购主链 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端到端联调。
|