Sync: expand data model and gitops notes

This commit is contained in:
2026-04-24 14:49:34 +08:00
parent 7550b4ee18
commit 75b9e25e68
13 changed files with 2418 additions and 318 deletions

View File

@@ -70,7 +70,59 @@
---
## 二、公共 SchemaShared / Public
## 二、领域概览Domain Overview
本节用业务语言描述系统的核心领域对象及其关系,作为各子模块数据模型的导读。
### 核心领域对象
| 领域对象 | 表/子文档 | 业务说明 |
|----------|-----------|----------|
| **Tenant租户** | `public.tenants` | 每家房产经纪公司对应一个租户数据完全隔离Schema-per-Tenant |
| **OrgUnit组织架构** | `org_units` → [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 树形组织架构(总部/区域/城市/大区/分公司/门店/团队/虚拟团队),物化路径存储,支持权限继承 |
| **Staff员工** | `staff` → [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 经纪人/店长/经理,绑定组织节点,手机号加密存储,与账号(登录)分离 |
| **District城区** | `districts` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 行政区划,如「静安区」,是区域体系的顶层节点 |
| **BusinessArea商圈** | `business_areas` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 商圈/板块,从属于城区,一个楼盘可归属多个商圈 |
| **School学校** | `schools` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 对口学校数据库,是买家购房决策的核心参考,与楼盘多对多关联 |
| **Complex楼盘/小区)** | `complexes` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 房源录入的基础底座,维护楼盘标准名称/坐标/锁定状态/别名等 |
| **Building楼栋/单元)** | `buildings` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘下的物理楼栋,区分标准结构与非标结构 |
| **RoomUnit房号** | `room_units` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼层+房间号,房源定位的最细粒度 |
| **Property房源** | `properties` → §3.3 | 系统核心表,每套二手房源的完整档案,支持出售/出租/出售兼出租三态 |
| **Client客源** | `clients` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 买家/租客档案,分私客/公客/成交客,含活跃度评分与自动公客转换机制 |
| **Viewing带看** | `client_viewings` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 经纪人带客户看房的完整记录 |
| **Match配对** | `client_property_matches` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 系统/人工推荐的客源↔房源配对 |
### 领域关系快速导航
```
District (城区)
└─ BusinessArea (商圈)
└─ Complex (楼盘) ─── School (对口学校)
├─ Building (楼栋)
│ └─ RoomUnit (房号)
└─ Property (房源)
├─ PropertyContact (联系人/委托方)
├─ FollowLog (跟进日志)
├─ Viewing (带看记录) ──── Client (客源)
└─ Match (配对记录) ──────┘
OrgUnit (组织架构)
└─ Staff (员工/经纪人) ─── Property / Client / Viewing / Match
```
### 子文档索引
| 子文档 | 覆盖模块 | 状态 |
|--------|----------|------|
| [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 组织人事org_units, staff, 异动/奖惩/教育/家庭等) | ✅ 完成 |
| [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘/区域districts, business_areas, complexes, buildings, room_units, schools 等) | ✅ 完成 |
| [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 客源管理clients, requirements, follow_logs, viewings, matches 等) | ✅ 完成 |
| 本文档 §3.3§3.16 | 房源核心properties 及配套 12 张表)、系统设置 | ✅ 完成 |
---
## 三、公共 SchemaShared / Public
```sql
-- ============================================================
@@ -107,7 +159,7 @@ CREATE INDEX idx_domains_primary ON public.domains(tenant_id) WHERE is_primary =
---
## 、租户 SchemaTenant Schema
## 、租户 SchemaTenant Schema
以下所有表均在每个租户的独立 Schema 内创建。
@@ -115,190 +167,56 @@ CREATE INDEX idx_domains_primary ON public.domains(tenant_id) WHERE is_primary =
### 3.1 组织人事模块Organization & HR
```sql
-- ============================================================
-- 组织架构:公司 → 区域 → 门店 → 组
-- ============================================================
> **详细模型** → 见 [`DATA_MODEL_ORG.md`](./DATA_MODEL_ORG.md)
> 该文件为权威定义,包含完整字段、枚举、查询模式和禁止操作。
-- 组织节点表(树形结构,支持无限层级)
CREATE TABLE org_units (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL
CHECK (type IN ('company','region','store','group')),
parent_id UUID REFERENCES org_units(id) ON DELETE RESTRICT,
path TEXT NOT NULL, -- 物化路径:/root_id/parent_id/self_id/
depth SMALLINT NOT NULL DEFAULT 0,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
**核心表概览**(开发时以 DATA_MODEL_ORG.md 为准):
CREATE INDEX idx_org_units_parent ON org_units(parent_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_org_units_path ON org_units USING gist(path gist_trgm_ops);
-- 注gist_trgm_ops 需要 pg_trgm 扩展,用于路径前缀查询
| 表名 | 说明 |
|------|------|
| `org_units` | 组织树节点(公司/事业部/大区/区域/片区/门店/店组/职能),物化路径树 |
| `staff` | 员工主表含加密手机号、角色、在职状态、Django auth 绑定 |
| `staff_personal_info` | 员工个人信息扩展证件、学历、婚育等1:1 |
| `staff_transfer_logs` | 人事异动不可变审计日志(入职/调动/离职/复职等) |
| `staff_reward_punish` | 奖惩记录 |
| `staff_work_experiences` | 工作经历 |
| `staff_educations` | 教育经历 |
| `staff_trainings` | 培训经历 |
| `staff_family_members` | 家庭成员 |
| `staff_accounts` | 第三方平台账号绑定58安居客/中国网络经纪人等) |
-- 员工表
CREATE TABLE staff (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_unit_id UUID NOT NULL REFERENCES org_units(id) ON DELETE RESTRICT,
name VARCHAR(50) NOT NULL,
phone_hash VARCHAR(64), -- SHA-256 哈希,用于唯一性校验
phone_enc BYTEA, -- AES-256-GCM 加密后的手机号
email VARCHAR(255),
role VARCHAR(30) NOT NULL
CHECK (role IN ('agent','store_manager','admin','operator','system')),
job_title VARCHAR(100), -- 职务描述
avatar_key TEXT, -- R2/S3 存储路径
is_active BOOLEAN NOT NULL DEFAULT TRUE,
joined_at DATE,
left_at DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
-- 关联 Django auth user用于登录认证
user_id INTEGER UNIQUE -- FK to django auth_user
);
CREATE INDEX idx_staff_org ON staff(org_unit_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_staff_role ON staff(role) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_staff_phone_hash ON staff(phone_hash) WHERE deleted_at IS NULL;
```
**关键约束提示**
- `staff.phone_enc` AES-256-GCM 加密,`staff.phone_hash` SHA-256 用于唯一索引
- `staff_transfer_logs` **无 deleted_at**,不可删除
- `org_units` 路径查询:`WHERE path LIKE '/root/{target_id}/%'`
- 员工离职:`status = 'resigned'` + `deleted_at` 软删除,记录永久保留
---
### 3.2 区域与楼盘模块Region & Complex Management
```sql
-- ============================================================
-- 行政区 → 商圈 → 楼盘/小区 → 楼栋
-- 注:楼盘数据是房源录入的基础底座,数据质量直接影响房源录入效率
-- ============================================================
> **详细模型** → 见 [`DATA_MODEL_COMPLEX.md`](./DATA_MODEL_COMPLEX.md)
> 本节仅作概览,开发时以 DATA_MODEL_COMPLEX.md 为权威定义。
-- 城市/行政区
CREATE TABLE districts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) NOT NULL,
city VARCHAR(50) NOT NULL DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
**核心表概览**(开发时以 DATA_MODEL_COMPLEX.md 为准):
-- 商圈/板块
CREATE TABLE business_areas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
district_id UUID NOT NULL REFERENCES districts(id) ON DELETE RESTRICT,
name VARCHAR(100) NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE INDEX idx_business_areas_district ON business_areas(district_id);
-- 地铁线路
CREATE TABLE metro_lines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) NOT NULL,
color VARCHAR(7) -- 线路颜色 HEX
);
-- 地铁站
CREATE TABLE metro_stations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
metro_line_id UUID NOT NULL REFERENCES metro_lines(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0
);
-- 学校
CREATE TABLE schools (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
district_id UUID REFERENCES districts(id) ON DELETE SET NULL,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) -- 小学/初中/高中/九年一贯制 等
);
CREATE INDEX idx_schools_district ON schools(district_id);
-- 楼盘/小区(核心基础表)
CREATE TABLE complexes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
alias VARCHAR(200), -- 别名/曾用名
district_id UUID REFERENCES districts(id) ON DELETE SET NULL,
business_area_id UUID REFERENCES business_areas(id) ON DELETE SET NULL,
address VARCHAR(500),
latitude NUMERIC(10,7),
longitude NUMERIC(10,7),
-- 楼盘物理属性
developer VARCHAR(200), -- 开发商
property_company VARCHAR(200), -- 物业公司
property_fee NUMERIC(8,2), -- 物业费 元/㎡/月
green_rate NUMERIC(5,2), -- 绿化率 %
plot_ratio NUMERIC(5,2), -- 容积率
built_year SMALLINT, -- 竣工年份
ownership_years VARCHAR(20), -- 产权年限枚举
-- 配套信息
has_elevator BOOLEAN,
parking_info TEXT, -- 车位情况描述
-- 全文检索向量(定期更新)
search_vector TSVECTOR,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_complexes_district ON complexes(district_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_complexes_name_trgm ON complexes USING gin(name gin_trgm_ops);
CREATE INDEX idx_complexes_search ON complexes USING gin(search_vector);
CREATE INDEX idx_complexes_geo ON complexes(latitude, longitude) WHERE deleted_at IS NULL;
-- 楼盘与商圈多对多(一个楼盘可跨多个商圈)
CREATE TABLE complex_business_areas (
complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE,
business_area_id UUID NOT NULL REFERENCES business_areas(id) ON DELETE CASCADE,
PRIMARY KEY (complex_id, business_area_id)
);
-- 楼盘与学校关联
CREATE TABLE complex_schools (
complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE,
school_id UUID NOT NULL REFERENCES schools(id) ON DELETE CASCADE,
school_zone VARCHAR(50), -- 学区情况:对口/参考等
PRIMARY KEY (complex_id, school_id)
);
-- 楼盘与地铁站关联
CREATE TABLE complex_metro_stations (
complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE,
station_id UUID NOT NULL REFERENCES metro_stations(id) ON DELETE CASCADE,
distance_meters INTEGER, -- 步行距离(米)
PRIMARY KEY (complex_id, station_id)
);
-- 楼栋
CREATE TABLE buildings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL, -- 楼栋名,如"1号楼"
total_floors SMALLINT NOT NULL,
has_elevator BOOLEAN,
building_type VARCHAR(30), -- 楼型:板楼/塔楼/板塔结合
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_buildings_complex ON buildings(complex_id);
```
| 表名 | 说明 | 关键字段 |
|------|------|----------|
| `districts` | 城区/行政区 | `city`, `name`, `short_name`, `sort_order` |
| `business_areas` | 商圈/板块(从属于城区) | `district_id`, `name`, `latitude`, `longitude` |
| `metro_lines` | 地铁线路 | `city`, `name`, `color` |
| `metro_stations` | 地铁站点 | `metro_line_id`, `name`, `latitude`, `longitude` |
| `schools` | 学校(对口学区) | `district_id`, `name`, `type`, `nature`, `level` |
| `complexes` | 楼盘/小区(房源底座) | `name`, `district_id`, `address`, `latitude/longitude`, `lock_*`, `search_vector` |
| `complex_aliases` | 楼盘别名(含系统别名/用户自定义别名) | `complex_id`, `alias`, `is_system` |
| `complex_business_areas` | 楼盘↔商圈多对多(含主商圈标识) | `complex_id`, `business_area_id`, `is_primary` |
| `complex_schools` | 楼盘↔学校关联(含学区类型) | `complex_id`, `school_id`, `zone_type` |
| `complex_metro_stations` | 楼盘↔地铁站关联(含步行距离) | `complex_id`, `station_id`, `distance_meters` |
| `buildings` | 楼栋/单元 | `complex_id`, `name`, `is_standard`, `total_floors` |
| `room_units` | 房号/结构单元(楼层+房间号) | `building_id`, `floor`, `room_no`, `is_standard` |
| `complex_photos` | 楼盘照片(楼盘图/户型图/VR | `complex_id`, `category`, `file_key`, `is_cover` |
| `complex_attachments` | 楼盘附件 | `complex_id`, `file_key`, `file_name` |
| `complex_price_trends` | 楼盘价格走势(月度) | `complex_id`, `record_month`, `avg_unit_price` |
---
@@ -1066,123 +984,30 @@ CREATE TABLE property_completeness (
### 3.17 客源管理Client Management
```sql
-- ============================================================
-- 客源:私客为核心,公客/成交客为后续版本
-- ============================================================
> **详细模型** → 见 [`DATA_MODEL_CLIENT.md`](./DATA_MODEL_CLIENT.md)
> 该文件为权威定义,包含完整字段、枚举、状态机、查询模式和禁止操作。
CREATE TABLE clients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
**核心表概览**(开发时以 DATA_MODEL_CLIENT.md 为准):
client_type VARCHAR(20) NOT NULL DEFAULT 'private'
CHECK (client_type IN ('private','public','transacted')),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active','converted_public',
'transacted','invalid')),
| 表名 | 说明 |
|------|------|
| `clients` | 客源主表(私客/公客/成交客),含加密手机号哈希、活跃度、归属人 |
| `client_contacts` | 联系人1:N手机号加密+哈希,支持多联系人 |
| `client_requirements` | 需求信息(可多类型:二手/新房/租房),含预算/面积/商圈/朝向等偏好 |
| `client_follow_logs` | 跟进日志高写入频率5种类型敏感查看类型不可删 |
| `client_follow_log_attachments` | 跟进附件(图片/录音最大20MB |
| `client_viewings` | 带看/预约记录1:N含陪看人/合作带看人) |
| `client_property_matches` | 智能配房结果(录客配房/系统配房,匹配度评分) |
| `client_status_logs` | 状态变更不可变审计日志(改状态/改等级/转公/转成交/转无效等) |
| `client_favorite_folders` | 私客收藏夹(经纪人自定义分组) |
| `client_folder_items` | 收藏夹与客源的多对多关联 |
| `client_school_preferences` | 意向学校(拆表,支持精确查询) |
name VARCHAR(50) NOT NULL,
gender VARCHAR(10)
CHECK (gender IN ('male','female','unknown')),
-- 手机号加密存储
phone_enc BYTEA NOT NULL,
phone_hash VARCHAR(64) NOT NULL,
phone2_enc BYTEA,
phone2_hash VARCHAR(64),
-- 购房需求
purpose VARCHAR(10) NOT NULL
CHECK (purpose IN ('buy','rent')),
budget_min NUMERIC(12,2),
budget_max NUMERIC(12,2),
area_min NUMERIC(8,2),
area_max NUMERIC(8,2),
bedroom_needs SMALLINT[], -- 可接受的卧室数量数组
-- 意向区域(存 district/business_area ID 数组)
district_ids UUID[],
business_area_ids UUID[],
-- 活跃度分层(由系统计算)
activity_level VARCHAR(10)
CHECK (activity_level IN ('hot','warm','cold','frozen')),
last_active_at TIMESTAMPTZ,
-- 负责经纪人
agent_id UUID REFERENCES staff(id) ON DELETE SET NULL,
org_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL,
source VARCHAR(50),
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE INDEX idx_clients_agent ON clients(agent_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_phone_hash ON clients(phone_hash) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_status ON clients(status, client_type) WHERE deleted_at IS NULL;
CREATE INDEX idx_clients_activity ON clients(activity_level, last_active_at DESC)
WHERE deleted_at IS NULL;
-- 客源跟进日志(复用结构,单独表避免与房源日志混合)
CREATE TABLE client_follow_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
purpose VARCHAR(50),
content TEXT,
log_tag VARCHAR(50),
is_public BOOLEAN NOT NULL DEFAULT TRUE,
operator_id UUID REFERENCES staff(id) ON DELETE SET NULL,
operator_snapshot JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_client_logs_client ON client_follow_logs(client_id, created_at DESC)
WHERE deleted_at IS NULL;
-- 智能配房记录(客源 ↔ 房源 匹配)
CREATE TABLE client_property_matches (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE,
match_score NUMERIC(5,2), -- 匹配度评分
match_reason JSONB, -- 匹配原因详情
status VARCHAR(20) NOT NULL DEFAULT 'suggested'
CHECK (status IN ('suggested','shared','viewing','rejected')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES staff(id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX idx_client_property_match
ON client_property_matches(client_id, property_id);
-- 带看记录
CREATE TABLE viewings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT,
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
agent_id UUID REFERENCES staff(id) ON DELETE SET NULL,
viewing_type VARCHAR(20) NOT NULL DEFAULT 'first'
CHECK (viewing_type IN ('first','revisit','empty','interview')),
-- first=带看, revisit=复看, empty=空看, interview=面访
scheduled_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
result VARCHAR(20)
CHECK (result IN ('interested','not_interested',
'negotiating','cancelled')),
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_viewings_property ON viewings(property_id);
CREATE INDEX idx_viewings_client ON viewings(client_id);
```
**关键约束提示**
- `client_contacts.phone_hash` 是重复客源检测的唯一依据,录入前必须查重
- `client_status_logs` **无 deleted_at**,不可删除
- 私客超时(配置天数内无跟进)→ Celery 自动转公(`transfer_to_public_type = 'auto'`
- 活跃度 `activity_level` 由 Celery 每日凌晨批量计算,不实时更新
---
@@ -1290,7 +1115,7 @@ CREATE INDEX idx_number_holder_approvals_status ON number_holder_approvals(statu
---
## 、关键索引汇总与查询优化策略
## 、关键索引汇总与查询优化策略
### 4.1 房源列表页核心查询分析
@@ -1372,7 +1197,7 @@ CREATE TRIGGER trg_update_last_followed
---
## 、Redis 缓存策略
## 、Redis 缓存策略
### 5.1 缓存 Key 规范
@@ -1422,7 +1247,7 @@ CREATE TRIGGER trg_update_last_followed
---
## 、Django Model 层设计要点
## 、Django Model 层设计要点
### 6.1 抽象基类
@@ -1500,7 +1325,7 @@ class PropertyManager(ActiveManager):
---
## 、数据量与性能预测
## 、数据量与性能预测
| 表名 | 预估行数 | 增长速度 | 分区策略 |
|------|---------|---------|---------|
@@ -1514,7 +1339,7 @@ class PropertyManager(ActiveManager):
---
## 、必须在开发启动前明确的数据架构决策
## 、必须在开发启动前明确的数据架构决策
| 决策项 | 推荐方案 | 风险 |
|-------|---------|------|