423 lines
14 KiB
Markdown
423 lines
14 KiB
Markdown
> **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)
|
||
**关联契约规范**: `TECH_STACK/API_CONTRACT.md`(全局 API 契约权威)
|
||
**关联枚举字典**: `DATA_MODEL/ENUMS.md`
|
||
**最后更新**: 2026-04-27
|
||
|
||
---
|
||
|
||
## 变更历史
|
||
|
||
| 日期 | 变更人 | 变更内容 |
|
||
|---|---|---|
|
||
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
|
||
|
||
## 一、文档定位与边界
|
||
|
||
本文件聚焦客源模块的:
|
||
|
||
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 API(P0 必需)
|
||
|
||
| 端点 | 方法 | 权限 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 验收条目
|