Files
dealerhub/PROGRESS_AGI_ITERATION_6.md
T

117 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 行业专版:按客户驱动,未动。