chore: sync local project changes

This commit is contained in:
Shen Wei
2026-04-27 16:26:07 +08:00
parent dfcf7de003
commit 5854781fa8
144 changed files with 12849 additions and 12330 deletions

View File

@@ -0,0 +1,415 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey 客源管理技术方案
**版本**: 1.0
**项目**: Fonrey 房产经纪管理系统
**技术栈**: Django 4.x + HTMX + Alpine.js + PostgreSQL 16 + Redis + Celery + Cloudflare R2
**关联 PRD**: `PRD/客源管理/客源管理模块PRD.md`v1.4
**关联数据模型**: `DATA_MODEL/DATA_MODEL_CLIENT.md`(本方案不重复 DDL
**关联枚举字典**: `DATA_MODEL/ENUMS.md`
**最后更新**: 2026-04-27
---
## 一、文档定位与边界
本文件聚焦客源模块的:
1. 服务边界与模块协作
2. API 端点设计P0/P1 兼容)
3. HTMX 局刷与页面分片约定
4. 权限与数据范围控制
5. 异步任务、缓存、性能与测试映射
> **不在本文件展开**数据表字段、DDL、索引、触发器。以 `DATA_MODEL_CLIENT.md` 为唯一权威。
---
## 二、范围定义(以 TASK.md 为准)
### 2.1 P0 必须覆盖Phase 1
`US-CLIENT-001 ~ US-CLIENT-017`
- 新增私客、列表筛选、详情、需求编辑、跟进、带看
- 联系人管理、等级/状态变更、转公客/转成交/转无效
- 相关员工管理
- 自动转公、重复检测
### 2.2 非 P0仅预留端点不实现复杂能力
- AI 行为解读深度模型
- 新房推荐高级排序引擎
- 公客/成交客完整运营闭环P1/P2
---
## 三、模块架构边界
## 3.1 模块职责(`apps/client`
- 客源主流程:新增、列表、详情、状态流转
- 客源关联流程:联系人、需求、跟进、带看、智能配房结果
- 质量控制:重复检测、自动转公、操作审计
## 3.2 外部依赖
| 依赖模块 | 依赖内容 | 依赖方式 |
|---|---|---|
| `apps/org` | 员工、组织树、归属人/首录人 | FK + Service |
| `apps/property` | 配房候选房源、带看房源关联 | 只读查询 + FK |
| `apps/permission` | 角色与数据范围权限 | PermissionChecker + ScopeQueryBuilder |
| `apps/setting` | 可配置枚举(来源、跟进目的)与查重规则 | TenantSettingsService |
| `core/encryption.py` | 手机号/证件号加密与哈希 | 统一工具调用 |
| `core/cache.py` | 列表缓存、重复计数缓存、任务进度 | Redis |
| `Celery` | 自动转公、重复检测统计、配房重算、导出 | 异步任务 |
| `Cloudflare R2` | 跟进图片、带看附件 | 预签名上传 |
---
## 四、API 设计原则
1. 页面路由与数据 API 分离:
- 页面:`/client/...`
- 数据:`/api/client/...`
2. 列表筛选、Tab 加载、弹窗提交优先 HTMX。
3. 手机号明文永不落库;默认脱敏显示。
4. 重复检测与自动转公走异步批处理 + 局部实时校验组合。
5. 所有状态变更必须写操作日志(不可省略)。
---
## 五、端点清单(核心)
## 5.1 页面路由SSR
| 路径 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/client/list/` | GET | 是 | 客源列表主页面(私客/公客/成交客 Tab 容器) |
| `/client/create/` | GET | 是 | 新增私客页面 |
| `/client/{client_id}/` | GET | 是 | 客源详情主页面 |
| `/client/{client_id}/edit/` | GET | 是 | 编辑客源页面 |
| `/client/{client_id}/operation-logs/` | GET | 是 | 客源操作日志页面 |
## 5.2 HTMX 片段端点
| 路径 | 方法 | 用途 | 返回 |
|---|---|---|---|
| `/client/fragments/list-table/` | POST | 列表筛选/排序/分页局刷 | HTML |
| `/client/fragments/repeat-counters/` | GET | 顶部重复统计局刷 | HTML |
| `/client/{id}/fragments/tab/{tab}/` | GET | 详情 Tab 懒加载(需求/跟进/带看/配房) | HTML |
| `/client/{id}/fragments/contact-panel/` | GET | 联系人面板局刷 | HTML |
| `/client/{id}/fragments/staff-panel/` | GET | 相关员工面板局刷 | HTML |
| `/client/{id}/fragments/follow-timeline/` | POST | 跟进筛选后局刷 | HTML |
> fragment 端点必须校验 `HX-Request=true`,非 HTMX 请求返回 400。
## 5.3 JSON APIP0 必需)
| 端点 | 方法 | 权限 code建议 | 说明 |
|---|---|---|---|
| `/api/client/` | POST | `client.private.create.allow` | 新增私客US-001 |
| `/api/client/list/query/` | POST | `client.private.view.scope` | 私客列表筛选US-002 |
| `/api/client/{id}/detail/` | GET | `client.private.view.scope` | 详情聚合US-004 |
| `/api/client/{id}/requirements/` | PATCH | `client.private.edit.allow` | 编辑需求US-005 |
| `/api/client/{id}/follow-logs/` | POST | `client.private.follow.create.allow` | 写入跟进US-006 |
| `/api/client/{id}/follow-logs/query/` | POST | `client.private.follow.view.scope` | 跟进查询US-006 |
| `/api/client/{id}/viewings/` | POST | `client.private.viewing.create.allow` | 新增带看US-007 |
| `/api/client/{id}/viewings/query/` | POST | `client.private.viewing.view.scope` | 带看查询US-007 |
| `/api/client/{id}/contacts/` | POST | `client.private.contact.create.allow` | 新增联系人US-008 |
| `/api/client/{id}/contacts/{contact_id}/` | PATCH | `client.private.contact.edit.allow` | 编辑联系人US-008 |
| `/api/client/{id}/grade/change/` | POST | `client.private.grade.change.allow` | 改等级US-009 |
| `/api/client/{id}/status/change/` | POST | `client.private.status.change.allow` | 改状态US-010 |
| `/api/client/{id}/transfer-public/` | POST | `client.private.transfer_public.allow` | 手动转公客US-011 |
| `/api/client/{id}/transfer-transacted/` | POST | `client.private.transfer_transacted.allow` | 转成交US-012 |
| `/api/client/{id}/mark-invalid/` | POST | `client.private.mark_invalid.allow` | 转无效US-013 |
| `/api/client/{id}/base-info/` | PATCH | `client.private.edit.allow` | 编辑基础信息US-014 |
| `/api/client/{id}/related-staff/` | PATCH | `client.private.related_staff.edit.allow` | 修改首录/归属人US-015 |
| `/api/client/duplicate/check/` | POST | `client.private.create.allow` | 手机号实时重复检测US-001/017 |
| `/api/client/duplicate/summary/` | GET | `client.private.view.scope` | 重复统计(私客-成交、公客US-017 |
| `/api/client/matches/{id}/query/` | GET | `client.private.view.scope` | 智能配房结果查询US-020预留P0可简版 |
| `/api/client/export/jobs/` | POST | `client.private.export.scope` | 导出任务创建US-002 |
| `/api/client/export/jobs/{job_id}/` | GET | `client.private.export.scope` | 导出任务状态 |
| `/api/client/export/jobs/{job_id}/download/` | GET | `client.private.export.scope` | 导出下载 |
---
## 六、关键 API 规范(请求/响应)
## 6.1 新增私客
`POST /api/client/`
```json
{
"contacts": [
{
"name": "李雷",
"gender": "male",
"phone_country_code": "+86",
"phone": "13800000000",
"phone2": null,
"wechat": "lilei_wechat"
}
],
"base_info": {
"status": "buying",
"property_usage": "residential",
"grade": "B",
"source": "store_reception"
}
}
```
成功 `201`
```json
{
"id": "uuid",
"client_no": "KY202604270001",
"redirect_url": "/client/uuid/",
"message": "保存成功"
}
```
## 6.2 列表查询Keyset
`POST /api/client/list/query/`
```json
{
"client_type": "private",
"tab": "buying",
"keyword": "1380000",
"filters": {
"grade": ["A", "B"],
"status": ["buying", "buy_rent"],
"budget": {"min": 200, "max": 500}
},
"sort": {"field": "last_follow_at", "order": "desc"},
"pagination": {"mode": "keyset", "cursor": "opaque_cursor", "limit": 20}
}
```
## 6.3 手机号重复检测
`POST /api/client/duplicate/check/`
```json
{
"phone_country_code": "+86",
"phone": "13800000000",
"scene": "create",
"client_id": null
}
```
响应:
```json
{
"duplicated": true,
"scope": "dept",
"hits": [
{
"client_id": "uuid",
"client_type": "private",
"owner_name": "王店长",
"created_at": "2026-04-27T10:00:00+08:00"
}
]
}
```
## 6.4 状态与转化类接口(统一协议)
- 改状态:`/status/change/`
- 转公:`/transfer-public/`
- 转成交:`/transfer-transacted/`
- 转无效:`/mark-invalid/`
统一请求字段:
```json
{
"reason": "客户需求变化",
"payload": {}
}
```
额外示例(转成交):
```json
{
"reason": "已签约",
"payload": {
"transacted_date": "2026-04-27",
"transacted_price": "365.00",
"transacted_type": "bought",
"property_id": "uuid"
}
}
```
---
## 七、HTMX 交互约定
## 7.1 Header 约定
- 请求:`HX-Request: true`
- 成功:`HX-Trigger: {"toast":{"level":"success","message":"操作成功"}}`
- 失败:`HX-Trigger: {"toast":{"level":"error","message":"操作失败"}}`
- 跳转:`HX-Redirect: /client/{id}/`
## 7.2 模板分片命名
| 场景 | 模板 |
|---|---|
| 列表 | `templates/client/fragments/list_table.html` |
| 重复计数条 | `templates/client/fragments/repeat_counters.html` |
| 跟进时间线 | `templates/client/fragments/follow_timeline.html` |
| 带看时间线 | `templates/client/fragments/viewing_timeline.html` |
| 联系人侧栏 | `templates/client/fragments/contact_panel.html` |
| 相关员工侧栏 | `templates/client/fragments/staff_panel.html` |
---
## 八、权限与数据范围
## 8.1 最小权限矩阵P0
| 能力 | 权限 code |
|---|---|
| 查看私客列表范围 | `client.private.view.scope` |
| 新增私客 | `client.private.create.allow` |
| 编辑私客信息 | `client.private.edit.allow` |
| 写跟进 | `client.private.follow.create.allow` |
| 查看跟进 | `client.private.follow.view.scope` |
| 管理联系人 | `client.private.contact.create.allow` / `client.private.contact.edit.allow` |
| 改等级/改状态 | `client.private.grade.change.allow` / `client.private.status.change.allow` |
| 转公/转成交/转无效 | `client.private.transfer_public.allow` / `client.private.transfer_transacted.allow` / `client.private.mark_invalid.allow` |
| 导出 | `client.private.export.scope` |
## 8.2 数据范围叠加逻辑
最终可见数据 = `权限 scope``client_type 过滤``业务状态过滤`
- 禁止不带 `client_type` 的全量查询
- 私客、公客、成交客必须分流查询
## 8.3 敏感信息查看审计
查看号码动作必须:
1. 校验查看权限与日限额(若配置)
2. 仅本次响应返回明文
3. 自动写 `client_follow_logs(log_type='sensitive_view')`
4. `sensitive_view` 记录不可删除
---
## 九、异步任务与缓存策略
## 9.1 状态机与规则
- `private -> public`(手动转公或自动转公)
- `private -> transacted`(转成交)
- `private/public/transacted -> invalid`(状态字段,不改变历史)
必须通过 service 校验状态机,禁止跳转。
### 最低规则P0
- `buying <-> buy_rent`
- `renting <-> buy_rent`
- `buying/renting/buy_rent -> paused`
- `paused -> buying/renting`
- 任意可跟进状态 -> `invalid`(必须填写原因)
## 9.2 Celery 任务
| 任务 | 触发时机 | 说明 |
|---|---|---|
| `client_auto_transfer_public_task` | 每小时 | 超时未跟进私客自动转公US-016 |
| `client_duplicate_summary_task` | 每日/按需 | 重复统计US-017 |
| `client_match_recompute_task` | 需求变更后 | 重新计算配房结果 |
| `client_export_task` | 导出任务创建后 | 异步导出 Excel |
> 所有任务必须传入 `tenant_schema_name` 并在任务开头切 schema。
## 9.3 Redis Key 规范
| Key | TTL | 说明 |
|---|---|---|
| `{schema}:client:list:query:{hash}` | 60s | 热门列表筛选缓存 |
| `{schema}:client:repeat:summary` | 300s | 顶部重复计数缓存 |
| `{schema}:client:detail:{id}` | 120s | 详情聚合缓存 |
| `{schema}:client:export:{job_id}` | 24h | 导出任务状态 |
---
## 十、性能与可靠性约束
1. 客源列表、跟进时间线全部使用 Keyset 分页。
2. 高频筛选列建立组合索引(见数据模型文档)。
3. 手机号、证件号统一加密 + hash 索引;禁止明文。
4. 附件上传限制:`bmp/jpg/jpeg/png/gif`20MB/文件。
5. 导出、重复统计、自动转公均走异步,禁止阻塞请求线程。
---
## 十一、安全与合规
1. 手机号、证件号默认脱敏显示,明文查看需权限与审计。
2. 跟进/转化等关键动作必须记录操作者与来源。
3. 重复检测接口不得泄露超出权限范围的完整客户信息。
---
## 十二、错误码建议
| code | HTTP | 场景 |
|---|---|---|
| `CLIENT_NOT_FOUND` | 404 | 客源不存在或无权限 |
| `CLIENT_DUPLICATED_PHONE` | 409 | 号码重复(按租户规则) |
| `CLIENT_INVALID_TRANSITION` | 400 | 非法状态/类型流转 |
| `CLIENT_CONTACT_PHONE_INVALID` | 400 | 联系人电话格式错误 |
| `CLIENT_PERMISSION_DENIED` | 403 | 权限不足 |
| `CLIENT_EXPORT_JOB_NOT_READY` | 409 | 导出未完成 |
---
## 十三、测试映射P0
| US | 最低覆盖 |
|---|---|
| US-CLIENT-001 | 新增成功/必填失败/重复检测提示 |
| US-CLIENT-002 | 组合筛选 + Keyset 分页 + 导出 |
| US-CLIENT-003 | 批量操作权限与审计 |
| US-CLIENT-004~006 | 详情/需求/跟进流程 |
| US-CLIENT-007~008 | 带看与联系人新增编辑 |
| US-CLIENT-009~013 | 等级/状态/转公/转成交/转无效 |
| US-CLIENT-014~015 | 编辑完整信息 + 相关员工变更 |
| US-CLIENT-016 | 自动转公定时任务 |
| US-CLIENT-017 | 重复统计任务与查询接口 |
测试落点:`tests/integration/client/test_us_client.py`
---
## 十四、落地顺序建议
1. 列表查询 + scope 权限US-002
2. 新增私客 + 重复检测US-001/017
3. 详情 + 需求/跟进US-004~006
4. 联系人/带看US-007/008
5. 状态与转化US-009~013
6. 自动任务与导出US-016/017 + 导出)
---
## 十五、文档同步规则
- 枚举变更同步:`DATA_MODEL/ENUMS.md`
- 权限 code 变更同步:`DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 新增配置项同步:`DATA_MODEL/DATA_MODEL_SETTING.md`
- API 变更同步:本文件 + 对应 PRD 验收条目