docs: add payment status and paid-amount semantics implementation plan

Break the confirmed spec into TDD tasks covering backend yjf-only paid
amounts, web/mobile label renames, and menu SQL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-12 17:50:54 +08:00
1 parent 06a30ff658
commit a1be31c694
2 files changed
+546 -1

No files matched your search

@@ -0,0 +1,545 @@
# 转账付款状态文案 + 期次已缴费仅计 yjf 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:** 转账笔状态统一为「已付款 / 付款确认中 / 付款完成」;期次「已缴费」仅计 `yjf`、欠缴费=应缴−已缴;菜单「缴费财务确认及开票」改为「缴费财务确认」;Web 与市场小程序口径一致。
**Architecture:** 金额口径以 `summarize_period` 为后端权威、`schedulePaidArrears` 为前端权威,二者均改为仅合计 `yjf`;文案以 `status-tag-registry.payStatus` 与小程序 `PAY_STATUS` 映射为源;菜单同步 `sqxp.ts` / `menu.py` / `sys_menu` 迁移。状态码与状态机不变。
**Tech Stack:** FastAPI/pytest、Vue 3 + Element Plus(web-admin)、mobile-oa-h5(Node assert 测试)、MySQL `sys_menu`
**Spec:** `docs/superpowers/specs/2026-10-12-payment-status-and-paid-amount-semantics-design.md`
---
### Task 1: 后端期次已缴费口径仅计 yjf
**Files:**
- Modify: `backend/app/services/sample_payment_period_summary.py`
- Modify: `backend/app/services/payment_chase.py`(文档字符串)
- Modify: `backend/tests/test_sample_payment_period_summary.py`
- Modify: `backend/tests/test_payment_chase.py`
- [ ] **Step 1: 改写失败用例期望(TDD)**
在 `test_sample_payment_period_summary.py`:
- `test_dfj_counts_as_display_paid` → 改名为 `test_dfj_does_not_count_as_display_paid`:期望 `displayPaidAmount == 0`,`arrearsAmount == 1000`
- `test_confirming_wins`:`displayPaidAmount` 从 `2000` 改为 `0`(`jfqrz` 不计);`financeAudit` 仍为 `jfqrz`
- 保留纯 `yjf` 用例期望不变
在 `test_payment_chase.py`:
```python
def test_chase_paid_only_counts_yjf(self):
"""已缴 = 仅 yjf;欠缴 = 应缴 − 已缴"""
paid, arrears = chase_period_money(
due_amount=Decimal("1000"),
transfers=[
{"transferAmount": Decimal("300"), "payStatus": "dfj", "transferTime": "2026-04-01 10:00:00"},
{"transferAmount": Decimal("200"), "payStatus": "jfqrz", "transferTime": "2026-04-02 10:00:00"},
{"transferAmount": Decimal("100"), "payStatus": "yjf", "transferTime": "2026-04-03 10:00:00"},
],
)
self.assertEqual(paid, Decimal("100"))
self.assertEqual(arrears, Decimal("900"))
def test_chase_dfj_only_still_has_arrears(self):
paid, arrears = chase_period_money(
due_amount=Decimal("500"),
transfers=[
{"transferAmount": Decimal("500"), "payStatus": "dfj", "transferTime": "2026-04-01 10:00:00"},
],
)
self.assertEqual(paid, Decimal("0"))
self.assertEqual(arrears, Decimal("500"))
self.assertTrue(
is_chase_candidate(
due_date=date(2026, 9, 1),
today=date(2026, 10, 9),
arrears=arrears,
)
)
```
删除或替换旧的 `test_chase_paid_includes_dfj_jfqrz_yjf`、`test_chase_full_display_paid_not_candidate`。
- [ ] **Step 2: 跑测试确认失败**
```bash
cd backend && source venv/bin/activate
pytest tests/test_sample_payment_period_summary.py tests/test_payment_chase.py -v
```
Expected: 与 `displayPaidAmount` / `paid` 相关断言 FAIL。
- [ ] **Step 3: 最小实现**
`sample_payment_period_summary.py`:
```python
_DISPLAY_PAID_STATUSES = ("yjf",)
```
更新模块 docstring / `summarize_period` 注释为「已缴费金额仅计付款完成(yjf)」。
`payment_chase.py` 中 `chase_period_money` docstring:
```python
"""已缴 = 仅 yjf;欠缴 = max(应缴 − 已缴, 0)。"""
```
- [ ] **Step 4: 跑测试确认通过**
```bash
pytest tests/test_sample_payment_period_summary.py tests/test_payment_chase.py -v
```
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add backend/app/services/sample_payment_period_summary.py \
backend/app/services/payment_chase.py \
backend/tests/test_sample_payment_period_summary.py \
backend/tests/test_payment_chase.py
git commit -m "$(cat <<'EOF'
fix(backend): count period paid amount as yjf only
Align displayPaidAmount/arrears with business rule: 已缴费 excludes
已付款 and 付款确认中 transfers.
EOF
)"
```
---
### Task 2: 市场催缴 API 测试对齐新口径
**Files:**
- Modify: `backend/tests/test_market_payment_api.py`
- (实现已在 Task 1;本任务只修断言与夹具)
- [ ] **Step 1: 改写 `test_chase_counts_dfj_jfqrz_and_yjf_and_excludes_fully_paid`**
`_payment_tree` 每笔转账金额固定 `20`。旧口径下 `due=60` + 三态各 20 → 已缴 60、欠缴 0;新口径仅 `yjf` 计 20 → 欠缴 40,两单都会进催缴。
改为:
```python
def test_chase_counts_only_yjf_and_excludes_fully_confirmed(self):
from app.services.market_payment import list_payment_chase
db = _session()
_payment_tree(
db,
'part-paid',
due_amount=100,
transfer_statuses=('dfj', 'jfqrz', 'yjf'),
)
_payment_tree(
db,
'fully-paid',
due_amount=60,
transfer_statuses=('yjf', 'yjf', 'yjf'), # 3×20 = 60,全部付款完成
)
items, total = list_payment_chase(db, ctx=_ctx(), user_info=_user())
assert total == 1
assert items[0]['scheduleId'] == 'sch-part-paid'
assert items[0]['paidAmount'] == '20.00'
assert items[0]['arrearsAmount'] == '80.00'
```
若其它用例依赖「纯 `dfj` 视为已缴清而不进催缴」,同步改为欠缴>0 期望或改用 `yjf` 填满应缴。
- [ ] **Step 2: 跑测试**
```bash
cd backend && source venv/bin/activate
pytest tests/test_market_payment_api.py -k chase -v
```
Expected: PASS
- [ ] **Step 3: Commit**
```bash
git add backend/tests/test_market_payment_api.py
git commit -m "$(cat <<'EOF'
test(backend): align market chase assertions with yjf-only paid
EOF
)"
```
---
### Task 3: Web 前端期次已缴口径
**Files:**
- Modify: `web-admin/src/utils/protocol-sample-payment.ts`
- Modify: `web-admin/src/utils/protocol-sample-payment.test.ts`
- [ ] **Step 1: 改失败测试**
```ts
describe('schedulePaidArrears', () => {
it('display paid includes only yjf', () => {
const { paid, arrears } = schedulePaidArrears(25880, [
{ payStatus: 'dfj', transferAmount: 480 },
{ payStatus: 'dfj', transferAmount: 6000 },
{ payStatus: 'jfqrz', transferAmount: 100 },
{ payStatus: 'yjf', transferAmount: 200 },
{ payStatus: 'ytf', transferAmount: 50 }
])
assert.equal(paid, 200)
assert.equal(arrears, 25680)
})
})
```
- [ ] **Step 2: 跑测试确认失败**
```bash
cd web-admin && node --test src/utils/protocol-sample-payment.test.ts
```
- [ ] **Step 3: 实现**
将 `DISPLAY_PAID_STATUSES` 改为仅 `yjf`,或让 `schedulePaidArrears` 直接委托:
```ts
export function schedulePaidArrears(
dueAmount: number,
transfers: TransferLike[]
): { paid: number; arrears: number } {
const { paid, arrears } = scheduleFinanceConfirmedPaidArrears(dueAmount, transfers)
return { paid, arrears: arrears > 0 ? arrears : 0 }
}
```
注意:`scheduleFinanceConfirmedPaidArrears` 当前欠缴可为负(超缴);展示口径需 `max(0)`。若委托,保持与后端一致的非负欠缴。
更新注释:「已缴 = 仅 yjf」。
- [ ] **Step 4: 跑测试确认通过 → Commit**
```bash
git add web-admin/src/utils/protocol-sample-payment.ts \
web-admin/src/utils/protocol-sample-payment.test.ts
git commit -m "$(cat <<'EOF'
fix(web-admin): align schedule paid/arrears with yjf-only rule
EOF
)"
```
---
### Task 4: Web 状态文案注册表 + 硬编码
**Files:**
- Modify: `web-admin/src/utils/status-tag-registry.ts`
- Modify: `web-admin/src/utils/protocol-sample-payment.ts`(错误文案)
- Modify: `web-admin/src/utils/protocol-pay-mode-switch.ts`
- Modify: `web-admin/src/utils/protocol-fq-params-change.ts`
- Modify: `web-admin/src/components/business/payment-receipt-confirm-dialog/index.vue`
- Modify: `web-admin/src/views/cell-storage/payment-confirm/index.vue`
- Modify: `web-admin/src/views/cell-storage/payment-confirm/RefundConfirmTab.vue`
- Modify: `web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue`
- Modify: `web-admin/src/views/cell-storage/protocol-audit/PaymentInfoTab.vue`(文件头注释)
- Modify: `web-admin/src/views/cell-storage/payment-info/PaymentBySampleTab.vue`
- Modify: `web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue`
- Modify: `web-admin/src/views/pilot-service/clinical-payment-confirm/RefundConfirmTab.vue`
- Modify: `web-admin/src/views/pilot-service/pilot-application/ClinicalPaymentTab.vue`
- (扫尾)其它 grep 命中的硬编码「已缴费」「缴费确认中」「缴费成功」若属转账态
**不要改:** 列名「已缴费金额 / 已缴费时间 / 应缴费 / 欠缴费」。
- [ ] **Step 1: 更新注册表**
```ts
export const payStatus: StatusRegistry = {
dfj: { label: '已付款', tagType: 'primary' },
jfqrz: { label: '付款确认中', tagType: 'warning' },
yjf: { label: '付款完成', tagType: 'success' },
tfqrz: { label: '退费确认中', tagType: 'warning' },
ytf: { label: '已退费', tagType: 'danger' }
}
/** 按样本列表整体进度(勿与转账笔 payStatus 混用:payStatus.dfj=已付款) */
export const samplePaymentProgress: StatusRegistry = {
dfj: { label: '待缴费', tagType: 'info' },
jfqrz: { label: '付款确认中', tagType: 'warning' },
bfyj: { label: '部分已缴', tagType: 'primary' },
yjq: { label: '已缴清', tagType: 'success' }
}
```
`periodFinanceAudit` 保持短文案「确认中 / 已通过」(spec §4.3)。
- [ ] **Step 2: 替换硬编码用户可见文案(对照表)**
| 旧 | 新 |
|----|----|
| 缴费确认中(转账态) | 付款确认中 |
| 已缴费(指 `dfj` 或退回后草稿) | 已付款 |
| 缴费成功 | 付款完成 |
| 提交后状态变为「缴费确认中」 | 提交后状态变为「付款确认中」 |
| 退回后状态将变为「已缴费」 | 退回后状态将变为「已付款」 |
| 退回后状态将变为「缴费成功」 | 退回后状态将变为「付款完成」 |
| 存在缴费确认中的转账… | 存在付款确认中的转账… |
`ProtocolViewDialog` 时间线 `subLabel: '缴费成功'` → `'付款完成'`;`subLabel: '已缴费'`(若指转账态)→ `'已付款'`。
`PaymentBySampleTab` 筛选项 `label: '缴费确认中'` → `'付款确认中'`。
- [ ] **Step 3: 全库扫尾**
```bash
rg -n '缴费确认中|缴费成功|"已缴费"' web-admin/src --glob '*.{vue,ts}'
```
仅改转账状态语义;保留期次金额列名。
- [ ] **Step 4: Commit**
```bash
git add web-admin/src/utils/status-tag-registry.ts \
web-admin/src/utils/protocol-sample-payment.ts \
web-admin/src/utils/protocol-pay-mode-switch.ts \
web-admin/src/utils/protocol-fq-params-change.ts \
web-admin/src/components/business/payment-receipt-confirm-dialog/index.vue \
web-admin/src/views/cell-storage \
web-admin/src/views/pilot-service
git commit -m "$(cat <<'EOF'
feat(web-admin): rename transfer pay statuses to 付款 wording
Use 已付款/付款确认中/付款完成 for payStatus; keep period money
labels 应缴费/已缴费/欠缴费 unchanged.
EOF
)"
```
---
### Task 5: 菜单改名(前端 + 后端 + SQL)
**Files:**
- Modify: `web-admin/src/router/modules/sqxp.ts`
- Modify: `backend/app/api/menu.py`
- Modify: `web-admin/src/views/cell-storage/payment-confirm/index.vue`(文件头注释)
- Create: `docs/sql/migrations/2026-10-12-rename-payment-confirm-menu.sql`
- [ ] **Step 1: 改固定菜单文案**
两处 `title` / `meta.title`:`缴费财务确认及开票` → `缴费财务确认`。
`payment-confirm/index.vue` 首行注释同步。
- [ ] **Step 2: 写迁移 SQL**
```sql
-- =============================================================================
-- 菜单改名:缴费财务确认及开票 → 缴费财务确认
-- id: 50000000000000000000000000000004
-- perms: biz:cellStoragePaymentConfirm:list
-- =============================================================================
UPDATE sys_menu
SET menu_name = '缴费财务确认',
update_time = NOW()
WHERE del_flag = 0
AND (
id = '50000000000000000000000000000004'
OR perms = 'biz:cellStoragePaymentConfirm:list'
)
AND menu_name <> '缴费财务确认';
```
- [ ] **Step 3: Commit**
```bash
git add web-admin/src/router/modules/sqxp.ts \
backend/app/api/menu.py \
web-admin/src/views/cell-storage/payment-confirm/index.vue \
docs/sql/migrations/2026-10-12-rename-payment-confirm-menu.sql
git commit -m "$(cat <<'EOF'
chore: rename payment-confirm menu to 缴费财务确认
EOF
)"
```
---
### Task 6: 后端用户可见错误文案 + 旁路映射
**Files:**
- Modify: `backend/app/crud/protocol_sample_payment.py`
- Modify: `backend/app/crud/collection_protocol.py`
- Modify: `backend/app/crud/presign_order.py`
- Modify: `backend/app/crud/clinical_payment.py`
- Modify: `backend/app/services/market_payment.py`
- Modify: `backend/app/services/protocol_transfer_status.py`(docstring)
- Modify: `backend/app/services/protocol_pay_mode_switch.py`
- Modify: `backend/app/services/qc_work_order_fee_summary.py`
- Modify: 相关 pytest 中 `match=` / 文案断言(按失败再改)
- [ ] **Step 1: 统一替换用户可见 ValueError(对照)**
| 旧(校验态) | 新 |
|--------------|-----|
| 仅已缴费状态可编辑…(实际 `dfj`) | 仅已付款状态可编辑… |
| 仅已缴费状态可提交财务确认 | 仅已付款状态可提交财务确认 |
| 仅缴费确认中可撤回 | 仅付款确认中可撤回 |
| 仅缴费确认中状态可确认收款 / 可退回 | 仅付款确认中状态可… |
| 存在缴费确认中或退费处理中… | 存在付款确认中或退费处理中… |
| 仅已缴费状态可发起退费(临床,实际 `yjf`) | 仅付款完成状态可发起退费 |
`qc_work_order_fee_summary.py`:
```python
_PAY_STATUS_LABELS = {
"dfj": "已付款",
"jfqrz": "付款确认中",
"yjf": "付款完成",
"tfqrz": "退费确认中",
"ytf": "已退费",
}
```
- [ ] **Step 2: 跑相关测试并修 match 字符串**
```bash
cd backend && source venv/bin/activate
pytest tests/test_protocol_sample_payment.py tests/test_presign_order.py \
tests/test_market_payment_api.py tests/test_market_presign_api.py -q
```
- [ ] **Step 3: Commit**
```bash
git add backend/app/crud backend/app/services backend/tests
git commit -m "$(cat <<'EOF'
fix(backend): align pay-status user messages with 付款 wording
EOF
)"
```
---
### Task 7: 市场小程序状态文案
**Files:**
- Modify: `mobile-oa-h5/src/market/modules/payment.js`
- Modify: `mobile-oa-h5/src/market/modules/presign.js`
- Modify: `mobile-oa-h5/src/market/components/PresignTransferCard.vue`
- Modify: `mobile-oa-h5/src/market/module-contracts.test.js`
- Modify: 其它断言「已确认」「待缴费」作为**转账** `payStatus` 的测试
- [ ] **Step 1: 更新映射**
`payment.js`:
```js
const PAYMENT_PROGRESS = Object.freeze({
dfj: '待缴费', jfqrz: '付款确认中', bfyj: '部分已缴', yjq: '已缴清',
待缴费: '待缴费', 部分缴费: '部分缴费', 已缴清: '已缴清'
})
const PAY_STATUS = Object.freeze({
dfj: '已付款', jfqrz: '付款确认中', yjf: '付款完成', tfqrz: '退费确认中', ytf: '已退费'
})
```
`transferCard` 中 `statusTone`:`statusText === '已确认'` 改为 `statusText === '付款完成'`。
`presign.js` / `PresignTransferCard.vue`:`dfj→已付款`,`jfqrz→付款确认中`,`yjf→付款完成`(默认回退文案勿再用「待缴费」表示 `dfj`)。
- [ ] **Step 2: 更新测试期望**
例:`module-contracts.test.js` 中 `statusLabel === '已确认'` → `'付款完成'`。
- [ ] **Step 3: 跑测试**
```bash
cd mobile-oa-h5
node --test src/market/module-contracts.test.js \
src/market/mobile-parity-ui.test.js \
src/market/presign/*.test.js
```
(以仓库惯用命令为准。)
- [ ] **Step 4: Commit**
```bash
git add mobile-oa-h5/src/market
git commit -m "$(cat <<'EOF'
feat(mobile-oa-h5): align transfer payStatus labels with 付款 wording
EOF
)"
```
---
### Task 8: 回归与图谱
- [ ] **Step 1: 后端回归**
```bash
cd backend && source venv/bin/activate
pytest tests/test_sample_payment_period_summary.py \
tests/test_payment_chase.py \
tests/test_sample_payment_progress.py \
tests/test_market_payment_api.py \
tests/test_protocol_sample_payment.py -q
```
Expected: PASS
- [ ] **Step 2: 前端 / 小程序相关单测**
```bash
cd web-admin && node --test src/utils/protocol-sample-payment.test.ts
cd ../mobile-oa-h5 && node --test src/market/module-contracts.test.js src/market/mobile-parity-ui.test.js
```
- [ ] **Step 3: 更新 graphify(若有函数/文案映射签名变更)**
```bash
./scripts/update-graph.sh backend
./scripts/update-graph.sh frontend
```
仅当本次改动触及图谱关注的符号时提交 `graphify-out`;纯文案可跳过。
- [ ] **Step 4: 将 spec 状态改为已确认(若仍为待审阅)**
`docs/superpowers/specs/2026-10-12-payment-status-and-paid-amount-semantics-design.md` 头部:`待用户审阅` → `已确认`。
- [ ] **Step 5: 最终 commit(若有残留)**
```bash
git add docs/superpowers/specs/2026-10-12-payment-status-and-paid-amount-semantics-design.md
git commit -m "$(cat <<'EOF'
docs: mark payment status semantics spec confirmed
EOF
)"
```
---
## Spec 覆盖对照
| Spec 要求 | Task |
|-----------|------|
| 期次已缴费仅 `yjf`、欠缴公式 | Task 1–3 |
| 催缴级联 | Task 1–2 |
| 转账文案 已付款/付款确认中/付款完成 | Task 4、6、7 |
| samplePaymentProgress.jfqrz → 付款确认中 | Task 4、7 |
| 菜单改名 + SQL | Task 5 |
| 临床共用注册表 | Task 4 |
| 小程序对齐 | Task 7 |
| 不改码值/状态机/期次列名 | 全任务约束 |
| 验收回归 | Task 8 |
@@ -1,6 +1,6 @@
# 转账「付款」状态文案 + 期次「已缴费」仅计付款完成
> 状态:待用户审阅
> 状态:已确认
> 日期:2026-10-12
> 界面模式:细胞存储缴费相关列表/表单(P02 / P13);市场小程序缴费/催缴卡片
> 关联: