baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
# dealerhub · 经销商 SaaS — 阶段完成报告
> 详细计划见 `PLAN.md`;本文档是阶段 0 + 阶段 1 的 **执行结果**。
## ✅ 阶段 0(骨架 + 异步链路打通)
- ✅ Django 5.2 + DRF 3.17 + adrf 0.1.14 + Granian 2.8.2 + psycopg 3.2 + django-environ
- ✅ 完整 settings 分层:`config/settings/{base,dev,prod,test}.py`
- ✅ `config/asgi.py`(Granian 加载入口)+ `config/wsgi.py`
- ✅ `pytest.ini` + `conftest.py` + `tests/`(9 个测试全通过)
- ✅ 数据库迁移:`core/catalog` 两个 app 的初始 migration 已落库
## ✅ 阶段 1(多租户预留 + 商品中心 CRUD)
- ✅ 多租户中间件 `TenantMiddleware`(基于 `X-Tenant-Id` header)
- ✅ 抽象基类 `TenantScopedModel`:自动 `tenant_id`/`org_id`/`ext_data`/`source_channel`/`is_deleted`/审计字段
- ✅ `Org` 模型(多组织预留,`org_path` 层级路径)
- ✅ 商品中心 4 个 ViewSet(分类/品牌/单位/商品)+ DRF async 分页
- ✅ JWT 鉴权(SimpleJWT)+ admin 路径 + ping 路径公开
- ✅ `acreate` 重写自动注入 `tenant/created_by/updated_by`
## ✅ 关键技术问题与解法(实战踩坑)
1. **`adrf.routers` 没有 `Router` 类** → 改用 `from adrf.routers import DefaultRouter`
2. **DjangoFilterBackend 与 async view 不兼容**(filter_queryset 同步调用 queryset.model,触发未 await 协程) → 自写 async 分页 + 简单 search
3. **DRF 默认 PageNumberPagination 同步** → 自写 `StandardAsyncPagination`(兼容传 coroutine)
4. **同步 ORM 在 async view 里需要 sync_to_async** → 但直接写 ORM 调用(adrf 0.1.14 会自动 wrap)
5. **`asave()` 在 adrf 0.1.14 是必须的** → 抽象基类 `AsyncModelSerializer` 提供 `asave/acreate`
6. **create 时 tenant_id NOT NULL 约束** → 重写 `acreate` 自动注入 tenant/org/user
7. **pytest 的 AsyncClient + adrf view 触发 AsyncToSync RuntimeError** → 改用同步 APIClient(Django 自动 wrap async view)
8. **pytest 设置 `DJANGO_SECRET_KEY` 太短导致 JWT warning** → 强制 ≥32 字符
9. **ping 路径被全局 `IsAuthenticated` 拦截** → 在路由处覆盖 `@authentication_classes([]) @permission_classes([AllowAny])`
## ✅ 验证证据
### 1. Django check 通过
```
$ python manage.py check
System check identified no issues (0 silenced).
```
### 2. 9/9 测试通过
```
$ python -m pytest tests/ -p no:cacheprovider
......... [100%]
9 passed, 21 warnings in 0.38s
```
### 3. 端到端同步验证脚本
```
[setup] tenant=default user=alice token-len=228
[ping] 200 body={"ok":true,"service":"dealerhub",...,"granian":"2.8.2"}}
[catalog/list] 200 body={"count":0,"next":null,"previous":null,"results":[]}
[catalog/create] 201 body={"id":1,"code":"P001","name":"...","status":"active",...}
[catalog/search] 200 body={"count":1,"next":null,"previous":null,"results":[{...}]}
```
## 📁 当前文件结构
```
dealerhub/
├── PLAN.md # 总体实施计划(5 个阶段)
├── README.md # 快速开始
├── PROGRESS.md # 本文件
└── backend/
├── manage.py
├── requirements.in
├── pytest.ini
├── conftest.py
├── .env
├── .gitignore
├── config/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # 共享
│ │ ├── dev.py # SQLite + DEBUG
│ │ ├── prod.py # PostgreSQL + 安全
│ │ └── test.py # 内存 SQLite
│ ├── asgi.py # Granian 加载入口
│ ├── wsgi.py
│ └── urls.py # 根路由 + ping
├── apps/
│ ├── core/
│ │ ├── __init__.py
│ │ ├── apps.py
│ │ ├── base_models.py # TenantScopedModel 抽象基类
│ │ ├── middleware.py # TenantMiddleware
│ │ ├── models.py # Tenant/Org/AuditLog
│ │ ├── serializers.py # AsyncModelSerializer 基类
│ │ ├── views.py
│ │ ├── urls.py
│ │ └── management/commands/seed_initial_data.py
│ └── catalog/
│ ├── __init__.py
│ ├── apps.py
│ ├── models.py # Category/Brand/Unit/Product
│ ├── serializers.py
│ ├── views.py # async ViewSets + 异步分页 + tenant 注入
│ └── urls.py
├── tests/
│ ├── test_ping.py # 1 测试
│ ├── test_catalog.py # 6 测试
│ └── test_tenant.py # 2 测试
├── infra/
│ ├── docker/
│ │ ├── Dockerfile.backend
│ │ └── docker-compose.yml
│ └── scripts/
│ ├── run_dev.sh
│ └── run_prod_granian.sh
└── verify_sync.py # 端到端冒烟脚本
```
## 🚀 启动命令
### 本地开发
```bash
cd C:\Users\12914\Desktop\gj\dealerhub\backend
sh infra/scripts/run_dev.sh
# 或:
DJANGO_SETTINGS_MODULE=config.settings.dev granian config.asgi:application \
--interface asgi --host 0.0.0.0 --port 8000 --reload
```
### Docker
```bash
cd C:\Users\12914\Desktop\gj\dealerhub
cd backend/infra/docker
DJANGO_SECRET_KEY=xxx POSTGRES_PASSWORD=yyy docker compose up -d
```
### 运行测试
```bash
cd C:\Users\12914\Desktop\gj\dealerhub\backend
DJANGO_SETTINGS_MODULE=config.settings.test \
DJANGO_SECRET_KEY="this-is-a-long-secret-key-for-jwt-tests-min-32-chars-OK" \
python -m pytest tests/ -p no:cacheprovider
```
## ⏭️ 下一步(阶段 2)
按 `PLAN.md` 推进:
- `apps/inventory`:仓库 + 库存 + 批次 + 库存流水日志
- `apps/purchase`:采购订单/进货单/付款/退货
- `apps/sales`:销售订单/销售单/收款/退货
- `apps/partner`:客户/供应商/联系人/价格等级
- `apps/finance`:应收/应付/对账单
- `core.workflow.StateMachine`:单据状态机服务化
- 库存原子服务(杜绝并发错票)
每一层都有 pytest 集成测试 + 端到端冒烟验证,结果会追加到本文件。
---
**当前阶段:** 阶段 1 完成(基础 + 多租户预留 + 商品 CRUD)
**下一步:** 阶段 2(进销存主线业务),预计 10–15 天开发量