117 lines
7.7 KiB
Markdown
117 lines
7.7 KiB
Markdown
# PROGRESS_AGI_ITERATION_6 · P0 收口 + 财务闭环 + PG 并发复跑
|
||
|
||
> 实跑基线(2026-09-12,`pytest` 全量口径):
|
||
> - SQLite:**509 passed / 4 skipped**(4 skipped 均为 `tests/test_concurrency.py` 的 PG 专用用例)
|
||
> - PostgreSQL(127.0.0.1:5433,`config.settings.pgtest`):**499 passed / 0 skipped**(含 `-m postgres` 4 并发用例全绿)
|
||
> - 前端:`npm run build` ✅ 6.32s;`npm test` ✅ 11/11
|
||
> `manage.py check` 0 issues;`makemigrations --check` No changes。
|
||
|
||
## 本轮做了什么
|
||
|
||
P0-1(交接计划 `NEXT_PLAN.md` §P0-1)收口。接管时工作区已有未提交的半成品
|
||
(permissions 超管放行 + 未知租户 400 分层、viewset 未知租户显式 400、
|
||
seed_demo/demo 幂等兜底、两个测试侧 membership 修补、exceptions 去 `exc_type`
|
||
+ 补 `_with_error_code`),本轮在其上完成最后闭环:
|
||
|
||
1. `tests/test_membership.py`(新建,8 用例):无 membership→403+code、
|
||
跨租户→403、inactive membership→403、未知租户→400、超管放行、
|
||
API Key 分支(单元级)+ 集成接线现实、demo token 只读可读、
|
||
`seed_initial_data` membership 幂等。
|
||
2. `tests/test_exception_handler.py::test_handler_returns_structured_500_for_unknown`:
|
||
期望从 `exc_type == "RuntimeError"` 改为 `"exc_type" not in resp.data`,
|
||
对齐 P0-4 已落地的去泄露修复(实现先于测试更新)。
|
||
|
||
## 语义决策(理由)
|
||
|
||
- **未知租户 → 400 而非 403**:参数错误与授权失败分离,未知 code 走视图层
|
||
`ValidationError({"tenant": "无法识别租户"})`,不泄露租户存在性判断给授权层。
|
||
- **超管直通**:与 `seed_initial_data` 的 bootstrap 契约一致
|
||
("existing superusers are operators for every seeded tenant")。
|
||
- **demo 幂等兜底**:demo token 流 bypass 正常登录路径,`enter_demo` 内
|
||
`get_or_create` 保证手工建的 demo 租户也能进。
|
||
- **API Key 分支的接线现实**(实测,非假设):
|
||
`TenantMembershipPermission` 的 APIKey 分支目前没有视图同时接线——
|
||
`BaseTenantViewSet` 系用 JWT/Session,`Open*` 系用 `require_scope`。
|
||
所以用例 6 按"单元级分支验证 + 集成级现实记录"写,不伪造 403。
|
||
若后续要把 APIKey 接进租户系视图,再把该用例升级为端到端。
|
||
|
||
## 踩坑
|
||
|
||
- `test_demo.py`、`test_printing.py` 的 membership 修补在接管时已在工作区,
|
||
本轮只验证了它们变绿,没有重写。
|
||
- `pytest -q` 不打印 passed 汇总行(只有 dots + warnings + FAILED/SKIPPED),
|
||
用例数用 `--collect-only | grep -c "::"` 拿:全量 490(tests/ 476 + core 14)。
|
||
- 交接计划写的"482 全量 / 477 passed"是旧基线数字,本轮实测已是 490,
|
||
差异来自期间新增用例;文档数字以本轮实跑为准。
|
||
|
||
## 本轮新增(2026-09-12 续)
|
||
|
||
1. **P0 级阻塞修复**:`BaseTenantViewSet.acreate()` 在 async 上下文同步跑
|
||
`serializer.is_valid()`,凡带 FK 的创建(收款/付款单等)全 500
|
||
`SynchronousOnlyOperation`。修法:包进 `sync_to_async`(与 printing 已有写法对齐)。
|
||
验证:收款单 201 / 付款单 201 / 重复编码 400。
|
||
落点:`backend/apps/core/viewset.py`(+3 行注释)。
|
||
2. **前端财务入口闭环**(`NEXT_PLAN.md` §附录 A #1#2 + f7eb4325"补前端财务入口与错误状态"):
|
||
- `Receivables.vue`:"+登记收款"按钮 → 收款单创建 dialog(客户下拉/单号留空自生成/日期/金额>0/方式/备注)
|
||
+ 每行"核销"按钮 → 收款单选择 + 金额 dialog(默认未结余额,超额后端 400 透出)。
|
||
- `Payables.vue`:镜像实现(登记付款 + 核销应付)。
|
||
- 后端字段级错误(customer/bill_no/amount/allocations/detail)本地透出 `ElMessage.error`,
|
||
全局拦截器兜底其余状态。
|
||
- 验证:`npm run build` ✅ 6.32s;后端 finance 22 用例绿;全量 509 绿无带崩。
|
||
3. **P1-1 PG 并发复跑**:`python scripts/setup_pg.py` 连通 127.0.0.1:5433;
|
||
`-m postgres` 4/4 绿;PG 全量 499 绿。
|
||
4. **日志清理**:删 `backend/g8060/g8070/g8080/g8090.log`(P0-4 现场 g8070 已无保留价值,
|
||
`*.log` 已在 `.gitignore`)。
|
||
|
||
## 本轮新增(2026-09-12 续-2 · 诚实性修复 + P0-3 真 bug)
|
||
|
||
1. **P0-3 真 bug**:`frontend/src/router/index.js` 的 `PUBLIC_ROUTES`
|
||
只放行 `login/storefront`,匿名进 `/register` 被守卫踢回 `/login`——
|
||
注册页存在但不可达,闭环是断的。修法:`PUBLIC_ROUTES` 加 `register`。
|
||
Playwright 真机(dev :18099 + 新 build):
|
||
空表单提交被拦(4 条必填标红)/ 非法值被拦(手机号格式/密码至少8位/
|
||
两次密码不一致)/ 合法值 201 并跳 `#/dashboard` / 0 console errors。
|
||
2. **P0-5 缺头**:`check_apikey_rate_limit` 抛 `Throttled` 未传 `wait`,
|
||
DRF 无 `wait` 不回 `Retry-After`(`views.py::exception_handler` 实测)。
|
||
修法:`wait=APIKEY_WINDOW_SECONDS` + 回归断言 `Retry-After in 429`。
|
||
3. **P1-3 定时接线**:`apps/core/tasks.py`(新建,`purge_expired_audit_logs`
|
||
+ dramatiq actor)+ `config/dramatiq.py` 每日 04:00 cron +
|
||
`test_purge_actor_registered_and_runs`。此前 command 存在但无人调度。
|
||
4. **P1-2 Channel 补校验**:`Channel.vue` 的 `el-form` 挂了不存在的
|
||
`formRef/rules`(模板引用了未定义的变量,保存直调 API)。
|
||
补 `rules`(平台/店铺ID/店铺名必填,App Key 留空合法)+ 保存前 validate。
|
||
5. **文档数字**:README PG 口径 `499` → `514`(实跑)。
|
||
|
||
## 本轮新增(2026-09-12 续-3 · 附录 A #3–#10 全接入 + P1-2 真机验证)
|
||
|
||
7 个新页面(路由+菜单已接线,构建 ✅ 6.37s):
|
||
|
||
| # | 页面 | 路由 | 后端接口 | 真机数据 |
|
||
|---|---|---|---|---|
|
||
| 3 | 核销记录 `Allocations.vue`(只读) | `/finance/allocations` | `GET /finance/allocations/` | 30 行 |
|
||
| 4 | 库存流水 `Movements.vue`(只读) | `/stocks/movements` | `GET /inventory/movements/` | 50 行 |
|
||
| 5 | 预警规则 `AlertRules.vue`(CRUD+开关+init-default) | `/notify/rules` | `/notify/rules/` + `init-default` | 4 行 |
|
||
| 6 | 打印模板 `PrintTemplates.vue`(CRUD) | `/printing/templates` | `/printing/templates/` | 空态 200 ✅ |
|
||
| 7 | 自定义报表 `ReportDefinitions.vue`(init-system+run) | `/report/definitions` | `definitions/` + `init-system` + `run` | 空态 200 ✅ |
|
||
| 8 | 库存大盘 | Dashboard 库存卡片点击 → 明细抽屉 | `/report/dashboard/inventory-status/` | 2 仓/30734 件/¥131,509.92 |
|
||
| 9 | 科目管理 `Accounts.vue`(init-chart+启用开关) | `/finance/accounts` | `/finance/accounts/` + `init-chart` | 空态 200 ✅ |
|
||
| 10 | 价格等级/联系人 `PriceLevels.vue`(等级 CRUD + 联系人只读) | `/partner/price-levels` | `price-levels/` + `contacts/` | 空态 200 ✅ |
|
||
|
||
P1-2 Playwright 真机(dev :18099 + demo 租户,0 console errors):
|
||
空提交被拦——Vouchers(请输入摘要)/ AlertRules(编码+名称)/ Channel(店铺ID+店铺名),
|
||
dialog 不关闭;抽屉字段曾与后端键名错位(`by_warehouse` vs 实际 `warehouses`/
|
||
`grand_total_*`),已按实测键修好并复验出真数据。
|
||
|
||
## 最终验证(2026-09-12 终版)
|
||
|
||
- 后端 SQLite:**510 passed / 4 skipped**(PG 专用并发用例)
|
||
- 后端 PostgreSQL 5433:**514 passed / 0 skipped**
|
||
- 前端:`npm run build` ✅ 6.37s;`npm test` ✅ 11/11
|
||
- `manage.py check` 0 issues;`makemigrations --check` No changes
|
||
- Playwright:Register 空/非法被拦、合法 201 跳 dashboard;9 页渲染全过;3 页空提交被拦;抽屉真数据;全程 0 console errors
|
||
|
||
## 遗留(诚实记录)
|
||
|
||
- P2-2 部署 readiness gate + 上线 192.168.5.7:需 Docker/服务器凭证,未动。
|
||
- P2-3/D2 移动开单、P3-1 行业专版:按客户驱动,未动。
|