dev2 · 2026-09-06 · worktree
feature/backfill-shiyisheng-offline(自origin/test)
本次全部在云机本地假数据库真跑验证,未碰生产写操作;生产部分只做了sudo db-prod只读 SELECT 印证。
与 8-21 那次 4 家门店批量补录(backfill_legacy_offline_stores.py)不同:这次是单机构、UPDATE 现有草稿档案,不 INSERT 新档案。
| 文件 | 路径 | sha256 |
|---|---|---|
| 补录脚本 | settlement/backend/scripts/backfill_shiyisheng_offline_store.py(worktree 内) |
275822f220fb4e0b0a2ea1fe51d535d3ba9cfced9b407ee9696ab9dcc7b10d75 |
| 付款凭证 | /home/dev2/inbox-vouchers/凭证归档/视宜生眼科-付款凭证-2380-20260902.jpg |
4eae4f002503f9f1b0e47cd4212e5f44eb86f3d2c4a09758cc3b8b8113d7578c |
⚠️ 交付包内容一变,这两个 sha 必须同步改并重发(8-21 那次踩过的坑:脚本改了、说明书里的 sha 没同步改,双重校验形同虚设)。
脚本行数 444 行;已跑 python3 -m py_compile 通过语法检查。
org_signup_profiles.org_id=10000011(待提交资料 / core_org_id 空),脚本原地把它改成 已开通,绝不再插一行新档案。TARGET_ORG_ID / CORE_ORG_ID / 两个渠道 UUID / 金额 / 日期 全在文件顶部)。service_end_at 是长官给的显式日期 2027-09-03 08:13 Asia/Shanghai(对齐核心侧席位到期),不是 paid_at + timedelta 算出来的。SERVICE_DAYS=366 只是文档/展示常量,不参与该字段计算。_money / _paid_at / _next_no / _split_settlement_rate)直接 from scripts.backfill_legacy_offline_stores import ...,不复制一份逻辑;_upload_voucher 因为 org_id 语义不同(这里用已存在的 10000011,不分配新的结算签约编号)单独重写,key 规则原样照抄(store-payment-vouchers/{org_id}/{uuid}-{文件名})。payment_orders(微信小程序在线支付待支付单):该机构从未走过、也不会走微信支付链路,脚本不 import 也不查询这张表。sudo db-prod 逐条 SELECT,原样贴)$ sudo db-prod "SELECT org_id, channel_id, status, core_org_id, license_company_name, store_display_name, address, contact_name, contact_phone FROM org_signup_profiles WHERE org_id = 10000011"
org_id | channel_id | status | core_org_id | license_company_name | store_display_name | address | contact_name | contact_phone
----------+--------------------------------------+------------+-------------+----------------------+--------------------+---------+--------------+---------------
10000011 | e02b3643-0473-49ff-9899-46d44e919d6d | 待提交资料 | | | 视宜生眼科 | | 张恒 | 13330961935
(1 row)
$ sudo db-prod "SELECT id, name, is_active FROM organizations WHERE id = 22"
id | name | is_active
----+----------------------------+-----------
22 | 四川视宜生眼科诊所有限公司 | t
(1 row)
$ sudo db-prod "SELECT id, name, rate_percent, channel_level, parent_channel_id, settlement_mode, status FROM channels WHERE id::text LIKE 'e52ac807%' OR id = 'e02b3643-0473-49ff-9899-46d44e919d6d'"
id | name | rate_percent | channel_level | parent_channel_id | settlement_mode | status
--------------------------------------+----------------------------+--------------+---------------+--------------------------------------+-----------------+--------
e02b3643-0473-49ff-9899-46d44e919d6d | 京东方健康-姜怀 | 30.00 | 2 | e52ac807-6436-4f6c-aff1-eb2d3261ca24 | parent_offline | active
e52ac807-6436-4f6c-aff1-eb2d3261ca24 | 北京京东方健康科技有限公司 | 50.00 | 1 | | platform_direct | active
(2 rows)
$ sudo db-prod "SELECT count(*) FROM store_service_orders WHERE org_id = 10000011"
count
-------
0
(1 row)
$ sudo db-prod "SELECT count(*) FROM payment_vouchers WHERE org_id = 10000011"
count
-------
0
(1 row)
$ sudo db-prod "SELECT org_id, status, core_org_id FROM org_signup_profiles WHERE core_org_id = 22"
org_id | status | core_org_id
--------+--------+-------------
(0 rows)
$ sudo db-prod "SELECT order_no FROM store_service_orders WHERE order_no LIKE 'SVC20260902%' ORDER BY order_no"
(0 rows)
$ sudo db-prod "SELECT payment_no FROM payment_vouchers WHERE payment_no LIKE 'PAY20260902%' ORDER BY payment_no"
(0 rows)
结论:档案行现状、机构、渠道费率、单号空位(生产上 SVC20260902001/PAY20260902001 都是空位,跟脚本里 dry-run 预告一致)、幂等前置条件(无重复 core_org_id=22)全部对得上脚本里写死的常量,未发现需要改脚本的偏差。
doctor_ai_brain@localhost:5432,全测试数据)按上面第三节的生产 SELECT 结果,在本地库按同一批 id 插入最小假数据:channels 两条(一级 rate 50 / 二级 rate 30,父子关系一致)、organizations id=22、org_signup_profiles org_id=10000011 草稿行;凭证文件复制到 /tmp/backfill-test-vouchers/。
$ BACKFILL_VOUCHER_DIR=/tmp/backfill-test-vouchers python3 -m scripts.backfill_shiyisheng_offline_store --dry-run
【dry-run · 不写库】 视宜生眼科(org_id=10000011):
档案更新(org_signup_profiles · UPDATE org_id=10000011)
core_org_id = 22
...
service_end_at = 2027-09-03 08:13:00+08:00
订单(store_service_orders · INSERT)
order_no = SVC20260902113 ← 本地库有 112 条无关历史测试数据占用该前缀,脚本自动避让到 113(生产是空位,会落 001)
...
凭证(payment_vouchers · INSERT)
payment_no = PAY20260902001
...
收益快照(revenue_snapshots · INSERT)
结算渠道(一级) 北京京东方健康科技有限公司(20.00%) = 476.00
来源渠道(二级) 京东方健康-姜怀(30.00%) = 714.00
period_key = 2026-09-H1
(dry-run 已回滚,库里没有任何改动)
⚠️ order_no 落在 113 是因为本地测试库恰好有 112 条无关的历史测试数据占用了 SVC20260902 前缀(第三节生产 SELECT 已确认生产该前缀是空位)——这正好实测验证了 _next_no 的避让逻辑真的在工作,不是巧合对上、也不是 bug。
$ BACKFILL_VOUCHER_DIR=/tmp/backfill-test-vouchers python3 -m scripts.backfill_shiyisheng_offline_store --commit
【真写入】 视宜生眼科(org_id=10000011):
...(同上)
✅ 已提交
commit 后 SQL 核对五张表(原样贴):
--- org_signup_profiles ---
{'org_id': 10000011, 'core_org_id': 22, 'license_company_name': '四川视宜生眼科诊所有限公司',
'store_display_name': '视宜生眼科', 'status': '已开通',
'submitted_at': 2026-09-02 04:00 UTC(=12:00 上海), 'reviewed_at': 同上, 'activated_at': 同上,
'activated_seat_count': 1, 'service_start_at': 同上,
'service_end_at': 2027-09-03 00:13 UTC(=08:13 上海)}
--- store_service_orders ---
{'order_no': 'SVC20260902113', 'org_id': 10000011, 'source_channel_id': e02b3643...,
'order_type': '首次开通', 'seat_count': 1, 'service_days': 366, 'amount_due': 2380.00,
'payment_method': '线下转账', 'status': '已完成', 'contract_status': '待补合同',
'created_terminal': 'backfill'}
--- payment_vouchers ---
{'payment_no': 'PAY20260902001', 'org_id': 10000011, 'payer_name': '四川视宜生眼科诊所有限公司',
'submitted_amount': 2380.00, 'purchase_seat_count': 1, 'status': '已确认到账',
'service_order_id': <指向上面订单id>,
'voucher_file_key': 'store-payment-vouchers/10000011/<uuid>-视宜生眼科-付款凭证-2380-20260902.jpg'}
--- payment_confirmations ---
{'voucher_id': <指向上面凭证id>, 'org_id': 10000011, 'confirmed_amount': 2380.00,
'confirmed_by': None, 'result': 'confirmed'}
--- revenue_snapshots ---
{'payment_confirmation_id': <指向上面确认id>, 'channel_id': e52ac807...(一级),
'source_channel_id': e02b3643...(二级), 'org_id': 10000011, 'received_amount': 2380.00,
'rate_percent_snapshot': 20.00, 'source_rate_percent_snapshot': 30.00,
'source_channel_amount': 714.00, 'channel_amount': 476.00,
'period_month': '2026-09', 'period_key': '2026-09-H1', 'settlement_status': 'in_statement',
'service_order_id': <指向上面订单id>}
linkage ok: True True True True ← service_order_id / voucher_id / payment_confirmation_id 全部正确串联
物理落盘凭证文件也确认存在:
settlement/backend/data/archive/store-payment-vouchers/10000011/<uuid>-视宜生眼科-付款凭证-2380-20260902.jpg
$ BACKFILL_VOUCHER_DIR=/tmp/backfill-test-vouchers python3 -m scripts.backfill_shiyisheng_offline_store --commit
⏭ 已补过,跳过:org_id=10000011
EXIT=0
channels 0
organizations 0
org_signup_profiles 0
store_service_orders 0
payment_vouchers 0
payment_confirmations 0
revenue_snapshots 0
(同时删除了本地落盘的凭证文件、临时种子脚本、临时凭证目录,本地库/磁盘均已复原为改动前状态。)
tests/unit/test_backfill_shiyisheng.py,no-mock 真连本地库)$ python3 -m pytest tests/unit/test_backfill_shiyisheng.py -v
tests/unit/test_backfill_shiyisheng.py::test_split_settlement_rate_30_50_gives_20_percent_diff PASSED
tests/unit/test_backfill_shiyisheng.py::test_paid_at_is_noon_shanghai_and_service_end_is_explicit_shanghai_time PASSED
tests/unit/test_backfill_shiyisheng.py::test_precheck_rejects_when_core_org_id_already_set PASSED
tests/unit/test_backfill_shiyisheng.py::test_dry_run_does_not_write_any_row PASSED
============================== 4 passed in 0.95s ===============================
四条用例:①分成算式 30/50→差额 20%(714/476)②paid_at 中午上海时区 + service_end_at 显式上海时间点 ③precheck 拒绝 core_org_id 已非空 ④dry-run 真跑本地库确认 5 张表 0 行。每条用例自带 setup/teardown,不留残留。
sudo backfill-prod 通道可直接用8-21 那条 sudo backfill-prod {status|dry-run|commit|cleanup} 通道是 sha256 锁死了 backfill_legacy_offline_stores.py(4 家批量脚本) 的专用通道(/usr/local/bin/backfill-prod 就是个 SSH 转发,具体校验逻辑在生产主机那一端,我这边看不到、也不该猜)。
这次是新脚本,我判断不出它是否已经/能够被那条通道识别 —— 这件事只能 root 判断,两条路选一条:
sudo backfill-prod status/dry-run/commit;无论走哪条,下面的暂存 / 指纹 / dry-run / 批准闸 / commit 步骤都适用:
# 1. 脚本 + 凭证放到生产可访问位置后,先双重校验 sha256(本地已算好,见第一节)
sha256sum settlement/backend/scripts/backfill_shiyisheng_offline_store.py
# 期望: 275822f220fb4e0b0a2ea1fe51d535d3ba9cfced9b407ee9696ab9dcc7b10d75
sha256sum "视宜生眼科-付款凭证-2380-20260902.jpg"
# 期望: 4eae4f002503f9f1b0e47cd4212e5f44eb86f3d2c4a09758cc3b8b8113d7578c
# 任一不符 → 立即中止,不要往下跑
DATABASE_URL=<生产真实DSN> python3 -m scripts.backfill_shiyisheng_offline_store --dry-run
期望输出(跟第四节本地跑出来的结构完全一致,唯二数字不同):
- order_no = SVC20260902001(生产该前缀是空位,见第三节 SELECT)
- payment_no = PAY20260902001
- 分成 714.00(二级 · 京东方健康-姜怀)/ 476.00(一级 · 北京京东方健康科技有限公司)
- period_key = 2026-09-H1
把这份 dry-run 输出发给长官过目,长官点头才能进下一步。
DATABASE_URL=<生产真实DSN> python3 -m scripts.backfill_shiyisheng_offline_store --commit
# 若长官指定了具体确认操作人(users.id),加:--confirmed-by <该id>
commit 后照第四节 4.3 的 SQL 模板逐张表核对(换成 sudo db-prod 跑,不是本地库了)。
--confirmed-by 参数说明payment_confirmations.confirmed_by 默认 NULL(线下补录没有真实操作人,不伪造成某个管理员,与 8-21 那次先例一致)。如果长官要求落一个具体人(例如 ljg_admin),root 先 sudo db-prod "SELECT id FROM users WHERE username='ljg_admin'" 查到 id,再在 commit 命令后加 --confirmed-by <id>。
-- 执行前务必先用同样 WHERE 跑一遍 SELECT,确认只命中视宜生眼科这一笔
-- ① 收益快照:确认还没被结算单领走(settlement_statement_id IS NULL)才能删
SELECT id, settlement_statement_id FROM revenue_snapshots WHERE org_id = 10000011;
-- 若 settlement_statement_id 非空 → 停手,联系我人工处理,不要贸然删
DELETE FROM revenue_snapshots WHERE org_id = 10000011 AND settlement_statement_id IS NULL;
-- ② 凭证:驳回、不删除(留痕)
UPDATE payment_vouchers
SET status = '已驳回', reject_reason = '补录误操作回滚', rejected_at = now()
WHERE org_id = 10000011;
-- ③ 订单:取消、不删除(留痕)
UPDATE store_service_orders
SET status = '已取消'
WHERE org_id = 10000011 AND created_terminal = 'backfill';
-- ④ 档案:回退到补录前的草稿态
UPDATE org_signup_profiles
SET core_org_id = NULL,
status = '待提交资料',
submitted_at = NULL,
reviewed_at = NULL,
activated_at = NULL,
activated_seat_count = NULL,
service_start_at = NULL,
service_end_at = NULL
WHERE org_id = 10000011;
🔴 payment_confirmations 不可逆,本回滚脚本不碰它:该表语义是"这笔钱确认到账了"这一事实本身,不因业务态回退而删除或修改。如果最终判定这笔钱确实没收到,怎么处理这条历史记录必须长官单独拍板,不在本脚本范围内(与项目既有纪律"批B/C/D 硬事实:凭证链退役只退一半/confirmation 不可逆"一致)。
payment_orders(微信小程序在线支付待支付/已支付单):全程不 import、不查询。该机构走的是线下转账,从未也不会经过微信支付链路。core_org_id 写进档案,绝不触发新建机构。s2-rework 分支:本次改动全部在独立 worktree feature/backfill-shiyisheng-offline(自 origin/test 拉出),未碰其他分支。sudo backfill-prod 通道要不要接入这个新脚本——见第六节 6.1,这个决定权在 root,我这边看不到远端脚本内容,不敢替root下结论。--confirmed-by 要不要填——默认 NULL,长官若要落具体操作人需额外给一个 users.id。worktree feature/backfill-shiyisheng-offline(自 origin/test)内已提交(未 push):
chore(settlement): 视宜生眼科线下补录脚本(单机构·UPDATE 现有档案)· 长官 9-06 口径
包含:scripts/backfill_shiyisheng_offline_store.py、tests/unit/__init__.py、tests/unit/test_backfill_shiyisheng.py。
云机环境 · 第三节生产数据为 sudo db-prod 只读实查;第四/五节为本地假数据 e2e 真打,非生产验收证据。