docs: 新增系统配置模块PRD及数据模型文档,更新TASK.md

- 新增 PRD/系统配置/系统配置模块PRD.md(v0.1 Draft)
  - MVP 范围:US-SETTING-001-A(Lookup Items)、B(房源字段必填规则)、C(客源录入规则)
- 新增 PRD/系统配置/系统配置数据模型设计说明_for_Atlas.md
- 新增 PRD/系统配置/系统配置参数数据.md(竞品参数数据)
- 删除旧版 PRD/系统配置/系统配置.md(已被新PRD替代)
- 新增 DATA_MODEL/DATA_MODEL_SETTING.md(系统配置数据模型)
- 新增 DATA_MODEL/ENUMS.md(枚举定义与约定)
- 新增 AGENTS.md(AI Agent 开发规范)
- 更新 PRD/TASK.md:US-SETTING-001 拆分为 A/B/C 三个子任务,修正参考文档路径与验收标准
- 新增 VIBE_CODING_开工前缺失清单.md
- 新增 TECH_STACK/房源管理技术方案.md
- 更新 DATA_MODEL/DATA_MODEL.md、DATA_MODEL_CLIENT.md、DATA_MODEL_LOGIN.md
- 更新 PRD/PRD_MVP.md、PRD/权限管理/权限管理模块PRD.md
- 更新 TECH_STACK/TECH_STACK.md、权限管理系统技术方案.md
- 更新 UI_DESIGN/preview.html、UI_SYSTEM/UI_SYSTEM.md
- 新增 prompt/PRD - 为系统设置生成PRD设计文档.md、更新 prompt 模板
This commit is contained in:
Shen Wei
2026-04-27 15:31:48 +08:00
parent 4422c0eac8
commit 712a33fbac
21 changed files with 6466 additions and 2387 deletions

View File

