AutoRpa/state_center/README.md

125 lines
6.1 KiB
Markdown
Raw Permalink 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.

# 状态中心(采购主链RPA一期 · 第1步骨架)
Python + FastAPI + MySQL。库里只存**流程状态 + 对码累积别名 + 证据**;对码货品主数据在 **Oracle**,直连查(不复制)。
## 目录
```
state_center/
├── sql/schema.sql 建库脚本(job 那套 + mapping_config + 证据)
├── app/
│ ├── config.py 配置(MySQL/Oracle 连接、表名列名,从环境变量读)
│ ├── db.py MySQL 访问(pymysql)
│ ├── match_engine.py 对码引擎(两级命中 + 规格标准化 + Oracle 取数)
│ └── main.py FastAPI:建单 + 回调(含幂等/乱序拒绝)+ 合同审批等待 + 整单查询
├── tests/test_match_engine.py 对码单测(不依赖 DB/Oracle)
└── requirements.txt
```
## 起步
以下命令在 macOS/Linux 的终端执行;Windows PowerShell 只需要把激活命令换成下一段对应写法,接口和浏览器端完全相同。
```bash
python -m pip install -r state_center/requirements.txt
# 1) 建库(先起个 MySQL 8)
mysql -uroot -p < state_center/sql/schema.sql
# 2) 配连接(默认 root/root@127.0.0.1)
export MYSQL_DSN="mysql+pymysql://root:root@127.0.0.1:3306/rpa_center?charset=utf8mb4"
# 3) 起服务
uvicorn state_center.app.main:app --reload --port 8000
# 打开 http://127.0.0.1:8000/docs 有全部接口可点
```
Windows PowerShell:
```powershell
py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r state_center\requirements.txt
$env:DEV_MODE = "true"
$env:MYSQL_DSN = "mysql+pymysql://root:root@127.0.0.1:3306/rpa_center?charset=utf8mb4"
python -m uvicorn state_center.app.main:app --reload --port 8000
```
浏览器端另开一个终端:
```bash
cd state_center_web
npm install
npm run dev
```
然后打开 `http://localhost:5173/`。Windows 和 macOS 均使用标准浏览器访问,不需要 Electron 或系统专用脚本。`DEV_MODE=true` 只用于本地样例货品和节点回调模拟,生产环境不要开启。
本地样例联调顺序:建单 → 写入订单明细(`POST /api/jobs/{job_id}/items`) → 开发接口装载样例货品(`POST /api/dev/match/load-fixture-goods`) → 运行对码。真实环境把样例接口替换为 Oracle 货品装载即可。
## 验证第1步(不接真实系统,假回调走全链)
```bash
# 建单
curl -X POST http://127.0.0.1:8000/api/jobs -H 'Content-Type: application/json' \
-d '{"source_type":"YYC","order_date":"2026-08-04","supplier_name":"重庆亿昊药业"}'
# 拿到 job_id 后,模拟艺赛旗回调(带 token):
curl -X POST http://127.0.0.1:8000/api/jobs/<JOB_ID>/nodes/ORDER_FETCH/start \
-H 'X-Agent-Token: dev-agent-token' -H 'Content-Type: application/json' -d '{"attemptNo":1}'
curl -X POST http://127.0.0.1:8000/api/jobs/<JOB_ID>/nodes/ORDER_FETCH/complete \
-H 'X-Agent-Token: dev-agent-token' -H 'Content-Type: application/json' \
-d '{"attemptNo":1,"summary":{"orderCount":12}}'
# 反复 start/complete 走完各节点,最后 GET 看整单轨迹:
curl http://127.0.0.1:8000/api/jobs/<JOB_ID>
```
## 节点编排(13 = 11 主 + 2 条件)
主链 11 步(每单都走):
`ORDER_FETCH → PRODUCT_MATCH → CONTRACT_CREATE → CONTRACT_APPROVE_WAIT → REBATE_HEADER → REBATE_IMPORT → REBATE_SUBMIT → M5_CREATE_ORDER → M5_MANUAL_CONFIRM → M5_RECEIPT_NOTICE → ARCHIVE_JOB`
2 个条件节点(不满足条件自动跳过,不算漏节点):
| 条件节点 | 何时执行 | 何时跳过 |
| --- | --- | --- |
| `OCR_EXTRACT` | 线下手工单(source_type=OFFLINE) | 线上单(医药城/小药药) |
| `SOURCE_ADD` | 对码判定缺货源(needs_source_add=1) | 货源齐全 |
转移不是"数组下一个",是 `main.py` 里的显式转移表 `TRANSITIONS` + 条件跳过。
每个节点标了执行方 `NODE_EXECUTOR`:STATE_CENTER(对码,内部算)/ RPA(艺赛旗点页面)/ HUMAN(一期人工)。
所以文档里"11 个节点"和代码里"13 个"是同一套:**11 主 + 2 条件 = 13**。
## 关键设计点(会审确认)
- **回调幂等**:同节点同 attempt 重复 complete 不会二次推进(靠 job_node 状态 + `callback_idempotency_key`)。
- **乱序拒绝**:回调节点必须是 job 当前节点,否则 409(避免网络重发把状态推乱)。
- **合同审批等待**(CONTRACT_APPROVE_WAIT)**不连续轮询**:进 `WAITING_EXTERNAL` 态,每
`CONTRACT_WAIT_INTERVAL_MIN`(默认 30 分)由调度器扫 `GET /api/scheduler/due-contract-checks`
触发 RPA 查一次,结果回 `POST .../CONTRACT_APPROVE_WAIT/check-result`;查满
`CONTRACT_WAIT_MAX_CHECKS`(默认 8 次)还没过就升级 OPS。
- **对码状态只有 4 种**:EXACT / ALIAS / MULTI / NONE(无 FUZZY;ALIAS=累积别名命中,非当次人工)。
- **PRODUCT_MATCH 是状态中心自己算的节点**(executor=STATE_CENTER),不走 RPA 三回调,靠
`POST /api/jobs/{id}/match/run` 推进:跑 `order_item` → 落 `product_match` → MULTI/NONE 建
待办转 WAITING_USER,全命中自动往下走;`POST /api/match/{id}/confirm` 人工确认后沉淀
`mapping_config` 别名并推进。**前置**:`order_item` 已落库 + `POST /api/match/reload-goods`
从 Oracle 装载过货品主数据(这两样是 Trae 任务2/3 的接真数据部分)。
- **SOURCE_ADD 触发源明确**:对码拿到 `goods_id` 后,状态中心按历史 `supplier_name + goods_id`
判断是否已有货源;没历史则写 `product_match.source_add_needed=1`、同步 `job.needs_source_add=1`
并创建一条聚合的 `SOURCE_ADD` 待办。
- **对码重跑幂等**:`product_match` 以 `(job_id, order_item_id)` 为当前态唯一键,
`match/run` 重跑时 upsert 结果并重建 OPEN 待办,不重复造结果/待办。
- **alias 当前态唯一**:`match/confirm` 会先失效同一 `config_key` 的旧 active 记录,再插入新 active;
装载 alias 时按 `updated_at/id` 固定顺序读取,历史脏数据也有确定结果。
## 对码接 Oracle(拿到连接串后)
1. `pip install oracledb`
2. 在 `config.py` / 环境变量填 `ORACLE_DSN/USER/PASSWORD`,把 `ORACLE_GOODS_TABLE` 和各列名改成真实的。
3. 开工时 `load_goods_from_oracle(settings)` 拉全量 → `MatchEngine.load_goods(...)`,之后内存匹配。
## 对码单测
```bash
python3 state_center/tests/test_match_engine.py # 6/6 passed
```