21 KiB
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 靠下面这张地图定点打开,禁止用扫仓代替思考。改对应了,反工和消耗都会少。
怎么干活
- 先定落点(不开文件):哪个函数、成功长什么样(哪条 ACCEPT / 哪个点名用例)、这次不碰什么。方向说不清就先问,不要靠翻目录找感觉。
- 只在落点上想透:规则、边界、会不会拿错号/写错状态。打开文件是为了验证落点,不是再想要不要改别处。
- 薄验收就停:控制台
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 请求路径里录合同。
- 不要为「显得全面」多开文件、重跑、看图。落点外的改动会制造反工。