Compare commits
No commits in common. "feat/import-windows-rpa" and "main" have entirely different histories.
feat/impor
...
main
|
|
@ -0,0 +1,49 @@
|
|||
# OS / editor
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
!rpa_selection/.vscode/
|
||||
!rpa_selection/.vscode/**
|
||||
*.swp
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.venv/
|
||||
.venv_test/
|
||||
.whisper_venv/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Local runtime / agent
|
||||
.runtime/
|
||||
rpa_contract/screenshots/
|
||||
rpa_rebate/screenshots/
|
||||
rpa_cart/exports/
|
||||
rpa-chrome-profile/
|
||||
**/chrome-profile/
|
||||
.tmp/
|
||||
.grok/
|
||||
.grokignore
|
||||
.claude/
|
||||
.superpowers/
|
||||
.worktrees/
|
||||
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# 选品凭据与运行结果
|
||||
rpa_selection/secrets.json
|
||||
rpa_selection/data/.auth/
|
||||
rpa_selection/output/结果/*.xlsx
|
||||
rpa_selection/output/结果/*.txt
|
||||
|
||||
# Media (keep docs, drop recordings and extracted frames)
|
||||
*.mp4
|
||||
video_frames_v2/
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
# AutoRpa
|
||||
|
||||
洁达 RPA。当前主线是**订单中心入站**(`rpa_oms`)、**选品班次**(`rpa_selection/`)、**加购班次**(`rpa_cart/`)、**合同班次**(`rpa_contract/`)和**返利班次**(`rpa_rebate/`):收件落 Oracle,请求里不跑选品、不录合同;选品另起进程捞 `deal_flag=0`,凑齐后同一进程按本次拆定的 sku 加车;加购进程只补表 2 里仍是「系统管理员」或过期「加购中」的行;合同另起进程领表 3;返利另起进程领表 4 起草行 / 表 3 只录协议行。根上不再有 `选品流程/`、`state_center/`、`rpa_yisaiqi/`。
|
||||
|
||||
智力用在**定落点、想清楚对错**;速度和 token 靠下面这张地图定点打开,禁止用扫仓代替思考。改对应了,反工和消耗都会少。
|
||||
|
||||
## 怎么干活
|
||||
|
||||
1. **先定落点**(不开文件):哪个函数、成功长什么样(哪条 ACCEPT / 哪个点名用例)、这次不碰什么。方向说不清就先问,不要靠翻目录找感觉。
|
||||
2. **只在落点上想透**:规则、边界、会不会拿错号/写错状态。打开文件是为了验证落点,不是再想要不要改别处。
|
||||
3. **薄验收就停**:控制台 `ACCEPT SUMMARY` 或一条点名用例。过了收工。
|
||||
|
||||
问「法子 / 怎么优化」先答方案;用户说「改 / 重构 / 收口」再动代码。一件事一个会话,换任务 `/new`。
|
||||
|
||||
## 项目地图
|
||||
|
||||
按问题打开一行,不要 `list_dir` 根目录。
|
||||
|
||||
| 问题 | 打开 | 验收 |
|
||||
| --- | --- | --- |
|
||||
| 接口1 收件契约 | `rpa_oms/app/inbound/sales_goods.py`、`envelope.py`、`parse.py` | `rpa_oms/tests/test_sales_goods_receive.py::点名` |
|
||||
| 接口1 Oracle 落表 | `rpa_oms/app/inbound/oracle_repo.py` | `rpa_oms/tests/test_oracle_repo.py::点名` |
|
||||
| 接口1 入口 / 鉴权 / 审计 | `rpa_oms/app/main.py`、`auth.py`、`inbound/audit.py` | 同上;真库见 `rpa_oms/README.md` 运维 |
|
||||
| 接口2 取数组装 | `rpa_oms/app/outbound/cart_settlement.py` | `rpa_oms/tests/test_cart_settlement_outbound.py::test_assembles_unpushed_rows_as_document_envelope` |
|
||||
| 接口2 Oracle 取数 | `rpa_oms/app/outbound/oracle_repo.py` | `rpa_oms/tests/test_oracle_cart_repo.py::test_list_unpushed_keeps_whole_oms_group_when_limit_is_3` |
|
||||
| 接口4 取数组装 | `rpa_oms/app/outbound/contract_agreement.py` | `rpa_oms/tests/test_contract_agreement_outbound.py::test_assembles_unpushed_rows_as_document_envelope` |
|
||||
| 接口4 Oracle 取数 | `rpa_oms/app/outbound/oracle_repo.py` | `rpa_oms/tests/test_oracle_agreement_repo.py::test_list_unpushed_keeps_whole_contract_no_group_when_limit_is_3` |
|
||||
| 接口3 收件契约 | `rpa_oms/app/inbound/purchase_contract.py` | `rpa_oms/tests/test_purchase_contract_receive.py::点名` |
|
||||
| 接口3 Oracle 落表 | `rpa_oms/app/inbound/oracle_contract_repo.py` | `rpa_oms/tests/test_oracle_contract_repo.py::点名` |
|
||||
| 选品规则 / 拆量 | `rpa_selection/rules.py`、`pipeline.py` | `rpa_selection/tests/test_pipeline.py::test_split_across_malls_is_success` |
|
||||
| 选品班次落表 / 凑齐后加车 | `rpa_selection/job.py`、`persist.py`、`repo.py`;加车 `rpa_cart/cart_job.py` `add_known_lines` | `rpa_selection/tests/test_job.py::test_success_adds_each_split_sku_without_rereading_table2` |
|
||||
| 合同班合单落表 | `rpa_contract/job.py`、`group.py`、`persist.py`、`repo.py` | `rpa_contract/tests/test_job.py::test_job_merges_same_supplier_and_writes_table4` |
|
||||
| 合同页面步骤 | `rpa_contract/contract_login.py`(S02–S05)/ `contract_form.py`(S06–S08、S10)/ `contract_goods.py`(S09)/ `contract_upload.py`(S11)/ `contract_submit.py`(S12)/ `contract_number.py`(S13);共用 `contract_common.py`;入口 `contract_flow_steps.py` | `ACCEPT SUMMARY` 或 `rpa_contract/screenshots/` 下 `ACCEPT.txt` |
|
||||
| 合同入参 / 客商草稿 | `rpa_contract/contract_draft.py` | `rpa_contract` 点名用例 |
|
||||
| 节点入口 | `rpa_contract/contract_create_node.py`、`main.py` | 读到正式合同号 |
|
||||
| 浏览器适配 | `rpa_browser/adapter.py`;返利 junction/口在 `rpa_rebate/apa_browser_adapter.py` | `rpa_contract/tests/test_apa_browser_adapter.py` |
|
||||
| 文字验收 / 截图策略 | `rpa_contract/accept.py` | 只读 `ACCEPT.txt`,禁止读 png |
|
||||
| 加购班次 | `rpa_cart/cart_job.py`、`cart_repo.py`、`cart_match.py` | `rpa_cart/tests/test_cart_job.py::test_job_adds_split_rows_without_insert` |
|
||||
| 支付回传 | `rpa_cart/job.py`、`settle.py` | 1 号药城支付按 Excel 落表,不对加购行 |
|
||||
| 返利班查号落表 | `rpa_rebate/job.py`、`persist.py`、`repo.py` | `rpa_rebate/tests/test_job.py::test_job_finds_contract_and_writes_agreement` |
|
||||
| 返利共用(登录/查号/勾行/弹层) | `rpa_rebate/rebate_common.py` | 四个节点共用;改这里四个一起变 |
|
||||
| 返利班查号页面 | `rpa_rebate/rebate_find.py`、`rebate_finder.py` | `ACCEPT SUMMARY REBATE_FIND` 或 `rpa_rebate/tests/test_finder.py::test_find_flow_writes_accept_for_missing_number` |
|
||||
| 返利总单页面 | `rpa_rebate/rebate_header.py`、`run_header.py` | `ACCEPT SUMMARY REBATE_HEADER` 或 `rpa_rebate/tests/test_header.py::test_header_flow_writes_accept_with_order_no` |
|
||||
| 返利细单新增 | `rpa_rebate/rebate_import.py`、`run_import.py` | `ACCEPT SUMMARY REBATE_IMPORT` 或 `rpa_rebate/tests/test_import.py::test_import_flow_writes_accept_with_success_fail` |
|
||||
| 返利送审 | `rpa_rebate/rebate_submit.py`、`run_submit.py` | `ACCEPT SUMMARY REBATE_SUBMIT` 或 `rpa_rebate/tests/test_submit.py::test_submit_flow_writes_accept_with_order_nos` |
|
||||
| 四个口地图 | `rpa_oms/README.md`、根 `README.md` | — |
|
||||
| 节点业务规则(点名一节) | `docs/RPA订单中心接口开发口径_20260930.md` | 禁止通读 `docs/` |
|
||||
|
||||
禁止打开:`node_modules/`、`chrome-profile/`、任何 png。Chrome 用户目录只在仓库外 `D:\work\rpa-chrome-profile`。Oracle 密码放本机 `rpa_oms/.env`(选品空缺时复用);商城账号放 `rpa_selection/.env`;返利中心账号放 `rpa_rebate/.env`。都已 gitignore,不要写进仓库、README、示例 JSON,不要 `git add` / commit / push。
|
||||
|
||||
## 接口1 落点(订单中心)
|
||||
|
||||
订单中心四接口,本期开放接口 1、3 收件。鉴权 header `X-Oms-Token`(`OMS_INBOUND_TOKEN`)。接口 1:`POST /api/oms/v1/sales-goods`,落表 `rpa_sales_goods_mapping_data`,`deal_flag=0`,**请求里不跑选品**。接口 3:`POST /api/oms/v1/purchase-contracts`,落表 `rpa_purchase_contract_data`,`process_status=0`,按对方 `fid` 幂等,**请求里不录合同**。接口 2 只从表 2 取 `push_status=0` 组装报文,路径仍空,不发 HTTP。
|
||||
|
||||
运行时只落 Oracle。入站单测用假连接录 SQL,不连库。服务端口 **8001**。连接从 `rpa_oracle` 池借,一个请求一条。
|
||||
|
||||
| 步 | 函数 | 要点 |
|
||||
| --- | --- | --- |
|
||||
| 信封 | `parse_envelope` | 缺 `data` / 空数组 / 超过 100 条:整包 422,一条都不落 |
|
||||
| 整批校验 | `receive_sales_goods` | 内存 REJECTED / 同批 CONFLICT,再一次查重一次 `executemany` |
|
||||
| 鉴权 | `token_ok` | `X-Oms-Token` 共享密钥,未配一律失败;失败进审计 |
|
||||
| 落表 | `existing_oms_ids` + `insert_many` | `fid` 由序列触发器生成,INSERT 不绑这一列;只认 `uk_rpa_sgmd_oms_id` |
|
||||
| 幂等 | 同 `omsId` | 已存在 → `DUPLICATE`,不更新、不重跑 |
|
||||
| 同批冲突 | 同一 POST 里 `omsId` 再现 | 后一条 `CONFLICT`,只落第一条 |
|
||||
| 字段不过 | `_parse_row` | `REJECTED`,不入库 |
|
||||
| 混批 | `post_sales_goods` | 逐条有成功/重复/拒绝时 HTTP 仍 200,看 `results[].resultCode` |
|
||||
| 对外文案 | `messages.py` | 401「身份认证失败」;503「数据库不可用」;body 不含 ORA |
|
||||
| 审计 | `write_audit` | 一行请求一条;token 打码;写失败不把业务打成 500 |
|
||||
|
||||
`omsId` / `saleQty` / `planPurchaseQty` / 接口 3 的 `fid` 必须是 JSON 整数,不把 `"10"` 当 10。Windows 连真库测;Instant Client 位数与 Python 一致。
|
||||
|
||||
接口 2:`load_cart_settlement_envelope` 捞 `push_status=0`,同一 `omsId` 整组进一批,上限默认 500(`OMS_OUTBOUND_BATCH_LIMIT`),SQL 侧截组;金额四位小数字符串,NULL 金额写成 null,不抛整批。连接从 `rpa_oracle` 池借,`get_pool` 懒加载加锁。`mark_pushed` 已备,不 POST 订单中心。
|
||||
|
||||
接口 3:`receive_purchase_contracts` 按 `fid` 幂等;同批 `fid` 再现 → `CONFLICT`;`contractEntryStatus=0` 必须带 `contractNo`;结束日不得早于开始日。`INSERT` 绑 `fid`。形状与接口 1 相同:内存整批 → `existing_fids` → `insert_many`。
|
||||
|
||||
接口 4:`load_contract_agreement_envelope` 捞 `push_status=0`,同一非空 `contractNo` 整组进一批,上限同样默认 500,SQL 侧截组。空 `contractNo` 按行独立。`mark_pushed` 已备,不 POST。
|
||||
|
||||
验收(假连接、不连库):`cd rpa_oms` → `uv run pytest tests/test_sales_goods_receive.py -q`。真库:`GET /health` 后把 `examples/sales_goods_batch.json` POST 两次(第一次两条 `ACCEPTED`,第二次两条 `DUPLICATE`)。接口 3 点名 `tests/test_purchase_contract_receive.py`;真库 POST `examples/purchase_contract_batch.json` 两次。接口 2 点名 `tests/test_cart_settlement_outbound.py::test_assembles_unpushed_rows_as_document_envelope`。
|
||||
|
||||
修接口1不要顺手改 `rpa_selection/`、`rpa_cart/`、`rpa_contract/`、`rpa_rebate/`。接口 2、4 只组装不推送。
|
||||
|
||||
## 选品落点
|
||||
|
||||
班次作业在 `rpa_selection/`,不在接口 1 请求里跑。从表 1 领 `deal_flag=0`,按**条形码**搜 1 号药城 / 药帮忙,两平台合并后按四级仓序贪心拆量,写入表 2。凑齐在内存里定,才落成功行;凑不齐不落部分成功。凑齐的新行同一班按这次拆定的 sku 加车,不再去表 2 重捞来判断齐没齐。表 2 已有该 `omsId` 时不加第二份。
|
||||
|
||||
口径:`docs/superpowers/specs/2026-10-02-selection-split-allocation-design.md`。
|
||||
|
||||
| 步 | 函数 | 要点 |
|
||||
| --- | --- | --- |
|
||||
| 领取 | `claim_pending` | `deal_flag` 0→3;`FOR UPDATE SKIP LOCKED`;中途崩溃停在 3,超过租约(`deal_time` 30 分钟)可再领 |
|
||||
| 规则 | `allocate` / `run_selection` | 自营仓全称匹配、毛利严格大于 4%、效期 ≥ 12 月;同仓多个 sku 都参与拆量,低价先吃;按仓序拆量 |
|
||||
| 落表 | `build_result` + `save_result` | 成功每条拆量一行 `status=1`,合计=计划量;`push_status=0`。要马上加车的新行更新人写成「加购中」,并 RETURNING 回 fid |
|
||||
| 库存不足 | `KIND_BUSINESS_FAIL` | 两边都搜到但合计不够:`deal_flag=2`,该 `omsId` 不重跑 |
|
||||
| 对端故障 | `KIND_SYSTEM_FAIL` | 一边故障且剩下的凑不齐:可把 `deal_flag` 改回 0;禁止写成库存不足 |
|
||||
|
||||
湖南壹号、甘肃壹号云不在自营集合。凑齐后按本次 sku 加车,不推接口 2。加购失败不回滚表 1 的选品成功。登录或查询失败把表 2 戳放回「系统管理员」。同单有成功又有失败时,失败原因带「未加齐」。
|
||||
|
||||
验收:`cd rpa_selection` → `uv run pytest tests/test_pipeline.py::test_split_across_malls_is_success -q`(杭州 3 + 广东 7 = 计划 10)。
|
||||
|
||||
## 加购落点
|
||||
|
||||
补偿班在 `rpa_cart/`。正常凑齐的加车发生在选品班里。本班领表 2 `operate_type=1 AND status=1 AND push_status=0`,戳为 `系统管理员` 或过期的 `加购中`。这些行没有 sku,登录 1 号药城 / 药帮忙后按条形码再搜,对上 `supplier_name`,按行加车。
|
||||
|
||||
| 步 | 函数 | 要点 |
|
||||
| --- | --- | --- |
|
||||
| 领取 | `claim_pending` | 盖 `加购中`;`FOR UPDATE SKIP LOCKED`;中途崩溃停在这戳,超过租约(`update_time` 30 分钟)可再领 |
|
||||
| 对仓 | `match_offer` | 条形码结果对商家全称;同仓留低价 |
|
||||
| 加车 | `run_cart_batch` + 平台 `add_*_cart` | 改数量加入购物车。无 `sku_id`,必须再搜 |
|
||||
| 成功 | `mark_success` | 不插行,`update_user_name=加购班`,`status` 仍 1 |
|
||||
| 失败 | `mark_fail` | 该行 `status=2` + 原因。登录挂写 `LOGIN_FAILED`;库存/中包装/对不上仓写业务原因 |
|
||||
|
||||
同一 `omsId` 拆两仓要加齐;一行失败只改那一行。同组里有成功又有失败时,失败行原因带「未加齐」,不回退已成功的行。不推接口 2,不清空购物车,不碰合同、表 1。接口 2 仍捞全部 `push_status=0`,加购班跑完之前不要组装。
|
||||
|
||||
验收:`cd rpa_cart` → `uv run pytest tests/test_cart_job.py::test_job_adds_split_rows_without_insert -q`(杭州 3 + 广东 7,不插新行)。
|
||||
|
||||
## CONTRACT_CREATE 落点
|
||||
|
||||
班次作业在 `rpa_contract/`,不在接口 3 请求里跑。领表 3 `process_status=0`(跳过协议重录行),按 `supplierId` + `org_code` + 签订日/开始日/结束日合单,任一不同则各自成组,调页面流,写表 4。`run_contract_create_flow` 串页面;修一步只打开对应文件。`contract_flow_steps.py` 仍是对外入口。
|
||||
|
||||
| 步 | 打开 | 要点 |
|
||||
| --- | --- | --- |
|
||||
| S02 打开 | `contract_login.py` `open_contract_system` | `http://contract-test.gzmpc.com:8080/` |
|
||||
| S03 登录 | `contract_login.py` `login_contract_system` | 账密只认 `CONTRACT_USERNAME` / `CONTRACT_PASSWORD` |
|
||||
| S04–S05 起草 | `contract_login.py` `enter_contract_create`、`select_contract_template` | 直达 `FORM_PAGE_URL`,不要点 SPA 菜单 |
|
||||
| S06 签约方 | `contract_form.py` `fill_contract_signatory_info` | 客商 `DW0100046361` / `重庆亿昊药业有限公司` |
|
||||
| 尽调 | `contract_form.py` `check_customer_diligence` | 正文含「未尽调」则失败,不提交 |
|
||||
| S07 基础 | `contract_form.py` `fill_contract_basic_info` | 标题 `_expected_title` |
|
||||
| S08 金额日期 | `contract_form.py` `fill_contract_amount_and_dates` | 签订日/开始日用接口 3 三日期,缺则回退 `order_date` |
|
||||
| S09 货品 | `contract_goods.py` `fill_contract_goods_lines` | 弹窗按操作码搜,找不到则本单失败 |
|
||||
| S10 付款 | `contract_form.py` `fill_contract_payment_info` | |
|
||||
| S11 PDF | `contract_upload.py` `upload_contract_document_if_needed` | 仅 YYC 传合并 PDF |
|
||||
| S12 提交 | `contract_submit.py` `submit_contract_for_approval` | 成功后**不要等详情页** |
|
||||
| S13 合同号 | `contract_number.py` `read_contract_number` | 回「我的合同」,`fdId` > 标题+客户编码;禁止盲取最新一条 |
|
||||
|
||||
成功路径默认不截图(`RPA_SCREENSHOT=always` 才全截)。页面等待用 `wait_for_text` / `wait_for_element`,不要固定长睡。
|
||||
|
||||
## 返利班落点
|
||||
|
||||
班次作业在 `rpa_rebate/`,不在接口 3 请求里跑。领表 4 `action_code=1 AND contract_status=1`(合同班起草、尚未回成功),以及表 3 `contract_entry_status=0` 且已有 `contractNo` 的只录协议行。按合同号查返利中心;搜到后同一班走总单 → 细单新增 → 送审,送审成功才插 `actionCode=2`、`agreementStatus=1` 并带上号。搜不到不重录合同,留给下一班。接口 4 组包在 oms,path 空,不发 HTTP。同一合同一组一个结果。
|
||||
|
||||
| 步 | 函数 | 要点 |
|
||||
| --- | --- | --- |
|
||||
| 领取 | `claim_pending` | 表 4 `process_status` 0→3,同一合同号整组;表 3 `process_status` 0→3;`FOR UPDATE SKIP LOCKED`;中途崩溃停在 3,超过租约(表 4 `update_time` / 表 3 `process_time` 30 分钟)可再领 |
|
||||
| 查号 | `RebateFinder` / `search_contract` | 打开 `REBATE_URL` → 登录 → 采购协议 → 按合同号搜;表里出现该号才 `True`,输入框回显不算 |
|
||||
| 录协议 | `recorder` | 搜到后总单 → 细单新增(按货品ID,不做 Excel)→ 送审;送审成功才插 `actionCode=2` |
|
||||
| 落表 | `build_found` + `save_group` | 送审成功:起草行 `contract_status=2`、`process_status=1`,每 `oms_id` 插 `action_code=2`、`agreement_status=1`,带总单/细单号。失败:起草行 `contract_status` 保持 1、`process_status` 改回 0,写 `agreement_fail_reason`,下一班还能领。`push_status=0`。人名固定 `系统管理员` |
|
||||
| 搜不到 | `build_not_found` | 表 4 `process_status` 改回 `0`;表 3 改回 `0`;不插协议行 |
|
||||
|
||||
验收:`cd rpa_rebate` → `uv run pytest tests/test_job.py::test_job_finds_contract_and_writes_agreement -q`。页面:`uv run python run_find.py`,只读 `ACCEPT SUMMARY REBATE_FIND` 或 `rpa_rebate/screenshots/REBATE_FIND/ACCEPT.txt`。总单:`uv run python run_header.py`,只读 `ACCEPT SUMMARY REBATE_HEADER`。细单新增:`uv run python run_import.py`,只读 `ACCEPT SUMMARY REBATE_IMPORT`。送审:`uv run python run_submit.py`,只读 `ACCEPT SUMMARY REBATE_SUBMIT`。接口 4:`cd rpa_oms` → `uv run pytest tests/test_contract_agreement_outbound.py::test_assembles_unpushed_rows_as_document_envelope -q`。现行字段与步骤见 `rpa_rebate/现行模式.md`、`rpa_rebate/返利流程.md`。
|
||||
|
||||
不推接口 4,不改 `push_status`,不碰选品 / 加购 / 合同页面。货源细单ID 本期不填(弹层按联系人苏文辉过滤)。计划员现阶段廖建琦。
|
||||
|
||||
## 返利中心及之后
|
||||
|
||||
查号页面已接 `REBATE_FIND`。总单页面已接 `REBATE_HEADER`。细单新增已接 `REBATE_IMPORT`(节点名仍叫 IMPORT,页面走新增,不做 Excel)。送审已接 `REBATE_SUBMIT`。班次串查号 → 总单 → 细单新增 → 送审,送审成功才写 `actionCode=2`。三把钥匙:`contractSystemNo` → `rebateOrderNo` → `receiptNoticeNo`。开页面时按节点打开 `rebate_find.py` / `rebate_header.py` / `rebate_import.py` / `rebate_submit.py`,共用登录/查号/勾行/弹层打开 `rebate_common.py`。`rebate_flow_steps.py` 仍是对外入口。验收只看 ACCEPT / 点名用例。
|
||||
|
||||
| 节点 | 谁做 | 要回传 | 页面要做什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| `REBATE_FIND` | RPA | 搜到 / 搜不到 | 已拆:登录 → 采购协议 → 按合同号搜 |
|
||||
| `REBATE_HEADER` | RPA | `rebateOrderNo` | 已拆:勾已有总单,按 `返利流程.md` 填现行字段 → 保存拿协议总单ID |
|
||||
| `REBATE_IMPORT` | RPA | 货品表行数 | 已拆:勾总单 → 货品「新增」→ 货品编码三个点按 `goodsId` 搜 → 卡片确认落到表 → 货品工具栏保存。细单备注=订单号,特定补偿=0,承付参数=25。不做 Excel |
|
||||
| `REBATE_SUBMIT` | RPA | `submitted` + 总单/细单号 | 已拆:点送审;马上能见号则带回 |
|
||||
| `M5_CREATE_ORDER` | RPA 桌面端 | `m5OrderNo` | 录单后**停、不保存**。`M5_QC_MISSING` 必须识别准(直接转人工、不重试) |
|
||||
| `M5_MANUAL_CONFIRM` | 采购 | — | 人在 M5 里保存签订 |
|
||||
| `M5_RECEIPT_NOTICE` | 采购 | `receiptNoticeNo` | 人填收货通知单号,全链终点钥匙 |
|
||||
|
||||
不要连续轮询审批;返利段开始前大约 30 分钟查一次。
|
||||
|
||||
## 验收
|
||||
|
||||
- 接口1:点名 `rpa_oms/tests/test_sales_goods_receive.py` 或 `test_oracle_repo.py`;禁止为「显得全面」跑整包再去连真库。
|
||||
- 接口2:点名 `rpa_oms/tests/test_cart_settlement_outbound.py::test_assembles_unpushed_rows_as_document_envelope`;SQL 截组 `tests/test_oracle_cart_repo.py::test_list_unpushed_keeps_whole_oms_group_when_limit_is_3`。
|
||||
- 接口3:点名 `rpa_oms/tests/test_purchase_contract_receive.py` 或 `test_oracle_contract_repo.py`。
|
||||
- 选品:点名 `rpa_selection/tests/test_pipeline.py::test_split_across_malls_is_success` 或 `test_rules.py::test_margin_exactly_4_percent_passes`。凑齐后加车点名 `rpa_selection/tests/test_job.py::test_success_adds_each_split_sku_without_rereading_table2`。
|
||||
- 加购:点名 `rpa_cart/tests/test_cart_job.py::test_job_adds_split_rows_without_insert` 或 `test_job_marks_out_of_stock`。选品交来的 sku 点名 `test_known_lines_keep_each_sku_and_flag_incomplete`。
|
||||
- 合同班:点名 `rpa_contract/tests/test_job.py::test_job_merges_same_supplier_and_writes_table4` 或 `test_job_marks_diligence_fail`。
|
||||
- 返利班:点名 `rpa_rebate/tests/test_job.py::test_job_finds_contract_and_writes_agreement` 或 `test_job_leaves_not_found_for_next_shift`。
|
||||
- 返利查号页:只读控制台 `ACCEPT SUMMARY REBATE_FIND` 或 `rpa_rebate/screenshots/REBATE_FIND/ACCEPT.txt`。
|
||||
- 返利总单页:只读控制台 `ACCEPT SUMMARY REBATE_HEADER` 或 `rpa_rebate/screenshots/REBATE_HEADER/ACCEPT.txt`;点名 `rpa_rebate/tests/test_header.py::test_header_flow_writes_accept_with_order_no`。
|
||||
- 返利细单新增:只读控制台 `ACCEPT SUMMARY REBATE_IMPORT` 或 `rpa_rebate/screenshots/REBATE_IMPORT/ACCEPT.txt`;点名 `rpa_rebate/tests/test_import.py::test_import_flow_writes_accept_with_success_fail`。
|
||||
- 返利送审:只读控制台 `ACCEPT SUMMARY REBATE_SUBMIT` 或 `rpa_rebate/screenshots/REBATE_SUBMIT/ACCEPT.txt`;点名 `rpa_rebate/tests/test_submit.py::test_submit_flow_writes_accept_with_order_nos`。
|
||||
- 接口4:点名 `rpa_oms/tests/test_contract_agreement_outbound.py::test_assembles_unpushed_rows_as_document_envelope`;SQL 截组 `tests/test_oracle_agreement_repo.py::test_list_unpushed_keeps_whole_contract_no_group_when_limit_is_3`。
|
||||
- 合同页:只读控制台 `ACCEPT` 或 `rpa_contract/screenshots/` 下 `ACCEPT.txt`;步骤门闸点名 `rpa_contract/tests/test_flow_gates.py`。
|
||||
- 单测只跑点名用例。禁止跑整份带页面等待的套件当回归。
|
||||
|
||||
## 不要
|
||||
|
||||
- 不要扫根目录,不要通读 `docs/`。需要背景就点名打开一节。
|
||||
- 不要把 Oracle 密码写进仓库、README、示例 JSON。
|
||||
- 不要在接口1 请求路径里跑选品。
|
||||
- 不要在接口3 请求路径里录合同。
|
||||
- 不要为「显得全面」多开文件、重跑、看图。落点外的改动会制造反工。
|
||||
84
Main.seq
|
|
@ -1,84 +0,0 @@
|
|||
{
|
||||
"LineList": [
|
||||
{
|
||||
"LineColor": "255, 255, 255",
|
||||
"LineCommented": 0,
|
||||
"LineEnd": {
|
||||
"Pidx": "20260813200000/EPin_Flow_In",
|
||||
"Px": "512",
|
||||
"Py": "300"
|
||||
},
|
||||
"LineNote": "",
|
||||
"LineSplit": {
|
||||
"Px": "",
|
||||
"Py": ""
|
||||
},
|
||||
"LineStart": {
|
||||
"Pidx": "20260813100000/EPin_Flow_Out",
|
||||
"Px": "457",
|
||||
"Py": "246"
|
||||
},
|
||||
"LineTag": "20260813300000",
|
||||
"LineType": 0
|
||||
}
|
||||
],
|
||||
"NodeList": [
|
||||
{
|
||||
"NodePanelData": {
|
||||
"ExpandedGroups": [],
|
||||
"InputProperty": {
|
||||
"mouse_x": 0,
|
||||
"mouse_y": 0
|
||||
},
|
||||
"PubProperty": "",
|
||||
"RetPins": []
|
||||
},
|
||||
"NodeProperty": {
|
||||
"EP_CONTROL": {
|
||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"aW1wb3J0IHN5cywgb3MsIGpzb24KZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgpfQ09ERVNfRElSID0gUGF0aChyJ2Q6XFJQQVxBdXRvUnBhXGNvZGVzJykKaWYgc3RyKF9DT0RFU19ESVIpIG5vdCBpbiBzeXMucGF0aDoKICAgIHN5cy5wYXRoLmluc2VydCgwLCBzdHIoX0NPREVTX0RJUikpCgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2NyZWF0ZV9ub2RlIGltcG9ydCAoCiAgICBDb250cmFjdENyZWF0ZUVycm9yLCBsb2FkX2lucHV0X2RhdGEsIHJ1bl9jb250cmFjdF9jcmVhdGUsCikKCmRlZiBfZmlyc3QoKnZhbHVlcyk6CiAgICBmb3IgdmFsdWUgaW4gdmFsdWVzOgogICAgICAgIGlmIHZhbHVlIGlzIG5vdCBOb25lIGFuZCBzdHIodmFsdWUpLnN0cmlwKCk6CiAgICAgICAgICAgIHJldHVybiBzdHIodmFsdWUpLnN0cmlwKCkKICAgIHJldHVybiAiIgoKcmF3ID0gKHNlbGYuaW5wdXRfYXJnIG9yICIiKS5zdHJpcCgpCmlmIG5vdCByYXc6CiAgICByYWlzZSBDb250cmFjdENyZWF0ZUVycm9yKCLnvLrlsJHkuJrliqHlhaXlj4LvvJror7fpgJrov4cgLWkvLS1pbnB1dCDkvKDlhaUgSlNPTiDlrZfnrKbkuLLmiJbmlofku7bot6/lvoQiKQoKZGF0YSA9IGxvYWRfaW5wdXRfZGF0YShyYXcpCgpzdGF0ZV9jZW50ZXJfdXJsID0gX2ZpcnN0KAogICAgZGF0YS5nZXQoInN0YXRlQ2VudGVyVXJsIiksIGRhdGEuZ2V0KCJzdGF0ZUNlbnRlclVSTCIpLAogICAgb3MuZ2V0ZW52KCJTVEFURV9DRU5URVJfVVJMIiksICJodHRwOi8vMTI3LjAuMC4xOjgwMDAiLAopCmFnZW50X3Rva2VuID0gX2ZpcnN0KAogICAgZGF0YS5nZXQoImFnZW50VG9rZW4iKSwgb3MuZ2V0ZW52KCJBR0VOVF9UT0tFTiIpLCAiZGV2LWFnZW50LXRva2VuIiwKKQoKcHJpbnQoIj0iICogNjApCnByaW50KCLlkIjlkIzlu7rljZUgQ09OVFJBQ1RfQ1JFQVRFIC0g5byA5aeLIikKcHJpbnQoImpvYklkPSVzIHNvdXJjZVR5cGU9JXMgc3VibWl0TW9kZT0lcyIgJSAoCiAgICBkYXRhLmdldCgiam9iSWQiKSwgZGF0YS5nZXQoInNvdXJjZVR5cGUiKSwgZGF0YS5nZXQoInN1Ym1pdE1vZGUiKSkpCnByaW50KCI9IiAqIDYwKQoKcmVzdWx0ID0gcnVuX2NvbnRyYWN0X2NyZWF0ZSgKICAgIGRhdGEsCiAgICBzdGF0ZV9jZW50ZXJfdXJsPXN0YXRlX2NlbnRlcl91cmwsCiAgICBhZ2VudF90b2tlbj1hZ2VudF90b2tlbiwKKQpwcmludChqc29uLmR1bXBzKHJlc3VsdCwgZW5zdXJlX2FzY2lpPUZhbHNlKSkK\"\n}"
|
||||
},
|
||||
"EP_Gen": {
|
||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||
"EP_Gen_BEFOREDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||
"EP_Gen_NOTE": "{\n \"val\": \"6K+75Y+W5YWl5Y+C5bm25omn6KGMIENPTlRSQUNUX0NSRUFURe+8iOWQq+eKtuaAgeS4reW/g+Wbnuiwg++8iQ==\"\n}"
|
||||
}
|
||||
},
|
||||
"NodeUI": {
|
||||
"NodeElementID": "",
|
||||
"NodeModel": "2",
|
||||
"NodePropErr": false,
|
||||
"NodePx": "515",
|
||||
"NodePy": "270",
|
||||
"NodeSubType": "EnumWidget_Code_Custom",
|
||||
"NodeTag": "20260813200000",
|
||||
"NodeTitle": "读取入参并执行合同建单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"NodePanelData": {
|
||||
"ExpandedGroups": [],
|
||||
"InputProperty": {
|
||||
"mouse_x": 0,
|
||||
"mouse_y": 0
|
||||
},
|
||||
"PubProperty": "",
|
||||
"RetPins": []
|
||||
},
|
||||
"NodeProperty": "",
|
||||
"NodeUI": {
|
||||
"NodeElementID": "",
|
||||
"NodeModel": "2",
|
||||
"NodePropErr": false,
|
||||
"NodePx": "330",
|
||||
"NodePy": "230",
|
||||
"NodeSubType": "EnumWidget_Begin",
|
||||
"NodeTag": "20260813100000",
|
||||
"NodeTitle": "开始"
|
||||
}
|
||||
}
|
||||
],
|
||||
"SeqTag": "Main",
|
||||
"ViewCenterPx": "600",
|
||||
"ViewCenterPy": "300",
|
||||
"ViewScaling": "1"
|
||||
}
|
||||
46
Project.pro
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"GlobalVar": [
|
||||
],
|
||||
"OpenFlow": "[\n {\n \"current-tab\": \"true\",\n \"seq\": \"Main\",\n \"tag\": \"2026081210133309518\",\n \"title\": \"Main\",\n \"type\": \"bp\"\n }\n]\n",
|
||||
"ProInfo": {
|
||||
"CodeCustomCount": "0",
|
||||
"ProAbsolutePath": "D:/RPA/AutoRpa",
|
||||
"ProChange": "2026/08/13 14:24:41",
|
||||
"ProColorRef": "4",
|
||||
"ProCreate": "2026/08/12 10:13:31",
|
||||
"ProDesc": "",
|
||||
"ProDocVersion": "1.0.0.4",
|
||||
"ProImports": "",
|
||||
"ProIsAdminRun": "0",
|
||||
"ProIsNewReturn": "1",
|
||||
"ProName": "AutoRpa",
|
||||
"ProStudio": "2026.1.0.36",
|
||||
"ProTag": "2026081210133159316",
|
||||
"ProTheme": "light",
|
||||
"ProUIADeepMatch": "1",
|
||||
"ProUserID": "34BFF76918A9449ABAB323149DDCEA73",
|
||||
"ProUserName": "Administrator",
|
||||
"ProVersion": "1.0.0.0",
|
||||
"operatingSystem": 0,
|
||||
"releaseUserName": "16624637724"
|
||||
},
|
||||
"SeqGroupList": [
|
||||
],
|
||||
"SeqPythonModuleList": [
|
||||
],
|
||||
"SeqTextFileList": [
|
||||
],
|
||||
"SequenceList": [
|
||||
{
|
||||
"sdc": "",
|
||||
"seq": "Main",
|
||||
"seqptag": "",
|
||||
"seqret": "1",
|
||||
"seqtag": "2026081210133309518",
|
||||
"spr": [
|
||||
],
|
||||
"spv": [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# AutoRpa
|
||||
|
||||
福建洁达采购 RPA。仓库里叠过两套产品,以本文「现行」为准。
|
||||
|
||||
## 现行工作
|
||||
|
||||
订单中心四接口 + 四条独立作业。对码在订单中心做完,本方收件、选品、录合同/返利、回写结果。
|
||||
|
||||
| 口 | 方向 | 底表 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 订单中心 → 本方 | `rpa_sales_goods_mapping_data` | `POST /api/oms/v1/sales-goods` |
|
||||
| 2 | 本方 → 订单中心 | `rpa_cart_settlement_goods_data` | 取 `push_status=0` 组装报文;路径仍空,不发 HTTP |
|
||||
| 3 | 订单中心 → 本方 | `rpa_purchase_contract_data` | `POST /api/oms/v1/purchase-contracts` |
|
||||
| 4 | 本方 → 订单中心 | `rpa_contract_agreement_result` | 取 `push_status=0` 组装报文;路径仍空,不发 HTTP |
|
||||
|
||||
作业互不串联:选品读表 1,合同/返利读表 3。HTTP 收件不同步跑 RPA。
|
||||
|
||||
## 代码入口
|
||||
|
||||
| 目录 | 身份 |
|
||||
| --- | --- |
|
||||
| `rpa_oms/` | 四个 HTTP 口,一个 FastAPI。接口 1、3 收件;接口 2、4 组装报文 |
|
||||
| `rpa_selection/` | 选品班。读表 1 `deal_flag=0`,双平台选品,写表 2 |
|
||||
| `rpa_cart/` | 加购已接;1 号药城支付落表。表 2 → 口 2 仍未推 |
|
||||
| `rpa_contract/` | 合同班。表 3 合单录入 → 表 4;接口 4 组包在 oms,path 空 |
|
||||
| `rpa_rebate/` | 返利班。表 4 查号 / 表 3 只录协议 → 表 4;查号 → 总单 → 细单新增 → 送审同一班 |
|
||||
|
||||
| 包 | 读 | 写 | 口 |
|
||||
| --- | --- | --- | --- |
|
||||
| `rpa_selection` | 表 1 `deal_flag=0` | 表 2 `operate_type=1`,表 1 处理标志 | 不直接调口;口 2 由 `rpa_cart` 推 |
|
||||
| `rpa_cart` | 表 2 | 表 2 `push_status`;支付行 | 口 2 |
|
||||
| `rpa_contract` | 表 3 `process_status=0` | 表 4 `actionCode=1` | 口 3 收件在 oms;口 4 回写 |
|
||||
| `rpa_rebate` | 表 4 起草行 / 表 3 只录协议行 | 表 4 `contractStatus=2`、`actionCode=2` | 口 4(组包在 oms,path 空) |
|
||||
|
||||
合同怎么录、合单和回写口径见 `docs/合同系统录入流程.md`。页面控件以 `rpa_contract/contract_flow_steps.py` 为准。
|
||||
|
||||
新接口只进 `rpa_oms/`。起服务见 [`rpa_oms/README.md`](rpa_oms/README.md)。
|
||||
|
||||
## 本期未做
|
||||
|
||||
| 项 | 口径 |
|
||||
| --- | --- |
|
||||
| 返利货源细单ID | 弹层按总单联系人(现苏文辉)过滤;本期不填,见 `rpa_rebate/返利流程.md` |
|
||||
| 返利计划员 | 现阶段「廖建琦」,后期改回表 3 `planner_name` |
|
||||
| 口 2 / 口 4 HTTP | path 空,只组包不发 |
|
||||
| 药帮忙支付 | 只做了 1 号药城 |
|
||||
| M5 | 录单停、不保存;收货通知单号人填 |
|
||||
|
||||
大批量现场还缺调度、崩溃扫戳、清车、合同生产地址,见 [`docs/大批量跑缺口.md`](docs/大批量跑缺口.md)。
|
||||
|
||||
## 文档入口
|
||||
|
||||
现行口径和表:[`docs/README.md`](docs/README.md)。
|
||||
|
||||
开发对照只看:
|
||||
|
||||
- `docs/RPA订单中心接口开发口径_20260930.md`
|
||||
- `docs/ddl/rpa_oms_staging_tables.sql`
|
||||
|
||||
一期主链需求/设计在 `docs/archive/`;`docs/superpowers/specs/` 里 9 月 12–13 日的四流程稿也是归档。作业语义以口径为准。
|
||||
145
build_seq.py
|
|
@ -1,145 +0,0 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
构建 Main.seq - 合同建单流程(输入驱动 + 状态中心回调版本)
|
||||
|
||||
说明:
|
||||
Main.seq 是 iS-RPA Studio 的流程源文件,Studio 编译时据此生成 codes/Main.py。
|
||||
请在关闭 Studio 的情况下运行本脚本生成 Main.seq,再重新打开项目编译。
|
||||
"""
|
||||
import json
|
||||
import base64
|
||||
|
||||
def b64_apa(val):
|
||||
return json.dumps({"val": base64.b64encode(str(val).encode()).decode()}, indent=4)
|
||||
|
||||
def b64_raw(val):
|
||||
return base64.b64encode(str(val).encode()).decode()
|
||||
|
||||
# 单节点代码块:从 input_arg 读取业务 JSON → 调用 run_contract_create。
|
||||
# run_contract_create 内部完成 start/complete/fail 回调、Selenium 起草与合同编号读取。
|
||||
body = """\
|
||||
import sys, os, json
|
||||
from pathlib import Path
|
||||
|
||||
_CODES_DIR = Path(r'd:\\RPA\\AutoRpa\\codes')
|
||||
if str(_CODES_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_CODES_DIR))
|
||||
|
||||
from rpa_yisaiqi.contract_create_node import (
|
||||
ContractCreateError, load_input_data, run_contract_create,
|
||||
)
|
||||
|
||||
def _first(*values):
|
||||
for value in values:
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
return ""
|
||||
|
||||
raw = (self.input_arg or "").strip()
|
||||
if not raw:
|
||||
raise ContractCreateError("缺少业务入参:请通过 -i/--input 传入 JSON 字符串或文件路径")
|
||||
|
||||
data = load_input_data(raw)
|
||||
|
||||
state_center_url = _first(
|
||||
data.get("stateCenterUrl"), data.get("stateCenterURL"),
|
||||
os.getenv("STATE_CENTER_URL"), "http://127.0.0.1:8000",
|
||||
)
|
||||
agent_token = _first(
|
||||
data.get("agentToken"), os.getenv("AGENT_TOKEN"), "dev-agent-token",
|
||||
)
|
||||
|
||||
print("=" * 60)
|
||||
print("合同建单 CONTRACT_CREATE - 开始")
|
||||
print("jobId=%s sourceType=%s submitMode=%s" % (
|
||||
data.get("jobId"), data.get("sourceType"), data.get("submitMode")))
|
||||
print("=" * 60)
|
||||
|
||||
result = run_contract_create(
|
||||
data,
|
||||
state_center_url=state_center_url,
|
||||
agent_token=agent_token,
|
||||
)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
"""
|
||||
|
||||
begin_tag = "20260813100000"
|
||||
code_tag = "20260813200000"
|
||||
line_tag = "20260813300000"
|
||||
|
||||
main_seq = {
|
||||
"LineList": [
|
||||
{
|
||||
"LineColor": "255, 255, 255",
|
||||
"LineCommented": 0,
|
||||
"LineEnd": {"Pidx": f"{code_tag}/EPin_Flow_In", "Px": "512", "Py": "300"},
|
||||
"LineNote": "",
|
||||
"LineSplit": {"Px": "", "Py": ""},
|
||||
"LineStart": {"Pidx": f"{begin_tag}/EPin_Flow_Out", "Px": "457", "Py": "246"},
|
||||
"LineTag": line_tag,
|
||||
"LineType": 0,
|
||||
}
|
||||
],
|
||||
"NodeList": [
|
||||
{
|
||||
"NodePanelData": {
|
||||
"ExpandedGroups": [],
|
||||
"InputProperty": {"mouse_x": 0, "mouse_y": 0},
|
||||
"PubProperty": "",
|
||||
"RetPins": [],
|
||||
},
|
||||
"NodeProperty": {
|
||||
"EP_CONTROL": {
|
||||
"EP_CONTROL_IMPORT": json.dumps(
|
||||
{"PImport": b64_raw(""), "PBCode": b64_raw(body)},
|
||||
indent=4,
|
||||
)
|
||||
},
|
||||
"EP_Gen": {
|
||||
"EP_Gen_AFTERDELAY": b64_apa("0"),
|
||||
"EP_Gen_BEFOREDELAY": b64_apa("0"),
|
||||
"EP_Gen_NOTE": b64_apa("读取入参并执行 CONTRACT_CREATE(含状态中心回调)"),
|
||||
},
|
||||
},
|
||||
"NodeUI": {
|
||||
"NodeElementID": "",
|
||||
"NodeModel": "2",
|
||||
"NodePropErr": False,
|
||||
"NodePx": "515",
|
||||
"NodePy": "270",
|
||||
"NodeSubType": "EnumWidget_Code_Custom",
|
||||
"NodeTag": code_tag,
|
||||
"NodeTitle": "读取入参并执行合同建单",
|
||||
},
|
||||
},
|
||||
{
|
||||
"NodePanelData": {
|
||||
"ExpandedGroups": [],
|
||||
"InputProperty": {"mouse_x": 0, "mouse_y": 0},
|
||||
"PubProperty": "",
|
||||
"RetPins": [],
|
||||
},
|
||||
"NodeProperty": "",
|
||||
"NodeUI": {
|
||||
"NodeElementID": "",
|
||||
"NodeModel": "2",
|
||||
"NodePropErr": False,
|
||||
"NodePx": "330",
|
||||
"NodePy": "230",
|
||||
"NodeSubType": "EnumWidget_Begin",
|
||||
"NodeTag": begin_tag,
|
||||
"NodeTitle": "开始",
|
||||
},
|
||||
},
|
||||
],
|
||||
"SeqTag": "Main",
|
||||
"ViewCenterPx": "600",
|
||||
"ViewCenterPy": "300",
|
||||
"ViewScaling": "1",
|
||||
}
|
||||
|
||||
with open(r"d:\RPA\AutoRpa\Main.seq", "w", encoding="utf-8") as f:
|
||||
json.dump(main_seq, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print("Main.seq 已生成(单节点输入驱动流程)")
|
||||
print("节点: 开始 -> 读取入参并执行合同建单")
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# coding=utf-8
|
||||
# 编译日期:2026-08-12 10:13:31
|
||||
# 版权所有:www.i-search.com.cn
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 测试运行或调试以上全局函数入口
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
121
codes/Main.py
|
|
@ -1,121 +0,0 @@
|
|||
# coding=utf-8
|
||||
# 编译日期:2026-08-13 14:30:00
|
||||
# 版权所有:www.i-search.com.cn
|
||||
import ubpa.init_input as iinput
|
||||
from ubpa.base_util import StdOutHook, ExceptionHandler
|
||||
import time
|
||||
import pdb
|
||||
from ubpa.ilog import ILog
|
||||
import getopt
|
||||
from sys import argv
|
||||
import sys
|
||||
import os
|
||||
import datetime as is_datetime
|
||||
import pandas
|
||||
from ubpa.base_img import *
|
||||
import ubpa.iplatform as iplatform
|
||||
|
||||
class AutoRpa:
|
||||
|
||||
def __init__(self,**kwargs):
|
||||
self.__logger = ILog(__file__)
|
||||
self.path = set_img_res_path(__file__)
|
||||
self.robot_no = ''
|
||||
self.proc_no = ''
|
||||
self.job_no = ''
|
||||
self.input_arg = ''
|
||||
if('robot_no' in kwargs.keys()):
|
||||
self.robot_no = kwargs['robot_no']
|
||||
if('proc_no' in kwargs.keys()):
|
||||
self.proc_no = kwargs['proc_no']
|
||||
if('job_no' in kwargs.keys()):
|
||||
self.job_no = kwargs['job_no']
|
||||
ILog.JOB_NO, ILog.OLD_STDOUT = self.job_no, sys.stdout
|
||||
sys.stdout = StdOutHook(self.job_no, sys.stdout)
|
||||
ExceptionHandler.JOB_NO, ExceptionHandler.OLD_STDERR = self.job_no, sys.stderr
|
||||
sys.excepthook = ExceptionHandler.handle_exception
|
||||
if('input_arg' in kwargs.keys()):
|
||||
self.input_arg = kwargs['input_arg']
|
||||
if(len(self.input_arg) <= 0):
|
||||
self.input_arg = iinput.load_init(__file__)
|
||||
if self.input_arg is None:
|
||||
sys.exit(0)
|
||||
|
||||
def Main(self):
|
||||
import sys, os, json
|
||||
from pathlib import Path
|
||||
|
||||
_CODES_DIR = Path(r'd:\RPA\AutoRpa\codes')
|
||||
if str(_CODES_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_CODES_DIR))
|
||||
|
||||
from rpa_yisaiqi.contract_create_node import (
|
||||
ContractCreateError, load_input_data, run_contract_create,
|
||||
)
|
||||
|
||||
def _first(*values):
|
||||
for value in values:
|
||||
if value is not None and str(value).strip():
|
||||
return str(value).strip()
|
||||
return ""
|
||||
|
||||
raw = (self.input_arg or "").strip()
|
||||
if not raw:
|
||||
raise ContractCreateError("缺少业务入参:请通过 -i/--input 传入 JSON 字符串或文件路径")
|
||||
|
||||
data = load_input_data(raw)
|
||||
|
||||
state_center_url = _first(
|
||||
data.get("stateCenterUrl"), data.get("stateCenterURL"),
|
||||
os.getenv("STATE_CENTER_URL"), "http://127.0.0.1:8000",
|
||||
)
|
||||
agent_token = _first(
|
||||
data.get("agentToken"), os.getenv("AGENT_TOKEN"), "dev-agent-token",
|
||||
)
|
||||
|
||||
print("=" * 60)
|
||||
print("合同建单 CONTRACT_CREATE - 开始")
|
||||
print("jobId=%s sourceType=%s submitMode=%s" % (
|
||||
data.get("jobId"), data.get("sourceType"), data.get("submitMode")))
|
||||
print("=" * 60)
|
||||
|
||||
result = run_contract_create(
|
||||
data,
|
||||
state_center_url=state_center_url,
|
||||
agent_token=agent_token,
|
||||
)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ILog.begin_init()
|
||||
robot_no = ''
|
||||
proc_no = ''
|
||||
job_no = ''
|
||||
input_arg = ''
|
||||
params = {}
|
||||
fn = "Main"
|
||||
flow_params={'Main':[]}
|
||||
try:
|
||||
argv = sys.argv[1:]
|
||||
opts, args = getopt.getopt(argv,"hr:p:j:i:c:",["robot = ","proc = ","job = ","input = ","params = "])
|
||||
except getopt.GetoptError:
|
||||
print ('robot.py -r <robot> -p <proc> -j <job>')
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print ('robot.py -r <robot> -p <proc> -j <job>')
|
||||
elif opt in ("-r", "--robot"):
|
||||
robot_no = arg
|
||||
elif opt in ("-p", "--proc"):
|
||||
proc_no = arg
|
||||
elif opt in ("-j", "--job"):
|
||||
job_no = arg
|
||||
elif opt in ("-i", "--input"):
|
||||
input_arg = arg
|
||||
elif opt in ("-c", "--params"):
|
||||
cParam: dict = iinput.parseConnectorParam(arg)
|
||||
params = cParam.pop("Params", params)
|
||||
fn = cParam.pop("FlowName", fn)
|
||||
pro = AutoRpa(robot_no=robot_no,proc_no=proc_no,job_no=job_no,input_arg=input_arg)
|
||||
iplatform.Return(MainResult=getattr(pro, fn)(**params), job_no=job_no, fn=fn, flow_return_params=flow_params.get(fn,[]))
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# coding: utf8
|
||||
"""
|
||||
Python模块脚本
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# 项目路径、资源库元素文件(建议其他元素库文件路径在此处声明)
|
||||
ProjectPath = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
CodePath = os.path.join(ProjectPath, "codes")
|
||||
ModulePath = os.path.join(CodePath, "modules")
|
||||
ElementFile = os.path.join(ModulePath, "resources", "elements.json")
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
|
||||
# 更多示例代码 http://support.i-search.com.cn:8088/showdoc/web/#/page/11923
|
||||
|
||||
## 导入资源库
|
||||
|
||||
'''
|
||||
from iresource import iSResource
|
||||
|
||||
is_resource: iSResource = iSResource(__file__)
|
||||
|
||||
或者手动指定其他工程元素库文件(当前项目或其他项目)
|
||||
|
||||
ElementFile = r"~/NewProject1/codes/modules/resources/elements.json"
|
||||
is_resource: iSResource = iSResource(json_file=ElementFile)
|
||||
'''
|
||||
|
||||
|
||||
## 全部资源
|
||||
|
||||
'''
|
||||
all_resource = is_resource.all()
|
||||
'''
|
||||
|
||||
|
||||
## 获取全局变量、流程变量(参数)
|
||||
|
||||
'''
|
||||
gv1 = is_resource.get_global_var("gv_1")
|
||||
lv1 = is_resource.get_flow_var("lv_1")
|
||||
'''
|
||||
|
||||
|
||||
## 设置全局变量、流程变量(参数)
|
||||
|
||||
'''
|
||||
gv1 = is_resource.set_global_var("gv_1", "gv_1新值")
|
||||
lv1 = is_resource.set_flow_var("lv_1", "lv_1新值")
|
||||
'''
|
||||
|
||||
|
||||
## 获取元素对象(这段代码可从【元素库】中右击元素,点击【复制代码】直接获得)
|
||||
|
||||
'''
|
||||
element1 = is_resource.get_element("元素ID")
|
||||
element1.show() # 打印显示元素信息
|
||||
all_element = is_resource.elements() # 全部元素
|
||||
'''
|
||||
|
||||
|
||||
## 支持 By.ID,By.CODE,By.NAME 获取元素对象
|
||||
|
||||
'''
|
||||
is_resource.get_element(By.ID, "元素ID")
|
||||
is_resource.get_element(By.CODE, "元素Code")
|
||||
is_resource.get_element(By.NAME, "元素Name")
|
||||
|
||||
|
||||
## 也支持具体方法获取元素对象
|
||||
|
||||
'''
|
||||
is_resource.get_element_by_id("元素id")
|
||||
is_resource.get_element_by_id("元素Code")
|
||||
is_resource.get_element_by_id("元素Name")
|
||||
'''
|
||||
|
||||
|
||||
## 元素对象的 方法 和 属性
|
||||
|
||||
'''
|
||||
element1.actions() # 所有方法
|
||||
element1.properties() # 所有属性
|
||||
element1.path # 元素路径
|
||||
element1.snapshot # 元素快照
|
||||
element1.help("click") # 查看方法使用文档
|
||||
element1.click(wait_seconds=5)
|
||||
is_resource.set_action_wait_seconds(5) # 全局调整动作方法执行的等待时间秒数
|
||||
element1.set_text(value="测试一下")
|
||||
'''
|
||||
|
||||
## 对象化使用
|
||||
|
||||
'''
|
||||
# 使用对象库,通过iSObject对象可以调用子对象(如:键盘、窗口、Excel等)
|
||||
iSObject.Keyboard(element1.window_title).keypress_input("abc") # 模拟按键
|
||||
iSObject.Server().set_var("abc", "123") # 设置服务器变量
|
||||
'''
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"version": "2.0"
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
# 艺赛旗 CONTRACT_CREATE 联调骨架
|
||||
|
||||
这个目录放艺赛旗侧可复制到 Windows 虚拟机的代码。第一版目标是先验证:
|
||||
|
||||
1. Windows 虚拟机能访问状态中心。
|
||||
2. `CONTRACT_CREATE` 能按约定调用 `start`。
|
||||
3. 合同系统动作完成后能调用 `complete`,并带 `businessRefs.contractSystemNo`。
|
||||
4. 登录失败、页面改版、合同禁用等异常能调用 `fail`。
|
||||
|
||||
## 合同节点步骤点
|
||||
|
||||
来自 `state_center/给艺赛旗的开发交接.md`:
|
||||
|
||||
1. 打开合同测试系统:`http://contract-test.gzmpc.com:8080/`
|
||||
2. 登录:`test002 / 1`
|
||||
3. 左侧菜单进入 `合同管理 > 合同起草`
|
||||
4. 在最近使用模板中选择 `templateName`,非格式默认 `非格式--采购协议`
|
||||
5. 填签约方信息:客户编码、客户名称
|
||||
6. 填基础信息:合同版本类型、合同类别、结算类型、合同背景概述、合同性质
|
||||
7. 填金额和日期,合同签订日期和合同开始日期必须等于订单日期
|
||||
8. 填货品明细
|
||||
9. 填付款信息与特殊标志:是否重大合同、是否预付款、归属年份、付款方式、付款期限、合同状态、是否包含血液制品
|
||||
10. 医药城 `YYC`:同供应商同日 PDF 先合并成一个再上传
|
||||
11. 小药药 `XYY`:格式合同,跳过合同证文上传
|
||||
12. 提交并读取合同编号
|
||||
13. 调状态中心 `complete`,必传 `businessRefs.contractSystemNo`
|
||||
|
||||
## 先做联通验证
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```powershell
|
||||
python -m rpa_yisaiqi.contract_create_node `
|
||||
--state-center-url http://127.0.0.1:8000 `
|
||||
--token dev-agent-token `
|
||||
--job-id YYC-联调测试供应商-20260811-a6e5df `
|
||||
--supplier-name 联调测试供应商 `
|
||||
--order-date 2026-08-11 `
|
||||
--source-type YYC `
|
||||
--contract-system-no HT-20260811-001
|
||||
```
|
||||
|
||||
这条只验证回调链路,合同号由命令行传入。
|
||||
|
||||
## 真实合同起草运行
|
||||
|
||||
先准备合同起草输入 JSON,参考:
|
||||
|
||||
- `examples/contract_draft_yyc.json`
|
||||
- `examples/contract_draft_xyy.json`
|
||||
|
||||
YYC 有多份平台 PDF 时需要合并,先安装:
|
||||
|
||||
```powershell
|
||||
pip install -r rpa_yisaiqi/requirements.txt
|
||||
```
|
||||
|
||||
然后运行:
|
||||
|
||||
```powershell
|
||||
python -m rpa_yisaiqi.contract_create_node `
|
||||
--state-center-url http://192.168.x.x:8000 `
|
||||
--token dev-agent-token `
|
||||
--job-id <状态中心job_id> `
|
||||
--supplier-name 重庆亿昊药业 `
|
||||
--order-date 2026-08-11 `
|
||||
--source-type YYC `
|
||||
--draft-path rpa_yisaiqi/examples/contract_draft_yyc.json `
|
||||
--merged-pdf-output C:/rpa/jobs/<状态中心job_id>/contract-merged.pdf
|
||||
```
|
||||
|
||||
这条会走真实起草流程:
|
||||
|
||||
1. 状态中心 `/start`
|
||||
2. 打开合同系统并登录
|
||||
3. 进入 `合同管理 > 合同起草`
|
||||
4. 选择最近使用模板
|
||||
5. 填签约方、基础信息、金额日期、货品明细、付款和特殊标志
|
||||
6. YYC 合并并上传 PDF,XYY 跳过上传
|
||||
7. 提交
|
||||
8. 读取合同编号
|
||||
9. 状态中心 `/complete`
|
||||
|
||||
### 先做 dry-run 预览
|
||||
|
||||
如果你只想先验证页面,不想真的提交,把输入里的 `submitMode` 设成 `dry_run`。这时流程会跑到提交前并输出 `contract-dry-run-ready` 截图,不会点提交,也不会读取真实合同号。
|
||||
|
||||
适合先校准 selector 的情况:
|
||||
|
||||
1. 进入 `合同管理 > 合同起草`
|
||||
2. 选择最近使用模板
|
||||
3. 填签约方、基础信息、金额日期、明细、付款和特殊标志
|
||||
4. 停在提交前
|
||||
|
||||
如果状态中心部署在 macOS 主机,`--state-center-url` 换成 Windows 虚拟机能访问到的地址,例如:
|
||||
|
||||
```powershell
|
||||
--state-center-url http://192.168.x.x:8000
|
||||
```
|
||||
|
||||
## 接入真实页面自动化
|
||||
|
||||
真实艺赛旗页面操作按流程图拆在:
|
||||
|
||||
- `flows/contract_create_flow.json`:流程图节点清单
|
||||
- `contract_flow_steps.py`:每个流程图节点对应的动作函数
|
||||
- `yisaiqi_adapter_template.py`:艺赛旗浏览器活动适配模板
|
||||
|
||||
艺赛旗设计器里建议按这个顺序拖流程图:
|
||||
|
||||
1. `S01_CALLBACK_START`:HTTP 请求状态中心 `/start`
|
||||
2. `S02_OPEN_CONTRACT_SYSTEM`:打开合同系统
|
||||
3. `S03_LOGIN`:输入 `j_username`、`j_password`,点击 `btn_submit`
|
||||
4. `S04_ENTER_CONTRACT_DRAFT`:进入 `合同管理 > 合同起草`
|
||||
5. `S05_SELECT_RECENT_TEMPLATE`:选择最近使用模板
|
||||
6. `S06_FILL_SIGNATORY_INFO`:填客户编码、客户名称
|
||||
7. `S07_FILL_CONTRACT_BASIC_FORM`:填合同类别、结算类型、背景概述、合同性质
|
||||
8. `S08_FILL_AMOUNT_AND_DATES`:填金额、签订日期、开始日期、结束日期
|
||||
9. `S09_FILL_GOODS_LINES`:循环填货品明细
|
||||
10. `S10_FILL_PAYMENT_AND_FLAGS`:填付款方式、付款期限、合同状态、是否包含血液制品等
|
||||
11. `S11_MERGE_PDF_IF_YYC`:`sourceType=YYC` 时先合并同供应商同日 PDF;`XYY` 跳过
|
||||
12. `S12_UPLOAD_PDF_IF_YYC`:`sourceType=YYC` 时上传合并 PDF;`XYY` 跳过
|
||||
13. `S13_SUBMIT`:提交
|
||||
14. `S14_READ_CONTRACT_NO`:读取合同编号到变量 `contractSystemNo`
|
||||
15. `S15_CALLBACK_COMPLETE`:HTTP 请求状态中心 `/complete`
|
||||
|
||||
如果只做预览,把这条流程里的 `submitMode` 设成 `dry_run`,或者直接导入 `flows/contract_create_preview_flow.json`。
|
||||
|
||||
如果艺赛旗支持 Python 脚本块,直接把每个 `contract_flow_steps.py` 函数挂到对应流程图节点。
|
||||
如果艺赛旗只支持原生活动,就按 `flows/contract_create_flow.json` 的节点顺序拖活动,用 `yisaiqi_adapter_template.py` 里的方法名做映射。
|
||||
|
||||
外层回调骨架仍然在 `contract_create_node.py`:
|
||||
|
||||
```python
|
||||
create_contract_in_contract_system(spec)
|
||||
```
|
||||
|
||||
这个函数最后返回合同编号字符串即可。状态中心回调、幂等键、错误码映射已经封在外层。后续接真页面时,可以在这里调用艺赛旗流程图,或让流程图最后把合同编号回传给这个函数。
|
||||
|
||||
## 异常映射
|
||||
|
||||
| 场景 | 异常类 | 回调错误码 | retryable | routeRole |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 登录失败 | `LoginFailed` | `SYS_LOGIN_FAIL` | true | OPS |
|
||||
| 供应商合同被禁/失效 | `SupplierContractDisabled` | `BIZ_CONTRACT_DISABLED` | false | PURCHASER |
|
||||
| PDF 合并能力不支持 | `PdfMergeUnsupported` | `CAP_PDF_MERGE_UNSUPPORTED` | false | OPS |
|
||||
| 页面改版/元素找不到 | `ContractCreateError` | `UI_ELEMENT_NOT_FOUND` | false | OPS |
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
"""YiSaiQi RPA integration helpers."""
|
||||
|
||||
|
|
@ -1,343 +0,0 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
APA (艺赛旗) Selenium Chrome BrowserAdapter 实现
|
||||
通过 Selenium WebDriver 直接操控 Chrome,不依赖 APA 浏览器模块
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class ApaBrowserAdapter:
|
||||
"""Selenium Chrome 浏览器适配器,实现 BrowserAdapter 协议"""
|
||||
|
||||
def __init__(self, url: str = ""):
|
||||
self._url = url
|
||||
self._driver = None
|
||||
self._is_open = False
|
||||
|
||||
def _get_driver(self):
|
||||
"""创建或获取 Chrome WebDriver"""
|
||||
if self._driver is not None:
|
||||
return self._driver
|
||||
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from webdriver_manager.chrome import ChromeDriverManager
|
||||
|
||||
options = Options()
|
||||
# 不自动关闭浏览器,方便调试
|
||||
options.add_experimental_option("detach", True)
|
||||
# 避免被检测为自动化
|
||||
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
options.add_experimental_option("useAutomationExtension", False)
|
||||
# 基本参数
|
||||
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
options.add_argument("--no-first-run")
|
||||
options.add_argument("--no-default-browser-check")
|
||||
|
||||
service = Service(ChromeDriverManager().install())
|
||||
self._driver = webdriver.Chrome(service=service, options=options)
|
||||
return self._driver
|
||||
|
||||
def _ensure_open(self):
|
||||
if self._driver is None:
|
||||
driver = self._get_driver()
|
||||
if self._url:
|
||||
driver.get(self._url)
|
||||
self._is_open = True
|
||||
|
||||
# ---- 内部查找方法 ----
|
||||
|
||||
def _find(self, selector: str, timeout: int = 5):
|
||||
"""
|
||||
智能查找元素:支持 CSS、XPath、text=、label=、aria=、title=、:contains()
|
||||
"""
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
import re
|
||||
|
||||
# 1. text=xxx → 查找包含对应文本的元素
|
||||
if selector.startswith("text="):
|
||||
text_val = selector[5:].strip('"').strip("'")
|
||||
xpath = f'//*[contains(text(),"{text_val}")]'
|
||||
return self._driver.find_element(By.XPATH, xpath)
|
||||
|
||||
# 2. label=xxx → 找到对应标签的输入框
|
||||
if selector.startswith("label="):
|
||||
text_val = selector[6:].strip('"').strip("'")
|
||||
xpath = f'//label[contains(text(),"{text_val}")]/following-sibling::*[1][self::input or self::select or self::textarea]'
|
||||
try:
|
||||
return self._driver.find_element(By.XPATH, xpath)
|
||||
except NoSuchElementException:
|
||||
# Fallback: find the label's "for" attribute
|
||||
try:
|
||||
for_id = self._driver.find_element(
|
||||
By.XPATH, f'//label[contains(text(),"{text_val}")]'
|
||||
).get_attribute("for")
|
||||
if for_id:
|
||||
return self._driver.find_element(By.ID, for_id)
|
||||
except NoSuchElementException:
|
||||
pass
|
||||
raise
|
||||
|
||||
# 3. aria=xxx → aria-label 属性
|
||||
if selector.startswith("aria="):
|
||||
text_val = selector[5:].strip('"').strip("'")
|
||||
return self._driver.find_element(By.CSS_SELECTOR, f'[aria-label="{text_val}"]')
|
||||
|
||||
# 4. title=xxx → title 属性
|
||||
if selector.startswith("title="):
|
||||
text_val = selector[6:].strip('"').strip("'")
|
||||
return self._driver.find_element(By.CSS_SELECTOR, f'[title="{text_val}"]')
|
||||
|
||||
# 5. :contains() → 转 XPath(支持 button:contains(文本) / a:contains("文本"))
|
||||
if ":contains(" in selector:
|
||||
m = re.match(r'(.+?):contains\(["\']?(.+?)["\']?\)', selector)
|
||||
if m:
|
||||
tag_part, text_val = m.groups()
|
||||
xpath = f'//{tag_part}[contains(.,"{text_val}")]'
|
||||
return self._driver.find_element(By.XPATH, xpath)
|
||||
|
||||
# 6. 显式 XPath(以 / 或 // 开头)优先按 XPath 查找
|
||||
if selector.startswith("//") or selector.startswith("/"):
|
||||
return self._driver.find_element(By.XPATH, selector)
|
||||
|
||||
# 7. 标准 CSS selector
|
||||
return self._driver.find_element(By.CSS_SELECTOR, selector)
|
||||
|
||||
# ---- BrowserAdapter 协议实现 ----
|
||||
|
||||
def open_url(self, url: str) -> None:
|
||||
self._url = url
|
||||
self._get_driver().get(url)
|
||||
self._is_open = True
|
||||
|
||||
def _set_value_via_js(self, el, value: str) -> None:
|
||||
"""通过原生 value setter + 事件注入设置值,兼容 LUI/React 受控组件"""
|
||||
self._driver.execute_script(
|
||||
"""
|
||||
var el = arguments[0];
|
||||
var value = arguments[1];
|
||||
var proto = el.tagName === 'TEXTAREA'
|
||||
? window.HTMLTextAreaElement.prototype
|
||||
: window.HTMLInputElement.prototype;
|
||||
var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
|
||||
setter.call(el, value);
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||
""",
|
||||
el,
|
||||
str(value),
|
||||
)
|
||||
|
||||
def fill_first(self, selectors: list, value: str) -> None:
|
||||
self._ensure_open()
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
# 先尝试原生输入;LUI 受控组件用 JS 注入兜底
|
||||
try:
|
||||
el.clear()
|
||||
el.send_keys(str(value))
|
||||
except Exception:
|
||||
pass
|
||||
if el.get_attribute("value") != str(value):
|
||||
self._set_value_via_js(el, str(value))
|
||||
time.sleep(0.3)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"fill_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def click_first(self, selectors: list) -> None:
|
||||
self._ensure_open()
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
# LUI/委托事件用 JS 点击最可靠
|
||||
self._driver.execute_script("arguments[0].click()", el)
|
||||
time.sleep(0.5)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"click_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def double_click_first(self, selectors: list) -> None:
|
||||
self._ensure_open()
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
ActionChains(self._driver).double_click(el).perform()
|
||||
time.sleep(0.5)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"double_click_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None:
|
||||
self._ensure_open()
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
try:
|
||||
WebDriverWait(self._driver, timeout_seconds).until(
|
||||
EC.text_to_be_present_in_element((By.TAG_NAME, "body"), text)
|
||||
)
|
||||
except Exception as e:
|
||||
raise TimeoutError(f"wait_for_text '{text}' timed out: {e}")
|
||||
|
||||
def wait_for_element(self, selectors: list, timeout_seconds: int = 15) -> None:
|
||||
self._ensure_open()
|
||||
deadline = time.time() + timeout_seconds
|
||||
last_error = None
|
||||
while time.time() < deadline:
|
||||
for sel in selectors:
|
||||
try:
|
||||
self._find(sel, timeout=1)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
time.sleep(0.5)
|
||||
raise TimeoutError(f"wait_for_element timed out: {selectors}, last: {last_error}")
|
||||
|
||||
def element_exists(self, selectors: list) -> bool:
|
||||
self._ensure_open()
|
||||
for sel in selectors:
|
||||
try:
|
||||
self._find(sel, timeout=1)
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
||||
def switch_to_frame(self, selectors: list) -> None:
|
||||
"""切换到匹配到的 iframe(客商选择弹窗等)。"""
|
||||
self._ensure_open()
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel, timeout=1)
|
||||
self._driver.switch_to.frame(el)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"switch_to_frame failed: {selectors}, last: {last_error}")
|
||||
|
||||
def switch_to_default_content(self) -> None:
|
||||
"""切回主文档(顶层 frame)。"""
|
||||
self._ensure_open()
|
||||
self._driver.switch_to.default_content()
|
||||
|
||||
def select_first(self, selectors: list, value: str) -> None:
|
||||
self._ensure_open()
|
||||
from selenium.webdriver.support.ui import Select
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
# 原生 <select>
|
||||
try:
|
||||
Select(el).select_by_visible_text(value)
|
||||
time.sleep(0.3)
|
||||
if self._selected_text_matches(el, value):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
# 兜底:JS 定位 option 并设置选中
|
||||
self._select_via_js(el, value)
|
||||
time.sleep(0.3)
|
||||
if self._selected_text_matches(el, value):
|
||||
return
|
||||
raise RuntimeError(f"select_first 未选中目标项: {value}")
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"select_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def _selected_text_matches(self, el, value: str) -> bool:
|
||||
try:
|
||||
script = (
|
||||
"var el = arguments[0];"
|
||||
"if (el.selectedIndex != null) {"
|
||||
" var o = el.options[el.selectedIndex];"
|
||||
" return o ? (o.textContent || o.text || '').trim() : '';"
|
||||
"}"
|
||||
"return (el.value || '').trim();"
|
||||
)
|
||||
selected = self._driver.execute_script(script, el)
|
||||
return (selected or "").strip() == str(value).strip()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _select_via_js(self, el, value: str) -> None:
|
||||
self._driver.execute_script(
|
||||
"""
|
||||
var el = arguments[0];
|
||||
var value = arguments[1];
|
||||
var options = el.options || [];
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var t = (options[i].textContent || options[i].text || '').trim();
|
||||
if (t === value || options[i].value === value) {
|
||||
el.selectedIndex = i;
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
""",
|
||||
el,
|
||||
str(value),
|
||||
)
|
||||
|
||||
def upload_file_first(self, selectors: list, file_path: str) -> None:
|
||||
self._ensure_open()
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
el.send_keys(file_path)
|
||||
time.sleep(1)
|
||||
return
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"upload_file_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def read_first(self, selectors: list, value_name: str = "") -> str:
|
||||
self._ensure_open()
|
||||
last_error = None
|
||||
for sel in selectors:
|
||||
try:
|
||||
el = self._find(sel)
|
||||
result = el.text or el.get_attribute("value") or ""
|
||||
if result:
|
||||
return result
|
||||
except Exception as e:
|
||||
last_error = e
|
||||
continue
|
||||
raise RuntimeError(f"read_first failed: {selectors}, last: {last_error}")
|
||||
|
||||
def read_body_text(self) -> str:
|
||||
self._ensure_open()
|
||||
try:
|
||||
return self._driver.find_element("tag name", "body").text
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def screenshot(self, name: str) -> str:
|
||||
import os
|
||||
save_dir = os.path.join(os.path.dirname(__file__), "screenshots")
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
path = os.path.join(save_dir, f"{name}.png")
|
||||
if self._driver:
|
||||
self._driver.save_screenshot(path)
|
||||
return path
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
"""HTTP client for YiSaiQi callbacks into the state center."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Callable
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import quote
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
JsonDict = dict[str, Any]
|
||||
Opener = Callable[[Request, float], Any]
|
||||
|
||||
|
||||
class StateCenterError(RuntimeError):
|
||||
def __init__(self, status: int, detail: Any):
|
||||
super().__init__(f"state center returned {status}: {detail}")
|
||||
self.status = status
|
||||
self.detail = detail
|
||||
|
||||
|
||||
class StateCenterClient:
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
token: str,
|
||||
*,
|
||||
timeout: float = 15,
|
||||
opener: Opener | None = None,
|
||||
):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.token = token
|
||||
self.timeout = timeout
|
||||
self._opener = opener or _urlopen_with_timeout
|
||||
|
||||
def start_node(
|
||||
self,
|
||||
job_id: str,
|
||||
node_code: str,
|
||||
*,
|
||||
attempt_no: int = 1,
|
||||
operator: str = "agent",
|
||||
) -> JsonDict:
|
||||
return self._post(
|
||||
f"/api/jobs/{_path_part(job_id)}/nodes/{_path_part(node_code)}/start",
|
||||
{
|
||||
"attemptNo": attempt_no,
|
||||
"executorPlatform": "iS-RPA",
|
||||
"operator": operator,
|
||||
},
|
||||
)
|
||||
|
||||
def complete_node(
|
||||
self,
|
||||
job_id: str,
|
||||
node_code: str,
|
||||
payload: JsonDict,
|
||||
) -> JsonDict:
|
||||
return self._post(f"/api/jobs/{_path_part(job_id)}/nodes/{_path_part(node_code)}/complete", payload)
|
||||
|
||||
def fail_node(
|
||||
self,
|
||||
job_id: str,
|
||||
node_code: str,
|
||||
payload: JsonDict,
|
||||
) -> JsonDict:
|
||||
return self._post(f"/api/jobs/{_path_part(job_id)}/nodes/{_path_part(node_code)}/fail", payload)
|
||||
|
||||
def _post(self, path: str, payload: JsonDict) -> JsonDict:
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
request = Request(
|
||||
f"{self.base_url}{path}",
|
||||
data=data,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Agent-Token": self.token,
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with self._opener(request, self.timeout) as response:
|
||||
raw = response.read().decode("utf-8")
|
||||
except HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise StateCenterError(exc.code, detail) from exc
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _path_part(value: str) -> str:
|
||||
return quote(value, safe="")
|
||||
|
||||
|
||||
def _urlopen_with_timeout(request: Request, timeout: float) -> Any:
|
||||
return urlopen(request, timeout=timeout)
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
"""Flowchart-sized CONTRACT_CREATE browser steps for YiSaiQi.
|
||||
|
||||
Each function is intentionally small enough to map to one YiSaiQi flowchart
|
||||
node or one script block behind a flowchart node.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Protocol
|
||||
|
||||
from .errors import (
|
||||
ContractCreateError,
|
||||
LoginFailed,
|
||||
PdfMergeUnsupported,
|
||||
SupplierContractDisabled,
|
||||
)
|
||||
|
||||
|
||||
class BrowserAdapter(Protocol):
|
||||
def open_url(self, url: str) -> None: ...
|
||||
def fill_first(self, selectors: list[str], value: str) -> None: ...
|
||||
def click_first(self, selectors: list[str]) -> None: ...
|
||||
def double_click_first(self, selectors: list[str]) -> None: ...
|
||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: ...
|
||||
def wait_for_element(self, selectors: list[str], timeout_seconds: int = 15) -> None: ...
|
||||
def element_exists(self, selectors: list[str]) -> bool: ...
|
||||
def switch_to_frame(self, selectors: list[str]) -> None: ...
|
||||
def switch_to_default_content(self) -> None: ...
|
||||
def select_first(self, selectors: list[str], value: str) -> None: ...
|
||||
def upload_file_first(self, selectors: list[str], file_path: str) -> None: ...
|
||||
def read_first(self, selectors: list[str], value_name: str = "") -> str: ...
|
||||
def read_body_text(self) -> str: ...
|
||||
def screenshot(self, name: str) -> str: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ContractGoodsLine:
|
||||
operation_code: str
|
||||
goods_code: str
|
||||
goods_name: str
|
||||
approval_no: str
|
||||
spec: str
|
||||
unit: str
|
||||
quantity: float
|
||||
mdm_code: str = ""
|
||||
unit_price: str = ""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ContractFlowContext:
|
||||
contract_url: str
|
||||
username: str
|
||||
password: str
|
||||
supplier_name: str
|
||||
order_date: str
|
||||
source_type: str
|
||||
template_type: str = "FORMAT"
|
||||
template_name: str = ""
|
||||
customer_code: str = ""
|
||||
customer_name: str = ""
|
||||
contract_category: str = ""
|
||||
settlement_type: str = "付款"
|
||||
contract_background: str = ""
|
||||
contract_nature: str = "主合同"
|
||||
amount_currency: str = "人民币"
|
||||
amount: str = ""
|
||||
contract_end_date: str = ""
|
||||
is_major_contract: str = "否"
|
||||
is_prepayment: str = "否"
|
||||
belong_year: str = ""
|
||||
payment_method: str = "账期支付"
|
||||
payment_days: str = "30"
|
||||
contract_status: str = "正式合同"
|
||||
contains_blood_products: str = "否"
|
||||
submit_mode: str = "submit"
|
||||
goods_lines: list[ContractGoodsLine] = field(default_factory=list)
|
||||
merged_pdf_path: str | None = None
|
||||
order_no_list: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def run_contract_create_flow(browser: BrowserAdapter, ctx: ContractFlowContext) -> str:
|
||||
open_contract_system(browser, ctx)
|
||||
login_contract_system(browser, ctx)
|
||||
enter_contract_create(browser)
|
||||
select_contract_template(browser, ctx)
|
||||
fill_contract_signatory_info(browser, ctx)
|
||||
fill_contract_basic_info(browser, ctx)
|
||||
fill_contract_amount_and_dates(browser, ctx)
|
||||
fill_contract_goods_lines(browser, ctx)
|
||||
fill_contract_payment_info(browser, ctx)
|
||||
upload_contract_document_if_needed(browser, ctx)
|
||||
if not submit_contract_for_approval(browser, ctx):
|
||||
return ""
|
||||
return read_contract_number(browser)
|
||||
|
||||
|
||||
# 合同起草页(模板选择)直达 URL,绕过 SPA 菜单/iframe 复杂性
|
||||
DRAFT_PAGE_URL = (
|
||||
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/"
|
||||
"draft.jsp?isSimpleCategory=false"
|
||||
"&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply"
|
||||
"&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl"
|
||||
)
|
||||
|
||||
# 非格式-采购协议(2025集团版)模板 ID(测试环境固定值)
|
||||
# 直接跳转表单页,绕过「点模板」这一依赖前端委托事件、Selenium 点击无效的步骤
|
||||
TEMPLATE_ID = "198d10b04325279b8ffeb8b4c6493b5a"
|
||||
FORM_PAGE_URL = (
|
||||
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/"
|
||||
"kmAgreementApply.do?method=add"
|
||||
f"&fdTemplateId={TEMPLATE_ID}"
|
||||
f"&.fdTemplate={TEMPLATE_ID}"
|
||||
f"&i.docTemplate={TEMPLATE_ID}"
|
||||
"&s_css=default"
|
||||
)
|
||||
|
||||
# Landray/LUI 表单字段的真实 name
|
||||
F = {
|
||||
"customer_code": 'input[name="extendDataFormInfo.value(fd_customeropcode)"]',
|
||||
"customer_name": 'input[name="extendDataFormInfo.value(fd_customername)"]',
|
||||
"contract_category": 'input[name="extendDataFormInfo.value(fd_contract_cate_type)"]',
|
||||
"settlement_type": 'select#fdSettype',
|
||||
"contract_background": 'textarea[name="extendDataFormInfo.value(fd_desc_info)"]',
|
||||
"nature_main": 'input[name="fdType"][value="00"]',
|
||||
"amount": 'input[name="fdAccount"]',
|
||||
"sign_date": 'input[name="fdSigndate"]',
|
||||
"begin_date": 'input[name="fdEffedate"]',
|
||||
"end_date": 'input[name="fdEnddate"]',
|
||||
"is_major_contract": 'input[name="extendDataFormInfo.value(fd_is_major_contract)"]',
|
||||
"is_prepayment": 'input[name="extendDataFormInfo.value(fd_advance_amout)"]',
|
||||
"belong_year": 'input[name="extendDataFormInfo.value(fd_contract_year_text)"]',
|
||||
"payment_method": 'input[name="extendDataFormInfo.value(fd_paytype)"]',
|
||||
"payment_days": 'input[name="extendDataFormInfo.value(fd_pay_time_limit)"]',
|
||||
"contract_status": 'input[name="extendDataFormInfo.value(fd_suconstatus)"]',
|
||||
"contains_blood": 'input[name="extendDataFormInfo.value(fd_3d1ce3953f1162)"]',
|
||||
}
|
||||
|
||||
# 货品明细行的字段后缀;行索引从 0 开始(fd_detail1.0.* 为第 1 行)
|
||||
GOODS_FIELD_SUFFIXES = {
|
||||
"goods_code": "fd_opcode",
|
||||
"goods_name": "fd_goodsname",
|
||||
"goods_mdm": "fd_mdmCode",
|
||||
"goods_approval": "fd_registdocno",
|
||||
"goods_spec": "fd_goodstype",
|
||||
"goods_unit": "fd_goodsunit",
|
||||
"goods_qty": "fd_goodsqty",
|
||||
"goods_price": "fd_unitprice",
|
||||
}
|
||||
|
||||
ADD_ROW_SELECTORS = [
|
||||
"text=添加行",
|
||||
"text=新增明细",
|
||||
"text=新增行",
|
||||
"text=新增",
|
||||
"button:contains(添加行)",
|
||||
]
|
||||
|
||||
# 客商选择窗口(点击客户名称旁「选择」按钮弹出)。若真实页面改版,仅需调整此处。
|
||||
CUSTOMER_PICKER = {
|
||||
"trigger": [
|
||||
'div.inputselectsgl[onclick*="fdCustomerEdit"]',
|
||||
'div[class*="inputselect"][onclick*="fdCustomer"]',
|
||||
],
|
||||
"frame": [
|
||||
'iframe[src*="common-template"]',
|
||||
'iframe[src*="KmRelativeMain"]',
|
||||
'iframe[src*="relative"]',
|
||||
],
|
||||
"search_input": ["input.search-box-text"],
|
||||
"search_button": ["input.search-box-btn"],
|
||||
"result_checkbox": ['input[name="List_Selected"]'],
|
||||
"confirm_button": ["div.btn.ok", "div.lui_toolbar_btn_def"],
|
||||
}
|
||||
|
||||
|
||||
def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
browser.open_url(ctx.contract_url)
|
||||
|
||||
|
||||
def login_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
try:
|
||||
browser.fill_first(['input[name="j_username"]', 'input[placeholder="用户名"]'], ctx.username)
|
||||
browser.fill_first(['input[name="j_password"]', 'input[placeholder="密码"]'], ctx.password)
|
||||
browser.click_first(['a[class*="login_button"]', 'a:contains("登录")', 'input[name="btn_submit"]'])
|
||||
browser.wait_for_text("合同", timeout_seconds=20)
|
||||
except Exception as exc:
|
||||
browser.screenshot("contract-login-failed")
|
||||
raise LoginFailed(f"合同系统登录失败:{exc}") from exc
|
||||
|
||||
|
||||
def enter_contract_create(browser: BrowserAdapter) -> None:
|
||||
# 登录后直接导航到起草表单页,跳过模板选择(前端委托事件 Selenium 点击无效)
|
||||
browser.open_url(FORM_PAGE_URL)
|
||||
browser.wait_for_text("客户编码", timeout_seconds=20)
|
||||
|
||||
|
||||
def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
# 已通过 URL 直接指定模板,本步骤仅兜底:若尚未进入表单则再次导航
|
||||
if "客户编码" not in browser.read_body_text():
|
||||
browser.open_url(FORM_PAGE_URL)
|
||||
browser.wait_for_text("客户编码", timeout_seconds=20)
|
||||
|
||||
|
||||
def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
expected_name = (ctx.customer_name or ctx.supplier_name).strip()
|
||||
if not ctx.customer_code:
|
||||
raise SupplierContractDisabled("客商操作码(customerCode)为空,无法加载客商")
|
||||
|
||||
# 点击客户名称字段旁的「选择」按钮打开客商选择窗口,按编码搜索并带回客商
|
||||
browser.click_first(CUSTOMER_PICKER["trigger"])
|
||||
_pick_customer_from_dialog(browser, ctx.customer_code)
|
||||
loaded_name = _wait_customer_name(browser, timeout=8)
|
||||
|
||||
if not loaded_name:
|
||||
browser.screenshot("contract-customer-load-failed")
|
||||
raise SupplierContractDisabled(
|
||||
f"客商加载失败,请确认输入的客商操作码是否有误:{ctx.customer_code}"
|
||||
)
|
||||
|
||||
if expected_name and loaded_name != expected_name:
|
||||
browser.screenshot("contract-customer-name-mismatch")
|
||||
raise SupplierContractDisabled(
|
||||
f"客商名称校验不通过:期望「{expected_name}」,实际「{loaded_name}」"
|
||||
)
|
||||
|
||||
browser.screenshot("contract-signatory-info-filled")
|
||||
|
||||
|
||||
def _wait_customer_name(browser: BrowserAdapter, timeout: int) -> str:
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
body = browser.read_body_text()
|
||||
if "自动加载客商失败" in body or "客商操作码是否有误" in body:
|
||||
return ""
|
||||
name = _try_read(browser, [F["customer_name"]])
|
||||
if name and name not in ("==请选择==", "请选择"):
|
||||
return name
|
||||
time.sleep(0.5)
|
||||
return ""
|
||||
|
||||
|
||||
def _pick_customer_from_dialog(browser: BrowserAdapter, code: str) -> None:
|
||||
# 客商选择窗口是一个 iframe 弹窗:切入后搜索编码 → 选中结果复选框 → 切回主文档。
|
||||
# 单选模式下点击复选框即触发回填并关闭弹窗;若未自动关闭再点确认按钮兜底。
|
||||
browser.wait_for_element(CUSTOMER_PICKER["frame"], timeout_seconds=10)
|
||||
browser.switch_to_frame(CUSTOMER_PICKER["frame"])
|
||||
try:
|
||||
browser.fill_first(CUSTOMER_PICKER["search_input"], code)
|
||||
browser.click_first(CUSTOMER_PICKER["search_button"])
|
||||
time.sleep(1)
|
||||
browser.wait_for_element(CUSTOMER_PICKER["result_checkbox"], timeout_seconds=10)
|
||||
browser.click_first(CUSTOMER_PICKER["result_checkbox"])
|
||||
time.sleep(1)
|
||||
finally:
|
||||
browser.switch_to_default_content()
|
||||
|
||||
|
||||
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
if ctx.contract_category:
|
||||
browser.fill_first([F["contract_category"]], ctx.contract_category)
|
||||
if ctx.settlement_type:
|
||||
_select_settlement_type(browser, ctx.settlement_type)
|
||||
if ctx.contract_background:
|
||||
browser.fill_first([F["contract_background"]], ctx.contract_background)
|
||||
# 合同性质默认主合同(页面已勾选),仅在需要从合同时切换
|
||||
if ctx.contract_nature and ctx.contract_nature != "主合同":
|
||||
browser.click_first(['input[name="fdType"][value="01"]'])
|
||||
browser.screenshot("contract-basic-info-filled")
|
||||
|
||||
|
||||
def _select_settlement_type(browser: BrowserAdapter, value: str) -> None:
|
||||
browser.select_first([F["settlement_type"]], value)
|
||||
selected = _try_read(browser, [F["settlement_type"]])
|
||||
if not selected or selected in ("==请选择==", "请选择"):
|
||||
# LUI 自定义下拉兜底:点击展开后按可见文本选择
|
||||
browser.click_first([F["settlement_type"]])
|
||||
browser.click_first([f"//option[contains(text(),'{value}')]", f"text={value}"])
|
||||
selected = _try_read(browser, [F["settlement_type"]])
|
||||
if not selected or selected in ("==请选择==", "请选择"):
|
||||
raise ContractCreateError(f"结算方式下拉未选中,仍停留在「==请选择==」,期望「{value}」")
|
||||
|
||||
|
||||
def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
if ctx.amount:
|
||||
browser.fill_first([F["amount"]], ctx.amount)
|
||||
browser.fill_first([F["sign_date"]], ctx.order_date)
|
||||
browser.fill_first([F["begin_date"]], ctx.order_date)
|
||||
if ctx.contract_end_date:
|
||||
browser.fill_first([F["end_date"]], ctx.contract_end_date)
|
||||
browser.screenshot("contract-amount-and-dates-filled")
|
||||
|
||||
|
||||
def fill_contract_goods_lines(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
for index, line in enumerate(ctx.goods_lines):
|
||||
row = index # 第 1 行 row=0,第 2 行 row=1,依此类推
|
||||
if index > 0:
|
||||
browser.click_first(ADD_ROW_SELECTORS)
|
||||
browser.wait_for_element([_goods_selector("goods_code", row)], timeout_seconds=15)
|
||||
|
||||
browser.fill_first([_goods_selector("goods_code", row)], line.goods_code)
|
||||
browser.fill_first([_goods_selector("goods_name", row)], line.goods_name)
|
||||
if line.mdm_code:
|
||||
browser.fill_first([_goods_selector("goods_mdm", row)], line.mdm_code)
|
||||
if line.approval_no:
|
||||
browser.fill_first([_goods_selector("goods_approval", row)], line.approval_no)
|
||||
if line.spec:
|
||||
browser.fill_first([_goods_selector("goods_spec", row)], line.spec)
|
||||
if line.unit:
|
||||
browser.fill_first([_goods_selector("goods_unit", row)], line.unit)
|
||||
browser.fill_first([_goods_selector("goods_qty", row)], str(line.quantity))
|
||||
if line.unit_price:
|
||||
browser.fill_first([_goods_selector("goods_price", row)], str(line.unit_price))
|
||||
browser.screenshot("contract-goods-lines-filled")
|
||||
|
||||
|
||||
def _goods_selector(field_key: str, row_index: int) -> str:
|
||||
suffix = GOODS_FIELD_SUFFIXES[field_key]
|
||||
return f'input[name="extendDataFormInfo.value(fd_detail1.{row_index}.{suffix})"]'
|
||||
|
||||
|
||||
def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
browser.fill_first([F["is_major_contract"]], ctx.is_major_contract)
|
||||
browser.fill_first([F["is_prepayment"]], ctx.is_prepayment)
|
||||
if ctx.belong_year:
|
||||
browser.fill_first([F["belong_year"]], ctx.belong_year)
|
||||
if ctx.payment_method:
|
||||
browser.fill_first([F["payment_method"]], ctx.payment_method)
|
||||
if ctx.payment_days:
|
||||
browser.fill_first([F["payment_days"]], ctx.payment_days)
|
||||
if ctx.contract_status:
|
||||
browser.fill_first([F["contract_status"]], ctx.contract_status)
|
||||
if ctx.contains_blood_products:
|
||||
browser.fill_first([F["contains_blood"]], ctx.contains_blood_products)
|
||||
browser.screenshot("contract-payment-filled")
|
||||
|
||||
|
||||
def upload_contract_document_if_needed(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||
if ctx.source_type != "YYC":
|
||||
# XYY / OFFLINE:跳过合同证文上传
|
||||
return
|
||||
if ctx.submit_mode == "dry_run":
|
||||
# dry_run 只跑到提交前,不真正上传附件
|
||||
browser.screenshot("contract-pdf-upload-skipped")
|
||||
return
|
||||
if not ctx.merged_pdf_path:
|
||||
raise PdfMergeUnsupported("YYC 合同起草缺少合并后的 PDF 路径,无法上传")
|
||||
browser.upload_file_first(['input[type="file"]', "label=合同证文上传"], ctx.merged_pdf_path)
|
||||
browser.screenshot("contract-pdf-uploaded")
|
||||
|
||||
|
||||
def submit_contract_for_approval(browser: BrowserAdapter, ctx: ContractFlowContext) -> bool:
|
||||
if ctx.submit_mode == "dry_run":
|
||||
browser.screenshot("contract-dry-run-ready")
|
||||
return False
|
||||
browser.click_first(["text=提交", "text=提交送审", "text=送审"])
|
||||
browser.wait_for_text("合同编号", timeout_seconds=30)
|
||||
browser.screenshot("contract-submitted")
|
||||
return True
|
||||
|
||||
|
||||
def read_contract_number(browser: BrowserAdapter) -> str:
|
||||
return browser.read_first(
|
||||
['input[name="contractNo"]', 'input[name="contractSystemNo"]', "label=合同编号", "text=合同编号"],
|
||||
"contract_number",
|
||||
)
|
||||
|
||||
|
||||
def _try_read(browser: BrowserAdapter, selectors: list[str]) -> str:
|
||||
if not browser.element_exists(selectors):
|
||||
return ""
|
||||
try:
|
||||
return (browser.read_first(selectors, "") or "").strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def contract_version_label(ctx: ContractFlowContext) -> str:
|
||||
return "非格式合同" if ctx.template_type == "NON_FORMAT" else "格式合同"
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""诊断:登录 → 起草页 → 点模板,打印点击前后 URL 和页面文本。"""
|
||||
import os, sys, time
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
PARENT = os.path.dirname(BASE)
|
||||
if PARENT not in sys.path:
|
||||
sys.path.insert(0, PARENT)
|
||||
|
||||
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||
|
||||
DRAFT = ("http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/"
|
||||
"draft.jsp?isSimpleCategory=false"
|
||||
"&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply"
|
||||
"&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl")
|
||||
|
||||
a = ApaBrowserAdapter("http://contract-test.gzmpc.com:8080/")
|
||||
a.open_url("http://contract-test.gzmpc.com:8080/")
|
||||
time.sleep(2)
|
||||
|
||||
# 登录
|
||||
a.fill_first(['input[name="j_username"]'], "test002")
|
||||
a.fill_first(['input[name="j_password"]'], "1")
|
||||
a.click_first(['a[class*="login_button"]'])
|
||||
time.sleep(2)
|
||||
print("登录后 URL:", a._driver.current_url)
|
||||
|
||||
# 进起草页
|
||||
a.open_url(DRAFT)
|
||||
time.sleep(3)
|
||||
print("起草页 URL:", a._driver.current_url)
|
||||
print("起草页是否有 最近使用模板:", "最近使用模板" in a._driver.find_element("tag name", "body").text)
|
||||
|
||||
# 找模板元素
|
||||
try:
|
||||
from selenium.webdriver.common.by import By
|
||||
el = a._driver.find_element(By.XPATH, '//p[contains(@class,"title")][contains(@title,"非格式-采购协议")]')
|
||||
print("找到模板元素:", el.get_attribute("title"))
|
||||
print("元素可见:", el.is_displayed(), "位置:", el.location, "大小:", el.size)
|
||||
|
||||
# 尝试原生 click
|
||||
print("尝试原生 click ...")
|
||||
el.click()
|
||||
time.sleep(3)
|
||||
print("原生 click 后 URL:", a._driver.current_url)
|
||||
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||
except Exception as e:
|
||||
print("错误:", e)
|
||||
|
||||
# 尝试 JS click
|
||||
print("尝试 JS click ...")
|
||||
try:
|
||||
a._driver.execute_script("arguments[0].click()", el)
|
||||
time.sleep(3)
|
||||
print("JS click 后 URL:", a._driver.current_url)
|
||||
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||
except Exception as e:
|
||||
print("JS click 错误:", e)
|
||||
|
||||
# 尝试 mousedown/mouseup/click 完整事件链
|
||||
print("尝试完整事件链 click ...")
|
||||
try:
|
||||
a._driver.execute_script("""
|
||||
var el = arguments[0];
|
||||
var rect = el.getBoundingClientRect();
|
||||
var cx = rect.left + rect.width/2, cy = rect.top + rect.height/2;
|
||||
['mousedown','mouseup','click'].forEach(function(type){
|
||||
el.dispatchEvent(new MouseEvent(type, {bubbles:true, cancelable:true, clientX:cx, clientY:cy}));
|
||||
});
|
||||
""", el)
|
||||
time.sleep(3)
|
||||
print("事件链 click 后 URL:", a._driver.current_url)
|
||||
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||
except Exception as e:
|
||||
print("事件链 click 错误:", e)
|
||||
|
||||
print("DONE")
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
"""CONTRACT_CREATE 领域异常及状态中心错误码映射。"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class ContractCreateError(RuntimeError):
|
||||
"""页面元素找不到 / 页面改版(含入参缺失,均不可自动重试)。"""
|
||||
|
||||
error_code = "UI_ELEMENT_NOT_FOUND"
|
||||
retryable = False
|
||||
route_role = "OPS"
|
||||
|
||||
|
||||
class LoginFailed(ContractCreateError):
|
||||
"""合同系统登录失败。"""
|
||||
|
||||
error_code = "SYS_LOGIN_FAIL"
|
||||
retryable = True
|
||||
route_role = "OPS"
|
||||
|
||||
|
||||
class SupplierContractDisabled(ContractCreateError):
|
||||
"""供应商/客商被禁或失效(含客商加载失败)。"""
|
||||
|
||||
error_code = "BIZ_CONTRACT_DISABLED"
|
||||
retryable = False
|
||||
route_role = "PURCHASER"
|
||||
|
||||
|
||||
class PdfMergeUnsupported(ContractCreateError):
|
||||
"""PDF 合并失败或不受支持。"""
|
||||
|
||||
error_code = "CAP_PDF_MERGE_UNSUPPORTED"
|
||||
retryable = False
|
||||
route_role = "OPS"
|
||||
|
||||
|
||||
class UnknownRpaError(ContractCreateError):
|
||||
"""未知异常。"""
|
||||
|
||||
error_code = "RPA_UNKNOWN_ERROR"
|
||||
retryable = True
|
||||
route_role = "OPS"
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
{
|
||||
"flowName": "CONTRACT_CREATE_合同建单",
|
||||
"platform": "YiSaiQi iS-RPA",
|
||||
"nodeCode": "CONTRACT_CREATE",
|
||||
"inputs": [
|
||||
"stateCenterUrl",
|
||||
"agentToken",
|
||||
"jobId",
|
||||
"supplierName",
|
||||
"orderDate",
|
||||
"sourceType",
|
||||
"templateType",
|
||||
"templateName",
|
||||
"customerCode",
|
||||
"customerName",
|
||||
"contractCategory",
|
||||
"settlementType",
|
||||
"contractBackground",
|
||||
"amount",
|
||||
"contractEndDate",
|
||||
"paymentMethod",
|
||||
"paymentDays",
|
||||
"submitMode",
|
||||
"goodsLines",
|
||||
"mergedPdfPath"
|
||||
],
|
||||
"outputs": [
|
||||
"contractSystemNo",
|
||||
"nextNode"
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"id": "S01_CALLBACK_START",
|
||||
"name": "回调状态中心-start",
|
||||
"script": "StateCenterClient.start_node(jobId, 'CONTRACT_CREATE')"
|
||||
},
|
||||
{
|
||||
"id": "S02_OPEN_CONTRACT_SYSTEM",
|
||||
"name": "打开合同系统",
|
||||
"script": "open_contract_system(browser, ctx)"
|
||||
},
|
||||
{
|
||||
"id": "S03_LOGIN",
|
||||
"name": "登录合同系统",
|
||||
"script": "login_contract_system(browser, ctx)",
|
||||
"selectors": {
|
||||
"username": "input[name='j_username']",
|
||||
"password": "input[name='j_password']",
|
||||
"submit": "input[name='btn_submit']"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "S04_ENTER_CONTRACT_DRAFT",
|
||||
"name": "进入合同起草",
|
||||
"script": "enter_contract_create(browser)"
|
||||
},
|
||||
{
|
||||
"id": "S05_SELECT_RECENT_TEMPLATE",
|
||||
"name": "选择最近使用模板",
|
||||
"script": "select_contract_template(browser, ctx)",
|
||||
"rule": "优先点击最近使用模板中的 templateName;非格式默认非格式--采购协议"
|
||||
},
|
||||
{
|
||||
"id": "S06_FILL_SIGNATORY_INFO",
|
||||
"name": "填写签约方信息",
|
||||
"script": "fill_contract_signatory_info(browser, ctx)",
|
||||
"fields": [
|
||||
"客户编码",
|
||||
"客户名称"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S07_FILL_CONTRACT_BASIC_FORM",
|
||||
"name": "填写合同基础表单",
|
||||
"script": "fill_contract_basic_info(browser, ctx)",
|
||||
"fields": [
|
||||
"合同版本类型",
|
||||
"合同类别",
|
||||
"结算类型",
|
||||
"合同背景概述",
|
||||
"合同性质"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S08_FILL_AMOUNT_AND_DATES",
|
||||
"name": "填写金额与日期",
|
||||
"script": "fill_contract_amount_and_dates(browser, ctx)",
|
||||
"hardRules": [
|
||||
"合同签订日期=orderDate",
|
||||
"合同开始日期=orderDate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S09_FILL_GOODS_LINES",
|
||||
"name": "填写货品明细",
|
||||
"script": "fill_contract_goods_lines(browser, ctx)"
|
||||
},
|
||||
{
|
||||
"id": "S10_FILL_PAYMENT_AND_FLAGS",
|
||||
"name": "填写付款信息与特殊标志",
|
||||
"script": "fill_contract_payment_info(browser, ctx)"
|
||||
},
|
||||
{
|
||||
"id": "S11_MERGE_PDF_IF_YYC",
|
||||
"name": "医药城合并同供应商同日PDF",
|
||||
"script": "ensure_merged_pdf(platformPdfPaths, mergedPdfOutput)",
|
||||
"branch": "sourceType=YYC 且 platformPdfPaths 多于一份时执行;单份直接沿用;sourceType=XYY 跳过"
|
||||
},
|
||||
{
|
||||
"id": "S12_UPLOAD_PDF_IF_YYC",
|
||||
"name": "医药城上传合并合同PDF",
|
||||
"script": "upload_contract_document_if_needed(browser, ctx)",
|
||||
"branch": "sourceType=YYC 才执行;sourceType=XYY 跳过"
|
||||
},
|
||||
{
|
||||
"id": "S13_SUBMIT",
|
||||
"name": "提交",
|
||||
"script": "submit_contract_for_approval(browser)"
|
||||
},
|
||||
{
|
||||
"id": "S14_READ_CONTRACT_NO",
|
||||
"name": "读取合同编号",
|
||||
"script": "read_contract_number(browser)"
|
||||
},
|
||||
{
|
||||
"id": "S15_CALLBACK_COMPLETE",
|
||||
"name": "回调状态中心-complete",
|
||||
"script": "StateCenterClient.complete_node(jobId, 'CONTRACT_CREATE', businessRefs.contractSystemNo)"
|
||||
},
|
||||
{
|
||||
"id": "E_LOGIN_FAILED",
|
||||
"name": "异常-登录失败",
|
||||
"script": "StateCenterClient.fail_node(..., errorCode='SYS_LOGIN_FAIL', retryable=true, routeRole='OPS')"
|
||||
},
|
||||
{
|
||||
"id": "E_CONTRACT_DISABLED",
|
||||
"name": "异常-供应商合同禁用",
|
||||
"script": "StateCenterClient.fail_node(..., errorCode='BIZ_CONTRACT_DISABLED', retryable=false, routeRole='PURCHASER')"
|
||||
},
|
||||
{
|
||||
"id": "E_PDF_MERGE_UNSUPPORTED",
|
||||
"name": "异常-PDF合并能力不支持",
|
||||
"script": "StateCenterClient.fail_node(..., errorCode='CAP_PDF_MERGE_UNSUPPORTED', retryable=false, routeRole='OPS')"
|
||||
},
|
||||
{
|
||||
"id": "E_PAGE_CHANGED",
|
||||
"name": "异常-页面改版",
|
||||
"script": "StateCenterClient.fail_node(..., errorCode='UI_ELEMENT_NOT_FOUND', retryable=false, routeRole='OPS')"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"flowName": "CONTRACT_CREATE_预览",
|
||||
"platform": "YiSaiQi iS-RPA",
|
||||
"nodeCode": "CONTRACT_CREATE",
|
||||
"inputs": [
|
||||
"stateCenterUrl",
|
||||
"agentToken",
|
||||
"jobId",
|
||||
"supplierName",
|
||||
"orderDate",
|
||||
"sourceType",
|
||||
"templateType",
|
||||
"templateName",
|
||||
"customerCode",
|
||||
"customerName",
|
||||
"contractCategory",
|
||||
"settlementType",
|
||||
"contractBackground",
|
||||
"amount",
|
||||
"contractEndDate",
|
||||
"paymentMethod",
|
||||
"paymentDays",
|
||||
"submitMode",
|
||||
"goodsLines",
|
||||
"mergedPdfPath"
|
||||
],
|
||||
"outputs": [
|
||||
"previewDone"
|
||||
],
|
||||
"steps": [
|
||||
{ "id": "P01", "name": "打开合同系统", "script": "open_contract_system(browser, ctx)" },
|
||||
{ "id": "P02", "name": "登录合同系统", "script": "login_contract_system(browser, ctx)" },
|
||||
{ "id": "P03", "name": "进入合同起草", "script": "enter_contract_create(browser)" },
|
||||
{ "id": "P04", "name": "选择模板", "script": "select_contract_template(browser, ctx)" },
|
||||
{ "id": "P05", "name": "填写签约方", "script": "fill_contract_signatory_info(browser, ctx)" },
|
||||
{ "id": "P06", "name": "填写基础表单", "script": "fill_contract_basic_info(browser, ctx)" },
|
||||
{ "id": "P07", "name": "填写金额日期", "script": "fill_contract_amount_and_dates(browser, ctx)" },
|
||||
{ "id": "P08", "name": "填写明细", "script": "fill_contract_goods_lines(browser, ctx)" },
|
||||
{ "id": "P09", "name": "填写付款与标志", "script": "fill_contract_payment_info(browser, ctx)" },
|
||||
{ "id": "P10", "name": "预览结束", "script": "submit_contract_for_approval(browser, ctx) with submitMode=dry_run" }
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pypdf==5.1.0
|
||||
selenium
|
||||
webdriver-manager
|
||||
requests
|
||||
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
|
@ -1,179 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""用真实 Selenium Chrome 验证合同建单填表流程(dry_run,不提交)。"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
# 确保能 import rpa_yisaiqi 包
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
PARENT = os.path.dirname(BASE)
|
||||
if PARENT not in sys.path:
|
||||
sys.path.insert(0, PARENT)
|
||||
|
||||
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||
from rpa_yisaiqi.contract_flow_steps import (
|
||||
ContractFlowContext,
|
||||
ContractGoodsLine,
|
||||
login_contract_system,
|
||||
enter_contract_create,
|
||||
select_contract_template,
|
||||
fill_contract_signatory_info,
|
||||
fill_contract_basic_info,
|
||||
fill_contract_amount_and_dates,
|
||||
fill_contract_goods_lines,
|
||||
fill_contract_payment_info,
|
||||
)
|
||||
|
||||
CONTRACT_URL = "http://contract-test.gzmpc.com:8080/"
|
||||
|
||||
|
||||
def build_ctx() -> ContractFlowContext:
|
||||
return ContractFlowContext(
|
||||
contract_url=CONTRACT_URL,
|
||||
username="test002",
|
||||
password="1",
|
||||
supplier_name="重庆亿昊药业有限公司",
|
||||
order_date="2026-08-12",
|
||||
source_type="YYC",
|
||||
template_type="NON_FORMAT",
|
||||
template_name="非格式-采购协议",
|
||||
customer_code="DW0100046361",
|
||||
customer_name="重庆亿昊药业有限公司",
|
||||
contract_category="采购协议",
|
||||
settlement_type="付款",
|
||||
contract_background="自动化测试合同起草",
|
||||
contract_nature="主合同",
|
||||
amount="50000",
|
||||
contract_end_date="2026-12-31",
|
||||
is_major_contract="否",
|
||||
is_prepayment="否",
|
||||
belong_year="2026",
|
||||
payment_method="账期支付",
|
||||
payment_days="30",
|
||||
contract_status="正式合同",
|
||||
contains_blood_products="否",
|
||||
submit_mode="dry_run",
|
||||
goods_lines=[
|
||||
ContractGoodsLine(
|
||||
operation_code="ADD",
|
||||
goods_code="YP-001",
|
||||
goods_name="测试药品A",
|
||||
approval_no="国药准字H00000001",
|
||||
spec="50mg*30片",
|
||||
unit="盒",
|
||||
quantity=10,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def read_values(driver):
|
||||
"""回读表单中已填入的值,用于校验。"""
|
||||
script = """
|
||||
function getVal(name) {
|
||||
var el = document.querySelector('input[name="' + name + '"],textarea[name="' + name + '"],select[name="' + name + '"]');
|
||||
return el ? (el.value || '') : '(missing)';
|
||||
}
|
||||
var keys = [
|
||||
'extendDataFormInfo.value(fd_customeropcode)',
|
||||
'extendDataFormInfo.value(fd_customername)',
|
||||
'extendDataFormInfo.value(fd_contract_cate_type)',
|
||||
'fdAccount',
|
||||
'fdSigndate',
|
||||
'fdEffedate',
|
||||
'fdEnddate',
|
||||
'extendDataFormInfo.value(fd_is_major_contract)',
|
||||
'extendDataFormInfo.value(fd_advance_amout)',
|
||||
'extendDataFormInfo.value(fd_contract_year_text)',
|
||||
'extendDataFormInfo.value(fd_paytype)',
|
||||
'extendDataFormInfo.value(fd_pay_time_limit)',
|
||||
'extendDataFormInfo.value(fd_suconstatus)',
|
||||
'extendDataFormInfo.value(fd_3d1ce3953f1162)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_opcode)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_goodsname)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_registdocno)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_goodstype)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)',
|
||||
'extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)'
|
||||
];
|
||||
var out = {};
|
||||
for (var i = 0; i < keys.length; i++) { out[keys[i]] = getVal(keys[i]); }
|
||||
var bg = document.querySelector('textarea[name="extendDataFormInfo.value(fd_desc_info)"]');
|
||||
out['background'] = bg ? bg.value : '(missing)';
|
||||
return JSON.stringify(out);
|
||||
"""
|
||||
return driver.execute_script(script)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Selenium 合同建单填表验证 (dry_run, 不提交)")
|
||||
print("=" * 60)
|
||||
|
||||
adapter = ApaBrowserAdapter(CONTRACT_URL)
|
||||
ctx = build_ctx()
|
||||
|
||||
print("[1/6] 打开合同系统并登录 ...")
|
||||
adapter.open_url(CONTRACT_URL)
|
||||
time.sleep(2)
|
||||
login_contract_system(adapter, ctx)
|
||||
print(" [OK] 登录成功")
|
||||
|
||||
print("[2/6] 进入合同起草页 ...")
|
||||
enter_contract_create(adapter)
|
||||
print(" [OK] 已进入模板选择页")
|
||||
|
||||
print("[3/6] 选择模板 ...")
|
||||
select_contract_template(adapter, ctx)
|
||||
time.sleep(2)
|
||||
print(" [OK] 模板已选择")
|
||||
|
||||
print("[4/6] 填写签约方 + 基础信息 ...")
|
||||
fill_contract_signatory_info(adapter, ctx)
|
||||
fill_contract_basic_info(adapter, ctx)
|
||||
print(" [OK] 已填写")
|
||||
|
||||
print("[5/6] 填写金额日期 + 货品 + 付款 ...")
|
||||
fill_contract_amount_and_dates(adapter, ctx)
|
||||
fill_contract_goods_lines(adapter, ctx)
|
||||
fill_contract_payment_info(adapter, ctx)
|
||||
print(" [OK] 已填写")
|
||||
|
||||
print("[6/6] 回读校验 ...")
|
||||
time.sleep(1)
|
||||
values = read_values(adapter._driver)
|
||||
print(" 回读值:")
|
||||
import json
|
||||
for k, v in json.loads(values).items():
|
||||
print(f" {k} = {v}")
|
||||
|
||||
# 校验关键字段
|
||||
expected = {
|
||||
"extendDataFormInfo.value(fd_customeropcode)": "DW0100046361",
|
||||
"extendDataFormInfo.value(fd_customername)": "重庆亿昊药业有限公司",
|
||||
"extendDataFormInfo.value(fd_contract_cate_type)": "采购协议",
|
||||
"fdAccount": "50000.00",
|
||||
"fdSigndate": "2026-08-12",
|
||||
"fdEnddate": "2026-12-31",
|
||||
"extendDataFormInfo.value(fd_paytype)": "账期支付",
|
||||
"extendDataFormInfo.value(fd_pay_time_limit)": "30",
|
||||
"extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)": "10",
|
||||
}
|
||||
actual = json.loads(values)
|
||||
all_ok = True
|
||||
for k, v in expected.items():
|
||||
got = actual.get(k, "")
|
||||
status = "OK" if str(got) == str(v) else "MISMATCH"
|
||||
if status != "OK":
|
||||
all_ok = False
|
||||
print(f" [{status}] {k}: 期望={v} 实际={got}")
|
||||
|
||||
print("=" * 60)
|
||||
print("验证结果:", "全部通过" if all_ok else "存在不匹配,请检查")
|
||||
print("=" * 60)
|
||||
print("浏览器保持打开(detach),可人工核对。")
|
||||
sys.exit(0 if all_ok else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
"""YiSaiQi adapter template for CONTRACT_CREATE flowchart nodes.
|
||||
|
||||
This file is a mapping template. In the YiSaiQi designer, replace each method
|
||||
body with the platform's native browser activities: open webpage, input text,
|
||||
click element, select dropdown, upload file, read text, and screenshot.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class YiSaiQiBrowserAdapter:
|
||||
def open_url(self, url: str) -> None:
|
||||
"""Flowchart node: 浏览器打开网页."""
|
||||
raise NotImplementedError("Map to YiSaiQi browser open-url activity")
|
||||
|
||||
def fill_first(self, selectors: list[str], value: str) -> None:
|
||||
"""Flowchart node: 按候选定位器依次查找,找到后输入文本."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi input-text activity: {selectors=} {value=}")
|
||||
|
||||
def click_first(self, selectors: list[str]) -> None:
|
||||
"""Flowchart node: 按候选定位器依次查找,找到后点击."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi click activity: {selectors=}")
|
||||
|
||||
def double_click_first(self, selectors: list[str]) -> None:
|
||||
"""Flowchart node: 双击元素(客商选择窗口等)."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi double-click activity: {selectors=}")
|
||||
|
||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None:
|
||||
"""Flowchart node: 等待页面出现指定文本."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi wait-text activity: {text=} {timeout_seconds=}")
|
||||
|
||||
def wait_for_element(self, selectors: list[str], timeout_seconds: int = 15) -> None:
|
||||
"""Flowchart node: 等待元素出现."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi wait-element activity: {selectors=}")
|
||||
|
||||
def element_exists(self, selectors: list[str]) -> bool:
|
||||
"""Flowchart node: 判断元素是否存在."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi element-exists activity: {selectors=}")
|
||||
|
||||
def switch_to_frame(self, selectors: list[str]) -> None:
|
||||
"""Flowchart node: 切换到 iframe(客商选择弹窗等)."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi switch-to-frame activity: {selectors=}")
|
||||
|
||||
def switch_to_default_content(self) -> None:
|
||||
"""Flowchart node: 切回主文档."""
|
||||
raise NotImplementedError("Map to YiSaiQi switch-to-default-content activity")
|
||||
|
||||
def read_body_text(self) -> str:
|
||||
"""Flowchart node: 读取页面 body 文本."""
|
||||
raise NotImplementedError("Map to YiSaiQi read-body-text activity")
|
||||
|
||||
def select_first(self, selectors: list[str], value: str) -> None:
|
||||
"""Flowchart node: 下拉框选择."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi select activity: {selectors=} {value=}")
|
||||
|
||||
def upload_file_first(self, selectors: list[str], file_path: str) -> None:
|
||||
"""Flowchart node: 文件上传."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi upload-file activity: {selectors=} {file_path=}")
|
||||
|
||||
def read_first(self, selectors: list[str], value_name: str) -> str:
|
||||
"""Flowchart node: 读取页面文本/输入框值并写入流程变量."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi read-text activity: {selectors=} {value_name=}")
|
||||
|
||||
def screenshot(self, name: str) -> str:
|
||||
"""Flowchart node: 截图存证,返回文件路径."""
|
||||
raise NotImplementedError(f"Map to YiSaiQi screenshot activity: {name=}")
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
[]
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# docs
|
||||
|
||||
按现行 / 作业资产 / 历史查阅。
|
||||
|
||||
## 现行(订单中心四接口)
|
||||
|
||||
| 文件 | 用途 |
|
||||
| --- | --- |
|
||||
| `RPA订单中心接口开发口径_20260930.md` | 开发对照,作业语义以这份为准 |
|
||||
| `RPA订单中心接口口径讨论纪要_20260929.md` | 为什么这么定 |
|
||||
| `ddl/rpa_oms_staging_tables.sql` | Oracle 11.2 四张暂存表 |
|
||||
| `ddl/列对照.md` | 与接口 Excel 列对照 |
|
||||
| `API鉴权.doc` | 调订单中心时的 Token;接口 1、3 入站鉴权由本方定 |
|
||||
| `下一会话任务_建表.md` | 建表任务说明(已完成) |
|
||||
| `合同系统录入流程.md` | 合同班合单、尽调、提交、回写;页面步骤指向 `rpa_contract` |
|
||||
| `../rpa_rebate/返利流程.md` | 返利页面现行(总单/细单/送审)。与映射表冲突听这份 |
|
||||
| `../rpa_rebate/现行模式.md` | 返利班相对旧稿改了什么、下一会话未做 |
|
||||
| `大批量跑缺口.md` | 四条班次相对大批量现场还缺什么(口 2/4 HTTP、调度、崩溃戳、支付/M5) |
|
||||
| `写死测试配置整改清单.md` | 上正式前要拆的测试站、测试账号和干跑回退 |
|
||||
|
||||
接口字段、示例报文仍以 Excel 为准(当前 `Downloads/RPA与订单中心接口设计_20260922.xlsx`)。Excel 与口径冲突时,作业语义听口径。
|
||||
|
||||
`ddl 2/`、`ddl.zip` 与 `ddl/` 重复,不要用。
|
||||
|
||||
## 作业资产
|
||||
|
||||
| 文件 | 用途 |
|
||||
| --- | --- |
|
||||
| `合同返利系统字段映射_20260911.xlsx` | 返利旧默认值;现行字段以 `rpa_rebate/返利流程.md` 为准 |
|
||||
| `采购平台选品条件优先项_20260902.txt` | 选品规则原文 |
|
||||
| `【项目方案】福建洁达采购合同RPA自动化录入需求_v20260924.docx` | 页面操作参考 |
|
||||
|
||||
## 历史(一期 13 节点主链)
|
||||
|
||||
见 `archive/`:`PLAN.md`、一期需求/设计/开发规划/验收规范、`一期开发任务清单_表结构_API草案.md`、会议整理版、业务思路 docx、界面截图记录。
|
||||
|
||||
那套模型是「一单合同走状态机」。不要用它解释接口 1 的收件箱。
|
||||
|
||||
`superpowers/` 是 8–9 月设计稿,同样视为归档。
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# 纪要:RPA 与订单中心接口口径
|
||||
|
||||
- 时间:2026-09-29~30
|
||||
- 材料:`RPA与订单中心接口设计_20260922.xlsx`、`API鉴权.doc`、`docs/合同返利系统字段映射_20260911.xlsx`、方案 v20260924
|
||||
- 开发对照:`docs/RPA订单中心接口开发口径_20260930.md`
|
||||
|
||||
---
|
||||
|
||||
## 一、接口怎么理解
|
||||
|
||||
四个口不是四条流程一对一。1 是选品收件,2 是加购/支付回传,3 是合同和返利共用的行级下发,4 是合同/协议结果回写。
|
||||
|
||||
对码不在本方。接口 1 的 `goodsId` 进来就已经有了。
|
||||
|
||||
表 1 是收件箱:订单中心推 → 本方接口写入表 1 → 选品捞待处理。不是手填。
|
||||
|
||||
---
|
||||
|
||||
## 二、已锁定论
|
||||
|
||||
### 合单
|
||||
|
||||
福建确认一天两班(中午、下午)。Albert:未处理行按供应商汇总,一个供应商一份合同、一份返利。
|
||||
|
||||
锁定:
|
||||
|
||||
- 按 `supplierId` 合,不用 `omsId`,不用名称
|
||||
- 任务启动冻结快照,新进数据下一班
|
||||
- 头字段取该班该供应商第一行(同一班账期不一致也照录,算对方发数)
|
||||
- 下午再来的另开一份,不改上午已录合同
|
||||
- 带合同号的协议重录不进按供应商合
|
||||
|
||||
### 选品 / 加购 / 支付
|
||||
|
||||
- 搜商城用条形码
|
||||
- 规则不过:表 1 失败 + 表 2 `status=2` + 原因;默认对方**不重推**该 `omsId`(口头再跟对方确认,开发按已锁)
|
||||
- `status` 只用于加购。支付:有单号就推 `operateType=2`,没付不推,没有支付失败
|
||||
- 入站一单一个品种;多平台凑数量时同一 `omsId` 多行、`multi_platform_flag=1`、必须同包回(已进表 2)
|
||||
- 付款仍是采购员两班;支付回传另排,选品不等
|
||||
|
||||
### 合同 / 返利回写(业务确认)
|
||||
|
||||
合同选 A:提交后回起草;成功=返利中心能搜到合同号。搜不到下一班再搜,不重录合同。
|
||||
|
||||
返利选 A 的字面、按现场做:据说无审批流,点送审即成功,点完就回成功并带协议号。以后若开审批流再改。
|
||||
|
||||
查号挂在返利班,不单开查状态任务。合同班只录+提交+回起草。
|
||||
|
||||
这期不做「合同审完订单中心再推返利包」。二次推只用接口已写的「协议失败带合同号重录」。
|
||||
|
||||
暂存号不用于返利。返利用正式合同号去搜。
|
||||
|
||||
### 这期范围
|
||||
|
||||
- 只做账期。`customerType` 预留,对方固定传 1。预付项目组不做。
|
||||
- 平台字典:66 = 1 药城,48 = 药帮忙
|
||||
- 返利总单默认值跟映射表,不跟方案 5.5
|
||||
- MDM 码对方说默认必填,按必填做;示例没带先不管
|
||||
- 失败行数量/单价必填、总价舍入:先不管,真数据出问题打回订单中心
|
||||
|
||||
### 联调
|
||||
|
||||
- Token 测试地址已有;测试用户名 `rpa`(密码不入库)
|
||||
- 接口 2、4 路径暂时空着,先造数
|
||||
|
||||
---
|
||||
|
||||
## 三、讨论过程中澄清过的误解
|
||||
|
||||
1. 接口 3 一次可以很多行,那是批量收件,不是「已经合成一份合同」。合单是本方按供应商做的。
|
||||
2. 供应商编码不是小药药商家名。这期用对方给的 MDM/供应商字段填页面。
|
||||
3. 接口 4 示例是 RPA 把结果推出去,不是「合同审批完通知订单中心再推返利」。
|
||||
4. `contractStatus=2` 不是让本方重复录合同,是提交后按班次去返利**查号**。
|
||||
5. 审批/驳回不是本方作业;但合同成功码按接口仍要查号,不能理解成「填完报成功」。
|
||||
6. 加购「跳过」最后不单开状态码,用失败+原因;和多平台同包回不是一件事。
|
||||
|
||||
---
|
||||
|
||||
## 四、后置(不挡造数)
|
||||
|
||||
- 接口 2、4 真实 URL;`orgSecret` 是否要带;订单中心调本方 1、3 的鉴权
|
||||
- 只凑到一部分数量时,多平台报文怎么组
|
||||
- 返利测试环境确认:点送审后协议号是否立即可见
|
||||
- 加购业务失败不重推:口头跟对方确认一句
|
||||
|
||||
开发以口径文档为准。本文只记为什么这么定。
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# 洁达 RPA × 订单中心接口——开发口径
|
||||
|
||||
对照开发,避免跑偏。口径锁于 2026-09-29~30。接口路径暂时空着,本阶段用造数。
|
||||
|
||||
依据:
|
||||
|
||||
- `Downloads/RPA与订单中心接口设计_20260922.xlsx`(以当时最新一份为准)
|
||||
- `docs/API鉴权.doc`
|
||||
- `docs/合同返利系统字段映射_20260911.xlsx`(返利旧默认值,对照用)
|
||||
- `rpa_rebate/返利流程.md`(返利页面现行;与映射表冲突听这份)
|
||||
- `rpa_selection/` 既有规则
|
||||
|
||||
---
|
||||
|
||||
## 1. 总原则
|
||||
|
||||
- 四条作业独立:选品、加购/支付回传、合同、返利。不要一个任务从加购挂到付款、录完协议。
|
||||
- 对码在订单中心做完。接口 1 进来时已有 `goodsId`,本方不再做对码。
|
||||
- 订单中心推接口 1、3;本方推接口 2、4。表是落盘,不是接口本身。
|
||||
- 给什么填什么。领导批不批、驳回,RPA 不盯。驳回后要再录,等他们重新推数。
|
||||
- 这期只做账期客户。`customerType` 固定按 `1`。预付不做。
|
||||
- 出问题、接口没写清的数,打回订单中心。不要猜着填。
|
||||
|
||||
---
|
||||
|
||||
## 2. 四张表和四个口
|
||||
|
||||
| 口 | 方向 | 底表 | 用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 订单中心 → RPA | 销售商品对码数据 | 选品收件箱 |
|
||||
| 2 | RPA → 订单中心 | 加购及结算商品数据 | 加购结果、支付单 |
|
||||
| 3 | 订单中心 → RPA | 采购合同数据 | 合同 + 返利源数据(行级) |
|
||||
| 4 | RPA → 订单中心 | 合同协议录入结果 | 合同结果、协议结果 |
|
||||
|
||||
- 表 1:接口收下才有数,不是手填。选品只读待处理行。
|
||||
- 幂等:接口 1 按 `omsId`,已存在回 `DUPLICATE`,不更新、不重跑。接口 3 按对方 `fid`。
|
||||
- 平台:`48` 药帮忙,`66` 1 药城。
|
||||
- MDM:`goodsMdmCode`、`supplierMdmCode`(及选品侧货品/客户 MDM)按必填收、必填存。
|
||||
- 货主 `orgCode` 原样落表、回写带上。
|
||||
|
||||
---
|
||||
|
||||
## 3. 选品
|
||||
|
||||
### 3.1 入口
|
||||
|
||||
从表 1 捞 `deal_flag=0`,一条条处理。一张 `omsId` 一个品种。
|
||||
|
||||
商城查询键:**条形码**。批准文号照收,不拿去搜。
|
||||
|
||||
规则沿用 `rpa_selection/`(自营仓、毛利 ≥ 4%、效期 ≥ 12 个月;两平台合并后按仓序拆量,凑齐计划量才算成功)。毛利用 `salePrice`,拆量目标用 `planPurchaseQty`。详见 `docs/superpowers/specs/2026-10-02-selection-split-allocation-design.md`。
|
||||
|
||||
### 3.2 加购结果(接口 2,`operateType=1`)
|
||||
|
||||
`status` 只给加购用。
|
||||
|
||||
| 现场 | 表 1 | 表 2 | 回订单中心 |
|
||||
| --- | --- | --- | --- |
|
||||
| 规则过了,已加购 | 处理成功 | `status=1`,带平台/供应商/价/量 | 推送 |
|
||||
| 登录挂、页面报错 | 处理失败 | `status=2`,原因写系统故障 | 推送;对方可重推再跑 |
|
||||
| 规则不过,故意不买 | 处理失败 | `status=2`,原因如「无符合规则商品,毛利不足 4%」或「库存不足」 | 推送;**此 `omsId` 不重推、不重跑选品** |
|
||||
|
||||
不再要第三种状态码。失败原因必须能区分「业务不买」和「系统故障」。
|
||||
|
||||
### 3.3 一单多平台
|
||||
|
||||
入站一行一个药。数量要两个平台才凑得齐时:
|
||||
|
||||
- 表 2 多行,同一个 `omsId`
|
||||
- `multi_platform_flag=1`
|
||||
- **同一次报文**推回,禁止先回一个平台再补另一个
|
||||
- 这组平台都判定完之前不要推
|
||||
|
||||
凑不齐计划量:不落部分成功行,整单 `status=2`,原因「库存不足」。一边平台故障且另一边单独凑不齐:系统故障,可重跑。
|
||||
|
||||
### 3.4 支付(接口 2,`operateType=2`)
|
||||
|
||||
采购员在商城自己付。选品任务加购回传后结束,不要挂着等付款。
|
||||
|
||||
另排班(对准中午 12 点、下午 5 点付完后,例如 12:30、17:30)导出已支付订单:
|
||||
|
||||
- 付了:推 `operateType=2`,带 `payOrderNo`、`payOrderDtlId`。有单号就是成功。`status` 填空。
|
||||
- 没付:**不推** 这一条。不是支付失败。
|
||||
- 没有「支付失败」回写。
|
||||
- 数量单价以商城实付为准,不要把加购行原样再推一遍。
|
||||
|
||||
### 3.5 造数阶段
|
||||
|
||||
失败行的单价/数量/总价接口仍标必填且数量大于 0:先填能过校验的数。`purchaseTotal` 舍入没写:先按单价×数量四位小数。对不上再打回。
|
||||
|
||||
---
|
||||
|
||||
## 4. 合同
|
||||
|
||||
### 4.1 班次
|
||||
|
||||
一天两次(中午、下午)。任务启动瞬间冻结本班未处理名单,跑的过程中新进行留给下一班。
|
||||
|
||||
### 4.2 合单
|
||||
|
||||
按 `supplierId` 合成一份合同。不用 `omsId` 合,不用供应商名称。
|
||||
|
||||
- 头字段(账期、签订日、起止日、采购员、计划员)取该班该供应商**第一行**。
|
||||
- 下午再推来的,哪怕同一供应商,**另开一份**。不追加、不改上午已录合同。
|
||||
- `contractEntryStatus=0` 且带 `contractNo`:只录协议,不进「按供应商合」,不重开合同。
|
||||
|
||||
### 4.3 录入
|
||||
|
||||
这期一律按账期客户填(不勾预付款,付款期限用 `paymentTerm`)。尽调仍看合同系统页面;未尽调本单失败,原因「客户未尽调」,不录。
|
||||
|
||||
点提交(不要拿暂存号去录返利)。提交成功:
|
||||
|
||||
- 接口 4 `actionCode=1`,`contractStatus=1` 起草,带上合同号
|
||||
- **合同任务到此结束。不要重录合同。**
|
||||
|
||||
同一份合同下多个 `fid`:回写同一套合同号和状态。
|
||||
|
||||
### 4.4 合同「成功」
|
||||
|
||||
接口成功定义是:合同号能在返利中心搜到。不是提交成功。
|
||||
|
||||
查号挂在**返利班**上,不单开查状态任务。搜到再回 `contractStatus=2`。搜不到:不重录,留给下一班再搜。
|
||||
|
||||
---
|
||||
|
||||
## 5. 返利
|
||||
|
||||
### 5.1 班次
|
||||
|
||||
与合同同一天两班,建议合同班跑完再跑。不做「合同审完订单中心再推返利包」。二次推只处理协议失败重录(带合同号)。
|
||||
|
||||
### 5.2 查号 + 录入
|
||||
|
||||
拿已有合同号、尚未回合同成功的单,以及对方推来只录协议的单,打开返利中心按合同号搜:
|
||||
|
||||
- 搜到:先回合同成功,再录协议
|
||||
- 搜不到:本班不录协议、不重开合同,下一班再搜
|
||||
|
||||
总单/细单现行字段以 `rpa_rebate/返利流程.md` 为准:计划员现阶段「廖建琦」(后期改回订单中心),联系人「苏文辉」,结算「电汇」,承付「下月 N 号结」参数 `25`,协议开始/结束当天。细单走页面新增(按 `goods_id` 搜),不做 Excel。货源细单ID 本期不填。不要用方案 5.5 的「登录人 / 账期 / 当年最后一天」。映射表作对照,冲突听返利流程。
|
||||
|
||||
### 5.3 协议「成功」
|
||||
|
||||
现场据说无审批流,点送审即成功。点完回 `actionCode=2`、`agreementStatus=1`,带上当时的协议总单/细单号。
|
||||
|
||||
测试环境先点一次确认:送审后号是否马上能看见。以后若打开审批流,改成送审后下一班再查,不能再点完就回成功。
|
||||
|
||||
---
|
||||
|
||||
## 6. 联调与造数
|
||||
|
||||
本阶段路径空着,造数跑通落表和作业。
|
||||
|
||||
鉴权(调订单中心时):
|
||||
|
||||
- Token:`POST https://gzmpc-paas-restcloud-test.gzmpc.com/gw/rest/core/auth/token`
|
||||
- 请求头:`identitytoken`
|
||||
- 测试用户名:`rpa`(密码放本机环境变量,禁止写入仓库)
|
||||
- 接口 2、4 真实 path 仍缺(文档 `gw/xxxx/xxxx`)
|
||||
- 示例里的 `orgSecret` 是否要带,未写,真联调再问
|
||||
- 订单中心调本方 1、3 的鉴权由本方定,不在这份鉴权文档里
|
||||
|
||||
---
|
||||
|
||||
## 7. 开发时不要做的
|
||||
|
||||
- 选品按批准文号搜商城
|
||||
- 加购成功后顺手推支付
|
||||
- 规则不过报 `status=1`,或报失败却让对方当故障重跑
|
||||
- 多平台加购拆成两次推送
|
||||
- 按 `omsId` 或「同供应商同日」另写合单
|
||||
- 同一班两套账期时整组停掉(已定为取第一行照录)
|
||||
- 合同搜不到号就再录一遍
|
||||
- 用方案 5.5 默认值填返利总单
|
||||
- 把密码、token 写进 Git
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
# 采购主链 RPA 编排与手机待办一期方案
|
||||
|
||||
## Summary
|
||||
|
||||
一期按 `RPA执行平台 + Python/MySQL 轻量状态中心 + 企业微信正式应用 + 本地 OCR` 落地,覆盖主链 `合同扫描件/合同系统 -> 返利中心采购协议 -> M5 采购订单`。
|
||||
目标不是只做一个机器人脚本,而是做成一套**业务可见、异常可处理、手机可协同**的流程编排系统:
|
||||
|
||||
- 主流程默认尽量全自动运行
|
||||
- OCR/映射不确定项转为企业微信手机待办
|
||||
- 每个节点状态、耗时、错误原因、处理人都写入状态中心
|
||||
- 业务可在手机端完成 `查看上下文 / 补录字段 / 继续 / 重试 / 转人工`
|
||||
- RPA 执行平台只负责系统操作与节点执行,不承载核心状态和业务协同
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 0. 入池触发与文件规范
|
||||
|
||||
一期默认以“**共享文件夹入池 + 状态中心建单**”作为统一触发方式,不接受 RPA 平台内部自行起单。
|
||||
|
||||
- 合同扫描件、年度协议、批次协议、返利中心导入模板都必须先进入约定共享目录
|
||||
- 状态中心负责监听入池目录,或由人工在状态中心页面选择文件后建单
|
||||
- RPA 平台只能消费已经建好的 `job`,不能绕过状态中心直接执行
|
||||
|
||||
入池对象至少区分以下几类:
|
||||
|
||||
- 年度协议
|
||||
- 批次合同
|
||||
- 合同附件/补充页
|
||||
- 返利中心细单导入模板
|
||||
|
||||
文件命名规则一期建议固定为:
|
||||
|
||||
- `日期_供应商名称_计划员_合同类型`
|
||||
- 允许补充流水号或附件序号,例如 `20260625_国药控股_张三_批次合同_01.pdf`
|
||||
|
||||
入池校验至少包括:
|
||||
|
||||
- 文件类型是否在允许范围内(PDF/图片/Excel)
|
||||
- 是否命中命名规范
|
||||
- 是否能识别供应商名称与计划员
|
||||
- 同一合同编号/同一业务键是否已存在未完成实例
|
||||
- 是否存在多页缺失、重复上传、同名覆盖
|
||||
|
||||
入池失败时不得直接丢给 RPA 平台,应由状态中心生成待处理记录:
|
||||
|
||||
- 命名不规范 -> 计划员待办
|
||||
- 关键文件缺失 -> 计划员待办
|
||||
- 疑似重复入池 -> 提示并阻止新建,必要时转运维或项目负责人确认
|
||||
|
||||
### 1. 总体架构与职责边界
|
||||
|
||||
- **RPA 执行平台(影刀 / 艺赛旗 / 其他)**
|
||||
- 负责登录系统、页面检索、字段填充、模板导入、截图、日志/录屏采集、执行结果回传
|
||||
- 每完成一个节点,调用状态中心接口上报节点结果
|
||||
- 不直接保存业务主状态,不直接承载人工审批逻辑
|
||||
|
||||
- **轻量状态中心(Python + MySQL)**
|
||||
- 负责流程实例、节点状态、待办、角色路由、重试控制、日志归档、配置映射、执行平台路由
|
||||
- 提供给 RPA 平台的流程启动、节点回调、异常上报、人工处理、继续执行接口
|
||||
- 提供给企业微信的消息卡片、待办链接、处理回调接口
|
||||
|
||||
- **本地 OCR 服务**
|
||||
- 一期默认本地部署,优先按 PaddleOCR 设计
|
||||
- 负责从扫描件/PDF 中提取文本和结构化字段
|
||||
- 输出带置信度的 JSON,不直接写业务系统
|
||||
|
||||
- **企业微信正式应用**
|
||||
- 负责个人待办、异常告警、处理入口、节点进度消息
|
||||
- 不做主流程编排,只作为移动交互层
|
||||
|
||||
#### 1.1 我方自有能力边界
|
||||
|
||||
以下能力必须沉淀在“我们自己的状态中心”中,而不是绑定在某一家 RPA 平台内部:
|
||||
|
||||
- 流程主状态与节点状态机
|
||||
- 合同入池、判重与幂等控制
|
||||
- OCR 结果存档、映射配置、规则版本
|
||||
- 手机待办、角色路由、人工处理留痕
|
||||
- 成功判定口径、错误码体系、证据归档
|
||||
- 多 RPA 平台的统一查询、统一审计和统一对外口径
|
||||
|
||||
换句话说,RPA 平台是可替换的“执行手”,状态中心才是我们的“流程主控”和“业务资产沉淀层”。
|
||||
|
||||
#### 1.2 执行平台可替换边界
|
||||
|
||||
一期支持将底层执行器设计为可切换,但边界要先写死:
|
||||
|
||||
- 可切换的是“执行平台”,不可切换的是状态中心、企业微信协同、业务规则和成功判定口径
|
||||
- 允许“按新单切换平台”,也允许“按节点配置平台”
|
||||
- 不做“单个节点执行到一半跨平台接力”
|
||||
- 老单默认沿用建单时选定的平台,不在执行过程中强行迁移
|
||||
- 新平台接入前必须通过统一 POC 验证清单
|
||||
|
||||
#### 1.3 候选平台能力判断原则
|
||||
|
||||
当前候选执行平台可包括影刀、艺赛旗等,但是否可用于本项目,不能只看厂商宣传页,要按本项目关键动作验证:
|
||||
|
||||
- 目标系统登录与稳定检索
|
||||
- 表单填写、弹窗处理、模板导入
|
||||
- 文件上传/下载与本地目录交互
|
||||
- 截图、日志、录屏留证
|
||||
- 自定义 HTTP 回调状态中心
|
||||
|
||||
满足上述能力后,才能认为该平台可替代现有执行器。
|
||||
|
||||
### 2. 一期业务主链与节点定义
|
||||
|
||||
一期只做以下主链,其他如订单中心分货、招采子系统不纳入一期:
|
||||
|
||||
1. 合同文件入池
|
||||
2. OCR 识别与字段抽取
|
||||
3. 字段映射与业务校验
|
||||
4. 合同系统信息补齐/确认
|
||||
5. 返利中心采购协议检索与总单录入
|
||||
6. 返利中心细单模板生成与导入
|
||||
7. 返利中心结果校验
|
||||
8. M5 采购订单创建
|
||||
9. M5 保存与收货通知单生成
|
||||
10. 流程完成归档
|
||||
|
||||
#### 2.1 合同系统前置条件
|
||||
|
||||
返利中心与 M5 的自动执行,必须建立在“合同系统已达到可执行状态”的前提上。
|
||||
|
||||
一期要明确区分以下几个动作:
|
||||
|
||||
- 合同系统信息补齐
|
||||
- 合同系统送审
|
||||
- 合同系统审核通过
|
||||
- 审核驳回/需补充资料
|
||||
|
||||
建议一期按以下边界落地:
|
||||
|
||||
- 机器人可协助录入合同系统字段,并回写录入结果
|
||||
- “送审”动作是否由机器人执行,需要业务确认后写死
|
||||
- 只有合同处于“已审核/审核通过”状态,才能进入返利中心节点
|
||||
- 审核未通过或缺资料,不得继续推进,直接转计划员或采购待办
|
||||
|
||||
状态中心中必须记录:
|
||||
|
||||
- 合同系统单号
|
||||
- 合同系统当前审核状态
|
||||
- 审核通过时间
|
||||
- 驳回原因摘要
|
||||
|
||||
#### 2.2 一期明确不纳入范围
|
||||
|
||||
以下内容虽然在原始资料中提到,但不纳入一期实施:
|
||||
|
||||
- 订单中心分货与销售订单联动
|
||||
- 招采子系统医院计划抓取与回传
|
||||
- 发货后的一票/二票自动上传
|
||||
- 渠道价管理与维护
|
||||
|
||||
如后续要做,应单独建二期/三期方案,不在一期需求、排期和验收中混入。
|
||||
|
||||
每个节点统一有以下状态:
|
||||
|
||||
- `PENDING`
|
||||
- `RUNNING`
|
||||
- `WAITING_USER`
|
||||
- `SUCCESS`
|
||||
- `FAILED_RETRYABLE`
|
||||
- `FAILED_MANUAL`
|
||||
- `CANCELLED`
|
||||
|
||||
#### 2.3 状态流转规则
|
||||
|
||||
节点状态不仅要有枚举,还要有明确迁移规则,避免同一单被重复推进:
|
||||
|
||||
- `PENDING -> RUNNING`
|
||||
- `RUNNING -> SUCCESS`
|
||||
- `RUNNING -> WAITING_USER`
|
||||
- `RUNNING -> FAILED_RETRYABLE`
|
||||
- `RUNNING -> FAILED_MANUAL`
|
||||
- `WAITING_USER -> RUNNING`
|
||||
- `WAITING_USER -> CANCELLED`
|
||||
- `WAITING_USER -> FAILED_MANUAL`
|
||||
- `FAILED_RETRYABLE -> RUNNING`
|
||||
|
||||
补充约束:
|
||||
|
||||
- 同一节点同一时刻只能有一个有效执行上下文
|
||||
- 手机端“确认并继续”只能成功推进一次,后续重复提交必须返回幂等结果
|
||||
- 节点超时后不能静默停留,必须升级为催办或异常状态
|
||||
- `CANCELLED` 和 `FAILED_MANUAL` 默认不可自动恢复,只能人工明确重开
|
||||
|
||||
每个节点必须记录:
|
||||
|
||||
- 流程单号
|
||||
- 合同编号
|
||||
- 供应商
|
||||
- 当前节点编码
|
||||
- 节点开始/结束时间
|
||||
- 执行结果
|
||||
- 错误码
|
||||
- 错误摘要
|
||||
- 截图/日志路径
|
||||
- 当前处理角色
|
||||
- 当前处理人
|
||||
- 重试次数
|
||||
|
||||
### 3. 手机待办与角色路由
|
||||
|
||||
手机待办默认按**角色路由**,不是固定发给某个人。
|
||||
|
||||
一期角色至少包括:
|
||||
|
||||
- 计划员
|
||||
- 采购
|
||||
- 质管
|
||||
- 运维/机器人管理员
|
||||
|
||||
路由规则:
|
||||
|
||||
- OCR/映射不确定 -> 计划员
|
||||
- 返利中心录入失败且需业务判断 -> 采购
|
||||
- M5 保存失败且提示质管基础数据问题 -> 质管
|
||||
- 机器人登录失败、页面结构变化、系统超时 -> 运维
|
||||
- 多次重试仍失败 -> 运维 + 当前业务角色
|
||||
|
||||
#### 3.1 企业微信一期落地方式
|
||||
|
||||
一期建议将企业微信能力落成:
|
||||
|
||||
- 消息通知
|
||||
- 模版卡片消息更新
|
||||
- H5 处理页/详情页
|
||||
- 用户身份识别与回调处理
|
||||
|
||||
即:企业微信负责“通知 + 打开处理入口 + 提交处理动作”,状态推进仍由状态中心完成。
|
||||
|
||||
说明:
|
||||
|
||||
- 一期文档中的“手机待办”默认指“企业微信消息卡片 + H5 待处理页”的组合能力
|
||||
- 不把企业微信原生待办体系作为一期已确认前提,若后续验证可接,再单独扩展
|
||||
- 所有按钮动作最终都必须调用状态中心接口,并以状态中心结果为准
|
||||
|
||||
企业微信待办支持的动作固定为:
|
||||
|
||||
- `查看详情`
|
||||
- `补录/修改字段`
|
||||
- `确认并继续`
|
||||
- `重新执行当前节点`
|
||||
- `转人工处理`
|
||||
- `终止本单`
|
||||
|
||||
消息内容必须包含:
|
||||
|
||||
- 合同编号
|
||||
- 供应商名称
|
||||
- 当前节点
|
||||
- 错误摘要或待确认原因
|
||||
- 已识别关键字段摘要
|
||||
- 最近一张错误截图
|
||||
- 处理入口链接
|
||||
|
||||
#### 3.2 人工处理字段边界
|
||||
|
||||
手机端“补录/修改字段”不能放成无限编辑,必须收敛为白名单字段。
|
||||
|
||||
一期建议按角色控制:
|
||||
|
||||
- 计划员:合同编号、承付模式、承付参数、计划员归属、合同有效期、OCR 识别结果修正
|
||||
- 采购:供应商映射、货品映射、协议单价、返利中心导入模板相关字段
|
||||
- 质管:仅处理基础资料维护完成确认,不直接改合同业务字段
|
||||
- 运维:只能做重试、转派、失败归档,不修改业务字段值
|
||||
|
||||
以下字段若涉及修改,必须记录修改前后值、修改人、修改时间、修改原因:
|
||||
|
||||
- 合同编号
|
||||
- 供应商名称/编码
|
||||
- 结算方式
|
||||
- 承付模式与承付参数
|
||||
- 货品编码/数量/单价
|
||||
- M5 货源选择
|
||||
|
||||
#### 3.3 人性化待办体验
|
||||
|
||||
为了提升业务接受度,一期建议把以下体验项明确纳入需求,而不是留到后期“有空再优化”:
|
||||
|
||||
- **人话状态**
|
||||
- 不只显示节点编码,要显示“系统正在做什么、通常还要多久、下一步会怎样”
|
||||
- **错误翻译**
|
||||
- 除错误码外,必须给出业务能看懂的错误原因和建议动作
|
||||
- **为什么发给你**
|
||||
- 每条待办都说明“当前归你处理的原因”和“本次需要你确认什么”
|
||||
- **修改前后对比**
|
||||
- 补录字段时同时展示 OCR 原值、系统建议值、用户当前填写值
|
||||
- **提交后即时回执**
|
||||
- 用户点击继续/重试后,立即反馈“已受理、预计多久再次执行、不要重复提交”
|
||||
- **一屏看完最近证据**
|
||||
- 待办详情首页直接展示合同编号、供应商、关键字段、最近错误截图、错误摘要
|
||||
- **预计耗时提示**
|
||||
- 关键节点给出大概执行时长,降低业务等待焦虑
|
||||
- **高风险动作撤回窗口**
|
||||
- 对“终止本单”“转人工处理”等动作,建议提供短时间撤回能力
|
||||
|
||||
### 4. OCR 与规则映射策略
|
||||
|
||||
一期默认**本地 OCR**,并预留适配层,后续可切云 OCR,但一期不依赖云能力。
|
||||
|
||||
OCR 输出必须是结构化 JSON,至少包含:
|
||||
|
||||
- `supplier_name`
|
||||
- `planner_name`
|
||||
- `contract_no`
|
||||
- `settlement_text`
|
||||
- `payment_term_text`
|
||||
- `contract_validity`
|
||||
- `items[]`
|
||||
- `field_confidence{}`
|
||||
|
||||
映射与校验不在 RPA 平台里硬编码,统一放在状态中心配置表中。配置表至少包括:
|
||||
|
||||
- 供应商映射表
|
||||
- 货品映射表
|
||||
- 结算方式映射表
|
||||
- 承付模式映射表
|
||||
- 合同编号规则表
|
||||
- 角色路由规则表
|
||||
- 执行平台路由规则表
|
||||
|
||||
配置治理还需补充:
|
||||
|
||||
- 所有映射配置必须带版本号、生效时间、维护人
|
||||
- 每个 `job` 在开始执行时要绑定一份映射版本快照
|
||||
- 重试默认沿用原映射版本,除非人工明确选择“按最新配置重跑”
|
||||
- 配置表变更要保留审计记录,不能直接覆盖无痕修改
|
||||
|
||||
承付模式映射规则一期采用“**规则优先 + 人工兜底**”:
|
||||
|
||||
- 精确命中字典表达,直接转系统编码
|
||||
- 能识别 `N天` 的标准表达,拆成 `模式代码 + N`
|
||||
- 无法稳定命中或置信度低于阈值,转 `WAITING_USER`
|
||||
- 禁止在低置信度情况下自动猜测继续录入
|
||||
|
||||
### 5. 状态中心接口与数据实体
|
||||
|
||||
重要接口/公共契约如下:
|
||||
|
||||
- `POST /api/jobs`
|
||||
- 创建流程实例,入参为合同文件元数据和发起来源
|
||||
- `POST /api/jobs/{jobId}/nodes/{nodeCode}/start`
|
||||
- RPA 平台或调度器上报节点开始
|
||||
- `POST /api/jobs/{jobId}/nodes/{nodeCode}/complete`
|
||||
- 上报节点成功,附输出数据
|
||||
- `POST /api/jobs/{jobId}/nodes/{nodeCode}/fail`
|
||||
- 上报失败,附错误码、截图、日志、是否可重试
|
||||
- `POST /api/tasks/{taskId}/submit`
|
||||
- 手机端提交人工处理结果
|
||||
- `POST /api/jobs/{jobId}/retry`
|
||||
- 人工或系统触发重试
|
||||
- `POST /api/jobs/{jobId}/handoff`
|
||||
- 转人工或转其他角色
|
||||
- `GET /api/jobs/{jobId}`
|
||||
- 查询整单状态与节点轨迹
|
||||
- `GET /api/tasks/my`
|
||||
- 查询当前用户手机待办
|
||||
|
||||
#### 5.1 幂等与唯一键
|
||||
|
||||
状态中心必须承担幂等控制,不能把“是否重复执行”交给具体 RPA 平台临时判断。
|
||||
|
||||
至少要定义以下唯一键/幂等键:
|
||||
|
||||
- `job` 业务唯一键:建议由 `合同编号 + 合同类型 + 供应商 + 发起来源` 组合生成
|
||||
- 入池幂等键:建议由 `文件哈希 + 文件名 + 上传时间窗` 组合生成
|
||||
- 节点回调幂等键:`jobId + nodeCode + attemptNo + callbackType`
|
||||
- 手机待办提交幂等键:`taskId + action + submitToken`
|
||||
|
||||
系统行为建议固定为:
|
||||
|
||||
- 未完成实例存在时,同业务键默认禁止重复建单
|
||||
- 允许“重跑当前节点”,但不得覆盖历史节点记录
|
||||
- 如需整单重跑,应新建新的 `job`,并关联原 `jobId`
|
||||
- 历史 `job` 的 OCR 结果、截图、日志必须可追溯,不得被新单覆盖
|
||||
|
||||
核心实体至少包括:
|
||||
|
||||
- `job`:一份合同的一次全流程实例
|
||||
- `job_node`:流程实例下的节点执行记录
|
||||
- `user_task`:手机待办/人工处理单
|
||||
- `mapping_config`:业务映射配置
|
||||
- `executor_binding_config`:节点与执行平台绑定配置
|
||||
- `job_artifact`:截图、OCR JSON、导入模板、日志文件
|
||||
- `job_event`:状态变更事件流
|
||||
|
||||
建议增加的关键字段:
|
||||
|
||||
- `job.parent_job_id`:整单重跑或克隆来源
|
||||
- `job.mapping_version`:执行时绑定的配置版本
|
||||
- `job.executor_platform`:本单默认执行平台
|
||||
- `job.source_file_hash`:源文件去重标识
|
||||
- `job_node.attempt_no`:节点第几次尝试
|
||||
- `job_node.executor_platform`:本次节点尝试由哪个平台执行
|
||||
- `job_node.executor_run_id`:平台侧任务号/运行号
|
||||
- `user_task.submit_token`:防重复提交令牌
|
||||
- `job_artifact.artifact_type`:OCR JSON、截图、模板、日志等分类
|
||||
|
||||
### 6. 节点执行与重试策略
|
||||
|
||||
重试分三类:
|
||||
|
||||
- **系统型自动重试**
|
||||
- 页面超时、偶发网络异常、系统短暂不可用
|
||||
- 默认自动重试 1 到 2 次
|
||||
- **业务型人工确认后重试**
|
||||
- 字段识别不确定、映射缺失、返利中心校验不通过
|
||||
- 手机待办确认后重试当前节点
|
||||
- **人工接管**
|
||||
- 连续失败、逻辑分支超出机器人能力、系统规则变化
|
||||
- 标记为 `FAILED_MANUAL`,由人工线下处理,流程保留上下文
|
||||
|
||||
必须明确的失败分流:
|
||||
|
||||
- OCR 失败 -> 待办给计划员
|
||||
- 合同编号无法匹配 -> 待办给计划员
|
||||
- 返利中心模板导入失败 -> 待办给采购
|
||||
- M5 因质管基础数据不全无法保存 -> 待办给质管
|
||||
- 登录失败/控件定位失败/页面结构变化 -> 待办给运维
|
||||
- 平台能力不满足当前节点要求 -> 待办给运维/项目负责人评估是否切换执行平台
|
||||
|
||||
#### 6.1 超时与升级策略
|
||||
|
||||
除失败分流外,还需要定义“长时间没人处理”与“节点卡住”的升级规则。
|
||||
|
||||
建议一期先定简单规则:
|
||||
|
||||
- OCR/映射类待办超过 30 分钟未处理 -> 再次通知当前角色
|
||||
- 超过 2 小时未处理 -> 升级通知项目负责人/备岗人员
|
||||
- 返利中心/M5 运行中节点超过阈值未回调 -> 标记超时异常并通知运维
|
||||
- 夜间非工作时段是否升级,需按业务班次另行配置
|
||||
|
||||
所有升级动作都必须进入 `job_event` 留痕。
|
||||
|
||||
### 7. 业务可见性设计
|
||||
|
||||
一期不单独建设复杂 BI 看板,但状态中心必须支持后续扩展。当前可见性分两层:
|
||||
|
||||
- **企业微信消息流**
|
||||
- 面向业务个人,解决“手机知道发生了什么”
|
||||
- **状态中心查询页**
|
||||
- 面向运维/项目负责人,按合同编号查看节点轨迹、截图、当前责任方、重试记录
|
||||
|
||||
查询页至少支持:
|
||||
|
||||
- 按日期/供应商/合同编号/状态筛选
|
||||
- 查看当前卡点节点
|
||||
- 查看待办归属角色与处理记录
|
||||
- 查看 OCR 结果和映射结果
|
||||
- 查看返利中心/M5 错误截图与日志摘要
|
||||
|
||||
#### 7.1 成功判定与结果回写口径
|
||||
|
||||
一期必须明确“节点成功”不是页面点完就算成功,而是拿到对应业务对象与校验结果。
|
||||
|
||||
至少要求:
|
||||
|
||||
- 合同系统成功:返回合同系统单号,且能查询到对应状态
|
||||
- 返利中心总单成功:返回总单编号,且关键字段与输入一致
|
||||
- 返利中心细单导入成功:返回导入行数、失败行数、失败原因摘要
|
||||
- M5 成功:返回采购订单号
|
||||
- 收货通知单生成成功:返回通知单号
|
||||
|
||||
建议状态中心在节点成功回调中强制要求携带:
|
||||
|
||||
- 业务单号
|
||||
- 关键字段摘要
|
||||
- 成功截图
|
||||
- 校验结果
|
||||
|
||||
如仅有“操作已提交”但未取得业务编号,应落为中间态或待校验态,不能直接记 `SUCCESS`。
|
||||
|
||||
#### 7.3 多 RPA 切换策略
|
||||
|
||||
为了形成长期竞争力,一期方案支持“执行平台可替换”,但切换策略必须保守可控:
|
||||
|
||||
- 切换单位以“新单”或“节点配置”优先,不以“运行中半节点切换”为目标
|
||||
- 每个节点先声明所需能力标签,例如 `desktop_ui`、`file_upload`、`excel_template`、`http_callback`
|
||||
- 每个执行平台声明自己支持的能力标签
|
||||
- 状态中心根据节点所需能力和平台可用能力决定路由
|
||||
- 平台切换前必须通过统一验证清单:登录、检索、模板导入、截图/日志、HTTP 回调
|
||||
|
||||
这样做的目的不是追求脚本 100% 通用,而是确保:
|
||||
|
||||
- 业务流程不被单一厂商绑定
|
||||
- 状态、待办、规则、证据统一留在我方系统
|
||||
- 当成本、授权或稳定性发生变化时,可平滑更换底层执行平台
|
||||
|
||||
#### 7.4 面向业务方的竞争价值
|
||||
|
||||
多 RPA 可切换的价值,不只是技术选型灵活,更要落实到业务能感受到的收益:
|
||||
|
||||
- 不因单一工具变化而中断业务流程
|
||||
- 同一套手机待办、同一套状态查询,不需要业务重新学一套系统
|
||||
- 平台替换时,合同轨迹、截图、历史处理记录不会丢
|
||||
- 某个平台不稳定时,可逐步把新单切到更稳的平台,业务侧感知更平滑
|
||||
- 后续新增流程时,优先复用既有状态中心和手机协同能力,减少重复建设
|
||||
|
||||
#### 7.2 证据留存与权限控制
|
||||
|
||||
一期虽然不做复杂平台,但必须先把资料、日志和截图的留存规则定下来。
|
||||
|
||||
至少要明确:
|
||||
|
||||
- 合同原件、OCR JSON、返利模板、执行截图、运行日志分别存放在哪里
|
||||
- 文件目录命名是否按 `jobId/节点/时间` 归档
|
||||
- 保留时长与清理策略
|
||||
- 哪些角色可查看合同原件,哪些只能看摘要/脱敏截图
|
||||
- 企业微信消息中是否只放缩略图和链接,不直接下发完整合同文件
|
||||
|
||||
涉及合同和供应商信息时,建议默认:
|
||||
|
||||
- 手机端只展示必要摘要
|
||||
- 完整附件放状态中心受控页面查看
|
||||
- 所有下载和查看动作记录审计日志
|
||||
|
||||
## Test Plan
|
||||
|
||||
### 核心流程场景
|
||||
|
||||
1. 合同文件命名规范、字段完整、OCR 置信度高
|
||||
- 预期:全自动跑通到 M5 完成,无人工介入
|
||||
2. OCR 成功但承付模式未命中字典
|
||||
- 预期:转计划员手机待办,补录后继续执行
|
||||
3. 返利中心细单导入模板格式错误
|
||||
- 预期:节点失败,通知采购,可修正后重试当前节点
|
||||
4. M5 保存时报质管未维护
|
||||
- 预期:转质管手机待办,修复后重试
|
||||
5. 当前执行平台登录返利中心失败
|
||||
- 预期:自动重试后仍失败,转运维待办
|
||||
6. 业务在手机端点击“转人工处理”
|
||||
- 预期:流程进入 `FAILED_MANUAL`,保留完整上下文
|
||||
7. 同一合同重复发起
|
||||
- 预期:状态中心拦截或提示重复实例,避免重复录单
|
||||
8. 合同系统未审核通过
|
||||
- 预期:流程停在审核前置节点,不进入返利中心
|
||||
9. 企业微信待办重复点击提交
|
||||
- 预期:只推进一次,其余请求返回幂等结果
|
||||
10. 返利中心细单部分导入成功、部分失败
|
||||
- 预期:记录成功/失败行明细,转采购判断是否修正后续跑
|
||||
11. 入池文件命名不规范或缺页
|
||||
- 预期:不启动任何 RPA 平台,直接转计划员待办
|
||||
12. 待办长时间未处理
|
||||
- 预期:按升级规则再次通知并记录事件
|
||||
13. 新单切换到另一执行平台
|
||||
- 预期:状态中心仍可统一建单、回调、留痕与查询
|
||||
14. 用户提交待办后立即收到受理回执
|
||||
- 预期:用户知道系统已接单、无需重复点击
|
||||
15. 用户查看待办详情时可直接看懂错误原因
|
||||
- 预期:无需再去群里追问“这条待办要我干嘛”
|
||||
|
||||
### 接口与状态场景
|
||||
|
||||
1. 节点开始、成功、失败回调顺序正确
|
||||
2. 同一节点重复回调具备幂等处理
|
||||
3. 手机待办提交后只能推进一次,避免重复操作
|
||||
4. 重试后产生新的节点执行记录,但保留历史失败记录
|
||||
5. 企业微信消息与状态中心中的责任人、状态一致
|
||||
6. 整单重跑会创建新的 `job`,并保留原 `job` 关联
|
||||
7. 节点超时后会触发升级通知,不会无限停留在 `RUNNING`
|
||||
8. 不同执行平台回调到状态中心时,状态口径与证据口径保持一致
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 业务能在手机端知道每单当前跑到哪一步
|
||||
- 关键不确定项不会被机器人“猜着填”
|
||||
- 任何失败都能定位到具体节点、原因、截图和责任角色
|
||||
- 一份合同从入池到 M5 完成的轨迹可完整追溯
|
||||
- 能区分“本次只是操作提交”与“业务单号已真正生成”
|
||||
- 同一合同不会因重复上传或重复点击而生成重复业务结果
|
||||
- 不依赖 OpenClaw,也不依赖单一 RPA 厂商内置 OCR 即可完成一期
|
||||
|
||||
## Assumptions
|
||||
|
||||
- 一期默认只接入一个主执行平台上线,但架构上不与单一平台绑定
|
||||
- 影刀、艺赛旗等候选平台是否正式接入,需以项目 POC 验证结果为准
|
||||
- OpenClaw 一期不使用
|
||||
- 企业微信可申请正式企业应用并支持消息、回调和待办入口
|
||||
- 轻量服务技术栈固定为 Python + MySQL
|
||||
- OCR 一期默认本地部署,优先按 PaddleOCR 设计
|
||||
- 一期范围只覆盖 `合同 -> 返利中心 -> M5`
|
||||
- 映射规则不再依赖 Excel,统一进入状态中心配置表
|
||||
- 企业微信一期至少可提供消息卡片、H5 处理入口、身份识别与回调能力
|
||||
- 合同扫描件默认来自共享目录或状态中心手工入池,不考虑邮件自动收件作为一期入口
|
||||
- 业务目标是“尽量全自动”,但 OCR/映射不确定项必须转手机待办,不做自动猜测
|
||||
|
|
@ -0,0 +1,910 @@
|
|||
# 采购主链 RPA 一期 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 建成采购主链一期的轻量状态中心、统一 RPA 执行适配层、企业微信手机协同入口,以及支撑 OCR、返利中心、M5 流程追踪的最小可用中台。
|
||||
|
||||
**Architecture:** 采用“RPA 执行平台 + Python/MySQL 状态中心 + 本地 OCR + 企业微信 H5 协同”的分层方案。状态中心负责建单、节点状态机、幂等、待办、日志、审计和执行平台路由,底层 RPA 平台只负责外部系统操作与结果回传,企业微信只负责通知与手机端操作入口。
|
||||
|
||||
**Tech Stack:** Python、MySQL、HTTP API、RPA 执行平台(影刀 / 艺赛旗等)、企业微信应用、本地 OCR(PaddleOCR 优先)
|
||||
|
||||
---
|
||||
|
||||
## 0. 使用说明
|
||||
|
||||
本文件既可作为技术实施底稿,也可作为后续拆更细执行计划的母稿。
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
本文面向开发、测试、实施和项目负责人,目标是把 [PLAN.md](/Users/yuuuu/Documents/AutoRpa/资料/PLAN.md) 进一步落成“可排期、可拆人、可建库、可对接口”的实施稿。
|
||||
|
||||
默认前提:
|
||||
|
||||
- 一期范围只覆盖 `合同扫描件/合同系统 -> 返利中心采购协议 -> M5 采购订单`
|
||||
- RPA 执行平台负责页面操作
|
||||
- Python + MySQL 负责状态中心
|
||||
- 企业微信负责消息通知和 H5 处理入口
|
||||
- OCR 一期采用本地部署方案
|
||||
|
||||
如果后续要正式开工,建议以本文件作为技术排期底稿,再补充具体工期、负责人和环境信息。
|
||||
|
||||
---
|
||||
|
||||
## 2. 推荐项目结构
|
||||
|
||||
当前仓库还没有正式代码结构,一期建议直接按下面的边界建工程,避免把流程编排、RPA 平台回调、企业微信回调、查询页混在一起。
|
||||
|
||||
```text
|
||||
AutoRpa/
|
||||
├── 资料/
|
||||
│ ├── PLAN.md
|
||||
│ ├── 一期开发任务清单_表结构_API草案.md
|
||||
│ └── 一期需求简版说明_业务版.md
|
||||
├── backend/
|
||||
│ ├── app/
|
||||
│ │ ├── main.py
|
||||
│ │ ├── config.py
|
||||
│ │ ├── db.py
|
||||
│ │ ├── models/
|
||||
│ │ │ ├── job.py
|
||||
│ │ │ ├── job_node.py
|
||||
│ │ │ ├── user_task.py
|
||||
│ │ │ ├── mapping_config.py
|
||||
│ │ │ ├── job_artifact.py
|
||||
│ │ │ └── job_event.py
|
||||
│ │ ├── schemas/
|
||||
│ │ │ ├── job.py
|
||||
│ │ │ ├── task.py
|
||||
│ │ │ ├── node_callback.py
|
||||
│ │ │ └── wecom.py
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── jobs.py
|
||||
│ │ │ ├── tasks.py
|
||||
│ │ │ ├── admin.py
|
||||
│ │ │ └── wecom.py
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── intake_service.py
|
||||
│ │ │ ├── workflow_service.py
|
||||
│ │ │ ├── retry_service.py
|
||||
│ │ │ ├── task_service.py
|
||||
│ │ │ ├── executor_router.py
|
||||
│ │ │ ├── ocr_service.py
|
||||
│ │ │ ├── mapping_service.py
|
||||
│ │ │ ├── artifact_service.py
|
||||
│ │ │ └── wecom_service.py
|
||||
│ │ ├── integrations/
|
||||
│ │ │ ├── rpa/
|
||||
│ │ │ │ ├── base_adapter.py
|
||||
│ │ │ │ ├── yingdao_adapter.py
|
||||
│ │ │ │ └── isearch_adapter.py
|
||||
│ │ │ ├── paddle_ocr_client.py
|
||||
│ │ │ └── wecom_client.py
|
||||
│ │ └── views/
|
||||
│ │ └── h5_task_page.py
|
||||
│ ├── migrations/
|
||||
│ └── tests/
|
||||
│ ├── api/
|
||||
│ ├── services/
|
||||
│ └── integration/
|
||||
├── agent-contract/
|
||||
│ ├── node_callback_examples.json
|
||||
│ └── error_codes.md
|
||||
└── deployment/
|
||||
├── env.example
|
||||
├── nginx.conf
|
||||
└── systemd/
|
||||
```
|
||||
|
||||
边界原则:
|
||||
|
||||
- `api/` 只收发请求,不放业务流程判断
|
||||
- `services/` 负责状态迁移、幂等、路由、重试、消息发送
|
||||
- `integrations/` 只封装外部系统调用
|
||||
- `executor_router.py` 统一决定节点该交给哪个平台执行
|
||||
- `models/` 和 `schemas/` 分开,避免数据库对象和接口对象互相污染
|
||||
|
||||
---
|
||||
|
||||
## 3. 自有能力边界与多平台策略
|
||||
|
||||
我们自己的能力必须沉淀在状态中心中:
|
||||
|
||||
- 流程主状态
|
||||
- 合同入池、判重、幂等
|
||||
- OCR 归档和映射配置
|
||||
- 手机待办、人工协同、角色路由
|
||||
- 成功判定、错误码体系、证据归档
|
||||
- 多平台统一查询和统一审计
|
||||
|
||||
RPA 平台提供的是底层执行能力:
|
||||
|
||||
- 桌面/UI 自动化
|
||||
- 文件上传下载
|
||||
- 表单填写与模板导入
|
||||
- 截图、日志、录屏
|
||||
- 对状态中心发起回调
|
||||
|
||||
多平台切换边界必须明确:
|
||||
|
||||
- 支持“按新单切换平台”
|
||||
- 支持“按节点配置平台”
|
||||
- 不支持“半节点执行过程中跨平台接力”
|
||||
- 平台切换前先跑统一 POC 用例
|
||||
|
||||
---
|
||||
|
||||
## 4. 里程碑与开发任务清单
|
||||
|
||||
## Milestone 0:项目初始化与基础设施
|
||||
|
||||
目标:把后端服务、数据库连接、配置管理、日志规范先搭起来。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 创建 `backend/app/main.py`,跑通基础 HTTP 服务
|
||||
2. 创建 `backend/app/config.py`,统一读取数据库、OCR、企业微信、RPA 平台回调密钥配置
|
||||
3. 创建 `backend/app/db.py`,封装 MySQL 连接和事务入口
|
||||
4. 建立 `backend/tests/` 目录,先接入最基础的接口测试框架
|
||||
5. 创建 `deployment/env.example`,列出所有必需环境变量
|
||||
6. 定义统一日志字段:`trace_id`、`job_id`、`node_code`、`task_id`
|
||||
|
||||
交付物:
|
||||
|
||||
- 服务能启动
|
||||
- 健康检查接口可用
|
||||
- 数据库连接可用
|
||||
- 开发/测试环境变量模板可发给团队
|
||||
|
||||
## Milestone 1:入池建单与判重
|
||||
|
||||
目标:把“共享目录文件 -> 状态中心建单 -> 判重 -> 可追踪 job”跑通。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 建 `job`、`job_artifact`、`job_event` 三张核心表
|
||||
2. 开发 `POST /api/jobs` 建单接口
|
||||
3. 开发共享目录文件入池服务,读取文件基础元数据
|
||||
4. 实现文件哈希计算与 `source_file_hash` 去重逻辑
|
||||
5. 实现业务唯一键判重逻辑:`合同编号 + 合同类型 + 供应商 + 发起来源`
|
||||
6. 入池失败时生成事件记录和人工待处理状态
|
||||
7. 支持通过状态中心页面手工建单,作为共享目录监听的兜底入口
|
||||
|
||||
交付物:
|
||||
|
||||
- 同一合同重复上传不会生成多条未完成流程
|
||||
- 文件命名异常可被识别并阻断
|
||||
- 每次建单都有 `job_id`
|
||||
|
||||
## Milestone 2:OCR 与映射规则
|
||||
|
||||
目标:把扫描件变成结构化字段,并明确哪些能自动映射、哪些必须转人工。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 创建 `mapping_config` 表和版本字段
|
||||
2. 建立 OCR 适配接口,统一输出 JSON
|
||||
3. 保存原始 OCR JSON 到 `job_artifact`
|
||||
4. 实现供应商、货品、结算方式、承付模式映射服务
|
||||
5. 实现置信度阈值判断和低置信度转待办逻辑
|
||||
6. 为每个 `job` 绑定 `mapping_version`
|
||||
7. 把字段识别结果、映射结果写入 `job_event`
|
||||
|
||||
交付物:
|
||||
|
||||
- OCR 结果可追溯
|
||||
- 映射规则支持版本化
|
||||
- 低置信度不会自动猜测后继续执行
|
||||
|
||||
## Milestone 3:流程编排与节点状态机
|
||||
|
||||
目标:把节点开始、成功、失败、待人工、重试这些规则稳定下来。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 创建 `job_node` 表
|
||||
2. 定义节点状态枚举与状态迁移规则
|
||||
3. 定义节点尝试号 `attempt_no`
|
||||
4. 开发节点开始接口 `POST /api/jobs/{jobId}/nodes/{nodeCode}/start`
|
||||
5. 开发节点成功接口 `POST /api/jobs/{jobId}/nodes/{nodeCode}/complete`
|
||||
6. 开发节点失败接口 `POST /api/jobs/{jobId}/nodes/{nodeCode}/fail`
|
||||
7. 实现节点回调幂等控制
|
||||
8. 实现节点超时扫描与升级策略
|
||||
|
||||
交付物:
|
||||
|
||||
- 节点不会重复推进
|
||||
- 同一节点重复回调时可安全去重
|
||||
- 失败与超时都能落事件
|
||||
|
||||
## Milestone 4:RPA 执行适配层与统一契约
|
||||
|
||||
目标:让底层 RPA 平台只做执行,不做流程主状态判断,并具备可切换能力。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 输出 `agent-contract/node_callback_examples.json`
|
||||
2. 定义统一的 `base_adapter` 接口
|
||||
3. 实现 `yingdao_adapter` 与 `isearch_adapter` 骨架
|
||||
4. 定义平台回调鉴权方式
|
||||
5. 约定统一回传字段:错误码、截图路径、日志路径、录屏路径、输出业务单号
|
||||
6. 约定节点编码字典,例如 `OCR_EXTRACT`、`REBATE_IMPORT_ITEMS`、`M5_CREATE_ORDER`
|
||||
7. 定义平台失败错误码分层:系统类、业务类、配置类、页面变化类、能力不满足类
|
||||
8. 为每个节点定义能力标签,例如 `desktop_ui`、`file_upload`、`excel_template`、`http_callback`
|
||||
9. 制作联调用的假回调脚本或 Postman 集合
|
||||
|
||||
交付物:
|
||||
|
||||
- 不同平台都可以按统一 JSON 回调
|
||||
- 开发和平台实施同学对节点编码、错误码、能力标签理解一致
|
||||
|
||||
## Milestone 5:手机待办、企业微信与 H5 页面
|
||||
|
||||
目标:让业务能在手机端收到通知、看上下文、补字段、继续或转人工。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 创建 `user_task` 表
|
||||
2. 实现角色路由规则:计划员、采购、质管、运维
|
||||
3. 开发 `GET /api/tasks/my`
|
||||
4. 开发 `POST /api/tasks/{taskId}/submit`
|
||||
5. 开发 `POST /api/jobs/{jobId}/handoff`
|
||||
6. 开发企业微信消息发送与模版卡片更新
|
||||
7. 开发“人话状态”和“错误翻译”展示逻辑
|
||||
8. 开发 H5 处理页,展示关键字段、截图、待办动作
|
||||
9. 开发“为什么发给你”“修改前后对比”“提交后即时回执”
|
||||
10. 接入企业微信身份识别
|
||||
11. 为待办提交增加 `submit_token`,防止重复点击重复推进
|
||||
12. 为高风险动作增加短时间撤回设计
|
||||
|
||||
交付物:
|
||||
|
||||
- 业务能在企业微信打开待办详情
|
||||
- 人工提交只能推进一次
|
||||
- 所有修改留痕
|
||||
- 用户能看懂待办原因、知道该怎么处理、处理后知道系统已受理
|
||||
|
||||
## Milestone 6:查询页、审计与运维能力
|
||||
|
||||
目标:让项目负责人和运维能查问题、追流程、看证据。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 开发 `GET /api/jobs/{jobId}`
|
||||
2. 开发按合同编号、供应商、日期、状态筛选列表接口
|
||||
3. 展示节点轨迹、责任角色、重试记录
|
||||
4. 展示 OCR 结果、映射结果、截图和日志摘要
|
||||
5. 增加人工重试接口 `POST /api/jobs/{jobId}/retry`
|
||||
6. 增加“整单重跑”能力,生成新的 `job`
|
||||
7. 增加查看/下载审计日志
|
||||
|
||||
交付物:
|
||||
|
||||
- 出问题时能快速看出卡在哪一层
|
||||
- 能区分当前实例和历史重跑实例
|
||||
|
||||
## Milestone 7:测试、联调与上线准备
|
||||
|
||||
目标:把关键链路和关键失败场景都压一遍。
|
||||
|
||||
任务清单:
|
||||
|
||||
1. 覆盖全自动正向场景
|
||||
2. 覆盖 OCR 低置信度转计划员场景
|
||||
3. 覆盖返利中心模板部分失败场景
|
||||
4. 覆盖 M5 质管基础资料不全场景
|
||||
5. 覆盖不同平台回调重复提交场景
|
||||
6. 覆盖企业微信待办重复提交场景
|
||||
7. 覆盖共享目录重复上传场景
|
||||
8. 覆盖节点超时升级场景
|
||||
9. 覆盖新单切换执行平台场景
|
||||
10. 输出上线切换清单、回滚清单、账号权限清单
|
||||
|
||||
交付物:
|
||||
|
||||
- 联调报告
|
||||
- UAT 场景清单
|
||||
- 上线/回滚 checklist
|
||||
|
||||
---
|
||||
|
||||
## 5. 表结构草案
|
||||
|
||||
说明:下面是一期最低可用表结构,不追求一次做成大而全平台,先满足幂等、留痕、查询和待办协同。
|
||||
|
||||
### 4.1 `job`
|
||||
|
||||
一份合同的一次流程实例。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| job_id | varchar(64) unique | 对外流程单号 |
|
||||
| business_key | varchar(128) | 业务唯一键 |
|
||||
| parent_job_id | varchar(64) null | 重跑来源 |
|
||||
| source_type | varchar(32) | shared_folder/manual_upload |
|
||||
| source_file_name | varchar(255) | 原始文件名 |
|
||||
| source_file_hash | varchar(64) | 原始文件哈希 |
|
||||
| contract_type | varchar(32) | 年度协议/批次合同 |
|
||||
| contract_no | varchar(64) null | 合同编号 |
|
||||
| supplier_name | varchar(128) null | 供应商名称 |
|
||||
| planner_name | varchar(64) null | 计划员 |
|
||||
| current_node_code | varchar(64) | 当前节点 |
|
||||
| status | varchar(32) | PENDING/RUNNING/WAITING_USER/... |
|
||||
| mapping_version | varchar(64) null | 映射配置版本 |
|
||||
| executor_platform | varchar(32) 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(32) null | 当前责任角色 |
|
||||
| current_assignee | varchar(64) null | 当前责任人 |
|
||||
| created_by | varchar(64) | 发起人或系统 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
| finished_at | datetime null | 完成时间 |
|
||||
|
||||
索引建议:
|
||||
|
||||
- `uniq_job_business_open`:`business_key + status`
|
||||
- `idx_job_contract_no`
|
||||
- `idx_job_supplier_status`
|
||||
- `idx_job_created_at`
|
||||
|
||||
### 4.2 `job_node`
|
||||
|
||||
记录每个节点每次尝试。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| job_id | varchar(64) | 流程单号 |
|
||||
| node_code | varchar(64) | 节点编码 |
|
||||
| attempt_no | int | 第几次执行 |
|
||||
| status | varchar(32) | 节点状态 |
|
||||
| executor_platform | varchar(32) null | 实际执行平台 |
|
||||
| executor_run_id | varchar(64) null | 平台侧任务号 |
|
||||
| started_at | datetime null | 开始时间 |
|
||||
| ended_at | datetime null | 结束时间 |
|
||||
| duration_ms | bigint null | 耗时 |
|
||||
| error_code | varchar(64) null | 错误码 |
|
||||
| error_summary | varchar(500) null | 错误摘要 |
|
||||
| role_at_failure | varchar(32) null | 失败时责任角色 |
|
||||
| assignee_at_failure | varchar(64) null | 失败时责任人 |
|
||||
| output_json | json null | 节点输出 |
|
||||
| callback_idempotency_key | varchar(128) null | 回调幂等键 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
|
||||
索引建议:
|
||||
|
||||
- `uniq_job_node_attempt`
|
||||
- `idx_job_node_status`
|
||||
|
||||
### 4.3 `user_task`
|
||||
|
||||
企业微信/H5 里的人工处理单。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| task_id | varchar(64) unique | 待办编号 |
|
||||
| job_id | varchar(64) | 所属流程 |
|
||||
| node_code | varchar(64) | 对应节点 |
|
||||
| role_code | varchar(32) | 目标角色 |
|
||||
| assignee_user_id | varchar(64) null | 当前处理人 |
|
||||
| title | varchar(255) | 待办标题 |
|
||||
| reason_type | varchar(32) | OCR_LOW_CONF/REBATE_FAIL/... |
|
||||
| reason_summary | varchar(500) | 待办原因 |
|
||||
| reason_user_friendly | varchar(500) | 面向业务的人话说明 |
|
||||
| why_me_text | varchar(255) | 为什么发给当前角色/当前人 |
|
||||
| editable_fields_json | json | 白名单字段 |
|
||||
| action_schema_json | json | 允许动作 |
|
||||
| comparison_snapshot_json | json null | 原值/建议值/当前值对比 |
|
||||
| status | varchar(32) | OPEN/SUBMITTED/CANCELLED/EXPIRED |
|
||||
| submit_token | varchar(64) | 防重复令牌 |
|
||||
| submitted_action | varchar(32) null | 继续/重试/转人工/终止 |
|
||||
| submitted_payload_json | json null | 人工提交内容 |
|
||||
| submitted_by | varchar(64) null | 提交人 |
|
||||
| submitted_at | datetime null | 提交时间 |
|
||||
| due_at | datetime null | 超时升级时间 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
|
||||
### 4.4 `mapping_config`
|
||||
|
||||
映射规则表,一期可以先做统一表,后续再拆子表。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| config_key | varchar(128) | 业务键 |
|
||||
| config_type | varchar(32) | supplier/item/settlement/payment_term/route |
|
||||
| source_value | varchar(255) | 来源值 |
|
||||
| target_value | varchar(255) | 目标值 |
|
||||
| extra_json | json null | 扩展参数 |
|
||||
| version_no | varchar(64) | 版本号 |
|
||||
| is_active | tinyint | 是否生效 |
|
||||
| effective_from | datetime | 生效时间 |
|
||||
| effective_to | datetime null | 失效时间 |
|
||||
| updated_by | varchar(64) | 更新人 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
### 4.5 `executor_binding_config`
|
||||
|
||||
节点与执行平台路由配置。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| node_code | varchar(64) | 节点编码 |
|
||||
| preferred_platform | varchar(32) | 首选平台 |
|
||||
| fallback_platform | varchar(32) null | 备用平台 |
|
||||
| required_capabilities_json | json | 所需能力标签 |
|
||||
| route_policy | varchar(32) | FIXED / PREFERRED / FALLBACK |
|
||||
| is_active | tinyint | 是否生效 |
|
||||
| updated_by | varchar(64) | 更新人 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
### 4.6 `job_artifact`
|
||||
|
||||
存证表。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| job_id | varchar(64) | 流程单号 |
|
||||
| node_code | varchar(64) null | 所属节点 |
|
||||
| artifact_type | varchar(32) | SOURCE_FILE/OCR_JSON/SCREENSHOT/TEMPLATE/LOG |
|
||||
| storage_path | varchar(500) | 存储路径 |
|
||||
| file_name | varchar(255) | 文件名 |
|
||||
| file_hash | varchar(64) null | 文件哈希 |
|
||||
| is_masked | tinyint | 是否脱敏 |
|
||||
| visible_roles_json | json | 可见角色 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
|
||||
### 4.7 `job_event`
|
||||
|
||||
事件流表,用来查轨迹。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| id | bigint PK | 主键 |
|
||||
| job_id | varchar(64) | 流程单号 |
|
||||
| node_code | varchar(64) null | 节点 |
|
||||
| event_type | varchar(64) | JOB_CREATED/NODE_FAILED/TASK_CREATED/... |
|
||||
| event_summary | varchar(500) | 事件摘要 |
|
||||
| event_payload_json | json null | 事件详情 |
|
||||
| operator_type | varchar(32) | SYSTEM/ROBOT/USER |
|
||||
| operator_id | varchar(64) | 操作人 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
## 5. MySQL DDL 示例
|
||||
|
||||
以下 DDL 不是最终生产版,但足够作为建库初稿。
|
||||
|
||||
```sql
|
||||
CREATE TABLE job (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
job_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
business_key VARCHAR(128) NOT NULL,
|
||||
parent_job_id VARCHAR(64) NULL,
|
||||
source_type VARCHAR(32) NOT NULL,
|
||||
source_file_name VARCHAR(255) NOT NULL,
|
||||
source_file_hash VARCHAR(64) NOT NULL,
|
||||
contract_type VARCHAR(32) NOT NULL,
|
||||
contract_no VARCHAR(64) NULL,
|
||||
supplier_name VARCHAR(128) NULL,
|
||||
planner_name VARCHAR(64) NULL,
|
||||
current_node_code VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
mapping_version VARCHAR(64) NULL,
|
||||
executor_platform VARCHAR(32) 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(32) NULL,
|
||||
current_assignee VARCHAR(64) NULL,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
finished_at DATETIME NULL,
|
||||
KEY idx_job_contract_no (contract_no),
|
||||
KEY idx_job_supplier_status (supplier_name, status),
|
||||
KEY idx_job_created_at (created_at)
|
||||
);
|
||||
|
||||
CREATE TABLE job_node (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
job_id VARCHAR(64) NOT NULL,
|
||||
node_code VARCHAR(64) NOT NULL,
|
||||
attempt_no INT NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
executor_platform VARCHAR(32) NULL,
|
||||
executor_run_id VARCHAR(64) NULL,
|
||||
started_at DATETIME NULL,
|
||||
ended_at DATETIME NULL,
|
||||
duration_ms BIGINT NULL,
|
||||
error_code VARCHAR(64) NULL,
|
||||
error_summary VARCHAR(500) NULL,
|
||||
role_at_failure VARCHAR(32) NULL,
|
||||
assignee_at_failure VARCHAR(64) NULL,
|
||||
output_json JSON NULL,
|
||||
callback_idempotency_key VARCHAR(128) NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
UNIQUE KEY uniq_job_node_attempt (job_id, node_code, attempt_no)
|
||||
);
|
||||
|
||||
CREATE TABLE user_task (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
task_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
job_id VARCHAR(64) NOT NULL,
|
||||
node_code VARCHAR(64) NOT NULL,
|
||||
role_code VARCHAR(32) NOT NULL,
|
||||
assignee_user_id VARCHAR(64) NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
reason_type VARCHAR(32) NOT NULL,
|
||||
reason_summary VARCHAR(500) NOT NULL,
|
||||
editable_fields_json JSON NOT NULL,
|
||||
action_schema_json JSON NOT NULL,
|
||||
status VARCHAR(32) NOT NULL,
|
||||
submit_token VARCHAR(64) NOT NULL,
|
||||
submitted_action VARCHAR(32) NULL,
|
||||
submitted_payload_json JSON NULL,
|
||||
submitted_by VARCHAR(64) NULL,
|
||||
submitted_at DATETIME NULL,
|
||||
due_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API 草案
|
||||
|
||||
说明:接口统一由状态中心对外提供,RPA 平台、H5 页面、企业微信回调都走状态中心,不允许直接改数据库。
|
||||
|
||||
鉴权建议:
|
||||
|
||||
- 内部服务接口:`Authorization: Bearer <service-token>`
|
||||
- RPA 平台回调接口:`X-Agent-Token`
|
||||
- H5 用户接口:企业微信身份换取后端 session 或 JWT
|
||||
- 幂等写接口:增加 `X-Idempotency-Key`
|
||||
|
||||
### 6.1 建单接口
|
||||
|
||||
`POST /api/jobs`
|
||||
|
||||
用途:
|
||||
|
||||
- 共享目录监听器建单
|
||||
- 状态中心手工上传建单
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceType": "shared_folder",
|
||||
"sourceFileName": "20260625_国药控股_张三_批次合同_01.pdf",
|
||||
"sourceFileHash": "6f6f5d8c...",
|
||||
"contractType": "BATCH",
|
||||
"supplierName": "国药控股",
|
||||
"plannerName": "张三",
|
||||
"executorPlatform": "YINGDAO",
|
||||
"createdBy": "system-folder-watcher",
|
||||
"artifacts": [
|
||||
{
|
||||
"artifactType": "SOURCE_FILE",
|
||||
"storagePath": "/data/jobs/inbox/20260625_国药控股_张三_批次合同_01.pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"jobId": "JOB202606260001",
|
||||
"status": "PENDING",
|
||||
"currentNodeCode": "FILE_INTAKE"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 节点开始
|
||||
|
||||
`POST /api/jobs/{jobId}/nodes/{nodeCode}/start`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"attemptNo": 1,
|
||||
"executorPlatform": "YINGDAO",
|
||||
"operator": "yingdao-agent-01",
|
||||
"startedAt": "2026-06-26T10:00:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 节点成功
|
||||
|
||||
`POST /api/jobs/{jobId}/nodes/{nodeCode}/complete`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"attemptNo": 1,
|
||||
"executorPlatform": "YINGDAO",
|
||||
"executorRunId": "YD-RUN-20260626-001",
|
||||
"endedAt": "2026-06-26T10:03:25+08:00",
|
||||
"businessRefs": {
|
||||
"contractSystemNo": "HT20260626018",
|
||||
"rebateOrderNo": "FL20260626009",
|
||||
"m5OrderNo": null,
|
||||
"receiptNoticeNo": null
|
||||
},
|
||||
"summary": {
|
||||
"importedItemCount": 18,
|
||||
"failedItemCount": 0
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"artifactType": "SCREENSHOT",
|
||||
"storagePath": "/data/jobs/JOB202606260001/REBATE_IMPORT_ITEMS/success.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 如果没有业务编号且节点本身要求有编号,则不能直接记 `SUCCESS`
|
||||
|
||||
### 6.4 节点失败
|
||||
|
||||
`POST /api/jobs/{jobId}/nodes/{nodeCode}/fail`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"attemptNo": 1,
|
||||
"executorPlatform": "YINGDAO",
|
||||
"executorRunId": "YD-RUN-20260626-001",
|
||||
"endedAt": "2026-06-26T10:05:01+08:00",
|
||||
"errorCode": "REBATE_TEMPLATE_PARTIAL_FAIL",
|
||||
"errorSummary": "18 行中有 3 行校验失败",
|
||||
"retryable": false,
|
||||
"routeRole": "PURCHASER",
|
||||
"artifacts": [
|
||||
{
|
||||
"artifactType": "SCREENSHOT",
|
||||
"storagePath": "/data/jobs/JOB202606260001/REBATE_IMPORT_ITEMS/error.png"
|
||||
},
|
||||
{
|
||||
"artifactType": "LOG",
|
||||
"storagePath": "/data/jobs/JOB202606260001/REBATE_IMPORT_ITEMS/agent.log"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 我的待办
|
||||
|
||||
`GET /api/tasks/my`
|
||||
|
||||
查询参数建议:
|
||||
|
||||
- `status=open`
|
||||
- `roleCode=PLANNER`
|
||||
- `pageNo=1`
|
||||
- `pageSize=20`
|
||||
|
||||
响应字段建议:
|
||||
|
||||
- `taskId`
|
||||
- `jobId`
|
||||
- `title`
|
||||
- `reasonSummary`
|
||||
- `reasonUserFriendly`
|
||||
- `whyMeText`
|
||||
- `nodeCode`
|
||||
- `supplierName`
|
||||
- `contractNo`
|
||||
- `dueAt`
|
||||
- `detailUrl`
|
||||
|
||||
### 6.6 待办提交
|
||||
|
||||
`POST /api/tasks/{taskId}/submit`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"submitToken": "9f5f2c1d",
|
||||
"action": "CONFIRM_AND_CONTINUE",
|
||||
"payload": {
|
||||
"paymentModeCode": "2",
|
||||
"paymentModeDays": 30,
|
||||
"settlementCode": "3"
|
||||
},
|
||||
"comment": "已根据合同正文修正承付模式"
|
||||
}
|
||||
```
|
||||
|
||||
动作枚举建议:
|
||||
|
||||
- `CONFIRM_AND_CONTINUE`
|
||||
- `RETRY_CURRENT_NODE`
|
||||
- `TRANSFER_MANUAL`
|
||||
- `TERMINATE_JOB`
|
||||
|
||||
提交成功后的响应,建议额外返回:
|
||||
|
||||
- `accepted`: 是否已受理
|
||||
- `receiptMessage`: 给用户看的即时回执
|
||||
- `nextExpectedAction`: 系统接下来会做什么
|
||||
- `estimatedWaitMinutes`: 预计多久有下一次结果
|
||||
|
||||
### 6.7 重试当前节点
|
||||
|
||||
`POST /api/jobs/{jobId}/retry`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"scope": "CURRENT_NODE",
|
||||
"operatorId": "zhangsan",
|
||||
"reason": "计划员已修正承付模式,允许重试"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.8 转派/转人工
|
||||
|
||||
`POST /api/jobs/{jobId}/handoff`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"targetRole": "OPS",
|
||||
"targetUserId": "wangwu",
|
||||
"reason": "返利中心页面结构变化,需要机器人管理员处理"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.9 查询整单
|
||||
|
||||
`GET /api/jobs/{jobId}`
|
||||
|
||||
返回建议包含:
|
||||
|
||||
- `job` 基础信息
|
||||
- 当前状态
|
||||
- 节点轨迹数组
|
||||
- 当前待办数组
|
||||
- 关键业务编号
|
||||
- OCR 摘要
|
||||
- 映射摘要
|
||||
- 证据文件列表
|
||||
- 执行平台与平台侧运行号
|
||||
|
||||
### 6.10 企业微信回调
|
||||
|
||||
`POST /api/wecom/callback`
|
||||
|
||||
用途:
|
||||
|
||||
- 卡片动作回调
|
||||
- 身份事件回调
|
||||
|
||||
注意:
|
||||
|
||||
- 回调只负责接收动作和身份,不直接写业务结果
|
||||
- 必须先换成状态中心标准动作,再调用内部任务提交逻辑
|
||||
|
||||
---
|
||||
|
||||
## 7. H5 页面草案
|
||||
|
||||
页面最低能力建议:
|
||||
|
||||
1. 显示合同基础信息:合同编号、供应商、计划员、当前节点
|
||||
2. 显示“当前系统正在做什么”的人话状态
|
||||
3. 显示 OCR 关键字段和置信度
|
||||
4. 显示最近错误截图和业务可读错误说明
|
||||
5. 显示“为什么发给你”
|
||||
6. 显示允许修改的字段和修改前后对比
|
||||
7. 显示操作按钮:继续、重试、转人工、终止
|
||||
8. 显示处理历史和最近一次失败原因
|
||||
9. 提交动作后显示即时回执,避免重复点击
|
||||
|
||||
页面路由建议:
|
||||
|
||||
- `/h5/tasks/{taskId}`
|
||||
- `/h5/jobs/{jobId}`
|
||||
|
||||
---
|
||||
|
||||
## 8. 平台接入 POC 清单
|
||||
|
||||
任何新 RPA 平台接入前,先做下面的最小验证:
|
||||
|
||||
1. 登录目标系统并打开指定页面
|
||||
2. 读取一份合同字段并调用一次外部 HTTP API
|
||||
3. 在返利中心完成一次模板导入
|
||||
4. 失败时自动截图并回调到状态中心
|
||||
5. 验证平台日志、录屏和运行号能被统一采集
|
||||
|
||||
通过后,才允许接入正式流程路由。
|
||||
|
||||
## 9. 联调顺序建议
|
||||
|
||||
建议不要一上来就四方一起联调,按下面顺序推进更稳:
|
||||
|
||||
1. 状态中心单独完成建单、节点流转、待办流转
|
||||
2. 先用假 OCR 和假 RPA 回调,把流程跑通
|
||||
3. 接入真实 OCR
|
||||
4. 接入真实企业微信消息和 H5 身份
|
||||
5. 最后接入真实 RPA 流程
|
||||
6. 再做返利中心/M5 端到端联调
|
||||
|
||||
---
|
||||
|
||||
## 10. 测试重点
|
||||
|
||||
接口测试重点:
|
||||
|
||||
- 重复建单
|
||||
- 重复回调
|
||||
- 重复待办提交
|
||||
- 非法状态迁移
|
||||
- 低置信度转待办
|
||||
- 整单重跑
|
||||
|
||||
集成测试重点:
|
||||
|
||||
- OCR 返回空字段
|
||||
- 返利中心部分导入失败
|
||||
- M5 保存失败
|
||||
- 企业微信回调延迟或重复
|
||||
- 节点长时间未回调
|
||||
|
||||
UAT 重点:
|
||||
|
||||
- 计划员在手机端补录后能继续执行
|
||||
- 采购能看到失败明细和截图
|
||||
- 质管收到问题后只做基础资料确认
|
||||
- 运维能定位页面变化或登录失效
|
||||
- 待办文案是否足够人话,业务无需培训也能理解主要动作
|
||||
- 用户提交后是否明确感知“系统已受理”
|
||||
|
||||
---
|
||||
|
||||
## 11. 上线前必补清单
|
||||
|
||||
以下内容现在还没落在代码里,但上线前必须补齐:
|
||||
|
||||
1. 企业微信应用信息、回调域名、可信域名
|
||||
2. 共享目录所在服务器、权限和目录清理规则
|
||||
3. 合同附件的访问权限与脱敏策略
|
||||
4. 执行平台运行节点、账号和截图/录屏落盘路径
|
||||
5. 返利中心与 M5 的测试账号、生产账号隔离
|
||||
6. 错误码清单和责任角色映射表
|
||||
7. 环境监控:服务存活、数据库连接、磁盘容量、回调异常告警
|
||||
|
||||
---
|
||||
|
||||
## 12. 建议下一步
|
||||
|
||||
如果要继续推进,建议按下面顺序立刻开工:
|
||||
|
||||
1. 先定表结构和节点编码
|
||||
2. 再定统一 RPA 回调 JSON 契约和能力标签
|
||||
3. 再做企业微信“H5 待办页”原型和人性化文案
|
||||
4. 然后并行开发:状态中心后端、RPA 平台适配器、企业微信消息联调
|
||||
|
||||
这份文档适合作为技术 kickoff 和任务拆分底稿。
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
# 采购主链 RPA 一期需求简版说明
|
||||
|
||||
## 1. 这次要做什么
|
||||
|
||||
一期要做的不是单个机器人脚本,而是一套“**采购主链自动执行 + 异常可协同 + 手机可处理**”的小型流程系统。
|
||||
|
||||
覆盖范围只包含一条主链:
|
||||
|
||||
`合同扫描件/合同系统 -> 返利中心采购协议 -> M5 采购订单 -> 收货通知单`
|
||||
|
||||
核心目标:
|
||||
|
||||
- 能自动跑通的单据尽量自动跑完
|
||||
- 不能确定的地方不让机器人乱猜,直接转人工
|
||||
- 业务人员在手机上就能知道卡在哪、为什么卡、该怎么处理
|
||||
- 每一单都能查到节点轨迹、截图、日志和责任人
|
||||
|
||||
---
|
||||
|
||||
## 2. 为什么要做
|
||||
|
||||
当前采购主链里,有几类工作重复度高、人工录入重、出错后又难追:
|
||||
|
||||
- 合同扫描件/合同信息需要人工录入
|
||||
- 合同编号、承付模式、结算方式、货品等需要人工翻译到返利中心
|
||||
- 返利中心录完后还要继续去 M5 做采购订单
|
||||
- 遇到识别不准、模板报错、基础资料缺失时,只能靠人线下沟通和排查
|
||||
|
||||
一期方案要解决的,不只是“替人点页面”,更是把整条链做成:
|
||||
|
||||
- 业务看得见
|
||||
- 异常有人接
|
||||
- 手机能协同
|
||||
- 后续能扩展
|
||||
|
||||
---
|
||||
|
||||
## 3. 一期怎么做
|
||||
|
||||
一期按下面的方式落地:
|
||||
|
||||
- RPA 执行平台:负责系统页面操作
|
||||
- 轻量状态中心:负责流程状态、待办、日志、重试、规则和执行平台路由
|
||||
- 本地 OCR:负责识别合同扫描件
|
||||
- 企业微信应用:负责手机通知和处理入口
|
||||
|
||||
简单理解:
|
||||
|
||||
- RPA 执行平台像“执行手”
|
||||
- 状态中心像“总控台”
|
||||
- 企业微信像“手机协同入口”
|
||||
|
||||
这里要特别说明两层边界:
|
||||
|
||||
- 我们自己的能力:流程主控、规则配置、待办协同、日志证据、统一查询
|
||||
- 底层工具能力:页面操作、文件上传下载、截图录屏、执行回调
|
||||
|
||||
也就是说,真正要沉淀在自己手里的不是某个 RPA 工具脚本,而是上层的流程和业务资产。
|
||||
|
||||
---
|
||||
|
||||
## 4. 业务流程怎么走
|
||||
|
||||
一期主链步骤如下:
|
||||
|
||||
1. 采购或计划员把合同扫描件放进共享目录
|
||||
2. 状态中心建单并做基础判重
|
||||
3. OCR 识别合同字段
|
||||
4. 状态中心按规则做供应商、货品、结算方式、承付模式映射
|
||||
5. 合同系统信息补齐,并以“已审核/审核通过”为继续前提
|
||||
6. 机器人进入返利中心,检索采购协议并录入总单
|
||||
7. 机器人生成并导入返利中心细单模板
|
||||
8. 机器人进入 M5,创建采购订单
|
||||
9. 机器人完成保存并生成收货通知单
|
||||
10. 状态中心归档并保留完整轨迹
|
||||
|
||||
---
|
||||
|
||||
## 4.1 多 RPA 切换边界
|
||||
|
||||
一期方案支持“底层执行平台可替换”,但这个边界要先定清楚:
|
||||
|
||||
- 可切换的是底层执行平台,比如影刀、艺赛旗等
|
||||
- 不可切换的是上层状态中心、企业微信协同、规则配置和验收口径
|
||||
- 支持按“新单”切换平台
|
||||
- 也支持按“节点配置”决定由哪个平台执行
|
||||
- 不做“一个节点执行到一半临时换平台”
|
||||
- 新平台接入前,必须先做 POC 验证
|
||||
|
||||
这么设计的目的,是避免未来被单一厂商绑定。
|
||||
|
||||
---
|
||||
|
||||
## 5. 手机端能做什么
|
||||
|
||||
业务人员在企业微信里至少能做这些事:
|
||||
|
||||
- 看当前这单跑到哪一步
|
||||
- 看失败原因或待确认原因
|
||||
- 看关键字段摘要和错误截图
|
||||
- 补录或修正允许修改的字段
|
||||
- 确认后继续
|
||||
- 重试当前节点
|
||||
- 转人工处理
|
||||
- 终止本单
|
||||
|
||||
而且不是冷冰冰给一条待办,而是尽量做到:
|
||||
|
||||
- 让业务一眼看懂“这单卡在哪、为什么找你”
|
||||
- 让业务直接看到关键字段和最近错误截图
|
||||
- 让业务知道改之前是什么、改之后会变成什么
|
||||
- 让业务提交后马上知道“系统已经接单了”
|
||||
|
||||
这里的重点不是“人人都能改所有东西”,而是:
|
||||
|
||||
- 计划员处理 OCR/合同编号/承付模式这类问题
|
||||
- 采购处理返利中心录入和模板问题
|
||||
- 质管处理基础资料维护问题
|
||||
- 运维处理机器人登录、页面变化、系统异常问题
|
||||
|
||||
---
|
||||
|
||||
## 6. 一期的关键原则
|
||||
|
||||
1. 机器人只负责执行,不负责拍脑袋判断
|
||||
识别不准、映射不稳时,直接转人工,不允许“猜着填”。
|
||||
|
||||
2. 所有状态都进状态中心
|
||||
不把主状态留在某个 RPA 工具脚本里,避免后面没人知道这单停在哪。
|
||||
|
||||
3. 手机待办按角色路由,不按个人写死
|
||||
这样有人请假、换人、轮班时更好处理。
|
||||
|
||||
4. 任何失败都要能追到证据
|
||||
包括节点、错误码、错误摘要、截图、日志和责任角色。
|
||||
|
||||
5. 成功必须以“业务结果生成”为准
|
||||
不是页面点完就算成功,而是要拿到返利中心单号、M5 订单号、收货通知单号等结果。
|
||||
|
||||
6. 上层能力归自己,下层执行器可替换
|
||||
不把状态、规则、待办、留痕长在某个 RPA 平台内部。
|
||||
|
||||
---
|
||||
|
||||
## 7. 一期带来的直接价值
|
||||
|
||||
对业务:
|
||||
|
||||
- 减少重复录入和跨系统搬运
|
||||
- 出问题时不用到处问人,手机上就能看到卡点和原因
|
||||
- 计划员、采购、质管之间的责任更清楚
|
||||
- 不需要先学很多系统术语,待办本身就能告诉你现在该做什么
|
||||
- 提交后不容易反复点击、反复确认,手机端使用更安心
|
||||
|
||||
对管理:
|
||||
|
||||
- 每单可追踪
|
||||
- 哪类问题最多、卡在哪个节点更容易看清
|
||||
- 后续做统计和优化会更容易
|
||||
- 平台更换时不影响业务使用习惯,推广阻力更小
|
||||
|
||||
对技术:
|
||||
|
||||
- 一期先做成小中台,后面二期三期可以继续接订单中心、招采子系统
|
||||
- 不把流程逻辑绑死在单个机器人脚本里
|
||||
- 当影刀、艺赛旗或其他平台的成本、授权、稳定性变化时,可以平滑替换底层执行器
|
||||
|
||||
---
|
||||
|
||||
## 8. 一期明确包含什么
|
||||
|
||||
包含:
|
||||
|
||||
- 共享目录入池
|
||||
- OCR 识别
|
||||
- 映射规则管理
|
||||
- 合同系统前置确认
|
||||
- 返利中心采购协议录入
|
||||
- 返利中心细单模板导入
|
||||
- M5 采购订单创建
|
||||
- 收货通知单生成
|
||||
- 企业微信通知和手机处理入口
|
||||
- 待办“人话状态”“错误翻译”“修改前后对比”“即时回执”等体验设计
|
||||
- 状态查询、日志、截图、待办留痕
|
||||
- 多 RPA 执行平台的可切换设计
|
||||
|
||||
---
|
||||
|
||||
## 9. 一期明确不包含什么
|
||||
|
||||
以下内容不纳入一期:
|
||||
|
||||
- 订单中心分货与销售订单联动
|
||||
- 招采子系统医院计划抓取
|
||||
- 发货后一票/二票自动上传
|
||||
- 渠道价管理与维护
|
||||
- 邮件自动收件作为主入口
|
||||
- 复杂 BI 看板
|
||||
|
||||
这些内容如果后续要做,建议单独立项,作为二期或三期推进。
|
||||
|
||||
---
|
||||
|
||||
## 10. 上线前必须先确认的前置条件
|
||||
|
||||
1. 企业微信正式应用能申请下来
|
||||
2. 企业微信支持消息、H5 入口、身份识别和回调
|
||||
3. 共享目录位置和权限已定
|
||||
4. 合同扫描件的命名规范已定
|
||||
5. 一期主执行平台的运行环境和账号已准备
|
||||
6. 返利中心、M5、合同系统的测试账号已准备
|
||||
7. 承付模式、结算方式、供应商、货品等映射表已准备
|
||||
8. 合同系统“送审/审核通过”的业务规则已确认
|
||||
9. 选定的一期主执行平台已完成 POC 验证
|
||||
|
||||
---
|
||||
|
||||
## 11. 一期验收怎么判断
|
||||
|
||||
达到下面几点,就说明一期做成了:
|
||||
|
||||
- 业务能在手机端知道每单当前跑到哪一步
|
||||
- 关键不确定项不会被机器人自动猜测
|
||||
- 任何失败都能定位到具体节点、原因、截图和责任角色
|
||||
- 一份合同从入池到 M5 完成的轨迹可完整追溯
|
||||
- 同一合同不会因为重复上传或重复点击而形成重复业务结果
|
||||
- 能明确拿到返利中心单号、M5 订单号、收货通知单号等结果
|
||||
|
||||
---
|
||||
|
||||
## 12. 建议推进方式
|
||||
|
||||
建议按下面顺序推进:
|
||||
|
||||
1. 先确认一期边界和前置条件
|
||||
2. 再确认节点定义、角色路由和人工处理边界
|
||||
3. 再开始状态中心、RPA 执行平台、企业微信并行开发
|
||||
4. 最后做端到端联调和试运行
|
||||
|
||||
如果要给业务和领导一句话总结,这个项目可以这样讲:
|
||||
|
||||
“我们不是单做一个 RPA 机器人,而是在采购主链先搭一个轻量自动化中台,让合同到返利中心再到 M5 的流程尽量自动跑通,异常时能第一时间在企业微信手机端协同处理,并把整单过程完整留痕,同时让底层 RPA 执行工具保持可替换。”
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# 界面截图证据记录
|
||||
|
||||
> 用途:记录从会议录像截取的系统界面字段结构,作为需求文档的画面级证据。
|
||||
> 说明:视频原始分辨率仅 960×480,抽帧OCR质量有限;以下以直接可辨识的界面截图为准。
|
||||
|
||||
## 1. 返利中心(flgzmpc.com)
|
||||
|
||||
- 平台主体:福建广药洁达医药有限公司,登录人:廖建峰
|
||||
- 功能入口:采购协议管理
|
||||
- 首页有"**待审批事项**"列表,字段:流程名称、流程标识、当前节点、申请人、申请时间、操作
|
||||
- 支持**异步报表导出**:提示"待收返利报表_202683170210 的数据导出文件可以下载了"
|
||||
- 送审后申请人=操作人,进入待审批列表
|
||||
|
||||
## 2. 合同系统(contract.gzmpc.com/km/agreement/)
|
||||
|
||||
- 独立域名的合同管理系统
|
||||
- 左侧完整菜单树
|
||||
- 两套模板:格式采购合同(药品通用)、非格式采购合同(单次,承兑用)
|
||||
|
||||
## 3. M5 采购订单管理 1055(INCA 系统)
|
||||
|
||||
- 角色:福建洁达采购员;部门:采购部;操作员:廖建峰;公司:福建广药洁达医药(907)
|
||||
|
||||
### 顶部功能按钮(两行)
|
||||
第一行:新增、编辑、作废、删除、查询、送达、导入导出、选库存报警信息生成细单、新增细单、编辑细单、删除细单、打印、保存
|
||||
第二行:签订、作废、拆分、勾选采购协议、查询转付款、最新采购情况、批量操作、切换独立单元、仓库转款、生成收货通知单、打印单作废、清空下发标记
|
||||
|
||||
### 采购订单总单列
|
||||
行号、采购订单ID、制单人、订单编号、业务日期、供应商、数据员、联系人、订单金额、书面订单号、采购订单完成、生成状态、开始日期、结果日期、签约日期、结算方式、预付款、其他费用、配送加点率、签约人、送达地点、代理方式、签约类
|
||||
|
||||
### 采购订单细单列
|
||||
行号、采购订单细单ID、采购员、货品ID、通用名、商品名、条形码、规格(规格型号)、基本单位、生产厂家、产地、上市许可持有人、货品数量、单价、金额
|
||||
|
||||
## 4. 医药城订单导出表(订单明细.xls)
|
||||
|
||||
列:下单时间、创建人、订单号、父订单号、支付方式、商家、订单类型、订单状态、品名、产品编码、规格、厂家、国药准字
|
||||
|
||||
样例数据:
|
||||
- 下单时间 `2026-08-04 18:50:48.0`
|
||||
- 订单号 `ZQD202608041850483308`
|
||||
- 支付方式 `账期支付`
|
||||
- 商家 `重庆亿昊药业有限公司`
|
||||
- 订单类型 `普通订单`
|
||||
- 订单状态 `待付款/已取消/待收货`
|
||||
- 品名 `哈药牌钙铁锌口服液`
|
||||
- 产品编码 `2919482534`
|
||||
|
||||
## 5. M5 货品对码基础表(货品运营管理.xls)
|
||||
|
||||
列:通用名、批准文号、规格、基本单位、生产厂家、货品ID、货品编码
|
||||
|
||||
- 匹配键(四要素):通用名 + 批准文号 + 规格 + 生产厂家 → 货品ID + 货品编码
|
||||
- 厂家名含大量英文(进口药):如 Abbott Biologicals B.V.、AESICA PHARMACEUTICALS S.r.l.、ALCON CUSI S.A.
|
||||
|
||||
## 尚未获得清晰画面(需补充)
|
||||
|
||||
- 小药药平台界面
|
||||
- 返利中心总单/细单录入页字段全貌
|
||||
- 合同系统模板内部字段
|
||||
- 线下手工单开单截图样式
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
# 福建洁达采购合同全链条自动化设计说明
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档名称 | 福建洁达采购合同全链条自动化设计说明 |
|
||||
| 版本 | V1.1 |
|
||||
| 日期 | 2026-09-12 |
|
||||
| 适用范围 | 福建广药洁达医药有限公司采购合同、返利中心录入及相关选品作业 |
|
||||
| 依据 | 《福建洁达采购合同全链条 RPA 自动化录入流程》《采购平台选品条件优先项》(2026-09-02)《合同返利系统字段映射》(2026-09-11) |
|
||||
| 配套文档 | 《福建洁达四流程技术实现说明》 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本方案在现有状态中心与 RPA 执行体系上,将采购作业拆为四条可独立启动的流程:选品采购、订单对码、合同录入、返利录入。
|
||||
|
||||
页面操作仍由 RPA 完成。**四条流程启动后的第一步,均由状态中心调用订单中心接口取数(或回写),写入 RPA 数据库并标记处理状态;接口未成功返回前,RPA 不得打开下游系统页面。** 操作人员可在状态中心启动作业、查看进度与业务数据,亦可按约定时间自动触发。
|
||||
|
||||
四条流程独立启动、独立失败、独立重跑,互不占用同一执行会话。同一笔业务单据的数据仍按「选品 → 对码 → 合同 → 返利」顺序衔接;前置数据未就绪时,后续流程不予执行,并记录原因。
|
||||
|
||||
---
|
||||
|
||||
## 2. 建设目标
|
||||
|
||||
1. 提供统一操作入口:状态中心可分别启动四条流程,或按计划定时运行。
|
||||
2. 保证过程可见:每条作业的执行状态、当前环节、责任角色、业务数据及失败原因可在状态中心查询。
|
||||
3. 保证数据可追溯:订单中心接口数据先落入 RPA 库,记录待处理、成功、失败等状态,支持失败重拉与按单重跑。
|
||||
4. 保证规则可落地:选品按已确认的优先项执行;合同、返利按字段映射表填写,不自行推断未提供的字段。
|
||||
5. 控制操作风险:商城付款、未尽调客户的合同填报,均不由机器人自动完成。
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体方案
|
||||
|
||||
```
|
||||
状态中心(启动 / 监控 / 待办)
|
||||
│
|
||||
│ 每条流程启动后,先调订单中心接口
|
||||
▼
|
||||
订单中心 ──读取──► 销售总单 / 客户主数据 / 货品 / 计划员 / 账期
|
||||
◄──回写── 对码结果
|
||||
│
|
||||
▼
|
||||
RPA 数据库(带处理状态:待处理 / 成功 / 失败)
|
||||
│
|
||||
│ 仅 STATUS=就绪 时,RPA 才点页面
|
||||
▼
|
||||
1号药城 / 药帮忙 / 合同系统 / 返利中心
|
||||
```
|
||||
|
||||
| 流程 | 业务内容 | 建议运行时点 |
|
||||
| --- | --- | --- |
|
||||
| 选品采购 | 读取订单中心销售总单,在 1 号药城、药帮忙按规则选品,生成待复核清单 | 销售订单产生之后 |
|
||||
| 订单对码 | 将选品结果或手工单字段写入订单中心对码表 | 选品完成,或手工单到达之后 |
|
||||
| 合同录入 | 按字段映射填写合同系统 | 对码完成,且客户已尽调 |
|
||||
| 返利录入 | 按合同号填写返利中心总单与细单 | 合同编号已生成 |
|
||||
|
||||
启动某一流程仅处理该流程范围内的任务,不强制四条连续一次跑完。某流程缺少必要数据或处于人工待办时,系统停留在该流程并写明原因,不向下游系统提交空数据。
|
||||
|
||||
---
|
||||
|
||||
## 4. 订单中心接口
|
||||
|
||||
订单中心是本方案的**唯一业务数据源**。合同系统、返利中心页面上由 RPA 填写、且映射表标注「从订单中心获取」的字段,一律经本接口取得,禁止从商城页面抄写,禁止在 RPA 脚本中写死。
|
||||
|
||||
调用原则:
|
||||
|
||||
1. **只由状态中心调用。** RPA 不直连订单中心。状态中心将结果写入 RPA 库后,机器人只读本地就绪数据。
|
||||
2. **先接口、后页面。** 接口失败、超时、缺必填字段,本流程记失败并停止,不打开 1 号药城、合同系统或返利中心。
|
||||
3. **原文留存。** 每次响应写入 RPA 库(含原始报文),状态中心可查,便于对账与重拉。
|
||||
4. **按单幂等。** 同一业务键重复启动不重复插入成功数据;失败允许按同一键重拉。
|
||||
|
||||
正式 URL、鉴权、报文格式由订单中心提供后锁定。本节先按业务能力列出必须具备的接口;能力未齐备前,对应流程不得对生产系统写入。
|
||||
|
||||
### 4.1 接口清单与调用时点
|
||||
|
||||
| 序号 | 接口能力 | 方向 | 调用流程 | 调用时点 | 失败后果 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| A | 销售总单查询 | 读 | 选品 | 选品启动后、上商城之前 | 选品停止,不上 1 号药城 / 药帮忙 |
|
||||
| B | 销售明细查询 | 读 | 选品 | 紧接 A,按总单取行 | 同 A;无批准文号的行单独失败,不阻断其他行 |
|
||||
| C | 对码结果回写 | 写 | 对码 | 选品完成或手工单识别之后 | 对码失败;合同不得启动 |
|
||||
| D | 对码商家与货品查询 | 读 | 对码 / 合同 | 回写成功后,取客户名称、货品编码等 | 合同不得启动 |
|
||||
| E | 客户主数据查询 | 读 | 合同 | 合同启动后、打开合同系统之前 | 合同停止;不填报 |
|
||||
| F | 合同货品明细查询 | 读 | 合同 | 与 E 一并发起 | 同 E;缺货品行则本单失败 |
|
||||
| G | 计划员与采购员对照查询 | 读 | 返利 | 返利启动后、打开返利中心之前 | 返利停止;不猜测计划员 |
|
||||
| H | 返利源数据查询 | 读 | 返利 | 与 G 一并发起 | 缺供应商编码、订单号或货源细单号则本单失败 |
|
||||
|
||||
### 4.2 各接口必须返回的字段
|
||||
|
||||
字段名称以订单中心正式文档为准。下表为业务含义,来源为流程图及《合同返利系统字段映射》。
|
||||
|
||||
**A 销售总单查询**
|
||||
|
||||
入参:订单日期(默认前一日),可选供应商名称。
|
||||
出参(每张总单):销售总单号、订单日期、客户(商家)名称、客户编码、来源、总金额。
|
||||
|
||||
**B 销售明细查询**
|
||||
|
||||
入参:销售总单号。
|
||||
出参(每行):订单号、批准文号、通用名、规格、生产厂家、采购数量、**销售价格**(选品计算 5% 毛利的基准,不得用采购价替代)、平台商品编码(如有)。
|
||||
|
||||
无批准文号、无销售价格的行标记失败,不进入商城查询。
|
||||
|
||||
**C 对码结果回写**
|
||||
|
||||
入参:订单号或销售明细行标识,以及待写入对码表的字段(品名、规格、厂家、批准文号、数量、选品后的货品信息等)。
|
||||
出参:写入是否成功。
|
||||
说明:对应流程图「提取需要字段数据,传到订单中心对码功能数据表」。此为写接口,与查询分开。
|
||||
|
||||
**D 对码商家与货品查询**
|
||||
|
||||
入参:客户(商家)标识、货品匹配条件。
|
||||
出参:客户名称、客户编码;货品编码、货品名称、注册证号、规格、单位。
|
||||
说明:合同系统「客户名称」对照此处填写,不以商城店铺名为准。
|
||||
|
||||
**E 客户主数据查询**
|
||||
|
||||
入参:客户编码或客户名称。
|
||||
出参:
|
||||
|
||||
| 字段 | 用途 |
|
||||
| --- | --- |
|
||||
| 客户名称、客户编码 | 合同「客户名称」 |
|
||||
| 客户类型(预付 / 账期) | 预付:合同版本「我司定制」、预付款「是」、付款期限 1 天;账期:合同版本「非格式合同」、预付款「否」、付款期限取账期天数 |
|
||||
| 账期天数 | 仅账期客户必填;映射表要求订单中心具备该维护功能 |
|
||||
| 合同签订日期、开始日期、结束日期 | 合同对应日期字段 |
|
||||
| 尽调状态(如订单中心有维护) | 未尽调则本份合同跳过并邮件通知采购员。若尽调仅在合同系统回显,则本字段改为页面读取,须在接口说明书中明确 |
|
||||
|
||||
**F 合同货品明细查询**
|
||||
|
||||
入参:销售总单号或订单号。
|
||||
出参(每行):货品编码、货品名称、注册证号、规格、单位、数量、单价。
|
||||
说明:用于合同明细导入模板。数量、单价取订单中心,不取商城实时价。
|
||||
|
||||
**G 计划员与采购员对照查询**
|
||||
|
||||
入参:计划员或采购员。可按全量或按供应商查询。
|
||||
出参:计划员、对应采购员。
|
||||
说明:映射表注明「订单中心需要有计划员和采购员的对应关系」。无对照则返利总单「计划员」无法填写。
|
||||
|
||||
**H 返利源数据查询**
|
||||
|
||||
入参:合同号或销售总单号。
|
||||
出参:供应商编码、返利单位编码、计划员、订单号、货源细单号;以及细单行的货品编码、数量、含税协议单价。
|
||||
说明:返利细单「细单备注」填订单号,「货源细单 ID」填货源细单号,二者均须订单中心给出。
|
||||
|
||||
### 4.3 调用顺序(按一笔业务)
|
||||
|
||||
```
|
||||
选品启动
|
||||
① 调 A、B → 写入销售总单/明细(就绪) → 再上商城查价
|
||||
A/B 失败 → 选品失败,不上商城
|
||||
|
||||
对码启动
|
||||
② 调 C 回写对码表 → 调 D 取客户与货品编码 → 就绪后方可合同
|
||||
C/D 失败 → 对码失败,合同入口拒绝启动
|
||||
|
||||
合同启动
|
||||
③ 调 E、F → 客户类型/账期/日期/货品行就绪 → 再打开合同系统
|
||||
必填缺失 → 合同失败,不填报
|
||||
未尽调 → 本单跳过,邮件通知,处理下一单
|
||||
|
||||
返利启动
|
||||
④ 调 G、H → 计划员对照与细单源数据就绪 → 再打开返利中心
|
||||
必填缺失 → 返利失败,不猜测字段
|
||||
```
|
||||
|
||||
四条流程仍可分时、分别启动;上述顺序约束的是**同一业务键上的数据依赖**,不是强制一次连续跑完四条。
|
||||
|
||||
### 4.4 失败与重试
|
||||
|
||||
| 情形 | 处理 |
|
||||
| --- | --- |
|
||||
| 网络超时、服务不可用 | 本流程失败,可重拉接口;不启动 RPA |
|
||||
| 返回成功但缺必填字段 | 本单失败,记录缺失字段名称;不启动 RPA |
|
||||
| 部分明细行缺批准文号或销售价格 | 仅该行失败,其余行继续 |
|
||||
| 对码回写失败 | 对码失败;合同、返利不得启动 |
|
||||
| 客户未尽调 | 本合同跳过(不算接口失败),发邮件,继续下一客户 |
|
||||
| 计划员对照不存在 | 返利失败,不使用默认计划员 |
|
||||
|
||||
重拉只覆盖失败数据,已成功入库的记录不得覆盖。
|
||||
|
||||
### 4.5 需订单中心确认的事项
|
||||
|
||||
1. 上表 A–H 的正式地址、鉴权方式、测试与生产环境。
|
||||
2. 客户类型、账期天数、计划员与采购员对照、货源细单号是否已有维护功能与查询接口(映射表已提出,当前文档未提供接口说明)。
|
||||
3. 尽调状态以订单中心为准,或以合同系统选择客户后的回显为准。
|
||||
4. 对码功能数据表的写入接口是否已经开放;未开放则对码流程无法闭环。
|
||||
|
||||
未确认前,状态中心可用测试数据验证「调用 → 入库 → 状态中心可见」链路,不对生产订单中心与生产合同系统写入。
|
||||
|
||||
---
|
||||
|
||||
## 5. 流程说明
|
||||
|
||||
### 5.1 选品采购
|
||||
|
||||
调用接口 A、B,取得销售总单及明细后,按批准文号检索 1 号药城、药帮忙商品,依据第 6 节规则筛选。筛选结果形成待复核清单,由采购人员确认后再付款。机器人不执行支付操作。
|
||||
|
||||
已完成的商城登录与按文号查询能力纳入本流程复用,查询结果改为写入 RPA 库及状态中心,不再以桌面表格作为唯一产出。
|
||||
|
||||
### 5.2 订单对码
|
||||
|
||||
调用接口 C,将选品结果或 DocAgent 识别的手工单字段写入订单中心对码功能数据表;再调用接口 D,取得客户名称与货品编码。合同系统中的「客户名称」以接口 D 为准。
|
||||
|
||||
### 5.3 合同录入
|
||||
|
||||
调用接口 E、F 取得客户主数据与货品明细后,依据《合同返利系统字段映射》填写合同系统。录入方式分为系统自动生成、RPA 填写、无需填写三类;RPA 仅处理标注为「RPA」的字段。
|
||||
|
||||
客户未完成主体尽调时:向采购员发送邮件,本份合同跳过,继续处理后续合同,不中断整批作业。
|
||||
|
||||
客户类型为预付时:合同版本类型取「我司定制」,是否预付款为「是」,付款期限为 1 天。
|
||||
客户类型为账期时:合同版本类型取「非格式合同」,是否预付款为「否」,付款期限取接口 E 返回的账期天数。
|
||||
|
||||
### 5.4 返利录入
|
||||
|
||||
调用接口 G、H 取得计划员对照与返利源数据后,按合同号查询采购协议,填写总单必填项并导入细单。提交失败转为人工处理,不循环强行提交。
|
||||
|
||||
---
|
||||
|
||||
## 6. 选品规则
|
||||
|
||||
规则依据《采购平台选品条件优先项》(2026-09-02)。下列第 1 至 4 项须同时满足,再比较仓库距离。销售价格以订单中心接口 B 为准。
|
||||
|
||||
1. **自营仓**:仅在已勾选的自营仓范围内选择。
|
||||
2. **价格**:与销售价格比较,毛利不低于 5%。
|
||||
3. **有效期**:大于等于 12 个月。
|
||||
4. **库存**:库存数量不低于本单采购数量。
|
||||
5. **仓库距离**:在满足以上条件后,按下列自营仓顺序优先选择距离较近的仓库:
|
||||
|
||||
1. 广东壹号药业有限公司
|
||||
2. 昆山亿方药业有限公司
|
||||
3. 湖北亿昊药业有限公司
|
||||
4. 天津壹号药业有限公司
|
||||
5. 重庆亿昊药业有限公司
|
||||
6. 重庆壹号医药有限公司
|
||||
7. 湖南壹号药业股份有限公司
|
||||
8. 辽宁亿昊药业有限公司
|
||||
9. 河南壹号医药有限公司
|
||||
10. 甘肃壹号云医药有限责任公司企业店
|
||||
11. 陕西壹号药业有限公司
|
||||
|
||||
现有商城查价程序实现了双平台登录与按批准文号取价,可作为选品流程的查询能力复用。该程序现行过滤逻辑与上述规则存在差异,上线口径以本节为准:
|
||||
|
||||
| 现有查价程序 | 本方案采用规则 |
|
||||
| --- | --- |
|
||||
| 按采购价格上下浮动 10% 过滤 | 相对销售价格,毛利不低于 5% |
|
||||
| 未限定自营仓范围 | 仅在上述 11 家自营仓内选择 |
|
||||
| 未校验库存是否覆盖采购数量 | 库存须大于等于采购数量 |
|
||||
| 结果输出至本地表格 | 结果写入 RPA 库,并在状态中心展示 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 合同与返利录入口径
|
||||
|
||||
字段来源、录入方式以《合同返利系统字段映射》(2026-09-11)为准。凡标注「从订单中心获取」的字段,取值路径见第 4 节,缺项不得提交。
|
||||
|
||||
合同、返利中标注为默认值的字段(如结算类型「收支双向」、付款方式「电汇」、税率 0.13、承付模式及参数等)按映射表执行,不在运行时另行解释。
|
||||
|
||||
---
|
||||
|
||||
## 8. 运行监控
|
||||
|
||||
状态中心提供以下可见信息:
|
||||
|
||||
- 当日四条流程的执行数量,以及成功、失败、等待人工处理的分布
|
||||
- 单笔作业的当前环节与责任角色
|
||||
- 订单中心各接口最近一次调用时间、成功或失败、缺失字段
|
||||
- 选品候选、毛利是否达标、选用自营仓
|
||||
- 合同关键字段(客户名称、金额、日期、尽调结果)
|
||||
- 返利总单编号、细单导入结果
|
||||
- 失败原因及是否允许按本流程重跑
|
||||
|
||||
接口数据写入 RPA 测试库,并标记处理状态(待处理 / 处理中 / 成功 / 失败 / 跳过)。状态中心同步展示,作为业务查询与问题定位的依据。
|
||||
|
||||
---
|
||||
|
||||
## 9. 本期范围
|
||||
|
||||
**本期包含**
|
||||
|
||||
- 四条流程的独立启动、定时启动与状态查询
|
||||
- 订单中心接口 A–H 的调用、入库与失败停机
|
||||
- 选品查询能力与优先项规则落地
|
||||
- 合同、返利按映射表自动填写
|
||||
|
||||
**本期不包含**
|
||||
|
||||
- 机器人自动完成商城支付
|
||||
- 对未尽调客户自动填写并提交合同
|
||||
- 将四条流程拆成互不共享状态的独立工具
|
||||
- 在订单中心正式接口就绪前对生产系统写入
|
||||
|
||||
流程图未包含 M5 采购订单。若现场仍需 M5 作业,另行立项,不纳入本四条流程。
|
||||
|
||||
---
|
||||
|
||||
## 10. 待确认事项
|
||||
|
||||
1. 四条流程采用统一状态中心入口、分别启动;允许只运行其中一条。
|
||||
2. 选品正式规则以第 6 节为准;现有查价程序仅复用登录与查询能力。
|
||||
3. 订单中心按第 4.5 节提供接口说明书,并确认客户类型、账期天数、计划员对照、货源细单号、对码回写、尽调来源。未齐备前,合同与返利不得按缺省猜测值提交。
|
||||
|
||||
上述事项确认后,按《福建洁达四流程技术实现说明》实施。实施顺序为:先完成订单中心取数入库与状态中心可见,再接入选品、合同与返利。
|
||||
|
|
@ -0,0 +1,626 @@
|
|||
# 采购主链 RPA 一期 · 实施与验收规范
|
||||
|
||||
> 用途:把《三方会审版需求文档》和《设计文档》落成**开发能照着做、验收能照着勾**的执行手册。
|
||||
> 读者:①Trae(写上层状态中心代码) ②艺赛旗 iS-RPA 开发(搭下层页面流程) ③业务方(按业务验收标准确认) ④项目负责人(你,通盘把控)。
|
||||
> 语言:全中文。术语见文末《名词表》。
|
||||
|
||||
---
|
||||
|
||||
## 第一部分 · 总览:这套系统到底怎么分工
|
||||
|
||||
### 1.1 上下两层(务必先分清)
|
||||
|
||||
整套系统是**上下两层**,由**两个不同工具**实现,不要混为一谈:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 上层:状态中心(大脑) ← Trae 写后台+网页代码 │
|
||||
│ · 决定每一单现在该走哪一步、出错派给谁 │
|
||||
│ · 存 job 状态 / 对码结果 / 证据 / 业务编号 │
|
||||
│ · 【步骤树状图界面】就在这层,是网页,业务打开就能看进度 │
|
||||
│ · 不亲自去点任何外部系统的页面 │
|
||||
└───────────────────────────▲──────────────────────────────┘
|
||||
│ HTTP 接口:下命令(派节点) / 收回执(成功/失败)
|
||||
│ 传的都是 JSON,不共享数据库
|
||||
┌───────────────────────────┴──────────────────────────────┐
|
||||
│ 下层:艺赛旗 iS-RPA(手脚) ← 在艺赛旗设计器里可视化编排 │
|
||||
│ · 真正去点 医药城/合同系统/返利中心/M5 的页面 │
|
||||
│ · 是"搭流程",不是"录视频";能处理 M5 桌面客户端 │
|
||||
│ · 每个动作做完,HTTP 回调状态中心报结果 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> 澄清你最初的困惑:**不是人去录视频**。是两拨代码活——Trae 写状态中心(网页+后台),艺赛旗里搭 RPA 流程。步骤树状图是 Trae 做出来的**网页界面**,实时显示每单进度,不是 RPA 录出来的东西。
|
||||
|
||||
### 1.2 谁做什么(职责边界)
|
||||
|
||||
| 工作 | 归谁 | 产物 |
|
||||
| --- | --- | --- |
|
||||
| 数据库建表、状态机、对码引擎、幂等去重 | Trae | 后台服务 |
|
||||
| 派发节点、接收回调、判断下一步 | Trae | API |
|
||||
| 待办网页、步骤树状图、整单查询页 | Trae | 前端网页 |
|
||||
| 企业微信推送、H5 待办确认 | Trae | 集成 |
|
||||
| 登录各系统、导出Excel、建单、上传PDF、录入M5 | 艺赛旗 | RPA流程包 |
|
||||
| 页面动作做完回调状态中心 | 艺赛旗 | HTTP回调 |
|
||||
| OCR 识别线下手工单 | 独立OCR服务(本地) | 字段JSON |
|
||||
| 按业务验收标准确认每步对不对 | 业务方 | 验收签字 |
|
||||
|
||||
### 1.3 一次完整流程 = 一个 job
|
||||
|
||||
**一个 job = 同一供应商 + 同一订单日期 + 同一来源 的一批订单 = 一条合同。**
|
||||
job 从①走到⑥,每一步是一个"节点",节点做完才走下一个。
|
||||
|
||||
---
|
||||
|
||||
## 第二部分 · 一页地图(贴在最显眼处)
|
||||
|
||||
```
|
||||
════════════════════════════════════════════════════════════════════
|
||||
采购主链 RPA 一期 · 一页导读
|
||||
以销定采:先在京东/阿里卖货 → 反向采购 → 把每天手工采购链自动化
|
||||
════════════════════════════════════════════════════════════════════
|
||||
【主链六段】 ▓=RPA干 ░=人干 ⊙=RPA等 谁干 停了怎么办
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ ① 导订单 登录平台→选前一天→导出Excel │ ▓▓▓ 登录失败→自动重试→运维
|
||||
│ 医药城:合同要合并PDF上传 │
|
||||
│ 小药药:年度协议,不传合同 │
|
||||
│ 线下单:图片走OCR (~100份/月) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ② 产品对码 ★核心难点★ │ ▓▓▓ 单条命中不了
|
||||
│ 平台药名→[通用名+批准文号+规格+厂家]→M5货品 │ ░ →这条挂起转人工
|
||||
│ ②b 没采过的品种→M5新增货源 │ →整批继续
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ③ 合同建单 选模板→改签约日=订单日 │ ▓▓▓ 合同禁掉失效
|
||||
│ 医药城:同供应商同日合同合并成1个PDF上传 │ →邮件通知采购
|
||||
│ ┌送审→等半小时→出【合同编号】🔑 │ ⊙ (建议轮询别死等)
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ④ 返利中心 用【合同编号】查总单→填→导细单 │ ▓▓▓ 重复编码
|
||||
│ 细单备注必填【订单号】特定补偿必填0 │ ░ →挂起,整批继续
|
||||
│ ┌送审 ⭐决定成败⭐ 不做则M5品种跳不出来 │ ▓▓▓ 不送审→硬阻断
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ⑤ M5建采购单(1055) 选正常保管账,备注填订单号 │ ▓▓▓ 质管资料缺失
|
||||
│ 结算/发票/接收方式=返利中心已下发,不用填 │ →停!转人工,不等质管
|
||||
│ ★★录完就停,不点保存★★ │ ░ (②阶段其实已能预警)
|
||||
├──────────────────────────────────────────────┤
|
||||
│ ⑥ 收尾 检查→保存→签订→生成收货通知单 │ ░→▓ —
|
||||
│ ⚠不可逆:签订后改不了,一期人工把关 │ (准确率OK后交RPA)
|
||||
└──────────────────────────────────────────────┘
|
||||
════════════════════════════════════════════════════════════════════
|
||||
【两把钥匙:五系统无API,全靠RPA搬,靠这两个值接力】
|
||||
🔑货品ID/编码(②产出) ─贯穿─► ③④⑤ 填明细都用它
|
||||
🔑合同编号(③产出) ─► ④按合同号查总单→送审 ─► ⑤M5能带出协议
|
||||
反直觉:卡M5的送审在【返利中心】,不在合同系统
|
||||
🧵订单号(①产出,只追溯): 返利备注→物流软件看 │ M5备注→结算勾对账看
|
||||
════════════════════════════════════════════════════════════════════
|
||||
【停点分类:停住不蠢,分不清"整批停/单条挂起"才蠢】
|
||||
A.该停·业务拍板 M5质管缺失→停转人工不等质管;不可逆保存签订→留给人
|
||||
B.单条挂起别拖累 对码不上/返利重复编码→这条转人工,整批继续
|
||||
C.可以更聪明 等审批→轮询别死等;网页卡顿→自动重试1-2次再报警
|
||||
════════════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第三部分 · 步骤树状图长什么样(Trae 要做的网页界面)
|
||||
|
||||
这是状态中心的核心界面,业务打开就知道每单跑到哪、卡在哪。
|
||||
|
||||
```
|
||||
📋 Job: YYC-亿昊药业-20260804 状态: ⏸ 等待人工处理
|
||||
────────────────────────────────────────────────────────────
|
||||
✅ ① 导订单 ORDER_FETCH 导出12单 22:01 12秒 [看证据]
|
||||
✅ ② 产品对码 PRODUCT_MATCH 对码 10/12 ⚠ 22:01 3秒
|
||||
└─ ⏸ 2条未命中,等计划员选择 [去处理→]
|
||||
⬜ ②b 货源新增 SOURCE_ADD —
|
||||
⬜ ③ 合同建单 CONTRACT_CREATE —
|
||||
⬜ ④ 返利总单 REBATE_HEADER —
|
||||
⬜ ④ 返利细单 REBATE_IMPORT —
|
||||
⬜ ④ 返利送审 REBATE_SUBMIT —
|
||||
⬜ ⑤ M5建单 M5_CREATE_ORDER —
|
||||
⬜ ⑥ 人工保存签订 —
|
||||
⬜ ⑥ 生成收货通知单 —
|
||||
────────────────────────────────────────────────────────────
|
||||
🔑合同编号: — 🔑返利总单号: — 🧵订单号: ZQD...(共12个)
|
||||
图标说明: ✅完成 ⏸等人处理 ❌失败 🔄重试中 ⬜未开始
|
||||
────────────────────────────────────────────────────────────
|
||||
[重跑本单] [转人工] [查看完整事件流] [导出证据包]
|
||||
```
|
||||
|
||||
**界面验收标准(给 Trae)**:
|
||||
- [ ] 能列出该 job 全部节点及状态(用上面10个节点)。
|
||||
- [ ] 每个已完成节点显示:完成时间、耗时、关键结果摘要、证据链接。
|
||||
- [ ] 等人处理的节点有醒目标识 + "去处理"按钮跳到待办页。
|
||||
- [ ] 顶部三把钥匙(合同编号/返利总单号/订单号)实时回填。
|
||||
- [ ] 失败节点显示错误码 + 人话原因 + 截图。
|
||||
- [ ] 列表能按状态筛选(全部/进行中/等人/失败/完成)。
|
||||
|
||||
---
|
||||
|
||||
## 第四部分 · 逐节点执行手册(核心)
|
||||
|
||||
> 读法:每个节点四块——
|
||||
> ①**节点信息**(谁干、前置、进出什么);
|
||||
> ②**页面操作步骤**(给艺赛旗,一步一动作,能照着搭);
|
||||
> ③**验收标准**分两栏——**代码验收**(给 Trae/艺赛旗,机器能自检)+**业务验收**(给业务方,人能对着勾);
|
||||
> ④**失败与去向**(停了怎么办,派给谁)。
|
||||
>
|
||||
> 术语:complete=节点成功回调;fail=节点失败回调;挂起=这一条转人工、整批继续;硬阻断=整个 job 停。
|
||||
|
||||
---
|
||||
|
||||
### 节点① ORDER_FETCH · 订单获取
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA |
|
||||
| 前置条件 | 平台账号可登录;job 已建(来源+日期已定) |
|
||||
| 输入 | 来源类型(医药城/小药药)、订单日期(默认前一天) |
|
||||
| 输出 | 订单明细 Excel、订单号列表、供应商列表 |
|
||||
|
||||
**页面操作步骤(给艺赛旗)**:
|
||||
1. 打开对应平台登录页,输入账号密码,登录。
|
||||
2. 进入"我的订单 / 订单管理"。
|
||||
3. 选择日期 = 前一天(或状态中心下发的指定日期)。
|
||||
4. 【待确认】按订单状态过滤(只取"待收货"还是含其他,等业务拍板)。
|
||||
5. 点"导出订单明细",等待下载完成。
|
||||
6. 把下载的 Excel 存到证据目录,截图当前页面。
|
||||
7. HTTP 回调 complete,带 Excel 路径 + 解析出的订单号列表 + 供应商列表。
|
||||
|
||||
**输出示例(回调 complete)**:
|
||||
```json
|
||||
{ "attemptNo": 1, "executorRunId": "YD-RUN-001",
|
||||
"artifacts": [{ "artifactType": "SOURCE_EXCEL", "storagePath": "/data/jobs/.../orders.xls" }],
|
||||
"summary": { "orderCount": 12, "supplierList": ["重庆亿昊药业有限公司"], "orderNoList": ["ZQD202608041850483308"] } }
|
||||
```
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收(机器自检) | 业务验收(人对着勾) |
|
||||
| --- | --- |
|
||||
| [ ] 登录成功,进到订单列表页 | [ ] 导出的单量 = 业务当天实际下单量,不多不少 |
|
||||
| [ ] 日期筛选值 = 目标日期 | [ ] 供应商名、订单号与平台页面一致 |
|
||||
| [ ] Excel 成功下载且非空 | [ ] 选的日期就是"前一天",没选错 |
|
||||
| [ ] 订单号/供应商已解析入 order_item | [ ] 抽查一单:品名/规格/数量与平台一致 |
|
||||
| [ ] 证据目录有 Excel + 截图 | |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 登录失败/验证码 | SYS_LOGIN_FAIL | 是(1-2次) | 仍失败→运维OPS |
|
||||
| 页面卡顿/超时 | SYS_PAGE_TIMEOUT | 是(1-2次) | 仍失败→运维OPS |
|
||||
| 导出按钮找不到(改版) | UI_ELEMENT_NOT_FOUND | 否 | 运维OPS |
|
||||
| 当天无订单 | — | — | 正常结束,job 标记"空单" |
|
||||
|
||||
---
|
||||
|
||||
### 节点①b OCR_EXTRACT · 线下手工单识别(仅线下单)
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 独立 OCR 服务(本地优先) + 艺赛旗取件 |
|
||||
| 前置条件 | 线下供应商发来的开单截图/PDF 已入证据目录 |
|
||||
| 输入 | 图片/PDF 文件 |
|
||||
| 输出 | 结构化字段 JSON(品名/规格/厂家/批准文号/数量/单价/供应商)+ 置信度 |
|
||||
|
||||
**页面操作步骤**:
|
||||
1. 读取线下单图片/PDF。
|
||||
2. 调 OCR 服务,拿回字段 + 每字段置信度。
|
||||
3. 低于阈值或缺关键字段 → 标记转人工。
|
||||
4. 识别结果写入 order_item(与线上同结构),进入对码。
|
||||
5. 回调 complete,带识别 JSON 与置信度。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 每字段带置信度 | [ ] 抽查:识别的品名/规格/数量与原图一致 |
|
||||
| [ ] 低置信度/缺字段 → 建人工待办 | [ ] 人工能看到原图,方便核对 |
|
||||
| [ ] 识别结果结构与线上订单一致 | [ ] 识别错的能改,改完能继续 |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 图片模糊/识别失败 | BIZ_OCR_LOW_CONF | 否 | 计划员PLANNER 看原图人工录 |
|
||||
| OCR 服务不可用 | SYS_OCR_DOWN | 是 | 仍失败→运维OPS |
|
||||
|
||||
> ⚠️ 一期最大卡点:线上 OCR 涉信息安全,须法务+数据中心批;否则本地部署。此节点部署方式**待确认**。
|
||||
|
||||
---
|
||||
|
||||
### 节点② PRODUCT_MATCH · 产品对码 ★核心难点★
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 状态中心(Trae 内部算,不点页面) |
|
||||
| 前置条件 | order_item 已入库;对码表 mapping_config(PRODUCT) 已加载 |
|
||||
| 输入 | 每行订单的:通用名/批准文号/规格/厂家 |
|
||||
| 输出 | 每行 match_status(EXACT/FUZZY/MULTI/NONE) + 货品ID/编码;未命中清单 |
|
||||
|
||||
**处理步骤(给 Trae)**:
|
||||
1. 每行做规范化(去空格、全半角、规格标准化,如 15×2↔30片、干一瓶↔×1瓶)。
|
||||
2. 拼四要素键:通用名+批准文号+规格+厂家。
|
||||
3. 精确命中对码表 → EXACT,回填货品ID/编码/基本单位。
|
||||
4. 规格/厂家容错命中 → FUZZY,标记待复核。
|
||||
5. 同商品名多规格 → MULTI,生成一对多候选,建人工待办。
|
||||
6. 无命中 → NONE,建人工待办。
|
||||
7. 全 EXACT/已确认 → 放行进③;有 MULTI/NONE → job 转 WAITING_USER。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 每行都有明确 match_status | [ ] 命中的货品ID确实是这个药(抽查) |
|
||||
| [ ] EXACT 行回填了货品ID+编码+单位 | [ ] 一对多候选是"同一个药的不同规格写法",不是乱配 |
|
||||
| [ ] MULTI 行有候选列表供 H5 选 | [ ] 未命中的能在页面上一对多选择/新增 |
|
||||
| [ ] 命中率有统计数字 | [ ] 人工确认后,下次同款能自动命中(累积迭代) |
|
||||
| [ ] 人工确认结果回写对码表(新版本号) | [ ] 进口药英文厂家名能容错匹配 |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 一对多 | BIZ_MATCH_MULTI | 否 | 该条挂起→计划员选候选;整批继续 |
|
||||
| 无命中 | BIZ_MATCH_NONE | 否 | 该条挂起→计划员人工匹配/新增;整批继续 |
|
||||
|
||||
> 关键:未命中是**单条挂起**,不停整批。计划员处理完这条,继续跑其余。
|
||||
|
||||
---
|
||||
|
||||
### 节点②b SOURCE_ADD · 货源新增
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 业务在 M5 手工新增(状态中心只通知) |
|
||||
| 前置条件 | 对码命中了货品,但该供应商从未采过该品种 |
|
||||
| 输入 | 供应商 + 货品 |
|
||||
| 输出 | 货源建好的确认 |
|
||||
|
||||
**步骤**:
|
||||
1. 状态中心判断"该供应商×该货品"无货源记录。
|
||||
2. 推企业微信待办给业务:去 M5 新增货源。
|
||||
3. 业务新增完,在待办点"已完成"。
|
||||
4. job 恢复继续。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 能识别缺货源并建待办 | [ ] 通知内容说清楚是哪个供应商+哪个品种 |
|
||||
| [ ] 业务确认后能继续 | [ ] 新增货源后 M5 里能查到 |
|
||||
|
||||
**失败与去向**:缺货源→通知业务→业务新增→继续。不阻断其余可跑节点。
|
||||
|
||||
---
|
||||
|
||||
### 节点③ CONTRACT_CREATE · 合同建单
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA |
|
||||
| 前置条件 | 对码全部通过;供应商对照表已加载 |
|
||||
| 输入 | 供应商、订单日期、货品明细、模板类型 |
|
||||
| 输出 | 🔑合同编号、合同状态、模板类型 |
|
||||
|
||||
**页面操作步骤(给艺赛旗)**:
|
||||
1. 登录合同系统 `contract.gzmpc.com`。
|
||||
2. 按供应商对照表判断模板:格式(默认)/非格式(承兑)。
|
||||
3. 可复制历史合同再改(业务现行做法)。
|
||||
4. 填基础信息;**改签约日期/合同开始日期 = 订单日期**(硬约束)。
|
||||
5. 填货品明细(操作码/货品编码、名称、国药准字、规格、单位、数量)。
|
||||
6. 填付款信息(必填:付款方式、天数)。
|
||||
7. **医药城分支**:下载平台购销合同,同供应商同日**合并成一个PDF**上传(先合并,否则第二份覆盖第一份)。
|
||||
8. **小药药分支**:格式合同,跳过合同证文上传。
|
||||
9. 汇总金额,提交送审。
|
||||
10. 回调 complete,带合同编号。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 模板选择与对照表一致 | [ ] 签约日期 = 订单日期,没填成今天 |
|
||||
| [ ] 签约日期字段 = 订单日期 | [ ] 医药城:PDF 是同供应商同日全部合并的一份 |
|
||||
| [ ] 医药城已上传合并PDF且未被覆盖 | [ ] 小药药:确实没传合同证文 |
|
||||
| [ ] 小药药未走上传分支 | [ ] 合同金额/品种/数量与订单一致 |
|
||||
| [ ] 回传了合同编号 | [ ] 承兑供应商用的是非格式模板 |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 供应商合同禁掉/失效 | BIZ_CONTRACT_DISABLED | 否 | 邮件通知采购(可多人)重新禁掉;该单挂起 |
|
||||
| PDF 合并能力不支持 | CAP_PDF_MERGE_UNSUPPORTED | 否 | 换平台/运维OPS |
|
||||
| 页面改版找不到元素 | UI_ELEMENT_NOT_FOUND | 否 | 运维OPS |
|
||||
|
||||
---
|
||||
|
||||
### 节点④ CONTRACT_APPROVE_WAIT · 等合同审批
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 状态中心(定时器)+ 艺赛旗查状态 |
|
||||
| 前置条件 | 合同已送审 |
|
||||
| 输入 | 合同编号 |
|
||||
| 输出 | 审批通过确认 |
|
||||
|
||||
**步骤**:
|
||||
1. 送审后启动定时器(约半小时)。
|
||||
2. 到点后艺赛旗登录查审批状态(**建议轮询,别死等**)。
|
||||
3. 已通过→放行进④返利;未通过→继续等并重查;超阈值→升级运维。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 有定时/轮询机制,不是硬睡死等 | [ ] 只有审批"真的通过"了才进下一步 |
|
||||
| [ ] 未通过会重查,不误判 | [ ] 卡太久会有人被通知去催 |
|
||||
| [ ] 超阈值升级 OPS | |
|
||||
|
||||
> 注意:未审批完也会生成合同编号,但**必须审批完才导入返利中心**。
|
||||
|
||||
**失败与去向**:超时未审完→保持等待并重查;超阈值→运维/催办。
|
||||
|
||||
---
|
||||
|
||||
### 节点⑤ REBATE_HEADER · 返利中心总单
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA |
|
||||
| 前置条件 | 合同审批通过,拿到🔑合同编号 |
|
||||
| 输入 | 合同编号 |
|
||||
| 输出 | 返利中心总单号 |
|
||||
|
||||
**页面操作步骤**:
|
||||
1. 登录返利中心 `flgzmpc.com`。
|
||||
2. 进"采购协议管理"。
|
||||
3. 按**合同编号**查对应总单。
|
||||
4. 总单信息空白,填必填项、勾必选项,保存。
|
||||
5. 回调 complete,带总单号。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 按合同号查到了对应总单 | [ ] 总单必填项都填了,能保存成功 |
|
||||
| [ ] 必填项无遗漏 | [ ] 查出来的总单确实对应这份合同 |
|
||||
| [ ] 回传总单号 | |
|
||||
|
||||
**失败与去向**:查不到总单/必填缺失→运维或采购核查。
|
||||
|
||||
---
|
||||
|
||||
### 节点⑥ REBATE_IMPORT · 返利细单导入
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA |
|
||||
| 前置条件 | 总单已建 |
|
||||
| 输入 | 对码结果、数量、单价、订单号 |
|
||||
| 输出 | 导入成功/失败行 |
|
||||
|
||||
**页面操作步骤**:
|
||||
1. 下载细单模板。
|
||||
2. 填字段:操作码/货品编码、数量、单价、税率、承付方式、参数、细单开始时间(=订单时间)、失效日期、结算方式、**特定补偿=0**、**细单备注=订单号**、货源信息。
|
||||
3. 上传导入。
|
||||
4. 读取成功/失败行及原因。
|
||||
5. 回调 complete,带成功/失败计数。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 特定补偿字段 = 0 | [ ] 细单备注确实填了订单号(物流部要看) |
|
||||
| [ ] 细单备注 = 订单号 | [ ] 数量/单价与订单一致 |
|
||||
| [ ] 细单开始时间 = 订单时间 | [ ] 导入的品种数 = 应导数 |
|
||||
| [ ] 成功/失败行有明细回传 | [ ] 失败行原因能看懂 |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 存在重复编码(订单已做过) | REBATE_DUP_CODE | 否 | 该单挂起→采购;整批继续(重建总单规则**待确认**) |
|
||||
|
||||
---
|
||||
|
||||
### 节点⑦ REBATE_SUBMIT · 返利送审 ⭐决定成败⭐
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA |
|
||||
| 前置条件 | 细单导入成功 |
|
||||
| 输入 | 总单 |
|
||||
| 输出 | 送审完成(自动审批通过) |
|
||||
|
||||
**页面操作步骤**:
|
||||
1. 在返利中心对该总单点"提交送审"。
|
||||
2. 送审即自动审批通过(合同前端已审过一遍)。
|
||||
3. 回调 complete。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 确认送审动作已执行且成功 | [ ] 送审后,M5 里能带出这个协议(关键验证) |
|
||||
| [ ] 未送审绝不放行进 M5 | [ ] 没漏送审的单 |
|
||||
|
||||
**失败与去向**:未送审→**硬阻断**(不送审 M5 带不出品种,整个 job 停在此)。
|
||||
|
||||
> ⭐反直觉核心:卡 M5 的送审在**返利中心**,不在合同系统。这一步不做,后面全废。
|
||||
|
||||
---
|
||||
|
||||
### 节点⑧ M5_CREATE_ORDER · M5 建采购单(1055)
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 艺赛旗 iS-RPA(处理 M5 桌面客户端) |
|
||||
| 前置条件 | 返利已送审 |
|
||||
| 输入 | 货品明细、数量、订单号 |
|
||||
| 输出 | M5 采购单(**录入待保存,不点保存**) |
|
||||
|
||||
**页面操作步骤**:
|
||||
1. M5 采购订单管理 1055 → 新增。
|
||||
2. 选保管账 = 正常保管账(默认,只有一个)。
|
||||
3. 日期:若隔天做,改成前一天签约日期。
|
||||
4. **备注填订单号**(给采购结算勾对账用)。
|
||||
5. 新增细单,选品种(返利已送审的才带得出)。
|
||||
6. 选对应数量(协议累计采购量为0说明没用过)。
|
||||
7. 结算/接收方式/发票 = 返利中心已下发自动带入,**不填**。
|
||||
8. **★录完就停,不点保存★**。
|
||||
9. 回调 complete,标记"M5待保存"。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 保管账 = 正常保管账 | [ ] 品种/数量与订单、返利协议一致 |
|
||||
| [ ] 备注 = 订单号 | [ ] 备注订单号填对了(结算要看) |
|
||||
| [ ] 结算/接收/发票未手工填(用带入值) | [ ] 确认停在"待保存",没被自动保存 |
|
||||
| [ ] **未触发保存动作** | [ ] 隔天做的,签约日期改成了前一天 |
|
||||
|
||||
**失败与去向**
|
||||
|
||||
| 情况 | errorCode | 可重试 | 去向 |
|
||||
| --- | --- | --- | --- |
|
||||
| 质管资料缺失(供应商年报过期等) | M5_QC_MISSING | **否** | **停止→FAILED_MANUAL,转人工手工做,不等质管** |
|
||||
|
||||
> ⚠️ 硬规则:质管缺失**不重试、不等质管**(RPA 晚上跑,质管不会半夜维护)。直接转人工。
|
||||
|
||||
---
|
||||
|
||||
### 节点⑨ M5_MANUAL_CONFIRM · 人工检查→保存→签订
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 一期=人工(可配开关,阶段二转 RPA) |
|
||||
| 前置条件 | M5 已录入待保存 |
|
||||
| 输入 | 待保存采购单 |
|
||||
| 输出 | 已保存、已签订 |
|
||||
|
||||
**步骤**:
|
||||
1. 状态中心推待办给计划员:去检查。
|
||||
2. 计划员核对数量等 → 点保存 → 签订。
|
||||
3. 在待办确认已完成。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] auto_save_enabled 一期=0(人工) | [ ] 数量核对无误才保存 |
|
||||
| [ ] 开关可切换,便于阶段二放开 | [ ] 保存/签订后不可逆,确认无误 |
|
||||
| [ ] 人工确认动作有留痕 | [ ] 错的能在保存前改掉 |
|
||||
|
||||
> 不可逆:保存/签订后改不了,一期人工把关。
|
||||
|
||||
---
|
||||
|
||||
### 节点⑩ M5_RECEIPT_NOTICE · 生成收货通知单
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 一期=人工(可配开关) |
|
||||
| 前置条件 | 已签订 |
|
||||
| 输入 | 已签订采购单 |
|
||||
| 输出 | 收货通知单号 |
|
||||
|
||||
**步骤**:签订后点"生成收货通知单",采购合同流转结束。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 收货通知单号已生成并回写 | [ ] 供应商/货品/数量与采购单一致 |
|
||||
| [ ] 与 M5 单号关联落库 | [ ] 通知单号能查到 |
|
||||
|
||||
---
|
||||
|
||||
### 节点⑪ ARCHIVE_JOB · 归档
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 执行方 | 状态中心 |
|
||||
| 前置条件 | 全链完成 |
|
||||
| 输入 | 各业务编号 |
|
||||
| 输出 | 完整编号链 + 证据包 |
|
||||
|
||||
**步骤**:把订单号→合同编号→返利总单号→M5单号→收货通知单号全部落库,证据(Excel/PDF/截图/日志/人工记录)归档。
|
||||
|
||||
**验收标准**
|
||||
|
||||
| 代码验收 | 业务验收 |
|
||||
| --- | --- |
|
||||
| [ ] 五个业务编号全部落库可查 | [ ] 拿任一编号能追到整条链 |
|
||||
| [ ] 证据包完整可导出 | [ ] 抽查证据能还原当时操作 |
|
||||
|
||||
---
|
||||
|
||||
## 第五部分 · 你还没想到的事(容易漏,先摆到桌面)
|
||||
|
||||
> 这些不是需求,是"做的时候一定会撞上、现在不定后面返工"的坑。按优先级分档。
|
||||
|
||||
### 5.1 必须先拍板(不定就没法开工)
|
||||
|
||||
| # | 事项 | 为什么重要 | 建议 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | **OCR 信息安全** | 线上OCR公司可能不批,决定 OCR_EXTRACT 部署方式 | 拉法务+数据中心+业务三方会;倾向本地部署 |
|
||||
| 2 | **各系统账号** | RPA 要登录5个系统,得有专用账号 | 要生产+测试账号;确认能否多开、会不会挤下线 |
|
||||
| 3 | **M5 是桌面客户端还是网页** | 决定艺赛旗怎么接(桌面自动化 vs 浏览器) | 让业务确认;艺赛旗能处理桌面,但要提前验证 |
|
||||
| 4 | **订单状态过滤** | 只取"待收货"还是含其他,取错会多做/漏做 | 业务给明确状态清单 |
|
||||
| 5 | **对码表/供应商对照表/计划员表/邮箱** | 缺了对码、判模板、派单、发通知都做不了 | 业务限时提供,格式先对齐 |
|
||||
|
||||
### 5.2 并发与调度(晚上批量跑必踩)
|
||||
|
||||
- [ ] **一台 RPA 机同时只能开一个 M5?** 65~75单/天,串行够不够跑完一晚?要不要多机。
|
||||
- [ ] **登录态互斥**:同账号多处登录会不会把前一个踢下线。
|
||||
- [ ] **一个 job 卡住,别堵住后面的 job**:队列要能跳过挂起的、继续跑能跑的。
|
||||
- [ ] **定时触发时间**:几点跑?跑前一天订单,那"前一天"按几点切界。
|
||||
- [ ] **半小时等审批期间**,RPA 机是空等还是去干别的 job(建议轮询+切换,别占着机器空等)。
|
||||
|
||||
### 5.3 数据与幂等(重复跑会出乱子)
|
||||
|
||||
- [ ] **重复触发/重跑**:同一天同供应商重跑,会不会在合同/返利/M5 建重复单。执行前必须先检索已有单号。
|
||||
- [ ] **加单/拆单**:父订单号怎么关联,加单要重做协议——一期先记录转人工(**待确认**)。
|
||||
- [ ] **返利重复编码**重建总单的完整规则(会议被打断没讲完,**待确认**)。
|
||||
- [ ] **金额/数量精度**:单价小数位、合计对不对得上,别因四舍五入差分。
|
||||
- [ ] **同名不同药**:对码四要素里任一不同就是不同药,别被商品名骗了。
|
||||
|
||||
### 5.4 异常与回滚(错了怎么收场)
|
||||
|
||||
- [ ] **保存/签订不可逆**:一期靠人工把关。切自动前必须有准确率报告(阶段二判据要定量)。
|
||||
- [ ] **中途失败的半成品**:合同建了但返利没做,这单状态怎么标、下次怎么接着做。
|
||||
- [ ] **RPA 跑一半崩了**:节点要能从断点续,attempt_no+1,不覆盖历史。
|
||||
- [ ] **页面改版**:5个系统任一改版,元素定位就失效。要有 UI_ 类告警,快速通知运维。
|
||||
- [ ] **误操作防线**:M5 阶段一严禁自动点保存(auto_save_enabled=0 是硬默认)。
|
||||
|
||||
### 5.5 验收与上线(别一次全上)
|
||||
|
||||
- [ ] **单来源先跑通**:建议先医药城一个来源端到端,再加小药药、线下。
|
||||
- [ ] **准确率统计**:对码命中率、各节点成功率要有数字,支撑"何时转全自动"。
|
||||
- [ ] **证据可回溯**:每单能导出证据包(Excel/PDF/截图/日志),出问题能复盘。
|
||||
- [ ] **权限可见性**:合同/OCR 证据按角色可见,敏感文件脱敏标记。
|
||||
- [ ] **联调顺序**:状态中心单独跑通→假OCR假RPA打通全链→接真对码→接企业微信H5→接真RPA(先医药城)→返利/M5端到端。
|
||||
|
||||
### 5.6 安全底线(别裸奔)
|
||||
|
||||
- [ ] 状态中心所有写接口必须鉴权(内部 Bearer / RPA X-Agent-Token / H5 企业微信换JWT)。
|
||||
- [ ] 回调接口必须校验 token + 幂等键,**禁止无认证暴露**。
|
||||
- [ ] 账号密码不写死在流程里,走密钥管理。
|
||||
|
||||
---
|
||||
|
||||
## 名词表
|
||||
|
||||
| 词 | 意思 |
|
||||
| --- | --- |
|
||||
| 状态中心 | 自研的编排+审计中台(Trae 写的后台+网页),大脑 |
|
||||
| 艺赛旗 iS-RPA | 下层 RPA 工具,真正去点各系统页面,手脚 |
|
||||
| job | 一次完整流程实例 = 同供应商+同订单日期+同来源 = 一条合同 |
|
||||
| 节点 | 流程里的一步(如 ORDER_FETCH),做完才走下一个 |
|
||||
| 对码 | 把平台药名匹配到 M5 货品ID/编码(四要素:通用名+批准文号+规格+厂家) |
|
||||
| 挂起 | 单条转人工,整批继续跑 |
|
||||
| 硬阻断 | 整个 job 停住 |
|
||||
| 待保存 | M5 录完但不点保存,一期留给人工检查 |
|
||||
| 三把钥匙 | 🔑货品编码 / 🔑合同编号 / 🧵订单号,系统间靠这三个值接力 |
|
||||
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
# 采购主链 RPA 一期开发规划
|
||||
|
||||
> 用途:作为开发启动前的对齐底稿,明确"先做什么、后做什么、为什么这个顺序、卡在哪里"。
|
||||
> 配套文档:《采购主链RPA一期需求文档_三方会审版》(业务规则)、《界面截图证据记录》(字段结构)。
|
||||
> 核心原则:**按风险和依赖排序,不按流程顺序**。让开发启动不被 OCR 决策和资料到位阻塞。
|
||||
|
||||
---
|
||||
|
||||
## 一、指导原则
|
||||
|
||||
1. **风险前置**:最可能推翻项目价值的假设(对码命中率),最先验证,且用最小成本验证。
|
||||
2. **依赖解耦**:状态中心与 RPA 执行器分层,业务规则和审计口径掌握在项目方手里,RPA 平台可替换。
|
||||
3. **单路打通**:先用最顺的一条来源(医药城)跑通端到端骨架,再横向扩展其他来源。
|
||||
4. **降难度上线**:一期只做到"录入待保存 + 人工检查",把"自动保存/签订"做成配置开关(关闭),规避不可逆操作风险。
|
||||
5. **阻塞并行**:OCR 信息安全、资料收集这类非技术阻塞,与开发并行推动,不占用开发关键路径。
|
||||
|
||||
---
|
||||
|
||||
## 二、三类拦路虎与应对
|
||||
|
||||
| 类型 | 具体事项 | 应对方式 | 是否阻塞开发启动 |
|
||||
| --- | --- | --- | --- |
|
||||
| 决策阻塞 | OCR 走线上还是本地部署(信息安全) | 拉法务部+数据中心+业务三方会 | 否(只阻塞线下手工单分支) |
|
||||
| 数据依赖 | 对码表、供应商对照表、计划员表、邮箱、测试账号 | 业务方并行收集 | 部分(对码表验证需要) |
|
||||
| 技术未知 | 对码命中率、页面稳定性、返利重复导入处理 | 离线脚本 + POC 试出来 | 是(需最先解决对码) |
|
||||
|
||||
**结论**:只有"技术未知"里的对码命中率必须最先攻,其余两类并行推进,不进关键路径。
|
||||
|
||||
---
|
||||
|
||||
## 三、阶段划分与任务拆解
|
||||
|
||||
### 阶段 0:可行性验证(不写 RPA 代码,最先做)
|
||||
|
||||
**目标**:验证对码命中率,决定项目价值和后续工作量。
|
||||
|
||||
| 任务 | 内容 | 产出 | 依赖 |
|
||||
| --- | --- | --- | --- |
|
||||
| 0.1 收集样本 | 现有货品对码表 + 一批真实历史订单Excel(医药城/小药药各若干天) | 样本数据集 | 业务提供 |
|
||||
| 0.2 离线匹配脚本 | 按"通用名+批准文号+规格+厂家"四要素拼接匹配,统计命中率 | 命中率报告 | 0.1 |
|
||||
| 0.3 未命中归因 | 分析未命中的规格差异模式(15×2 vs 30片、干一瓶 vs ×1瓶等) | 差异模式清单 | 0.2 |
|
||||
| 0.4 对码策略结论 | 精确匹配率、需模糊匹配的规则、需一对多人工的比例 | 对码方案 | 0.3 |
|
||||
|
||||
**为什么最先做**:如果命中率过低(如 <70%),意味着大量人工介入,需重新评估 RPA 价值主张或先集中精力做"对码表建设工具"。这一步投入最小、结论最快、不需要任何系统账号或RPA环境。
|
||||
|
||||
**验收**:给出明确的命中率数字和未命中处理成本估算。
|
||||
|
||||
---
|
||||
|
||||
### 阶段 1:状态中心骨架 + 医药城端到端最小闭环
|
||||
|
||||
**目标**:打通"医药城导出 → 对码 → 合同 → 返利 → M5录入待保存"一条可跑的链路。
|
||||
|
||||
#### 1.1 状态中心骨架
|
||||
| 任务 | 内容 |
|
||||
| --- | --- |
|
||||
| 数据模型 | `job / job_node / user_task / mapping_config / job_artifact / job_event` |
|
||||
| 状态机 | 7状态流转 + attempt_no 重试机制(不覆盖历史) |
|
||||
| 幂等机制 | 文件哈希、业务键(合同号+类型+供应商+来源)、回调幂等键 |
|
||||
| RPA回调接口 | 统一节点契约(jobId/nodeCode/attemptNo/status/businessRefs/artifacts...) |
|
||||
| 证据存储 | 目录结构 `job_id/节点编码/attempt_no/`,截图/日志/JSON |
|
||||
|
||||
#### 1.2 医药城业务节点(RPA执行器)
|
||||
| 节点编码 | 内容 | 关键细节 |
|
||||
| --- | --- | --- |
|
||||
| `ORDER_FETCH` | 登录医药城,选前一天,导出订单明细Excel | 订单状态过滤【待确认】 |
|
||||
| `PRODUCT_MATCH` | 四要素对码 → 货品ID/编码,未命中转人工 | 复用阶段0成果 |
|
||||
| `SOURCE_ADD` | 未采购过的供应商品种,通知M5新增货源 | — |
|
||||
| `CONTRACT_CREATE` | 合同系统建单,选模板,改签约日期=订单日期 | **同供应商同日合并PDF上传** |
|
||||
| `CONTRACT_APPROVE_WAIT` | 送审后等待(半小时后查审核状态) | 等待策略可配置 |
|
||||
| `REBATE_HEADER` | 返利中心按合同号查总单,填必填项保存 | 查出信息为空,需手工填 |
|
||||
| `REBATE_IMPORT` | 细单模板导入(操作码/数量/单价/税率/承付/结算/特定补偿0/细单备注填订单号/货源) | 重复编码处理【待确认】 |
|
||||
| `REBATE_SUBMIT` | 提交送审(决定M5能否带出协议) | 关键卡点 |
|
||||
| `M5_CREATE_ORDER` | M5-1055新增订单+细单,备注填订单号,选默认正常保管账 | **不点保存**(阶段一) |
|
||||
|
||||
#### 1.3 人工协同(企业微信/H5 最小版)
|
||||
| 任务 | 内容 |
|
||||
| --- | --- |
|
||||
| 待办推送 | 对码未命中、货源缺失、质管卡住等,推送到企业微信 |
|
||||
| H5确认页 | 查看待确认项、一对多选择、人工修改留痕 |
|
||||
| 人工检查入口 | M5录入待保存后,人工检查→保存→签订→生成收货通知单 |
|
||||
|
||||
**验收**:一条真实医药城订单能从导出跑到 M5 待保存,各节点有证据,人工可在H5介入。
|
||||
|
||||
---
|
||||
|
||||
### 阶段 2:横向扩展来源 + 完善异常
|
||||
|
||||
**目标**:接入小药药、线下手工单,补齐异常分流。
|
||||
|
||||
| 任务 | 内容 | 前置条件 |
|
||||
| --- | --- | --- |
|
||||
| 2.1 小药药接入 | 逻辑类似医药城,但格式合同不需上传PDF | **需先补小药药界面画面** |
|
||||
| 2.2 线下手工单+OCR | OCR识别开单截图→对码 | **OCR信息安全决策通过** |
|
||||
| 2.3 异常分流完善 | 对码未命中、货源缺失、合同禁掉失效邮件通知、返利重复、质管卡住转人工 | 邮箱/角色表到位 |
|
||||
| 2.4 收货通知单 | `M5_RECEIPT_NOTICE` + `ARCHIVE_JOB` | — |
|
||||
| 2.5 订单号追溯 | 返利(物流看)/M5(结算看)订单号贯穿校验 | — |
|
||||
|
||||
---
|
||||
|
||||
### 阶段 3:稳定性与自动化开关
|
||||
|
||||
**目标**:验证准确率,逐步放开自动化。
|
||||
|
||||
| 任务 | 内容 |
|
||||
| --- | --- |
|
||||
| 3.1 准确率统计 | 统计各节点自动完成率、人工纠正率 |
|
||||
| 3.2 自动保存开关 | 准确率达标后,打开 `M5_MANUAL_CONFIRM` 自动化(保存/签订/生成通知单) |
|
||||
| 3.3 重试与超时 | 系统异常自动重试1-2次;节点超时升级;待办30分钟/2小时提醒升级 |
|
||||
| 3.4 平台可替换POC | 若需接第二个RPA平台,验证登录/检索/导入/截图/回调用例 |
|
||||
|
||||
---
|
||||
|
||||
## 四、技术选型推荐
|
||||
|
||||
> 以下为推荐方案,**均可根据公司现有技术栈和团队能力调整**。医药商业公司IT环境通常以稳定、可维护为先。
|
||||
|
||||
### 状态中心(后端)
|
||||
| 项 | 推荐 | 理由 | 备选 |
|
||||
| --- | --- | --- | --- |
|
||||
| 语言/框架 | **Python 3 + FastAPI** | 与对码/OCR脚本同栈,开发快,Pydantic 校验回调契约方便;骨架已跑通(`state_center/`) | Java + Spring Boot(团队偏Java时可平移,契约不变) |
|
||||
| 数据库 | **MySQL 8** | 状态中心自有库,只存流程状态+累积别名+证据(对码表在 Oracle,直连查不复制) | PostgreSQL |
|
||||
| 状态机 | 轻量自实现(NODE_ORDER 顺序 + 7 状态枚举) | 7状态流转清晰,自实现更可控 | — |
|
||||
| 对象存储 | MinIO / 公司现有NAS | 合同PDF、截图、OCR JSON等证据 | 本地文件系统 |
|
||||
|
||||
### RPA 执行平台
|
||||
| 项 | 推荐 | 理由 |
|
||||
| --- | --- | --- |
|
||||
| 平台 | **国产商业RPA(影刀 / 来也UiBot / 艺赛旗)择一POC** | 5个系统均为国内Web/客户端,国产RPA对中文界面、验证码、客户端控件支持好,有本地部署和技术支持;需求已要求"平台可替换",先接一个主平台 |
|
||||
| 关键要求 | 支持HTTP回调状态中心、截图留证、Excel读写、PDF合并、文件上传 | 对应本项目节点契约 |
|
||||
|
||||
> 选型建议:让候选平台跑一个POC用例——登录返利中心+检索+模板导入+截图+HTTP回调,通过再定。
|
||||
|
||||
### OCR(仅线下手工单,取决于信息安全决策)
|
||||
| 场景 | 推荐 |
|
||||
| --- | --- |
|
||||
| 若允许本地部署 | **PaddleOCR(本地)** — 无信息安全顾虑,一次性投入 |
|
||||
| 若允许线上 | 阿里/腾讯云OCR — 按量付费,实际用量约十几元/月 |
|
||||
|
||||
### 企业微信 / H5
|
||||
| 项 | 推荐 |
|
||||
| --- | --- |
|
||||
| 待办推送 | 企业微信应用消息/群机器人 |
|
||||
| H5 | 轻量前端(Vue/React)+ 企业微信OAuth身份 |
|
||||
|
||||
### 部署
|
||||
| 项 | 推荐 |
|
||||
| --- | --- |
|
||||
| 状态中心 | 公司内网服务器/容器,与业务系统同网段便于RPA访问 |
|
||||
| RPA运行机 | 独立运行机/虚拟机,7×24或定时任务,保持登录态 |
|
||||
|
||||
---
|
||||
|
||||
## 五、关键路径与阻塞项(甘特视角)
|
||||
|
||||
```
|
||||
阶段0对码验证 ──┬──────────────────────────► (决定项目走向)
|
||||
│
|
||||
业务收集资料 ───┼───(对码表/账号/对照表/邮箱)──► 阶段1需要
|
||||
│
|
||||
OCR信息安全决策 ┼────────────────► 仅阻塞阶段2.2线下单
|
||||
│
|
||||
└─► 阶段1 骨架+医药城 ─► 阶段2 扩展 ─► 阶段3 自动化
|
||||
```
|
||||
|
||||
**关键路径**:阶段0对码验证 → 阶段1医药城闭环。
|
||||
**不在关键路径的阻塞**:OCR决策(只影响2.2)、小药药画面(只影响2.1)。
|
||||
|
||||
---
|
||||
|
||||
## 六、启动前需要业务方立即提供
|
||||
|
||||
| 优先级 | 事项 | 用于 |
|
||||
| --- | --- | --- |
|
||||
| P0 | 货品对码表 + 历史订单Excel样本 | 阶段0验证 |
|
||||
| P0 | 测试账号(合同系统/返利中心/M5/医药城) | 阶段1开发 |
|
||||
| P1 | 供应商对照表(合同类别/结算/合同性质) | `CONTRACT_CREATE` |
|
||||
| P1 | 计划员与合同对应关系表 | 待办路由 |
|
||||
| P1 | 通知邮箱(禁掉通知、质管缺料,可多人) | 异常分流 |
|
||||
| P2 | 小药药平台完整演示/截图 | 阶段2.1 |
|
||||
|
||||
## 七、需先拍板的决策项
|
||||
|
||||
1. **OCR信息安全**(最大卡点):线上是否获批 / 本地部署预算。
|
||||
2. **返利中心重复订单**完整处理规则(会议未讲完)。
|
||||
3. **医药城订单状态过滤**:只取"待收货"还是含其他。
|
||||
4. **加单场景**:父订单号关联、重做逻辑。
|
||||
5. **技术栈确认**:上述推荐是否与公司现有栈冲突。
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
# 采购主链 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端到端联调。
|
||||
|
|
@ -0,0 +1,488 @@
|
|||
# 采购主链 RPA 一期需求文档(三方会审版)
|
||||
|
||||
> 版本说明:本文基于三段会议录像(共约 87 分钟)的**音频逐句转写** + **系统界面截图**双重证据重写。
|
||||
>
|
||||
> 与《会议整理版》的关系:早期整理版把流程理解为"合同文件入池 → OCR → 映射",方向性错误。本文以**电商订单驱动**为主线完全重写业务骨架,保留早期版本扎实的工程框架(状态机、幂等、证据、异常分流)。
|
||||
>
|
||||
> 证据标注约定:
|
||||
> - `【音】mm:ss`:来自音频转写,可在 `video_frames/transcript_v1~v3.txt` 逐字核对。
|
||||
> - `【画】`:来自系统界面截图,字段名以画面为准。
|
||||
> - `【待确认】`:会议中被时间打断或未展开,需下次会议拍板。
|
||||
>
|
||||
> 视频顺序:因会议软件时长限制切成三段,真实顺序为 **视频3(医药城)→ 视频2(小药药+合同系统+返利中心)→ 视频1(M5演示+18问题复盘)**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句话业务本质
|
||||
|
||||
福建广药洁达医药是一家**以销定采**的医药商业公司:先在京东/阿里等平台卖货,再反向采购。采购来源分**线上电商平台**和**线下供应商**。RPA 要做的,是把采购人员**每天**手工做的"从平台导订单 → 产品对码 → 建合同 → 返利中心建协议 → M5 下采购单 → 生成收货通知单"这条重复链路自动化。
|
||||
|
||||
> 【音】v3 04:36 "我是先是以销定采的,全部都是这种"
|
||||
> 【音】v3 04:16 "我的客户是在京东阿里这样的全国性平台,包括大的连锁比如大参林"
|
||||
|
||||
---
|
||||
|
||||
## 1. 三种订单来源(一期核心分流)
|
||||
|
||||
| 来源 | 性质 | 数据获取方式 | 是否需OCR | 合同产生方式 | 一期是否纳入 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **医药城** | 线上平台 | 平台"我的订单→导出订单明细"下载 Excel | 否 | 平台**秒出**购销合同,需下载并**合并上传**到合同系统 | 是 |
|
||||
| **小药药** | 线上平台 | 平台导出 Excel | 否 | 已签**年度协议**,合同系统用**格式合同**,**不需上传合同证文** | 是 |
|
||||
| **线下供应商** | 手工单 | 供应商发来的**开单截图/PDF**(每家样式不同) | **是** | 后补合同(先出货后补章),需人工做电子版合同 | 是 |
|
||||
| 招财平台 | 线上平台 | — | — | 目前纯人工,量少 | **否**(一期不做) |
|
||||
|
||||
> 【音】v3 02:19 "一般选前一天的订单来做,这边有个订单导出的地方"
|
||||
> 【音】v2 08:37 "只要你在他平台上下单,他马上秒出合同…他们平台是秒出的"
|
||||
> 【音】v2 25:04 "小药药它省了一步,不需要传合同,用的是格式的,因为他签了年度协议"
|
||||
> 【音】v2 05:31 "线下都是手工单…如果是有账期的客户,我们叫对方开单,他就只会发一个开单的截图给我们"
|
||||
> 【音】v1 07:06 "招财平台…暂时不做"
|
||||
|
||||
**关键差异点(决定 RPA 分支逻辑)**:
|
||||
- 医药城 vs 小药药:**合同证文是否需要上传**(医药城要合并PDF上传,小药药不用)。
|
||||
- 线上 vs 线下:**是否需要OCR**(线上导出结构化Excel,线下是图片需OCR)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 全流程主链(以真实操作顺序重排)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[每天定时/手动触发] --> B{订单来源}
|
||||
B -->|医药城| C1[平台导出订单明细Excel]
|
||||
B -->|小药药| C2[平台导出订单明细Excel]
|
||||
B -->|线下手工单| C3[OCR识别开单截图/PDF]
|
||||
C1 --> D[产品对码: 通用名+批准文号+规格+厂家 → 货品ID/编码]
|
||||
C2 --> D
|
||||
C3 --> D
|
||||
D --> E{是否全部匹配}
|
||||
E -->|有未匹配| E1[通知业务人工维护对码表/新增货源]
|
||||
E -->|全部匹配| F[合同系统建单]
|
||||
F --> F1[医药城: 合并同供应商同日PDF并上传]
|
||||
F --> F2[小药药: 格式合同,无需上传]
|
||||
F1 --> G[合同送审, 半小时内审批]
|
||||
F2 --> G
|
||||
G --> H[取合同编号]
|
||||
H --> I[返利中心: 按合同号建采购协议总单]
|
||||
I --> J[返利中心: 细单模板导入货品明细]
|
||||
J --> K[返利中心: 提交送审]
|
||||
K --> L{M5能否带出协议}
|
||||
L -->|带不出| L1[返利中心未送审, 阻断]
|
||||
L -->|能带出| M[M5-1055: 新增采购订单+细单]
|
||||
M --> N{M5保存是否报错}
|
||||
N -->|质管资料缺失| N1[停止,转人工手工做, 不等质管]
|
||||
N -->|正常| O[人工检查数量→点保存→签订]
|
||||
O --> P[生成收货通知单]
|
||||
P --> Q[归档: 订单号/合同号/协议号/M5单号/通知单号]
|
||||
```
|
||||
|
||||
**本流程的关键设计要点**:
|
||||
1. 入口是"电商订单导出"而非"合同文件入池"。
|
||||
2. OCR 从贯穿全程的核心节点,降级为"仅线下手工单"的分支。
|
||||
3. 新增"产品对码"作为真正的核心难点节点。
|
||||
4. 送审的关键卡点在**返利中心**(决定 M5 能否带出协议),而非合同系统。
|
||||
5. 末端"保存/签订"一期由**人工**执行(分阶段上线,见 §9)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 节点级需求(字段级 + RPA动作级)
|
||||
|
||||
### N01 订单获取与来源分流
|
||||
|
||||
**触发**:每天定时(跑前一天订单)或手动触发。
|
||||
> 【音】v2 01:12 "每天…每天会到前一天";v3 02:16 "选前一天的或单天的合同订单来做"
|
||||
|
||||
**RPA 动作(线上平台)**:
|
||||
1. 登录医药城 / 小药药平台。
|
||||
2. 进入"我的订单",选择日期(默认前一天)。
|
||||
3. 点击"导出订单明细",下载 Excel。
|
||||
|
||||
**医药城订单明细表字段【画】**(订单明细.xls):
|
||||
|
||||
| 列 | 字段 | 说明 | RPA用途 |
|
||||
| --- | --- | --- | --- |
|
||||
| A | 下单时间 | 如 `2026-08-04 18:50:48.0` | → 合同签约日期 |
|
||||
| B | 创建人 | — | — |
|
||||
| C | 订单号 | 如 `ZQD202608041850483308` | **贯穿全流程的关键号**(见§6) |
|
||||
| D | 父订单号 | 加单/拆单关联 | 【待确认】加单合并逻辑 |
|
||||
| E | 支付方式 | 如"账期支付" | — |
|
||||
| F | 商家 | 如"重庆亿昊药业有限公司" | → 供应商,合同合并维度 |
|
||||
| G | 订单类型 | 如"普通订单" | — |
|
||||
| H | 订单状态 | 待付款/已取消/待收货 | **过滤条件**【待确认】只取哪些状态 |
|
||||
| I | 品名 | 如"哈药牌钙铁锌口服液" | 对码用 |
|
||||
| — | 产品编码 | 如 `2919482534` | 平台侧编码,对码用 |
|
||||
| — | 规格/厂家/国药准字 | | 对码四要素 |
|
||||
|
||||
**线下手工单**:来源是供应商发来的开单截图/PDF,**每家样式不同**,走 N02 OCR。
|
||||
> 【音】v2 06:57 "有可能是合同,也有可能是手工单,每个客户样式都长不一样"
|
||||
|
||||
**输出**:`job_id`、订单来源类型、原始订单Excel/图片存证、订单号列表、订单日期、供应商列表。
|
||||
|
||||
---
|
||||
|
||||
### N02 OCR 识别(仅线下手工单)
|
||||
|
||||
> ⚠️ 重点:OCR **不是全流程核心**,只服务线下手工单(约100多份/月)。线上两平台直接导出结构化 Excel,不走 OCR。
|
||||
> 【音】v1 08:54 "线下一百多份…手工单一个月一共一百多份"
|
||||
|
||||
**OCR 服务选型(未决,最大卡点)**:
|
||||
- 线上 OCR 服务:约 1000元/月(10亿token),按线下用量估算实际约十几元/月。
|
||||
- **信息安全风险**:合同信息走外部线上OCR,公司可能不批,需拉**法务部 + 数据中心**评估;否则改**本地部署**(费用更高)。
|
||||
> 【音】v1 09:20 "因为涉及信息安全…我担心他们不同意…需要跟法务部还有数据中心去沟通"
|
||||
> 【音】v1 30:15 "现在最麻烦的就是这个OCR的信息安全问题"
|
||||
|
||||
**抽取字段**:品名、规格、厂家、批准文号、数量、单价、供应商。
|
||||
**规则**:识别后同样进入 N03 对码;低置信度/缺字段转人工。
|
||||
|
||||
---
|
||||
|
||||
### N03 产品对码(真正的核心难点)
|
||||
|
||||
**目的**:把电商平台/开单截图上的药品,匹配到 M5 系统的货品ID/货品编码。
|
||||
|
||||
**匹配四要素**【音】v3 10:26:**通用名 + 批准文号 + 规格 + 生产厂家**(确定药品唯一性)。
|
||||
|
||||
**M5 货品对码基础表字段【画】**(货品运营管理.xls):
|
||||
|
||||
| 列 | 字段 |
|
||||
| --- | --- |
|
||||
| A | 通用名 |
|
||||
| B | 批准文号 |
|
||||
| C | 规格 |
|
||||
| D | 基本单位 |
|
||||
| E | 生产厂家 |
|
||||
| F | 货品ID |
|
||||
| G | 货品编码 |
|
||||
|
||||
**匹配方式**【音】v3 12:03:"把这几列合并在一起来 V(VLOOKUP)"——即用"通用名+批准文号+规格+厂家"拼接键做匹配,得到货品ID/编码。
|
||||
|
||||
**核心痛点:规格描述不一致导致匹配失败**:
|
||||
> 【音】v3 14:19 "阿卡波糖,我们是15乘2,对方可能直接写30片,那我们就匹不到"
|
||||
> 【音】v3 14:04 "他写干一瓶,我们基础是乘一瓶,就会没匹配到"
|
||||
> 【音】v3 14:28 "摩摩一…也匹配不到,都是规格上面的问题"
|
||||
|
||||
**业务已有资产**:业务方**已在维护"货品对码表"**,是累积迭代的(这次匹配剩下的下次继续拼)。
|
||||
> 【音】v3 14:44 "文华我们一直都在准备做这个叫货品对码表的信息"
|
||||
> 【音】v2 02:16 "我自己做了一个商品编码表…这一次跟下一次叠加,一直去拼"
|
||||
|
||||
**RPA 处理规则**:
|
||||
1. 能精确命中对码表 → 自动填货品ID/编码。
|
||||
2. 匹配不上 → **一对多显示**,把不同规格描述与M5同一商品名对应出来,转人工选择/新增。
|
||||
> 【音】v3 14:55 "匹不到的需要一对多的显示,把不同规格的样式跟我们选中的M5里面同一个商品名对应出来"
|
||||
> 【音】v3 15:47 "如果没匹配上,就通知你们去做人工的匹配…我们就知道要去新增这个东西"
|
||||
|
||||
**厂家名匹配难点**:对码表厂家名有大量英文(进口药,如 `Abbott Biologicals B.V.`、`AESICA PHARMACEUTICALS S.r.l.`),厂家名匹配需容错。【画】
|
||||
|
||||
**输出**:对码结果(货品ID、货品编码、基本单位)、未匹配清单、一对多候选。
|
||||
|
||||
---
|
||||
|
||||
### N03b 货源信息新增
|
||||
|
||||
**规则**:如果从未从某供应商采购过某品种,需在 **M5** 新增该"货源"。
|
||||
> 【音】v3 16:38 "没有从这家供应商购进过的话,我从这家供应商购进这个品种,就要增加这个货源"
|
||||
> 【音】v3 16:53 "货源基础数据在 M5"
|
||||
|
||||
**处理**:匹配不到货源 → 通知业务在 M5 新增 → 完成后继续。
|
||||
|
||||
---
|
||||
|
||||
### N04 合同系统建单
|
||||
|
||||
**系统**【画】:独立域名 `contract.gzmpc.com/km/agreement/`,走 RPA 访问。
|
||||
|
||||
**两套合同模板**【音】v2 11:51:
|
||||
| 模板 | 用途 |
|
||||
| --- | --- |
|
||||
| 格式采购合同(药品通用) | 默认,大部分用这个 |
|
||||
| 非格式采购合同(单次) | 承兑(要承兑)的用这个 |
|
||||
> 【音】v2 12:22 "要承兑的用非格式的,其他都是用格式的"
|
||||
|
||||
**合同维度(硬规则)**:**同一供应商 + 同一订单日期 = 一条合同**。
|
||||
> 【音】v2 23:32 "同一个商家、同一天订单,开一条合同"
|
||||
> 【音】v2 23:57 "不会两个商家做成同一个合同"
|
||||
|
||||
**签约日期硬约束**:合同签约日期 = 订单日期(当天做当天合同)。
|
||||
> 【音】v1 04:45 "唯一同意我的就是把签约日期改成前一天…他规定就是你要同一天做合同"
|
||||
> 【音】v2 18:31 "订单日期我下单哪天,合同就选哪天,6号我就选6号"
|
||||
|
||||
**RPA 动作**:
|
||||
1. 登录合同系统。
|
||||
2. 选择模板(格式/非格式,依供应商对照表判断)。
|
||||
3. **可复制历史合同**再改(业务现在就是复制起草)。
|
||||
> 【音】v2 16:16 "我们销售都是用复制的,你只要改后面的就好了"
|
||||
4. 填基础信息 + 修改签约日期/合同开始日期为订单日期。
|
||||
5. 填货品明细(先下模板,填操作码/货品编码、名称、国药准字、规格、单位、数量)。
|
||||
6. 填付款信息(必填):付款方式、天数等。
|
||||
> 【音】v2 20:38 "付款信息这些是必填的"
|
||||
7. **医药城分支**:下载平台购销合同,同供应商同日的**合并成一个PDF**上传到合同证文(传第二份会覆盖第一份,所以必须先合并)。
|
||||
> 【音】v2 24:44 "昆山的所有合并成一个PDF,重庆的合并成一个PDF"
|
||||
> 【音】v2 22:45 "如果你传第二份,他会把前面的覆盖掉"
|
||||
8. **小药药分支**:格式合同,跳过合同证文上传。
|
||||
9. 汇总金额到总单,提交。
|
||||
|
||||
**合同版本/类别/结算/合同性质等字段**:业务方提供**供应商对照表**,RPA 按表判断填写。
|
||||
> 【音】v1 12:34 "我们可以给你这个供应商的合同类型、结算类型、合同性质都列出来,给机器人一个总表"
|
||||
|
||||
**审批**:录入后送审,**半小时内**审批完成(不指定审批人,着急就催领导)。RPA 策略:录入后等半小时再启动下游。
|
||||
> 【音】v2 26:47 "半个小时内就批完了…着急我们就直接跑到领导旁边催他批"
|
||||
> 【音】v1 16:03 "录入完之后RPA就设定半个小时后再启动返利中心的功能"
|
||||
|
||||
**合同编号**:未审批完也会生成合同编号,但**必须审批完才导入返利中心**。
|
||||
> 【音】v2 26:16 "没有审批完他也会生成合同编号"
|
||||
> 【音】v2 26:47 "我们都等批完了然后才开始做的"
|
||||
|
||||
**输出**:合同编号、合同状态、模板类型。
|
||||
|
||||
---
|
||||
|
||||
### N05 返利中心采购协议总单
|
||||
|
||||
**系统**【画】:`flgzmpc.com`,主体"福建广药洁达医药",有"待审批事项"和异步报表导出下载。
|
||||
|
||||
**RPA 动作**:
|
||||
1. 登录返利中心。
|
||||
2. 进入"采购协议管理"。
|
||||
3. 按**合同编号**查询对应总单。
|
||||
4. 查到的总单信息是**空白的**,需手工填必填项(勾选必选项)。
|
||||
> 【音】v2 29:19 "这些信息都是空白的,是要手工填的…把必选项勾一下"
|
||||
5. 填总单必填字段并保存。
|
||||
> 【音】v2 29:55 "总单填完,然后下面的信息一样新增导入"
|
||||
|
||||
**输出**:返利中心总单号。
|
||||
|
||||
---
|
||||
|
||||
### N06 返利中心细单模板导入
|
||||
|
||||
**细单必填字段【音】v2 30:03-31:57**:
|
||||
|
||||
| 字段 | 值/规则 | 出处 |
|
||||
| --- | --- | --- |
|
||||
| 操作码/货品编码 | 前面对码结果 | v2 30:17 |
|
||||
| 数量 | 如 234 | v2 30:21 |
|
||||
| 单价 | 如 6.7 | v2 30:24 |
|
||||
| 税率 | 必填 | v2 30:28 |
|
||||
| 承付方式 | 如 11(下月N号结) | v2 30:38 |
|
||||
| 参数 | 如 25(号) | v2 30:41 |
|
||||
| 细单开始时间 | = 订单时间 | v2 30:43 |
|
||||
| 失效日期 | 拉长一点 | v2 30:52 |
|
||||
| 结算方式 | 如 7 | v2 31:03 |
|
||||
| 特定补偿 | **一定填 0** | v2 31:09 |
|
||||
| 约定或B | 不一定填 | v2 31:13 |
|
||||
| **细单备注** | **必填订单号** | v2 31:17 |
|
||||
| 货源信息 | 如"310设定上" | v2 31:51 |
|
||||
|
||||
> 【音】v2 31:22 "细单备注就是填定单号…这个是一定要填的,药城跟小药现在都有填"
|
||||
|
||||
**RPA 动作**:下模板 → 填上述字段 → 上传导入 → 读取成功/失败行及原因。
|
||||
|
||||
**重复订单报错**:如果订单已做过,导入会提示"存在重复的编码",需重新创建总单再导入。
|
||||
> 【音】v2 33:26 "如果重复了他会提示存在重复的编码,那你要重新创建总单,再去把细单输进去"
|
||||
> 【待确认】此处会议被时间打断,重复处理的完整规则未讲完。
|
||||
|
||||
**导入成功后**:提交送审。
|
||||
> 【音】v2 34:15 "导完成功了以后提交送审…然后就会到M5"
|
||||
|
||||
---
|
||||
|
||||
### N07 返利中心送审(决定 M5 能否带出协议)⭐关键卡点
|
||||
|
||||
> ⚠️ 注意:送审并非在合同系统完成。真正决定下游的送审在**返利中心**。
|
||||
|
||||
**规则**:返利中心细单**必须送审**,否则 M5 里选品种时协议**带不出来**。
|
||||
> 【音】v1 02:35 "没有点送审他到不了M5…如果返利中心没有送审,M5这里就不会跳出来"
|
||||
> 【音】v1 02:44 "你总单进完之后,输这个品种,他跳不出来,如果返利中心没有的话"
|
||||
|
||||
**送审即自动审批**(合同前端已审过一遍,返利中心送审后自动通过)。
|
||||
> 【音】v1 02:48 "直接送审就一次自动审批了…因为合同已经审过一次,前端已经审了一遍"
|
||||
|
||||
---
|
||||
|
||||
### N08 M5 采购订单创建(1055)
|
||||
|
||||
**系统**【画】:INCA 系统,角色"福建洁达采购员",采购订单管理 1055。
|
||||
|
||||
**采购订单总单字段【画】**:订单编号、业务日期、供应商、数据员、联系人、订单金额、书面订单号、开始日期、结果日期、**签约日期**、结算方式、预付款、其他费用、配送加点率、签约人、送达地点、代理方式、签约类。
|
||||
|
||||
**采购订单细单字段【画】**:采购员、货品ID、通用名、商品名、条形码、规格型号、基本单位、生产厂家、产地、上市许可持有人、**货品数量**、单价、金额。
|
||||
|
||||
**自动带入字段(不需RPA填,返利中心已选后下发)**:
|
||||
> 【音】v1 20:27 "接收方式不可以选,因为返利中心已经做了,直接下发下来的"
|
||||
> 【音】v1 21:15 "发票名称也是自动带下来的"
|
||||
|
||||
| 字段 | 处理 |
|
||||
| --- | --- |
|
||||
| 结算方式/承付模式 | 返利中心下发,M5不填 |
|
||||
| 接收方式 | 返利中心下发 |
|
||||
| 发票名称 | 自动带入 |
|
||||
| 保管账 | 只选默认"正常保管账"(只有一个) |
|
||||
|
||||
> 【音】v1 20:02 "保管账我们只选一个正常保管账…默认是正常保管账"
|
||||
|
||||
**关键操作细节**【音】v1 00:17-01:22:
|
||||
1. M5 直接做订单 → 新增。
|
||||
2. 选保管账(B选项,默认正常保管账)。
|
||||
3. 日期:若第二天做,改成前一天签约日期。
|
||||
4. **备注填订单号**(给采购结算勾对账用)。
|
||||
> 【音】v1 00:50 "备注要把订单号粘过来"
|
||||
> 【音】v1 01:11 "返利中心填的订单号给物流部软件看,M5这里填的订单号给我们(结算)看,打结算时看这个订单号去勾"
|
||||
5. 新增细单,选品种(返利中心已送审的才能带出)。
|
||||
6. 选对应数量(协议有累计采购量,为0说明没用过)。
|
||||
> 【音】v1 03:34 "累计采购量是零就说明这个是你自己的订单,没有用过"
|
||||
|
||||
**加单痛点**:一旦加单,要重新做协议(做两条三条),从头开始。
|
||||
> 【音】v1 04:02 "你一加单我们就是要做两条三条…重新从核酷开始做"
|
||||
> 【待确认】加单场景RPA如何处理。
|
||||
|
||||
---
|
||||
|
||||
### N08b M5 质管资料缺失处理(业务方明确决策)
|
||||
|
||||
> ⚠️ 曾有"转质管处理后重试"的设想。业务方**明确否决**:因为RPA晚上跑,质管不可能半夜维护。
|
||||
|
||||
**真实决策**:M5 保存报错(供应商基础信息过期等质管问题)→ **停止,转人工手工做这条单,不等质管**。
|
||||
> 【音】v1 24:36 "如果质管没维护好,我们就不做了,由你们直接去做这条单"
|
||||
> 【音】v1 25:00 "我们是晚上做,质管不可能半夜来录入信息"
|
||||
> 【音】v1 26:31 "报错卡住就停止,通知我们,下室(M5)我们手工做"
|
||||
|
||||
**质管卡的是什么**:供应商基础信息(如年报过期)导致 M5 无法保存。细单信息在增加时其实已存在,只是保存不了。
|
||||
> 【音】v1 23:33 "自管没维护好,无法保存时系统会提示…是供应商基础信息"
|
||||
> 【音】v1 26:00 "M5已经生成订单时,供应商资料问题(缺失或过期)就卡住,不让生成"
|
||||
|
||||
---
|
||||
|
||||
### N09 人工检查 → 保存 → 签订 → 生成收货通知单
|
||||
|
||||
**分阶段上线(业务方核心要求,见 §9)**:一期 RPA 只做到"**录入待保存**",人工检查后再点保存/签订。
|
||||
|
||||
**不可逆约束**(决定为何末端要人工把关):
|
||||
> 【音】v1 05:03 "签订完之后不能修改,生成收货通知单就回不了头了"
|
||||
> 【音】v1 27:33 "签订完之后不能修改;保存完之后也不能修改"
|
||||
|
||||
**RPA 动作(一期)**:录入完总单+细单后**先不点保存**,转人工。可继续做下一单(一直挂在待保存)。
|
||||
> 【音】v1 28:08 "录完了以后先不点保存,给你们检查,错了还可以改"
|
||||
> 【音】v1 28:11 "不点保存可以做下一单…会一直在待保存"
|
||||
|
||||
**人工确认后**:检查数量 → 点保存 → 签订 → 点"生成收货通知单"。
|
||||
> 【音】v1 05:03 "生成完以后点签订,签订完点生成收货通知单,采购合同就到下方双货币,单据自己结束"
|
||||
|
||||
**成功判定**:收货通知单号生成,供应商/货品/数量与采购订单一致。
|
||||
|
||||
---
|
||||
|
||||
### N10 归档
|
||||
|
||||
**归档业务编号链**:订单号 → 合同编号 → 返利中心总单号 → M5采购订单号 → 收货通知单号,全部可查。
|
||||
> 【音】v1 00:59 "这个订单号我们希望每一个地方都能查到"
|
||||
|
||||
**归档内容**:原始订单Excel/截图、对码结果、合同PDF、各系统业务编号、关键截图、运行日志、人工处理记录。
|
||||
|
||||
---
|
||||
|
||||
## 4. 量级数据(影响性能设计)
|
||||
|
||||
| 指标 | 数值 | 出处 |
|
||||
| --- | --- | --- |
|
||||
| 线下手工单 | ~100多份/月 | 【音】v1 08:54 |
|
||||
| 线上订单(总单) | 65~75条/天 | 【音】v1 15:00, 16:24 |
|
||||
| 产品细单 | ~13000条/月 | 【音】v1 19:36 |
|
||||
| 合同审批时长 | 半小时内 | 【音】v2 26:47 |
|
||||
| 线上OCR成本 | ~1000元/月(10亿token),实际线下用量约十几元/月 | 【音】v1 08:43 |
|
||||
| 平均每总单细单数 | ~5个 | 【音】v1 14:26 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 订单号贯穿全流程(关键追溯线)
|
||||
|
||||
| 位置 | 填什么 | 给谁看 | 出处 |
|
||||
| --- | --- | --- | --- |
|
||||
| 电商平台 | 原始订单号(如 ZQD...) | 源头 | 【画】 |
|
||||
| 返利中心细单备注 | 订单号 | **物流部软件** | 【音】v1 01:07 |
|
||||
| M5 订单备注 | 订单号 | **采购结算**(打结算勾对账) | 【音】v1 01:11 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 分阶段上线策略(业务方核心诉求)
|
||||
|
||||
**阶段一(上线初期)**:
|
||||
- RPA 做到"录入待保存",**不自动点保存/签订**。
|
||||
- 人工检查准确率,错了可改。
|
||||
- M5 的第17/18步(保存、生成收货通知单)由人工做。
|
||||
> 【音】v1 27:57 "先手工一段时间,因为不知道准确率怎么样"
|
||||
|
||||
**阶段二(准确率验证后)**:
|
||||
- 准确率OK后,RPA 接管保存/签订/生成收货通知单。
|
||||
> 【音】v1 28:26 "准确度可以的话,到时候就帮我们保存就好了"
|
||||
|
||||
**设计要求**:保存/签订是否自动,应做成**可配置开关**,便于从阶段一平滑切到阶段二。
|
||||
|
||||
---
|
||||
|
||||
## 7. 异常分流表(基于真实会议修正)
|
||||
|
||||
| 异常 | 处理角色 | 系统动作 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| 产品对码未命中 | 业务(计划员/采购) | 一对多候选,人工选择/新增对码表 | 核心难点 |
|
||||
| 货源未建 | 业务 | 通知在M5新增货源 | 必备节点 |
|
||||
| 供应商合同禁掉失效 | 采购(邮件通知) | 通知采购重新禁掉,可多人邮箱 | 邮件多人 |
|
||||
| OCR低置信度(仅线下单) | 计划员 | 展示原文,人工确认 | 范围仅线下 |
|
||||
| 合同未审批完 | 计划员 | 停在前置,等半小时再查 | 定时重查 |
|
||||
| 返利中心导入重复编码 | 采购 | 提示重复,重建总单【待确认】 | 规则待补 |
|
||||
| 返利中心未送审 | 采购 | M5带不出协议,阻断 | 硬卡点 |
|
||||
| **M5质管资料缺失** | **业务手工做,不等质管** | **停止转人工** | **不重试** |
|
||||
| 网页卡顿/登录失败 | 运维 | 截图日志告警 | 系统类 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 待确认事项(下次会议 / 需业务提供)
|
||||
|
||||
**下次会议需拍板**:
|
||||
1. **OCR信息安全**(最大卡点):线上OCR是否获批?否则本地部署费用?需拉法务部+数据中心+业务方三方会议。【音】v1 30:15
|
||||
2. **返利中心重复订单**的完整处理规则(会议被打断未讲完)。【音】v2 33:26
|
||||
3. **小药药平台**完整演示(三段视频均未完整覆盖,原定重开会议演示)。
|
||||
4. **加单场景**:父订单号如何关联、RPA如何处理加单导致的重做。
|
||||
5. 医药城订单**状态过滤**:只取"待收货"还是含其他状态?
|
||||
6. 保存/签订自动化的切换时点和判定标准。
|
||||
|
||||
**业务需提供的资料**:
|
||||
1. 供应商对照表(合同版本/类别/结算类型/合同性质对应关系)。【音】v1 12:34
|
||||
2. 货品对码表(现有的,持续维护)。【音】v3 14:44
|
||||
3. 计划员与合同对应关系表。【音】v1 33:xx
|
||||
4. 各类通知邮箱(禁掉通知、质管缺料通知,均可多人)。【音】v1 11:44, 22:49
|
||||
5. 生产/测试账号(合同系统、返利中心、M5)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 复用的工程框架
|
||||
|
||||
以下工程框架扎实,**建议保留并复用**(本文不再重复展开,详见《会议整理版》对应章节):
|
||||
|
||||
- 状态机:`PENDING/RUNNING/WAITING_USER/SUCCESS/FAILED_RETRYABLE/FAILED_MANUAL/CANCELLED`
|
||||
- 幂等要求:文件哈希入池幂等、`合同号+类型+供应商+来源`业务幂等、`job_id+node_code+attempt_no`回调幂等、`task_id+action+submit_token`手机提交幂等、业务系统执行前先检索已有单号
|
||||
- 证据要求:每节点记录编码/时间/平台/运行号/错误码/业务摘要/截图/日志/责任角色/重试次数
|
||||
- 核心实体:`job/job_node/user_task/mapping_config/executor_binding_config/job_artifact/job_event`
|
||||
- 统一RPA节点契约与回调字段
|
||||
- 企业微信待办与H5详情页需求
|
||||
- RPA执行平台可替换边界
|
||||
|
||||
**但节点编码需按本文重排**,建议:
|
||||
`ORDER_FETCH`(订单获取)、`OCR_EXTRACT`(仅线下)、`PRODUCT_MATCH`(对码)、`SOURCE_ADD`(货源新增)、`CONTRACT_CREATE`(合同建单,含医药城PDF合并)、`CONTRACT_APPROVE_WAIT`(等审批)、`REBATE_HEADER`(返利总单)、`REBATE_IMPORT`(返利细单导入)、`REBATE_SUBMIT`(返利送审)、`M5_CREATE_ORDER`(M5录入待保存)、`M5_MANUAL_CONFIRM`(人工保存签订)、`M5_RECEIPT_NOTICE`(收货通知单)、`ARCHIVE_JOB`(归档)。
|
||||
|
||||
---
|
||||
|
||||
## 附录A:三方会审的主要纠正(常见误解对照)
|
||||
|
||||
| # | 早期常见误解 | 视频真实业务 | 影响 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 合同文件入池是入口,OCR是核心 | **电商订单导出是入口**,OCR仅线下手工单 | 流程骨架方向性错误 |
|
||||
| 2 | 未区分订单来源 | **医药城/小药药/线下三来源**,逻辑分支不同 | 缺核心分流 |
|
||||
| 3 | 对码一笔带过 | **产品对码是核心难点**,规格描述不一致,需一对多,业务已有对码表 | 低估核心工作量 |
|
||||
| 4 | 无货源新增 | 未采购过需在M5**新增货源** | 遗漏节点 |
|
||||
| 5 | 无合同证文合并 | 同供应商同日**合并PDF**上传,覆盖问题 | 遗漏硬规则 |
|
||||
| 6 | 送审在合同系统 | 关键送审在**返利中心**,决定M5能否带协议 | 卡点位置错误 |
|
||||
| 7 | M5字段需RPA填 | 结算/接收方式/发票**返利中心下发自动带入** | 多填错填风险 |
|
||||
| 8 | 质管缺料转质管重试 | **停止转人工手工做,不等质管**(晚上跑) | 与真实决策相反 |
|
||||
| 9 | 无分阶段上线 | 一期**录入待保存**,人工检查,准确率OK后再全自动 | 遗漏核心诉求 |
|
||||
| 10 | 无量级数据 | 65-75单/天,细单1.3万/月,审批半小时 | 影响性能设计 |
|
||||
| 11 | 签约日期未强调 | **签约日期=订单日期**硬约束 | 遗漏规则 |
|
||||
| 12 | 未强调订单号追溯 | 订单号贯穿返利(物流看)/M5(结算看) | 遗漏追溯线 |
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
# 采购主链 RPA 一期需求文档
|
||||
|
||||
> 文档用途:作为产品、开发、RPA 实施、测试和业务确认的共同底稿。本文将当前已留存的会议相关业务底稿,与现有《采购业务 RPA 思路》《一期需求简版说明》《一期开发任务清单》统一整理。
|
||||
>
|
||||
> 证据说明:视频音频未上传至外部转写服务,本文不是视频逐句转写稿,而是基于当前项目资料整理出的可执行需求初稿。文末“上线前待确认事项”用于回看会议原声后完成最终确认。
|
||||
>
|
||||
> 范围说明:当前可落地的主线是“合同/协议 -> 返利中心 -> M5 -> 收货通知单”。订单中心分货、招采子系统和发货后票据上传属于后续范围,本文单独列出,不混入一期验收。
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目目标
|
||||
|
||||
将采购人员收到合同后的重复录入和跨系统搬运工作,建设为一条可追踪的 RPA 自动化流程:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[合同/协议进入共享目录] --> B[状态中心建单与判重]
|
||||
B --> C[OCR识别合同字段]
|
||||
C --> D[字段映射与业务校验]
|
||||
D --> E{合同系统是否审核通过}
|
||||
E -- 否 --> F[计划员手机待办]
|
||||
E -- 是 --> G[返利中心采购协议总单]
|
||||
G --> H[返利中心细单模板导入]
|
||||
H --> I[返利中心结果校验]
|
||||
I --> J[M5 1055采购订单]
|
||||
J --> K[生成收货通知单]
|
||||
K --> L[归档业务编号、截图、日志]
|
||||
```
|
||||
|
||||
核心原则:机器人只执行已经确认的规则;不能稳定识别或映射的字段必须转人工,禁止猜测填报。
|
||||
|
||||
### 1.2 一期范围
|
||||
|
||||
包含:
|
||||
|
||||
- 合同扫描件/合同文件从共享目录入池
|
||||
- 文件命名、完整性和重复性检查
|
||||
- OCR 识别合同基础字段和货品明细
|
||||
- 供应商、货品、结算方式、承付模式映射
|
||||
- 合同系统信息补齐、审核状态检查
|
||||
- 返利中心采购协议检索、总单录入、细单模板导入
|
||||
- 返利中心导入结果校验
|
||||
- M5 采购订单管理 `1055` 新增采购订单
|
||||
- M5 订单保存及收货通知单生成
|
||||
- 企业微信消息通知和 H5 手机待办
|
||||
- 状态中心、节点状态、日志、截图、重试和审计留痕
|
||||
- RPA 执行平台适配和可替换设计
|
||||
|
||||
### 1.3 一期不包含
|
||||
|
||||
- 订单中心品种分货、货源切换和销售订单联动
|
||||
- 招采子系统医院计划抓取
|
||||
- 发货后一票、二票信息自动上传
|
||||
- 渠道价管理与维护
|
||||
- 邮件自动收件作为主入口
|
||||
- 复杂 BI 看板和经营分析
|
||||
|
||||
以上内容可作为二期/三期独立需求,不应影响一期主链交付。
|
||||
|
||||
## 2. 角色与职责
|
||||
|
||||
| 角色 | 主要职责 | 可处理的异常/字段 |
|
||||
| --- | --- | --- |
|
||||
| 计划员 | 检查合同、确认 OCR 结果、确认数量和合同系统状态 | 合同编号、合同有效期、承付模式及参数、计划员归属、OCR 结果 |
|
||||
| 采购 | 确认返利中心协议及细单信息 | 供应商/货品映射、协议单价、导入模板错误、返利中心业务校验 |
|
||||
| 质管 | 维护或确认 M5 所需基础资料 | 质管基础资料缺失导致的 M5 保存失败 |
|
||||
| 运维/机器人管理员 | 处理账号、网络、页面变化、执行器和系统异常 | 登录失败、控件定位失败、接口回调失败、页面结构变化、超时 |
|
||||
| 项目负责人 | 处理升级事项、配置审批、平台切换和范围决策 | 多次失败、跨角色协调、执行平台切换 |
|
||||
|
||||
角色路由按角色配置,不将流程责任固定写死到个人账号。人员请假或轮班时,可由管理员替换实际处理人。
|
||||
|
||||
## 3. 总体架构与边界
|
||||
|
||||
### 3.1 系统职责
|
||||
|
||||
| 系统 | 负责内容 | 不负责内容 |
|
||||
| --- | --- | --- |
|
||||
| 状态中心 | 建单、判重、节点编排、业务规则、待办、重试、审计、证据、平台路由 | 直接操作返利中心或 M5 页面 |
|
||||
| RPA 执行平台 | 登录、检索、点击、录入、上传、下载、截图、结果回调 | 保存主流程状态、人工审批和业务规则主数据 |
|
||||
| OCR 服务 | 文件识别、文本抽取、字段置信度输出 | 直接向业务系统写单 |
|
||||
| 企业微信 | 消息、卡片、H5 入口、用户身份和人工操作提交 | 直接修改数据库或绕过状态中心推进流程 |
|
||||
| 合同系统 | 合同录入、审核和合同编号产生 | 一期不要求其改造接口,优先通过 RPA 访问 |
|
||||
| 返利中心 | 采购协议总单、细单、返利协议结果 | 不承担流程追踪和异常协同 |
|
||||
| M5 | `1055` 采购订单、收货通知单 | 不承担合同和返利中心数据映射 |
|
||||
|
||||
### 3.2 执行平台可替换边界
|
||||
|
||||
- 状态中心、规则、企业微信待办、日志和验收口径必须由项目方掌握。
|
||||
- 一期上线可以只接一个主平台,但节点设计必须保留统一适配接口。
|
||||
- 可按新单或节点配置选择平台;不支持一个节点执行到一半跨平台接力。
|
||||
- 新平台上线前必须通过登录、检索、模板导入、截图、日志、HTTP 回调等 POC 用例。
|
||||
- 老单默认沿用建单时的执行平台,避免中途迁移造成重复业务结果。
|
||||
|
||||
## 4. 主流程节点需求
|
||||
|
||||
### N01 合同文件入池
|
||||
|
||||
**目的**:将合同扫描件、年度协议或批次合同纳入统一流程,建立唯一流程实例。
|
||||
|
||||
**触发方式**:
|
||||
|
||||
- 计划员/采购将双方盖章版本放入约定共享目录;或
|
||||
- 用户在状态中心手工上传并建单。
|
||||
|
||||
**输入**:文件、文件名、上传时间、来源目录、发起人、合同类型(年度协议/批次合同)。
|
||||
|
||||
**处理规则**:
|
||||
|
||||
1. 校验文件格式,至少支持 PDF、常见图片格式;Excel 仅作为模板输入时支持。
|
||||
2. 校验文件是否可打开、页数是否异常、是否存在空文件或缺页。
|
||||
3. 计算文件哈希,拦截相同文件重复上传。
|
||||
4. 按“合同编号 + 合同类型 + 供应商 + 发起来源”判定业务重复。
|
||||
5. 记录原始文件,不覆盖历史文件。
|
||||
6. 创建 `job_id`,初始化节点为 `FILE_INTAKE`,绑定映射版本和默认执行平台。
|
||||
|
||||
**输出**:`job_id`、源文件存证、文件哈希、合同类型、来源信息。
|
||||
|
||||
**异常与路由**:
|
||||
|
||||
- 命名不规范:计划员待办,不启动 RPA。
|
||||
- 文件损坏、缺页或关键附件缺失:计划员待办。
|
||||
- 疑似重复:提示已有流程实例,由计划员/项目负责人确认,不直接新建。
|
||||
- 共享目录不可访问:运维告警。
|
||||
|
||||
**成功判定**:状态中心已生成唯一 `job_id`,且源文件、元数据、判重结果均已留痕。
|
||||
|
||||
### N02 OCR 识别与字段抽取
|
||||
|
||||
**目的**:将合同扫描件转换为可校验的结构化数据。
|
||||
|
||||
**输入**:N01 的源文件和合同类型。
|
||||
|
||||
**建议抽取字段**:
|
||||
|
||||
| 字段 | 说明 | 是否必填 |
|
||||
| --- | --- | --- |
|
||||
| `supplier_name` | 供应商名称 | 是 |
|
||||
| `planner_name` | 计划员/联系人 | 是 |
|
||||
| `contract_no` | 合同编号 | 是,若合同系统尚未产生可为空 |
|
||||
| `settlement_text` | 原文结算方式 | 是 |
|
||||
| `payment_term_text` | 原文承付模式及参数 | 是 |
|
||||
| `contract_validity` | 合同有效期 | 是 |
|
||||
| `items[]` | 货品、数量、单价等明细 | 批次合同必填 |
|
||||
|
||||
每个字段必须输出原文值、标准化值、页码/位置和置信度;保留完整 OCR JSON,不能只保存最终填入值。
|
||||
|
||||
**处理规则**:
|
||||
|
||||
- OCR 服务采用本地部署,状态中心通过适配接口调用,后续可替换云 OCR。
|
||||
- 关键字段缺失、置信度低于阈值、同一字段识别出多个候选值时,进入 `WAITING_USER`。
|
||||
- OCR 只负责识别,不直接决定系统编码,不直接写入返利中心或 M5。
|
||||
|
||||
**成功判定**:必需字段均有值或已明确进入人工确认,OCR 原始结果和截图可追溯。
|
||||
|
||||
**异常与路由**:OCR 服务不可用、文件无法识别、页码读取异常,转计划员;服务级异常同时通知运维。
|
||||
|
||||
### N03 字段映射与业务校验
|
||||
|
||||
**目的**:把合同语言转换为合同系统、返利中心和 M5 能使用的编码与字段。
|
||||
|
||||
**映射对象**:供应商、货品、结算方式、承付模式、合同编号、角色和执行平台。
|
||||
|
||||
**处理规则**:
|
||||
|
||||
1. 规则优先,精确命中字典才允许自动转换。
|
||||
2. 能识别标准 `N天` 的承付表达时,拆分为“承付模式代码 + 天数参数”。
|
||||
3. 货品需映射到系统货品编码,数量和单价保留合同原值及转换结果。
|
||||
4. 映射规则必须版本化;流程开始后绑定版本快照。
|
||||
5. 重试默认沿用原版本;人工选择“按最新配置重跑”时才更新版本。
|
||||
6. 任一必填项未命中、存在一对多结果或映射置信度不足,不得继续。
|
||||
|
||||
**承付模式字典**:
|
||||
|
||||
`0 手工指定`、`1 订单签订后N天`、`2 入库后N天`、`3 收到发票后N天`、`4 供应商开出发票后N天`、`5 销售发货后N天`、`6 销售出库后N天`、`7 销售回款后N天`、`8 门店铺货后N天`、`9 以销定结`、`10 销完后N天`、`11 下月N号结`、`12 押N批`、`13 预付`、`14 供应商发货日期N天`、`15 开完发票N天`、`16 销售发票入账后N天`、`99 无`。
|
||||
|
||||
**结算方式字典**:
|
||||
|
||||
`3 电汇`、`10 银行承兑汇票`、`11 商业承兑汇票`、`12 托收`、`13 信用证`、`17 电子债权凭证`、`27 电子银行承兑汇票`、`28 电子商业承兑汇票`、`30 应付保理`、`31 归集付款`、`32 电汇-非直联`、`33 不需支付`、`34 其他支付-直联`、`35 其他支付-非直联`、`36 电子债权凭证-非直联`、`37 电子银承汇票-非直联`。
|
||||
|
||||
**输出**:映射后的结构化业务对象、校验结果、未通过字段清单、映射版本。
|
||||
|
||||
**异常与路由**:供应商/货品未找到或多值匹配,计划员或采购待办;字典配置缺失,运维/项目负责人待办。
|
||||
|
||||
### N04 合同系统信息补齐与审核前置
|
||||
|
||||
**目的**:确保后续返利中心和 M5 使用的合同已在合同系统中建立并处于可执行状态。
|
||||
|
||||
**输入**:N02/N03 结构化字段和源文件。
|
||||
|
||||
**RPA 操作**:
|
||||
|
||||
- 登录合同系统。
|
||||
- 按供应商、计划员、合同编号或业务条件检索已有合同。
|
||||
- 补齐允许自动录入的合同字段。
|
||||
- 保存并获取合同系统单号。
|
||||
- 查询合同审核状态。
|
||||
|
||||
**前置规则**:
|
||||
|
||||
- 合同系统“已审核/审核通过”才允许进入返利中心。
|
||||
- 送审是否由机器人执行,需业务最终确认;未确认前默认由计划员操作或确认。
|
||||
- 审核驳回、缺资料或状态不明确,流程停留在前置节点。
|
||||
|
||||
**输出**:合同系统单号、审核状态、审核通过时间、驳回原因摘要。
|
||||
|
||||
**成功判定**:合同单号存在,系统查询状态为审核通过,且关键字段与状态中心数据一致。
|
||||
|
||||
### N05 返利中心采购协议总单
|
||||
|
||||
**目的**:根据合同编号在返利中心定位采购协议并完成总单必填信息。
|
||||
|
||||
**输入**:合同编号、供应商、承付模式、承付参数、结算方式、返利合约标识、有效期。
|
||||
|
||||
**RPA 操作**:
|
||||
|
||||
1. 登录返利中心。
|
||||
2. 进入“采购协议”。
|
||||
3. 以合同编号检索对应总单。
|
||||
4. 校验供应商和合同编号是否与状态中心一致。
|
||||
5. 录入/更新采购协议总单必填字段。
|
||||
6. 保存,获取返利中心总单号并截图。
|
||||
|
||||
**关键业务规则**:
|
||||
|
||||
- 采购协议至少包含品种、承付模式、承付参数和返利合约(一期默认为“否”或按业务配置)。
|
||||
- 承付模式和结算方式必须使用系统内已存在编码。
|
||||
- 批次合同在开户时必须先录入产品。
|
||||
- 批次合同有效期默认为 1 个月;如合同原文不同,以人工确认值为准。
|
||||
|
||||
**成功判定**:总单号已生成或检索到且关键字段核验一致;只有页面保存成功而无业务编号时不得直接成功。
|
||||
|
||||
**异常**:检索不到、供应商不一致、必填项校验失败、重复总单、登录或页面异常,按采购/计划员/运维角色分流。
|
||||
|
||||
### N06 返利中心细单模板生成与导入
|
||||
|
||||
**目的**:批量写入合同约定的货品、数量、单价等明细,减少逐行人工录入。
|
||||
|
||||
**输入**:货品映射结果、合同明细、返利中心模板格式、总单号。
|
||||
|
||||
**状态中心处理**:
|
||||
|
||||
- 按模板版本生成 Excel/导入文件。
|
||||
- 写入来源字段、目标字段和每行数据校验结果。
|
||||
- 模板作为 `TEMPLATE` 存证,记录生成时间和映射版本。
|
||||
|
||||
**RPA 操作**:
|
||||
|
||||
1. 下载或打开返利中心细单模板。
|
||||
2. 上传状态中心生成的模板。
|
||||
3. 提交导入。
|
||||
4. 读取导入总行数、成功行数、失败行数和失败原因。
|
||||
5. 保存错误明细截图和导入结果。
|
||||
|
||||
**成功判定**:成功行数等于应导入行数,失败行数为 0,且返利中心返回可查询的结果。
|
||||
|
||||
**部分成功处理**:必须保留逐行成功/失败明细;不得直接将整节点标记成功,也不得盲目重复导入造成重复货品。转采购待办,由采购决定修正、补导或人工处理。
|
||||
|
||||
### N07 返利中心结果校验
|
||||
|
||||
**目的**:确认总单与细单已经真正落库,并作为 M5 前置条件。
|
||||
|
||||
**校验项**:供应商、合同编号、货品编码、数量、协议单价、结算方式、承付模式、有效期、明细行数。
|
||||
|
||||
**输出**:返利中心总单号、明细校验摘要、差异项、成功截图。
|
||||
|
||||
**规则**:有差异即阻断 M5;差异项应展示“合同值/状态中心值/返利中心值”,转采购或计划员确认。
|
||||
|
||||
### N08 M5 采购订单创建(1055)
|
||||
|
||||
**目的**:依据已审核合同和返利中心协议,在 M5 的采购订单管理 `1055` 中创建采购订单。
|
||||
|
||||
**输入**:合同系统单号、返利中心协议结果、供应商、货品、数量、结算方式、承付模式、单价、货源/联系人信息。
|
||||
|
||||
**RPA 操作**:
|
||||
|
||||
1. 登录 M5,进入采购订单管理 `1055`。
|
||||
2. 新增采购订单和细单。
|
||||
3. 选择供应商、货品、货源。
|
||||
4. 填写结算方式、承付模式、数量、单价等字段。
|
||||
5. 执行页面校验并保存。
|
||||
6. 读取采购订单号。
|
||||
|
||||
**数量规则**:计划员需要确认采购数量是否不超过供应商可供数量;状态中心应保留合同数量、录入数量和差异说明。
|
||||
|
||||
**货源规则**:按合同管理联系人/业务配置选择货源;无法唯一确定时转计划员或采购,机器人不得自行选择。
|
||||
|
||||
**异常**:质管基础资料未维护、货品不存在、货源不唯一、金额或数量校验失败,分别转质管、计划员或采购;控件和登录问题转运维。
|
||||
|
||||
**成功判定**:M5 查询到采购订单号,且订单供应商、货品、数量、单价与输入校验一致。
|
||||
|
||||
### N09 收货通知单生成
|
||||
|
||||
**目的**:采购订单完成后通知仓库即将到货的货品和来源。
|
||||
|
||||
**输入**:M5 采购订单号及细单。
|
||||
|
||||
**RPA 操作**:进入收货通知单功能,基于采购订单生成通知单,保存并读取通知单号。
|
||||
|
||||
**成功判定**:收货通知单号已生成,且其供应商、货品、数量与采购订单一致。
|
||||
|
||||
**异常**:订单未完成、重复生成、仓库信息缺失、页面/登录失败,分别转业务角色或运维;重复操作必须先检索已有通知单。
|
||||
|
||||
### N10 流程归档
|
||||
|
||||
**目的**:以真实业务结果为准完成归档,形成可审计的完整轨迹。
|
||||
|
||||
**归档内容**:合同原件、OCR JSON、映射快照、返利中心总单号、导入结果、M5 订单号、收货通知单号、关键截图、运行日志、人工处理记录。
|
||||
|
||||
**成功判定**:所有必需业务编号已回写状态中心,关键节点成功,审计事件完整;页面点击完成但未拿到业务结果不得归档为成功。
|
||||
|
||||
## 5. 状态机、重试与异常处理
|
||||
|
||||
### 5.1 节点状态
|
||||
|
||||
`PENDING`、`RUNNING`、`WAITING_USER`、`SUCCESS`、`FAILED_RETRYABLE`、`FAILED_MANUAL`、`CANCELLED`。
|
||||
|
||||
允许迁移:
|
||||
|
||||
`PENDING -> RUNNING -> SUCCESS`;
|
||||
`RUNNING -> WAITING_USER -> RUNNING`;
|
||||
`RUNNING -> FAILED_RETRYABLE -> RUNNING`;
|
||||
`RUNNING -> FAILED_MANUAL`;
|
||||
`WAITING_USER -> CANCELLED`。
|
||||
|
||||
同一节点同一时刻只能有一个有效执行上下文;重试产生新的 `attempt_no`,不得覆盖历史失败记录。
|
||||
|
||||
### 5.2 异常分流表
|
||||
|
||||
| 异常 | 默认处理角色 | 系统动作 |
|
||||
| --- | --- | --- |
|
||||
| 文件命名不规范/缺页 | 计划员 | 不启动 RPA,创建待办 |
|
||||
| OCR 低置信度/关键字段缺失 | 计划员 | 展示原文和建议值,等待确认 |
|
||||
| 供应商或货品无法映射 | 计划员/采购 | 阻断下游,保留映射候选 |
|
||||
| 合同未审核通过 | 计划员 | 停在前置节点,不进入返利中心 |
|
||||
| 返利中心找不到总单 | 采购 | 自动重试后转人工确认 |
|
||||
| 返利中心模板导入失败 | 采购 | 保留逐行错误,允许修正后重试 |
|
||||
| M5 质管资料缺失 | 质管 | 处理基础资料后重试 |
|
||||
| 登录、控件、页面结构变化 | 运维 | 截图、日志、运行号齐全后告警 |
|
||||
| 节点超时无回调 | 运维 | 超时关闭执行上下文并升级 |
|
||||
| 多次失败或超出能力 | 运维/项目负责人 | 转人工,禁止无限重试 |
|
||||
|
||||
### 5.3 重试策略
|
||||
|
||||
- 系统型异常(超时、偶发网络错误):自动重试 1 至 2 次。
|
||||
- 业务型异常(映射不确定、校验失败):人工确认后只重试当前节点。
|
||||
- 人工接管:进入 `FAILED_MANUAL`,保留上下文,不允许后台自动反复拉起。
|
||||
- 整单重跑:新建流程实例并关联原 `parent_job_id`,不覆盖原轨迹。
|
||||
- 待办超过 30 分钟未处理再次提醒,超过 2 小时升级备岗/项目负责人;具体时长可配置。
|
||||
|
||||
## 6. 手机待办与企业微信需求
|
||||
|
||||
### 6.1 待办内容
|
||||
|
||||
每条消息至少包含:合同编号、供应商、当前节点、错误/待确认原因、关键字段摘要、最近错误截图、处理链接、预计处理时间。
|
||||
|
||||
### 6.2 H5 详情页
|
||||
|
||||
页面必须支持:
|
||||
|
||||
- 查看当前人话状态:系统正在做什么、通常耗时、下一步是什么。
|
||||
- 查看 OCR 原值、系统建议值和当前填写值。
|
||||
- 只编辑角色有权限修改的白名单字段。
|
||||
- 查看最近截图、错误翻译和处理历史。
|
||||
- 执行确认并继续、重试当前节点、转人工、终止本单。
|
||||
- 提交后立即返回“已受理、请勿重复提交、预计多久有结果”。
|
||||
|
||||
### 6.3 人工修改留痕
|
||||
|
||||
合同编号、供应商、结算方式、承付模式、货品编码、数量、单价、货源等字段的修改,必须记录修改前值、修改后值、修改人、时间和原因。
|
||||
|
||||
企业微信只展示必要摘要,完整合同和日志通过状态中心受控页面查看;查看和下载动作也要进入审计日志。
|
||||
|
||||
## 7. 状态中心与数据要求
|
||||
|
||||
### 7.1 核心实体
|
||||
|
||||
| 实体 | 用途 |
|
||||
| --- | --- |
|
||||
| `job` | 一份合同的一次流程实例 |
|
||||
| `job_node` | 每个节点每次尝试 |
|
||||
| `user_task` | 企业微信/H5 人工待办 |
|
||||
| `mapping_config` | 供应商、货品、结算、承付和路由配置 |
|
||||
| `executor_binding_config` | 节点和 RPA 平台绑定 |
|
||||
| `job_artifact` | 合同、OCR、模板、截图、日志存证 |
|
||||
| `job_event` | 全流程事件轨迹 |
|
||||
|
||||
### 7.2 幂等要求
|
||||
|
||||
- 入池幂等:文件哈希 + 文件名 + 时间窗口。
|
||||
- 业务幂等:合同编号 + 合同类型 + 供应商 + 来源。
|
||||
- 节点回调幂等:`job_id + node_code + attempt_no + callback_type`。
|
||||
- 手机提交幂等:`task_id + action + submit_token`。
|
||||
- 业务系统执行前先检索已有单号,避免重复创建采购协议、采购订单和收货通知单。
|
||||
|
||||
### 7.3 证据要求
|
||||
|
||||
每个节点至少记录:节点编码、开始/结束时间、执行平台、运行号、错误码、业务摘要、截图、日志、当前责任角色、重试次数。
|
||||
|
||||
推荐目录:`job_id/节点编码/attempt_no/`。
|
||||
|
||||
## 8. 统一 RPA 节点契约
|
||||
|
||||
RPA 平台只通过状态中心接口交互,禁止直接改状态中心数据库。
|
||||
|
||||
节点回调至少包含:
|
||||
|
||||
- `jobId`
|
||||
- `nodeCode`
|
||||
- `attemptNo`
|
||||
- `executorPlatform`
|
||||
- `executorRunId`
|
||||
- `status`
|
||||
- `errorCode/errorSummary`
|
||||
- `businessRefs`
|
||||
- `summary`
|
||||
- `artifacts`
|
||||
- `callbackIdempotencyKey`
|
||||
|
||||
建议节点编码:
|
||||
|
||||
`FILE_INTAKE`、`OCR_EXTRACT`、`FIELD_MAPPING`、`CONTRACT_SYSTEM_CHECK`、`REBATE_ORDER_HEADER`、`REBATE_IMPORT_ITEMS`、`REBATE_VERIFY`、`M5_CREATE_ORDER`、`M5_RECEIPT_NOTICE`、`ARCHIVE_JOB`。
|
||||
|
||||
## 9. 验收标准
|
||||
|
||||
### 9.1 主流程验收
|
||||
|
||||
- 合同从共享目录入池后可生成唯一流程单。
|
||||
- 完整且规则明确的合同可自动跑到 M5 和收货通知单。
|
||||
- 可查询合同系统单号、返利中心总单号、M5 订单号、收货通知单号。
|
||||
- 每个节点均有开始、结束、结果和证据。
|
||||
|
||||
### 9.2 安全与正确性验收
|
||||
|
||||
- 低置信度和多值映射不会被机器人猜测通过。
|
||||
- 合同未审核通过不会进入返利中心。
|
||||
- 重复上传、重复回调、重复点击不会生成重复业务结果。
|
||||
- 部分导入失败可查看逐行明细,不会被误判为成功。
|
||||
- 整单重跑不覆盖历史实例。
|
||||
|
||||
### 9.3 协同验收
|
||||
|
||||
- 计划员、采购、质管、运维收到的待办角色正确。
|
||||
- 手机端能看懂卡点、原因、处理动作和预计等待时间。
|
||||
- 人工修改有前后值对比和审计记录。
|
||||
- 提交后有即时受理回执,重复点击有幂等反馈。
|
||||
|
||||
## 10. 测试场景
|
||||
|
||||
1. 正常年度协议:字段完整,自动完成主链。
|
||||
2. 正常批次合同:合同有效期一个月,先录产品再进入细单。
|
||||
3. OCR 低置信度:转计划员,修正后继续。
|
||||
4. 承付模式未命中字典:不得自动猜,转计划员确认。
|
||||
5. 供应商/货品一对多映射:转人工选择。
|
||||
6. 合同系统未审核通过:流程停止,不启动返利中心。
|
||||
7. 返利中心模板全部成功:进入结果校验和 M5。
|
||||
8. 返利中心部分导入失败:记录逐行结果,转采购处理。
|
||||
9. M5 质管资料未维护:转质管,修复后重试。
|
||||
10. 执行平台登录失败:自动重试后转运维。
|
||||
11. 页面结构变化:截图、日志、运行号完整,转运维。
|
||||
12. 同一合同重复上传:拦截并提示已有流程。
|
||||
13. 企业微信待办重复提交:只推进一次。
|
||||
14. 节点长时间运行:超时升级,不允许永久停留在运行中。
|
||||
15. 新单切换执行平台:业务状态、待办和审计口径不变。
|
||||
|
||||
## 11. 上线前待确认事项
|
||||
|
||||
以下事项必须由业务、实施和技术共同拍板后写入配置或测试用例:
|
||||
|
||||
- 合同系统“送审”是否由机器人执行。
|
||||
- 合同系统的审核通过状态名称和可查询方式。
|
||||
- 批次合同有效期是否始终默认为一个月。
|
||||
- 返利合约字段是否一期固定为“否”。
|
||||
- 合同文件命名规则、共享目录、文件权限和清理周期。
|
||||
- OCR 置信度阈值及每个字段的必填性。
|
||||
- 计划员、采购、质管和运维的实际企业微信角色映射。
|
||||
- 各节点自动重试次数、超时阈值和工作时间。
|
||||
- 返利中心部分成功后的补导规则,是否允许重导失败行。
|
||||
- 货源选择规则和供应商可供数量校验口径。
|
||||
- 生产和测试账号、RPA 运行机、企业微信可信域名。
|
||||
- 合同、供应商和截图的查看权限、脱敏方式、保留周期。
|
||||
|
||||
## 12. 后续扩展方向
|
||||
|
||||
后续可在本期状态中心和待办体系上扩展:
|
||||
|
||||
- 订单中心货源拆分、分货和销售订单联动。
|
||||
- 招采子系统每日医院计划抓取及订单中心生成。
|
||||
- 发货后的一票、二票信息归集和上传。
|
||||
- 渠道价维护。
|
||||
|
||||
这些流程应复用本期的 `job/job_node/user_task/job_event`、规则版本、幂等和证据体系,不应重新建设一套孤立机器人流程。
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
-- RPA 库暂存表 DDL(Oracle 11.2)
|
||||
-- 依据:/Users/yuuuu/Downloads/RPA与订单中心接口设计_20260922.xlsx(2026-09-30 16:24)
|
||||
-- 四张「数据表」sheet。表1 已去掉 oms_orderno;表1 fid 为 number(19);
|
||||
-- 表2 push_time/create_time/update_time 为 date(create_time 写作 dateNOT NULL,按 date NOT NULL)。
|
||||
-- 库版本:11.2.0.4,无 IDENTITY;表1/2/4 自增用 SEQUENCE + BEFORE INSERT 触发器(fid 为空才取号,等同 BY DEFAULT)。
|
||||
-- 标识符不超过 30 字节。
|
||||
-- 范围:仅本方 RPA 库四张暂存表。不是订单中心对码功能表。本方不建对码引擎。
|
||||
-- oms_id 一律 number(20)。
|
||||
--
|
||||
-- 待确认(Excel 现稿保留,未删列):
|
||||
-- 表1 oms_id:需确认字段属性是否与订单中心一致。
|
||||
-- 表2 bar_code:确认同一条形码是否可出现在多个订单;普通索引已建,不作为主键。
|
||||
-- 表3 fid:确认重录协议时是否生成新的 fid。
|
||||
-- 表3 purchase_tax_rate:确认税率是百分数还是小数比例;Excel 备注为 0.13 表示 13%。
|
||||
-- 表3 contract_entry_status:确认是否保留 need_contract 旧字段名;现稿用本列名。
|
||||
--
|
||||
-- 主键 / 唯一(Excel 未写主键时按任务口径):
|
||||
-- 表1 fid 自增主键;oms_id 幂等唯一(因此补 NOT NULL)。
|
||||
-- 表2、表4 fid 本方序列主键。
|
||||
-- 表3 fid 订单中心来源唯一序号,作主键。
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. 销售商品对码数据(Excel 名;实为选品收件箱)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_sales_goods_mapping_data (
|
||||
oms_id NUMBER(20) NOT NULL,
|
||||
bar_code VARCHAR2(40),
|
||||
order_date DATE,
|
||||
sale_price NUMBER(16,4),
|
||||
sale_qty NUMBER(10),
|
||||
plan_purchase_qty NUMBER(10),
|
||||
goods_id VARCHAR2(20),
|
||||
goods_name VARCHAR2(200),
|
||||
goods_spec VARCHAR2(100),
|
||||
manufacturer VARCHAR2(100),
|
||||
approval_no VARCHAR2(100),
|
||||
unit VARCHAR2(20),
|
||||
customer_id VARCHAR2(20),
|
||||
customer_name VARCHAR2(200),
|
||||
create_time DATE,
|
||||
create_user_id NUMBER(10),
|
||||
create_user_name VARCHAR2(20),
|
||||
update_time DATE,
|
||||
update_user_id NUMBER(10),
|
||||
update_user_name VARCHAR2(20),
|
||||
deal_flag NUMBER(2) DEFAULT 0 NOT NULL,
|
||||
deal_result VARCHAR2(200),
|
||||
deal_time DATE,
|
||||
deal_user_id NUMBER(10),
|
||||
deal_user_name VARCHAR2(20),
|
||||
fid NUMBER(19) NOT NULL,
|
||||
org_code VARCHAR2(10),
|
||||
goods_mdm_code VARCHAR2(100),
|
||||
customer_mdm_code VARCHAR2(100),
|
||||
CONSTRAINT pk_rpa_sgmd PRIMARY KEY (fid),
|
||||
CONSTRAINT uk_rpa_sgmd_oms_id UNIQUE (oms_id)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_sgmd_fid
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_sgmd_bi
|
||||
BEFORE INSERT ON rpa_sales_goods_mapping_data
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_sgmd_fid.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
COMMENT ON TABLE rpa_sales_goods_mapping_data IS '销售商品对码数据(选品收件箱)。订单中心推入,按 oms_id 幂等。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.oms_id IS '订单中心序号。已存在则不入库、不重跑。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.bar_code IS '条形码。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.order_date IS '订单日期。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.sale_price IS '销售单价。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.sale_qty IS '销售数量。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.plan_purchase_qty IS '计划采购量。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.goods_id IS '货品ID。入站时已有,本方不做对码。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.goods_name IS '品名。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.goods_spec IS '货品规格。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.manufacturer IS '厂家。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.approval_no IS '国药准字/注册证号。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.unit IS '单位。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.customer_id IS '客户ID。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.customer_name IS '客户名称。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.create_time IS '创建时间。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.create_user_id IS '创建人ID。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.create_user_name IS '创建人名称。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.update_time IS '更新时间。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.update_user_id IS '更新人ID。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.update_user_name IS '更新人名称。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.deal_flag IS '处理标志。0-待处理、1-处理成功、2-处理失败、3-处理中。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.deal_result IS '处理结果。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.deal_time IS '处理时间。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.deal_user_id IS '处理人ID。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.deal_user_name IS '处理人名称。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.fid IS '序号,自增序列。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.org_code IS '货主编码。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.goods_mdm_code IS '货品MDM码。';
|
||||
COMMENT ON COLUMN rpa_sales_goods_mapping_data.customer_mdm_code IS '客户MDM码。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2. 加购及结算商品数据(加购/支付回传缓冲)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_cart_settlement_goods_data (
|
||||
fid NUMBER(19,0) NOT NULL,
|
||||
operate_type NUMBER(2,0) NOT NULL,
|
||||
oms_id NUMBER(20) NOT NULL,
|
||||
platform_type NUMBER(4,0) NOT NULL,
|
||||
supplier_name VARCHAR2(200 CHAR) NOT NULL,
|
||||
bar_code VARCHAR2(40 CHAR) NOT NULL,
|
||||
purchase_price NUMBER(16,4) NOT NULL,
|
||||
purchase_qty NUMBER(10,0) NOT NULL,
|
||||
purchase_total NUMBER(16,4) NOT NULL,
|
||||
status NUMBER(2,0),
|
||||
fail_reason VARCHAR2(500 CHAR),
|
||||
pay_order_no VARCHAR2(40 CHAR),
|
||||
pay_order_dtl_id VARCHAR2(40 CHAR),
|
||||
push_status NUMBER(1,0) DEFAULT 0 NOT NULL,
|
||||
push_result VARCHAR2(500 CHAR),
|
||||
push_time DATE,
|
||||
create_time DATE NOT NULL,
|
||||
update_time DATE,
|
||||
create_user_id NUMBER(19,0),
|
||||
create_user_name VARCHAR2(100 CHAR),
|
||||
update_user_id NUMBER(19,0),
|
||||
update_user_name VARCHAR2(100 CHAR),
|
||||
org_code VARCHAR2(10),
|
||||
multi_platform_flag NUMBER(1,0) DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT pk_rpa_csgd PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_csgd_fid
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_csgd_bi
|
||||
BEFORE INSERT ON rpa_cart_settlement_goods_data
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_csgd_fid.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
CREATE INDEX idx_rpa_csgd_bar_code
|
||||
ON rpa_cart_settlement_goods_data (bar_code);
|
||||
|
||||
COMMENT ON TABLE rpa_cart_settlement_goods_data IS '加购及结算商品数据。RPA 回传缓冲;同一 oms_id 多平台多行。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.fid IS 'RPA记录技术主键;接口推送时作为业务回执关联键。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.operate_type IS '操作类型。1-加购,2-支付。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.oms_id IS '订单中心单据编号。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.platform_type IS '平台类型。48 药帮忙,66 1药城。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.supplier_name IS '供应商名称。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.bar_code IS '条形码。保留前导零,不作为单独主键。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.purchase_price IS '采购单价。金额精度4位小数。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.purchase_qty IS '采购数量。大于0。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.purchase_total IS '采购总价。金额字段统一 NUMBER(16,4)。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.status IS '整单采购状态。仅加购:1-成功,2-失败。支付填空。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.fail_reason IS '整单采购失败原因。status=2时填写。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.pay_order_no IS '支付订单号。支付场景填写。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.pay_order_dtl_id IS '支付订单明细ID。支付场景填写。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.push_status IS '推送状态。0-未推送,1-推送成功,2-推送失败,3-推送中。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.push_result IS '推送结果。保存订单中心回执或失败原因。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.push_time IS '推送时间。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.create_time IS '创建时间。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.update_time IS '更新时间。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.create_user_id IS '创建人ID。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.create_user_name IS '创建人名称。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.update_user_id IS '更新人ID。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.update_user_name IS '更新人名称。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.org_code IS '货主编码。';
|
||||
COMMENT ON COLUMN rpa_cart_settlement_goods_data.multi_platform_flag IS '多平台满足标志。同一 OMS ID 多行时为1,须同报文返回。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. 采购合同数据
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_purchase_contract_data (
|
||||
fid NUMBER(19,0) NOT NULL,
|
||||
oms_id NUMBER(20) NOT NULL,
|
||||
supplier_id VARCHAR2(20 CHAR) NOT NULL,
|
||||
supplier_name VARCHAR2(200 CHAR) NOT NULL,
|
||||
contract_sign_date DATE NOT NULL,
|
||||
contract_start_date DATE NOT NULL,
|
||||
contract_end_date DATE NOT NULL,
|
||||
payment_term NUMBER(5,0) NOT NULL,
|
||||
goods_id VARCHAR2(20 CHAR) NOT NULL,
|
||||
goods_name VARCHAR2(200 CHAR) NOT NULL,
|
||||
reg_no VARCHAR2(100 CHAR) NOT NULL,
|
||||
spec VARCHAR2(100 CHAR) NOT NULL,
|
||||
unit VARCHAR2(20 CHAR) NOT NULL,
|
||||
qty NUMBER(16,4) NOT NULL,
|
||||
purchase_tax_rate NUMBER(8,4) NOT NULL,
|
||||
unit_price NUMBER(16,4) NOT NULL,
|
||||
goods_source_id VARCHAR2(20 CHAR) NOT NULL,
|
||||
purchaser_id VARCHAR2(20 CHAR) NOT NULL,
|
||||
purchaser_name VARCHAR2(100 CHAR) NOT NULL,
|
||||
planner_id VARCHAR2(20 CHAR) NOT NULL,
|
||||
planner_name VARCHAR2(100 CHAR) NOT NULL,
|
||||
contract_entry_status NUMBER(1,0),
|
||||
contract_no VARCHAR2(40 CHAR),
|
||||
process_status NUMBER(1,0) DEFAULT 0 NOT NULL,
|
||||
process_result VARCHAR2(1000 CHAR),
|
||||
process_time TIMESTAMP(6),
|
||||
create_time TIMESTAMP(6) NOT NULL,
|
||||
update_time TIMESTAMP(6),
|
||||
org_code VARCHAR2(10),
|
||||
customer_type NUMBER(2,0) NOT NULL,
|
||||
goods_mdm_code VARCHAR2(100),
|
||||
supplier_mdm_code VARCHAR2(100),
|
||||
CONSTRAINT pk_rpa_pcd PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE rpa_purchase_contract_data IS '采购合同数据。订单中心行级下发;按来源 fid 幂等。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.fid IS '订单中心来源稳定唯一序号。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.oms_id IS '订单中心单据编号。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.supplier_id IS '供应商ID。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.supplier_name IS '供应商名称。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.contract_sign_date IS '合同签订日期。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.contract_start_date IS '合同开始日期。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.contract_end_date IS '合同结束日期。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.payment_term IS '付款期限(账期),单位:天。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.goods_id IS '货品ID。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.goods_name IS '货品名称。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.reg_no IS '注册证号。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.spec IS '规格。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.unit IS '单位。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.qty IS '数量。支持非整数采购数量。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.purchase_tax_rate IS '进货税率。0.13 表示 13%。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.unit_price IS '采购单价。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.goods_source_id IS '货源细单ID。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.purchaser_id IS '采购员ID。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.purchaser_name IS '采购员。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.planner_id IS '计划员ID。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.planner_name IS '计划员。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.contract_entry_status IS '录入合同状态。null-首次下发,0-失败,1-成功。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.contract_no IS '采购合同号。失败重录协议时填写。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.process_status IS '处理状态。0-待处理,1-成功,2-失败,3-处理中。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.process_result IS '处理结果。保存RPA处理结果或失败原因。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.process_time IS '处理时间。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.create_time IS '创建时间。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.update_time IS '更新时间。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.org_code IS '货主编码。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.customer_type IS '客户类型。口径本期固定 1 账期。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.goods_mdm_code IS '货品MDM码。';
|
||||
COMMENT ON COLUMN rpa_purchase_contract_data.supplier_mdm_code IS '供应商MDM码。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 4. 合同协议录入结果
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_contract_agreement_result (
|
||||
fid NUMBER(19,0) NOT NULL,
|
||||
oms_id NUMBER(20) NOT NULL,
|
||||
action_code NUMBER(2,0) NOT NULL,
|
||||
contract_status NUMBER(2,0),
|
||||
contract_fail_reason VARCHAR2(500 CHAR),
|
||||
contract_no VARCHAR2(40 CHAR),
|
||||
agreement_status NUMBER(2,0),
|
||||
agreement_fail_reason VARCHAR2(500 CHAR),
|
||||
agreement_id VARCHAR2(40 CHAR),
|
||||
agreement_dtl_id VARCHAR2(40 CHAR),
|
||||
push_status NUMBER(1,0) DEFAULT 0 NOT NULL,
|
||||
push_result VARCHAR2(500 CHAR),
|
||||
push_time TIMESTAMP(6),
|
||||
create_time TIMESTAMP(6) NOT NULL,
|
||||
update_time TIMESTAMP(6),
|
||||
create_user_id NUMBER(19,0),
|
||||
create_user_name VARCHAR2(100 CHAR),
|
||||
update_user_id NUMBER(19,0),
|
||||
update_user_name VARCHAR2(100 CHAR),
|
||||
org_code VARCHAR2(10),
|
||||
process_status NUMBER(1,0) DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT pk_rpa_car PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_car_fid
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_car_bi
|
||||
BEFORE INSERT ON rpa_contract_agreement_result
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_car_fid.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
COMMENT ON TABLE rpa_contract_agreement_result IS '合同协议录入结果。本方回写缓冲。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.fid IS 'RPA记录技术主键;接口推送时作为回执关联键。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.oms_id IS '订单中心单据编号。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.action_code IS '回传动作。1-合同,2-返利协议。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.contract_status IS '合同状态。1-起草,2-成功,3-失败。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.contract_fail_reason IS '合同录入失败原因。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.contract_no IS '合同编号。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.agreement_status IS '返利协议状态。1-成功,2-失败。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.agreement_fail_reason IS '协议录入失败原因。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.agreement_id IS '采购协议总单ID。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.agreement_dtl_id IS '采购协议细单ID。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.push_status IS '推送状态。0-未推送,1-推送成功,2-推送失败,3-推送中。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.push_result IS '推送结果。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.push_time IS '推送时间。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.create_time IS '创建时间。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.update_time IS '更新时间。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.create_user_id IS '创建人ID。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.create_user_name IS '创建人名称。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.update_user_id IS '更新人ID。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.update_user_name IS '更新人名称。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.org_code IS '货主编码。';
|
||||
COMMENT ON COLUMN rpa_contract_agreement_result.process_status IS '返利处理状态。0-待查号,1-返利已回写,2-合同失败不进返利,3-查号中。不进接口4。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 入站审计(一行请求一条;写失败不影响业务 HTTP)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_oms_inbound_audit (
|
||||
fid NUMBER(19) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
api_no NUMBER(1) NOT NULL,
|
||||
http_code NUMBER(3) NOT NULL,
|
||||
request_json CLOB,
|
||||
response_json CLOB,
|
||||
error_text VARCHAR2(4000),
|
||||
token_masked VARCHAR2(8),
|
||||
CONSTRAINT pk_rpa_oms_in_aud PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_oms_in_aud
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_oms_in_aud_bi
|
||||
BEFORE INSERT ON rpa_oms_inbound_audit
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_oms_in_aud.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
COMMENT ON TABLE rpa_oms_inbound_audit IS '订单中心入站审计。一行请求一条;token 只存打码。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.fid IS '本方序列主键。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.created_at IS '请求处理时间。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.api_no IS '口。1-销售商品,3-采购合同。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.http_code IS '对外 HTTP 状态码。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.request_json IS '入参 JSON。不含 token 明文。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.response_json IS '出参 JSON。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.error_text IS '内部错误原文,含 ORA。对外 503 不用这一列。';
|
||||
COMMENT ON COLUMN rpa_oms_inbound_audit.token_masked IS '有 token 则为 ***。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 选品查询失败审计(一次查询失败一条;写失败不影响班次落表)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_selection_query_audit (
|
||||
fid NUMBER(19) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
oms_id NUMBER(20) NOT NULL,
|
||||
mapping_fid NUMBER(19) NOT NULL,
|
||||
mall_name VARCHAR2(64) NOT NULL,
|
||||
bar_code VARCHAR2(40),
|
||||
error_text VARCHAR2(4000),
|
||||
CONSTRAINT pk_rpa_sel_qry_aud PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_sel_qry_aud
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_sel_qry_aud_bi
|
||||
BEFORE INSERT ON rpa_selection_query_audit
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_sel_qry_aud.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
COMMENT ON TABLE rpa_selection_query_audit IS '选品查询失败审计。一次平台查询失败一条;不含业务短码。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.fid IS '本方序列主键。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.created_at IS '写入时间。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.oms_id IS '订单中心序号。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.mapping_fid IS '表 1 rpa_sales_goods_mapping_data.fid。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.mall_name IS '平台名。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.bar_code IS '查询用的条形码。';
|
||||
COMMENT ON COLUMN rpa_selection_query_audit.error_text IS '异常原文,含 ORA。超长截断。不进 deal_result。';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 加购 / 合同 / 返利未捕获异常审计(一条故障一条;写失败不影响班次落表)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE rpa_shift_error_audit (
|
||||
fid NUMBER(19) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
shift_name VARCHAR2(32) NOT NULL,
|
||||
biz_key VARCHAR2(128),
|
||||
error_code VARCHAR2(64) NOT NULL,
|
||||
error_text VARCHAR2(4000),
|
||||
CONSTRAINT pk_rpa_shift_err_aud PRIMARY KEY (fid)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE seq_rpa_shift_err_aud
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NOMAXVALUE
|
||||
NOCYCLE
|
||||
NOCACHE;
|
||||
|
||||
CREATE OR REPLACE TRIGGER trg_rpa_shift_err_aud_bi
|
||||
BEFORE INSERT ON rpa_shift_error_audit
|
||||
FOR EACH ROW
|
||||
WHEN (new.fid IS NULL)
|
||||
BEGIN
|
||||
SELECT seq_rpa_shift_err_aud.NEXTVAL INTO :NEW.fid FROM dual;
|
||||
END;
|
||||
/
|
||||
|
||||
COMMENT ON TABLE rpa_shift_error_audit IS '加购、合同、返利未捕获异常审计。业务列仍写短码。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.fid IS '本方序列主键。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.created_at IS '写入时间。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.shift_name IS '班次。加购、合同或返利。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.biz_key IS '加购行 fid、合同供应商或返利合同号。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.error_code IS '业务列上的短码,如 QUERY_FAILED、PAGE_FAILED。';
|
||||
COMMENT ON COLUMN rpa_shift_error_audit.error_text IS '异常原文,含 ORA。超长截断。不进失败原因列。';
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# 四张暂存表 ↔ Excel 列对照
|
||||
|
||||
依据:`/Users/yuuuu/Downloads/RPA与订单中心接口设计_20260922.xlsx`(2026-09-30 16:24)四张「数据表」sheet。
|
||||
DDL:`docs/ddl/rpa_oms_staging_tables.sql`(Oracle 11.2)。
|
||||
Excel 无表英文名;表名按中文意译,未用拼音。
|
||||
|
||||
库是 11.2.0.4:表1/2/4 自增不用 IDENTITY,改 SEQUENCE + 触发器;约束/索引名压到 30 字节内。列集合未改。
|
||||
|
||||
约束(非列):表1 `oms_id` 唯一、`fid` 主键;表2/4 `fid` 主键;表3 `fid` 主键(来源唯一);表2 `bar_code` 普通索引。
|
||||
|
||||
## 总览
|
||||
|
||||
| Excel 数据表 | DDL 表名 | 列数 | 缺列 | 多列 |
|
||||
| --- | --- | ---: | --- | --- |
|
||||
| 销售商品对码数据 | `rpa_sales_goods_mapping_data` | 29 | 无 | 无 |
|
||||
| 加购及结算商品数据 | `rpa_cart_settlement_goods_data` | 24 | 无 | 无 |
|
||||
| 采购合同数据 | `rpa_purchase_contract_data` | 32 | 无 | 无 |
|
||||
| 合同协议录入结果 | `rpa_contract_agreement_result` | 20 | 无 | 无 |
|
||||
|
||||
口径点名且 Excel 已有的列,按所在表落地:`org_code`(四张都有);MDM 在表1(货品/客户)、表3(货品/供应商);`customer_type` 仅表3;`multi_platform_flag` 仅表2;`push_status` 在表2、表4。
|
||||
|
||||
---
|
||||
|
||||
## 1. 销售商品对码数据
|
||||
|
||||
Excel 待确认:`oms_id` 属性是否与订单中心一致。
|
||||
|
||||
| Excel 序号 | 中文名 | 英文名 | Excel 属性 | DDL | 差异 |
|
||||
| ---: | --- | --- | --- | --- | --- |
|
||||
| 1 | 订单中心序号 | oms_id | number(20) | NUMBER(20) NOT NULL | 幂等唯一,补 NOT NULL + UNIQUE |
|
||||
| 2 | 条形码 | bar_code | varchar2(40) | VARCHAR2(40) | 同 |
|
||||
| 3 | 订单日期 | order_date | date | DATE | 同 |
|
||||
| 4 | 销售单价 | sale_price | number(16,4) | NUMBER(16,4) | 同 |
|
||||
| 5 | 销售数量 | sale_qty | number(10) | NUMBER(10) | 同 |
|
||||
| 6 | 计划采购量 | plan_purchase_qty | number(10) | NUMBER(10) | 同 |
|
||||
| 7 | 货品ID | goods_id | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 8 | 品名 | goods_name | varchar2(200) | VARCHAR2(200) | 同 |
|
||||
| 9 | 货品规格 | goods_spec | varchar2(100) | VARCHAR2(100) | 同 |
|
||||
| 10 | 厂家 | manufacturer | varchar2(100) | VARCHAR2(100) | 同 |
|
||||
| 11 | 国药准字/注册证号 | approval_no | varchar2(100) | VARCHAR2(100) | 同 |
|
||||
| 12 | 单位 | unit | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 13 | 客户ID | customer_id | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 14 | 客户名称 | customer_name | varchar2(200) | VARCHAR2(200) | 同 |
|
||||
| 15 | 创建时间 | create_time | date | DATE | 同 |
|
||||
| 16 | 创建人ID | create_user_id | number(10) | NUMBER(10) | 同 |
|
||||
| 17 | 创建人名称 | create_user_name | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 18 | 更新时间 | update_time | date | DATE | 同 |
|
||||
| 19 | 更新人ID | update_user_id | number(10) | NUMBER(10) | 同 |
|
||||
| 20 | 更新人名称 | update_user_name | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 21 | 处理标志 | deal_flag | number(2) DEFAULT 0 NOT NULL | NUMBER(2) DEFAULT 0 NOT NULL | 同 |
|
||||
| 22 | 处理结果 | deal_result | varchar2(200) | VARCHAR2(200) | 同 |
|
||||
| 23 | 处理时间 | deal_time | date | DATE | 同 |
|
||||
| 24 | 处理人ID | deal_user_id | number(10) | NUMBER(10) | 同 |
|
||||
| 25 | 处理人名称 | deal_user_name | varchar2(20) | VARCHAR2(20) | 同 |
|
||||
| 26 | 序号 | fid | number(19) 自增序列 | NUMBER(19) 主键 + SEQUENCE/触发器 | 11g 无 IDENTITY |
|
||||
| 27 | 货主编码 | org_code | varchar2(10) | VARCHAR2(10) | 同 |
|
||||
| 28 | 货品MDM码 | goods_mdm_code | varchar2(100) | VARCHAR2(100) | 同 |
|
||||
| 29 | 客户MDM码 | customer_mdm_code | varchar2(100) | VARCHAR2(100) | 同 |
|
||||
|
||||
相对接口 sheet:数据表多审计字段、`deal_*`、`fid`。接口业务字段均在表中。`oms_orderno` 已删。
|
||||
|
||||
---
|
||||
|
||||
## 2. 加购及结算商品数据
|
||||
|
||||
Excel 待确认:同一条形码是否可出现在多个订单。平台字典已写入备注:48 药帮忙,66 1药城。
|
||||
|
||||
| Excel 序号 | 中文名 | 英文名 | Excel 属性 | DDL | 差异 |
|
||||
| ---: | --- | --- | --- | --- | --- |
|
||||
| 1 | 序号 | fid | number(19,0) IDENTITY | NUMBER(19,0) 主键 + SEQUENCE/触发器 | 11g 无 IDENTITY,主键补 NOT NULL |
|
||||
| 2 | 操作类型 | operate_type | number(2,0) NOT NULL | 同 | 无 |
|
||||
| 3 | 订单中心单据编号 | oms_id | number(20) NOT NULL | 同 | 无 |
|
||||
| 4 | 平台类型 | platform_type | number(4,0) NOT NULL | 同 | 无 |
|
||||
| 5 | 供应商名称 | supplier_name | varchar2(200 char) NOT NULL | 同 | 无 |
|
||||
| 6 | 条形码 | bar_code | varchar2(40 char) NOT NULL | 同;普通索引 | 无 |
|
||||
| 7 | 采购单价 | purchase_price | number(16,4) NOT NULL | 同 | 无 |
|
||||
| 8 | 采购数量 | purchase_qty | number(10,0) NOT NULL | 同 | 无 |
|
||||
| 9 | 采购总价 | purchase_total | number(16,4) NOT NULL | 同 | 无 |
|
||||
| 10 | 整单采购状态 | status | number(2,0) | 同 | 无 |
|
||||
| 11 | 整单采购失败原因 | fail_reason | varchar2(500 char) | 同 | 无 |
|
||||
| 12 | 支付订单号 | pay_order_no | varchar2(40 char) | 同 | 无 |
|
||||
| 13 | 支付订单明细ID | pay_order_dtl_id | varchar2(40 char) | 同 | 无 |
|
||||
| 14 | 推送状态 | push_status | number(1,0) DEFAULT 0 NOT NULL | 同 | 无 |
|
||||
| 15 | 推送结果 | push_result | varchar2(500 char) | 同 | 无 |
|
||||
| 16 | 推送时间 | push_time | date | DATE | 无 |
|
||||
| 17 | 创建时间 | create_time | date NOT NULL | DATE NOT NULL | Excel 写作 dateNOT NULL |
|
||||
| 18 | 更新时间 | update_time | date | DATE | 无 |
|
||||
| 19 | 创建人ID | create_user_id | number(19,0) | 同 | 无 |
|
||||
| 20 | 创建人名称 | create_user_name | varchar2(100 char) | 同 | 无 |
|
||||
| 21 | 更新人ID | update_user_id | number(19,0) | 同 | 无 |
|
||||
| 22 | 更新人名称 | update_user_name | varchar2(100 char) | 同 | 无 |
|
||||
| 23 | 货主编码 | org_code | varchar2(10) | 同 | 无 |
|
||||
| 24 | 多平台满足标志 | multi_platform_flag | number(1,0) DEFAULT 0 NOT NULL | 同 | 无 |
|
||||
|
||||
相对接口 sheet:数据表多 `push_*`、审计、`multi_platform_flag`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 采购合同数据
|
||||
|
||||
Excel 待确认:重录协议是否新 `fid`;税率百分数还是小数;是否保留 `need_contract` 旧名。
|
||||
|
||||
| Excel 序号 | 中文名 | 英文名 | Excel 属性 | DDL | 差异 |
|
||||
| ---: | --- | --- | --- | --- | --- |
|
||||
| 1 | 序号 | fid | number(19,0) NOT NULL | 同,主键(来源唯一) | 无 |
|
||||
| 2 | 订单中心单据编号 | oms_id | number(20) NOT NULL | 同 | 无 |
|
||||
| 3 | 供应商ID | supplier_id | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 4 | 供应商名称 | supplier_name | varchar2(200 char) NOT NULL | 同 | 无 |
|
||||
| 5 | 合同签订日期 | contract_sign_date | date NOT NULL | 同 | 无 |
|
||||
| 6 | 合同开始日期 | contract_start_date | date NOT NULL | 同 | 无 |
|
||||
| 7 | 合同结束日期 | contract_end_date | date NOT NULL | 同 | 无 |
|
||||
| 8 | 付款期限(账期) | payment_term | number(5,0) NOT NULL | 同 | 无 |
|
||||
| 9 | 货品ID | goods_id | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 10 | 货品名称 | goods_name | varchar2(200 char) NOT NULL | 同 | 无 |
|
||||
| 11 | 注册证号 | reg_no | varchar2(100 char) NOT NULL | 同 | 无 |
|
||||
| 12 | 规格 | spec | varchar2(100 char) NOT NULL | 同 | 无 |
|
||||
| 13 | 单位 | unit | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 14 | 数量 | qty | number(16,4) NOT NULL | 同 | 无 |
|
||||
| 15 | 进货税率 | purchase_tax_rate | number(8,4) NOT NULL | 同 | 无 |
|
||||
| 16 | 采购单价 | unit_price | number(16,4) NOT NULL | 同 | 无 |
|
||||
| 17 | 货源细单ID | goods_source_id | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 18 | 采购员ID | purchaser_id | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 19 | 采购员 | purchaser_name | varchar2(100 char) NOT NULL | 同 | 无 |
|
||||
| 20 | 计划员ID | planner_id | varchar2(20 char) NOT NULL | 同 | 无 |
|
||||
| 21 | 计划员 | planner_name | varchar2(100 char) NOT NULL | 同 | 无 |
|
||||
| 22 | 录入合同状态 | contract_entry_status | number(1,0) | 同 | 无 |
|
||||
| 23 | 采购合同号 | contract_no | varchar2(40 char) | 同 | 无 |
|
||||
| 24 | 处理状态 | process_status | number(1,0) DEFAULT 0 NOT NULL | 同 | 无 |
|
||||
| 25 | 处理结果 | process_result | varchar2(1000 char) | 同 | 无 |
|
||||
| 26 | 处理时间 | process_time | timestamp(6) | 同 | 无 |
|
||||
| 27 | 创建时间 | create_time | timestamp(6) NOT NULL | 同 | 无 |
|
||||
| 28 | 更新时间 | update_time | timestamp(6) | 同 | 无 |
|
||||
| 29 | 货主编码 | org_code | varchar2(10) | 同 | 无 |
|
||||
| 30 | 客户类型 | customer_type | number(2,0) NOT NULL | 同 | 无 |
|
||||
| 31 | 货品MDM码 | goods_mdm_code | varchar2(100) | 同 | 无 |
|
||||
| 32 | 供应商MDM码 | supplier_mdm_code | varchar2(100) | 同 | 无 |
|
||||
|
||||
相对接口 sheet:数据表多 `process_*`、`create_time`、`update_time`。未建 `need_contract`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 合同协议录入结果
|
||||
|
||||
无 Excel「待确认」列。
|
||||
|
||||
| Excel 序号 | 中文名 | 英文名 | Excel 属性 | DDL | 差异 |
|
||||
| ---: | --- | --- | --- | --- | --- |
|
||||
| 1 | 序号 | fid | number(19,0) IDENTITY | NUMBER(19,0) 主键 + SEQUENCE/触发器 | 11g 无 IDENTITY,主键补 NOT NULL |
|
||||
| 2 | 订单中心单据编号 | oms_id | number(20) NOT NULL | 同 | 无 |
|
||||
| 3 | 回传动作 | action_code | number(2,0) NOT NULL | 同 | 无 |
|
||||
| 4 | 合同状态 | contract_status | number(2,0) | 同 | 无 |
|
||||
| 5 | 合同录入失败原因 | contract_fail_reason | varchar2(500 char) | 同 | 无 |
|
||||
| 6 | 合同编号 | contract_no | varchar2(40 char) | 同 | 无 |
|
||||
| 7 | 返利协议状态 | agreement_status | number(2,0) | 同 | 无 |
|
||||
| 8 | 协议录入失败原因 | agreement_fail_reason | varchar2(500 char) | 同 | 无 |
|
||||
| 9 | 采购协议总单ID | agreement_id | varchar2(40 char) | 同 | 无 |
|
||||
| 10 | 采购协议细单ID | agreement_dtl_id | varchar2(40 char) | 同 | 无 |
|
||||
| 11 | 推送状态 | push_status | number(1,0) DEFAULT 0 NOT NULL | 同 | 无 |
|
||||
| 12 | 推送结果 | push_result | varchar2(500 char) | 同 | 无 |
|
||||
| 13 | 推送时间 | push_time | timestamp(6) | 同 | 无 |
|
||||
| 14 | 创建时间 | create_time | timestamp(6) NOT NULL | 同 | 无 |
|
||||
| 15 | 更新时间 | update_time | timestamp(6) | 同 | 无 |
|
||||
| 16 | 创建人ID | create_user_id | number(19,0) | 同 | 无 |
|
||||
| 17 | 创建人名称 | create_user_name | varchar2(100 char) | 同 | 无 |
|
||||
| 18 | 更新人ID | update_user_id | number(19,0) | 同 | 无 |
|
||||
| 19 | 更新人名称 | update_user_name | varchar2(100 char) | 同 | 无 |
|
||||
| 20 | 货主编码 | org_code | varchar2(10) | 同 | 无 |
|
||||
| — | 返利处理状态 | process_status | — | NUMBER(1,0) DEFAULT 0 NOT NULL | 本方补。0 待查号,1 返利已回写,2 合同失败不进返利,3 查号中。不进接口 4 |
|
||||
|
||||
相对接口 sheet:数据表多 `push_*`、审计字段、`process_status`。
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# SOURCE_ADD / 对码幂等 / Alias 唯一态 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 让 `SOURCE_ADD` 有明确触发源,让 `PRODUCT_MATCH` 可重复跑且不重复落库,让 alias 沉淀成唯一当前态。
|
||||
|
||||
**Architecture:** 对码仍然留在 `app/main.py` 的状态中心内执行,但把“判定缺货源 / 对码执行 / 人工确认”拆成稳定的写入边界。`product_match` 以 `(job_id, order_item_id)` 作为幂等键,`mapping_config` 只保留一个 active alias 当前态,`job.needs_source_add` 作为 `SOURCE_ADD` 的唯一触发开关。
|
||||
|
||||
**Tech Stack:** Python, FastAPI, MySQL, pymysql, pytest/unittest
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 让对码重跑幂等
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql`
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py`
|
||||
- Test: `/Users/yuuuu/Documents/AutoRpa/state_center/tests/test_match_engine.py` or a new focused test file
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
def test_match_run_replay_does_not_duplicate_product_match_rows():
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: fail because `product_match` lacks a stable dedupe key or the code still inserts duplicates.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Add a unique key on `(job_id, order_item_id)`, switch `match_run` to upsert by that key, and keep `user_task` creation tied to `product_match_id`.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: pass for the replay case.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add /Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql /Users/yuuuu/Documents/AutoRpa/state_center/app/main.py /Users/yuuuu/Documents/AutoRpa/state_center/tests
|
||||
git commit -m "fix: make product match replay idempotent"
|
||||
```
|
||||
|
||||
### Task 2: 让 SOURCE_ADD 有明确触发源
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py`
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql`
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/给Trae的开发交接.md`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
def test_source_add_only_triggers_when_job_flag_is_set():
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: fail because `needs_source_add` is not yet driven by a concrete write path.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Introduce a single write path for `job.needs_source_add`, use it in `match_run` / confirm flow, and only let `_next_node()` expose `SOURCE_ADD` when that flag is set.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: pass for both the flagged and skipped cases.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add /Users/yuuuu/Documents/AutoRpa/state_center/app/main.py /Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql /Users/yuuuu/Documents/AutoRpa/state_center/给Trae的开发交接.md
|
||||
git commit -m "fix: make source add trigger explicit"
|
||||
```
|
||||
|
||||
### Task 3: 让 alias 沉淀成唯一当前态
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py`
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql`
|
||||
- Modify: `/Users/yuuuu/Documents/AutoRpa/state_center/README.md`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
def test_alias_confirm_replaces_existing_active_alias_for_same_key():
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: fail because multiple active aliases for the same `config_key` are still possible.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Make confirm flow deactivate the previous active alias for the same key before inserting the new one, and make alias loading deterministic by reading only the latest active row per key.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python3 -m pytest /Users/yuuuu/Documents/AutoRpa/state_center/tests -q`
|
||||
Expected: pass with one active alias per key.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add /Users/yuuuu/Documents/AutoRpa/state_center/app/main.py /Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql /Users/yuuuu/Documents/AutoRpa/state_center/README.md
|
||||
git commit -m "fix: keep alias mapping deterministic"
|
||||
```
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# State Center Web State Machine Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build the browser-based Web state machine for `state_center`: job list, timeline, task list, and task/node actions.
|
||||
|
||||
**Architecture:** Keep the existing FastAPI backend as the source of truth for workflow state. Add small, focused backend endpoints for timeline, jobs, tasks, and dev ingestion, then build a React/Vite frontend that only renders backend-computed state and posts actions back. Do not move business rules into the frontend.
|
||||
|
||||
**Tech Stack:** Python 3.13, FastAPI, Pydantic, pymysql, pytest, Node.js, Vite, React, TypeScript, plain CSS.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add backend task/timeline data contracts
|
||||
|
||||
**Files:**
|
||||
- Modify: `state_center/sql/schema.sql`
|
||||
- Modify: `state_center/app/main.py`
|
||||
- Create: `state_center/tests/test_web_state_machine.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add tests for:
|
||||
- `GET /api/jobs/{job_id}/timeline` returns all 13 nodes with backend-computed statuses.
|
||||
- `POST /api/tasks/{task_id}/submit` stores `submitted_result_json` and returns the stored result on duplicate `submitToken`.
|
||||
- `POST /api/jobs/{job_id}/items` rejects unsafe replace-after-match behavior.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `python -m pytest state_center/tests/test_web_state_machine.py -v`
|
||||
Expected: fail because endpoints/fields do not exist yet.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Add the schema column for stored task results, then implement the backend helpers and endpoints in `main.py`.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `python -m pytest state_center/tests/test_web_state_machine.py -v`
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center/sql/schema.sql state_center/app/main.py state_center/tests/test_web_state_machine.py
|
||||
git commit -m "feat: add web state machine backend contracts"
|
||||
```
|
||||
|
||||
### Task 2: Add browser-ready frontend app
|
||||
|
||||
**Files:**
|
||||
- Create: `state_center_web/package.json`
|
||||
- Create: `state_center_web/vite.config.ts`
|
||||
- Create: `state_center_web/index.html`
|
||||
- Create: `state_center_web/src/main.tsx`
|
||||
- Create: `state_center_web/src/App.tsx`
|
||||
- Create: `state_center_web/src/api/client.ts`
|
||||
- Create: `state_center_web/src/components/JobList.tsx`
|
||||
- Create: `state_center_web/src/components/JobTimeline.tsx`
|
||||
- Create: `state_center_web/src/components/TaskList.tsx`
|
||||
- Create: `state_center_web/src/styles.css`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add a lightweight frontend build check and a small unit test for timeline status mapping.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: fail because the app does not exist yet.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Create the Vite app, route-less operational UI, and fetch-based API client.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `npm test`
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center_web
|
||||
git commit -m "feat: add web state machine frontend"
|
||||
```
|
||||
|
||||
### Task 3: Wire local dev workflow
|
||||
|
||||
**Files:**
|
||||
- Create: `state_center_web/README.md`
|
||||
- Modify: `state_center/README.md`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Not required; this task is documentation and run instructions only.
|
||||
|
||||
- [ ] **Step 2: Write minimal implementation**
|
||||
|
||||
Document the start commands for the FastAPI backend and Vite frontend on Windows/macOS.
|
||||
|
||||
- [ ] **Step 3: Verify manually**
|
||||
|
||||
Start backend and frontend locally, open the browser app, and confirm the timeline and task list render.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center/README.md state_center_web/README.md
|
||||
git commit -m "docs: add web state machine runbook"
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
# 业务优先单页收敛 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 把状态中心页面收敛成“业务先看懂单据、运维再下钻”的单页结构,首屏只保留结论、关键编号、当前待办和简化流程,证据/节点详情/验收收口默认折叠。
|
||||
|
||||
**Architecture:** 页面以单据概览为主视图,流程图和当前待办保持可见,但证据、节点详情、验收清单移到折叠区。状态计算仍留在 `stateMachine.ts`,组件只消费派生结果,不在 JSX 里推导业务规则。视觉上减少同屏卡片数量,保留一条主叙事线。
|
||||
|
||||
**Tech Stack:** React 18 + TypeScript + CSS + 现有 FastAPI 数据契约
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 提炼业务概览的数据模型
|
||||
|
||||
**Files:**
|
||||
- Modify: `state_center_web/src/stateMachine.ts`
|
||||
- Modify: `state_center_web/src/stateMachine.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```javascript
|
||||
test('概览模型只保留首屏必要信息', () => {
|
||||
const model = buildBusinessOverview(job, timeline, tasks, artifacts);
|
||||
assert.equal(model.header.currentStatusText, '已完成');
|
||||
assert.equal(model.header.currentNodeText, '归档');
|
||||
assert.equal(model.referenceChain.length, 5);
|
||||
assert.equal(model.primaryTask.title, '新增货源:重庆亿昊药业');
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd state_center_web && node --test --experimental-strip-types src/stateMachine.test.mjs`
|
||||
Expected: FAIL with missing `buildBusinessOverview` export.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Implement `buildBusinessOverview()` in `stateMachine.ts` so it returns:
|
||||
- 首屏头部摘要
|
||||
- 关键编号链
|
||||
- 当前待办
|
||||
- 简化的流程状态
|
||||
- 折叠区所需的计数摘要
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd state_center_web && node --test --experimental-strip-types src/stateMachine.test.mjs`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center_web/src/stateMachine.ts state_center_web/src/stateMachine.test.mjs
|
||||
git commit -m "feat: add business overview view model"
|
||||
```
|
||||
|
||||
### Task 2: 重排单页骨架为业务优先布局
|
||||
|
||||
**Files:**
|
||||
- Modify: `state_center_web/src/App.tsx`
|
||||
- Modify: `state_center_web/src/components/JobTimeline.tsx`
|
||||
- Modify: `state_center_web/src/components/JobReferenceChain.tsx`
|
||||
- Modify: `state_center_web/src/components/JobAcceptanceChecklist.tsx`
|
||||
- Modify: `state_center_web/src/components/JobNodeDetailList.tsx`
|
||||
- Modify: `state_center_web/src/components/JobEvidenceList.tsx`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Use the existing browser mockup as the acceptance target:
|
||||
- 首屏只显示一个业务结论区
|
||||
- 关键编号和当前待办在首屏可见
|
||||
- 证据、节点详情、验收清单默认收起
|
||||
|
||||
Add a component test or state snapshot asserting the new layout consumes the business overview model instead of rendering all detail blocks at equal weight.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd state_center_web && node --test --experimental-strip-types src/*.test.mjs`
|
||||
Expected: FAIL because the page still renders the old dense stack.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Recompose `App.tsx` into:
|
||||
- 顶部单据概览
|
||||
- 中间流程主区 + 当前待办
|
||||
- 底部三个折叠区:证据、节点详情、验收对照
|
||||
|
||||
Keep existing data loading and callbacks, but move the dense panels behind `<details>` or equivalent collapsible controls. Limit the header to the business summary, not all fields.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
- `cd state_center_web && node --test --experimental-strip-types src/*.test.mjs`
|
||||
- `cd state_center_web && ./node_modules/.bin/tsc --noEmit`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center_web/src/App.tsx state_center_web/src/components/JobTimeline.tsx state_center_web/src/components/JobReferenceChain.tsx state_center_web/src/components/JobAcceptanceChecklist.tsx state_center_web/src/components/JobNodeDetailList.tsx state_center_web/src/components/JobEvidenceList.tsx
|
||||
git commit -m "feat: simplify business overview layout"
|
||||
```
|
||||
|
||||
### Task 3: 压缩视觉密度并做浏览器验收
|
||||
|
||||
**Files:**
|
||||
- Modify: `state_center_web/src/styles.css`
|
||||
- Optional modify: `state_center_web/src/components/*.tsx` only if a small class split is needed
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
No new logic test needed; use a visual acceptance check:
|
||||
- 业务结论区在首屏可见
|
||||
- 折叠区默认关闭
|
||||
- 屏幕上不同时出现过多等重卡片
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd state_center_web && ./node_modules/.bin/vite build`
|
||||
Expected: build passes before visual check, but browser screenshot should still show the dense layout, proving the need for CSS reduction.
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Reduce weight in CSS:
|
||||
- 收窄主卡片数量
|
||||
- 给折叠区明显但低优先级的视觉权重
|
||||
- 让编号链和状态结论压在首屏
|
||||
- 让路径、证据、验收清单更像下钻信息,不像主内容
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
- `cd state_center_web && ./node_modules/.bin/vite build`
|
||||
- `cd state_center_web && ./node_modules/.bin/tsc --noEmit`
|
||||
- `cd state_center_web && node --test --experimental-strip-types src/*.test.mjs`
|
||||
|
||||
Then open the local app in the browser and verify:
|
||||
- 首屏只有一条主叙事
|
||||
- 折叠区默认收起
|
||||
- 证据路径可点
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add state_center_web/src/styles.css
|
||||
git commit -m "feat: reduce dashboard density"
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
# 班次大批量收口(打勾用)
|
||||
|
||||
改完把 `- [ ]` 改成 `- [x]`,勾上日期。一次只开一条。说「做 BATCH-01」再动代码。
|
||||
|
||||
建议顺序:BATCH-01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10。
|
||||
|
||||
目标:一个班次可以安全地重复跑,对方系统里的单据不因重跑而多出一份。
|
||||
|
||||
口径不变:接口 1 不跑选品;接口 3 不录合同;选品拆量规则不动;合同 S02–S13 填表步骤不动;接口 2、4 这轮不接推送。
|
||||
|
||||
已定,不要再讨论:
|
||||
|
||||
- 领取 `UPDATE` 的 `IN` 每段 500 个 `fid`,同一事务,任一段失败整笔回滚。调用方 `limit` 仍管这一班做多少。
|
||||
- 班次池保持 `min=1, max=1`。还没 `commit` 的失败:`drop` 坏连接,再借一次,只重试这一笔。已提交的不重做。`ping_interval=60`。
|
||||
- 序列保持 `NOCACHE`。这轮不改 DDL,不加注释。
|
||||
- `rpa_oms_inbound_audit`、`rpa_selection_query_audit`、`rpa_shift_error_audit` 这轮不清理。
|
||||
- 合同查重三态:0 条才起草,恰好 1 条才复用合同号,多条停在 `process_status=3`。标题是客商名 + 「采购协议」 + 签订日,不含货主和起止日,标题 + 客商不是合单键。
|
||||
|
||||
**注释门禁(每条收工都查):** 本条改过或新建的模块、类、每一个函数/方法(含 `_` 私有、测试函数)都要有 docstring。第一句写做什么,随后只补签名里看不出的规则。不用 Args/Returns/Raises 小标题。缺一条不算完成。
|
||||
|
||||
---
|
||||
|
||||
## 1. 领取分段,入口封顶
|
||||
|
||||
### BATCH-01 领取 UPDATE 分段
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_oracle` 放共用分段更新。`rpa_selection/repo.py`、`rpa_cart/cart_repo.py`、`rpa_contract/repo.py`、`rpa_rebate/repo.py` 的领取 `UPDATE` 改走它。
|
||||
- 改成:每段 500 个 `fid`,同一事务,任一段失败整笔回滚。`rpa_rebate/repo.py` 的 `_take_draft_groups` 第一组仍整组领,只把更新拆段。
|
||||
- 验收:领取 1001 条时第一段能领走,不报「数据库不可用」。返利一个合同号超过 1000 行仍能整组领走。点名加在各自 `test_oracle_repo.py` / `test_cart_repo.py`。
|
||||
|
||||
### BATCH-02 入口默认上限
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_selection/main.py`、`rpa_cart/main.py`、`rpa_rebate/run_batch.py`、`rpa_contract/run_batch.py`。
|
||||
- 改成:不传 `--limit` 用默认值,不再表示全部领走。选品 50,加购 20,合同和返利按行 10。合同入口补上 `--limit`。
|
||||
- 验收:四个入口不传参时领取条数不超过默认值。
|
||||
|
||||
---
|
||||
|
||||
## 2. 按单落库,页面期间不占连接
|
||||
|
||||
### BATCH-03 选品一行一落,续租,重领不重插
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_selection/job.py` 的 `run_batch`,`rpa_selection/repo.py` 的 `save_results`。
|
||||
- 改成:一行搜完就 `save_results` 这一行。搜索过程中刷新 `deal_time`。落库前再读:仍是本进程领的 `deal_flag=3`,并且表 2 还没有该 `oms_id` 的加购行,才插入。
|
||||
- 验收:`rpa_selection/tests/test_job.py` 点名。租约过期后第二个进程不再插入第二套表 2。
|
||||
|
||||
### BATCH-04 页面期间归还连接
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_contract/job.py`、`rpa_rebate/job.py`、`rpa_cart/cart_job.py` 的 `session()`。
|
||||
- 改成:`session()` 只包住领取和落库,不包住页面或商城调用。一组做完就提交。
|
||||
- 验收:对应 `test_job.py` / `test_cart_job.py`。页面执行期间连接已归还。
|
||||
|
||||
### BATCH-05 坏连接丢弃后再借一次
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_oracle/pool.py`。
|
||||
- 改成:`create_pool` 写 `ping_interval=60`。未提交失败时 `drop` 坏连接,再借一次,只重试这一笔。注释写明班次池只有一条,靠探活和丢弃换连接。班次池保持 `min=1, max=1`。
|
||||
- 验收:`rpa_oracle/tests/test_pool.py`。探活失败或执行前连接已死时,下一次借到新连接。已提交的语句不重做。
|
||||
|
||||
---
|
||||
|
||||
## 3. 瞬时故障保持可再领
|
||||
|
||||
### BATCH-06 选品系统故障改回待领
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_selection/persist.py`。
|
||||
- 改成:`LOGIN_FAILED` / `QUERY_FAILED` 且凑不齐时,`deal_flag` 改回 0,不写表 2。库存不足、毛利、效期仍写 `deal_flag=2`。
|
||||
- 验收:`rpa_selection/tests/test_job.py` 里系统故障用例改断言。业务失败用例仍是终态。
|
||||
|
||||
### BATCH-07 加购登录和查询失败放回队列
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_cart/cart_job.py`。
|
||||
- 改成:登录失败和查询失败把戳改回「系统管理员」,`status` 保持 1。对不上仓、库存不足仍写 `status=2`。
|
||||
- 验收:`rpa_cart/tests/test_cart_job.py`。登录失败的行下一班还能领。
|
||||
|
||||
---
|
||||
|
||||
## 4. 重跑不产生第二份外部单据
|
||||
|
||||
### BATCH-08 加购前查车
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_cart/cart_job.py` 的 `_add_one`。
|
||||
- 改成:加车前按条形码和供应商查购物车。数量已经不少于本行,直接 `mark_success`。多出来的数量不当成另一张单。
|
||||
- 验收:`rpa_cart/tests/test_cart_job.py`。对方购物车已有本行数量、本地仍是「加购中」时,重跑不加第二份。
|
||||
|
||||
### BATCH-09 合同查重三态
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_contract/run_batch.py` 的 `production_runner`。每个合单组开始前把浏览器拉回起草页。
|
||||
- 改成:进起草前查「我的合同」。0 条才走 S06–S12;恰好 1 条只落表 4;多条停在 `process_status=3`,不新建,也不取最新一条。提交后读不到号也停在 3,下一班只查号。注释写明三态,以及标题不含货主和起止日。
|
||||
- 验收:`rpa_contract/tests/test_job.py`。对方已有唯一合同号、本地没写上时,重跑不起草第二份。命中多条时停在处理中。
|
||||
|
||||
### BATCH-10 返利按合同号查重
|
||||
|
||||
- [x] 2026-10-24
|
||||
- 落点:`rpa_rebate/job.py` 的 `recorder`,`rpa_rebate/persist.py` 的 `build_found`。
|
||||
- 改成:送审前按合同号查已有协议。已有总单号就补写 `action_code=2`,不再新建。只在确认对方没有单据时,才把起草行改回 `process_status=0`。读号失败保持处理中。
|
||||
- 验收:`rpa_rebate/tests/test_job.py`。对方已有协议、本地没写上时,重跑不产生第二份。
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# State Center Web State Machine Design
|
||||
|
||||
## Goal
|
||||
|
||||
Build a browser-based Web application for the state center so users can see where each procurement RPA job is, inspect node details, handle open tasks, and manually advance supported human nodes. The Web app must run the same way on Windows and macOS through a standard browser.
|
||||
|
||||
## Scope
|
||||
|
||||
The first version is option B: status diagram + task list + node actions.
|
||||
|
||||
In scope:
|
||||
- A React/Vite frontend under `state_center_web/`.
|
||||
- FastAPI endpoints needed by the frontend for state machine metadata, job search/list, task list, task submit, and manual node completion.
|
||||
- A visual job detail page showing all 13 nodes, current node, executor owner, completed/running/waiting/failed states, and skipped conditional nodes.
|
||||
- A task page for open tasks by role and status.
|
||||
- Basic node action controls for development/demo only: create job, start/complete/fail RPA nodes with token, run product match, submit task actions, and complete manual nodes where allowed.
|
||||
- Cross-platform scripts based on Node/npm and Python commands only; no Electron or OS-specific shell behavior.
|
||||
|
||||
Out of scope for this first version:
|
||||
- Evidence file browser as a full module.
|
||||
- Enterprise WeChat login/JWT exchange.
|
||||
- Real Oracle credentials and real external-system page automation.
|
||||
- Desktop app packaging.
|
||||
|
||||
Because real Oracle credentials are out of scope, local demo must include one explicit fixture path for the goods engine. The fixture loader is development-only and separate from `/api/match/reload-goods`; production still reads goods master data from Oracle only.
|
||||
|
||||
## Architecture
|
||||
|
||||
The existing FastAPI service remains the single owner of business state. The frontend never computes the next node by itself; it renders the backend state machine metadata and calls backend actions. 艺赛旗 remains a node executor: it calls `start`, `complete`, and `fail`; it does not own the global business state machine.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser["Browser Web App"] --> API["FastAPI State Center"]
|
||||
API --> MySQL["MySQL rpa_center"]
|
||||
API -. read only .-> Oracle["Oracle Goods Master"]
|
||||
ISRPA["iS-RPA"] --> API
|
||||
Human["Business User"] --> Browser
|
||||
```
|
||||
|
||||
## Backend Design
|
||||
|
||||
Keep `state_center/app/main.py` as the current entry point, but add small, focused endpoints instead of changing the existing callback contract.
|
||||
|
||||
Add:
|
||||
- `GET /api/state-machine`: returns `NODE_ORDER`, transitions, executor map, conditional node set, and display labels.
|
||||
- `GET /api/jobs`: list/search jobs by `status`, `source_type`, `supplier_name`, `date_from`, `date_to`, with compact fields for the dashboard.
|
||||
- `GET /api/jobs/{job_id}/timeline`: returns all 13 nodes with backend-computed display status for each node: `PENDING`, `RUNNING`, `SUCCESS`, `FAILED`, `WAITING_USER`, `WAITING_EXTERNAL`, or `SKIPPED`.
|
||||
- `GET /api/tasks/my`: list tasks by `roleCode` and `status`. For v1, this is a role-filtered dev endpoint, not a user-identity endpoint.
|
||||
- `POST /api/tasks/{task_id}/submit`: accepts `action`, `payload`, `submitToken`, and `operator`.
|
||||
- `POST /api/jobs/{job_id}/items`: development/data-ingest endpoint for order details before real `ORDER_FETCH` integration. Request body is `{"replace": true, "rows": [...]}`. Each row maps to `order_item`: `orderNo`, `rawProductName`, `platformProductCode`, `spec`, `manufacturer`, `approvalNo`, `qty`, `unitPrice`, and `sourceRowNo`. Repeated ingest with `replace=true` deletes current rows for that job, inserts the new rows, and updates `order_no_list_json` from the payload.
|
||||
- `POST /api/dev/match/load-fixture-goods`: development-only endpoint that loads a small in-memory goods fixture so `/api/jobs/{job_id}/match/run` can be demonstrated without Oracle.
|
||||
- `GET /api/jobs/{job_id}/match/unmatched`: not required by v1 if task rows with `candidates_json` cover the UI; this spec treats the task list as the v1 source of truth and supersedes the older unmatched view for this phase.
|
||||
|
||||
`replace=true` is only valid before `PRODUCT_MATCH` has produced any durable downstream state, or it must transactionally clear dependent `product_match`, `user_task`, and related node/task state for that job in the same operation. The first version should prefer the pre-match restriction.
|
||||
|
||||
Human-node lifecycle rules:
|
||||
- Human nodes use these roles in v1:
|
||||
- `SOURCE_ADD` -> `PURCHASER`
|
||||
- `M5_MANUAL_CONFIRM` -> `PURCHASER`
|
||||
- `M5_RECEIPT_NOTICE` -> `PURCHASER`
|
||||
- Entering `SOURCE_ADD`, `M5_MANUAL_CONFIRM`, or `M5_RECEIPT_NOTICE` creates an open `user_task`, inserts a `job_node` row for the node if one does not exist yet, sets `job.status='WAITING_USER'`, and sets `job.current_role` to the matching role.
|
||||
- Leaving a human node happens through `POST /api/tasks/{task_id}/submit` with `CONFIRM_AND_CONTINUE`, not through the RPA callback path.
|
||||
- `SOURCE_ADD` completion clears `job.needs_source_add` and advances to the next node.
|
||||
- `M5_MANUAL_CONFIRM` completion advances to `M5_RECEIPT_NOTICE`.
|
||||
- `M5_RECEIPT_NOTICE` completion requires `payload.receiptNoticeNo`, writes `job.receipt_notice_no`, and advances to `ARCHIVE_JOB`.
|
||||
|
||||
Task submission rules:
|
||||
- `SELECT_CANDIDATE` payload must include `productMatchId` and `goodsId`; it may include `goodsCode`, `baseUnit`, and `saveAsAlias`.
|
||||
- `SELECT_CANDIDATE` delegates to the existing `match_confirm` behavior by mapping `productMatchId` to the existing `match_id` path and passing through `goodsId`, `goodsCode`, `baseUnit`, and `saveAsAlias`.
|
||||
- `CONFIRM_AND_CONTINUE` can complete manual nodes such as `SOURCE_ADD`, `M5_MANUAL_CONFIRM`, and `M5_RECEIPT_NOTICE` when the task is open and the job is on that node.
|
||||
- `RETRY_CURRENT_NODE`, `TRANSFER_MANUAL`, and `TERMINATE_JOB` are dev-mode task actions only in v1 and must write an event plus task submission record; they do not invent new business routing.
|
||||
- `TERMINATE_JOB` sets the job to `CANCELLED` with an event.
|
||||
- `TRANSFER_MANUAL` changes `current_role` on the job and records the requested target role; full reassignment is later.
|
||||
- `RETRY_CURRENT_NODE` leaves the job on the same node and records the retry request; it does not delete or overwrite `job_node` history.
|
||||
- `submitToken` is required.
|
||||
- First submit locks the task row, stores token/action/payload/result, marks the task `SUBMITTED`, and writes the event.
|
||||
- Repeat submit with the same token returns the stored result.
|
||||
- Repeat submit with a different token returns HTTP 409.
|
||||
- `submitted_result_json` is added to `user_task` to hold the replayable response body.
|
||||
|
||||
Authentication in this first version uses the existing `X-Agent-Token` for write/action endpoints so the demo remains consistent with current code. Read-only endpoints can stay open for local development unless project authentication is added.
|
||||
For browser-based business submission in v1, the app is local-dev only and exposes a small operator settings panel where the developer pastes the token once per session. The token stays in browser session state and is never bundled into app code. The dev simulator can use the token directly; ordinary business screens must not hardcode it.
|
||||
|
||||
The browser UI must not embed the agent token in normal production flows. RPA callback simulators are allowed only behind an explicit dev flag and must be visually separated from business user actions.
|
||||
|
||||
## Frontend Design
|
||||
|
||||
Create `state_center_web/` with React, TypeScript, Vite, and plain CSS. The UI is an operational dashboard, not a marketing page.
|
||||
|
||||
Views:
|
||||
- Job list: dense table with job id, supplier, order date, source, status, current node, role, and timestamps.
|
||||
- Job detail: state-machine diagram across the 13 nodes, plus side panel for job summary, node attempts, open tasks, and allowed actions.
|
||||
- Tasks: filterable open/submitted task list with role/status controls and submit forms.
|
||||
- Dev actions: a clearly labeled simulator section for local callback testing only.
|
||||
|
||||
Core frontend files:
|
||||
- `src/api/client.ts`: fetch wrapper and API types.
|
||||
- `src/stateMachine.ts`: visual labels and derived status helpers only; no next-node business logic.
|
||||
- `src/App.tsx`: route/view state and page composition.
|
||||
- `src/components/JobTimeline.tsx`: node diagram.
|
||||
- `src/components/JobList.tsx`: job table.
|
||||
- `src/components/TaskList.tsx`: task table/actions.
|
||||
- `src/styles.css`: responsive operational layout.
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. User opens the browser app.
|
||||
2. Frontend loads `/api/state-machine` and `/api/jobs`.
|
||||
3. User selects a job.
|
||||
4. Frontend loads `/api/jobs/{job_id}` and `/api/jobs/{job_id}/timeline` and renders node state from backend-computed data.
|
||||
5. User submits a task/action.
|
||||
6. Backend updates `user_task`, `job`, `job_node`, and `job_event` as needed.
|
||||
7. Frontend reloads job/task data.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The frontend should show backend `detail` messages directly in a compact error area. Conflicts such as out-of-order callbacks remain HTTP 409. Missing order items before product match remains HTTP 400. Missing token remains HTTP 401.
|
||||
|
||||
## Testing
|
||||
|
||||
Backend tests:
|
||||
- State machine metadata includes all 13 nodes and conditional nodes.
|
||||
- Job list returns compact rows.
|
||||
- Task list filters by role/status.
|
||||
- Task submit is idempotent by `task_id + submit_token`.
|
||||
- Manual node submit rejects jobs not currently on that node.
|
||||
- Timeline response returns all 13 nodes with `SKIPPED` computed server-side.
|
||||
- Dev action simulator remains disabled unless the dev flag is enabled.
|
||||
|
||||
Frontend tests:
|
||||
- Timeline marks completed, current, waiting, failed, and skipped nodes from API data.
|
||||
- Task list renders open tasks and calls submit with token/action.
|
||||
|
||||
Manual verification:
|
||||
- Start FastAPI on port 8000.
|
||||
- Start Vite on port 5173.
|
||||
- Create a job, ingest fixture order items, simulate callbacks, open job detail, and verify the diagram follows backend state.
|
||||
- In a local demo build, exercise the dev-only callback simulator without leaking the token into normal business screens.
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
> **历史版本,已合并替代。** 当前请使用[福建洁达采购 RPA 概要设计与详细设计 V3.0](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-13-jieda-rpa-integrated-design.md)。合同与返利一次接收、共用接口头明细,以新稿为准。下文保留历史内容,不作为当前实施依据。
|
||||
|
||||
# 福建洁达 RPA 四流程设计稿
|
||||
|
||||
版本:V1.9 · 2026-09-12
|
||||
配套:[技术实现说明 V2.3](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-12-jieda-four-flow-impl.md)。本次为文档修订,应用代码及数据库尚未改动。
|
||||
|
||||
## 1. 范围和运行方式
|
||||
|
||||
本期做四条流程:选品加购、平台订单导出推送、合同录入、返利录入。业务可以点击各自脚本执行,也可以设置不同时间、不同频率自动执行。一条脚本处理完本批数据就退出。
|
||||
|
||||
FastAPI 接收订单中心推送的数据,校验必填字段,缺项当次返回失败及具体字段;通过后保存到 RPA 表并返回成功。RPA 按各自排期读取未处理数据,不在接收请求中同步执行四条流程。
|
||||
|
||||
业务范围以本次确认内容为准;字段依据[合同返利字段映射](/Users/yuuuu/Documents/AutoRpa/资料/合同返利系统字段映射_20260911.xlsx),选品依据[采购条件](/Users/yuuuu/Documents/AutoRpa/资料/采购平台选品条件优先项_20260902.txt),合同和返利操作参考 `资料/合同系统录入流程.md`。手工单暂不做。
|
||||
|
||||
## 2. 四条流程
|
||||
|
||||
### 2.1 选品加购 `PROCURE`
|
||||
|
||||
```text
|
||||
读取订单中心已传入的未处理销售明细
|
||||
→ 取一条批准文号去平台查询
|
||||
→ 满足采购条件:加购物车,记录已加购
|
||||
→ 不满足:记录已跳过及原因
|
||||
→ 下一条 → 本批处理完结束
|
||||
```
|
||||
|
||||
本次业务已确认,相同批准文号只有一个规格。选品按批准文号查询,不增加规格、厂家、包装和单位换算匹配。
|
||||
|
||||
每条记录已加购、已跳过或执行失败。再次执行读取未处理及需要重试的数据。人工付款在平台由业务处理,第一条脚本不等付款。
|
||||
|
||||
### 2.2 平台订单导出推送 `ORDER_PUSH`
|
||||
|
||||
```text
|
||||
从对应平台导出订单 → 保存到该平台对应的共享文件夹
|
||||
→ 按订单中心接口格式组装数据 → 推送订单中心
|
||||
→ 记录接收结果 → 本批结束
|
||||
```
|
||||
|
||||
1 药城、药帮忙分别配置共享目录及导出字段映射。保留原订单文件、来源订单号和推送结果。对码在订单中心内部完成,本方不增加对码表、候选确认或对码轮询。
|
||||
|
||||
### 2.3 合同录入 `CONTRACT`
|
||||
|
||||
```text
|
||||
读取已接收成功、未处理的合同数据
|
||||
→ 打开合同系统,选择客户,填写合同头和导入明细
|
||||
→ 汇总金额并保存合同 → 记录合同号和保存结果
|
||||
→ 调用订单中心链路接口更新对应节点 → 完成
|
||||
```
|
||||
|
||||
必填字段在接收时检查,成功落表后直接供 RPA 录入。合同系统保存成功后才更新订单中心节点;更新失败保留“合同已保存、链路更新失败”,再次处理只补接口调用。
|
||||
|
||||
按原业务流程读取尽调回显;未尽调由采购员处理,本条停止自动录入,继续下一条。沿用业务要求的通知方式,不新增审批步骤。
|
||||
|
||||
### 2.4 返利录入 `REBATE`
|
||||
|
||||
```text
|
||||
读取已接收的返利数据 → 按合同号查询采购协议
|
||||
→ 填写返利总单 → 导入细单 → 提交
|
||||
→ 记录成功或失败 → 下一条
|
||||
```
|
||||
|
||||
字段按第 4 节填写。提交失败按原流程交业务处理。
|
||||
|
||||
## 3. 选品条件
|
||||
|
||||
前四项全部满足后,按仓库名单顺序选择。
|
||||
|
||||
| 条件 | 业务要求 |
|
||||
| --- | --- |
|
||||
| 自营仓 | 业务勾选的自营仓 |
|
||||
| 盈利 | 对比销售价格,有 5% 以上盈利 |
|
||||
| 有效期 | 大于等于 12 个月 |
|
||||
| 库存 | 满足需求采购数量 |
|
||||
| 仓库顺序 | 按以下名单优先选择 |
|
||||
|
||||
广东壹号药业有限公司、昆山亿方药业有限公司、湖北亿昊药业有限公司、天津壹号药业有限公司、重庆亿昊药业有限公司、重庆壹号医药有限公司、湖南壹号药业股份有限公司、辽宁亿昊药业有限公司、河南壹号医药有限公司、甘肃壹号云医药有限责任公司企业店、陕西壹号药业有限公司。
|
||||
|
||||
## 4. 合同及返利字段
|
||||
|
||||
以下字段和行号对应 Excel 的两个业务工作表。订单中心提供的字段在接收时校验;固定默认值由程序填写,系统生成及页面回显字段不要求上游必传。
|
||||
|
||||
### 4.1 合同头
|
||||
|
||||
| 目标字段 | 取值规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 客户名称 | 订单中心 | 12 |
|
||||
| 客户编码 | 目标系统自动生成 | 11 |
|
||||
| 尽调情况 | 读取回显;未尽调停止本单自动录入并转采购员处理 | 13 |
|
||||
| 结算类型 | 收支双向 | 15 |
|
||||
| 合同版本类型 | 预付:我司定制;账期:非格式合同 | 17 |
|
||||
| 合同金额(单位元) | 预估本次合作金额 | 24 |
|
||||
| 金额(元) | 明细汇总;与导入后页面汇总核对并回填 | 25 |
|
||||
| 合同签订日期、合同开始日期、合同结束日期 | 订单中心分别提供,三个独立字段 | 26–28 |
|
||||
| 是否重大合同 | 否 | 29 |
|
||||
| 是否预付款 | 预付:是;账期:否 | 30 |
|
||||
| 付款方式 | 电汇 | 32 |
|
||||
| 付款期限 | 预付:1;账期:订单中心账期天数,缺失不默认 30 | 33 |
|
||||
| 合同具体分类 | 国内药品类、器械类品种 | 37 |
|
||||
| 运输方式 | 公路运输 | 39 |
|
||||
|
||||
### 4.2 合同明细
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码 | 订单中心 | 43 |
|
||||
| 货品名称 | 订单中心标准货品名称 | 44 |
|
||||
| 注册证号 | 订单中心 | 46 |
|
||||
| 规格、单位 | 按订单中心标准字段填报 | 47–48 |
|
||||
| 数量、单价 | 按订单中心最终采购数量、单价填报 | 49–50 |
|
||||
| 付款方式 | 电汇 | 52 |
|
||||
| 付款条件 | 订单中心;模板无此字段时不填 | 53 |
|
||||
| 付款期限 | 同合同头规则 | 54 |
|
||||
|
||||
### 4.3 返利总单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 返利合约 | 否 | 5 |
|
||||
| 供应商编码 | 订单中心 | 6 |
|
||||
| 返利单位编码 | 订单中心 | 7 |
|
||||
| 计划员 | 订单中心,按业务给定的计划员/采购员对应关系填写 | 8 |
|
||||
| 采购结算方式 | 文案“否”;与细单代码 3 分别映射 | 9 |
|
||||
| 承付模式 | 下月 N 号结 | 10 |
|
||||
| 协议开始日期、协议结束日期 | 当天 | 11–12 |
|
||||
|
||||
### 4.4 返利细单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码、数量、含税协议单价 | 订单中心返利源数据 | 15–17 |
|
||||
| 税率、关税税率 | 均为 0.13 | 18–19 |
|
||||
| 承付模式、承付参数 | 11、25 | 20–21 |
|
||||
| 细单开始日期 | 当天 | 22 |
|
||||
| 细单结束日期 | 本年度最后一天 | 23 |
|
||||
| 采购结算方式 | 3 | 24 |
|
||||
| 特定补偿预估值、进货补偿预估值 | 均为 0 | 25–26 |
|
||||
| 细单备注 | 订单中心订单号 | 33 |
|
||||
| 货源细单ID | 订单中心货源细单号;与订单号分列 | 34 |
|
||||
|
||||
合同明细导入列按 Excel 的 Sheet3。表中标注无需填写的字段不填;系统自动生成字段由系统处理。
|
||||
|
||||
## 5. 接口和存储
|
||||
|
||||
### 5.1 接口方向
|
||||
|
||||
| 方向 | 数据 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| 订单中心 → 本方 | 销售总单及明细 | 校验后落表,供选品使用 |
|
||||
| 订单中心 → 本方 | 合同头及明细 | 校验后落表,供合同录入使用 |
|
||||
| 订单中心 → 本方 | 返利头及明细、计划员对应信息 | 校验后落表,供返利使用 |
|
||||
| 本方 → 订单中心 | 平台导出的订单 | 按正式接口格式推送,保存接收结果 |
|
||||
| 本方 → 订单中心 | 合同保存结果及对应链路节点 | 保存成功后调用更新接口,保存返回结果 |
|
||||
|
||||
接收成功表示数据校验通过并已保存。缺必填返回字段错误,订单中心补齐后重传;数据库保存失败返回失败。相同来源单据重复传入时,按双方约定的来源标识识别,避免重复执行。
|
||||
|
||||
### 5.2 需要保存的数据
|
||||
|
||||
表名为建议,开发时先核对已有 RPA Oracle 表,能复用的直接复用。正式列名和类型按接口报文及 Excel 确定。
|
||||
|
||||
| 逻辑表/现有记录 | 保存内容 |
|
||||
| --- | --- |
|
||||
| `RPA_JD_SALES_HEAD / LINE` | 来源销售单号、明细标识、批准文号、采购数量、销售价格及上游提供的商品信息 |
|
||||
| `RPA_JD_CONTRACT_HEAD / LINE` | 第 4 节合同字段、来源标识、订单中心链路关联 |
|
||||
| `RPA_JD_REBATE_HEAD / LINE` | 第 4 节返利字段、合同/协议关联及计划员对应信息 |
|
||||
| 现有 `job / job_node` | 流程类型、来源数据关联、处理状态、已加购/已跳过结果、合同号、失败原因及执行次数 |
|
||||
| 现有 `job_artifact / job_event` | 平台订单文件路径、平台、来源订单号、发送报文和接收结果;合同链路请求和返回结果 |
|
||||
|
||||
一个业务头关联它自己的明细,订单号和明细标识分别保存。原平台订单文件保存在共享目录。选品和订单推送不另建一套主数据匹配表。
|
||||
|
||||
业务数据放 RPA Oracle,执行状态复用 MySQL 状态中心。脚本通过状态中心 API 取数和回报,由后端统一读写数据库。
|
||||
|
||||
## 6. 对当前代码的改动
|
||||
|
||||
| 位置 | 本次需要改什么 |
|
||||
| --- | --- |
|
||||
| `state_center/app/main.py` | 增加销售/合同/返利接收及取数接口;必填在接收时校验;按流程返回待处理数据 |
|
||||
| `state_center/app/db.py` 及数据库脚本 | 接入 RPA 业务表;job 增加流程类型和来源关联,原供应商+日期键改为适合各流程的来源标识 |
|
||||
| `state_center/app/web_state_machine.py` | 按流程定义节点及完成条件 |
|
||||
| `rpa_yisaiqi/agent.py`、`runtime/shell.py`、`callback_client.py` | 支持四个独立脚本入口,取数、逐条处理、回报及本批退出 |
|
||||
| 选品与平台导出模块 | 接入平台查询/加购、订单导出、共享目录保存、报文组装及推送 |
|
||||
| `contract_draft.py`、`contract_create_node.py`、`contract_flow_steps.py` | 按第 4 节字段录入,保存成功后调用链路更新接口;原页面操作可复用部分继续使用 |
|
||||
| 返利执行模块 | 按第 4 节字段导入和提交;真实页面流程需补齐 |
|
||||
| 前端列表/详情 | 按四条流程展示状态、文件、结果及失败原因,复用现有页面 |
|
||||
|
||||
当前实现仍是一条固定主链;平台真实查询/导出能力需核对现有模块,不能把 mock 当作已完成。整体改动集中在数据接入、流程拆分和接口适配,合同页面操作有可复用基础。
|
||||
|
||||
具体执行节点和开发顺序见[技术实现说明](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-12-jieda-four-flow-impl.md)。
|
||||
|
||||
## 7. 验收
|
||||
|
||||
1. 接收数据缺必填当次失败;补齐后可重传,成功数据能被对应脚本读取。
|
||||
2. 选品合格加购、不合格跳过,处理完本批结束,不等待付款。
|
||||
3. 平台订单保存到对应共享文件夹,按接口格式推送并记录结果。
|
||||
4. 合同字段按 Excel 填写;保存成功后更新订单中心节点,更新失败不重复建合同。
|
||||
5. 返利按字段映射填写并记录提交结果。
|
||||
6. 四条脚本按各自时间和频率执行,失败可定位到具体记录,已完成数据不重复处理。
|
||||
|
||||
## 8. 待补充信息和可能出现的问题
|
||||
|
||||
以下是联调需要补齐的信息及实现注意点,不增加业务操作步骤。
|
||||
|
||||
| 项目 | 需要处理的内容 |
|
||||
| --- | --- |
|
||||
| 正式接口和文件 | 提供订单中心接收/链路更新格式、节点代码,平台导出样例、共享路径和四条排期 |
|
||||
| 5% 盈利 | 原材料未给出计算公式,需明确按什么基数计算 |
|
||||
| 合同/返利模板 | 确认实际导入模板、报文中每份合同及返利明细的范围;附件及审批要求以实际系统和业务确认为准,不先设计额外流程 |
|
||||
| 重复运行 | 同一脚本运行中再次点击不重复启动;重复传单或重新导出同一订单不重复办理 |
|
||||
| 接口超时 | 记录本次请求及错误,结果不明先核对;合同已保存时仅补链路更新 |
|
||||
| 平台/共享目录故障 | 查询失败和无合格商品分开记录;文件保存失败时记录错误,不继续推送 |
|
||||
| 当前合同代码 | 缺单价默认 1.00、同编码行定位和前 30 行扫描等行为需修正;接收缺价应直接返回错误 |
|
||||
| 旧流程影响 | 新四流程按 flow 区分,保留旧在途单的原节点和处理方式,避免改全局节点影响其他流程 |
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
> **历史版本,已合并替代。** 当前请使用[福建洁达采购 RPA 概要设计与详细设计 V3.0](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-13-jieda-rpa-integrated-design.md)。合同与返利一次接收、共用接口头明细,以新稿为准。下文保留历史内容,不作为当前实施依据。
|
||||
|
||||
# 福建洁达四流程技术实现说明
|
||||
|
||||
版本:V2.3 · 2026-09-12
|
||||
配套:[设计稿 V1.9](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-12-jieda-four-flow-design-codex.md)。本文为待实施说明,代码和数据库尚未修改。
|
||||
|
||||
## 1. 在现有结构上改
|
||||
|
||||
FastAPI 接收并校验订单中心数据,RPA Oracle 保存业务头明细,Oracle 继续管理 job、节点、日志和结果。四条脚本分别由业务点击或定时任务启动,不在一个 HTTP 请求里同步执行四条流程。
|
||||
|
||||
现有 `job` 增加流程类型和来源数据关联;按各流程配置节点。优先复用 `job_node / job_event / job_artifact`,不另建运行中心、对码中心或多套状态表。
|
||||
|
||||
## 2. 接收和取数
|
||||
|
||||
建议本方接收入口为 sales、contract-data、rebate-data,正式路径开发时统一命名。每个入口执行:
|
||||
|
||||
```text
|
||||
检查对应必填字段 → 不通过:返回失败和字段位置
|
||||
→ 通过:同事务保存业务头明细 → 返回成功
|
||||
```
|
||||
|
||||
字段来源和默认值按[设计稿第 4 节](/Users/yuuuu/Documents/AutoRpa/docs/superpowers/specs/2026-09-12-jieda-four-flow-design-codex.md)。系统生成和回显字段不作为上游必填。失败由订单中心修正重传,不建立等待补字段的业务作业。
|
||||
|
||||
取数接口按 flow 返回成功接收且未处理的数据。保存来源单号及明细标识,用于识别重复传入。状态中心登记或复用对应 job;领取时将其标记处理中,避免重复执行。脚本逐条回报,处理完本批退出。
|
||||
|
||||
## 3. 四条脚本
|
||||
|
||||
| flow | job 对象 | 执行节点 | 结束结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| PROCURE | 一条销售明细 | `PROCURE_ITEM_RUN` | 已加购或已跳过;执行失败记原因 |
|
||||
| ORDER_PUSH | 一次平台订单导出批次 | `ORDER_EXPORT_PUSH` | 文件已保存,应推送订单已接收;失败保留具体订单和原因 |
|
||||
| CONTRACT | 一份上游合同数据及其明细 | `CONTRACT_CREATE → CONTRACT_CHAIN_UPDATE` | 合同已保存,订单中心节点已更新 |
|
||||
| REBATE | 一份返利数据及其明细 | `REBATE_HEADER → REBATE_IMPORT → REBATE_SUBMIT` | 提交成功或失败 |
|
||||
|
||||
**选品脚本:** 按批准文号查询,依次判断自营仓、盈利、效期、库存和仓库顺序;合格加购物车,不合格记录跳过,继续下一条。同批准文号按一个规格处理,不增加属性匹配。复用节点结果记录已加购/已跳过,不等待人工付款。
|
||||
|
||||
**订单脚本:** 登录平台导出订单,保存到平台对应共享目录,按映射生成订单中心报文并推送。原文件、来源订单号、发送内容和回执保存在现有文件/日志记录中。再次处理依据已接收订单记录跳过成功项。
|
||||
|
||||
**合同脚本:** 读取成功接收的合同头明细,复用页面操作填写并保存。确认保存成功后回报合同号,进入链路更新节点。该节点通过订单中心接口适配器调用更新接口并回报结果。两步在同一条合同流程中执行,失败后恢复当前步骤;不增加独立后台调度任务。
|
||||
|
||||
**返利脚本:** 按合同号查询协议,填写总单、导入明细并提交,回报实际结果。原业务要求的未尽调合同及返利失败交业务处理,继续其他记录。
|
||||
|
||||
## 4. 数据如何保存
|
||||
|
||||
| 内容 | 保存位置 |
|
||||
| --- | --- |
|
||||
| 销售头明细 | RPA Oracle 销售表,字段按订单中心数据及选品条件 |
|
||||
| 合同头明细及链路关联 | RPA Oracle 合同表,字段按 Excel |
|
||||
| 返利头明细及计划员对应信息 | RPA Oracle 返利表,字段按 Excel |
|
||||
| 流程、处理状态、合同号、失败原因 | 现有 Oracle job/job_node |
|
||||
| 文件路径及推送/链路调用记录 | 现有 job_artifact/job_event |
|
||||
| 平台原始订单文件 | 对应平台共享文件夹 |
|
||||
|
||||
表名及列定义见设计稿第 5 节;开发前对照现场已有表和正式报文确定。头明细按来源关联保存,不用供应商+日期代替所有业务唯一标识,不自行合并合同。
|
||||
|
||||
处理状态复用现有待处理、处理中、成功、失败等状态。选品正常跳过属于处理完成,业务结果记录为“已跳过”。合同的“保存成功”和“链路更新成功”分别记录,更新失败不回退到重新建合同。
|
||||
|
||||
## 5. 代码改动
|
||||
|
||||
| 文件/模块 | 改动 |
|
||||
| --- | --- |
|
||||
| `state_center/app/main.py` | 接收校验、取数及结果接口;按 flow 创建和查询作业 |
|
||||
| `state_center/app/db.py`、数据库脚本 | RPA 业务表访问、头明细事务、job 的 flow 和来源字段;成功数据重复传入不重复建单 |
|
||||
| `state_center/app/web_state_machine.py` | 四条节点配置;旧作业继续用旧配置 |
|
||||
| `state_center/app/config.py` | 数据库、订单中心接口、平台共享路径和字段映射配置 |
|
||||
| 订单中心接口适配器 | 两项调用:平台订单推送、合同保存后的链路节点更新;路径及参数按对方协议 |
|
||||
| `rpa_yisaiqi/agent.py`、`runtime/shell.py`、`callback_client.py` | 四种脚本入口、取数循环、实际执行次数、结果回报、失败步骤恢复 |
|
||||
| 选品/平台导出模块 | 登录查询、采购筛选、加购、导出、共享文件保存及报文组装 |
|
||||
| `contract_draft.py`、`contract_create_node.py`、`contract_flow_steps.py` | 按 Excel 字段录入;确认保存结果,衔接链路接口;修正错误单价默认值及明细定位 |
|
||||
| 返利模块 | 补真实页面填写、导入、提交操作 |
|
||||
| 前端列表/详情 | 展示四种流程及已有状态,增加文件、接收结果和链路更新结果 |
|
||||
|
||||
现有固定主链、供应商+日期建单规则不能直接套用到全部新流程。商城实际登录/查询/导出模块需核对是否已有可用代码;现有 mock 代理和返利 runner 不能算真实执行已完成。
|
||||
|
||||
## 6. 开发顺序
|
||||
|
||||
1. 按正式报文和 Excel 确定字段,核对已有表,完成接收校验和落表。
|
||||
2. 增加 flow、取数、结果记录及四个脚本入口,复用现有状态中心。
|
||||
3. 完成平台导出、共享归档和订单推送。
|
||||
4. 完成合同字段适配,接上保存后的链路更新接口。
|
||||
5. 完成逐条选品加购及返利页面操作。
|
||||
6. 在实际执行机配置四条排期,按设计稿第 7 节联调验收。
|
||||
|
||||
## 7. 待确认及可能的问题
|
||||
|
||||
- 订单中心正式字段、链路节点代码、平台导出样例、共享路径及排期需提供;5% 盈利公式需明确。
|
||||
- 重复点击、重复传单、重复导出可能重复办理;使用现有状态和来源标识防重,同一流程不重入。
|
||||
- 保存或推送成功后回报失败,不能直接重做;先查看已有文件、回执或目标合同结果,再从失败步骤继续。
|
||||
- 合同链路更新失败只补接口调用;不额外增加后台维护任务。
|
||||
- 当前合同缺价填 1.00、同编码行定位及前 30 行扫描行为需要处理;具体风险见设计稿末尾。
|
||||
- 新四流程改动按 flow 区分,保留旧在途单节点,避免影响旧 OCR、对码和 M5 流程。
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# 福建洁达:接口数据入 RPA 库(小改)
|
||||
|
||||
> 日期:2026-09-12
|
||||
> 范围更正:流程仍是 RPA,状态机不动。改的是图上那些「从系统/表来」的数据——FastAPI 调接口,落到对方 RPA Oracle,带状态,再给 RPA 用。
|
||||
> 上一版把四步理解成换主链、砍 M5,范围过大,作废。
|
||||
|
||||
---
|
||||
|
||||
## 1. 改什么、不改什么
|
||||
|
||||
不改:
|
||||
|
||||
- 艺赛旗照旧点 1 药城 / 药帮忙 / 合同系统 / 返利中心
|
||||
- 状态中心 MySQL 状态机、`start/complete/fail`、待办、幂等
|
||||
- 人工闸门:选品复核付款、未尽调、返利失败
|
||||
|
||||
改:
|
||||
|
||||
- 原先靠导 Excel / 页面上抄的**主数据、对照表、总单**,改为接口拉取
|
||||
- FastAPI 按四步拉数,写入 `RPADBTEST`(`192.168.12.244/rpa`,用户 `rpainit`)
|
||||
- 每张业务表带处理状态,失败可重拉、可跳过、可给 RPA 重跑,不把半截 JSON 丢给机器人
|
||||
|
||||
```
|
||||
对方接口 → FastAPI(四步同步) → Oracle 暂存表(带 STATUS)
|
||||
↓
|
||||
RPA 读 READY 行,点页面
|
||||
↓
|
||||
回写 SUCCESS/FAILED
|
||||
↓
|
||||
状态中心节点 complete/fail
|
||||
```
|
||||
|
||||
两套状态分工:
|
||||
|
||||
| 库 | 记什么 | 不记什么 |
|
||||
| --- | --- | --- |
|
||||
| Oracle `RPADBTEST` | 这批数据到了没有、RPA 用过没有 | 整单流程走到哪一步 |
|
||||
| MySQL `rpa_center` | job / 节点 / 待办 | 接口原始报文(必要时只存镜像) |
|
||||
|
||||
不要在 Oracle 再做一套 13 节点状态机。
|
||||
|
||||
---
|
||||
|
||||
## 2. 四步对应图上哪些数据
|
||||
|
||||
图上没有写「接口」二字。按「这格是取数/对照表,不是点按钮」切开。点按钮的仍归 RPA。
|
||||
|
||||
| 步 | FastAPI | 图上取数 | RPA 随后用它做什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `POST /api/jieda/sales/sync` | 订单中心「销售总单」 | 按行里的批准文号去 1 药城 / 药帮忙搜品、筛选、加购 |
|
||||
| 2 | `POST /api/jieda/match/sync` | 手工单 DocAgent 结果;提取字段;对码商家信息;回写订单中心对码表的入参 | 合同里填「客户名称」;对码表有数才能往下 |
|
||||
| 3 | `POST /api/jieda/contract-refs/sync` | 客户是否已尽调;《客户类型》预付/账期;合同明细导入用的货品行 | 选格式/非格式、填模板、导明细、回填合同金额 |
|
||||
| 4 | `POST /api/jieda/rebate-refs/sync` | 采购总单;计划员↔采购员对照;返利细单模板行 | 按合同号查总单、填必填项、导细单 |
|
||||
|
||||
第 1 步失败:RPA 不要上平台乱搜。
|
||||
第 3 步尽调=否:RPA 不填合同,走现有人工待办(邮件通知采购员)。
|
||||
第 4 步对照表缺失:RPA 不猜计划员,节点 fail。
|
||||
|
||||
---
|
||||
|
||||
## 3. Oracle 表(建议新建,前缀 `RPA_JD_`)
|
||||
|
||||
连接(只放环境变量,不进仓库):
|
||||
|
||||
```
|
||||
RPA_ORACLE_HOST=192.168.12.244
|
||||
RPA_ORACLE_PORT=1521
|
||||
RPA_ORACLE_SERVICE=rpa
|
||||
RPA_ORACLE_USER=rpainit
|
||||
RPA_ORACLE_PASSWORD=...
|
||||
```
|
||||
|
||||
本机目前 TCP `192.168.12.244:1521` 不通,建表要在能进这台库的机器上执行。先 `SELECT table_name FROM user_tables`,有现成表就映射,不要重复造。
|
||||
|
||||
### 3.1 每张业务表共用状态列
|
||||
|
||||
```
|
||||
JOB_ID VARCHAR2(64) -- 对齐状态中心 job_id
|
||||
BATCH_ID VARCHAR2(64) -- 一次 sync
|
||||
STATUS VARCHAR2(16) -- NEW / READY / RUNNING / SUCCESS / FAILED / SKIPPED
|
||||
ERR_CODE VARCHAR2(64)
|
||||
ERR_MSG VARCHAR2(500)
|
||||
ATTEMPT_NO NUMBER(4)
|
||||
RAW_JSON CLOB -- 接口原文,排障用
|
||||
SYNCED_AT DATE
|
||||
UPDATED_AT DATE
|
||||
IDEMPOTENCY_KEY VARCHAR2(128)
|
||||
```
|
||||
|
||||
行状态只表示**这条数据**:
|
||||
|
||||
- FastAPI 写入成功 → `READY`
|
||||
- 接口失败、校验失败 → `FAILED`(RPA 不抢)
|
||||
- RPA 开始用 → `RUNNING`
|
||||
- 页面做完 → `SUCCESS`
|
||||
- 业务跳过(如未尽调)→ `SKIPPED`
|
||||
|
||||
同一 `JOB_ID + IDEMPOTENCY_KEY` 再 sync:已 `SUCCESS` 的不覆盖;`FAILED` 允许重拉。
|
||||
|
||||
### 3.2 四步表
|
||||
|
||||
**第 1 步 销售总单**
|
||||
|
||||
- `RPA_JD_SALES_HEAD`:销售总单号、订单日期、客户/商家、金额、来源
|
||||
- `RPA_JD_SALES_LINE`:批准文号、品名、规格、厂家、数量、单价、平台商品编码
|
||||
|
||||
**第 2 步 对码 / 手工单**
|
||||
|
||||
- `RPA_JD_MATCH_PARTY`:对码商家信息(合同「客户名称 / 客户编码」从这里取)
|
||||
- `RPA_JD_MATCH_LINE`:对码后货品 ID/编码、是否命中;也承接 DocAgent 抽出的手工单行
|
||||
- 回写订单中心对码表:另调对方写入接口,本地留 `PUSH_STATUS`
|
||||
|
||||
**第 3 步 合同辅助**
|
||||
|
||||
- `RPA_JD_CUSTOMER_REF`:客户编码、是否已尽调、客户类型(预付/账期)、应用哪套合同模板
|
||||
- `RPA_JD_CONTRACT_LINE`:按合同明细导入模板整理好的货品行(RPA 只负责上传,不在页面上拼 Excel)
|
||||
|
||||
**第 4 步 返利辅助**
|
||||
|
||||
- `RPA_JD_PLANNER_MAP`:计划员 ↔ 采购员(可全量同步,不必每单)
|
||||
- `RPA_JD_REBATE_HEAD`:采购总单上要填进返利总单的字段
|
||||
- `RPA_JD_REBATE_LINE`:细单导入行
|
||||
|
||||
列级 DDL 等对方接口字段确定后再锁。现在只锁表职责和状态机,避免先猜 40 个列名再推翻。
|
||||
|
||||
---
|
||||
|
||||
## 4. FastAPI(状态中心里加一层,不新起服务)
|
||||
|
||||
四个同步入口,鉴权仍用 `X-Agent-Token`。
|
||||
|
||||
```
|
||||
POST /api/jieda/sales/sync
|
||||
POST /api/jieda/match/sync
|
||||
POST /api/jieda/contract-refs/sync
|
||||
POST /api/jieda/rebate-refs/sync
|
||||
```
|
||||
|
||||
入参最小集:`job_id`、`order_date`、可选 `idempotencyKey`。
|
||||
出参:`ok`、`batch_id`、`ready_count`、`failed_count`、`status`。
|
||||
|
||||
每个 sync:
|
||||
|
||||
1. 调对方接口(没有正式文档前走 fixture,契约用 Pydantic 写死)
|
||||
2. 校验必填(第 1 步无批准文号直接 `FAILED`)
|
||||
3. upsert 到对应 Oracle 表,`STATUS=READY`
|
||||
4. 写状态中心事件;该节点仍由 RPA `start/complete` 推进,**sync 成功不等于节点成功**
|
||||
|
||||
查询给 RPA:
|
||||
|
||||
```
|
||||
GET /api/jieda/{job_id}/sales
|
||||
GET /api/jieda/{job_id}/contract-refs
|
||||
...
|
||||
```
|
||||
|
||||
RPA 也可以直连 Oracle 读 `READY` 行。推荐先走 FastAPI 读,避免脚本里写死账号;直连作为艺赛旗侧备选。
|
||||
|
||||
RPA 回写状态:
|
||||
|
||||
```
|
||||
POST /api/jieda/{job_id}/rows/{table}/{id}/status
|
||||
body: { "status": "RUNNING|SUCCESS|FAILED", "errMsg": "..." }
|
||||
```
|
||||
|
||||
内部更新 Oracle 行状态。节点级成功仍走原来的 `/nodes/{node}/complete`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 和现有节点怎么接(不大改)
|
||||
|
||||
不改 `NODE_ORDER`。四步 sync 挂在现有节点**开始前**:
|
||||
|
||||
| 现有节点 | 开始前多做的 sync | RPA 行为变化 |
|
||||
| --- | --- | --- |
|
||||
| `ORDER_FETCH` | `sales/sync` | 不再以导 Excel 为唯一数据源;以 Oracle 总单/明细为准去搜品 |
|
||||
| `OCR_EXTRACT` / 对码前 | `match/sync` | 线下单走 DocAgent 结果表;商家名称从表读 |
|
||||
| `CONTRACT_CREATE` | `contract-refs/sync` | 尽调、客户类型、明细行从表读;未尽调不点填报 |
|
||||
| `REBATE_HEADER` / `REBATE_IMPORT` | `rebate-refs/sync` | 总单必填项、计划员、细单行从表读 |
|
||||
|
||||
`PRODUCT_MATCH`、`CONTRACT_APPROVE_WAIT`、`M5_*` 先不动。新图没画 M5,不等于本期删 M5。
|
||||
|
||||
艺赛旗每个泳道开头加一个「取数」代码块:调对应 `sync`(或确认 scheduler 已 sync)→ 读 `READY` → 再点页面。失败把行打成 `FAILED` 并调节点 `fail`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码改动面(小)
|
||||
|
||||
| 位置 | 做什么 |
|
||||
| --- | --- |
|
||||
| `state_center/app/config.py` | 增加 `RPA_ORACLE_*`,密码只走环境变量 |
|
||||
| `state_center/app/rpa_oracle.py` 新建 | 连接、upsert、按 job 查询、改行状态 |
|
||||
| `state_center/app/jieda_sync.py` 新建 | 四个 sync 的编排 + Pydantic |
|
||||
| `state_center/app/adapters/` 新建 | 订单中心 / 尽调 / 客户类型 / 计划员 的 HTTP client,先 fixture |
|
||||
| `state_center/app/main.py` | 挂 4 个 sync + 查询 + 行状态 |
|
||||
| `state_center/sql/rpa_jd_oracle.sql` 新建 | 上面那些表的 DDL |
|
||||
| `rpa_yisaiqi` | 合同/选品/返利流程开头读 FastAPI 或 Oracle,不改状态机顺序 |
|
||||
| 测试 | sync 幂等、失败不覆盖 SUCCESS、无批准文号 → FAILED、未尽调 → SKIPPED |
|
||||
|
||||
密码和 TNS 不进 git。`connection.local.json` 已有先例。
|
||||
|
||||
---
|
||||
|
||||
## 7. 落地顺序
|
||||
|
||||
1. 能连 `192.168.12.244` 的机器上看 `rpainit` 已有表,避免重名。
|
||||
2. 建第 1 步两张表 + `sales/sync` fixture,RPA 能把批准文号读出来。
|
||||
3. 第 3 步尽调 / 客户类型(合同填报刚需)。
|
||||
4. 第 4 步计划员对照 + 采购总单。
|
||||
5. 第 2 步对码回写和 DocAgent,等对方对码接口。
|
||||
|
||||
没有接口文档时,每步用一份 JSON fixture 冒充对方响应,表结构和 FastAPI 先定。
|
||||
|
||||
---
|
||||
|
||||
## 8. 还缺的输入
|
||||
|
||||
- 四个接口的 URL、鉴权、字段(尤其销售总单、尽调、客户类型、计划员对照)
|
||||
- `rpainit` 是否已有同类暂存表
|
||||
- RPA 读数:走我们 FastAPI,还是艺赛旗直连 Oracle
|
||||
|
||||
这三项不齐也能先建表和 fixture;接真接口时只换 adapter。
|
||||
|
|
@ -0,0 +1,683 @@
|
|||
# 福建洁达采购 RPA 详细设计
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 版本 | V3.1 |
|
||||
| 日期 | 2026-09-13 |
|
||||
| 配套概要设计 | [《福建洁达采购 RPA 概要设计》](./2026-09-13-jieda-rpa-overview-design.md) |
|
||||
| 状态 | 设计评审稿;未实施代码或建表 |
|
||||
| 适用范围 | 接口接收、四条流程执行、状态记录、运维与异常恢复 |
|
||||
|
||||
本文件承接概要设计,规定可供开发、测试和运维执行的表、字段、状态、接口和处理规则。外部正式字段名、节点代码和现场 Oracle 表结构以第 9 章未决事项为准。
|
||||
|
||||
**本期范围收敛:**只实现订单接收、平台订单推送、选品、合同、返利四条流程及基本状态记录。版本冲突、内容哈希、执行令牌、文件证据表、复杂事件分类和自动化核验接口不作为本期新增必做项;现场已有能力可以复用,确需新增时另行确认。
|
||||
|
||||
## 4. 详细设计:数据模型
|
||||
|
||||
### 4.1 实体关系和存储位置
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
IF[接口接收记录<br/>RPA_JD_IF_REQ]
|
||||
SH[销售接口头<br/>RPA_JD_SALES_H]
|
||||
SL[销售接口明细<br/>RPA_JD_SALES_L]
|
||||
OH[合同返利共用头<br/>RPA_JD_ORDER_H]
|
||||
OL[合同返利共用明细<br/>RPA_JD_ORDER_L]
|
||||
J[流程作业<br/>JOB]
|
||||
N[作业节点<br/>JOB_NODE]
|
||||
E[操作事件<br/>JOB_EVENT]
|
||||
A[文件证据<br/>JOB_ARTIFACT]
|
||||
P[订单推送台账<br/>JD_ORDER_PUSH]
|
||||
IF --> SH
|
||||
IF --> OH
|
||||
SH --> SL
|
||||
OH --> OL
|
||||
SL --> J
|
||||
OH --> J
|
||||
J --> N
|
||||
J --> E
|
||||
J --> A
|
||||
J --> P
|
||||
```
|
||||
|
||||
*图中括号内为数据库表名,前面的中文为业务含义。*
|
||||
|
||||
本节所有实体均存放在同一套 **Oracle 数据库**,不再区分接口库、状态库或 MySQL。实体按用途分为三组:
|
||||
|
||||
| 分组 | 表 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| 接口输入表 | `RPA_JD_IF_REQ`、`RPA_JD_SALES_H/L`、`RPA_JD_ORDER_H/L` | 保存订单中心传入的原始报文、销售数据及合同/返利共用数据 |
|
||||
| 流程业务表 | `JOB`、`JOB_NODE`、`JOB_EVENT`、`JOB_ARTIFACT` | 保存四条流程的作业、节点执行、操作记录和文件证据 |
|
||||
| 订单推送表 | `JD_ORDER_PUSH` | 保存平台订单导出后推送订单中心的请求、回执和推送状态 |
|
||||
|
||||
接口输入数据只保存一份。合同和返利通过相同的 `INPUT_ID`、`INPUT_VERSION` 关联;两条流程分别在 `JOB` 中记录自己的状态,不复制两套合同/返利输入表。已有表能承载上述用途时优先复用,只有确实缺少的表或字段才在 Oracle 中新增。图中的关联由主键、外键或业务关联字段实现,具体以现场 Oracle 表结构核对结果为准。
|
||||
|
||||
接口表内的合同/返利状态不各写一份。运维总览按 INPUT_ID 汇总两份 job 与其节点,展示三个独立标志:合同保存、链路更新、返利办理。读写来源见第 5 章。
|
||||
|
||||
### 4.2 接收记录 `RPA_JD_IF_REQ`
|
||||
|
||||
一行是一回接收尝试;错误请求也能查到,不能依赖先有 job 才记录。
|
||||
|
||||
| 字段 | 建议类型 | 含义与约束 |
|
||||
| --- | --- | --- |
|
||||
| REQUEST_LOG_ID | VARCHAR2(64) PK | 本方生成,返回给上游和运维定位 |
|
||||
| TRACE_ID | VARCHAR2(64) | 本次请求关联应用日志 |
|
||||
| SOURCE_SYSTEM / MESSAGE_ID | VARCHAR2(32) / VARCHAR2(128) | 调用方及其请求标识;MESSAGE_ID 格式由对方提供 |
|
||||
| DATA_TYPE | VARCHAR2(24) | SALES 或 ORDER_COMMON;没有独立 REBATE 接收类型 |
|
||||
| BODY_HASH | VARCHAR2(64) | 规范化业务报文哈希,识别同请求内容是否一致 |
|
||||
| RECEIVE_STATUS | VARCHAR2(16) | ACCEPTED、REJECTED、DUPLICATE、CONFLICT |
|
||||
| ORIGINAL_REQ_ID | VARCHAR2(64),可空 | 重复请求关联首次成功记录 |
|
||||
| RAW_BODY / RESPONSE_BODY | CLOB | 实际业务报文和返回内容,不记录鉴权口令 |
|
||||
| ERROR_CODE / ERROR_DETAIL | VARCHAR2(64) / CLOB | 字段路径、明细标识、错误说明 |
|
||||
| RECEIVED_AT / RESPONDED_AT | TIMESTAMP | 收到及生成返回结果的时间 |
|
||||
| REGISTER_STATUS | VARCHAR2(8),可空 | 成功记录的作业登记标志:PENDING、DONE、FAILED;非成功记录为空 |
|
||||
| REGISTER_ERROR / REGISTERED_AT | VARCHAR2(500) / TIMESTAMP | 作业登记失败原因及最终登记时间 |
|
||||
|
||||
RECEIVE_STATUS 表示接收事实,REGISTER_STATUS 仅表示是否已登记作业,均不代表合同或返利完成。成功接收同事务保存接口头明细并置 REGISTER_STATUS=PENDING;脚本取数前补登记,完成后更新 DONE。登记失败在下一次脚本取数重试,无需上游重传。
|
||||
|
||||
约束:对 ACCEPTED 记录建立 `(SOURCE_SYSTEM, MESSAGE_ID)` 条件唯一索引;Oracle 可用 CASE 表达式索引实现。失败/重复尝试保留独立行,不占用成功消息键。索引还包括 `(DATA_TYPE, REGISTER_STATUS, RECEIVED_AT)`、`TRACE_ID`、`(SOURCE_SYSTEM, MESSAGE_ID)`。
|
||||
|
||||
### 4.3 接口头公共字段
|
||||
|
||||
销售头和共用订单头均使用以下控制列。
|
||||
|
||||
| 字段 | 建议类型 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| INPUT_ID | VARCHAR2(64) PK | 本方输入身份,跨流程统一关联 |
|
||||
| REQUEST_LOG_ID | VARCHAR2(64) FK | 首次成功接收记录 |
|
||||
| SOURCE_SYSTEM / SOURCE_DOC_ID | VARCHAR2(32) / VARCHAR2(128) | 上游稳定业务标识;与展示单号分开 |
|
||||
| SOURCE_DOC_NO | VARCHAR2(128) | 业务查询展示用单号 |
|
||||
| INPUT_VERSION | VARCHAR2(64) | 上游版本;未提供时首版用固定 1,变更走第 6.2 节冲突规则 |
|
||||
| DATA_HASH | VARCHAR2(64) | 本对象完整头明细内容哈希 |
|
||||
| DATA_STATUS | VARCHAR2(16) | READY:成功接收的完整输入;本期不另设待补字段状态 |
|
||||
| CREATED_AT | TIMESTAMP | 成功保存时间 |
|
||||
|
||||
头表唯一键为 `(SOURCE_SYSTEM, SOURCE_DOC_ID, INPUT_VERSION)`。明细以 `(INPUT_ID, SOURCE_LINE_ID)` 唯一,关联本头,不以货品编码唯一。成功输入不由 RPA 改写,处理结果记录在 Oracle 业务表中。
|
||||
|
||||
### 4.4 销售接口头明细 `RPA_JD_SALES_H / L`
|
||||
|
||||
| 表 | 业务字段 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| SALES_H | 第 4.3 节公共字段、BUSINESS_DATE | 一份销售总单 |
|
||||
| SALES_L | LINE_ID、INPUT_ID、SOURCE_LINE_ID、APPROVAL_NO、PURCHASE_QTY、SALES_PRICE | 逐条查询所需批准文号、需求采购数量、销售价格 |
|
||||
| SALES_L 可选描述 | GOODS_NAME、SPEC、MANUFACTURER、UNIT | 按上游原值保留供查看,不作为新增属性匹配门槛 |
|
||||
|
||||
LINE_ID 建议 VARCHAR2(64),数量 NUMBER(18,4),价格 NUMBER(18,6),标识/文号 VARCHAR2(128),描述 VARCHAR2(256)。最终长度和精度按真实接口及目标系统核对。
|
||||
|
||||
### 4.5 合同/返利共用接口头明细 `RPA_JD_ORDER_H / L`
|
||||
|
||||
一份头代表上游明确给定的一组合同及对应返利数据,范围由报文的 SOURCE_DOC_ID 和明细集合确定。首版不按供应商+日期自行合单。若实际存在一组数据对应多份合同/返利,需要上游给出组标识,见第 12 章。
|
||||
|
||||
| 位置 | 建议字段 | 含义/使用流程 |
|
||||
| --- | --- | --- |
|
||||
| ORDER_H | 第 4.3 节公共字段 | 合同和返利共用输入标识 |
|
||||
| ORDER_H | CUSTOMER_NAME、CUSTOMER_TYPE、CREDIT_DAYS | 合同客户名称、预付/账期类型、账期天数 |
|
||||
| ORDER_H | CONTRACT_SIGN_DATE、CONTRACT_START_DATE、CONTRACT_END_DATE | 三个独立合同日期 |
|
||||
| ORDER_H | SUPPLIER_CODE、REBATE_UNIT_CODE、PLANNER、PURCHASER | 返利供应商、返利单位、计划员及采购员对应信息 |
|
||||
| ORDER_H | CHAIN_REF | 订单中心链路/源业务关联;精确结构按正式接口 |
|
||||
| ORDER_L | LINE_ID、INPUT_ID、SOURCE_LINE_ID、ORDER_NO、SOURCE_DETAIL_ID | 本方行 ID、来源行、订单号、货源细单号;订单号与货源细单号分列 |
|
||||
| ORDER_L | GOODS_CODE、GOODS_NAME、REG_CERT_NO、SPEC、UNIT | 标准货品编码/名称、注册证号、规格、单位 |
|
||||
| ORDER_L | PURCHASE_QTY、PURCHASE_UNIT_PRICE | 采购数量和单价;用于合同明细 |
|
||||
| ORDER_L | REBATE_QTY、REBATE_TAX_PRICE | 返利数量和含税协议单价;用于返利明细 |
|
||||
| ORDER_L | PAYMENT_CONDITION,可空 | 实际合同模板需要时从同一报文提供 |
|
||||
|
||||
同一次报文中数量如只有一个来源字段且供两流程共用,映射层同时引用它;不因此要求上游重复传两个数量。采购单价与含税协议单价按 Excel 的不同含义分别映射,未经接口确认不互相代替。
|
||||
|
||||
建议数量 NUMBER(18,4)、价格 NUMBER(18,6)、金额 NUMBER(18,2)、日期 DATE;名称 VARCHAR2(256)、编码 VARCHAR2(128)、类型 VARCHAR2(16)、账期 NUMBER(6)、CHAIN_REF CLOB 或按正式关联键拆列。固定默认值不强迫订单中心传入,在节点执行输入中生成并记录所用模板/映射版本。
|
||||
|
||||
**此处没有 `RPA_JD_CONTRACT_HEAD / RPA_JD_REBATE_HEAD` 两套输入表。两流程取数的 INPUT_ID 必须相同。**
|
||||
|
||||
### 4.6 业务作业与节点记录
|
||||
|
||||
| 表/字段 | 现有或新增 | 详细用途 |
|
||||
| --- | --- | --- |
|
||||
| job.flow_code / flow_version | 新增 VARCHAR(24) / VARCHAR(16) | 旧作业归原主链,新作业区分四条流程 |
|
||||
| job.input_id / input_version | 新增 VARCHAR(64) | 指向 Oracle 成功输入;ORDER_PUSH 指向本方导出批次 ID |
|
||||
| job.source_line_id | 新增 VARCHAR(128),头级作业用空串 | PROCURE 对应一条销售明细;合同/返利对应头 |
|
||||
| job.business_key | 复用 | 改为上述来源关联的展示/定位键;防重以组合唯一约束为准 |
|
||||
| job.status / current_node_code | 复用 | 当前执行状态和恢复节点 |
|
||||
| job.contract_system_no / rebate_order_no | 复用 | 合同 job 保存合同号,返利 job 保存返利总单/协议编号 |
|
||||
| job.next_check_at | 复用 | 可重试失败或外部等待的下次处理时间 |
|
||||
| job.execution_token / execution_host | 本期不新增 | 如现有状态中心已有执行占用机制则继续复用,否则按单实例运行约束处理 |
|
||||
| job_node.attempt_no、status、output_json | 复用 | 每次尝试独立记录;输入引用、业务结果和外部编号 |
|
||||
| job_node.started_at / ended_at / duration_ms | 复用 | 操作开始结束及耗时 |
|
||||
| job_node.error_code / error_summary | 复用 | 可筛选错误分类和具体原因 |
|
||||
| job_node.executor_run_id / callback_idempotency_key | 复用 | 一次脚本运行 ID、稳定回报键,重发回报不重复推进 |
|
||||
|
||||
本期按 `(flow_code, input_id, source_line_id)` 识别同一流程作业;已有系统若必须保留版本字段,可作为可选扩展,不因规则升级自动创建第二份作业。
|
||||
|
||||
共用数据成功接收后登记两份 job:CONTRACT 初始 PENDING,REBATE 初始 WAITING_EXTERNAL,原因为 WAIT_CONTRACT。同一 Oracle 输入即便登记调用重复,也只建立这一对作业。一次接收多组业务对象则逐头建立,不共享一个总完成标志。
|
||||
|
||||
旧列兼容迁移:保留 job_id、business_key、source_type、current_node_code、status、created_by 非空;supplier_name 和 order_date 改为允许空值,新来源数据有值时填入。旧入口继续校验其原必填,不给新作业补造供应商或日期。business_key 由新唯一键各项组合生成可读/哈希值;created_by 记录业务启动人或计划任务账号。
|
||||
|
||||
| flow | source_type | supplier_name / order_date | 首节点 |
|
||||
| --- | --- | --- | --- |
|
||||
| PROCURE | 本方定义 ORDER_CENTER | 供应商为空;日期取销售业务日(若提供) | PROCURE_ITEM_RUN |
|
||||
| ORDER_PUSH | 明确的平台配置代码 | 文件批次可含多供应商,供应商为空;日期不强制单日 | ORDER_EXPORT_FILE |
|
||||
| CONTRACT / REBATE | ORDER_CENTER | 接口明确提供的展示供应商及业务日期;合同三个日期仍取接口独立字段 | CONTRACT_CREATE / REBATE_HEADER |
|
||||
|
||||
source_type 上述取值是本方分类建议;药帮忙使用单独配置代码,不映射成旧 XYY。若任一新增标识超出现有列长度,迁移时扩列。旧 job 赋旧 flow/version 及唯一 input_id,新组合唯一键对全部历史行先回填再建立。
|
||||
|
||||
### 4.7 逐订单推送记录 `JD_ORDER_PUSH`(按现有表复用,缺少时再新增)
|
||||
|
||||
文件/操作日志不能替代按订单查询的处理标志,因此该表只保存推送控制与结果,不复制完整平台业务表。
|
||||
|
||||
| 字段 | 建议类型 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT PK | 推送记录 ID |
|
||||
| platform / account_key / source_order_id | VARCHAR(32) / VARCHAR(64) / VARCHAR(128) | 来源订单唯一键,不按文件名去重 |
|
||||
| source_order_no / job_id / artifact_id | VARCHAR(128) / VARCHAR(64) / BIGINT | 展示单号、首次导出 job、原始文件关联 |
|
||||
| content_hash / request_key | VARCHAR(64) / VARCHAR(128) | 订单内容识别和本次固定请求标识 |
|
||||
| push_status | VARCHAR(16) | PENDING、SENDING、ACCEPTED、REJECTED、UNKNOWN |
|
||||
| request_body / response_body | JSON | 本次实际结构化发送内容、返回内容或所属批次引用 |
|
||||
| receipt_no / error_code / error_message | VARCHAR(128) / VARCHAR(64) / VARCHAR(500) | 回执及失败原因 |
|
||||
| attempt_count / next_retry_at | INT / DATETIME | 发送次数及重试时间 |
|
||||
| created_at / last_sent_at / accepted_at | DATETIME | 关键时点 |
|
||||
|
||||
唯一键 `(platform, account_key, source_order_id)`;相同订单相同内容复用结果,内容变化不能覆盖旧成功记录,记录冲突交运维核对。每次发送详细历史写 job_event,最近状态保存在此表。多个文件含同一订单时,在各导出 job 的日志中记录对应 artifact_id 与台账 id。
|
||||
|
||||
接口若支持批量发送,固定请求键绑定整份实际请求体;同键重发内容不变。逐单成功的回执更新对应台账;只发失败子集时生成新请求键并保留与原批次关联。没有逐单回执时按对方整批接收语义记录,不凭本方猜部分成功。
|
||||
|
||||
## 5. 详细设计:标志字段与取数条件
|
||||
|
||||
### 5.1 状态来源
|
||||
|
||||
| 业务标志 | 保存/计算位置 | 何时改变 |
|
||||
| --- | --- | --- |
|
||||
| 接收是否成功 | IF_REQ.RECEIVE_STATUS | 当前接收请求完成时 |
|
||||
| 作业是否登记 | IF_REQ.REGISTER_STATUS | 在 Oracle 中幂等建立本次所需 job 后 |
|
||||
| 选品处理结果 | PROCURE job_node.output_json.result | 加购或正常跳过回报时:ADDED / SKIPPED |
|
||||
| 合同保存状态 | CONTRACT_CREATE 节点状态及 result | 开始、保存成功、失败或结果未知回报 |
|
||||
| 链路更新状态 | CONTRACT_CHAIN_UPDATE 节点状态及 result | 合同保存后调用订单中心,单独记录结果 |
|
||||
| 返利状态 | REBATE job.status / current_node_code | 等合同、执行、失败、提交成功 |
|
||||
| 平台订单接收状态 | JD_ORDER_PUSH.push_status | 每次发送和回执记录 |
|
||||
|
||||
运维接口返回 `contract_save_status / chain_update_status / rebate_status` 三列,是以上权威记录的汇总字段,**不再写回接口表形成第二套状态**。当前节点按 job 指针及该节点最新 attempt 取值,历史成功节点保留,不用整个合同 job.status 代替保存状态。
|
||||
|
||||
### 5.2 状态转换
|
||||
|
||||
| job 状态 | 含义 | 后续动作 |
|
||||
| --- | --- | --- |
|
||||
| PENDING | 已登记,等待执行 | 对应脚本领取 |
|
||||
| RUNNING | 已被领取且正在执行 | 接收匹配 execution_token 的回报 |
|
||||
| SUCCESS | 本流程应做步骤完成 | 常规取数不再领取 |
|
||||
| FAILED_RETRYABLE | 明确失败,可重试当前步骤 | 到 next_check_at 后或运维重试,新增 attempt |
|
||||
| WAITING_EXTERNAL | 前置尚未完成或结果待核对 | 按 wait_reason 处理,不能一律自动重做 |
|
||||
| WAITING_USER / FAILED_MANUAL | 业务或运维需介入 | 在原待办上处理、核对并记录动作 |
|
||||
| CANCELLED | 明确停止办理 | 保留原数据和历史 |
|
||||
|
||||
wait_reason 建议保存在 job 新增 VARCHAR(32) 字段:WAIT_CONTRACT、PROTOCOL_NOT_READY、RESULT_UNKNOWN。前两种下次检查条件;RESULT_UNKNOWN 需要核对外部结果,确认失败才可重试。
|
||||
|
||||
节点复用 PENDING/RUNNING/SUCCESS/FAILED,未知结果用节点 `WAITING_EXTERNAL` 并在 output_json 记 `result=UNKNOWN`;需要新增相应前后端状态支持。未尽调转人工保留待办,不能把“停止自动办理”显示为“合同已保存”。
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> PENDING
|
||||
PENDING --> RUNNING: 领取
|
||||
RUNNING --> SUCCESS: 完成
|
||||
RUNNING --> FAILED_RETRYABLE: 明确可重试失败
|
||||
RUNNING --> WAITING_EXTERNAL: 前置等待或结果未知
|
||||
RUNNING --> WAITING_USER: 业务处理
|
||||
FAILED_RETRYABLE --> PENDING: 重试当前步骤
|
||||
WAITING_EXTERNAL --> PENDING: 前置满足或核对未成功
|
||||
WAITING_EXTERNAL --> SUCCESS: 核对本流程已完成
|
||||
WAITING_USER --> PENDING: 处理后恢复未完成步骤
|
||||
PENDING --> CANCELLED: 停止办理
|
||||
```
|
||||
|
||||
*中文对照:`PENDING`=待执行,`RUNNING`=执行中,`SUCCESS`=成功,`FAILED_RETRYABLE`=可重试失败,`WAITING_EXTERNAL`=等待外部结果,`WAITING_USER`=等待人工处理,`CANCELLED`=已取消。*
|
||||
|
||||
上图为通用 job 状态。合同保存结果核对成功时若链路尚未更新,实际转移到链路节点 PENDING,不能直接标整个合同 SUCCESS。
|
||||
|
||||
### 5.3 领取规则
|
||||
|
||||
| flow | 读取范围及条件 | 成功/失败记录 |
|
||||
| --- | --- | --- |
|
||||
| PROCURE | SALES 数据 READY;分别按 1 号药城、药帮忙的平台配置领取;行 job=PENDING,或到期可重试 | 两个平台使用同一套采购条件;合格 ADDED、不合格 SKIPPED 均结束行 job;错误单独失败 |
|
||||
| ORDER_PUSH | 对应平台导出任务及历史未完成台账;ACCEPTED 不重发 | 归档文件及每单 push_status |
|
||||
| CONTRACT | ORDER_COMMON 数据 READY;合同 job 待执行,按 current_node 恢复 | CREATE 成功保存合同号;CHAIN 失败只重试 CHAIN |
|
||||
| REBATE | 同 INPUT_ID/版本的 CREATE 节点确认 SUCCESS,合同号非空;返利待执行或到期重试 | 从共用接口表读取返利字段;读取合同 job 合同号;提交后 SUCCESS |
|
||||
|
||||
新录入与重试均覆盖历史未完成记录,不只筛选当天。创建返利 job 时可处于 WAIT_CONTRACT,合同失败不改它的接口输入;合同成功后,返利下一次扫描将其转 PENDING。
|
||||
|
||||
领取使用现有状态中心的领取机制;同一条记录同一时刻只允许一个脚本处理。脚本中断后由运维确认并恢复,不自动启动第二个执行器。
|
||||
|
||||
### 5.4 节点定义与订单导出生命周期
|
||||
|
||||
| flow | 节点链 | 正常结束条件 |
|
||||
| --- | --- | --- |
|
||||
| PROCURE | PROCURE_ITEM_RUN | 本行 ADDED 或 SKIPPED |
|
||||
| ORDER_PUSH | ORDER_EXPORT_FILE → ORDER_CENTER_PUSH | 文件完整归档后,解析及推送该批应发送订单,均确认接收 |
|
||||
| CONTRACT | CONTRACT_CREATE → CONTRACT_CHAIN_UPDATE | 保存及链路更新分别成功;未尽调为当前节点人工待办,不另建供数流程 |
|
||||
| REBATE | REBATE_HEADER → REBATE_IMPORT → REBATE_SUBMIT | 正常提交成功;首次领取前检查合同保存条件 |
|
||||
|
||||
ORDER_PUSH 脚本先领取历史未完成批次;再按当前排期需要产生 export_batch_id 并登记新 job(input_id=export_batch_id,input_version=1,source_line_id=空串)。新 job 从 ORDER_EXPORT_FILE 领取执行。导出失败保留该 job;已保存文件和路径写 job_artifact,node_code=ORDER_EXPORT_FILE,恢复时先查已有归档文件。
|
||||
|
||||
归档成功后推进 ORDER_CENTER_PUSH:解析文件、按源订单建立/复用 JD_ORDER_PUSH、保存固定请求并推送。本批所有源订单到台账 ID 的关联写入该 job 的节点 output_json(大批量可用 artifact 保存清单),因此同一台账复用于多个文件时仍可判断每个批次是否完成。首次 job_id/artifact_id 不覆盖,后续文件关联由各批次记录持有。
|
||||
|
||||
源订单均为 ACCEPTED,或本轮确无可推送订单时,批次 job SUCCESS;零数据结果记 NO_DATA 并显示零条,不创建空订单。部分失败保留当前推送节点,已接收项跳过;同订单不同内容记冲突,阻止该批盲目完成,交运维核对。脚本退出与该批次 job 成功是两件事。
|
||||
|
||||
## 6. 详细设计:接口与交互时序
|
||||
|
||||
### 6.1.1 平台订单推送中的对码原始数据
|
||||
|
||||
平台订单推送给订单中心时,RPA 不传本方判断出的货品编码作为对码结论,而是传订单中心完成判断所需的原始订单信息。每条订单明细至少需要保留并按正式接口字段映射传递:
|
||||
|
||||
| 数据组 | 建议字段 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| 来源标识 | 平台代码、平台账号、源订单号、父订单号(如有)、源订单明细号、导出批次号 | 订单中心识别来源、拆单/明细及幂等关系 |
|
||||
| 商品原始信息 | 平台商品编码(如有)、平台品名、批准文号、通用名/商品名称、规格、生产厂家、单位 | 订单中心进行货品对码;平台没有的字段传空并按接口规则处理 |
|
||||
| 采购信息 | 实际采购数量、采购单价、订单金额、订单日期、订单状态 | 形成标准采购订单并供后续合同数据使用 |
|
||||
| 商家及追溯 | 平台商家/供应商原始标识、仓库、文件名、文件哈希、原始行号 | 追溯和订单关联;文件信息作为来源追踪,不替代业务订单键 |
|
||||
|
||||
其中,批准文号、平台品名、通用名/商品名称、规格、生产厂家、单位属于“给订单中心判断用的原始值”,不代表 RPA 已经完成对码。平台订单接口的正式必填字段、字段名称、枚举和缺项规则由订单中心确认;不能只传一个批准文号、数量和金额就宣称对码数据完整。
|
||||
|
||||
订单中心处理后至少应能按源订单号/源明细号返回或在合同共用接口数据中提供:标准货品编码、标准货品名称、注册证号、规格、单位,以及合同/返利所需的采购数量、单价、货源细单号等字段。返回方式可以是订单接收后的后续推送或双方约定的查询接口,但必须保持与源订单明细的关联。本方只使用该标准结果填合同和返利,不在本地补做对码。
|
||||
|
||||
### 6.1 接口清单
|
||||
|
||||
| 接口/方向 | 状态 | 主要契约 |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/jieda/inbound/sales` | 本方拟新增 | 完整销售对象,接收校验后落表 |
|
||||
| `POST /api/jieda/inbound/order-data` | 本方拟新增 | **一次传入合同及返利共同需要的数据**,返回接收记录 ID 和 INPUT_ID |
|
||||
| `POST /api/flows/{flow_code}/jobs/claim` | 复用或按现有方式实现 | 按流程领取待处理作业并返回输入关联 |
|
||||
| `POST /api/flows/ORDER_PUSH/jobs` | 本方拟新增 | 导出前按 export_batch_id 幂等登记平台/账号/范围及 job,导出失败也有作业可查 |
|
||||
| `GET /api/jobs/{job_id}/input` | 本方拟新增 | 对应接口头明细;返利额外返回关联合同 job 及合同号 |
|
||||
| `POST /api/jobs/{job_id}/nodes/{node}/{action}` | 复用 start/complete/fail | 保存结果、编号和错误原因 |
|
||||
| `POST /api/jobs/{job_id}/push-records` | 按现有回报方式实现 | 保存订单推送结果;不强制新增独立接口 |
|
||||
| `POST /api/jobs/{job_id}/retry` | 复用并修正 | 只恢复当前失败步骤,记录操作者及原因 |
|
||||
| 平台订单接收 API(本方→订单中心) | 外部契约待提供 | 来源订单、对码原始字段、数量金额及实际接收回执 |
|
||||
| 链路节点更新 API(本方→订单中心) | 外部契约待提供 | 来源链路关联、对应节点、已保存合同标识及业务结果 |
|
||||
|
||||
订单推送由 ORDER_PUSH 脚本、链路更新由 CONTRACT 脚本通过统一客户端调用。调用前通过状态中心记录固定请求,调用后保存回执。状态中心不同时后台发送同一请求。外部正式路径、业务枚举和鉴权由订单中心提供。
|
||||
|
||||
### 6.2 请求、校验及重复处理
|
||||
|
||||
接收报文在本方适配层统一表示为 `message_id + objects[]`。每个对象有 source_doc_id、input_version(可选)、header、lines。ORDER_COMMON 的 header/lines 包含第 4.5、7 章共同字段,不是分别推 contract 和 rebate。
|
||||
|
||||
| 返回情况 | 本方建议 HTTP | 业务返回及持久化 |
|
||||
| --- | --- | --- |
|
||||
| 缺字段/格式不合法/空明细 | 422 | REJECTED,errors 含对象标识、行标识及字段路径;无业务头明细 |
|
||||
| 全部校验通过且保存成功 | 200 | ACCEPTED、request_log_id、input_ids;表示接收成功 |
|
||||
| 已接收同消息同内容 | 200 | DUPLICATE,返回原 input_ids,另记接收尝试 |
|
||||
| 已接收同标识不同内容 | 409 | CONFLICT,旧成功输入及作业不变 |
|
||||
| 保存失败 | 503 | STORE_FAILED+trace_id;不返回接收成功 |
|
||||
| 鉴权失败 | 401/403 | 拒绝,不建立业务作业 |
|
||||
|
||||
首版整次请求校验通过后,同一个 Oracle 事务保存成功接收记录及全部对象头明细。任一必填缺失则整次失败,返回全部可识别字段错误;上游可按完整单据分批发送。缺的是返利字段也当次返回,因为这一份数据承担两个流程。
|
||||
|
||||
客户类型、三个合同日期、合同明细字段,以及供应商/返利单位/计划员/订单号/货源细单号/含税协议单价在本次共用数据中按第 7 章校验。合同号是本方执行结果,接收时允许不存在。固定值、系统生成字段、页面尽调回显不要求上游提供。
|
||||
|
||||
相同业务对象换 message_id 再传,由头表来源+版本唯一键识别:内容相同引用原 INPUT_ID,内容不同冲突。没有源版本号时,哈希只判断内容同异,不自动判定“哪条更新”。已成功接收的同 SOURCE_DOC_ID 改内容或改版本,本期返回 INPUT_CONFLICT,不自动创建第二组合同/返利 job;这是按业务标识检查,不能仅依赖版本唯一索引。运维和订单中心确认改单处理后再另行安排,原作业和结果保持可查。此前校验失败的修正重传可正常接收。
|
||||
|
||||
接收失败日志存 IF_REQ;Oracle 本身不可用时只能写应用错误日志和 trace_id,不能宣称数据库必有失败记录。响应在事务提交后产生;若响应丢失,上游重传可取得原接收结果。
|
||||
|
||||
### 6.3 时序图:订单中心接收共用数据
|
||||
|
||||
订单中心推送完成后只表示本方已接收并保存数据,合同、返利由各自排期执行。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant OC as 订单中心
|
||||
participant API as 接口服务
|
||||
participant ORA as Oracle
|
||||
OC->>API: 推送合同返利共用数据
|
||||
API->>API: 校验必填字段
|
||||
alt 校验失败
|
||||
API->>ORA: 保存失败记录
|
||||
API-->>OC: 失败及字段位置
|
||||
else 校验通过
|
||||
API->>ORA: 保存接口数据
|
||||
ORA-->>API: 返回 INPUT_ID
|
||||
API-->>OC: 返回接收成功
|
||||
end
|
||||
```
|
||||
|
||||
### 6.4 时序图:平台订单导出并推送
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as 订单脚本
|
||||
participant P as 采购平台
|
||||
participant F as 共享文件夹
|
||||
participant OC as 订单中心
|
||||
participant ORA as Oracle
|
||||
R->>P: 导出订单文件
|
||||
P-->>R: 返回订单文件
|
||||
R->>F: 保存平台订单文件
|
||||
R->>ORA: 保存文件和订单记录
|
||||
R->>OC: 推送订单及对码原始字段
|
||||
alt 接收成功
|
||||
OC-->>R: 返回接收结果
|
||||
R->>ORA: 保存推送成功
|
||||
else 保存失败或接口失败
|
||||
R->>ORA: 保存失败原因
|
||||
end
|
||||
```
|
||||
订单中心接收平台订单和对码原始字段,订单中心内部完成对码,并在后续共用数据中提供标准货品结果。
|
||||
|
||||
### 6.5 时序图:合同排期处理
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 合同脚本
|
||||
participant ORA as Oracle
|
||||
participant CT as 合同系统
|
||||
participant OC as 订单中心
|
||||
C->>ORA: 按合同排期领取待处理数据
|
||||
ORA-->>C: 返回共用数据
|
||||
C->>CT: 录入并保存合同
|
||||
CT-->>C: 返回合同号
|
||||
C->>ORA: 保存合同号和合同结果
|
||||
C->>OC: 更新订单链路
|
||||
OC-->>C: 返回链路结果
|
||||
C->>ORA: 保存链路结果
|
||||
```
|
||||
合同保存和链路更新分别记录。链路失败时保留合同号,后续只重试链路更新,不重新创建合同。
|
||||
|
||||
### 6.6 时序图:返利排期处理
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as 返利脚本
|
||||
participant ORA as Oracle
|
||||
participant RB as 返利中心
|
||||
R->>ORA: 按返利排期领取待处理数据
|
||||
ORA->>ORA: 检查同 INPUT_ID 的合同结果
|
||||
alt 合同未保存成功
|
||||
ORA-->>R: 暂不处理,保留等待状态
|
||||
else 合同已保存且合同号存在
|
||||
ORA-->>R: 返回共用数据和合同号
|
||||
R->>RB: 查询协议并提交返利
|
||||
RB-->>R: 返回办理结果
|
||||
R->>ORA: 保存返利结果
|
||||
end
|
||||
```
|
||||
|
||||
四张图之间的关系是:第一张完成数据接收,第二张完成平台订单推送,第三张和第四张由各自排期独立运行。合同和返利通过同一个 `INPUT_ID` 关联。
|
||||
|
||||
## 7. 详细设计:业务字段映射
|
||||
|
||||
本章保留 Excel 的实际字段和行号。合同/返利列来源都指向第 4.5 节同一份接口输入;按各自模板生成执行数据。生成的模板、默认日期及映射版本记录到节点 output_json/文件证据,不再复制成独立合同或返利输入表。
|
||||
|
||||
### 7.1 合同头
|
||||
|
||||
| 目标字段 | 取值规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 客户名称 | 订单中心 | 12 |
|
||||
| 客户编码 | 目标系统自动生成 | 11 |
|
||||
| 尽调情况 | 读取回显;未尽调停止本单自动录入并转采购员处理 | 13 |
|
||||
| 结算类型 | 收支双向 | 15 |
|
||||
| 合同版本类型 | 预付:我司定制;账期:非格式合同 | 17 |
|
||||
| 合同金额(单位元) | 预估本次合作金额 | 24 |
|
||||
| 金额(元) | 明细汇总;与导入后页面汇总核对并回填 | 25 |
|
||||
| 合同签订日期、合同开始日期、合同结束日期 | 订单中心分别提供,三个独立字段 | 26–28 |
|
||||
| 是否重大合同 | 否 | 29 |
|
||||
| 是否预付款 | 预付:是;账期:否 | 30 |
|
||||
| 付款方式 | 电汇 | 32 |
|
||||
| 付款期限 | 预付:1;账期:订单中心账期天数,缺失不默认 30 | 33 |
|
||||
| 合同具体分类 | 国内药品类、器械类品种 | 37 |
|
||||
| 运输方式 | 公路运输 | 39 |
|
||||
|
||||
### 7.2 合同明细
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码 | 订单中心 | 43 |
|
||||
| 货品名称 | 订单中心标准货品名称 | 44 |
|
||||
| 注册证号 | 订单中心 | 46 |
|
||||
| 规格、单位 | 按订单中心标准字段填报 | 47–48 |
|
||||
| 数量、单价 | 按订单中心最终采购数量、单价填报 | 49–50 |
|
||||
| 付款方式 | 电汇 | 52 |
|
||||
| 付款条件 | 订单中心;模板无此字段时不填 | 53 |
|
||||
| 付款期限 | 同合同头规则 | 54 |
|
||||
|
||||
### 7.3 返利总单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 返利合约 | 否 | 5 |
|
||||
| 供应商编码 | 订单中心 | 6 |
|
||||
| 返利单位编码 | 订单中心 | 7 |
|
||||
| 计划员 | 订单中心,按业务给定的计划员/采购员对应关系填写 | 8 |
|
||||
| 采购结算方式 | 文案“否”;与细单代码 3 分别映射 | 9 |
|
||||
| 承付模式 | 下月 N 号结 | 10 |
|
||||
| 协议开始日期、协议结束日期 | 当天 | 11–12 |
|
||||
|
||||
### 7.4 返利细单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码、数量、含税协议单价 | 本次共用接口数据的货品编码、数量及含税协议单价 | 15–17 |
|
||||
| 税率、关税税率 | 均为 0.13 | 18–19 |
|
||||
| 承付模式、承付参数 | 11、25 | 20–21 |
|
||||
| 细单开始日期 | 当天 | 22 |
|
||||
| 细单结束日期 | 本年度最后一天 | 23 |
|
||||
| 采购结算方式 | 3 | 24 |
|
||||
| 特定补偿预估值、进货补偿预估值 | 均为 0 | 25–26 |
|
||||
| 细单备注 | 订单中心订单号 | 33 |
|
||||
| 货源细单ID | 订单中心货源细单号;与订单号分列 | 34 |
|
||||
|
||||
合同明细导入列按 Excel 的 Sheet3。表中标注无需填写的字段不填;系统自动生成字段由系统处理。
|
||||
|
||||
|
||||
### 7.5 映射与运行日期规则
|
||||
|
||||
- 合同与返利的数量来自同一次报文;实际只提供一列数量时共用该列。含税协议单价与采购单价分别保留来源映射。
|
||||
- 首次执行该流程时生成所用默认日期及模板记录。重试已生成的相同步骤使用原输入,避免跨日重试更改已导入单据;新返利作业按返利首次执行当天生成“当天/年底”。这是实现规则,不增加业务确认步骤。
|
||||
- 合同客户编码为系统生成字段。现有 RPA 入口要求的 customerCode 实际用作客商搜索标识,须与系统自动生成的客户编码分清;按新页面取数方式适配,不要求上游为自动生成字段填占位符。
|
||||
- 映射表标注无需填写的字段不填。尽调读取页面回显;未尽调按原流程转采购员并通知。接口数据保留,人工完成后核对实际合同号并登记保存成功,进入链路更新步骤;返利按第 5.3 节条件领取。
|
||||
|
||||
## 8. 详细设计:操作记录与一致性
|
||||
|
||||
### 8.1 最少需要记录的操作信息
|
||||
|
||||
| 场景 | 保存位置 | 必须能查到的信息 |
|
||||
| --- | --- | --- |
|
||||
| 每次接口接收 | IF_REQ | 请求时间、来源消息、INPUT_ID 关联、原报文、返回结果、字段错误、trace_id |
|
||||
| 脚本执行一步 | job_node | flow、job、INPUT_ID/版本、节点、attempt、run_id、执行机、开始结束、结果、错误 |
|
||||
| 平台订单发送一次 | JD_ORDER_PUSH+job_event | 原文件、源订单、固定请求键、实际请求、HTTP/业务返回、耗时、成功/拒绝范围 |
|
||||
| 合同保存 | job_node+job_artifact | 实际合同标识、保存确认、目标页面证据、所用输入和模板 |
|
||||
| 链路调用一次 | job_event+链路 job_node | 链路关联、节点代码、合同号、请求键、报文、回执、错误 |
|
||||
| 运维操作一次 | job_event / user_task | 操作人、时间、动作、原因、前后状态、证据和操作结果 |
|
||||
| 文件 | job_artifact | 原始订单、导入模板、截图或日志文件路径,文件类型及哈希 |
|
||||
|
||||
job_event 建议事件类型包括 INPUT_BOUND、NODE_STARTED、NODE_SUCCESS、NODE_FAILED、API_REQUEST、API_RESPONSE、RETRY_REQUESTED、RESULT_VERIFIED、MANUAL_HANDOFF、JOB_FINISHED。detail_json 保存结构化信息。节点列表用 job_node.error_code/error_summary 筛选及展示;事件详情从 detail_json.errorCode/errorMessage 读取错误,不假定 job_event 存在独立错误列。详细错误与报文在详情展开。
|
||||
|
||||
接收失败没有 job 时由 IF_REQ 独立展示;所有能定位到作业的动作都写 job_event。脚本运行 ID 使用现有 executor_run_id;无数据或启动失败的本轮摘要写应用日志,通过 run_id 查询,暂不新增运行历史表。
|
||||
|
||||
### 8.2 事务及恢复顺序
|
||||
|
||||
| 写入过程 | 事务范围 | 恢复办法 |
|
||||
| --- | --- | --- |
|
||||
| 接收成功 | Oracle:成功请求记录+所有接口头明细 | 事务失败整体返回失败;响应丢失可同消息重传 |
|
||||
| 登记作业 | Oracle:同输入的合同和返利 job 一起幂等建立 | 成功后回写 IF_REQ.REGISTER_STATUS;回写失败下次重复登记返回同 job,再补 DONE |
|
||||
| 节点成功 | Oracle:该 attempt 成功+业务编号+操作日志/证据索引+推进当前节点 | 回报丢失重发同完成键;同成功结果返回原状态,不再次推进 |
|
||||
| 外部接口调用 | 先 Oracle 保存请求,事务提交后调用,返回后独立事务记结果 | 超时保留 UNKNOWN,不把网络调用放在数据库事务中 |
|
||||
| 人工恢复 | Oracle:核查结果、状态变更和操作日志 | 同一操作请求 ID 重复提交返回原结果 |
|
||||
|
||||
接口输入和业务执行结果均在 Oracle 中保存,登记、领取、结果回报使用同一数据库事务边界;外部接口调用仍在事务提交后执行。接口表登记失败时可再次幂等登记,组合唯一约束保证不多建合同/返利作业。
|
||||
|
||||
### 8.3 文件和配置
|
||||
|
||||
1 药城与药帮忙分别配置共享根目录和字段映射;现场路径由配置提供。下载完成后发布文件,文件名按批次区分,保留原件。后台返回文件列表,业务/运维通过现有证据入口打开或下载,不要求共享盘在所有机器使用同一盘符。
|
||||
|
||||
订单中心连接、凭据、超时、链路节点映射、四条排期、重试间隔及模板配置集中管理。真实环境缺必要配置返回明确错误,不自动使用 fixture。日期、数量和金额按第 4、7 章数据类型保存,日志不保存 token 或数据库密码。
|
||||
|
||||
## 9. 详细设计:正常路径与异常处理
|
||||
|
||||
### 9.1 正常路径示例
|
||||
|
||||
本例标识仅用于说明字段关系,不代表正式接口报文。
|
||||
|
||||
| 时间/动作 | 接口记录 | 合同保存 | 链路更新 | 返利 | 记录位置 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 接收 M001 | ACCEPTED,INPUT_ID=I001,REGISTER=PENDING | 未登记 | 未登记 | 未登记 | IF_REQ+ORDER_H/L |
|
||||
| 任一相关脚本启动登记 | REGISTER=DONE | PENDING | 未开始 | WAIT_CONTRACT | 两个 job 都绑定 I001/1 |
|
||||
| 合同脚本处理 | 原输入不变 | RUNNING | 未开始 | WAIT_CONTRACT | CREATE 第 1 次尝试 |
|
||||
| 合同保存成功 HT001 | 原输入不变 | SUCCESS | PENDING | 可领取 | 合同号及保存证据 |
|
||||
| 更新链路成功 | 原输入不变 | SUCCESS | SUCCESS | 可领取 | 链路请求、回执;合同 job 完成 |
|
||||
| 返利下次运行 | 原输入不变 | SUCCESS | SUCCESS | RUNNING | 读取 I001 同一头明细+HT001 |
|
||||
| 返利提交完成 FL001 | 原输入不变 | SUCCESS | SUCCESS | SUCCESS | 返利编号、模板及提交结果 |
|
||||
|
||||
### 9.2 异常矩阵
|
||||
|
||||
| 异常代码(建议) | 识别条件 | 状态和保留内容 | 处理及恢复位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| INPUT_REQUIRED | 本次共用数据缺合同或返利上游必填 | IF_REQ=REJECTED,字段路径/行号,无业务输入 | 上游补齐后重传;RPA 无需处理 |
|
||||
| INPUT_CONFLICT | 成功来源/版本再传不同内容 | CONFLICT,关联旧请求、保存新原文 | 核对来源及版本;不覆盖已使用数据 |
|
||||
| STORE_FAILED | 接口表事务失败 | 返回 503;应用日志 trace_id | 修复数据库后重传 |
|
||||
| JOB_REGISTER_FAILED | Oracle 已接收,作业登记失败 | REGISTER=FAILED、原因;输入 READY | 修复数据库或服务后,下一次脚本取数补登记 |
|
||||
| PLATFORM_QUERY_FAILED | 页面打不开或查询报错 | 行 job 明确失败,截图/错误 | 修复后重试该行;不记 SKIPPED |
|
||||
| NO_ELIGIBLE_PRODUCT | 查询成功但不满足采购条件 | job SUCCESS,result=SKIPPED、原因 | 正常结束,常规轮询不重复处理 |
|
||||
| FILE_SAVE_FAILED | 共享目录不可写/文件未完整 | ORDER_PUSH 失败、已知路径 | 修复目录后从导出/归档失败处继续 |
|
||||
| OC_ORDER_REJECTED | 订单中心明确业务拒收 | push=REJECTED,拒绝原因及范围 | 修正接口映射/源数据后处理失败单,成功单保留 |
|
||||
| EXTERNAL_RESULT_UNKNOWN | 加购、保存、推送或提交后无确定结果 | WAITING_EXTERNAL/UNKNOWN,请求或页面证据 | 先核对目标结果;确认未成功才重试 |
|
||||
| CONTRACT_DUE_DILIGENCE | 合同页面显示未尽调 | WAITING_USER、采购员待办和页面记录 | 按原业务人工处理,核验保存结果后继续链路 |
|
||||
| CHAIN_UPDATE_FAILED | 合同已保存,链路明确失败 | CREATE 成功;CHAIN 失败、回执 | 下次仅重试 CHAIN;合同号不清空 |
|
||||
| PROTOCOL_NOT_READY | 已有合同号,但返利协议不可查/暂不可操作 | 返利 WAITING_EXTERNAL,原因及检查时间 | 到期再查协议;不重建合同,不等第二次供数 |
|
||||
| REBATE_SUBMIT_FAILED | 返利系统明确提交失败 | 失败步骤、目标协议/总单、已导入信息 | 按原业务交人工处理;保留前面成功步骤 |
|
||||
| EXECUTOR_INTERRUPTED | 作业仍 RUNNING,进程已退出 | 执行机/run_id、最近节点、证据 | 运维确认无运行进程并核对结果后,恢复当前步骤 |
|
||||
|
||||
所有正常失败恢复保留原 INPUT_ID、job 和历史 attempt。通用“重试”只增加当前失败节点尝试,不把 job 从第一个节点重新执行。已完成外部写入不能仅凭“失败”按钮反向当作没有发生。
|
||||
|
||||
## 10. 运维设计:如何查看与处理
|
||||
|
||||
### 10.1 查询入口及页面内容
|
||||
|
||||
复用现有列表、节点详情、时间线、待办和证据组件,新增接收列表和“同一输入的合同/返利总览”,不用另建运维平台。
|
||||
|
||||
| 页面 | 筛选条件 | 列表/详情内容 |
|
||||
| --- | --- | --- |
|
||||
| 接口接收列表 | 时间、来源单号、MESSAGE_ID、TRACE_ID、接收状态、登记状态 | 请求/返回原文、缺字段路径、INPUT_ID、重复来源、登记失败原因 |
|
||||
| 合同返利总览 | INPUT_ID、来源单号、合同号、返利号、三个业务状态、时间 | 同一行显示合同保存、链路更新、返利状态及最近错误;点击进入各 job |
|
||||
| 作业详情 | flow、job_id、状态、执行机、run_id | 来源输入、当前节点、每次尝试、耗时、错误、回报、外部编号 |
|
||||
| 订单推送列表 | 平台/账号、订单号、导出批次、push_status | 原文件、发送内容、回执、次数、结果未知/拒收原因 |
|
||||
| 操作和证据 | job/INPUT_ID、操作者、动作时间 | 谁点击重试/人工处理,前后状态、原因、截图、模板及日志 |
|
||||
|
||||
“合同返利总览”由后端以 Oracle INPUT_ID 为基底,关联两份 job/节点结果;不存在 job 时显示未登记。分页/筛选由后端实现,不要求浏览器一次拉全表。
|
||||
|
||||
### 10.2 典型查看路径
|
||||
|
||||
| 运维问题 | 如何查 | 结论/下一步 |
|
||||
| --- | --- | --- |
|
||||
| 订单中心说推过,RPA 没处理 | 先查 MESSAGE_ID/来源单号的接收记录 | REJECTED 看缺字段;ACCEPTED+登记失败看数据库错误;DONE 看对应 job |
|
||||
| 合同存在,为什么流程显示失败 | 总览看合同保存和链路更新两列 | 保存成功/链路失败只补链路调用,不重建 |
|
||||
| 返利为什么一直没开始 | 看同 INPUT_ID 的合同保存结果及返利 wait_reason | 合同未保存则正常等待;保存成功但协议不可查则按目标状态处理 |
|
||||
| 同一批有的成功有的失败 | 订单推送按源订单筛选,选品按行 job 查看 | 重试失败项;已接收/已加购项不重复处理 |
|
||||
| 数据被改过或重复推过 | 接收尝试记录+DATA_HASH+操作日志 | 明确首次输入、重复/冲突报文及处理人 |
|
||||
|
||||
### 10.3 运维动作及权限
|
||||
|
||||
| 动作 | 允许条件 | 实际改变 | 必须留痕 |
|
||||
| --- | --- | --- | --- |
|
||||
| 重试当前步骤 | FAILED_RETRYABLE,且外部结果已明确 | 当前节点新增 attempt,job 转 PENDING | 操作人、原因、旧状态、新 attempt |
|
||||
| 重新登记作业 | 已 ACCEPTED 且 REGISTER≠DONE | 幂等补建缺失 job,完成登记标志 | 接收记录、结果、操作者 |
|
||||
| 核对结果:已成功 | UNKNOWN 或人工完成,提供实际业务编号/回执证据 | 确认对应节点成功并推进;合同保存后仍执行链路 | 目标编号、证据、核对人和时间 |
|
||||
| 核对结果:未成功 | 确认目标未写入,原执行进程已停止 | 转可重试当前步骤 | 核对依据、操作者、原因 |
|
||||
| 转业务处理 | 业务错误或系统要求人工 | 创建/复用现有 user_task,保存责任人 | 原因、接收人、操作记录 |
|
||||
| 停止办理 | 有明确业务指令 | CANCELLED,保留已产生结果 | 原因及外部已发生操作,不自动撤销外部单据 |
|
||||
|
||||
只读运维可看列表及证据;恢复动作限定 OPS,业务待办按采购员等现有角色处理。恢复入口必须核对实际操作者身份,不能只相信请求体里填写的 operator。沿用现有鉴权方案补动作权限,不另加业务审批流。
|
||||
|
||||
不能用“统一确认并继续”替代所有核对动作,不能直接改接口原始字段或数据库状态。金额、货品等源数据错误由订单中心修正重传;已被使用的输入按冲突规则处理。
|
||||
|
||||
### 10.4 查询 API 及日志定位
|
||||
|
||||
| API(建议) | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /api/jieda/receipts`、`/receipts/{request_log_id}` | 无 job 的失败接收也能查 |
|
||||
| `GET /api/jieda/orders`、`/orders/{input_id}` | 同一输入的合同/链路/返利总览及头明细 |
|
||||
| `GET /api/jieda/order-pushes` | 按源订单查推送及文件 |
|
||||
| 现有 `GET /api/jobs/{job_id}`、`/timeline`、`/evidence-package` | 节点、历史尝试和证据 |
|
||||
| `GET /api/jobs/{job_id}/events`(新增) | 操作日志和出站请求/返回记录 |
|
||||
| `POST /api/jieda/receipts/{id}/register`(新增) | 运维手动补登记,与脚本取数复用同一逻辑 |
|
||||
| `POST /api/jobs/{job_id}/verify-result`(新增) | 按第 10.3 节核对 UNKNOWN/人工结果,记录依据后恢复 |
|
||||
|
||||
应用日志统一携带 `trace_id、input_id、job_id、flow_code、node_code、attempt_no、run_id` 中当前已知项。接收阶段没有 job_id 时不编造;执行阶段没有 HTTP trace 时以 job/run 定位。列表展示最近错误,详情保留全部历史,不只覆盖最后一次失败。
|
||||
|
||||
索引建议:接口记录按时间/状态/来源,业务头按源单号,job 按 `(flow_code,status,next_check_at)` 和 `(input_id,input_version)`,节点按现有 `(job_id,node_code,attempt_no)`,事件按 `(job_id,created_at)`,推送按来源唯一键及 `(push_status,next_retry_at)`。
|
||||
|
||||
## 11. 实施影响与验收
|
||||
|
||||
### 11.1 现有代码到新设计的映射
|
||||
|
||||
| 现有位置 | 已核对现状 | 实施改动 |
|
||||
| --- | --- | --- |
|
||||
| [main.py 建单](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:499) | 供应商+日期键,固定首节点,只复用未完成作业 | 新接收/登记/领取按 flow 和 INPUT_ID;已完成作业也防重 |
|
||||
| [schema.sql](/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql:11) | job 无 flow/input 关联;已有节点、日志及证据表 | 第 4 章 Oracle 接口表及推送台账;job 扩展字段/索引,supplier_name/order_date 允许空;job_event 错误保存在现有 detail_json |
|
||||
| [db.py](/Users/yuuuu/Documents/AutoRpa/state_center/app/db.py:22) | 当前数据库访问实现 | 按 Oracle 驱动和事务方式调整登记、领取、结果及推进 |
|
||||
| [main.py 完成回报](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:712) | 保存节点、编号和推进分步执行;节点定义全局固定 | 同事务回报,校验 execution_token/attempt;按 flow 定义完成规则 |
|
||||
| [main.py 重试](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:812) | 已保留 attempt 历史、重试当前节点 | 保留机制,支持共用输入关联及 UNKNOWN 核对 |
|
||||
| [main.py 人工提交](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:1346) | 通用确认可直接推进;RETRY_CURRENT_NODE 分支当前只记录事件 | 按第 10.3 节校验结果和动作,实现真实重试及核查恢复 |
|
||||
| [agent.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/agent.py:1) | 通用代理主要调用 mock;存在节点硬编码与默认 attempt=1 | 四条独立入口、真实 runner、领取结果中的 attempt/token |
|
||||
| [contract_create_node.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/contract_create_node.py:241) | 已校验空明细及正数量,仍要求旧 customerCode | 适配共用接口数据和客商定位;保留有效输入检查 |
|
||||
| [contract_flow_steps.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/contract_flow_steps.py:788) | 缺价补 1.00,同码首行及前 30 行定位 | 移除缺价补值;按来源明细/目标行定位并核对实际导入结果 |
|
||||
| [Web 节点详情](/Users/yuuuu/Documents/AutoRpa/state_center_web/src/components/JobNodeDetailList.tsx:1) | 已展示时间、尝试次数、错误及证据 | 复用组件,补三个独立状态、输入关联、接收列表及恢复动作 |
|
||||
|
||||
当前 Oracle 配置主要服务货品主数据读取;新接口表写入连接和权限需现场配置。当前代码不等于已具备四条真实平台执行能力,平台查询/导出及返利页面模块需要核对或实现。
|
||||
|
||||
### 11.2 实施顺序
|
||||
|
||||
1. 按共同输入契约核对第 4、7 章字段及合同/返利范围;确认现场接口表复用方式。
|
||||
2. 完成接收校验、Oracle 同事务保存及接收查询,验证失败重传和重复消息。
|
||||
3. 增加 flow/input 关联、作业登记、独立状态、领取及结果事务,跑通“同一输入两个 job”。
|
||||
4. 接平台订单导出、共享文件、台账及推送回执。
|
||||
5. 接合同页面保存和链路更新;返利读取同 INPUT_ID 与合同号完成提交。
|
||||
6. 接逐条选品;补齐异常及运维动作,配置四条排期后联调。
|
||||
|
||||
### 11.3 验收用例
|
||||
|
||||
| 编号 | 场景 | 预期结果 |
|
||||
| --- | --- | --- |
|
||||
| AT-01 | 一次完整 ORDER_COMMON 推送 | 一份头明细;登记两个相同 INPUT_ID/版本的合同、返利 job |
|
||||
| AT-02 | 仅返利必填缺失 | 当次返回字段错误,无业务输入;补齐后可重传 |
|
||||
| AT-03 | 相同请求重发、相同对象换消息 ID 重发 | 返回原输入,不新增执行作业 |
|
||||
| AT-04 | 同已成功来源/版本传不同内容 | 冲突可查,旧数据和外部结果不被覆盖 |
|
||||
| AT-05 | 接收已提交,作业登记失败 | 接收记录显示登记失败;后续幂等补登记,不要求上游再推 |
|
||||
| AT-06 | 返利脚本先于合同脚本启动 | WAIT_CONTRACT;无额外供数请求、无返利提交 |
|
||||
| AT-07 | 合同保存成功,链路更新失败 | 保存 SUCCESS、链路 FAILED;只恢复链路,返利可按保存条件领取 |
|
||||
| AT-08 | 合同完成后返利按另一排期启动 | 读取同一接口数据和本方合同号,字段来源可追溯 |
|
||||
| AT-09 | 平台订单重复导出到不同文件 | 原件保留,已接收同源订单不重复入单 |
|
||||
| AT-10 | 推送部分成功/超时 | 按实际回执记录逐单结果;UNKNOWN 不直接按失败重发 |
|
||||
| AT-11 | 选品合格/不合格/查询失败 | 分别 ADDED、SKIPPED、FAILED;本批结束,不等付款 |
|
||||
| AT-12 | 脚本中断和重复点击 | 不重复领取;已有结果可查,确认停止并核对后恢复当前步骤 |
|
||||
| AT-13 | 合同已保存但回报丢失 | 重发完成键或核对目标结果,不再次建合同 |
|
||||
| AT-14 | 未尽调或返利提交失败 | 保留输入及原业务待办,其他记录继续;人工结果核对可留痕 |
|
||||
| AT-15 | 46 个 Excel RPA 字段 | 共用接口来源、固定值及日期按映射填报,缺价不补 1.00 |
|
||||
| AT-16 | 运维按 MESSAGE_ID、INPUT_ID、合同号查询 | 能串起接收、两流程、尝试、文件、请求和人工动作 |
|
||||
| AT-17 | 未授权恢复、旧 token 回报 | 拒绝写入;旧成功结果不被覆盖 |
|
||||
| AT-18 | 新旧流程并存 | 旧在途单保留原节点;新流程不误入对码/OCR/M5 |
|
||||
|
||||
文档检查与业务验收分开:当前只完成设计;以上需要在测试数据库、实际共享目录及外部测试系统执行,mock 成功不代表真实业务通过。
|
||||
|
||||
## 12. 未决事项、风险及对其他模块的影响
|
||||
|
||||
### 12.1 联调前需要确认
|
||||
|
||||
| 项目 | 已明确 | 剩余信息 |
|
||||
| --- | --- | --- |
|
||||
| 合同返利供数 | 一次推送共用 | 正式头明细、业务范围标识、消息 ID/版本;一组数据是否有多合同/多返利范围 |
|
||||
| 业务字段 | Excel 来源及默认值 | 实际 JSON 列名;采购/返利数量是否同字段,价格口径及精度 |
|
||||
| 订单出站接口及对码字段 | 本方归档后推送原始订单字段 | 确认批准文号、品名、通用名、规格、厂家、单位等字段是否必填,以及标准货品结果的返回/后续供数方式 |
|
||||
| 链路更新 | 合同保存成功后更新对应节点 | 节点代码、链路关联、合同标识字段及成功返回码 |
|
||||
| 返利可操作性 | 用同一份输入加已保存合同号 | 目标协议生成时间、是否依赖审批或链路更新;未确认前不新增业务审批门槛 |
|
||||
| 平台和共享目录 | 两平台分别导出归档 | 真实账号、订单状态/范围、文件样例、共享路径及四条排期 |
|
||||
| 盈利 | 5% 以上盈利 | 计算基数及边界;不直接套用旧方案 0.95 公式 |
|
||||
| 现场库和模板 | 接口输入与处理记录分开 | 现有 Oracle 表、权限、正式模板和确有要求的附件 |
|
||||
|
||||
### 12.2 可能问题与控制位置
|
||||
|
||||
| 问题 | 影响 | 对应设计 |
|
||||
| --- | --- | --- |
|
||||
| 共用一个 processed 标志 | 合同处理后返利漏取 | 第 5 章分离合同保存、链路、返利标志 |
|
||||
| 两份接口输入被分别修改 | 合同/返利货品数量不一致 | 同 INPUT_ID/版本只保留一份原始头明细 |
|
||||
| 成功接收后没有 job | 数据落表但长期未执行 | REGISTER_STATUS 和取数前补登记;运维可查 |
|
||||
| 接口/页面结果未知被当失败 | 重复订单、合同或返利 | UNKNOWN 核对后恢复,不无条件重做 |
|
||||
| 把链路失败当合同未保存 | 再建一份合同 | 保存节点结果与链路节点独立 |
|
||||
| 当前合同缺价补值或明细定位错误 | 错金额、同码行覆盖或漏行 | 接收校验及第 11.1 节代码修正;单规格规则不消除多订单行的问题 |
|
||||
| 原人工确认直接推进 | 未核实外部结果就完成作业 | 第 10.3 节按动作和证据校验 |
|
||||
| 新旧流程改全局状态机 | 旧对码、OCR、M5 在途单无法继续 | flow/version 分开,旧单保持原链;切换前确定同一来源由哪套办理 |
|
||||
| 两库或共享目录不可用 | 接收失败、原文不可查、文件不可达 | 独立错误可见;执行记录保留,不伪装成空数据或成功 |
|
||||
|
||||
本稿的表与字段是详细设计建议,尚未核对现场字典。对外契约尚未提供的部分保留为明确缺口;不以增加业务操作、复制数据或自动猜规则来填补。
|
||||
|
|
@ -0,0 +1,781 @@
|
|||
# 福建洁达采购 RPA 概要设计与详细设计
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 版本 | V3.0 |
|
||||
| 日期 | 2026-09-13 |
|
||||
| 状态 | 设计评审稿;未实施代码或建表 |
|
||||
| 适用系统 | 当前 AutoRpa 状态中心、RPA 执行程序及福建洁达采购业务 |
|
||||
| 本版核心关系 | 订单中心一次推送合同/返利共用数据;合同先处理,返利按自己的排期读取同一份数据 |
|
||||
| 文档范围 | 概要设计、表与状态详细设计、接口及时序、异常恢复、运维查询、实施与验收 |
|
||||
| 替代关系 | 整合原 Codex 设计稿 V1.9 和技术实现说明 V2.3;旧方案仅供历史参考 |
|
||||
|
||||
**阅读顺序:** 业务评审看第 1–3 章;开发看第 4–9 章;运维看第 10 章;实施验收及未决问题见第 11–12 章。
|
||||
|
||||
## 1. 设计依据与范围
|
||||
|
||||
### 1.1 已确认业务规则
|
||||
|
||||
| 编号 | 规则 |
|
||||
| --- | --- |
|
||||
| BR-01 | 四条脚本分别运行,自动时间和频率不同,也支持业务点击;一次 HTTP 接收不等待四条流程全部执行 |
|
||||
| BR-02 | 选品读取未处理销售明细,按批准文号查询;合格加购物车,不合格跳过,处理完本批结束;不等待人工付款 |
|
||||
| BR-03 | 本业务相同批准文号只有一个规格,不增加规格、厂家、包装或单位换算匹配 |
|
||||
| BR-04 | 平台订单导出到各平台对应共享文件夹,按订单中心接口格式组装并推送;对码由订单中心内部处理 |
|
||||
| BR-05 | 订单中心一次传来的完整业务数据供合同、返利共用,不拆成两次独立供数,不重复保存两份相同头明细 |
|
||||
| BR-06 | 接收时检查上游应传的必填字段,缺项当次返回失败和字段位置;通过并落表后返回成功 |
|
||||
| BR-07 | 合同保存成功后调用订单中心链路接口更新对应节点;返利读取同一份接口数据,并使用本方已取得的合同号 |
|
||||
| BR-08 | 手工单暂不做;合同/返利字段、固定值及原业务人工分支按提供材料执行 |
|
||||
|
||||
### 1.2 参考材料
|
||||
|
||||
| 材料 | 用途 |
|
||||
| --- | --- |
|
||||
| [合同系统录入流程](/Users/yuuuu/Documents/AutoRpa/资料/合同系统录入流程.md) | 合单、尽调、提交、回写;页面步骤指向 rpa_yisaiqi |
|
||||
| [选品条件 20260902](/Users/yuuuu/Documents/AutoRpa/资料/采购平台选品条件优先项_20260902.txt) | 自营仓、盈利、效期、库存和仓库顺序 |
|
||||
| [合同返利字段映射 20260911](/Users/yuuuu/Documents/AutoRpa/资料/合同返利系统字段映射_20260911.xlsx) | 两个工作表的 46 个 RPA 字段及 Sheet3 导入列 |
|
||||
| [仓库一期技术设计](/Users/yuuuu/Documents/AutoRpa/资料/采购主链RPA一期设计文档.md) | 参考概要/详细设计、实体、接口、状态和审计的组织方式;不沿用其旧业务范围 |
|
||||
| [现有数据库结构](/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql) | 判断 job、节点、待办、日志及证据记录可复用部分 |
|
||||
|
||||
本次会话的业务确认优先于旧文档。下文表名、内部代码、API 路径及列类型为本方建议,正式外部字段名、节点代码和现场 Oracle 结构仍需按第 12 章核对。
|
||||
|
||||
## 2. 概要设计:架构与分工
|
||||
|
||||
### 2.1 总体架构
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
OC[订单中心] -->|销售数据 / 合同返利共用数据| API[FastAPI 接收校验]
|
||||
API -->|校验成功同事务落表| IFDB[(Oracle 接口头明细及接收记录)]
|
||||
S[四条独立排期 / 点击脚本] --> RPA[RPA 执行程序]
|
||||
RPA <-->|领取数据 / 回报结果| SC[状态中心]
|
||||
SC -->|读取同一份输入| IFDB
|
||||
SC <--> BDB[(Oracle 作业、节点、调用记录)]
|
||||
RPA -->|查品加购 / 导出订单| PL[采购平台]
|
||||
PL --> F[对应共享文件夹]
|
||||
F -->|组装报文并推送| OC
|
||||
RPA -->|录入保存| CT[合同系统]
|
||||
RPA -->|保存后调用链路接口| OC
|
||||
RPA -->|同一接口数据加合同号| RB[返利中心]
|
||||
UI[运维列表 / 详情 / 日志] --> SC
|
||||
```
|
||||
|
||||
接口数据与业务处理记录分开:**Oracle 接口表回答“订单中心传了什么”,Oracle 业务记录回答“本方做到哪一步、结果是什么、谁操作过”。** RPA 通过状态中心读取接口数据,数据库连接集中在后端。
|
||||
|
||||
### 2.2 接口表与业务表的选择
|
||||
|
||||
| 方式 | 特点 | 本期选择 |
|
||||
| --- | --- | --- |
|
||||
| 仅一张接口表加一个 processed 标志 | 简单,但合同处理后可能被整体标记完成,返利漏取;也无法表达多次失败尝试 | 不采用 |
|
||||
| 接口表再完整复制成合同表、返利表 | 三份字段容易不一致,需要额外同步 | 不采用 |
|
||||
| 接口头明细保留一份输入,业务作业/节点保存独立状态与结果 | 合同、返利通过相同 INPUT_ID 关联;历史操作复用现有日志 | **采用** |
|
||||
|
||||
现有 `job / job_node` 就是本期业务处理表,无需再复制合同、返利输入。为无 job 的接收失败补接收记录;为跨导出文件识别同一订单,补逐订单推送台账。这两项有明确查询及防重用途。
|
||||
|
||||
### 2.3 模块职责及运行方式
|
||||
|
||||
| 模块 | 职责 |
|
||||
| --- | --- |
|
||||
| FastAPI | 校验、保存接收数据、取数、原子领取、保存回报、查询及运维动作 |
|
||||
| Oracle 接口库 | 接收尝试、销售头明细、合同/返利共用头明细;成功输入保留原值 |
|
||||
| Oracle 状态中心 | 各流程 job、节点尝试、业务编号、订单推送状态、操作日志、证据索引 |
|
||||
| 四条 RPA 脚本 | 本批逐条执行;平台订单和合同链路接口由对应脚本通过适配器调用并回报 |
|
||||
| 现有 Web | 接收查询、共用数据处理总览、节点尝试、文件和运维动作 |
|
||||
|
||||
四条脚本分别配置时间、频率及本轮条数上限,单执行机每条脚本设单实例启动控制。FastAPI 常驻。脚本启动时调用后端登记未建立的 job,再领取本流程数据;订单导出先生成并登记 export_batch_id,恢复失败批次沿用该 ID;本批结束释放本机运行锁。没有数据时记录本轮零条并退出,不新增第五条业务或后台维护流程。
|
||||
|
||||
## 3. 概要设计:业务流程与完成边界
|
||||
|
||||
| 流程 | 输入 | 正常处理 | 完成含义 |
|
||||
| --- | --- | --- | --- |
|
||||
| PROCURE | 销售接口表的一条明细 | 按批准文号查询,按采购条件加购或跳过 | 该行处理完;不代表付款成交 |
|
||||
| ORDER_PUSH | 对应平台导出的订单文件 | 保存共享目录、解析、组装、推送、记录回执 | 应推送订单确认接收;不等待内部对码 |
|
||||
| CONTRACT | 共用订单接口头明细 | 录入并保存合同,保存合同号,更新订单中心链路 | 保存及链路更新分别记录;链路失败不重建合同 |
|
||||
| REBATE | **同一共用订单接口头明细**+本方合同保存结果 | 按合同号查协议、填总单、导细单、提交 | 返利提交成功;不要求订单中心再次推数据 |
|
||||
|
||||
### 3.1 合同与返利的关联
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
I[一次推送:INPUT_ID = I001] --> C[合同 job:INPUT_ID = I001]
|
||||
I --> R[返利 job:INPUT_ID = I001]
|
||||
C --> CS[合同保存成功:合同号 HT001]
|
||||
CS --> CH[合同链路更新]
|
||||
CS -->|提供合同号| R
|
||||
R --> RS[返利提交结果]
|
||||
```
|
||||
|
||||
合同、返利是两个独立作业,绑定同一 INPUT_ID 和 INPUT_VERSION。合同保存号只从对应合同 job 取得,不能按同品种或同一天猜配。
|
||||
|
||||
返利默认在“合同已确认保存成功且合同号存在”后可领取;链路回传结果另列显示,**不擅自把链路成功增加为返利前置条件**。若目标返利协议暂不可查,记录等待原因,下次返利运行再查询。是否必须审批通过由目标系统及接口方确认,不新增审批动作。
|
||||
|
||||
### 3.2 选品规则
|
||||
|
||||
自营仓、5% 以上盈利、有效期大于等于 12 个月、库存满足采购数量四项都通过后,按下列仓库顺序选择。不合格记跳过,平台技术错误记失败。
|
||||
|
||||
广东壹号药业有限公司、昆山亿方药业有限公司、湖北亿昊药业有限公司、天津壹号药业有限公司、重庆亿昊药业有限公司、重庆壹号医药有限公司、湖南壹号药业股份有限公司、辽宁亿昊药业有限公司、河南壹号医药有限公司、甘肃壹号云医药有限责任公司企业店、陕西壹号药业有限公司。
|
||||
|
||||
## 4. 详细设计:数据模型
|
||||
|
||||
### 4.1 实体关系和存储位置
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
RPA_JD_IF_REQ ||--o{ RPA_JD_SALES_H : accepts
|
||||
RPA_JD_IF_REQ ||--o{ RPA_JD_ORDER_H : accepts
|
||||
RPA_JD_SALES_H ||--|{ RPA_JD_SALES_L : contains
|
||||
RPA_JD_ORDER_H ||--|{ RPA_JD_ORDER_L : contains
|
||||
RPA_JD_SALES_L ||--o{ JOB : procure
|
||||
RPA_JD_ORDER_H ||--o{ JOB : contract_and_rebate
|
||||
JOB ||--o{ JOB_NODE : attempts
|
||||
JOB ||--o{ JOB_EVENT : operations
|
||||
JOB ||--o{ JOB_ARTIFACT : files
|
||||
JOB ||--o{ JD_ORDER_PUSH : exports
|
||||
```
|
||||
|
||||
前五张为 Oracle 逻辑表:一张接收记录、销售头明细、共用订单头明细;已有同用途表优先映射复用。其余为 Oracle,`JD_ORDER_PUSH` 拟新增,其余复用。图中的同库关系是应用关联,不是跨数据库外键。
|
||||
|
||||
接口表内的合同/返利状态不各写一份。运维总览按 INPUT_ID 汇总两份 job 与其节点,展示三个独立标志:合同保存、链路更新、返利办理。读写来源见第 5 章。
|
||||
|
||||
### 4.2 接收记录 `RPA_JD_IF_REQ`
|
||||
|
||||
一行是一回接收尝试;错误请求也能查到,不能依赖先有 job 才记录。
|
||||
|
||||
| 字段 | 建议类型 | 含义与约束 |
|
||||
| --- | --- | --- |
|
||||
| REQUEST_LOG_ID | VARCHAR2(64) PK | 本方生成,返回给上游和运维定位 |
|
||||
| TRACE_ID | VARCHAR2(64) | 本次请求关联应用日志 |
|
||||
| SOURCE_SYSTEM / MESSAGE_ID | VARCHAR2(32) / VARCHAR2(128) | 调用方及其请求标识;MESSAGE_ID 格式由对方提供 |
|
||||
| DATA_TYPE | VARCHAR2(24) | SALES 或 ORDER_COMMON;没有独立 REBATE 接收类型 |
|
||||
| BODY_HASH | VARCHAR2(64) | 规范化业务报文哈希,识别同请求内容是否一致 |
|
||||
| RECEIVE_STATUS | VARCHAR2(16) | ACCEPTED、REJECTED、DUPLICATE、CONFLICT |
|
||||
| ORIGINAL_REQ_ID | VARCHAR2(64),可空 | 重复请求关联首次成功记录 |
|
||||
| RAW_BODY / RESPONSE_BODY | CLOB | 实际业务报文和返回内容,不记录鉴权口令 |
|
||||
| ERROR_CODE / ERROR_DETAIL | VARCHAR2(64) / CLOB | 字段路径、明细标识、错误说明 |
|
||||
| RECEIVED_AT / RESPONDED_AT | TIMESTAMP | 收到及生成返回结果的时间 |
|
||||
| REGISTER_STATUS | VARCHAR2(8),可空 | 成功记录的作业登记标志:PENDING、DONE、FAILED;非成功记录为空 |
|
||||
| REGISTER_ERROR / REGISTERED_AT | VARCHAR2(500) / TIMESTAMP | Oracle 登记失败原因及最终登记时间 |
|
||||
|
||||
RECEIVE_STATUS 表示接收事实,REGISTER_STATUS 仅表示是否已登记作业,均不代表合同或返利完成。成功接收同事务保存接口头明细并置 REGISTER_STATUS=PENDING;脚本取数前补登记,完成后更新 DONE。登记失败在下一次脚本取数重试,无需上游重传。
|
||||
|
||||
约束:对 ACCEPTED 记录建立 `(SOURCE_SYSTEM, MESSAGE_ID)` 条件唯一索引;Oracle 可用 CASE 表达式索引实现。失败/重复尝试保留独立行,不占用成功消息键。索引还包括 `(DATA_TYPE, REGISTER_STATUS, RECEIVED_AT)`、`TRACE_ID`、`(SOURCE_SYSTEM, MESSAGE_ID)`。
|
||||
|
||||
### 4.3 接口头公共字段
|
||||
|
||||
销售头和共用订单头均使用以下控制列。
|
||||
|
||||
| 字段 | 建议类型 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| INPUT_ID | VARCHAR2(64) PK | 本方输入身份,跨流程统一关联 |
|
||||
| REQUEST_LOG_ID | VARCHAR2(64) FK | 首次成功接收记录 |
|
||||
| SOURCE_SYSTEM / SOURCE_DOC_ID | VARCHAR2(32) / VARCHAR2(128) | 上游稳定业务标识;与展示单号分开 |
|
||||
| SOURCE_DOC_NO | VARCHAR2(128) | 业务查询展示用单号 |
|
||||
| INPUT_VERSION | VARCHAR2(64) | 上游版本;未提供时首版用固定 1,变更走第 6.2 节冲突规则 |
|
||||
| DATA_HASH | VARCHAR2(64) | 本对象完整头明细内容哈希 |
|
||||
| DATA_STATUS | VARCHAR2(16) | READY:成功接收的完整输入;本期不另设待补字段状态 |
|
||||
| CREATED_AT | TIMESTAMP | 成功保存时间 |
|
||||
|
||||
头表唯一键为 `(SOURCE_SYSTEM, SOURCE_DOC_ID, INPUT_VERSION)`。明细以 `(INPUT_ID, SOURCE_LINE_ID)` 唯一,关联本头,不以货品编码唯一。成功输入不由 RPA 改写,处理结果另存 Oracle。
|
||||
|
||||
### 4.4 销售接口头明细 `RPA_JD_SALES_H / L`
|
||||
|
||||
| 表 | 业务字段 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| SALES_H | 第 4.3 节公共字段、BUSINESS_DATE | 一份销售总单 |
|
||||
| SALES_L | LINE_ID、INPUT_ID、SOURCE_LINE_ID、APPROVAL_NO、PURCHASE_QTY、SALES_PRICE | 逐条查询所需批准文号、需求采购数量、销售价格 |
|
||||
| SALES_L 可选描述 | GOODS_NAME、SPEC、MANUFACTURER、UNIT | 按上游原值保留供查看,不作为新增属性匹配门槛 |
|
||||
|
||||
LINE_ID 建议 VARCHAR2(64),数量 NUMBER(18,4),价格 NUMBER(18,6),标识/文号 VARCHAR2(128),描述 VARCHAR2(256)。最终长度和精度按真实接口及目标系统核对。
|
||||
|
||||
### 4.5 合同/返利共用接口头明细 `RPA_JD_ORDER_H / L`
|
||||
|
||||
一份头代表上游明确给定的一组合同及对应返利数据,范围由报文的 SOURCE_DOC_ID 和明细集合确定。首版不按供应商+日期自行合单。若实际存在一组数据对应多份合同/返利,需要上游给出组标识,见第 12 章。
|
||||
|
||||
| 位置 | 建议字段 | 含义/使用流程 |
|
||||
| --- | --- | --- |
|
||||
| ORDER_H | 第 4.3 节公共字段 | 合同和返利共用输入标识 |
|
||||
| ORDER_H | CUSTOMER_NAME、CUSTOMER_TYPE、CREDIT_DAYS | 合同客户名称、预付/账期类型、账期天数 |
|
||||
| ORDER_H | CONTRACT_SIGN_DATE、CONTRACT_START_DATE、CONTRACT_END_DATE | 三个独立合同日期 |
|
||||
| ORDER_H | SUPPLIER_CODE、REBATE_UNIT_CODE、PLANNER、PURCHASER | 返利供应商、返利单位、计划员及采购员对应信息 |
|
||||
| ORDER_H | CHAIN_REF | 订单中心链路/源业务关联;精确结构按正式接口 |
|
||||
| ORDER_L | LINE_ID、INPUT_ID、SOURCE_LINE_ID、ORDER_NO、SOURCE_DETAIL_ID | 本方行 ID、来源行、订单号、货源细单号;订单号与货源细单号分列 |
|
||||
| ORDER_L | GOODS_CODE、GOODS_NAME、REG_CERT_NO、SPEC、UNIT | 标准货品编码/名称、注册证号、规格、单位 |
|
||||
| ORDER_L | PURCHASE_QTY、PURCHASE_UNIT_PRICE | 采购数量和单价;用于合同明细 |
|
||||
| ORDER_L | REBATE_QTY、REBATE_TAX_PRICE | 返利数量和含税协议单价;用于返利明细 |
|
||||
| ORDER_L | PAYMENT_CONDITION,可空 | 实际合同模板需要时从同一报文提供 |
|
||||
|
||||
同一次报文中数量如只有一个来源字段且供两流程共用,映射层同时引用它;不因此要求上游重复传两个数量。采购单价与含税协议单价按 Excel 的不同含义分别映射,未经接口确认不互相代替。
|
||||
|
||||
建议数量 NUMBER(18,4)、价格 NUMBER(18,6)、金额 NUMBER(18,2)、日期 DATE;名称 VARCHAR2(256)、编码 VARCHAR2(128)、类型 VARCHAR2(16)、账期 NUMBER(6)、CHAIN_REF CLOB 或按正式关联键拆列。固定默认值不强迫订单中心传入,在节点执行输入中生成并记录所用模板/映射版本。
|
||||
|
||||
**此处没有 `RPA_JD_CONTRACT_HEAD / RPA_JD_REBATE_HEAD` 两套输入表。两流程取数的 INPUT_ID 必须相同。**
|
||||
|
||||
### 4.6 业务作业与节点记录
|
||||
|
||||
| 表/字段 | 现有或新增 | 详细用途 |
|
||||
| --- | --- | --- |
|
||||
| job.flow_code / flow_version | 新增 VARCHAR(24) / VARCHAR(16) | 旧作业归原主链,新作业区分四条流程 |
|
||||
| job.input_id / input_version | 新增 VARCHAR(64) | 指向 Oracle 成功输入;ORDER_PUSH 指向本方导出批次 ID |
|
||||
| job.source_line_id | 新增 VARCHAR(128),头级作业用空串 | PROCURE 对应一条销售明细;合同/返利对应头 |
|
||||
| job.business_key | 复用 | 改为上述来源关联的展示/定位键;防重以组合唯一约束为准 |
|
||||
| job.status / current_node_code | 复用 | 当前执行状态和恢复节点 |
|
||||
| job.contract_system_no / rebate_order_no | 复用 | 合同 job 保存合同号,返利 job 保存返利总单/协议编号 |
|
||||
| job.next_check_at | 复用 | 可重试失败或外部等待的下次处理时间 |
|
||||
| job.execution_token / execution_host | 新增 VARCHAR(64) / VARCHAR(128) | 当前领取身份,拒绝旧执行回报;记录实际执行机 |
|
||||
| job_node.attempt_no、status、output_json | 复用 | 每次尝试独立记录;输入引用、业务结果和外部编号 |
|
||||
| job_node.started_at / ended_at / duration_ms | 复用 | 操作开始结束及耗时 |
|
||||
| job_node.error_code / error_summary | 复用 | 可筛选错误分类和具体原因 |
|
||||
| job_node.executor_run_id / callback_idempotency_key | 复用 | 一次脚本运行 ID、稳定回报键,重发回报不重复推进 |
|
||||
|
||||
新 flow 的组合唯一约束:`(flow_code, input_id, input_version, source_line_id)`;已成功作业也受约束。flow_version 是实现规则版本,不因升级自动为旧输入创建第二份作业。旧 job 回填其自身唯一 input_id,保留原入口规则。
|
||||
|
||||
共用数据成功接收后登记两份 job:CONTRACT 初始 PENDING,REBATE 初始 WAITING_EXTERNAL,原因为 WAIT_CONTRACT。同一 Oracle 输入即便登记调用重复,也只建立这一对作业。一次接收多组业务对象则逐头建立,不共享一个总完成标志。
|
||||
|
||||
旧列兼容迁移:保留 job_id、business_key、source_type、current_node_code、status、created_by 非空;supplier_name 和 order_date 改为允许空值,新来源数据有值时填入。旧入口继续校验其原必填,不给新作业补造供应商或日期。business_key 由新唯一键各项组合生成可读/哈希值;created_by 记录业务启动人或计划任务账号。
|
||||
|
||||
| flow | source_type | supplier_name / order_date | 首节点 |
|
||||
| --- | --- | --- | --- |
|
||||
| PROCURE | 本方定义 ORDER_CENTER | 供应商为空;日期取销售业务日(若提供) | PROCURE_ITEM_RUN |
|
||||
| ORDER_PUSH | 明确的平台配置代码 | 文件批次可含多供应商,供应商为空;日期不强制单日 | ORDER_EXPORT_FILE |
|
||||
| CONTRACT / REBATE | ORDER_CENTER | 接口明确提供的展示供应商及业务日期;合同三个日期仍取接口独立字段 | CONTRACT_CREATE / REBATE_HEADER |
|
||||
|
||||
source_type 上述取值是本方分类建议;药帮忙使用单独配置代码,不映射成旧 XYY。若任一新增标识超出现有列长度,迁移时扩列。旧 job 赋旧 flow/version 及唯一 input_id,新组合唯一键对全部历史行先回填再建立。
|
||||
|
||||
### 4.7 逐订单推送台账 `JD_ORDER_PUSH`(Oracle 拟新增)
|
||||
|
||||
文件/操作日志不能替代按订单查询的处理标志,因此该表只保存推送控制与结果,不复制完整平台业务表。
|
||||
|
||||
| 字段 | 建议类型 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| id | BIGINT PK | 推送记录 ID |
|
||||
| platform / account_key / source_order_id | VARCHAR(32) / VARCHAR(64) / VARCHAR(128) | 来源订单唯一键,不按文件名去重 |
|
||||
| source_order_no / job_id / artifact_id | VARCHAR(128) / VARCHAR(64) / BIGINT | 展示单号、首次导出 job、原始文件关联 |
|
||||
| content_hash / request_key | VARCHAR(64) / VARCHAR(128) | 订单内容识别和本次固定请求标识 |
|
||||
| push_status | VARCHAR(16) | PENDING、SENDING、ACCEPTED、REJECTED、UNKNOWN |
|
||||
| request_body / response_body | JSON | 本次实际结构化发送内容、返回内容或所属批次引用 |
|
||||
| receipt_no / error_code / error_message | VARCHAR(128) / VARCHAR(64) / VARCHAR(500) | 回执及失败原因 |
|
||||
| attempt_count / next_retry_at | INT / DATETIME | 发送次数及重试时间 |
|
||||
| created_at / last_sent_at / accepted_at | DATETIME | 关键时点 |
|
||||
|
||||
唯一键 `(platform, account_key, source_order_id)`;相同订单相同内容复用结果,内容变化不能覆盖旧成功记录,记录冲突交运维核对。每次发送详细历史写 job_event,最近状态保存在此表。多个文件含同一订单时,在各导出 job 的日志中记录对应 artifact_id 与台账 id。
|
||||
|
||||
接口若支持批量发送,固定请求键绑定整份实际请求体;同键重发内容不变。逐单成功的回执更新对应台账;只发失败子集时生成新请求键并保留与原批次关联。没有逐单回执时按对方整批接收语义记录,不凭本方猜部分成功。
|
||||
|
||||
## 5. 详细设计:标志字段与取数条件
|
||||
|
||||
### 5.1 状态来源
|
||||
|
||||
| 业务标志 | 保存/计算位置 | 何时改变 |
|
||||
| --- | --- | --- |
|
||||
| 接收是否成功 | IF_REQ.RECEIVE_STATUS | 当前接收请求完成时 |
|
||||
| 作业是否登记 | IF_REQ.REGISTER_STATUS | 在 Oracle 幂等建立本次所需 job 后 |
|
||||
| 选品处理结果 | PROCURE job_node.output_json.result | 加购或正常跳过回报时:ADDED / SKIPPED |
|
||||
| 合同保存状态 | CONTRACT_CREATE 节点状态及 result | 开始、保存成功、失败或结果未知回报 |
|
||||
| 链路更新状态 | CONTRACT_CHAIN_UPDATE 节点状态及 result | 合同保存后调用订单中心,单独记录结果 |
|
||||
| 返利状态 | REBATE job.status / current_node_code | 等合同、执行、失败、提交成功 |
|
||||
| 平台订单接收状态 | JD_ORDER_PUSH.push_status | 每次发送和回执记录 |
|
||||
|
||||
运维接口返回 `contract_save_status / chain_update_status / rebate_status` 三列,是以上权威记录的汇总字段,**不再写回接口表形成第二套状态**。当前节点按 job 指针及该节点最新 attempt 取值,历史成功节点保留,不用整个合同 job.status 代替保存状态。
|
||||
|
||||
### 5.2 状态转换
|
||||
|
||||
| job 状态 | 含义 | 后续动作 |
|
||||
| --- | --- | --- |
|
||||
| PENDING | 已登记,等待执行 | 对应脚本领取 |
|
||||
| RUNNING | 已被领取且正在执行 | 接收匹配 execution_token 的回报 |
|
||||
| SUCCESS | 本流程应做步骤完成 | 常规取数不再领取 |
|
||||
| FAILED_RETRYABLE | 明确失败,可重试当前步骤 | 到 next_check_at 后或运维重试,新增 attempt |
|
||||
| WAITING_EXTERNAL | 前置尚未完成或结果待核对 | 按 wait_reason 处理,不能一律自动重做 |
|
||||
| WAITING_USER / FAILED_MANUAL | 业务或运维需介入 | 在原待办上处理、核对并记录动作 |
|
||||
| CANCELLED | 明确停止办理 | 保留原数据和历史 |
|
||||
|
||||
wait_reason 建议保存在 job 新增 VARCHAR(32) 字段:WAIT_CONTRACT、PROTOCOL_NOT_READY、RESULT_UNKNOWN。前两种下次检查条件;RESULT_UNKNOWN 需要核对外部结果,确认失败才可重试。
|
||||
|
||||
节点复用 PENDING/RUNNING/SUCCESS/FAILED,未知结果用节点 `WAITING_EXTERNAL` 并在 output_json 记 `result=UNKNOWN`;需要新增相应前后端状态支持。未尽调转人工保留待办,不能把“停止自动办理”显示为“合同已保存”。
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> PENDING
|
||||
PENDING --> RUNNING: 领取
|
||||
RUNNING --> SUCCESS: 完成
|
||||
RUNNING --> FAILED_RETRYABLE: 明确可重试失败
|
||||
RUNNING --> WAITING_EXTERNAL: 前置等待或结果未知
|
||||
RUNNING --> WAITING_USER: 业务处理
|
||||
FAILED_RETRYABLE --> PENDING: 重试当前步骤
|
||||
WAITING_EXTERNAL --> PENDING: 前置满足或核对未成功
|
||||
WAITING_EXTERNAL --> SUCCESS: 核对本流程已完成
|
||||
WAITING_USER --> PENDING: 处理后恢复未完成步骤
|
||||
PENDING --> CANCELLED: 停止办理
|
||||
```
|
||||
|
||||
上图为通用 job 状态。合同保存结果核对成功时若链路尚未更新,实际转移到链路节点 PENDING,不能直接标整个合同 SUCCESS。
|
||||
|
||||
### 5.3 领取规则
|
||||
|
||||
| flow | 读取范围及条件 | 成功/失败记录 |
|
||||
| --- | --- | --- |
|
||||
| PROCURE | SALES 数据 READY;行 job=PENDING,或到期可重试 | 合格 ADDED、不合格 SKIPPED 均结束行 job;错误单独失败 |
|
||||
| ORDER_PUSH | 对应平台导出任务及历史未完成台账;ACCEPTED 不重发 | 归档文件及每单 push_status |
|
||||
| CONTRACT | ORDER_COMMON 数据 READY;合同 job 待执行,按 current_node 恢复 | CREATE 成功保存合同号;CHAIN 失败只重试 CHAIN |
|
||||
| REBATE | 同 INPUT_ID/版本的 CREATE 节点确认 SUCCESS,合同号非空;返利待执行或到期重试 | 从共用接口表读取返利字段;读取合同 job 合同号;提交后 SUCCESS |
|
||||
|
||||
新录入与重试均覆盖历史未完成记录,不只筛选当天。创建返利 job 时可处于 WAIT_CONTRACT,合同失败不改它的接口输入;合同成功后,返利下一次扫描将其转 PENDING。
|
||||
|
||||
领取使用 Oracle 短事务条件更新或行锁,同时检查状态并写 execution_token;不能先查询再无条件改状态。脚本中断保留 RUNNING,运维确认原进程已停止后释放并恢复;本期不以超时自动启动第二个执行器。
|
||||
|
||||
### 5.4 节点定义与订单导出生命周期
|
||||
|
||||
| flow | 节点链 | 正常结束条件 |
|
||||
| --- | --- | --- |
|
||||
| PROCURE | PROCURE_ITEM_RUN | 本行 ADDED 或 SKIPPED |
|
||||
| ORDER_PUSH | ORDER_EXPORT_FILE → ORDER_CENTER_PUSH | 文件完整归档后,解析及推送该批应发送订单,均确认接收 |
|
||||
| CONTRACT | CONTRACT_CREATE → CONTRACT_CHAIN_UPDATE | 保存及链路更新分别成功;未尽调为当前节点人工待办,不另建供数流程 |
|
||||
| REBATE | REBATE_HEADER → REBATE_IMPORT → REBATE_SUBMIT | 正常提交成功;首次领取前检查合同保存条件 |
|
||||
|
||||
ORDER_PUSH 脚本先领取历史未完成批次;再按当前排期需要产生 export_batch_id 并登记新 job(input_id=export_batch_id,input_version=1,source_line_id=空串)。新 job 从 ORDER_EXPORT_FILE 领取执行。导出失败保留该 job;已保存文件和路径写 job_artifact,node_code=ORDER_EXPORT_FILE,恢复时先查已有归档文件。
|
||||
|
||||
归档成功后推进 ORDER_CENTER_PUSH:解析文件、按源订单建立/复用 JD_ORDER_PUSH、保存固定请求并推送。本批所有源订单到台账 ID 的关联写入该 job 的节点 output_json(大批量可用 artifact 保存清单),因此同一台账复用于多个文件时仍可判断每个批次是否完成。首次 job_id/artifact_id 不覆盖,后续文件关联由各批次记录持有。
|
||||
|
||||
源订单均为 ACCEPTED,或本轮确无可推送订单时,批次 job SUCCESS;零数据结果记 NO_DATA 并显示零条,不创建空订单。部分失败保留当前推送节点,已接收项跳过;同订单不同内容记冲突,阻止该批盲目完成,交运维核对。脚本退出与该批次 job 成功是两件事。
|
||||
|
||||
## 6. 详细设计:接口与交互时序
|
||||
|
||||
### 6.1 接口清单
|
||||
|
||||
| 接口/方向 | 状态 | 主要契约 |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/jieda/inbound/sales` | 本方拟新增 | 完整销售对象,接收校验后落表 |
|
||||
| `POST /api/jieda/inbound/order-data` | 本方拟新增 | **一次传入合同及返利共同需要的数据**,返回接收记录 ID 和 INPUT_ID |
|
||||
| `POST /api/flows/{flow_code}/jobs/claim` | 本方拟新增 | 指定本轮 run_id,补登记后领取;返回 job、节点、attempt、执行令牌及输入关联 |
|
||||
| `POST /api/flows/ORDER_PUSH/jobs` | 本方拟新增 | 导出前按 export_batch_id 幂等登记平台/账号/范围及 job,导出失败也有作业可查 |
|
||||
| `GET /api/jobs/{job_id}/input` | 本方拟新增 | 对应接口头明细;返利额外返回关联合同 job 及合同号 |
|
||||
| `POST /api/jobs/{job_id}/nodes/{node}/{action}` | 复用 start/complete/fail 并扩展 | 结果、编号、错误、证据、attempt 和执行令牌;未知结果单独表达 |
|
||||
| `POST /api/jobs/{job_id}/push-records` | 本方拟新增 | 按源订单登记固定请求、逐单结果;校验所属 ORDER_PUSH 作业 |
|
||||
| `POST /api/jobs/{job_id}/retry` | 复用并修正 | 只恢复当前失败步骤,记录操作者及原因 |
|
||||
| 平台订单接收 API(本方→订单中心) | 外部契约待提供 | 来源订单、商品数量金额及实际接收回执 |
|
||||
| 链路节点更新 API(本方→订单中心) | 外部契约待提供 | 来源链路关联、对应节点、已保存合同标识及业务结果 |
|
||||
|
||||
订单推送由 ORDER_PUSH 脚本、链路更新由 CONTRACT 脚本通过统一客户端调用。调用前通过状态中心记录固定请求,调用后保存回执。状态中心不同时后台发送同一请求。外部正式路径、业务枚举和鉴权由订单中心提供。
|
||||
|
||||
### 6.2 请求、校验及重复处理
|
||||
|
||||
接收报文在本方适配层统一表示为 `message_id + objects[]`。每个对象有 source_doc_id、input_version(可选)、header、lines。ORDER_COMMON 的 header/lines 包含第 4.5、7 章共同字段,不是分别推 contract 和 rebate。
|
||||
|
||||
| 返回情况 | 本方建议 HTTP | 业务返回及持久化 |
|
||||
| --- | --- | --- |
|
||||
| 缺字段/格式不合法/空明细 | 422 | REJECTED,errors 含对象标识、行标识及字段路径;无业务头明细 |
|
||||
| 全部校验通过且保存成功 | 200 | ACCEPTED、request_log_id、input_ids;表示接收成功 |
|
||||
| 已接收同消息同内容 | 200 | DUPLICATE,返回原 input_ids,另记接收尝试 |
|
||||
| 已接收同标识不同内容 | 409 | CONFLICT,旧成功输入及作业不变 |
|
||||
| 保存失败 | 503 | STORE_FAILED+trace_id;不返回接收成功 |
|
||||
| 鉴权失败 | 401/403 | 拒绝,不建立业务作业 |
|
||||
|
||||
首版整次请求校验通过后,同一个 Oracle 事务保存成功接收记录及全部对象头明细。任一必填缺失则整次失败,返回全部可识别字段错误;上游可按完整单据分批发送。缺的是返利字段也当次返回,因为这一份数据承担两个流程。
|
||||
|
||||
客户类型、三个合同日期、合同明细字段,以及供应商/返利单位/计划员/订单号/货源细单号/含税协议单价在本次共用数据中按第 7 章校验。合同号是本方执行结果,接收时允许不存在。固定值、系统生成字段、页面尽调回显不要求上游提供。
|
||||
|
||||
相同业务对象换 message_id 再传,由头表来源+版本唯一键识别:内容相同引用原 INPUT_ID,内容不同冲突。没有源版本号时,哈希只判断内容同异,不自动判定“哪条更新”。已成功接收的同 SOURCE_DOC_ID 改内容或改版本,本期返回 INPUT_CONFLICT,不自动创建第二组合同/返利 job;这是按业务标识检查,不能仅依赖版本唯一索引。运维和订单中心确认改单处理后再另行安排,原作业和结果保持可查。此前校验失败的修正重传可正常接收。
|
||||
|
||||
接收失败日志存 IF_REQ;Oracle 本身不可用时只能写应用错误日志和 trace_id,不能宣称数据库必有失败记录。响应在事务提交后产生;若响应丢失,上游重传可取得原接收结果。
|
||||
|
||||
### 6.3 时序图:一次接收,两流程共用
|
||||
|
||||
**接收及合同处理。** 请求内校验并落表;合同排期触发后读取这份数据。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant OC as 订单中心
|
||||
participant API as 状态中心
|
||||
participant IF as 共用接口表
|
||||
participant C as 合同脚本
|
||||
participant CT as 合同系统
|
||||
OC->>API: 一次推送合同返利共用数据
|
||||
API->>API: 校验两流程上游必填
|
||||
alt 校验失败
|
||||
API->>IF: 记REJECTED,不建业务头明细
|
||||
API-->>OC: 失败及字段位置
|
||||
else 校验通过
|
||||
API->>IF: 同事务保存接收记录和头明细
|
||||
IF-->>API: 提交成功 INPUT_ID
|
||||
API-->>OC: 接收成功 INPUT_ID
|
||||
C->>API: 按合同排期领取
|
||||
API->>API: 幂等登记同输入的合同/返利job
|
||||
API->>IF: 按INPUT_ID读取输入并记登记完成
|
||||
API-->>C: 合同job及共用数据
|
||||
C->>CT: 录入并保存
|
||||
CT-->>C: 保存成功及合同号
|
||||
C->>API: 回报保存结果,推进至链路节点
|
||||
C->>OC: 更新对应链路节点
|
||||
OC-->>C: 更新回执
|
||||
C->>API: 保存链路结果
|
||||
end
|
||||
```
|
||||
|
||||
**返利在另一排期继续。** 返利不再向订单中心获取另一份数据,按相同 INPUT_ID 读取接口表,并取得本方关联合同结果。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as 返利脚本
|
||||
participant API as 状态中心
|
||||
participant DB as 业务记录
|
||||
participant IF as 同一共用接口表
|
||||
participant RB as 返利中心
|
||||
R->>API: 按返利排期领取
|
||||
API->>DB: 检查同INPUT_ID的合同保存结果
|
||||
alt 合同未保存
|
||||
API->>DB: 返利保持WAIT_CONTRACT
|
||||
API-->>R: 本条暂不领取,继续其他数据
|
||||
else 合同保存成功且合同号存在
|
||||
API->>IF: 读取相同INPUT_ID和版本
|
||||
IF-->>API: 原头明细中的返利字段
|
||||
API-->>R: 返利数据加本方合同号
|
||||
R->>RB: 查协议、填总单、导明细并提交
|
||||
RB-->>R: 提交结果
|
||||
R->>API: 回报返利结果
|
||||
API->>DB: 保存节点、编号和操作日志
|
||||
end
|
||||
```
|
||||
|
||||
两图通过同一 INPUT_ID 衔接,不要求两脚本连续运行。返利先启动时可先完成作业登记,但保持 WAIT_CONTRACT。
|
||||
|
||||
### 6.4 时序图:平台订单归档推送
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as 订单脚本
|
||||
participant P as 平台
|
||||
participant F as 共享文件夹
|
||||
participant API as 状态中心
|
||||
participant OC as 订单中心
|
||||
R->>P: 按约定范围导出订单
|
||||
P-->>R: 完整文件
|
||||
R->>F: 保存到对应平台目录
|
||||
alt 保存失败
|
||||
R->>API: 回报文件错误,保留失败步骤
|
||||
else 保存成功
|
||||
R->>API: 登记文件、源订单及固定请求
|
||||
API-->>R: 未接收订单和请求键
|
||||
R->>OC: 按正式格式推送
|
||||
alt 接收确认
|
||||
OC-->>R: 业务接收回执
|
||||
R->>API: 更新逐订单台账,记录成功/拒绝项
|
||||
else 超时或连接中断
|
||||
R->>API: 记录UNKNOWN及实际请求
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
共享目录保存后再组装推送。文件名冲突保留新批次路径,不覆盖原件;导出失败恢复导出,已归档的文件恢复时直接使用原件。
|
||||
|
||||
### 6.5 时序图:合同保存成功、链路更新失败
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 合同脚本
|
||||
participant CT as 合同系统
|
||||
participant API as 状态中心
|
||||
participant DB as Oracle
|
||||
participant OC as 订单中心
|
||||
C->>CT: 保存合同
|
||||
CT-->>C: 成功 HT001
|
||||
C->>API: CREATE complete,合同号和证据
|
||||
API->>DB: 保存CREATE成功,当前节点改CHAIN_UPDATE
|
||||
C->>API: 记录链路固定请求
|
||||
C->>OC: 链路更新
|
||||
OC-->>C: 明确失败或无响应
|
||||
C->>API: 记录FAILED或UNKNOWN及原因
|
||||
API->>DB: 保留合同号和失败位置
|
||||
Note over C,DB: 脚本继续下一单;运维可见保存成功、链路异常
|
||||
C->>API: 下次仅领取当前链路节点
|
||||
Note over C,OC: UNKNOWN先核对;明确可重试才再次调用
|
||||
C->>OC: 使用约定幂等请求更新
|
||||
OC-->>C: 更新成功
|
||||
C->>API: CHAIN complete及回执
|
||||
API->>DB: 完成合同job;原CREATE记录不变
|
||||
```
|
||||
|
||||
## 7. 详细设计:业务字段映射
|
||||
|
||||
本章保留 Excel 的实际字段和行号。合同/返利列来源都指向第 4.5 节同一份接口输入;按各自模板生成执行数据。生成的模板、默认日期及映射版本记录到节点 output_json/文件证据,不再复制成独立合同或返利输入表。
|
||||
|
||||
### 7.1 合同头
|
||||
|
||||
| 目标字段 | 取值规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 客户名称 | 订单中心 | 12 |
|
||||
| 客户编码 | 目标系统自动生成 | 11 |
|
||||
| 尽调情况 | 读取回显;未尽调停止本单自动录入并转采购员处理 | 13 |
|
||||
| 结算类型 | 收支双向 | 15 |
|
||||
| 合同版本类型 | 预付:我司定制;账期:非格式合同 | 17 |
|
||||
| 合同金额(单位元) | 预估本次合作金额 | 24 |
|
||||
| 金额(元) | 明细汇总;与导入后页面汇总核对并回填 | 25 |
|
||||
| 合同签订日期、合同开始日期、合同结束日期 | 订单中心分别提供,三个独立字段 | 26–28 |
|
||||
| 是否重大合同 | 否 | 29 |
|
||||
| 是否预付款 | 预付:是;账期:否 | 30 |
|
||||
| 付款方式 | 电汇 | 32 |
|
||||
| 付款期限 | 预付:1;账期:订单中心账期天数,缺失不默认 30 | 33 |
|
||||
| 合同具体分类 | 国内药品类、器械类品种 | 37 |
|
||||
| 运输方式 | 公路运输 | 39 |
|
||||
|
||||
### 7.2 合同明细
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码 | 订单中心 | 43 |
|
||||
| 货品名称 | 订单中心标准货品名称 | 44 |
|
||||
| 注册证号 | 订单中心 | 46 |
|
||||
| 规格、单位 | 按订单中心标准字段填报 | 47–48 |
|
||||
| 数量、单价 | 按订单中心最终采购数量、单价填报 | 49–50 |
|
||||
| 付款方式 | 电汇 | 52 |
|
||||
| 付款条件 | 订单中心;模板无此字段时不填 | 53 |
|
||||
| 付款期限 | 同合同头规则 | 54 |
|
||||
|
||||
### 7.3 返利总单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 返利合约 | 否 | 5 |
|
||||
| 供应商编码 | 订单中心 | 6 |
|
||||
| 返利单位编码 | 订单中心 | 7 |
|
||||
| 计划员 | 订单中心,按业务给定的计划员/采购员对应关系填写 | 8 |
|
||||
| 采购结算方式 | 文案“否”;与细单代码 3 分别映射 | 9 |
|
||||
| 承付模式 | 下月 N 号结 | 10 |
|
||||
| 协议开始日期、协议结束日期 | 当天 | 11–12 |
|
||||
|
||||
### 7.4 返利细单
|
||||
|
||||
| 目标字段 | 来源/规则 | 行号 |
|
||||
| --- | --- | --- |
|
||||
| 货品编码、数量、含税协议单价 | 本次共用接口数据的货品编码、数量及含税协议单价 | 15–17 |
|
||||
| 税率、关税税率 | 均为 0.13 | 18–19 |
|
||||
| 承付模式、承付参数 | 11、25 | 20–21 |
|
||||
| 细单开始日期 | 当天 | 22 |
|
||||
| 细单结束日期 | 本年度最后一天 | 23 |
|
||||
| 采购结算方式 | 3 | 24 |
|
||||
| 特定补偿预估值、进货补偿预估值 | 均为 0 | 25–26 |
|
||||
| 细单备注 | 订单中心订单号 | 33 |
|
||||
| 货源细单ID | 订单中心货源细单号;与订单号分列 | 34 |
|
||||
|
||||
合同明细导入列按 Excel 的 Sheet3。表中标注无需填写的字段不填;系统自动生成字段由系统处理。
|
||||
|
||||
|
||||
### 7.5 映射与运行日期规则
|
||||
|
||||
- 合同与返利的数量来自同一次报文;实际只提供一列数量时共用该列。含税协议单价与采购单价分别保留来源映射。
|
||||
- 首次执行该流程时生成所用默认日期及模板记录。重试已生成的相同步骤使用原输入,避免跨日重试更改已导入单据;新返利作业按返利首次执行当天生成“当天/年底”。这是实现规则,不增加业务确认步骤。
|
||||
- 合同客户编码为系统生成字段。现有 RPA 入口要求的 customerCode 实际用作客商搜索标识,须与系统自动生成的客户编码分清;按新页面取数方式适配,不要求上游为自动生成字段填占位符。
|
||||
- 映射表标注无需填写的字段不填。尽调读取页面回显;未尽调按原流程转采购员并通知。接口数据保留,人工完成后核对实际合同号并登记保存成功,进入链路更新步骤;返利按第 5.3 节条件领取。
|
||||
|
||||
## 8. 详细设计:操作记录与一致性
|
||||
|
||||
### 8.1 最少需要记录的操作信息
|
||||
|
||||
| 场景 | 保存位置 | 必须能查到的信息 |
|
||||
| --- | --- | --- |
|
||||
| 每次接口接收 | IF_REQ | 请求时间、来源消息、INPUT_ID 关联、原报文、返回结果、字段错误、trace_id |
|
||||
| 脚本执行一步 | job_node | flow、job、INPUT_ID/版本、节点、attempt、run_id、执行机、开始结束、结果、错误 |
|
||||
| 平台订单发送一次 | JD_ORDER_PUSH+job_event | 原文件、源订单、固定请求键、实际请求、HTTP/业务返回、耗时、成功/拒绝范围 |
|
||||
| 合同保存 | job_node+job_artifact | 实际合同标识、保存确认、目标页面证据、所用输入和模板 |
|
||||
| 链路调用一次 | job_event+链路 job_node | 链路关联、节点代码、合同号、请求键、报文、回执、错误 |
|
||||
| 运维操作一次 | job_event / user_task | 操作人、时间、动作、原因、前后状态、证据和操作结果 |
|
||||
| 文件 | job_artifact | 原始订单、导入模板、截图或日志文件路径,文件类型及哈希 |
|
||||
|
||||
job_event 建议事件类型包括 INPUT_BOUND、NODE_STARTED、NODE_SUCCESS、NODE_FAILED、API_REQUEST、API_RESPONSE、RETRY_REQUESTED、RESULT_VERIFIED、MANUAL_HANDOFF、JOB_FINISHED。detail_json 保存结构化信息。节点列表用 job_node.error_code/error_summary 筛选及展示;事件详情从 detail_json.errorCode/errorMessage 读取错误,不假定 job_event 存在独立错误列。详细错误与报文在详情展开。
|
||||
|
||||
接收失败没有 job 时由 IF_REQ 独立展示;所有能定位到作业的动作都写 job_event。脚本运行 ID 使用现有 executor_run_id;无数据或启动失败的本轮摘要写应用日志,通过 run_id 查询,暂不新增运行历史表。
|
||||
|
||||
### 8.2 事务及恢复顺序
|
||||
|
||||
| 写入过程 | 事务范围 | 恢复办法 |
|
||||
| --- | --- | --- |
|
||||
| 接收成功 | Oracle:成功请求记录+所有接口头明细 | 事务失败整体返回失败;响应丢失可同消息重传 |
|
||||
| 登记作业 | Oracle:同输入的合同和返利 job 一起幂等建立 | 成功后回写 IF_REQ.REGISTER_STATUS;回写失败下次重复登记返回同 job,再补 DONE |
|
||||
| 节点成功 | Oracle:该 attempt 成功+业务编号+操作日志/证据索引+推进当前节点 | 回报丢失重发同完成键;同成功结果返回原状态,不再次推进 |
|
||||
| 外部接口调用 | 先 Oracle 保存请求,事务提交后调用,返回后独立事务记结果 | 超时保留 UNKNOWN,不把网络调用放在数据库事务中 |
|
||||
| 人工恢复 | Oracle:核查结果、状态变更和操作日志 | 同一操作请求 ID 重复提交返回原结果 |
|
||||
|
||||
Oracle 接口输入和 Oracle 执行结果不要求同库事务,也不把执行状态反复写回 Oracle。接口表登记标志失败最多导致再次尝试登记,组合唯一约束保证不多建合同/返利作业。运维总览查两库时若 Oracle 不可用,仍可展示 Oracle 已保存的执行结果,并标记“接口原文暂不可查”,不能误显示数据不存在。
|
||||
|
||||
### 8.3 文件和配置
|
||||
|
||||
1 药城与药帮忙分别配置共享根目录和字段映射;现场路径由配置提供。下载完成后发布文件,文件名按批次区分,保留原件。后台返回文件列表,业务/运维通过现有证据入口打开或下载,不要求共享盘在所有机器使用同一盘符。
|
||||
|
||||
订单中心连接、凭据、超时、链路节点映射、四条排期、重试间隔及模板配置集中管理。真实环境缺必要配置返回明确错误,不自动使用 fixture。日期、数量和金额按第 4、7 章数据类型保存,日志不保存 token 或数据库密码。
|
||||
|
||||
## 9. 详细设计:正常路径与异常处理
|
||||
|
||||
### 9.1 正常路径示例
|
||||
|
||||
本例标识仅用于说明字段关系,不代表正式接口报文。
|
||||
|
||||
| 时间/动作 | 接口记录 | 合同保存 | 链路更新 | 返利 | 记录位置 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 接收 M001 | ACCEPTED,INPUT_ID=I001,REGISTER=PENDING | 未登记 | 未登记 | 未登记 | IF_REQ+ORDER_H/L |
|
||||
| 任一相关脚本启动登记 | REGISTER=DONE | PENDING | 未开始 | WAIT_CONTRACT | 两个 job 都绑定 I001/1 |
|
||||
| 合同脚本处理 | 原输入不变 | RUNNING | 未开始 | WAIT_CONTRACT | CREATE 第 1 次尝试 |
|
||||
| 合同保存成功 HT001 | 原输入不变 | SUCCESS | PENDING | 可领取 | 合同号及保存证据 |
|
||||
| 更新链路成功 | 原输入不变 | SUCCESS | SUCCESS | 可领取 | 链路请求、回执;合同 job 完成 |
|
||||
| 返利下次运行 | 原输入不变 | SUCCESS | SUCCESS | RUNNING | 读取 I001 同一头明细+HT001 |
|
||||
| 返利提交完成 FL001 | 原输入不变 | SUCCESS | SUCCESS | SUCCESS | 返利编号、模板及提交结果 |
|
||||
|
||||
### 9.2 异常矩阵
|
||||
|
||||
| 异常代码(建议) | 识别条件 | 状态和保留内容 | 处理及恢复位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| INPUT_REQUIRED | 本次共用数据缺合同或返利上游必填 | IF_REQ=REJECTED,字段路径/行号,无业务输入 | 上游补齐后重传;RPA 无需处理 |
|
||||
| INPUT_CONFLICT | 成功来源/版本再传不同内容 | CONFLICT,关联旧请求、保存新原文 | 核对来源及版本;不覆盖已使用数据 |
|
||||
| STORE_FAILED | 接口表事务失败 | 返回 503;应用日志 trace_id | 修复数据库后重传 |
|
||||
| JOB_REGISTER_FAILED | Oracle 已接收,Oracle 登记失败 | REGISTER=FAILED、原因;输入 READY | 恢复 Oracle,下一次脚本取数补登记 |
|
||||
| PLATFORM_QUERY_FAILED | 页面打不开或查询报错 | 行 job 明确失败,截图/错误 | 修复后重试该行;不记 SKIPPED |
|
||||
| NO_ELIGIBLE_PRODUCT | 查询成功但不满足采购条件 | job SUCCESS,result=SKIPPED、原因 | 正常结束,常规轮询不重复处理 |
|
||||
| FILE_SAVE_FAILED | 共享目录不可写/文件未完整 | ORDER_PUSH 失败、已知路径 | 修复目录后从导出/归档失败处继续 |
|
||||
| OC_ORDER_REJECTED | 订单中心明确业务拒收 | push=REJECTED,拒绝原因及范围 | 修正接口映射/源数据后处理失败单,成功单保留 |
|
||||
| EXTERNAL_RESULT_UNKNOWN | 加购、保存、推送或提交后无确定结果 | WAITING_EXTERNAL/UNKNOWN,请求或页面证据 | 先核对目标结果;确认未成功才重试 |
|
||||
| CONTRACT_DUE_DILIGENCE | 合同页面显示未尽调 | WAITING_USER、采购员待办和页面记录 | 按原业务人工处理,核验保存结果后继续链路 |
|
||||
| CHAIN_UPDATE_FAILED | 合同已保存,链路明确失败 | CREATE 成功;CHAIN 失败、回执 | 下次仅重试 CHAIN;合同号不清空 |
|
||||
| PROTOCOL_NOT_READY | 已有合同号,但返利协议不可查/暂不可操作 | 返利 WAITING_EXTERNAL,原因及检查时间 | 到期再查协议;不重建合同,不等第二次供数 |
|
||||
| REBATE_SUBMIT_FAILED | 返利系统明确提交失败 | 失败步骤、目标协议/总单、已导入信息 | 按原业务交人工处理;保留前面成功步骤 |
|
||||
| EXECUTOR_INTERRUPTED | 作业仍 RUNNING,进程已退出 | 执行机/run_id、最近节点、证据 | 运维确认无运行进程并核对结果后,恢复当前步骤 |
|
||||
|
||||
所有正常失败恢复保留原 INPUT_ID、job 和历史 attempt。通用“重试”只增加当前失败节点尝试,不把 job 从第一个节点重新执行。已完成外部写入不能仅凭“失败”按钮反向当作没有发生。
|
||||
|
||||
## 10. 运维设计:如何查看与处理
|
||||
|
||||
### 10.1 查询入口及页面内容
|
||||
|
||||
复用现有列表、节点详情、时间线、待办和证据组件,新增接收列表和“同一输入的合同/返利总览”,不用另建运维平台。
|
||||
|
||||
| 页面 | 筛选条件 | 列表/详情内容 |
|
||||
| --- | --- | --- |
|
||||
| 接口接收列表 | 时间、来源单号、MESSAGE_ID、TRACE_ID、接收状态、登记状态 | 请求/返回原文、缺字段路径、INPUT_ID、重复来源、登记失败原因 |
|
||||
| 合同返利总览 | INPUT_ID、来源单号、合同号、返利号、三个业务状态、时间 | 同一行显示合同保存、链路更新、返利状态及最近错误;点击进入各 job |
|
||||
| 作业详情 | flow、job_id、状态、执行机、run_id | 来源输入、当前节点、每次尝试、耗时、错误、回报、外部编号 |
|
||||
| 订单推送列表 | 平台/账号、订单号、导出批次、push_status | 原文件、发送内容、回执、次数、结果未知/拒收原因 |
|
||||
| 操作和证据 | job/INPUT_ID、操作者、动作时间 | 谁点击重试/人工处理,前后状态、原因、截图、模板及日志 |
|
||||
|
||||
“合同返利总览”由后端以 Oracle INPUT_ID 为基底,合并两份 Oracle job/节点结果;不存在 job 时显示未登记。分页/筛选由后端实现,不要求浏览器一次拉全表。只有接口原文不可查时保留错误提示,已存在的执行结果仍显示。
|
||||
|
||||
### 10.2 典型查看路径
|
||||
|
||||
| 运维问题 | 如何查 | 结论/下一步 |
|
||||
| --- | --- | --- |
|
||||
| 订单中心说推过,RPA 没处理 | 先查 MESSAGE_ID/来源单号的接收记录 | REJECTED 看缺字段;ACCEPTED+登记失败看数据库错误;DONE 看对应 job |
|
||||
| 合同存在,为什么流程显示失败 | 总览看合同保存和链路更新两列 | 保存成功/链路失败只补链路调用,不重建 |
|
||||
| 返利为什么一直没开始 | 看同 INPUT_ID 的合同保存结果及返利 wait_reason | 合同未保存则正常等待;保存成功但协议不可查则按目标状态处理 |
|
||||
| 同一批有的成功有的失败 | 订单推送按源订单筛选,选品按行 job 查看 | 重试失败项;已接收/已加购项不重复处理 |
|
||||
| 数据被改过或重复推过 | 接收尝试记录+DATA_HASH+操作日志 | 明确首次输入、重复/冲突报文及处理人 |
|
||||
|
||||
### 10.3 运维动作及权限
|
||||
|
||||
| 动作 | 允许条件 | 实际改变 | 必须留痕 |
|
||||
| --- | --- | --- | --- |
|
||||
| 重试当前步骤 | FAILED_RETRYABLE,且外部结果已明确 | 当前节点新增 attempt,job 转 PENDING | 操作人、原因、旧状态、新 attempt |
|
||||
| 重新登记作业 | 已 ACCEPTED 且 REGISTER≠DONE | 幂等补建缺失 job,完成登记标志 | 接收记录、结果、操作者 |
|
||||
| 核对结果:已成功 | UNKNOWN 或人工完成,提供实际业务编号/回执证据 | 确认对应节点成功并推进;合同保存后仍执行链路 | 目标编号、证据、核对人和时间 |
|
||||
| 核对结果:未成功 | 确认目标未写入,原执行进程已停止 | 转可重试当前步骤 | 核对依据、操作者、原因 |
|
||||
| 转业务处理 | 业务错误或系统要求人工 | 创建/复用现有 user_task,保存责任人 | 原因、接收人、操作记录 |
|
||||
| 停止办理 | 有明确业务指令 | CANCELLED,保留已产生结果 | 原因及外部已发生操作,不自动撤销外部单据 |
|
||||
|
||||
只读运维可看列表及证据;恢复动作限定 OPS,业务待办按采购员等现有角色处理。恢复入口必须核对实际操作者身份,不能只相信请求体里填写的 operator。沿用现有鉴权方案补动作权限,不另加业务审批流。
|
||||
|
||||
不能用“统一确认并继续”替代所有核对动作,不能直接改接口原始字段或数据库状态。金额、货品等源数据错误由订单中心修正重传;已被使用的输入按冲突规则处理。
|
||||
|
||||
### 10.4 查询 API 及日志定位
|
||||
|
||||
| API(建议) | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /api/jieda/receipts`、`/receipts/{request_log_id}` | 无 job 的失败接收也能查 |
|
||||
| `GET /api/jieda/orders`、`/orders/{input_id}` | 同一输入的合同/链路/返利总览及头明细 |
|
||||
| `GET /api/jieda/order-pushes` | 按源订单查推送及文件 |
|
||||
| 现有 `GET /api/jobs/{job_id}`、`/timeline`、`/evidence-package` | 节点、历史尝试和证据 |
|
||||
| `GET /api/jobs/{job_id}/events`(新增) | 操作日志和出站请求/返回记录 |
|
||||
| `POST /api/jieda/receipts/{id}/register`(新增) | 运维手动补登记,与脚本取数复用同一逻辑 |
|
||||
| `POST /api/jobs/{job_id}/verify-result`(新增) | 按第 10.3 节核对 UNKNOWN/人工结果,记录依据后恢复 |
|
||||
|
||||
应用日志统一携带 `trace_id、input_id、job_id、flow_code、node_code、attempt_no、run_id` 中当前已知项。接收阶段没有 job_id 时不编造;执行阶段没有 HTTP trace 时以 job/run 定位。列表展示最近错误,详情保留全部历史,不只覆盖最后一次失败。
|
||||
|
||||
索引建议:接口记录按时间/状态/来源,业务头按源单号,job 按 `(flow_code,status,next_check_at)` 和 `(input_id,input_version)`,节点按现有 `(job_id,node_code,attempt_no)`,事件按 `(job_id,created_at)`,推送按来源唯一键及 `(push_status,next_retry_at)`。
|
||||
|
||||
## 11. 实施影响与验收
|
||||
|
||||
### 11.1 现有代码到新设计的映射
|
||||
|
||||
| 现有位置 | 已核对现状 | 实施改动 |
|
||||
| --- | --- | --- |
|
||||
| [main.py 建单](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:499) | 供应商+日期键,固定首节点,只复用未完成作业 | 新接收/登记/领取按 flow 和 INPUT_ID;已完成作业也防重 |
|
||||
| [schema.sql](/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql:11) | job 无 flow/input 关联;已有节点、日志及证据表 | 第 4 章 Oracle 接口表及推送台账;job 扩展字段/索引,supplier_name/order_date 允许空;job_event 错误保存在现有 detail_json |
|
||||
| [db.py](/Users/yuuuu/Documents/AutoRpa/state_center/app/db.py:22) | Oracle 默认 autocommit=True | 对登记、领取、结果及推进使用显式短事务 |
|
||||
| [main.py 完成回报](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:712) | 保存节点、编号和推进分步执行;节点定义全局固定 | 同事务回报,校验 execution_token/attempt;按 flow 定义完成规则 |
|
||||
| [main.py 重试](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:812) | 已保留 attempt 历史、重试当前节点 | 保留机制,支持共用输入关联及 UNKNOWN 核对 |
|
||||
| [main.py 人工提交](/Users/yuuuu/Documents/AutoRpa/state_center/app/main.py:1346) | 通用确认可直接推进;RETRY_CURRENT_NODE 分支当前只记录事件 | 按第 10.3 节校验结果和动作,实现真实重试及核查恢复 |
|
||||
| [agent.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/agent.py:1) | 通用代理主要调用 mock;存在节点硬编码与默认 attempt=1 | 四条独立入口、真实 runner、领取结果中的 attempt/token |
|
||||
| [contract_create_node.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/contract_create_node.py:241) | 已校验空明细及正数量,仍要求旧 customerCode | 适配共用接口数据和客商定位;保留有效输入检查 |
|
||||
| [contract_flow_steps.py](/Users/yuuuu/Documents/AutoRpa/rpa_yisaiqi/contract_flow_steps.py:788) | 缺价补 1.00,同码首行及前 30 行定位 | 移除缺价补值;按来源明细/目标行定位并核对实际导入结果 |
|
||||
| [Web 节点详情](/Users/yuuuu/Documents/AutoRpa/state_center_web/src/components/JobNodeDetailList.tsx:1) | 已展示时间、尝试次数、错误及证据 | 复用组件,补三个独立状态、输入关联、接收列表及恢复动作 |
|
||||
|
||||
当前 Oracle 配置主要服务货品主数据读取;新接口表写入连接和权限需现场配置。当前代码不等于已具备四条真实平台执行能力,平台查询/导出及返利页面模块需要核对或实现。
|
||||
|
||||
### 11.2 实施顺序
|
||||
|
||||
1. 按共同输入契约核对第 4、7 章字段及合同/返利范围;确认现场接口表复用方式。
|
||||
2. 完成接收校验、Oracle 同事务保存及接收查询,验证失败重传和重复消息。
|
||||
3. 增加 flow/input 关联、作业登记、独立状态、领取及结果事务,跑通“同一输入两个 job”。
|
||||
4. 接平台订单导出、共享文件、台账及推送回执。
|
||||
5. 接合同页面保存和链路更新;返利读取同 INPUT_ID 与合同号完成提交。
|
||||
6. 接逐条选品;补齐异常及运维动作,配置四条排期后联调。
|
||||
|
||||
### 11.3 验收用例
|
||||
|
||||
| 编号 | 场景 | 预期结果 |
|
||||
| --- | --- | --- |
|
||||
| AT-01 | 一次完整 ORDER_COMMON 推送 | 一份头明细;登记两个相同 INPUT_ID/版本的合同、返利 job |
|
||||
| AT-02 | 仅返利必填缺失 | 当次返回字段错误,无业务输入;补齐后可重传 |
|
||||
| AT-03 | 相同请求重发、相同对象换消息 ID 重发 | 返回原输入,不新增执行作业 |
|
||||
| AT-04 | 同已成功来源/版本传不同内容 | 冲突可查,旧数据和外部结果不被覆盖 |
|
||||
| AT-05 | Oracle 已提交,Oracle 登记失败 | 接收记录显示登记失败;后续幂等补登记,不要求上游再推 |
|
||||
| AT-06 | 返利脚本先于合同脚本启动 | WAIT_CONTRACT;无额外供数请求、无返利提交 |
|
||||
| AT-07 | 合同保存成功,链路更新失败 | 保存 SUCCESS、链路 FAILED;只恢复链路,返利可按保存条件领取 |
|
||||
| AT-08 | 合同完成后返利按另一排期启动 | 读取同一接口数据和本方合同号,字段来源可追溯 |
|
||||
| AT-09 | 平台订单重复导出到不同文件 | 原件保留,已接收同源订单不重复入单 |
|
||||
| AT-10 | 推送部分成功/超时 | 按实际回执记录逐单结果;UNKNOWN 不直接按失败重发 |
|
||||
| AT-11 | 选品合格/不合格/查询失败 | 分别 ADDED、SKIPPED、FAILED;本批结束,不等付款 |
|
||||
| AT-12 | 脚本中断和重复点击 | 不重复领取;已有结果可查,确认停止并核对后恢复当前步骤 |
|
||||
| AT-13 | 合同已保存但回报丢失 | 重发完成键或核对目标结果,不再次建合同 |
|
||||
| AT-14 | 未尽调或返利提交失败 | 保留输入及原业务待办,其他记录继续;人工结果核对可留痕 |
|
||||
| AT-15 | 46 个 Excel RPA 字段 | 共用接口来源、固定值及日期按映射填报,缺价不补 1.00 |
|
||||
| AT-16 | 运维按 MESSAGE_ID、INPUT_ID、合同号查询 | 能串起接收、两流程、尝试、文件、请求和人工动作 |
|
||||
| AT-17 | 未授权恢复、旧 token 回报 | 拒绝写入;旧成功结果不被覆盖 |
|
||||
| AT-18 | 新旧流程并存 | 旧在途单保留原节点;新流程不误入对码/OCR/M5 |
|
||||
|
||||
文档检查与业务验收分开:当前只完成设计;以上需要在测试数据库、实际共享目录及外部测试系统执行,mock 成功不代表真实业务通过。
|
||||
|
||||
## 12. 未决事项、风险及对其他模块的影响
|
||||
|
||||
### 12.1 联调前需要确认
|
||||
|
||||
| 项目 | 已明确 | 剩余信息 |
|
||||
| --- | --- | --- |
|
||||
| 合同返利供数 | 一次推送共用 | 正式头明细、业务范围标识、消息 ID/版本;一组数据是否有多合同/多返利范围 |
|
||||
| 业务字段 | Excel 来源及默认值 | 实际 JSON 列名;采购/返利数量是否同字段,价格口径及精度 |
|
||||
| 订单出站接口 | 本方归档后推送 | 接收 URL、字段、批量/逐单回执、幂等和查询能力 |
|
||||
| 链路更新 | 合同保存成功后更新对应节点 | 节点代码、链路关联、合同标识字段及成功返回码 |
|
||||
| 返利可操作性 | 用同一份输入加已保存合同号 | 目标协议生成时间、是否依赖审批或链路更新;未确认前不新增业务审批门槛 |
|
||||
| 平台和共享目录 | 两平台分别导出归档 | 真实账号、订单状态/范围、文件样例、共享路径及四条排期 |
|
||||
| 盈利 | 5% 以上盈利 | 计算基数及边界;不直接套用旧方案 0.95 公式 |
|
||||
| 现场库和模板 | 接口输入与处理记录分开 | 现有 Oracle 表、权限、正式模板和确有要求的附件 |
|
||||
|
||||
### 12.2 可能问题与控制位置
|
||||
|
||||
| 问题 | 影响 | 对应设计 |
|
||||
| --- | --- | --- |
|
||||
| 共用一个 processed 标志 | 合同处理后返利漏取 | 第 5 章分离合同保存、链路、返利标志 |
|
||||
| 两份接口输入被分别修改 | 合同/返利货品数量不一致 | 同 INPUT_ID/版本只保留一份原始头明细 |
|
||||
| 成功接收后没有 job | 数据落表但长期未执行 | REGISTER_STATUS 和取数前补登记;运维可查 |
|
||||
| 接口/页面结果未知被当失败 | 重复订单、合同或返利 | UNKNOWN 核对后恢复,不无条件重做 |
|
||||
| 把链路失败当合同未保存 | 再建一份合同 | 保存节点结果与链路节点独立 |
|
||||
| 当前合同缺价补值或明细定位错误 | 错金额、同码行覆盖或漏行 | 接收校验及第 11.1 节代码修正;单规格规则不消除多订单行的问题 |
|
||||
| 原人工确认直接推进 | 未核实外部结果就完成作业 | 第 10.3 节按动作和证据校验 |
|
||||
| 新旧流程改全局状态机 | 旧对码、OCR、M5 在途单无法继续 | flow/version 分开,旧单保持原链;切换前确定同一来源由哪套办理 |
|
||||
| 两库或共享目录不可用 | 接收失败、原文不可查、文件不可达 | 独立错误可见;执行记录保留,不伪装成空数据或成功 |
|
||||
|
||||
本稿的表与字段是详细设计建议,尚未核对现场字典。对外契约尚未提供的部分保留为明确缺口;不以增加业务操作、复制数据或自动猜规则来填补。
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# 福建洁达采购 RPA 概要设计
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 版本 | V3.1 |
|
||||
| 日期 | 2026-09-13 |
|
||||
| 状态 | 设计评审稿;未实施代码或建表 |
|
||||
| 适用系统 | 当前 AutoRpa 状态中心、RPA 执行程序及福建洁达采购业务 |
|
||||
| 本版核心关系 | 订单中心一次推送合同/返利共用数据;合同先处理,返利按自己的排期读取同一份数据 |
|
||||
| 文档范围 | 概要设计、表与状态详细设计、接口及时序、异常恢复、运维查询、实施与验收 |
|
||||
| 替代关系 | 整合原 Codex 设计稿 V1.9 和技术实现说明 V2.3;旧方案仅供历史参考 |
|
||||
|
||||
**阅读顺序:** 业务评审看第 1–3 章;开发看第 4–9 章;运维看第 10 章;实施验收及未决问题见第 11–12 章。
|
||||
|
||||
|
||||
> 详细的数据模型、接口字段、状态、时序、异常、运维和验收见[《福建洁达采购 RPA 详细设计》](./2026-09-13-jieda-rpa-detailed-design.md)。
|
||||
|
||||
## 1. 设计依据与范围
|
||||
|
||||
### 1.1 已确认业务规则
|
||||
|
||||
| 编号 | 规则 |
|
||||
| --- | --- |
|
||||
| BR-01 | 四条脚本分别运行,自动时间和频率不同,也支持业务点击;一次 HTTP 接收不等待四条流程全部执行 |
|
||||
| BR-02 | 选品读取未处理销售明细,在 1 号药城和药帮忙均按同一套采购条件执行:按批准文号查询,依次校验自营仓、盈利、有效期、库存,再按仓库距离选择;合格加购物车,不合格跳过,处理完本批结束;不等待人工付款 |
|
||||
| BR-03 | 本业务相同批准文号只有一个规格,不增加规格、厂家、包装或单位换算匹配 |
|
||||
| BR-04 | 平台订单导出到各平台对应共享文件夹,按订单中心接口格式组装并推送;对码由订单中心内部处理 |
|
||||
| BR-05 | 订单中心一次传来的完整业务数据供合同、返利共用,不拆成两次独立供数,不重复保存两份相同头明细 |
|
||||
| BR-06 | 接收时检查上游应传的必填字段,缺项当次返回失败和字段位置;通过并落表后返回成功 |
|
||||
| BR-07 | 合同保存成功后调用订单中心链路接口更新对应节点;返利读取同一份接口数据,并使用本方已取得的合同号 |
|
||||
| BR-08 | 手工单暂不做;合同/返利字段、固定值及原业务人工分支按提供材料执行 |
|
||||
| BR-09 | RPA 将平台订单的对码原始信息随订单报文传给订单中心;订单中心返回或后续提供标准货品结果,本方不自行判断货品编码 |
|
||||
|
||||
### 1.2 参考材料
|
||||
|
||||
| 材料 | 用途 |
|
||||
| --- | --- |
|
||||
| [合同系统录入流程](/Users/yuuuu/Documents/AutoRpa/资料/合同系统录入流程.md) | 合单、尽调、提交、回写;页面步骤指向 rpa_yisaiqi |
|
||||
| [选品条件 20260902](/Users/yuuuu/Documents/AutoRpa/资料/采购平台选品条件优先项_20260902.txt) | 自营仓、盈利、效期、库存和仓库顺序 |
|
||||
| [合同返利字段映射 20260911](/Users/yuuuu/Documents/AutoRpa/资料/合同返利系统字段映射_20260911.xlsx) | 两个工作表的 46 个 RPA 字段及 Sheet3 导入列 |
|
||||
| [仓库一期技术设计](/Users/yuuuu/Documents/AutoRpa/资料/采购主链RPA一期设计文档.md) | 参考概要/详细设计、实体、接口、状态和审计的组织方式;不沿用其旧业务范围 |
|
||||
| [现有数据库结构](/Users/yuuuu/Documents/AutoRpa/state_center/sql/schema.sql) | 判断 job、节点、待办、日志及证据记录可复用部分 |
|
||||
|
||||
本次会话的业务确认优先于旧文档。下文表名、内部代码、API 路径及列类型为本方建议,正式外部字段名、节点代码和现场 Oracle 结构仍需按第 12 章核对。
|
||||
|
||||
## 2. 概要设计:架构与分工
|
||||
|
||||
### 2.1 总体架构
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
OC[订单中心] -->|销售数据 / 合同返利共用数据| API[FastAPI 接收校验]
|
||||
API -->|校验成功同事务落表| IFDB[(Oracle 接口头明细及接收记录)]
|
||||
S[四条独立排期 / 点击脚本] --> RPA[RPA 执行程序]
|
||||
RPA <-->|领取数据 / 回报结果| SC[状态中心]
|
||||
SC -->|读取同一份输入| IFDB
|
||||
SC <--> BDB[(Oracle 作业、节点、调用记录)]
|
||||
RPA -->|查品加购 / 导出订单| PL[采购平台]
|
||||
PL --> F[对应共享文件夹]
|
||||
F -->|组装报文并推送| OC
|
||||
RPA -->|录入保存| CT[合同系统]
|
||||
RPA -->|保存后调用链路接口| OC
|
||||
RPA -->|同一接口数据加合同号| RB[返利中心]
|
||||
UI[运维列表 / 详情 / 日志] --> SC
|
||||
```
|
||||
|
||||
接口数据与业务处理记录分开:**Oracle 接口表回答“订单中心传了什么”,Oracle 业务记录回答“本方做到哪一步、结果是什么、谁操作过”。** RPA 通过状态中心读取接口数据,数据库连接集中在后端。
|
||||
|
||||
### 2.2 接口表与业务表的选择
|
||||
|
||||
| 方式 | 特点 | 本期选择 |
|
||||
| --- | --- | --- |
|
||||
| 仅一张接口表加一个 processed 标志 | 简单,但合同处理后可能被整体标记完成,返利漏取;也无法表达多次失败尝试 | 不采用 |
|
||||
| 接口表再完整复制成合同表、返利表 | 三份字段容易不一致,需要额外同步 | 不采用 |
|
||||
| 接口头明细保留一份输入,业务作业/节点保存独立状态与结果 | 合同、返利通过相同 INPUT_ID 关联;历史操作复用现有日志 | **采用** |
|
||||
|
||||
现有 `job / job_node` 就是本期业务处理表,无需再复制合同、返利输入。为无 job 的接收失败补接收记录;为跨导出文件识别同一订单,补逐订单推送台账。这两项有明确查询及防重用途。
|
||||
|
||||
### 2.3 模块职责及运行方式
|
||||
|
||||
| 模块 | 职责 |
|
||||
| --- | --- |
|
||||
| FastAPI | 校验、保存接收数据、取数、原子领取、保存回报、查询及运维动作 |
|
||||
| Oracle 接口库 | 接收尝试、销售头明细、合同/返利共用头明细;成功输入保留原值 |
|
||||
| Oracle 状态中心 | 各流程 job、节点尝试、业务编号、订单推送状态、操作日志、证据索引 |
|
||||
| 四条 RPA 脚本 | 本批逐条执行;平台订单和合同链路接口由对应脚本通过适配器调用并回报 |
|
||||
| 现有 Web | 接收查询、共用数据处理总览、节点尝试、文件和运维动作 |
|
||||
|
||||
四条脚本分别配置时间、频率及本轮条数上限,单执行机每条脚本设单实例启动控制。FastAPI 常驻。脚本启动时调用后端登记未建立的 job,再领取本流程数据;订单导出先生成并登记 export_batch_id,恢复失败批次沿用该 ID;本批结束释放本机运行锁。没有数据时记录本轮零条并退出,不新增第五条业务或后台维护流程。
|
||||
|
||||
## 3. 概要设计:业务流程与完成边界
|
||||
|
||||
| 流程 | 输入 | 正常处理 | 完成含义 |
|
||||
| --- | --- | --- | --- |
|
||||
| PROCURE | 销售接口表的一条明细 | 按批准文号查询,按采购条件加购或跳过 | 该行处理完;不代表付款成交 |
|
||||
| ORDER_PUSH | 对应平台导出的订单文件 | 保存共享目录、提取对码原始信息、组装、推送、记录回执 | 应推送订单确认接收;不等待内部对码 |
|
||||
| CONTRACT | 共用订单接口头明细 | 录入并保存合同,保存合同号,更新订单中心链路 | 保存及链路更新分别记录;链路失败不重建合同 |
|
||||
| REBATE | **同一共用订单接口头明细**+本方合同保存结果 | 按合同号查协议、填总单、导细单、提交 | 返利提交成功;不要求订单中心再次推数据 |
|
||||
|
||||
### 3.1 合同与返利的关联
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
I[一次推送:INPUT_ID = I001] --> C[合同 job:INPUT_ID = I001]
|
||||
I --> R[返利 job:INPUT_ID = I001]
|
||||
C --> CS[合同保存成功:合同号 HT001]
|
||||
CS --> CH[合同链路更新]
|
||||
CS -->|提供合同号| R
|
||||
R --> RS[返利提交结果]
|
||||
```
|
||||
|
||||
合同、返利是两个独立作业,绑定同一 INPUT_ID 和 INPUT_VERSION。合同保存号只从对应合同 job 取得,不能按同品种或同一天猜配。
|
||||
|
||||
返利默认在“合同已确认保存成功且合同号存在”后可领取;链路回传结果另列显示,**不擅自把链路成功增加为返利前置条件**。若目标返利协议暂不可查,记录等待原因,下次返利运行再查询。是否必须审批通过由目标系统及接口方确认,不新增审批动作。
|
||||
|
||||
### 3.2 选品规则
|
||||
|
||||
1 号药城和药帮忙共用以下选品判断逻辑。平台只负责登录、搜索、读取商品信息和执行加购,不能改变判断优先级。自营仓、5% 以上盈利、有效期大于等于 12 个月、库存满足采购数量四项都通过后,按下列仓库顺序选择。不合格记跳过,平台技术错误记失败。
|
||||
|
||||
广东壹号药业有限公司、昆山亿方药业有限公司、湖北亿昊药业有限公司、天津壹号药业有限公司、重庆亿昊药业有限公司、重庆壹号医药有限公司、湖南壹号药业股份有限公司、辽宁亿昊药业有限公司、河南壹号医药有限公司、甘肃壹号云医药有限责任公司企业店、陕西壹号药业有限公司。
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
# 洁达选品:改造商城查价工程(详细方案)
|
||||
|
||||
> Windows 按本文改同事原工程。概要口径见 [2026-09-14-jieda-procure-crawler-overview.md](./2026-09-14-jieda-procure-crawler-overview.md)。
|
||||
> 不要改仓库一期状态机,不要把 ±10% 比价逻辑留在主流程里。
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 版本 | V1.0 |
|
||||
| 日期 | 2026-09-14 |
|
||||
| 执行环境 | Windows + 艺赛旗 Python 应用(`isa-rpa` + `uv`) |
|
||||
| 基线代码 | 同事工程 `main.py`(约 907 行) |
|
||||
| 建议工作副本 | 拷一份到本仓库 `rpa_yisaiqi/procure_mall/`,或 Windows 本地独立目录;不要在原 zip 上直接当唯一真源 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 原工程:留什么、删什么、补什么
|
||||
|
||||
### 1.1 原样保留(不要重写)
|
||||
|
||||
| 函数 / 能力 | 作用 |
|
||||
| --- | --- |
|
||||
| `SecretClient` + `YAOEX_SECRET_NAME` / `YBM_SECRET_NAME` | 密码箱登录 |
|
||||
| `get_yaoex_auth` / `ensure_yaoex_logged_in` | 1 号药城登录,抽 `token / userId / buyerCode` |
|
||||
| `get_ybm_cookies` / `ensure_ybm_logged_in` | 药帮忙登录,抽 cookie |
|
||||
| `build_yaoex_search_request` / `query_yaoex_products` | `homeSearchList`,`keyword=批准文号`,`per=20`,`deadLineStart=12` |
|
||||
| `decrypt_yaoex_price` / `build_yaoex_price_decrypt_key` | 价格 AES 解密 |
|
||||
| `build_ybm_search_payload` / `query_ybm_products` | `list-products`,`queryWord=批准文号`,`isThirdCompany=0`,`isEffectPeriodOverOneYear=1` |
|
||||
| `post_json_with_retry` | 3 次重试 |
|
||||
| 按批准文号缓存 | 同一文号不打两次 |
|
||||
|
||||
搜索接口:
|
||||
|
||||
- 1 号药城:`POST https://gateway-b2b.fangkuaiyi.com/home/search/homeSearchList`
|
||||
- 药帮忙:`POST https://www.ybm100.com/new-front/search/list-products`
|
||||
|
||||
字段映射继续用原代码:
|
||||
|
||||
| 结果字段 | 1 号药城 | 药帮忙 |
|
||||
| --- | --- | --- |
|
||||
| 平台索引ID | `spuCode` | `productInfo.skuId` |
|
||||
| 平台名称 | `shortName` | `productInfo.showName` |
|
||||
| 平台规格 | `spec` | `productInfo.spec` |
|
||||
| 平台价格 | `decrypt(price)` | `price`,空则 `fob`;库存 0 时价当 0 |
|
||||
| 效期 | `deadLine` | `productInfo.nearEffect` |
|
||||
| 商家 | `vendorName` | `productInfo.shopName` |
|
||||
| 库存 | `stockCount` | `productInfo.availableQty` |
|
||||
|
||||
### 1.2 必须删除或停用
|
||||
|
||||
- `is_in_purchase_price_band`
|
||||
- `filter_rows_by_purchase_price_band`
|
||||
- `main()` 里「价格区间过滤后总行数」那段
|
||||
- 把全部候选行当最终输出
|
||||
|
||||
### 1.3 必须新增
|
||||
|
||||
- 选品规则引擎(两平台共用)
|
||||
- 按行决策:每行每平台 0 或 1 个仓
|
||||
- 加购适配器(接口优先,页面兜底)
|
||||
- `--dry-run` / `--limit` / `--platform`
|
||||
- 新结果表(结论 + 原因,不再是比价明细大表)
|
||||
|
||||
---
|
||||
|
||||
## 2. 建议拆文件
|
||||
|
||||
原 `main.py` 已 900 行,不要再往里堆加购和规则。Windows 按下列拆;若艺赛旗工程限制只能单文件,也必须把规则和加购写成独立函数,禁止写进 `query_*`。
|
||||
|
||||
```text
|
||||
procure_mall/
|
||||
pyproject.toml # 沿用原工程
|
||||
main.py # 只编排:读入 → 登录 → 循环行 → 写结果
|
||||
search_yaoex.py # 从 main.py 搬登录+查询+解密
|
||||
search_ybm.py # 从 main.py 搬登录+查询
|
||||
http_util.py # post_json_with_retry 等
|
||||
rules.py # 合格判定 + 仓库排序
|
||||
cart_yaoex.py # 1 号药城加购
|
||||
cart_ybm.py # 药帮忙加购
|
||||
io_excel.py # 读输入、写结果
|
||||
tests/test_rules.py # 规则单测,不连商城
|
||||
```
|
||||
|
||||
`main.py` 目标骨架:
|
||||
|
||||
```python
|
||||
def main() -> None:
|
||||
args = parse_args() # --dry-run 默认 True;--limit N;--platform all|yaoex|ybm
|
||||
rows = load_input_rows(args.input_path)
|
||||
if args.limit:
|
||||
rows = rows[: args.limit]
|
||||
|
||||
yaoex_auth = asyncio.run(get_yaoex_auth()) if args.platform in ("all", "yaoex") else None
|
||||
ybm_cookies = asyncio.run(get_ybm_cookies()) if args.platform in ("all", "ybm") else None
|
||||
|
||||
decisions = []
|
||||
for purchase in rows:
|
||||
if yaoex_auth is not None:
|
||||
decisions.append(run_one_platform(purchase, "1号药城", yaoex_auth, dry_run=args.dry_run))
|
||||
if ybm_cookies is not None:
|
||||
decisions.append(run_one_platform(purchase, "药帮忙", ybm_cookies, dry_run=args.dry_run))
|
||||
|
||||
write_decision_file(decisions)
|
||||
```
|
||||
|
||||
单行单平台:
|
||||
|
||||
```python
|
||||
def run_one_platform(purchase, mall_name, auth, dry_run=True) -> dict:
|
||||
try:
|
||||
candidates = search_candidates(mall_name, auth, purchase["批准文号"])
|
||||
except Exception as exc:
|
||||
return decision(purchase, mall_name, status="FAILED", reason=f"QUERY_FAILED:{exc}")
|
||||
|
||||
picked, rejected = select_warehouse(candidates, purchase)
|
||||
if picked is None:
|
||||
return decision(purchase, mall_name, status="SKIPPED", reason=rejected_summary(rejected), raw=candidates)
|
||||
|
||||
if dry_run:
|
||||
return decision(purchase, mall_name, status="ADDED", reason="DRY_RUN", picked=picked)
|
||||
|
||||
try:
|
||||
cart_ref = add_to_cart(mall_name, auth, picked, qty=purchase["采购数量"])
|
||||
except Exception as exc:
|
||||
return decision(purchase, mall_name, status="FAILED", reason=f"CART_FAILED:{exc}", picked=picked)
|
||||
|
||||
return decision(purchase, mall_name, status="ADDED", reason="CART_OK", picked=picked, cart_ref=cart_ref)
|
||||
```
|
||||
|
||||
查询失败不得改成 SKIPPED。
|
||||
|
||||
---
|
||||
|
||||
## 3. 输入 / 输出
|
||||
|
||||
### 3.1 输入 Excel(改模板,不要沿用只有采购价格的比价模板)
|
||||
|
||||
路径建议:`桌面/福建商城选品测试/选品输入.xlsx`
|
||||
第一行表头,从第 2 行起数据。
|
||||
|
||||
| 列名 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 行号 | 否 | 没有则按 Excel 行号 |
|
||||
| 批准文号 | 是 | 查询关键字;空行跳过 |
|
||||
| 销售价格 | 是 | 毛利基准。缺或无法转数字 → 该行两平台都 SKIPPED,原因 `NO_SALE_PRICE` |
|
||||
| 采购数量 | 是 | 库存门槛和加购数量。缺或 ≤0 → SKIPPED,原因 `NO_QTY` |
|
||||
| 通用名 | 否 | 回填 |
|
||||
| 规格 | 否 | 回填,不参与匹配 |
|
||||
| 生产厂家 | 否 | 回填,不参与匹配 |
|
||||
|
||||
本期**不要用采购价格做合格判断**。模板里可以留着采购价格列,规则引擎忽略它。
|
||||
|
||||
`--limit 5` 只跑前 5 条有效行。
|
||||
|
||||
### 3.2 输出 Excel
|
||||
|
||||
路径:`桌面/福建商城选品测试/结果/选品结果_YYYYMMDD_HHMMSS.xlsx`
|
||||
一行 = 一个输入行 × 一个平台。
|
||||
|
||||
| 列名 | 说明 |
|
||||
| --- | --- |
|
||||
| 行号 | 输入行 |
|
||||
| 批准文号 | |
|
||||
| 通用名 | |
|
||||
| 销售价格 | |
|
||||
| 采购数量 | |
|
||||
| 商城 | `1号药城` / `药帮忙` |
|
||||
| 结论 | `ADDED` / `SKIPPED` / `FAILED` |
|
||||
| 原因 | 见 3.3 |
|
||||
| 选中商家 | ADDED 时必填 |
|
||||
| 平台索引ID | sku/spu |
|
||||
| 平台名称 | |
|
||||
| 平台规格 | |
|
||||
| 平台价格 | |
|
||||
| 效期原文 | |
|
||||
| 库存 | |
|
||||
| 毛利率 | `(销售价-平台价)/销售价`,保留 4 位小数 |
|
||||
| dry_run | `Y/N` |
|
||||
| 加购凭证 | 接口回执或页面摘要;dry-run 为空 |
|
||||
| 候选商家清单 | 可选,分号拼接,便于核对为什么没选上 |
|
||||
|
||||
另写一份 `未匹配商家_时间戳.txt`:所有出现过、但不在 11 家名单里的商家名(去重)。用来决定要不要加别名。
|
||||
|
||||
### 3.3 原因码
|
||||
|
||||
| 原因码 | 结论 | 何时 |
|
||||
| --- | --- | --- |
|
||||
| `NO_SALE_PRICE` | SKIPPED | 销售价缺失 |
|
||||
| `NO_QTY` | SKIPPED | 采购数量缺失或 ≤0 |
|
||||
| `NO_HIT` | SKIPPED | 接口无货或空列表 |
|
||||
| `NO_SELF_WAREHOUSE` | SKIPPED | 有货但不在 11 家 |
|
||||
| `MARGIN_LT_5PCT` | SKIPPED | 自营仓有货但毛利不够(可与其它原因并列,取「没有任何候选过齐 1~4」) |
|
||||
| `EXPIRY_LT_12M` | SKIPPED | 效期不够 |
|
||||
| `STOCK_LT_QTY` | SKIPPED | 库存不够 |
|
||||
| `NO_CANDIDATE_AFTER_RULES` | SKIPPED | 1~4 筛完为空(推荐作为总原因,明细放候选清单) |
|
||||
| `DRY_RUN` | ADDED | 规则通过,未真正加购 |
|
||||
| `CART_OK` | ADDED | 加购成功 |
|
||||
| `QUERY_FAILED:...` | FAILED | 搜索接口失败(已重试) |
|
||||
| `CART_FAILED:...` | FAILED | 加购失败 |
|
||||
| `CART_UNKNOWN:...` | FAILED | 加购后无法确认是否进车 |
|
||||
|
||||
SKIPPED 的总原因用 `NO_CANDIDATE_AFTER_RULES`,并在「候选商家清单」里带每条被淘汰原因。不要只写一个模糊的「不合格」。
|
||||
|
||||
---
|
||||
|
||||
## 4. 规则引擎(`rules.py`)
|
||||
|
||||
两平台只把候选翻译成同一结构,再进引擎。平台代码禁止自己改优先级。
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Candidate:
|
||||
mall_name: str
|
||||
sku_id: str
|
||||
name: str
|
||||
spec: str
|
||||
price: float
|
||||
expiry_raw: str
|
||||
expiry_months: float | None
|
||||
vendor_raw: str
|
||||
vendor_norm: str
|
||||
stock: float
|
||||
extra: dict
|
||||
|
||||
@dataclass
|
||||
class PurchaseLine:
|
||||
approval_no: str
|
||||
sale_price: float
|
||||
qty: float
|
||||
```
|
||||
|
||||
### 4.1 商家规范化
|
||||
|
||||
```text
|
||||
去首尾空格
|
||||
去掉前缀 "[自营]"(全角空格也去)
|
||||
压缩连续空格为单空格
|
||||
```
|
||||
|
||||
规范化后必须 **等于** 下面 11 个字符串之一,顺序即优先级(下标越小越优先):
|
||||
|
||||
```python
|
||||
WAREHOUSE_ORDER = [
|
||||
"广东壹号药业有限公司",
|
||||
"昆山亿方药业有限公司",
|
||||
"湖北亿昊药业有限公司",
|
||||
"天津壹号药业有限公司",
|
||||
"重庆亿昊药业有限公司",
|
||||
"重庆壹号医药有限公司",
|
||||
"湖南壹号药业股份有限公司",
|
||||
"辽宁亿昊药业有限公司",
|
||||
"河南壹号医药有限公司",
|
||||
"甘肃壹号云医药有限责任公司企业店",
|
||||
"陕西壹号药业有限公司",
|
||||
]
|
||||
```
|
||||
|
||||
本期不做别名、不做包含匹配。对不上的 `vendor_raw` 记入未匹配清单。
|
||||
|
||||
### 4.2 价格
|
||||
|
||||
```text
|
||||
毛利 = (销售价格 - 平台价) / 销售价格
|
||||
合格:毛利 >= 0.05
|
||||
即:平台价 <= 销售价格 * 0.95
|
||||
```
|
||||
|
||||
平台价无法转数字 → 该候选淘汰。销售价 ≤ 0 → 整行 SKIPPED。
|
||||
**禁止**用采购价、禁止 ±10% 区间。
|
||||
|
||||
### 4.3 效期
|
||||
|
||||
目标:剩余 ≥ 12 个月。解析顺序:
|
||||
|
||||
1. 纯数字(含小数):当作月数。
|
||||
2. 含「月」:抽出数字当月数。
|
||||
3. 日期(`YYYY-MM-DD` / `YYYY/MM/DD` / `YYYY-MM`):`(到期日 - 今天) / 30.4375`,到期日按该月最后一天。
|
||||
4. 都不是:`expiry_months = None`,该候选淘汰(原因 `EXPIRY_UNPARSEABLE`)。
|
||||
|
||||
接口虽已带 12 个月过滤,仍以本函数为准。
|
||||
|
||||
### 4.4 库存
|
||||
|
||||
`stock >= qty`。库存无法转数字或为 0 → 淘汰。药帮忙无货把价格改成 0 的旧逻辑可保留在映射层,引擎侧会因库存/价格再淘汰一次。
|
||||
|
||||
### 4.5 择仓
|
||||
|
||||
```text
|
||||
passed = [c for c in candidates if 自营仓 and 毛利 and 效期 and 库存]
|
||||
if not passed: return None
|
||||
return min(passed, key=lambda c: WAREHOUSE_ORDER.index(c.vendor_norm))
|
||||
```
|
||||
|
||||
同一仓库多条 SKU:取平台价更低的;再相同取库存更大的。不要两条都加购。
|
||||
|
||||
### 4.6 必须先写的单测(不连网)
|
||||
|
||||
文件:`tests/test_rules.py`。Windows 在改 `main.py` 前先把这些测过。
|
||||
|
||||
| 用例 | 期望 |
|
||||
| --- | --- |
|
||||
| 11 家名单内、毛利 6%、效期 13 月、库存够 | 选出该仓 |
|
||||
| 毛利刚好 5% | 选出(`>= 0.05`) |
|
||||
| 毛利 4.9% | 淘汰 |
|
||||
| 商家不在名单 | 淘汰 |
|
||||
| 商家带 `[自营] 广东壹号药业有限公司` | 视为广东壹号 |
|
||||
| 广东和昆山都合格 | 选广东 |
|
||||
| 只有昆山合格 | 选昆山 |
|
||||
| 库存 9、采购 10 | 淘汰 |
|
||||
| 效期 11 个月 | 淘汰 |
|
||||
| 效期 `2027-09-14` 且今天起 ≥12 个月 | 过 |
|
||||
| 空候选 | SKIPPED 语义(函数返回 None) |
|
||||
| 销售价用 100、平台价 96 | 淘汰(毛利 4%) |
|
||||
| 销售价 100、平台价 95 | 过 |
|
||||
|
||||
用采购价 ±10% 的旧例子(平台价 96、采购价 90)在新规则下若销售价是 100 应淘汰。加一条单测钉死,防止有人把 ±10% 加回来。
|
||||
|
||||
命令:
|
||||
|
||||
```bat
|
||||
cd /d 工程目录
|
||||
uv run pytest tests/test_rules.py -v
|
||||
```
|
||||
|
||||
期望:全绿。未装 pytest 则 `uv add --dev pytest`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 加购(原工程没有,这是新工作)
|
||||
|
||||
### 5.1 抓接口(每个平台做一次,记录进 `docs/cart-api.md` 或工程内 `CART_API.md`)
|
||||
|
||||
在 Windows 上用已经登录的账号:
|
||||
|
||||
1. 打开对应商城,F12 → Network,过滤 XHR/Fetch。
|
||||
2. 搜索一个确定能加的自营仓商品。
|
||||
3. 点「加入购物车」,看新出现的请求。
|
||||
4. 记录:URL、方法、请求头(cookie / token 哪些必要)、body、成功响应长什么样。
|
||||
5. 把 body 里的 sku/spu、数量、商家 ID 标出来。
|
||||
|
||||
1 号药城登录态用 `ycgltoken` 等;药帮忙用整份 cookie。加购请求必须带上搜品时同一套认证,不要重新登录。
|
||||
|
||||
若加购接口有 CSRF / 签名 / 人机验证:不要硬解,改页面点击(5.3)。
|
||||
|
||||
### 5.2 接口加购(优先)
|
||||
|
||||
`cart_yaoex.add(auth, candidate, qty) -> cart_ref`
|
||||
`cart_ybm.add(cookies, candidate, qty) -> cart_ref`
|
||||
|
||||
- 数量 = 采购数量。
|
||||
- 成功:响应明确成功,或随后查购物车能看到该 sku + 数量。
|
||||
- 响应含糊:结论 `FAILED`,原因 `CART_UNKNOWN`,不要报 ADDED。
|
||||
- 已在车里:视为成功(幂等),不要再加一次导致数量翻倍。先查车再加。
|
||||
|
||||
### 5.3 页面加购(接口抓不到才用)
|
||||
|
||||
只对规则已经选出的那 **1 条** 操作:
|
||||
|
||||
1. 打开商品页或搜索结果中该 sku。
|
||||
2. 填数量。
|
||||
3. 点加入购物车。
|
||||
4. 等成功提示或打开购物车核对。
|
||||
|
||||
禁止:重新用页面搜索批准文号再人工挑仓。仓已经由规则选定。
|
||||
|
||||
页面选择器用艺赛旗 `BrowserSession`,登录态复用。不要为加购再登一次(除非会话丢了)。
|
||||
|
||||
### 5.4 dry-run
|
||||
|
||||
默认 `--dry-run`。`ADDED + DRY_RUN` 表示规则通过。关 dry-run 必须显式 `--execute-cart`。
|
||||
|
||||
第一次真正加购只用 `--limit 1 --platform yaoex --execute-cart`,人工打开购物车核对后再加量。
|
||||
|
||||
---
|
||||
|
||||
## 6. Windows 环境与运行
|
||||
|
||||
### 6.1 环境
|
||||
|
||||
- 艺赛旗 Python 应用,能 `uv run main.py`
|
||||
- 密码箱已有:
|
||||
- `yaoex_GYJDLHT_login`
|
||||
- `yaobangmang_18146141930_login`
|
||||
- 账号必须是业务允许加购的测试/正式采购账号。真正加购前跟业务确认,避免测出一车真货。
|
||||
- 桌面目录:`福建商城选品测试\`
|
||||
|
||||
### 6.2 命令
|
||||
|
||||
```bat
|
||||
:: 只判定,不加购(默认)
|
||||
uv run main.py --dry-run --limit 5
|
||||
|
||||
:: 只跑一个平台
|
||||
uv run main.py --dry-run --limit 5 --platform yaoex
|
||||
|
||||
:: 确认规则后,真加购 1 条
|
||||
uv run main.py --execute-cart --limit 1 --platform yaoex
|
||||
```
|
||||
|
||||
把命令写进 `README.md`,替换原「查价结果」说明。
|
||||
|
||||
### 6.3 验收顺序(必须按这次序,不要一上来一千条真加购)
|
||||
|
||||
1. `pytest tests/test_rules.py` 全绿。
|
||||
2. `--dry-run --limit 5 --platform yaoex`:结果表 5 行,结论只有 ADDED/SKIPPED/FAILED,没有 ±10% 残留。
|
||||
3. 同样 5 条跑药帮忙。
|
||||
4. 人工抽 2 条 ADDED:对照网页,商家是否 11 家之一、毛利是否 ≥5%、效期、库存。
|
||||
5. `--execute-cart --limit 1` 一个平台,打开购物车看 sku 和数量。
|
||||
6. 再跑同一条,确认不会把数量翻倍(幂等)。
|
||||
7. 再 `--limit 20` dry-run,看耗时。预估一千条:约 `20 条耗时 × 50`,写入结果 README。登录应只发生两次(每平台一次)。
|
||||
|
||||
失败则停在当前步,不要跳到一千条。
|
||||
|
||||
---
|
||||
|
||||
## 7. 异常
|
||||
|
||||
| 情况 | 处理 |
|
||||
| --- | --- |
|
||||
| 密码箱空 | 立即失败退出,不写半份结果 |
|
||||
| 登录失败 | 该平台本批中止;另一平台仍可跑(已登录的话) |
|
||||
| 单文号查询 3 次仍失败 | 该行该平台 FAILED,继续下一行 |
|
||||
| 加购超时 | FAILED `CART_UNKNOWN`,不要当 SKIPPED |
|
||||
| 极验登录点一次不够 | 沿用原等待逻辑;仍失败则整平台中止,记登录错误 |
|
||||
| 关闭 dry-run 但加购模块未实现 | 启动时报错退出,禁止假装 ADDED |
|
||||
|
||||
脚本不点支付、不打开结算页。
|
||||
|
||||
---
|
||||
|
||||
## 8. Windows 任务清单
|
||||
|
||||
按序做。每步可单独验证。
|
||||
|
||||
### 任务 1:工作副本和输入模板
|
||||
|
||||
- [ ] 拷贝同事工程到工作目录(仓库 `rpa_yisaiqi/procure_mall/` 或 Windows 本地)
|
||||
- [ ] 桌面建 `福建商城选品测试\选品输入.xlsx`,表头按 3.1
|
||||
- [ ] 放 5 条真实批准文号、销售价格、采购数量(问业务要,不要用假文号)
|
||||
- [ ] `uv run python -c "import isa_rpa, requests, openpyxl; print('ok')"` 能过
|
||||
|
||||
### 任务 2:规则引擎 + 单测
|
||||
|
||||
- [ ] 新建 `rules.py`、`tests/test_rules.py`
|
||||
- [ ] 实现规范化、毛利、效期、库存、择仓
|
||||
- [ ] `uv run pytest tests/test_rules.py -v` 全绿
|
||||
- [ ] 确认没有 import 原 ±10% 函数
|
||||
|
||||
### 任务 3:搜品接到按行决策(仍不加购)
|
||||
|
||||
- [ ] 拆出或保留 `query_yaoex_products` / `query_ybm_products`
|
||||
- [ ] 候选映射成 `Candidate`
|
||||
- [ ] `main.py` 改为按行循环,默认 dry-run
|
||||
- [ ] 删除 `filter_rows_by_purchase_price_band` 调用
|
||||
- [ ] 写新结果表(3.2)
|
||||
- [ ] 跑 `uv run main.py --dry-run --limit 5`
|
||||
- [ ] 人工核对 2 条 ADDED、2 条 SKIPPED
|
||||
|
||||
### 任务 4:抓加购接口
|
||||
|
||||
- [ ] 1 号药城 F12 抓「加入购物车」,写入 `CART_API.md`
|
||||
- [ ] 药帮忙同样写一份
|
||||
- [ ] 判定:接口可用 / 必须页面点击
|
||||
|
||||
### 任务 5:实现加购(默认仍 dry-run)
|
||||
|
||||
- [ ] `cart_yaoex.py` / `cart_ybm.py`
|
||||
- [ ] 先查车再加,避免数量翻倍
|
||||
- [ ] `--execute-cart --limit 1` 单平台
|
||||
- [ ] 人工打开购物车核对
|
||||
- [ ] 再跑同一条,数量不翻倍
|
||||
|
||||
### 任务 6:收尾
|
||||
|
||||
- [ ] README:命令、dry-run 默认、输入输出路径、密码箱名称
|
||||
- [ ] `--limit 20` dry-run 耗时写进 README
|
||||
- [ ] 把 `未匹配商家` 列表交给业务看要不要补别名(本期不改代码硬匹配)
|
||||
- [ ] 交付:工程、5 条结果 xlsx、加购成功截图、CART_API.md
|
||||
|
||||
---
|
||||
|
||||
## 9. 与后续 Oracle / 状态中心的边界
|
||||
|
||||
本期 `decision` 字典就是以后 `PROCURE_ITEM_RUN` 的 `output_json`。字段先对齐:`result=ADDED|SKIPPED|FAILED`、原因、商家、sku、价格、数量。
|
||||
|
||||
不要在本期接 FastAPI、不要写 MySQL、不要为选品新建 Oracle 表。下一期只换输入输出通道,不换规则和搜品。
|
||||
|
||||
---
|
||||
|
||||
## 10. 未决(不影响 Windows 开工)
|
||||
|
||||
| 项 | 本期默认 | 谁拍板 |
|
||||
| --- | --- | --- |
|
||||
| 两平台都合格是否都加购 | 都加 | 业务;要互斥再改 `main` 循环 |
|
||||
| 毛利 刚好 5% | 算合格 `>=` | 已按设计说明「不低于 5%」 |
|
||||
| 商家名不完全等于 11 家 | 淘汰并记未匹配清单 | 业务补别名后再改 `WAREHOUSE_ORDER` 映射 |
|
||||
| 真正加购用测试账号还是正式采购账号 | 必须业务书面确认后再 `--execute-cart` | 业务 / 运维 |
|
||||
|
||||
加购接口在改代码前未知,这是正常的。任务 4 的产出就是答案,不是阻塞任务 1~3 的理由。
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
# 洁达选品:改造商城查价工程(概要)
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 版本 | V1.0 |
|
||||
| 日期 | 2026-09-14 |
|
||||
| 状态 | 本期实施依据;Windows 机按本文和详细方案改同事原工程 |
|
||||
| 基线工程 | 同事「1号药城与药帮忙货品价格库存下载」`isa-rpa` Python 应用(`main.py`) |
|
||||
| 详细方案 | [2026-09-14-jieda-procure-crawler-detail.md](./2026-09-14-jieda-procure-crawler-detail.md) |
|
||||
| 业务口径 | [洁达四流程概要 V3.1](./2026-09-13-jieda-rpa-overview-design.md) 的 **BR-02 / BR-03 / 第 3.2 节** |
|
||||
| 选品条件原文 | [采购平台选品条件优先项 20260902](/Users/yuuuu/Documents/AutoRpa/资料/采购平台选品条件优先项_20260902.txt) |
|
||||
|
||||
本期只做 **PROCURE 选品加购的第一刀**:在原查价工程上改,不接状态中心、不接 Oracle、不改合同/返利。先跑通「登录搜品 → 按行判定 → 合格加购」。
|
||||
|
||||
---
|
||||
|
||||
## 1. 要做成什么
|
||||
|
||||
一次脚本运行:
|
||||
|
||||
1. 读入待选品行(批准文号、销售价格、采购数量)。
|
||||
2. 浏览器登录 1 号药城、药帮忙各一次,拿 cookie/token。
|
||||
3. 按批准文号打现有搜索接口,拉候选(商家、价、效期、库存)。
|
||||
4. 用洁达选品规则,每个平台每行最多选出 **1 家仓**。
|
||||
5. 合格则加入该平台购物车;不合格记跳过;查询失败记失败。
|
||||
6. 写出结果表。人工付款仍由业务在平台做,脚本不等、不点支付。
|
||||
|
||||
完成标准:Windows 机用 5~20 个品种能稳定跑完;结果表能看出每行 `ADDED / SKIPPED / FAILED`;合格行购物车里能看到对应商品和数量。默认 `--dry-run` 只判定不加购,确认规则后再关。
|
||||
|
||||
## 2. 为什么改原工程,不另起
|
||||
|
||||
原工程已经有:双平台登录、搜索 API、1 号药城价格解密、按文号缓存、接口重试。缺的是选品决策和加购。合同那套 Studio 逐步点击不适合一千个品种。
|
||||
|
||||
原工程里必须删掉的:
|
||||
|
||||
- `filter_rows_by_purchase_price_band`(采购价 ±10%)
|
||||
- 把所有命中行倒进 Excel 当最终答案
|
||||
- 用「采购价格」做是否合格的依据
|
||||
|
||||
保留的:登录、`query_yaoex_products`、`query_ybm_products`、`decrypt_yaoex_price`、密码箱、重试。
|
||||
|
||||
## 3. 架构
|
||||
|
||||
```text
|
||||
Excel 输入(本期)
|
||||
→ 登录一次 / 平台
|
||||
→ 搜索接口(按批准文号,带缓存)
|
||||
→ 选品规则引擎(纯 Python,两平台同一套)
|
||||
→ 加购适配器(先接口,抓不到再点页面;仅合格行)
|
||||
→ 结果 Excel(每行一个平台一个结论)
|
||||
```
|
||||
|
||||
平台只负责登录、搜索、读商品、执行加购。**不能改规则优先级。**
|
||||
|
||||
本期输入仍用桌面 Excel,是为了在 Windows 上先试,不堵在 Oracle。列必须有 **销售价格**,不能再用采购价冒充。下一期再把输入/回报换成状态中心领取。
|
||||
|
||||
## 4. 按行怎么跑
|
||||
|
||||
一行输入 × 一个平台 = 一条结论。一次脚本把两个平台都跑完。
|
||||
|
||||
```text
|
||||
登录两个商城(各一次)
|
||||
for 每一行输入:
|
||||
for 平台 in [1号药城, 药帮忙]:
|
||||
搜批准文号
|
||||
规则选出 0 或 1 个仓
|
||||
合格 → 加购(dry-run 则跳过动作)→ ADDED
|
||||
无合格仓 → SKIPPED + 原因
|
||||
接口/登录级错误 → FAILED + 原因(不是 SKIPPED)
|
||||
写结果表,退出
|
||||
```
|
||||
|
||||
**两边都合格就两边都加购。** 同一销售行可能进两个购物车。若业务要互斥,只改这一条策略,不要改规则引擎。
|
||||
|
||||
登录失败整批中止。单个文号查询失败只失败该平台该行,继续后面的行。
|
||||
|
||||
## 5. 合格标准(两平台同一套)
|
||||
|
||||
按批准文号查询,不配规格、厂家、包装。前 4 项必须同时过,再按仓库名单取第一家。
|
||||
|
||||
| 顺序 | 条件 | 判定 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 自营仓 | 商家名规范化后,必须落在下列 11 家里 |
|
||||
| 2 | 价格 | `(销售价格 - 平台价) / 销售价格 >= 0.05`,即平台价 ≤ 销售价 × 0.95。基准是输入里的**销售价格** |
|
||||
| 3 | 效期 | ≥ 12 个月 |
|
||||
| 4 | 库存 | 平台库存 ≥ 本行采购数量 |
|
||||
| 5 | 仓库距离 | 在 1~4 都过的候选里,按下列顺序取第一家 |
|
||||
|
||||
仓库顺序(不可改、不可按页面默认排序):
|
||||
|
||||
1. 广东壹号药业有限公司
|
||||
2. 昆山亿方药业有限公司
|
||||
3. 湖北亿昊药业有限公司
|
||||
4. 天津壹号药业有限公司
|
||||
5. 重庆亿昊药业有限公司
|
||||
6. 重庆壹号医药有限公司
|
||||
7. 湖南壹号药业股份有限公司
|
||||
8. 辽宁亿昊药业有限公司
|
||||
9. 河南壹号医药有限公司
|
||||
10. 甘肃壹号云医药有限责任公司企业店
|
||||
11. 陕西壹号药业有限公司
|
||||
|
||||
商家名先去掉 `[自营]`、首尾空格再比。对不上名单 = 本候选淘汰,不是整行失败。对不上的原始商家名写入结果,方便以后补别名。本期不做模糊匹配。
|
||||
|
||||
效期:接口已带「12 个月」过滤,代码仍要再验。解析不了的效期,该候选淘汰。
|
||||
|
||||
查询失败、登录失败、加购后不确定是否进车,记 `FAILED` / `UNKNOWN`,**禁止记成 SKIPPED**。
|
||||
|
||||
## 6. 加购
|
||||
|
||||
原工程没有购物车代码。合格之后才加购,数量 = 本行采购数量。
|
||||
|
||||
1. 用 F12 抓「加入购物车」请求(详细方案有步骤)。
|
||||
2. 抓到则复用登录态打接口。
|
||||
3. 抓不到则只对合格行点页面,禁止一千条都去点搜索。
|
||||
4. `--dry-run` 默认开启:只写「将加购」不真正加。Windows 先用 dry-run 对规则,再用 1~2 行关 dry-run 验车。
|
||||
|
||||
脚本不打开结算、不点支付。
|
||||
|
||||
## 7. 本期不做
|
||||
|
||||
- 状态中心领取 / start / complete / fail
|
||||
- Oracle 接口表、行 job
|
||||
- 平台订单导出、合同、返利
|
||||
- 人工付款、待复核清单替代加购(新方案是合格就加购)
|
||||
- 把查价 ±10% 留作并行规则
|
||||
|
||||
## 8. Windows 交付物
|
||||
|
||||
- 改过的 `isa-rpa` 工程(可运行)
|
||||
- 输入模板(含销售价格、采购数量、批准文号)
|
||||
- 结果表(每行每平台:结论、原因、选中商家、平台价、效期、库存、是否 dry-run)
|
||||
- 加购接口记录(URL、方法、关键字段;或「改用页面点击」的说明)
|
||||
- 用 5 个品种跑通的截图或结果文件
|
||||
|
||||
详细到函数、表头、命令、测试步骤,见详细方案。
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
# 仓库布局:四接口一服务 + 四个英文作业包
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 日期 | 2026-10-02 |
|
||||
| 状态 | 已锁定。随后 `rpa_yisaiqi/` 已删,页面脚本迁入 `rpa_contract/` |
|
||||
| 范围 | 根目录命名与分家:`选品流程/` 整包迁到 `rpa_selection/`;占位 `rpa_cart/`、`rpa_contract/`、`rpa_rebate/`;`资料/` 并入 `docs/`。路径与文档引用。 |
|
||||
| 替代 | 根 `README.md` 代码入口表;`资料/README.md`(并入后成为 `docs/README.md`) |
|
||||
| 不变 | 四张表、四个口的作业语义、接口 1 行为、选品规则与拆量、`rpa_yisaiqi` 页面步骤 |
|
||||
|
||||
四个 HTTP 口不是四个服务。四条作业不是一个包。代码目录名全英文。
|
||||
|
||||
---
|
||||
|
||||
## 1. 要做成什么
|
||||
|
||||
根目录变成:
|
||||
|
||||
```
|
||||
AutoRpa/
|
||||
README.md
|
||||
rpa_oms/ # 四个 HTTP 口,一个 FastAPI
|
||||
rpa_selection/ # 选品班(现 选品流程/ 整包搬来)
|
||||
rpa_cart/ # 加购 + 支付回传(占位)
|
||||
rpa_contract/ # 合同班(占位)
|
||||
rpa_rebate/ # 返利班(占位)
|
||||
docs/ # 现行口径 + 归档(现 资料/ 并入)
|
||||
rpa_yisaiqi/ # 过渡:合同班能跑之后整包删除
|
||||
```
|
||||
|
||||
完成标准:
|
||||
|
||||
1. 仓库根上不再有 `选品流程/`、`资料/`。
|
||||
2. `cd rpa_selection && uv run pytest -v` 与搬前同等通过。
|
||||
3. `cd rpa_oms && uv run pytest -v` 通过。
|
||||
4. 根 README、`docs/README.md`、`.gitignore`、现行口径里的路径指向新目录。
|
||||
5. `rpa_cart/`、`rpa_contract/`、`rpa_rebate/` 各有 README 和空 `pyproject.toml`,能看出职责,没有可运行作业代码。
|
||||
|
||||
---
|
||||
|
||||
## 2. 不做什么
|
||||
|
||||
- 不加购、不支付回传、不录合同、不录返利
|
||||
- 不实现接口 2 / 3 / 4,不在 `rpa_oms` 里建空路由模块
|
||||
- 不改选品规则、班次判定、表结构、接口 1 报文
|
||||
- 不整理、不改名、不删除 `rpa_yisaiqi/`(本轮只改指向它的现行文档路径,若文档从 `资料/` 搬走)
|
||||
- 不把作业改成 `app/` 包;作业保持扁平脚本(`main.py` + `job.py`)
|
||||
- 不把四个作业收进 `rpa_jobs/`
|
||||
- 不改 `docs/superpowers/` 里历史 spec / plan 的死链(视为归档)
|
||||
- 不提交 `state_center/` 等本轮无关的已删文件(那些删除不在本 spec 范围)
|
||||
|
||||
---
|
||||
|
||||
## 3. 已锁定决策
|
||||
|
||||
| 项 | 决定 |
|
||||
| --- | --- |
|
||||
| 四个口 | 继续住在 `rpa_oms/`。入站 1、3 在 `app/inbound/`,出站 2、4 做到时再加 `app/outbound/`。本轮只把这张地图写进 `rpa_oms/README.md` |
|
||||
| 四个作业 | 根上并列:`rpa_selection`、`rpa_cart`、`rpa_contract`、`rpa_rebate` |
|
||||
| `rpa_cart` | 加购(接口 2 `operateType=1`)和支付回传(`operateType=2`)同一个包。两个班次,共享表 2 |
|
||||
| 选品 | `git mv 选品流程 rpa_selection`。业务代码、扁平 import、`pythonpath = ["."]` 不动 |
|
||||
| 占位包 | 只有 `README.md` + `pyproject.toml`。无 `main.py`、无 `tests/` |
|
||||
| 中文 | **代码目录**全英文。文档文件名可以中文。`docs/` 是目录名,里面的 `.md` 文件名不改 |
|
||||
| `rpa_yisaiqi` | 过渡资产。合同页面步骤仍以它为准。等 `rpa_contract` 能跑再整包删 |
|
||||
| `docs/superpowers/` | 原地不动,归档 |
|
||||
|
||||
---
|
||||
|
||||
## 4. `rpa_oms`:四个口的地图
|
||||
|
||||
本轮不改 Python 文件(README 除外)。运行时仍只开放接口 1。
|
||||
|
||||
| 口 | 方向 | 底表 | 代码落点(做到再写) |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 订单中心 → 本方 | `rpa_sales_goods_mapping_data` | 已有 `app/inbound/sales_goods.py`,`POST /api/oms/v1/sales-goods` |
|
||||
| 2 | 本方 → 订单中心 | `rpa_cart_settlement_goods_data` | 未来 `app/outbound/`,由 `rpa_cart` 班次调用,本服务也可提供 push 客户端 |
|
||||
| 3 | 订单中心 → 本方 | `rpa_purchase_contract_data` | 未来 `app/inbound/`,复用接口 1 信封/鉴权 |
|
||||
| 4 | 本方 → 订单中心 | `rpa_contract_agreement_result` | 未来 `app/outbound/`,由合同/返利班次调用 |
|
||||
|
||||
`rpa_oms/README.md` 用上表替换「本期只开放接口 1」的单一叙述,并写明:HTTP 收件不同步跑作业。
|
||||
|
||||
---
|
||||
|
||||
## 5. 四个作业包
|
||||
|
||||
作业语义仍以 `docs/RPA订单中心接口开发口径_20260930.md`(搬后路径)为准。包只是落点。
|
||||
|
||||
### 5.1 `rpa_selection/`(整包搬)
|
||||
|
||||
来源:`选品流程/`。
|
||||
|
||||
| 动作 | 细节 |
|
||||
| --- | --- |
|
||||
| 移动 | `git mv 选品流程 rpa_selection`。未跟踪残留(`.explore/`、`.magic/`、`output/结果` 下 html/png、本地 `.venv` / `.env`)不会跟着走,目录会留下。把需要的本地文件普通 `mv` 进 `rpa_selection/`,探测垃圾直接删,然后去掉空的 `选品流程/`。不要 `git add` `.explore` / `.magic` / 截图 |
|
||||
| `pyproject.toml` | `name = "rpa-selection"`;description 改成带新目录名。依赖、`requires-python`、pytest 配置不动 |
|
||||
| `README.md` | 所有 `cd 选品流程`、`` `选品流程/` `` 改成 `rpa_selection`;口径路径改 `docs/...` |
|
||||
| 代码 | 不改 import、不改 `PROJECT_ROOT = Path(__file__).resolve().parent` |
|
||||
| `output/结果` | 不改名(gitignored 产物)。`.gitignore` 路径跟着父目录改 |
|
||||
|
||||
搬完后包内仍是扁平脚本:`main.py`、`job.py`、`persist.py`、`repo.py`、`rules.py`、`pipeline.py`、`search_*.py`、`auth_login.py`、`tests/`。
|
||||
|
||||
### 5.2 占位包共同形态
|
||||
|
||||
`rpa_cart/`、`rpa_contract/`、`rpa_rebate/` 各两个文件,模板一致。
|
||||
|
||||
`pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "rpa-<slot>"
|
||||
version = "0.1.0"
|
||||
description = "<一句话职责>"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
package = false
|
||||
|
||||
[[tool.uv.index]]
|
||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
default = true
|
||||
```
|
||||
|
||||
三个 `name` / `description`:
|
||||
|
||||
| 目录 | name | description |
|
||||
| --- | --- | --- |
|
||||
| `rpa_cart` | `rpa-cart` | 洁达加购与支付回传。表 2 → 接口 2。本包尚未实现。 |
|
||||
| `rpa_contract` | `rpa-contract` | 洁达合同班。表 3 合单录入 → 表 4。本包尚未实现。 |
|
||||
| `rpa_rebate` | `rpa-rebate` | 洁达返利班。按合同号查号、录协议 → 表 4。本包尚未实现。 |
|
||||
|
||||
每个 README 必须写清:
|
||||
|
||||
1. 读哪张表、写哪张表、对应哪个口
|
||||
2. 班次(选品随接口 1;加购在选品之后另班;支付另班对准 12:30 / 17:30;合同/返利一天两班,返利建议合同班之后)
|
||||
3. 本期不做(占位)
|
||||
4. 合同包额外一句:页面步骤目前仍在 `rpa_yisaiqi/`,本包能跑之后再删艺赛旗
|
||||
|
||||
`rpa_cart` README 须写明两个入口将来都在本包:`operateType=1` 加购、`operateType=2` 支付回传。不要暗示会拆成第五个包。
|
||||
|
||||
### 5.3 作业与表的对照(写入根 README)
|
||||
|
||||
| 包 | 读 | 写 | 口 |
|
||||
| --- | --- | --- | --- |
|
||||
| `rpa_selection` | 表 1 `deal_flag=0` | 表 2 `operate_type=1`,表 1 处理标志 | 不直接调口;口 2 由 `rpa_cart` 推 |
|
||||
| `rpa_cart` | 表 2 | 表 2 `push_status`;支付行 | 口 2 |
|
||||
| `rpa_contract` | 表 3 `process_status=0` | 表 4 `actionCode=1` | 口 3 收件在 oms;口 4 回写 |
|
||||
| `rpa_rebate` | 表 3 / 表 4 待查号与待录协议 | 表 4 `contractStatus=2`、`actionCode=2` | 口 4 |
|
||||
|
||||
---
|
||||
|
||||
## 6. `资料/` → `docs/`
|
||||
|
||||
`docs/` 已有 `superpowers/`。不要新建第二套文档根。`资料/` 拆开并入后删除空目录。
|
||||
|
||||
已跟踪的用 `git mv`;未跟踪的用普通 `mv`,改完再 `git add`。不要对未跟踪文件跑 `git mv`。
|
||||
|
||||
仓库根 `README.md` 当前未跟踪。本轮按第 5.3 节写好后 `git add README.md`。
|
||||
|
||||
`选品流程/output/结果/.gitkeep` 已跟踪,随父目录 `git mv` 进入 `rpa_selection/output/结果/.gitkeep`。目录名保持 `结果`。
|
||||
|
||||
### 6.1 现行(`docs/` 根下)
|
||||
|
||||
已跟踪(`git mv`):
|
||||
|
||||
| 来源 | 去向 |
|
||||
| --- | --- |
|
||||
| `资料/RPA订单中心接口开发口径_20260930.md` | `docs/` 同名 |
|
||||
| `资料/采购平台选品条件优先项_20260902.txt` | `docs/` 同名 |
|
||||
|
||||
未跟踪(`mv` + `git add`):
|
||||
|
||||
| 来源 | 去向 |
|
||||
| --- | --- |
|
||||
| `资料/README.md` | `docs/README.md`(重写路径,去掉「路径先不动」) |
|
||||
| `资料/RPA订单中心接口口径讨论纪要_20260929.md` | `docs/` 同名 |
|
||||
| `资料/ddl/` | `docs/ddl/` |
|
||||
| `资料/API鉴权.doc` | `docs/` |
|
||||
| `资料/下一会话任务_建表.md` | `docs/` |
|
||||
| `资料/合同系统录入流程.md` | `docs/` |
|
||||
| `资料/合同返利系统字段映射_20260911.xlsx` | `docs/` |
|
||||
| `资料/【项目方案】福建洁达采购合同RPA自动化录入需求_v20260924.docx` | `docs/` |
|
||||
|
||||
口径正文里的 `` `资料/...` `` 改成 `` `docs/...` ``;`` `选品流程/` `` 改成 `` `rpa_selection/` ``。`合同系统录入流程.md` 里指向 `rpa_yisaiqi` 的句子保留。
|
||||
|
||||
### 6.2 归档(`docs/archive/`)
|
||||
|
||||
一期 13 节点主链,以及未列入现行表的旧设计说明。
|
||||
|
||||
已跟踪(`git mv` 进 `docs/archive/`):
|
||||
|
||||
- `PLAN.md`
|
||||
- `一期开发任务清单_表结构_API草案.md`
|
||||
- `一期需求简版说明_业务版.md`
|
||||
- `界面截图证据记录.md`
|
||||
- `采购业务RPA思路20260625.docx`
|
||||
- `采购主链RPA一期实施与验收规范.md`
|
||||
- `采购主链RPA一期开发规划.md`
|
||||
- `采购主链RPA一期设计文档.md`
|
||||
- `采购主链RPA一期需求文档_三方会审版.md`
|
||||
- `采购主链RPA一期需求文档_会议整理版.md`
|
||||
|
||||
未跟踪(`mv` + `git add` 进 `docs/archive/`):
|
||||
|
||||
- `福建洁达采购合同自动化设计说明.md`
|
||||
|
||||
这些文件内部的 `资料/` 链接不改。`docs/README.md` 的历史节指向 `docs/archive/`,并保留原警告:那套模型是「一单合同走状态机」,不要用来解释接口 1 收件箱。
|
||||
|
||||
### 6.3 必须改路径的现行文件
|
||||
|
||||
只改还在用的入口,不扫历史 spec。
|
||||
|
||||
| 文件 | 改什么 |
|
||||
| --- | --- |
|
||||
| `README.md`(仓库根) | 代码入口表换成五包;文档入口改 `docs/` |
|
||||
| `docs/README.md` | 见 6.1 |
|
||||
| `rpa_oms/README.md` | 四个口地图;文档链接 `docs/` |
|
||||
| `rpa_selection/README.md` | 目录名与口径路径 |
|
||||
| `rpa_yisaiqi/README.md` | `` `资料/合同系统录入流程.md` `` → `` `docs/合同系统录入流程.md` `` |
|
||||
| `.gitignore` | `选品流程/` → `rpa_selection/`(`.vscode` 例外规则、secrets、`data/.auth`、`output/结果`) |
|
||||
| `docs/RPA订单中心接口开发口径_20260930.md` | `选品流程/`、`资料/` 路径 |
|
||||
| `docs/RPA订单中心接口口径讨论纪要_20260929.md` | `资料/` 路径 |
|
||||
| `docs/ddl/列对照.md` | `资料/ddl/` → `docs/ddl/` |
|
||||
| `docs/下一会话任务_建表.md` | `资料/` 路径 |
|
||||
| `docs/合同系统录入流程.md` | 若正文有 `资料/` 自引用则改 |
|
||||
|
||||
`docs/superpowers/specs/2026-10-02-selection-split-allocation-design.md` 里的 `选品流程/`、`资料/` 是当时实施记录,不改。
|
||||
|
||||
---
|
||||
|
||||
## 7. `.gitignore`
|
||||
|
||||
`选品流程` 出现在两处,按前缀替换,不要当成一整段覆盖:
|
||||
|
||||
1. 顶部 `.vscode` 例外:`!选品流程/.vscode/` → `!rpa_selection/.vscode/`(两行)
|
||||
2. 底部凭据与产物:`选品流程/secrets.json` 等四行,前缀改 `rpa_selection/`
|
||||
|
||||
其余规则不动。根 README 的代码入口表仍保留 `rpa_yisaiqi` 一行,标明过渡;「五包」指 oms + 四个作业,不是把艺赛旗从入口拿掉。
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误与回归
|
||||
|
||||
这是搬家,没有新的运行时错误分支。失败模式只有路径漏改:
|
||||
|
||||
- 测试或文档还写 `选品流程`、`资料/`(现行入口)
|
||||
- `cd 选品流程` 还出现在现行 README
|
||||
- 占位包被误写成可运行作业(出现 `main.py` 却无实现)
|
||||
|
||||
回归:
|
||||
|
||||
```bash
|
||||
cd rpa_oms && uv run pytest -v
|
||||
cd rpa_selection && uv run pytest -v
|
||||
```
|
||||
|
||||
选品单测不得改断言、不得为搬家放宽。若有测试读仓库根路径或写死 `选品流程`,改为相对包根(`Path(__file__).resolve().parents[1]` 已是包根的保持不动)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 实施顺序
|
||||
|
||||
1. `git mv 选品流程 rpa_selection`;按 5.1 处理未跟踪残留,改该包 `pyproject.toml` 与 README
|
||||
2. 建三个占位包并 `git add`
|
||||
3. 按第 6 节:已跟踪文件 `git mv`,未跟踪文件 `mv` 后 `git add`;重写 `docs/README.md`
|
||||
4. 写根 `README.md` 并 `git add`(该文件当前未跟踪);改 `rpa_oms/README.md`、`rpa_yisaiqi/README.md`、`.gitignore`、搬过来的现行口径
|
||||
5. 跑第 8 节两条 pytest
|
||||
6. 确认根上没有 `选品流程/`、`资料/`
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
# 选品:跨平台仓序 + 拆量落表
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 日期 | 2026-10-02 |
|
||||
| 状态 | 已锁定,本轮实施依据 |
|
||||
| 范围 | `选品流程/` 规则、班次判定、表 2 落盘。不加购,不推接口 2 |
|
||||
| 替代 | `选品流程/README.md` 规则节;口径文档 3.1 / 3.3 中「单仓满量、两边都写满量」的作业语义 |
|
||||
| 不变 | 接口 1 收件、表结构、查询键(条形码)、两平台登录/搜索、接口 2 报文形态 |
|
||||
|
||||
一张 `omsId` 一个品种。`status` 仍是该品种整单采购状态。
|
||||
|
||||
---
|
||||
|
||||
## 1. 要做成什么
|
||||
|
||||
从表 1 领一行,两个商城都搜,**合并候选后统一判定**:
|
||||
|
||||
1. 硬过滤:自营仓、毛利 ≥ 4%、效期 ≥ 12 个月。库存不再是过滤项。
|
||||
2. 按四级仓序(同仓低价、再高库存)排序。
|
||||
3. 同一平台同一仓的多个 sku 都参与拆量,不再只留低价那条。
|
||||
4. 按序贪心拆量,直到计划采购量凑齐。
|
||||
5. 凑齐:每条拆量一行写入表 2,`status=1`,数量是该条拆到的件数,合计等于计划量。
|
||||
6. 凑不齐:不写成功行,表 2 `status=2`,表 1 `deal_flag=2`。
|
||||
|
||||
完成标准:`选品流程` 单测全绿;用内存仓库能演示「杭州 3 + 广东 7 = 计划 10」落两行,以及「合计 9 < 10」整单失败且成功行为 0。
|
||||
|
||||
---
|
||||
|
||||
## 2. 不做什么
|
||||
|
||||
- 真加购、清空购物车、页面点击
|
||||
- 接口 2 推送(`push_status` 仍为 0)
|
||||
- 批准文号搜索、商家名模糊匹配
|
||||
- 中包装 / 起订量
|
||||
- 改表结构、改接口 1
|
||||
|
||||
---
|
||||
|
||||
## 3. 已锁定决策
|
||||
|
||||
| 项 | 决定 |
|
||||
| --- | --- |
|
||||
| 毛利 | `(salePrice - 平台价) / salePrice >= 0.04`,恰好 4% 过 |
|
||||
| 拆量 | 跨仓跨平台贪心拆。凑不齐一件都不落成功行 |
|
||||
| 仓序 | 见第 4 节全称表。级内顺序即距离顺序 |
|
||||
| 湖南壹号、甘肃壹号云 | 移出自营集合 |
|
||||
| 同一平台同一仓 | 只留 1 条(低价,再高库存)。余量给下一仓 |
|
||||
| `multi_platform_flag` | 实际拆到两个 `platform_type` 时为 1;同一平台拆两仓为 0 |
|
||||
| 一边故障、另一边单独凑齐 | 按成功落,不写故障平台失败行 |
|
||||
| 一边故障、剩下的凑不齐 | **系统故障**(可把 `deal_flag` 改回 0 重跑),禁止写成库存不足 |
|
||||
| 两边都搜到、合计不够 | 业务失败「库存不足」,该 `omsId` 不重跑选品 |
|
||||
| 查询键 | 仍是条形码 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 自营仓全称与顺序
|
||||
|
||||
商家名去掉 `[自营]`、压缩空白后必须**等于**下表某一行。对不上 = 非自营,淘汰。
|
||||
|
||||
| 序 | 级 | 平台 | 全称 |
|
||||
| ---: | ---: | --- | --- |
|
||||
| 0 | 1 | 药帮忙 | 杭州小药药自营旗舰店 |
|
||||
| 1 | 1 | 药帮忙 | 长沙小药药自营旗舰店 |
|
||||
| 2 | 1 | 药帮忙 | 湖北小药药自营旗舰店 |
|
||||
| 3 | 2 | 1号药城 | 广东壹号药业有限公司 |
|
||||
| 4 | 2 | 1号药城 | 昆山亿方药业有限公司 |
|
||||
| 5 | 2 | 1号药城 | 湖北亿昊药业有限公司 |
|
||||
| 6 | 3 | 药帮忙 | 济南小药药自营旗舰店 |
|
||||
| 7 | 3 | 1号药城 | 天津壹号药业有限公司 |
|
||||
| 8 | 3 | 1号药城 | 重庆亿昊药业有限公司 |
|
||||
| 9 | 4 | 1号药城 | 辽宁亿昊药业有限公司 |
|
||||
| 10 | 4 | 1号药城 | 陕西壹号药业有限公司 |
|
||||
| 11 | 4 | 1号药城 | 河南壹号医药有限公司 |
|
||||
| 12 | 4 | 1号药城 | 重庆壹号医药有限公司 |
|
||||
| 13 | 4 | 药帮忙 | 重庆小药药自营旗舰店 |
|
||||
|
||||
删除 `WAREHOUSE_ALIASES` 里甘肃那条。平台字段与名单不一致(例如 1 号药城搜出杭州小药药)仍按**商家全称**定序,不按搜索来源改序。
|
||||
|
||||
---
|
||||
|
||||
## 5. 算法
|
||||
|
||||
计划量 `qty = int(plan_purchase_qty)`。`qty <= 0` 仍走现有 `NO_QTY`。库存按件向下取整后再拆。
|
||||
|
||||
### 5.1 过滤(单条候选)
|
||||
|
||||
`evaluate_candidate` 不再看库存。淘汰原因仅:
|
||||
|
||||
- `NOT_SELF_WAREHOUSE`
|
||||
- `PRICE_UNPARSEABLE`
|
||||
- `MARGIN_LT_4PCT`(原 `MARGIN_LT_5PCT`)
|
||||
- `EXPIRY_UNPARSEABLE` / `EXPIRY_LT_12M`
|
||||
- `NO_SALE_PRICE`(采购行级,先于搜品)
|
||||
|
||||
库存为 `None` 或向下取整后 `<= 0`:可以过过滤,但拆量时当作 0,拆不到量。
|
||||
|
||||
### 5.2 同仓
|
||||
|
||||
不再按 `(mall_name, vendor_norm)` 折成一条。过过滤的 sku 全部进入拆量。同仓低价先吃,不够再吃同仓下一条;仓序仍优先于跨仓低价。不再产生 `SAME_WAREHOUSE_NOT_BEST`。
|
||||
|
||||
### 5.3 排序
|
||||
|
||||
` (WAREHOUSE_RANK[vendor_norm], price, -stock) `。`price` 缺失视为 `+inf`(过滤阶段已淘汰,排序不应再见到)。
|
||||
|
||||
### 5.4 拆量
|
||||
|
||||
```
|
||||
remaining = qty
|
||||
lines = []
|
||||
for candidate in sorted_passed:
|
||||
take = min(int(stock), remaining)
|
||||
if take <= 0:
|
||||
continue
|
||||
lines.append(Allocation(candidate, take))
|
||||
remaining -= take
|
||||
if remaining == 0:
|
||||
break
|
||||
```
|
||||
|
||||
库存 `None` 时 `take` 当 0,不要对 `None` 做 `int()`。
|
||||
|
||||
`remaining == 0` → 成功,`lines` 即为表 2 成功行。
|
||||
`remaining > 0` → 见第 6 节,**丢弃 `lines`**,不落成功行。
|
||||
|
||||
禁止:先落部分成功行再标失败。
|
||||
|
||||
---
|
||||
|
||||
## 6. 班次:先搜齐再判定
|
||||
|
||||
`job.run_batch` 对一行先跑完本次启用的全部平台搜索,再调用一次统一判定。禁止再「每平台独立选仓、独立满量写入」。
|
||||
|
||||
每个平台的搜索结果是三种之一:
|
||||
|
||||
| 结果 | 含义 |
|
||||
| --- | --- |
|
||||
| `candidates` | 搜到 0..n 条(空列表 = 无命中,不是故障) |
|
||||
| `LOGIN_FAILED:...` | 本平台本班不可用 |
|
||||
| `QUERY_FAILED:...` | 本行本平台查询异常 |
|
||||
|
||||
无条形码:两个平台都不搜,业务失败 `NO_BAR_CODE`(与现在相同)。
|
||||
|
||||
统一判定:
|
||||
|
||||
1. 采购行缺销售价 / 数量:`SKIPPED`,不搜也行(保持现有,在搜之前拦)。
|
||||
2. 把所有成功搜到的 `candidates` 合并,走第 5 节。
|
||||
3. 凑齐:成功。故障平台的错误忽略,不写该平台失败行。
|
||||
4. 凑不齐,且存在任一平台 `LOGIN_FAILED` / `QUERY_FAILED`:整单 **系统故障**。
|
||||
5. 凑不齐,且无系统故障:业务失败。
|
||||
|
||||
只跑 `--platform yaoex` 时,可用候选只有 1 号药城。凑不齐且该平台无故障 → 业务失败;该平台故障 → 系统故障。
|
||||
|
||||
---
|
||||
|
||||
## 7. 落表
|
||||
|
||||
成功行(每条 `Allocation` 一行):
|
||||
|
||||
| 列 | 值 |
|
||||
| --- | --- |
|
||||
| `operate_type` | 1 |
|
||||
| `oms_id` | 本行 |
|
||||
| `platform_type` | 48 药帮忙 / 66 1号药城 |
|
||||
| `supplier_name` | `vendor_raw` |
|
||||
| `bar_code` / `org_code` | 原样 |
|
||||
| `purchase_price` | 该候选平台价,四位小数 |
|
||||
| `purchase_qty` | 该条 `take`,**不是**计划全量 |
|
||||
| `purchase_total` | 单价 × 该条数量,四位小数 |
|
||||
| `status` | 1 |
|
||||
| `fail_reason` | null |
|
||||
| `push_status` | 0 |
|
||||
| `multi_platform_flag` | 本单成功行里出现 ≥2 个不同 `platform_type` 则为 1,否则 0 |
|
||||
|
||||
表 1:`deal_flag=1`,`deal_result` 形如 `选品成功:药帮忙、1号药城`(按成功行出现的商城去重,顺序:药帮忙、1号药城)。
|
||||
|
||||
业务失败:每个**已启用且已判定**的平台落一行 `status=2`,`purchase_qty` 仍用计划量(造数校验:数量 > 0),`purchase_price` 仍用销售价兜底。`supplier_name` 仍为「无符合规则供应商」。`multi_platform_flag` 按失败行平台数(≥2 个平台则为 1)。
|
||||
|
||||
业务失败原因(`fail_reason` / `deal_result`)。先看过滤后是否留下可拆候选(含库存为 0。以过过滤的集合为准):
|
||||
|
||||
1. `NO_BAR_CODE` / `NO_SALE_PRICE` / `NO_QTY`:沿用现有中文
|
||||
2. **过过滤集合非空,但拆不满**:一律 `无符合规则商品,库存不足`。rejected 里即使同时有非自营、效期、毛利淘汰,也不改这条。
|
||||
3. **过过滤集合为空**时,才按候选清单(含淘汰原因)往下看,取第一条能成立的:
|
||||
- 含 `MARGIN_LT_4PCT`:`无符合规则商品,毛利不足 4%`
|
||||
- 含 `EXPIRY_LT_12M` / `EXPIRY_UNPARSEABLE`:`无符合规则商品,效期不足 12 个月`
|
||||
- 含 `NOT_SELF_WAREHOUSE`:`无符合规则商品,非自营仓`
|
||||
- 全部无命中:`无符合规则商品,平台无命中`
|
||||
- 其它:`无符合规则商品`
|
||||
|
||||
第 9 节必测补一条:杭州自营库存 4 + 若干非自营/效期短淘汰、计划 10 → 原因必须是库存不足,不能是非自营或效期。
|
||||
|
||||
系统故障:每个启用平台一行 `status=2`,原因带 `系统故障:`。有故障的平台写该平台错误;无故障但被连带失败的平台写 `系统故障:对端平台不可用,合计库存不足`。表 1 `deal_result` 以 `系统故障:` 开头。`SelectionResult.mall_errors` 按平台给出原始错误串,persist 据此写各行,禁止所有失败行共用一句。
|
||||
|
||||
`JobSummary` 按表 2 行计:`added` = `status=1` 行数,`skipped` = 业务失败行数,`failed` = 系统故障行数。
|
||||
|
||||
---
|
||||
|
||||
## 8. 模块怎么改
|
||||
|
||||
| 文件 | 改什么 |
|
||||
| --- | --- |
|
||||
| `rules.py` | 新名单;`MIN_MARGIN=0.04`;`evaluate_candidate` 去掉库存;新增 `allocate()`(过滤+同仓全留+排序+拆量);`select_warehouse` 删除或改为调用 `allocate` 后取第一仓——**禁止**再作为班次入口 |
|
||||
| `pipeline.py` | 新增 `run_selection(purchase, mall_outcomes)`,一次产出整单结果。`run_one_platform` 只留给单测过渡,班次不再调用 |
|
||||
| `job.py` | 先收集各平台搜索结果,再 `run_selection` |
|
||||
| `persist.py` | 吃整单结果:成功按拆量多行;`multi_platform_flag` 按平台种类数;失败原因 5% → 4%,并增加库存不足 |
|
||||
| `README.md` | 规则节换成本文摘要 |
|
||||
| `资料/RPA订单中心接口开发口径_20260930.md` | 3.1 改为毛利 4%、库存改为拆量;3.2 示例「毛利不足 5%」改为 4%;3.3 「只凑到一部分」改为整单失败、不落部分成功 |
|
||||
| `资料/采购平台选品条件优先项_20260902.txt` | 文首加「已被 2026-10-02 spec 替代」;不改历史原文 |
|
||||
|
||||
搜索、登录、`repo.py`、表 DDL 不动。
|
||||
|
||||
建议的数据结构(名称可改,语义不要改):
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class MallOutcome:
|
||||
mall_name: str
|
||||
candidates: list[Candidate] | None = None
|
||||
failed_reason: str | None = None # LOGIN_FAILED: / QUERY_FAILED:
|
||||
|
||||
@dataclass
|
||||
class Allocation:
|
||||
candidate: Candidate
|
||||
qty: int
|
||||
|
||||
@dataclass
|
||||
class SelectionResult:
|
||||
kind: str # SUCCESS / BUSINESS_FAIL / SYSTEM_FAIL
|
||||
reason: str # SELECTED / STOCK_INSUFFICIENT / NO_HIT / LOGIN_FAILED:... 等
|
||||
allocations: list[Allocation]
|
||||
rejected: list[Rejected]
|
||||
malls: list[str] # 本行启用过的平台,落失败行用
|
||||
mall_errors: dict[str, str] # 平台 → LOGIN_FAILED:... / QUERY_FAILED:...
|
||||
```
|
||||
|
||||
`reason` 在 `run_selection` / `allocate` 里用过过滤集合算完。persist 只做中文映射,禁止从 `rejected` 反推库存不足(库存为 0 的过过滤候选不在 rejected 里)。
|
||||
|
||||
`kind` 决定落表,不要用「有没有 FAILED 决策」去猜:
|
||||
|
||||
- `SUCCESS`:`allocations` 合计数量必须等于 `int(qty)`;表 1 `deal_flag=1`,表 2 只写成功行
|
||||
- `BUSINESS_FAIL`:`allocations` 必须为空;表 1 `deal_flag=2`,原因无「系统故障」前缀,该 `omsId` 不重跑
|
||||
- `SYSTEM_FAIL`:`allocations` 必须为空;表 1 `deal_flag=2`,`deal_result` 以 `系统故障:` 开头
|
||||
|
||||
`pipeline` 的展示字段若仍用 `结论=ADDED/SKIPPED/FAILED`,映射为 SUCCESS→ADDED、BUSINESS_FAIL→SKIPPED、SYSTEM_FAIL→FAILED。`persist` 只认 `kind`。
|
||||
|
||||
成功行 `platform_type` 按该候选的 `mall_name`(搜索来源)映射 48/66,仓序只按商家全称。第 4 节「不按搜索来源改序」只约束排序,不改 `platform_type`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 必须覆盖的行为(单测)
|
||||
|
||||
规则:
|
||||
|
||||
- 毛利恰好 4% 过;3.9% 淘汰(销售价 100、平台价 96 过,96.1 淘汰)
|
||||
- 杭州小药药优先于广东壹号
|
||||
- 湖南壹号、甘肃壹号云视为非自营
|
||||
- 近仓 3、次仓 20、计划 10 → 近仓 3 + 次仓 7
|
||||
- 合计 9、计划 10 → 无 allocation
|
||||
- 同一仓两 SKU(价 90 库存 5、价 92 库存 100)、计划 10 → 价 90 拿 5,价 92 拿 5,不把余量给下一仓
|
||||
- 库存 3.9 按 3 件拆
|
||||
|
||||
班次 / 落表:
|
||||
|
||||
- 两平台拆开:药帮忙 3 + 1 号药城 7,两行 `status=1`,数量分别为 3 和 7,`multi_platform_flag=1`,表 1 `deal_flag=1`
|
||||
- 只在药帮忙拆两仓:两行、同一 `platform_type=48`、`multi_platform_flag=0`
|
||||
- 药帮忙登录失败、广东壹号库存 20、计划 10 → 只落 1 号药城成功一行
|
||||
- 药帮忙登录失败、1 号药城库存 4、计划 10 → 系统故障,无成功行
|
||||
- 两边都搜到、合计库存 4、计划 10 → 业务失败「库存不足」,无成功行
|
||||
- 杭州自营库存 4 + 路边店/效期短淘汰、计划 10 → 业务失败「库存不足」(不是非自营、不是效期)
|
||||
- 无条形码仍不搜
|
||||
|
||||
---
|
||||
|
||||
## 10. 风险
|
||||
|
||||
- 搜到落表之间库存会变。这期只落判定,真加购时再处理。
|
||||
- 中包装可能导致拆出的件数加不进车。真加购时再对。
|
||||
- 订单中心若假定「一个 `omsId` + 一个 `platform_type` 只有一行」,同一平台拆两仓会撞上。表结构允许多行;联调时若对方拒,再改成同平台合一行(本期不合)。
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
# 合同班:表 3 合单录入 + 接口 4 组包
|
||||
|
||||
| 文档属性 | 内容 |
|
||||
| --- | --- |
|
||||
| 日期 | 2026-10-05 |
|
||||
| 状态 | 已锁定,本轮实施依据 |
|
||||
| 范围 | `rpa_contract/` 从表 3 领取、按 `supplierId` 合单、调现有页面 runner、尽调闸、写表 4 并回写表 3;`rpa_oms/app/outbound/` 接口 4 组包 |
|
||||
| 对照 | `docs/RPA订单中心接口开发口径_20260930.md` 第 4 节、第 6 节;`docs/合同系统录入流程.md`;Excel `4合同协议录入结果接口` |
|
||||
| 不变 | 接口 3 收件、表 3/4 DDL、页面步骤主体(登录/起草/货品/提交/读号)、接口 2 组包形态 |
|
||||
|
||||
合同班另起进程。HTTP 收件不同步跑作业。接口 4 真实 path 仍空:只组包,不发 HTTP。
|
||||
|
||||
---
|
||||
|
||||
## 1. 要做成什么
|
||||
|
||||
1. 合同班启动瞬间冻结本班可录名单:表 3 `process_status` 0→3。
|
||||
2. 按 `supplierId` 合成一份合同;头字段取该组按 `fid` 的第一行;货品行全收。
|
||||
3. `contractEntryStatus=0` 且已有 `contractNo` 的行不领取,留给返利班。
|
||||
4. 注入 runner 调现有合同页面流。单测不打开浏览器。
|
||||
5. 提交成功:表 4 `actionCode=1`、`contractStatus=1`、带合同号;表 3 `process_status=1`。同一合同下多个 `omsId` 回同一套号和状态。
|
||||
6. 失败(未尽调 / 页面挂):表 4 `contractStatus=3` 带原因;表 3 `process_status=2`。
|
||||
7. 接口 4 从表 4 取 `push_status=0` 组装 `{"data":[...]}`,一批最多 500。不改 `push_status`,不 POST。
|
||||
|
||||
完成标准(内存替身,不连库、不开浏览器):
|
||||
|
||||
- `cd rpa_contract` → `uv run pytest tests/test_job.py::test_job_merges_same_supplier_and_writes_table4 -q`
|
||||
- 同一供应商两行、两个 `omsId`:runner 调一次;表 4 两行同一合同号;表 3 两条 `process_status=1`
|
||||
- 带 `contractNo` 的协议重录行不领
|
||||
- `cd rpa_oms` → `uv run pytest tests/test_contract_agreement_outbound.py::test_assembles_unpushed_rows_as_document_envelope -q`
|
||||
|
||||
---
|
||||
|
||||
## 2. 不做什么
|
||||
|
||||
- 不实现返利班、不打开返利中心、不回 `contractStatus=2` / `actionCode=2`
|
||||
- 不向订单中心发 HTTP,不改 `push_status`
|
||||
- 不在接口 3 请求路径里录合同
|
||||
- 不按 `omsId` 或供应商名称合单
|
||||
- 不把下午新进行追加进上午已录合同(冻结快照自然保证)
|
||||
- 不默认合并/上传 PDF(接口 3 这条链没有平台扫描件)
|
||||
- 不改表结构、不改接口 1/2/3
|
||||
- 不改 `rpa_selection/`、`rpa_cart/`、`rpa_rebate/`
|
||||
- 合同班不 import `rpa_oms`(作业写表 4;组包在 oms 另调,对齐选品写表 2 / 接口 2 另组)
|
||||
- 不把 JSON 干跑入口 `main.py --input-json` 拆掉
|
||||
|
||||
---
|
||||
|
||||
## 3. 已锁定决策
|
||||
|
||||
| 项 | 决定 |
|
||||
| --- | --- |
|
||||
| 包边界 | 领取/合单/落表在 `rpa_contract`;接口 4 组包在 `rpa_oms/app/outbound/contract_agreement.py` |
|
||||
| 冻结 | `claim_pending` 一次领完本班可录行,0→3;过程中新进的仍是 0,下一班再处理 |
|
||||
| 可录 | `process_status=0` 且**不是**(`contract_entry_status=0` 且 `contract_no` 非空) |
|
||||
| 协议重录行 | 永不领取,保持 `process_status=0` |
|
||||
| 合单键 | `supplier_id`。头字段 = 该组 `fid` 升序第一行 |
|
||||
| 采购员 / 计划员 | 只存在表 3 行上,本轮不填页面(现有 flow 无对应控件) |
|
||||
| 表 4 粒度 | 每个 `oms_id` 一行。同一 `oms_id` 多货品仍一行。`org_code` 跟该 `oms_id` 在表 3 的原值 |
|
||||
| 表 4 `fid` | 序列触发器生成,INSERT 不绑这一列 |
|
||||
| 表 4 审计 | `create_user_id`/`update_user_id` = 0;名称 = `合同班`;`push_status=0` |
|
||||
| 客商 | `customerCode` = 第一行 `supplier_mdm_code`;`customerName` = `supplier_name` |
|
||||
| 日期 | 页面签订日/开始日/结束日 = 第一行 `contract_sign_date` / `contract_start_date` / `contract_end_date`(`YYYY-MM-DD`) |
|
||||
| S08 兼容 | `ContractFlowContext` 增加可选 `contract_sign_date`、`contract_start_date`。未给时回退 `order_date`(JSON 干跑不破) |
|
||||
| PDF | `source_type=""`,不走医药城合并上传 |
|
||||
| 尽调 | S06 之后扫页面正文含「未尽调」则失败,原因固定「客户未尽调」,不提交。无独立选择器 |
|
||||
| runner | `Callable[[ContractDraft], ContractOutcome]`。生产包装 `run_contract_create_flow`;单测注入 |
|
||||
| 崩溃 | 停在表 3 `process_status=3`,人工改回 0 再跑;未写表 4 |
|
||||
| 组包范围 | 全部 `push_status=0`,不按 `actionCode` 过滤(返利以后写 `actionCode=2` 也走同一函数) |
|
||||
| 组包拆批 | 同一非空 `contractNo` 不拆批,算法对齐接口 2 的 `omsId`。`contractNo` 为空(失败行)按行独立,不合成一组 |
|
||||
| `omsId` | JSON 整数 |
|
||||
| HTTP | 不做。path 空 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 领取与合单
|
||||
|
||||
### 4.1 行模型
|
||||
|
||||
`PurchaseContractRow`(表 3 已有列,camelCase 不进作业):
|
||||
|
||||
`fid, oms_id, supplier_id, supplier_name, contract_sign_date, contract_start_date, contract_end_date, payment_term, goods_id, goods_name, reg_no, spec, unit, qty, unit_price, purchaser_id, purchaser_name, planner_id, planner_name, contract_entry_status, contract_no, process_status, org_code, goods_mdm_code, supplier_mdm_code`
|
||||
|
||||
### 4.2 SQL 语义
|
||||
|
||||
```
|
||||
WHERE process_status = 0
|
||||
AND NOT (NVL(contract_entry_status, -1) = 0
|
||||
AND contract_no IS NOT NULL
|
||||
AND LENGTH(TRIM(contract_no)) > 0)
|
||||
ORDER BY fid
|
||||
```
|
||||
|
||||
领取:对命中行 `UPDATE ... SET process_status = 3, process_time = SYSTIMESTAMP, update_time = SYSTIMESTAMP WHERE fid = :fid AND process_status = 0`。`rowcount=1` 才算领到。可选 `limit` 仅供单测。
|
||||
|
||||
`contract_no` 空白串视为空。
|
||||
|
||||
### 4.3 分组
|
||||
|
||||
对已领取列表按 `fid` 升序扫描,`OrderedDict` 按 `supplier_id` 归组。组内顺序保持 `fid` 升序。每个 `supplier_id` 一份合同。
|
||||
|
||||
### 4.4 草稿映射
|
||||
|
||||
第一行 → 头:
|
||||
|
||||
| 草稿 | 来源 |
|
||||
| --- | --- |
|
||||
| `supplier_name` / `customer_name` | `supplier_name` |
|
||||
| `customer_code` | `supplier_mdm_code` |
|
||||
| `order_date` | `contract_sign_date`(标题用) |
|
||||
| `contract_sign_date` / `contract_start_date` / `contract_end_date` | 对应三列,格式 `YYYY-MM-DD` |
|
||||
| `payment_days` | `str(payment_term)` |
|
||||
| `is_prepayment` | `否` |
|
||||
| `payment_method` | `账期支付` |
|
||||
| `contract_category` | `采购协议` |
|
||||
| `source_type` | `""` |
|
||||
| `amount` | `sum(qty * unit_price)`,四位小数字符串 |
|
||||
| `belong_year` | 签订日年份 |
|
||||
|
||||
每一行 → `ContractGoodsLine`:
|
||||
|
||||
| 字段 | 来源 |
|
||||
| --- | --- |
|
||||
| `operation_code` | `goods_id`(弹窗实际搜 `goods_code`) |
|
||||
| `goods_code` | `goods_id` |
|
||||
| `goods_name` | `goods_name` |
|
||||
| `approval_no` | `reg_no` |
|
||||
| `spec` / `unit` | 同名 |
|
||||
| `quantity` | `qty` |
|
||||
| `mdm_code` | `goods_mdm_code` |
|
||||
| `unit_price` | 四位小数字符串 |
|
||||
|
||||
第一行 `supplier_mdm_code` 为空:整组业务失败,原因「缺少供应商MDM码」,不调 runner。
|
||||
|
||||
---
|
||||
|
||||
## 5. 页面改动(只两处)
|
||||
|
||||
现有 `run_contract_create_flow` 继续串 S02–S13。本轮只加:
|
||||
|
||||
1. **S08 日期**(`fill_contract_amount_and_dates`):签订日 = `contract_sign_date or order_date`;开始日 = `contract_start_date or order_date`;结束日仍用 `contract_end_date`。
|
||||
2. **尽调闸**(S06 之后、S07 之前):`check_customer_diligence` 读页面正文。含「未尽调」则抛 `DiligenceError`(`errors.py`,`NodeError` 子类,文案「客户未尽调」)。生产 runner 让它冒泡到 job。
|
||||
|
||||
不改货品弹窗搜索、不改读号策略、不改 JSON 干跑提交前停住的行为。
|
||||
|
||||
---
|
||||
|
||||
## 6. 班次结果
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class ContractOutcome:
|
||||
kind: str # "ok" | "diligence" | "fail"
|
||||
contract_no: str | None
|
||||
reason: str | None
|
||||
```
|
||||
|
||||
| `kind` | 表 3 `process_status` / `process_result` | 表 4 |
|
||||
| --- | --- | --- |
|
||||
| `ok` 且有合同号 | `1` / 成功说明 | `action_code=1`,`contract_status=1`,`contract_no`,失败原因空 |
|
||||
| `diligence` | `2` / `客户未尽调` | `action_code=1`,`contract_status=3`,`contract_fail_reason=客户未尽调`,`contract_no` 空 |
|
||||
| `fail` 或其他异常 | `2` / 原因(截到 1000) | `contract_status=3`,`contract_fail_reason` 截到 500,`contract_no` 空 |
|
||||
| runner 返回 `ok` 但合同号空 | 同 `fail`,「submit 模式未读取到真实合同编号」 | 同 `fail` |
|
||||
|
||||
同一组所有表 3 `fid` 写同一套处理状态。表 4 按组内**去重后的 `oms_id`**(首次出现顺序)各插一行,合同号和状态相同;`agreement_*` 全空;`org_code` 取该 `oms_id` 对应表 3 行。
|
||||
|
||||
一次 `save_group`:先插表 4,再改表 3。中途崩溃停在 3、表 4 可能部分写入——Oracle 同一连接一事务提交。内存替身同样先写 4 再改 3,一次调用内完成。
|
||||
|
||||
生产 runner:由 `ContractDraft` 填 `ContractFlowContext`(含三个日期),调 `run_contract_create_flow`,读到号 → `kind=ok`;捕获 `DiligenceError` → `kind=diligence`;其他 `NodeError` / 异常 → `kind=fail`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 接口 4 组包
|
||||
|
||||
对标 `app/outbound/cart_settlement.py`。
|
||||
|
||||
```python
|
||||
load_contract_agreement_envelope(repo, limit=500) -> {"data": [...]}
|
||||
```
|
||||
|
||||
`repo.list_unpushed()`:`WHERE push_status = 0 ORDER BY fid`。
|
||||
|
||||
每条 camelCase:
|
||||
|
||||
```
|
||||
fid, omsId, actionCode, contractStatus, contractFailReason,
|
||||
contractNo, agreementStatus, agreementFailReason,
|
||||
agreementId, agreementDtlId, orgCode
|
||||
```
|
||||
|
||||
- `fid` / `omsId` / `actionCode` / 状态码:JSON 整数(库值 `None` 则字段为 `null`)
|
||||
- 字符串字段原样,空为 `null`
|
||||
- 不改 `push_status`,不发 HTTP
|
||||
|
||||
拆批:按 `contract_no` 分组(仅非空)。空 `contract_no` 每行单独一组。一组放不进当前批且批已有内容则停;批为空则整组放入(可超过 500,对齐接口 2)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件落点
|
||||
|
||||
**`rpa_contract/`**(作业扁平,与选品同款):
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `group.py` | 可录判定、按 `supplier_id` 分组、草稿映射、金额四位小数 |
|
||||
| `job.py` | `claim_pending` → 分组 → runner → `save_group` |
|
||||
| `repo.py` | `MemoryContractRepo` + `OracleContractRepo`:领取、插表 4、回写表 3 |
|
||||
| `persist.py` | 由 `ContractOutcome` + 组行生成表 4 dict、表 3 状态 |
|
||||
| `db.py` | 拷选品连接模式;空缺复用 `rpa_oms/.env`;用户名戳 `合同班` |
|
||||
| `errors.py` | 新增 `DiligenceError` |
|
||||
| `contract_flow_steps.py` | S08 日期回退;S06 后尽调扫描 |
|
||||
| `run_batch.py` | 班次入口:`from_env` + 生产 runner。保留现有 `main.py` JSON 干跑 |
|
||||
| `pyproject.toml` | 加 `oracledb` |
|
||||
| `tests/test_job.py` | 点名合单/跳过重录/未尽调 |
|
||||
| `tests/test_group.py` | 头字段取第一行、金额合计、空白 `contract_no` 仍可领 |
|
||||
|
||||
**`rpa_oms/`**:
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| `app/outbound/contract_agreement.py` | `load_` / `build_contract_agreement_envelope` |
|
||||
| `app/outbound/memory_repo.py` | 增 `MemoryContractAgreementRepo.list_unpushed` |
|
||||
| `app/outbound/oracle_repo.py` | 增 `OracleContractAgreementRepo`(或同文件新类)只读未推行 |
|
||||
| `tests/test_contract_agreement_outbound.py` | 点名组包 |
|
||||
| `examples/contract_agreement_batch.json` | 与 Excel 请求示例同形(`omsId` 用整数) |
|
||||
| `README.md` | 接口 4:组包已做,path 空,不发 HTTP |
|
||||
|
||||
`rpa_contract/README.md`:合单/尽调/写表 4 本轮已接;接口 4 组包在 oms,本班不推。
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收用例(点名)
|
||||
|
||||
### 9.1 `test_job_merges_same_supplier_and_writes_table4`
|
||||
|
||||
两行同一 `supplier_id`,`oms_id` 分别为 `2026100501`、`2026100502`,`process_status=0`。第三行 `contract_entry_status=0` 且 `contract_no="HT-OLD"`。runner 记调用次数并返回 `kind=ok, contract_no="HT20261005001"`。
|
||||
|
||||
断言:
|
||||
|
||||
- runner 调用 1 次;草稿两行货品;客商编码为第一行 MDM
|
||||
- 表 4 两行:同一 `contract_no`,`action_code=1`,`contract_status=1`,`push_status=0`,`oms_id` 各一
|
||||
- 前两行表 3 `process_status=1`;第三行仍为 0(未领)
|
||||
|
||||
### 9.2 `test_job_marks_diligence_fail`
|
||||
|
||||
一组一行。runner 返回 `kind=diligence, reason="客户未尽调"`。
|
||||
|
||||
断言:表 3 `process_status=2`,`process_result` 含「客户未尽调」;表 4 `contract_status=3`,`contract_no` 空,`contract_fail_reason="客户未尽调"`。
|
||||
|
||||
### 9.3 `test_assembles_unpushed_rows_as_document_envelope`
|
||||
|
||||
内存两行 `push_status=0`:一行 `action_code=1, contract_status=1, contract_no="HT1"`;一行 `action_code=2, agreement_status=1`。另有一行 `push_status=1` 不进包。
|
||||
|
||||
断言信封 `data` 两条,camelCase 与第 7 节字段一致,`omsId` 为 `int`,已推行不出现。
|
||||
|
||||
另测(对齐接口 2 `_take_complete_groups`):同一 `contract_no` 三行、`limit=2`、当前批为空 → 整组三条都放入(允许超过 limit)。两个不同 `contract_no` 各一行、`limit=1` → 只出第一组。
|
||||
|
||||
---
|
||||
|
||||
## 10. 风险与边界
|
||||
|
||||
- 尽调靠正文扫描,页面若换文案会漏拦。本轮不补选择器。
|
||||
- 货品弹窗仍按 `goods_id` / 品名搜,不改成 MDM。
|
||||
- 表 4 没有表 3 `fid` 列,回执关联键是表 4 自己的 `fid` 和 `omsId`。
|
||||
- 真库领取/落表在 Windows 上做,本轮验收不连库。
|
||||
- 接口 4 path、Token、是否带 `orgSecret` 仍按口径第 6 节后置,本轮不发明 URL。
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# 下一会话任务:四张接口底表 DDL
|
||||
|
||||
先读再写,不要发挥。
|
||||
|
||||
必读:
|
||||
|
||||
1. `docs/RPA订单中心接口开发口径_20260930.md`(作业口径,列不能和口径打架)
|
||||
2. `docs/RPA订单中心接口口径讨论纪要_20260929.md`
|
||||
3. 接口 Excel 四张「数据表」sheet(列名、类型、备注以 Excel 为准)
|
||||
当前文件:`/Users/yuuuu/Downloads/RPA与订单中心接口设计_20260922.xlsx`
|
||||
若 Downloads 有更新,用最新一份。
|
||||
4. 不要凭方案 5.5 加列。返利默认值是填页面用的,不是表结构。
|
||||
|
||||
## 交付
|
||||
|
||||
给出 Oracle 建表语句,四张都是 **RPA 库暂存**,不是订单中心的对码功能表。对码在订单中心做完,本方不建对码引擎。
|
||||
|
||||
选品这一步本方只有两张:收件 + 回传。另外两张是合同收件、合同/协议回写。
|
||||
|
||||
- 销售商品对码数据(接口 1):选品收件箱。Excel 沿用对方名字,实为待选品行,不是对码表。
|
||||
- 加购及结算商品数据(接口 2):选品结果回传缓冲。
|
||||
- 采购合同数据(接口 3)
|
||||
- 合同协议录入结果(接口 4)
|
||||
|
||||
建议落盘:`docs/ddl/` 或仓库内现有 SQL 目录(若已有 Oracle 脚本目录,跟现有走)。
|
||||
|
||||
每张表包含:列、默认值、主键/唯一约束、口径里点名的索引(如条形码普通索引、接口 1 的 `oms_id` 幂等唯一、接口 3 的来源 `fid` 唯一)。表名、列名跟 Excel 英文名,不要自创拼音。
|
||||
|
||||
必须带上 Excel 已有、口径也锁了的列:`org_code`、MDM 码、`customer_type`、`multi_platform_flag`、`push_status` 等。`oms_id` 用 `number(20)`。
|
||||
|
||||
## 不要做
|
||||
|
||||
- 不要写选品/合同/返利页面流程
|
||||
- 不要写接收 API、推送客户端
|
||||
- 不要改口径文档
|
||||
- 不要把测试密码写入仓库
|
||||
- 接口 2、4 路径空着,DDL 不管网关
|
||||
|
||||
## 不确定时
|
||||
|
||||
Excel 与口径冲突:口径赢在作业语义,列集合以 Excel 表 sheet 为准。
|
||||
Excel「待确认」列:按现稿建,不要删列。在 DDL 文件头用注释列出待确认项。
|
||||
缺主键:接口 1 业务幂等是 `oms_id`;接口 2、4 的 `fid` 是本方自增主键;接口 3 的 `fid` 是订单中心来源唯一序号。
|
||||
|
||||
做完只交 DDL 和与 Excel 的列对照(缺了什么、多了什么)。不要展开下一阶段。
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# 合同系统录入流程(现行)
|
||||
|
||||
对照:`RPA订单中心接口开发口径_20260930.md` 第 4 节。
|
||||
页面动作对照:`rpa_contract/contract_flow_steps.py`、`rpa_contract/selectors/contract_create_selectors.json`、`rpa_contract/examples/`。
|
||||
|
||||
状态中心 job / 13 节点 / start-complete-fail **不再使用**。合单、班次、回写以口径为准;点哪些控件以 `rpa_contract` 为准。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据从哪来
|
||||
|
||||
订单中心推接口 3,落入 `rpa_purchase_contract_data`。合同班读 `process_status=0`。
|
||||
|
||||
一天两班(中午、下午)。启动瞬间冻结本班名单,跑的过程中新进的下一班再处理。
|
||||
|
||||
按 `supplierId` 合成一份合同:
|
||||
|
||||
- 不用 `omsId`,不用供应商名称
|
||||
- 头字段(账期、签订日、起止日、采购员、计划员)取该班该供应商**第一行**
|
||||
- 下午同一供应商另开一份,不改上午已录合同
|
||||
- `contractEntryStatus=0` 且带 `contractNo`:只录返利协议,不进合单、不重开合同
|
||||
|
||||
旧脚本按「供应商 + 订单日期」建单,并且把签订日/开始日写成订单日期。那是上一套模型,不要抄。
|
||||
|
||||
---
|
||||
|
||||
## 2. 页面步骤(合同测试系统)
|
||||
|
||||
地址:`http://contract-test.gzmpc.com:8080/`
|
||||
测试账号见 `rpa_contract/README.md`。客商必须是系统里真实存在的操作码,占位符搜不到。
|
||||
|
||||
脚本现用直达起草/模板 URL,绕过左侧菜单和「点模板」的前端委托(Selenium 点不中):
|
||||
|
||||
- 起草:`DRAFT_PAGE_URL`
|
||||
- 非格式采购协议(2025 集团版)模板 ID:`198d10b04325279b8ffeb8b4c6493b5a` → `FORM_PAGE_URL`
|
||||
|
||||
顺序:
|
||||
|
||||
1. 打开合同系统并登录
|
||||
2. 进起草,套非格式采购协议模板
|
||||
3. 签约方:客户编码、客户名称(用接口 3 的供应商 MDM / 名称,以页面能搜到的为准)
|
||||
4. 基础信息:合同版本类型、合同类别、结算类型、合同背景概述、合同性质=主合同
|
||||
5. 金额与日期:签订日 / 开始日 / 结束日用接口 3 第一行的 `contractSignDate`、`contractStartDate`、`contractEndDate`
|
||||
6. 货品明细:该班该供应商合进来的行(货品、规格、数量、单价、MDM)
|
||||
7. 付款与标志:这期一律账期,**不勾预付款**;付款期限用 `paymentTerm`。页面「账期支付」对应选项「电汇」(见脚本 `_PAYMENT_ALIASES`)
|
||||
8. **尽调**:看合同系统页面回显。未尽调 → 本单失败,原因「客户未尽调」,不往下填。旧脚本没有这一步,口径有
|
||||
9. 点**提交**,不要点暂存。暂存号不拿去录返利
|
||||
10. 从「我的合同」读正式合同号
|
||||
|
||||
医药城同供应商同日 PDF 合并上传,是旧主链(平台导出扫描件)的步骤。接口 3 这条链有没有附件,录合同时再核对,不要默认还去拼 PDF。
|
||||
|
||||
---
|
||||
|
||||
## 3. 提交之后
|
||||
|
||||
提交成功:
|
||||
|
||||
- 写入表 4,接口 4 `actionCode=1`,`contractStatus=1`(起草),带合同号
|
||||
- 同一份合同下多个 `fid` 回同一套号和状态
|
||||
- **合同任务结束。不要重录合同。**
|
||||
|
||||
「合同成功」不是提交成功,而是返利中心能搜到这个合同号。查号挂在返利班,搜到再回 `contractStatus=2`;搜不到不重录,下一班再搜。
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码要从哪抄
|
||||
|
||||
| 要 | 文件 |
|
||||
| --- | --- |
|
||||
| 打开/登录/填表/提交/读号 | `rpa_contract/contract_flow_steps.py` |
|
||||
| 字段 name / 选择器 | 同文件里的 `F`、`X`;`selectors/contract_create_selectors.json` |
|
||||
| 浏览器适配 | `rpa_contract/apa_browser_adapter.py` |
|
||||
| 干跑样例 | `rpa_contract/examples/contract_create_input_dry_run.json` |
|
||||
|
||||
旧状态中心(`callback_client.py` / `agent.py` / start-complete-fail)已从仓库删除。不要再接。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
已被 2026-10-02 spec 替代(毛利 4%、两平台混排四级仓、按库存拆量)。本文保留 9 月 2 日原文,不要当作业口径。
|
||||
|
||||
选品条件优先级:
|
||||
1)自营仓:勾选的自营仓
|
||||
|
||||
2)价格:跟销售价格对比,需要有5%以上盈利的商品;
|
||||
|
||||
3)有效期:大于等于12个月
|
||||
|
||||
4)满足库存数量
|
||||
|
||||
前4个选品条件必须满足后,再从仓库距离中选择。
|
||||
|
||||
5)仓库距离:优先选择距离近的店
|
||||
1. [自营] 广东壹号药业有限公司
|
||||
2. [自营] 昆山亿方药业有限公司
|
||||
3. [自营] 湖北亿昊药业有限公司
|
||||
4. [自营] 天津壹号药业有限公司
|
||||
5. [自营] 重庆亿昊药业有限公司
|
||||
6. [自营] 重庆壹号医药有限公司
|
||||
7. [自营] 湖南壹号药业股份有限公司
|
||||
8. [自营] 辽宁亿昊药业有限公司
|
||||
9. [自营] 河南壹号医药有限公司
|
||||
10. [自营] 甘肃壹号云医药有限责任公司企业店
|
||||
11. [自营] 陕西壹号药业有限公司
|
||||
|
||||