@@ -0,0 +1,391 @@
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey — 系统配置模块数据模型DATA_MODEL_SETTING
> **定位**:本文件是 `apps/setting/` 模块的数据模型权威来源。
> **版本**v1.0 | **日期**2026-04-27
> **关联 PRD**`PRD/系统配置/系统配置模块PRD.md`
> **关联文档**`DATA_MODEL/ENUMS.md`、`DATA_MODEL/DATA_MODEL.md`
---
## 一、模块定位与架构边界
### 1.1 系统配置模块职责
系统配置模块(`apps/setting/`)负责管理三类性质不同的配置数据:
| 类型 | 说明 | 表 | Schema |
| -------------- | ---------------- | --------------------------------------------- | -------------- |
| **A. 固定系统枚举** | 平台级固定值域,所有租户共享 | `enum_labels` | Publicshared |
| **B. 可配置枚举** | 各租户选项不同,管理员可增删排序 | `lookup_groups` + `lookup_items` | Tenant |
| **C. 行为规则与开关** | 标量配置开关 + 字段必填规则 | `tenant_settings` + `field_requirement_rules` | Tenant |
> **重要区分**
> - 类型 A (`enum_labels`) 已在 `DATA_MODEL/ENUMS.md` 完整定义,**本文件不重复**
> - 类型 B/C 均存于 **租户 Schema**,由租户管理员通过界面维护
> - `apps/setting/` 是 `tenant_apps`**非** `shared_apps`
### 1.2 依赖关系
```
apps/setting/
├── 依赖 → core.cacheRedis统一租户前缀
├── 依赖 → org.Staffcreated_by / updated_by FK
└── 被依赖 ← apps/property读取字段规则、枚举选项
← apps/client读取查重范围、枚举选项
```
---
## 二、可配置枚举表(类型 B
### 2.1 `lookup_groups`(枚举分组)
每个分组代表一类可配置枚举(如「客源来源」「跟进目的」),由研发预置,租户管理员**不可新增或删除分组**,仅可管理分组内的选项。
```sql
-- ============================================================
-- 可配置枚举分组Tenant Schema
-- 研发预置,租户不可修改分组本身
-- ============================================================
CREATE TABLE lookup_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module VARCHAR(50) NOT NULL, -- 'client' | 'property'
key VARCHAR(100) NOT NULL, -- 'source' | 'follow_purpose'
label_zh VARCHAR(50) NOT NULL, -- 界面显示名称,如「客源来源」
description TEXT, -- 说明文案(前端 tooltip 使用)
sort_order SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (module, key)
);
```
**MVP 预置分组(种子数据)**
| module | key | label_zh | description |
|--------|-----|----------|-------------|
| `client` | `source` | 客源来源 | 客源从何处获取,用于来源渠道分析 |
| `client` | `follow_purpose` | 跟进目的 | 客源跟进时选择的目的分类 |
| `property` | `source` | 房源来源 | 房源从何处获取 |
---
### 2.2 `lookup_items`(枚举选项)
```sql
-- ============================================================
-- 可配置枚举选项Tenant Schema
-- 租户管理员可增删排序is_system=True 的预制项不可物理删除
-- ============================================================
CREATE TABLE lookup_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES lookup_groups(id) ON DELETE CASCADE,
value VARCHAR(100) NOT NULL, -- 存储值,英文 snake_case如 'door_to_door'
label_zh VARCHAR(50) NOT NULL, -- 显示文本(如「上门」)
is_system BOOLEAN NOT NULL DEFAULT FALSE, -- True=系统预制,不可删除,仅可停用
is_active BOOLEAN NOT NULL DEFAULT TRUE,
sort_order SMALLINT NOT NULL DEFAULT 0,
created_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 系统预制时为 NULL
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (group_id, value)
);
CREATE INDEX idx_lookup_items_group_active
ON lookup_items(group_id, is_active, sort_order);
```
**关键约束**
- `is_system = TRUE` 的记录不允许物理删除Service 层强制拦截)
- `is_active = FALSE` 后:前端录入下拉不展示;历史已选该值的记录保留原值,展示时追加「(已停用)」后缀
- `value` 一旦写入不允许修改(历史数据依赖);如需改名,停用旧项、新增新项
---
### 2.3 MVP 预置种子数据(`is_system = TRUE`
以下选项在租户初始化时自动写入:
#### 客源来源(`client.source`
| value | label_zh | sort_order |
|-------|----------|------------|
| `store_reception` | 门店接待 | 1 |
| `old_client_referral` | 老客户转介绍 | 2 |
| `stationed_dispatch` | 驻守派单 | 3 |
| `walk_in` | 上门 | 4 |
| `online_58` | 网络-58同城 | 5 |
| `online_anjuke` | 网络-安居客 | 6 |
| `wechat` | 微信 | 7 |
| `friend_referral` | 朋友介绍 | 8 |
#### 跟进目的(`client.follow_purpose`
| value | label_zh | sort_order |
|-------|----------|------------|
| `callback` | 回拨 | 1 |
| `push_property` | 推房 | 2 |
| `showing` | 带看 | 3 |
| `maintain` | 维护 | 4 |
| `other` | 其他 | 5 |
#### 房源来源(`property.source`
| value | label_zh | sort_order |
|-------|----------|------------|
| `proactive_development` | 主动开发 | 1 |
| `owner_walk_in` | 业主上门 | 2 |
| `old_client_referral` | 老客户转介绍 | 3 |
| `online_inquiry` | 网络来电 | 4 |
---
## 三、行为规则与开关(类型 C
### 3.1 `tenant_settings`(标量配置键值表)
存储开关bool、阈值int、单选枚举string等标量类型配置项。
```sql
-- ============================================================
-- 租户标量配置键值对Tenant Schema
-- ============================================================
CREATE TABLE tenant_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category VARCHAR(50) NOT NULL, -- 配置分类:'client' | 'property' | 'showroom'
key VARCHAR(100) NOT NULL, -- 配置 key如 'duplicate_check_scope'
value JSONB NOT NULL, -- 存储任意类型bool/int/str如 {"v": "self"}
value_type VARCHAR(20) NOT NULL -- 'bool' | 'int' | 'string' | 'enum'(用于前端渲染控件)
CHECK (value_type IN ('bool', 'int', 'string', 'enum')),
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (category, key)
);
CREATE INDEX idx_tenant_settings_category ON tenant_settings(category);
```
**存储格式约定**
- `bool``{"v": true}``{"v": false}`
- `int``{"v": 30}`
- `string``{"v": "some_value"}`
- `enum``{"v": "self", "choices": ["self", "dept", "company"]}``choices` 由代码硬编码,不存 DB
**MVP 阶段预置 key**
| category | key | value_type | 默认值 | 说明 |
|----------|-----|-----------|--------|------|
| `client` | `duplicate_check_scope` | `enum` | `{"v": "self"}` | 新增私客查重范围:`self`(本人)/ `dept`(本部门)/ `company`(全公司) |
> 未来 P1 阶段可按需追加 key无需修改表结构。
---
### 3.2 `field_requirement_rules`(字段必填规则表)
按「模块 × 房源用途 × 交易状态 × 字段」四元组确定一条规则,控制录入界面的字段显示状态。
```sql
-- ============================================================
-- 字段必填/隐藏规则Tenant Schema
-- MVP 仅支持 module='property'
-- ============================================================
CREATE TABLE field_requirement_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module VARCHAR(20) NOT NULL, -- 'property' | 'client'MVP 只用 'property'
entity_type VARCHAR(50) NOT NULL, -- 与 property.property_type CHECK 约束值对齐
-- 'residential'|'villa'|'commercial_residential'|'shop'|'office'|'other'
trade_status VARCHAR(20) NOT NULL, -- 交易大类:'sale'|'rent'|'sale_rent'|'*'(全部)
CHECK (trade_status IN ('sale', 'rent', 'sale_rent', '*')),
field_key VARCHAR(50) NOT NULL, -- 字段 key如 'orientation'|'decoration'|'floor'
requirement VARCHAR(10) NOT NULL -- 规则值
CHECK (requirement IN ('required', 'optional', 'hidden')),
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (module, entity_type, trade_status, field_key)
);
CREATE INDEX idx_field_req_lookup
ON field_requirement_rules(module, entity_type, trade_status);
```
**与 `property.property_type` 对齐说明**
`entity_type` 的值域与 `property.property_type` 的 CHECK 约束完全一致:
| entity_type | property_type label_zh |
|-------------|----------------------|
| `residential` | 住宅 |
| `villa` | 别墅 |
| `commercial_residential` | 商住 |
| `shop` | 商铺 |
| `office` | 写字楼 |
| `other` | 其他 |
**`trade_status``property.status` 的映射关系**
| trade_status | 对应 property.status 值 |
|--------------|------------------------|
| `sale` | `for_sale` |
| `rent` | `for_rent` |
| `sale_rent` | `for_sale_rent` |
| `*` | 所有状态通用规则fallback |
> **重要**`trade_status` 是录入场景的交易意图分类,不是 `property.status` 的完整枚举。规则匹配逻辑:先查精确匹配(`entity_type + trade_status`),不存在则查 `*` 通配规则。
**MVP 初始规则(研发预置,管理员可覆盖)**
| module | entity_type | trade_status | field_key | requirement |
|--------|-------------|--------------|-----------|-------------|
| `property` | `residential` | `sale` | `orientation` | `optional` |
| `property` | `residential` | `sale` | `decoration` | `optional` |
| `property` | `residential` | `sale` | `floor` | `optional` |
| `property` | `residential` | `sale` | `building_area` | `required` |
| `property` | `residential` | `sale` | `inner_area` | `optional` |
| `property` | `residential` | `sale` | `room_layout` | `required` |
| `property` | `residential` | `rent` | `decoration` | `optional` |
| `property` | `residential` | `rent` | `floor` | `optional` |
| `property` | `residential` | `rent` | `building_area` | `required` |
| `property` | `residential` | `rent` | `room_layout` | `required` |
**MVP 可配置字段范围(对应 PRD AC-4**
| field_key | 说明 | 字段类型 |
|-----------|------|---------|
| `orientation` | 朝向(`property.orientation` 枚举) | 枚举 |
| `decoration` | 装修情况(`property.decoration` 枚举) | 枚举 |
| `floor` | 所在楼层/总楼层 | 数值 |
| `building_area` | 建筑面积(㎡) | 数值 |
| `inner_area` | 套内面积(㎡) | 数值 |
| `room_layout` | 房型(室/厅/卫) | 数值组 |
| `ownership_years` | 产权年限(年) | 数值 |
| `parking_count` | 车位数 | 数值 |
---
## 四、服务层设计
所有业务模块**禁止直接查询配置表**,必须通过统一服务层读取:
```python
# apps/setting/services/tenant_settings_service.py
class TenantSettingsService:
"""
系统配置统一读取服务。
所有配置均经 Redis 缓存TTL 5min写入时主动 invalidate。
Redis Key 规范:{tenant_schema}:setting:{type}:{key}
"""
def get(self, category: str, key: str, default=None):
"""
读取标量配置tenant_settings 表)
Cache Key: {tenant_schema}:setting:kv:{category}.{key}
返回 JSONB value 字段中 'v' 的值(已解包)
"""
def set(self, category: str, key: str, value, updated_by_id) -> None:
"""
写入标量配置 + 主动 invalidate 缓存
"""
def get_lookup_items(self, module: str, key: str) -> list[dict]:
"""
获取可配置枚举选项lookup_items 表)
仅返回 is_active=True 的项,按 sort_order 排序
Cache Key: {tenant_schema}:setting:lookup:{module}.{key}
返回格式:[{"value": "walk_in", "label_zh": "上门", "is_system": True}, ...]
"""
def get_field_requirements(
self, module: str, entity_type: str, trade_status: str
) -> dict[str, str]:
"""
获取字段必填规则
匹配顺序:精确匹配(entity_type + trade_status) > 通配规则(trade_status='*')
Cache Key: {tenant_schema}:setting:field_req:{module}.{entity_type}.{trade_status}
返回格式:{"orientation": "optional", "decoration": "required", ...}
"""
```
---
## 五、Redis 缓存键规范
| 用途 | Cache Key | TTL | 失效触发 |
|------|-----------|-----|---------|
| 标量配置 | `{schema}:setting:kv:{category}.{key}` | 300s | `TenantSettingsService.set()` |
| 可配置枚举 | `{schema}:setting:lookup:{module}.{key}` | 300s | 管理员保存 lookup_items |
| 字段规则 | `{schema}:setting:field_req:{module}.{entity_type}.{trade_status}` | 300s | 管理员保存 field_requirement_rules |
| 客源规则(整体) | `{schema}:setting:client_rules` | 300s | 任意客源规则变更 |
> TTL 300s5 分钟)对应 PRD 成功指标「配置变更生效时延 ≤ 5 分钟」。
---
## 六、目录结构
```
apps/setting/
├── models/
│ ├── lookup.py # LookupGroup, LookupItem
│ └── setting.py # TenantSetting, FieldRequirementRule
├── services/
│ └── tenant_settings_service.py # 统一配置读取服务(禁止直接查表)
├── views/
│ ├── lookup_views.py # 参数配置页面US-SETTING-001-A
│ ├── field_rule_views.py # 房源字段规则US-SETTING-001-B
│ └── client_rule_views.py # 客源规则US-SETTING-001-C
├── templates/setting/
├── fixtures/
│ ├── lookup_groups.json # 分组种子数据3 组)
│ ├── lookup_items.json # 选项种子数据is_system=True
│ ├── tenant_settings.json # 默认配置种子数据
│ └── field_requirement_rules.json # 默认字段规则
├── migrations/
│ ├── 0001_lookup_groups.py
│ ├── 0002_lookup_items.py
│ ├── 0003_tenant_settings.py
│ └── 0004_field_requirement_rules.py
└── urls.py
```
---
## 七、迁移执行顺序
```
0001_lookup_groups # 先建分组表(无外键依赖)
0002_lookup_items # 再建选项表(依赖 lookup_groups + staff
0003_tenant_settings # 独立,无外键依赖
0004_field_requirement_rules # 独立,仅依赖 staff
```
迁移执行后,通过 `call_command('loaddata', 'lookup_groups')` 等方式加载 fixtures 种子数据。
---
## 八、关键约束与禁止项
| 约束 | 规则 |
|------|------|
| 不可删除系统预制项 | `lookup_items.is_system = True` 的记录Service 层硬拦截物理删除请求 |
| 不可修改已有 value | `lookup_items.value` 写入后只读;修改请停用旧项 + 新增新项 |
| 不可直接查询配置表 | 业务模块property/client**必须**通过 `TenantSettingsService` 读取,禁止 ORM 直查 |
| entity_type 值域 | 必须与 `property.property_type` CHECK 约束完全一致(见第三章) |
| Redis Key 前缀 | 必须携带租户 schema 前缀,格式:`{tenant_schema}:setting:{type}:{key}` |
---
## 九、设计决策ADR
| 决策 | 选择 | 理由 |
|------|------|------|
| 枚举两级架构 | `enum_labels`Public/固定)+ `lookup_items`Tenant/可配置)分离 | 保障系统一致性的同时给租户灵活度 |
| `lookup_groups` 由研发预置 | 是 | 防止租户随意创建不规范分组,控制可配置范围边界 |
| `tenant_settings` 使用 JSONB | 是 | 支持 bool/int/string 等多类型,无需为每类型单独建列 |
| `field_requirement_rules` 不新增字段 | 是 | 规则层只控制「必填/选填/隐藏」,字段存在性由 DATA_MODEL_PROPERTY 决定 |
| 服务层统一读取 | `TenantSettingsService` | 统一缓存管理,业务层与配置存储解耦 |
| trade_status 不复用 property.status | 独立 `sale/rent/sale_rent/*` | 录入场景的交易意图与房源全生命周期状态不同,避免耦合 |