Files
nexus/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md
2026-04-30 06:33:50 +08:00

1191 lines
63 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> **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_PROPERTY
> **权威定义**:本文件是房源模块所有表结构的唯一权威来源。
> **主文档引用**`DATA_MODEL.md` §3.3§3.16 为本文件的概览摘要,开发以本文件为准。
> **版本**v1.0 | **日期**2026-04-24
---
## 目录
1. [模块说明](#1-模块说明)
2. [表清单](#2-表清单)
3. [枚举值总览](#3-枚举值总览)
4. [DDL 定义](#4-ddl-定义)
- 4.1 [properties房源主表](#41-properties房源主表)
- 4.2 [property_contacts联系人](#42-property_contacts联系人)
- 4.3 [listing_histories挂牌历史](#43-listing_histories挂牌历史)
- 4.4 [price_changes调价记录](#44-price_changes调价记录)
- 4.5 [follow_logs跟进日志](#45-follow_logs跟进日志)
- 4.6 [follow_log_attachments跟进附件](#46-follow_log_attachments跟进附件)
- 4.7 [follow_log_recordings跟进录音](#47-follow_log_recordings跟进录音)
- 4.8 [property_keys钥匙管理](#48-property_keys钥匙管理)
- 4.9 [key_attachments钥匙附件](#49-key_attachments钥匙附件)
- 4.10 [commissions委托管理](#410-commissions委托管理)
- 4.11 [commission_attachments委托附件](#411-commission_attachments委托附件)
- 4.12 [field_surveys实勘管理](#412-field_surveys实勘管理)
- 4.13 [survey_photos实勘照片](#413-survey_photos实勘照片)
- 4.14 [property_photos房源图片](#414-property_photos房源图片)
- 4.15 [property_attachments房源附件](#415-property_attachments房源附件)
- 4.16 [property_marketing营销信息](#416-property_marketing营销信息)
- 4.17 [property_certificates产证信息](#417-property_certificates产证信息)
- 4.18 [property_completeness维护完成度](#418-property_completeness维护完成度)
- 4.19 [property_tags / property_tag_relations标签](#419-property_tags--property_tag_relations标签)
- 4.20 [property_favorites收藏](#420-property_favorites收藏)
- 4.21 [property_protections保护房](#421-property_protections保护房)
- 4.22 [number_holder_approvals号码方审批](#422-number_holder_approvals号码方审批)
5. [触发器](#5-触发器)
6. [查询模式参考](#6-查询模式参考)
7. [禁止操作](#7-禁止操作)
---
## 1 模块说明
**房源Property** 是 Fonrey 系统的核心领域对象,代表一套二手房源的完整档案。
核心业务规则:
| 规则 | 说明 |
|------|------|
| 多态交易状态 | 一套房源可同时处于出售、出租或租售三态(`status` |
| 流通属性 | 公盘/私盘/特盘/封盘(`attribute`),控制可见范围 |
| 相关方体系 | 首录方/号码方/出售方/实买方,通过 `staff_id` 关联 |
| 联系人加密 | 业主/联系人手机号 AES-256-GCM 加密SHA-256 哈希索引 |
| 跟进日志不可删 | `sensitive_view` 类型跟进 `is_deletable=FALSE`,合规强制 |
| 挂牌历史不可删 | `listing_histories``deleted_at`append-only |
| 调价记录不可删 | `price_changes``deleted_at`append-only |
| 完成度异步计算 | `completeness_score` 由 Celery 任务更新,不实时 |
---
## 2 表清单
| # | 表名 | 说明 | 是否可删除 |
|---|------|------|----------|
| 1 | `properties` | 房源主表 | 软删除(`deleted_at` |
| 2 | `property_contacts` | 业主/联系人(手机号加密) | 软删除 |
| 3 | `listing_histories` | 挂牌历史快照 | **不可删除** |
| 4 | `price_changes` | 调价记录 | **不可删除** |
| 5 | `follow_logs` | 跟进日志6种类型 | 部分不可删(`sensitive_view` |
| 6 | `follow_log_attachments` | 跟进附件(图片) | 随日志联级 |
| 7 | `follow_log_recordings` | 跟进录音 | 随日志联级 |
| 8 | `property_keys` | 钥匙管理 | 软删除(`is_active=FALSE` |
| 9 | `key_attachments` | 钥匙附件 | 随钥匙联级 |
| 10 | `commissions` | 委托管理 | 状态驱动(`status='cancelled'` |
| 11 | `commission_attachments` | 委托附件 | 随委托联级 |
| 12 | `field_surveys` | 实勘管理 | 软删除 |
| 13 | `survey_photos` | 实勘照片(按空间分类) | 随实勘联级 |
| 14 | `property_photos` | 房源图片(经纪人管理) | 软删除 |
| 15 | `property_attachments` | 房源附件 | 直接删除 |
| 16 | `property_marketing` | 营销信息1:1 | 随房源联级 |
| 17 | `property_certificates` | 产证信息1:1 | 随房源联级 |
| 18 | `property_completeness` | 维护完成度快照1:1 | 随房源联级 |
| 19 | `property_tags` | 标签字典 | 系统标签不可删 |
| 20 | `property_tag_relations` | 房源↔标签多对多 | 随房源/标签联级 |
| 21 | `property_favorites` | 经纪人收藏房源 | 直接删除 |
| 22 | `property_protections` | 保护房设置1:1 | 随房源联级 |
| 23 | `number_holder_approvals` | 号码方变更审批 | 随房源联级 |
---
## 3 枚举值总览
### property_type房源类型
| 值 | 说明 |
|----|------|
| `residential` | 住宅 |
| `villa` | 别墅 |
| `commercial_residential` | 商住 |
| `shop` | 商铺 |
| `office` | 写字楼 |
| `other` | 其他 |
### status交易状态
| 值 | 说明 |
|----|------|
| `for_sale` | 出售 |
| `for_rent` | 出租 |
| `for_sale_rent` | 租售 |
| `suspended` | 暂缓 |
| `sold_elsewhere` | 他售 |
| `rented_elsewhere` | 他租 |
| `sold` | 成交 |
| `unlisted` | 未挂牌 |
### attribute流通属性
| 值 | 说明 |
|----|------|
| `public` | 公盘 |
| `private` | 私盘 |
| `special` | 特盘 |
| `sealed` | 封盘 |
### orientation朝向
| 值 | 说明 |
|----|------|
| `east` | 东 |
| `south` | 南 |
| `west` | 西 |
| `north` | 北 |
| `southeast` | 东南 |
| `northeast` | 东北 |
| `east_west` | 东西 |
| `south_north` | 南北 |
| `northwest` | 西北 |
| `southwest` | 西南 |
### decoration装修情况
| 值 | 说明 |
|----|------|
| `rough` | 毛坯 |
| `plain` | 清水 |
| `simple` | 简装 |
| `medium` | 中装 |
| `fine` | 精装 |
| `luxury` | 豪装 |
### grade等级
| 值 | 说明 |
|----|------|
| `A` | A急迫 |
| `B` | B较强 |
| `C` | C一般 |
| `D` | D较弱 |
### follow_log.log_type跟进日志类型
| 值 | 说明 | 可删除 |
|----|------|--------|
| `written` | 经纪人主动写入跟进 | 是 |
| `modified` | 字段变更自动生成 | 是 |
| `sensitive_op` | 敏感信息操作跟进 | 否 |
| `sensitive_view` | 敏感信息查看(查看号码等) | **否** |
| `other` | 其他(钥匙/新增联系人等) | 是 |
| `system` | 系统日志 | 是 |
---
## 4 DDL 定义
### 4.1 properties房源主表
```sql
-- ============================================================
-- 房源主表:系统最核心的表,全部筛选/排序/搜索围绕此表展开
-- 设计重点89,000+ 数据量,复合索引策略,分区预留
-- ============================================================
CREATE TABLE properties (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
-- ── 基础分类 ──
property_type VARCHAR(20) NOT NULL -- 房源类型residential=住宅 / villa=别墅 / commercial_residential=商住 / shop=商铺 / office=写字楼 / other=其他(详见 ENUMS.md §property_type
CHECK (property_type IN ('residential','villa','commercial_residential',
'shop','office','other')),
-- ── 交易状态 ──
status VARCHAR(20) NOT NULL DEFAULT 'for_sale' -- 房源交易状态for_sale=出售 / for_rent=出租 / for_sale_rent=租售 / suspended=暂缓 / sold_elsewhere=他售 / rented_elsewhere=他租 / sold=成交 / unlisted=未挂牌(详见 ENUMS.md §status
CHECK (status IN ('for_sale','for_rent','for_sale_rent',
'suspended','sold_elsewhere','rented_elsewhere',
'sold','unlisted')),
-- ── 流通属性 ──
attribute VARCHAR(20) NOT NULL DEFAULT 'public' -- 房源流通属性public=公盘 / private=私盘 / special=特盘 / sealed=封盘;控制可见范围(详见 ENUMS.md §attribute
CHECK (attribute IN ('public','private','special','sealed')),
private_reason TEXT, -- 私盘/封盘必填说明attribute 为 private/sealed 时必填最多200字
-- ── 位置信息 ──
complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE RESTRICT, -- 所属楼盘(关联 complexes 表,房源必须挂在楼盘下,禁止级联删除)
building_id UUID REFERENCES buildings(id) ON DELETE SET NULL, -- 所属楼栋(关联 buildings 表;楼栋被删除时置 NULL
block_no VARCHAR(30), -- 栋/幢/弄号(如"3栋"、"A幢";与 unit_no 组合定位具体位置)
unit_no VARCHAR(30), -- 单元号(如"1单元"、"055"
room_no VARCHAR(30), -- 房号/门牌号(如"0301"、"1502"
floor SMALLINT NOT NULL, -- 所在楼层(正整数,不超过 total_floorsCheckConstraint 校验)
total_floors SMALLINT NOT NULL, -- 楼栋总层数(正整数)
CONSTRAINT chk_floor CHECK (floor > 0 AND floor <= total_floors), -- 楼层合法性约束:所在楼层必须 > 0 且 ≤ 总楼层
-- ── 户型 ──
bedroom_count SMALLINT NOT NULL DEFAULT 0, -- 卧室数(室)
living_room_count SMALLINT NOT NULL DEFAULT 0, -- 客厅/餐厅数(厅)
bathroom_count SMALLINT NOT NULL DEFAULT 0, -- 卫生间数(卫)
kitchen_count SMALLINT NOT NULL DEFAULT 0, -- 厨房数(厨)
balcony_count SMALLINT NOT NULL DEFAULT 0, -- 阳台数0=无阳台)
-- ── 面积 ──
area NUMERIC(8,2) NOT NULL, -- 建筑面积含公摊录入必填
inner_area NUMERIC(8,2), -- 套内面积不含公摊选填编辑页专属字段
-- ── 价格 ──
sale_price NUMERIC(12,2), -- 挂牌售价(万元;出售类房源必填,出租类可为 NULL
sale_bottom_price NUMERIC(12,2), -- 售底价(万元;业主心理底价,仅内部可见,不对外展示)
sale_record_price NUMERIC(12,2), -- 备案/核验价(万元;填写后同步至营销库)
rent_price NUMERIC(10,2), -- 挂牌租价(元/月;出租类房源使用)
-- ── 基础物理属性 ──
orientation VARCHAR(10) -- 朝向east=东 / south=南 / west=西 / north=北 / southeast=东南 / northeast=东北 / east_west=东西 / south_north=南北 / northwest=西北 / southwest=西南(详见 ENUMS.md §orientation
CHECK (orientation IN ('east','south','west','north',
'southeast','northeast','east_west',
'south_north','northwest','southwest')),
decoration VARCHAR(10) -- 装修情况rough=毛坯 / plain=清水 / simple=简装 / medium=中装 / fine=精装 / luxury=豪装(详见 ENUMS.md §decoration
CHECK (decoration IN ('rough','plain','simple','medium',
'fine','luxury')),
has_elevator BOOLEAN, -- 是否有电梯true=有 / false=无 / NULL=未确认
built_year SMALLINT, -- 建成年份(如 2018可空老房源无记录建成年代为空可能影响营销发房
-- ── 用途 ──
usage_type VARCHAR(30), -- 房屋用途大类(如:住宅 / 商住 / 商业;对应更改用途浮窗第一级下拉)
usage_subtype VARCHAR(30), -- 房屋用途细分小类(如:普通住宅 / 花园洋房;对应更改用途浮窗第二级下拉)
-- ── 商铺专属 ──
shop_frontage NUMERIC(6,2), -- 开间(米;商铺专属,住宅类为 NULL
shop_depth NUMERIC(6,2), -- 进深(米;商铺专属)
shop_height NUMERIC(6,2), -- 层高(米;商铺专属)
shop_location VARCHAR(20) -- 商铺位置类型street=沿街 / mall=商场内 / residential=住宅底商 / ground_floor=楼栋底层 / complex=综合体(商铺专属)
CHECK (shop_location IS NULL OR
shop_location IN ('street','mall','residential',
'ground_floor','complex')),
-- ── 房屋状态 ──
house_status VARCHAR(20) -- 房屋现状owner_occupied=业主自住 / vacant=空置 / tenant_occupied=租客租住 / unknown=未知;影响带看安排
CHECK (house_status IN ('owner_occupied','vacant',
'tenant_occupied','unknown')),
viewing_time VARCHAR(20) -- 看房时间安排anytime=随时可看 / by_appointment=提前预约 / inconvenient=不方便看
CHECK (viewing_time IN ('anytime','by_appointment','inconvenient')),
-- ── 等级与标签 ──
grade VARCHAR(10) -- 房源等级业主出售意向A=急迫 / B=较强 / C=一般 / D=较弱(详见 ENUMS.md §grade
CHECK (grade IN ('A','B','C','D')),
-- ── 交易属性 ──
ownership_years VARCHAR(30), -- 房本年限不满2年/满2年/满5年 等(影响交易税费)
ownership_years_detail VARCHAR(20), -- 房本年限辅助说明:满五/不满五(与 ownership_years 组合使用)
ownership_nature VARCHAR(20) -- 产权性质commercial=商品房 / reform_housing=房改房 / collective=集资房 / economic=经济活用房
CHECK (ownership_nature IS NULL OR
ownership_nature IN ('commercial','reform_housing',
'collective','economic')),
is_only_house BOOLEAN, -- 是否唯一住房true=唯一 / false=非唯一 / NULL=未确认;影响交易税费计算
payment_method VARCHAR(30) -- 购房付款方式full=一次付清 / mortgage=按揭付款 / installment=分批次付款 / advance=垫资解按
CHECK (payment_method IS NULL OR
payment_method IN ('full','mortgage','installment','advance')),
tax_included VARCHAR(10) -- 包税费方式each_party=各付 / net=到手 / inclusive=包税
CHECK (tax_included IS NULL OR
tax_included IN ('each_party','net','inclusive')),
has_mortgage BOOLEAN, -- 是否有抵押true=有抵押 / false=无 / NULL=未确认
has_loan BOOLEAN, -- 是否有贷款未还清true=有 / false=无 / NULL=未确认
has_seal BOOLEAN, -- 是否被查封true=有查封 / false=无 / NULL=未确认
has_restriction BOOLEAN, -- 是否有限制其他限制true=有 / false=无 / NULL=未确认
original_price NUMERIC(12,2), -- 原购价(万元;业主当年购入价,用于计算增值)
sale_reason TEXT, -- 售房原因业主出售理由最多200字如"置换"
-- ── 营销备注 ──
remarks TEXT, -- 房源备注经纪人内部备注最多500字不对外展示
-- ── 相关方 ──
first_recorder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 首录方(最初录入该房源的经纪人;人员离职后置 NULL
number_holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 号码方(持有业主联系号码的经纪人;变更需走审批流)
seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 出售方(负责出售跟进的经纪人)
buyer_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 实买方(促成成交的买方经纪人)
-- ── 来源 ──
source VARCHAR(50), -- 房源来源渠道(枚举值由 lookup_items 维护,如:门店拓客/转介绍/网络等)
-- ── 维护完成度冗余缓存Celery 定期重算)──
completeness_score SMALLINT NOT NULL DEFAULT 0, -- 房源维护完成度评分0-100由 Celery 异步计算,非实时;前端列表页展示徽章)
-- ── 时间轨迹 ──
listed_at TIMESTAMPTZ, -- 最近一次挂牌时间(每次重新挂牌时更新)
last_followed_at TIMESTAMPTZ, -- 最后跟进时间(冗余字段,由触发器自动维护,加速超时未跟进排序)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
deleted_at TIMESTAMPTZ, -- 软删除时间戳NULL=未删除,非 NULL=已软删除
created_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 创建人(操作员工)
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 最后修改人(操作员工)
-- ── 全文检索向量 ──
search_vector TSVECTOR, -- 全文检索向量(由触发器 trg_property_search_vector 自动维护,覆盖栋号/单元/房号/备注)
-- ── 乐观锁 ──
version INTEGER NOT NULL DEFAULT 1 -- 乐观锁版本号(每次 UPDATE 必须 +1应用层检测 0 行受影响时抛 ConflictError
);
-- ── 索引策略 ──
-- 核心列表过滤status + attribute + type
CREATE INDEX idx_properties_status_attr ON properties(status, attribute, property_type)
WHERE deleted_at IS NULL;
-- 区域筛选
CREATE INDEX idx_properties_complex ON properties(complex_id)
WHERE deleted_at IS NULL;
-- 售价排序
CREATE INDEX idx_properties_sale_price ON properties(sale_price DESC NULLS LAST)
WHERE deleted_at IS NULL AND status IN ('for_sale','for_sale_rent');
-- 面积区间
CREATE INDEX idx_properties_area ON properties(area)
WHERE deleted_at IS NULL;
-- 挂牌时间倒序
CREATE INDEX idx_properties_listed_at ON properties(listed_at DESC NULLS LAST)
WHERE deleted_at IS NULL;
-- 超时跟进检测
CREATE INDEX idx_properties_last_followed ON properties(last_followed_at DESC NULLS LAST)
WHERE deleted_at IS NULL;
-- 户型筛选
CREATE INDEX idx_properties_bedroom ON properties(bedroom_count)
WHERE deleted_at IS NULL;
-- 等级筛选
CREATE INDEX idx_properties_grade ON properties(grade)
WHERE deleted_at IS NULL;
-- 完成度排序
CREATE INDEX idx_properties_completeness ON properties(completeness_score)
WHERE deleted_at IS NULL;
-- 全文搜索
CREATE INDEX idx_properties_search ON properties USING gin(search_vector);
-- 相关方快速定位
CREATE INDEX idx_properties_seller_agent ON properties(seller_agent_id)
WHERE deleted_at IS NULL;
CREATE INDEX idx_properties_number_holder ON properties(number_holder_id)
WHERE deleted_at IS NULL;
-- 列表默认排序status + listed_at
CREATE INDEX idx_properties_list_default ON properties(status, listed_at DESC NULLS LAST)
WHERE deleted_at IS NULL;
-- 高频复合索引status + attribute + complex_id + sale_price
CREATE INDEX idx_properties_list_composite ON properties
(status, attribute, complex_id, sale_price DESC NULLS LAST)
WHERE deleted_at IS NULL;
-- 个人仪表板(与我相关)
CREATE INDEX idx_properties_my_properties ON properties
(seller_agent_id, status, listed_at DESC NULLS LAST)
WHERE deleted_at IS NULL;
```
---
### 4.2 property_contacts联系人
```sql
-- ============================================================
-- 业主/联系人:手机号加密存储,哈希值支持重复检测
-- 安全要点:任何查看明文号码的行为均触发审计日志
-- ============================================================
CREATE TABLE property_contacts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表;房源删除时联级删除)
name VARCHAR(50) NOT NULL, -- 联系人姓名(如"张先生";业主或其代理人的真实姓名)
gender VARCHAR(10) NOT NULL DEFAULT 'male' -- 性别male=先生 / female=女士;默认先生
CHECK (gender IN ('male','female')),
identity VARCHAR(20) NOT NULL DEFAULT 'contact' -- 联系人身份owner=业主 / contact=联系人 / subletter=二房东 / tenant=租客 / agent=代理人 / corporate=企业法人;默认联系人
CHECK (identity IN ('owner','contact','subletter',
'tenant','agent','corporate')),
-- owner=业主, contact=联系人, subletter=二房东, tenant=租客,
-- agent=代理人, corporate=企业法人
-- 手机号:加密存储 + 哈希索引
phone_enc BYTEA NOT NULL, -- 手机号1密文AES-256-GCM 加密,不可直接查询)
phone_hash VARCHAR(64) NOT NULL, -- 手机号1哈希SHA-256用于重复房源检测和精确查询
phone2_enc BYTEA, -- 手机号2密文AES-256-GCM 加密;选填)
phone2_hash VARCHAR(64), -- 手机号2哈希SHA-256phone2_enc 存在时必填)
wechat VARCHAR(100), -- 微信号(选填;无数据时前端展示"-"
qq VARCHAR(20), -- QQ号选填无数据时前端展示"-"
remarks TEXT, -- 备注最多200字补充说明联系人情况
-- 号码方标记(关联审批流)
is_number_holder BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为号码方true=是号码方(审批通过)/ false=否;号码方变更须走审批流
number_holder_approved_at TIMESTAMPTZ, -- 号码方审批通过时间NULL=尚未成为号码方
sort_order INTEGER NOT NULL DEFAULT 0, -- 排序权重(数值越小越靠前;控制联系人在面板中的显示顺序)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
deleted_at TIMESTAMPTZ, -- 软删除时间戳NULL=未删除,非 NULL=已软删除
created_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 创建人(操作员工)
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 最后修改人(操作员工)
);
CREATE INDEX idx_contacts_property ON property_contacts(property_id)
WHERE deleted_at IS NULL;
-- 关键:手机号哈希全局索引(重复房源检测)
CREATE INDEX idx_contacts_phone_hash ON property_contacts(phone_hash)
WHERE deleted_at IS NULL;
CREATE INDEX idx_contacts_phone2_hash ON property_contacts(phone2_hash)
WHERE phone2_hash IS NOT NULL AND deleted_at IS NULL;
```
---
### 4.3 listing_histories挂牌历史
```sql
-- ============================================================
-- 挂牌历史:记录房源每次上架的完整快照
-- 注意:无 deleted_at不可删除append-only合规要求
-- ============================================================
CREATE TABLE listing_histories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, -- 所属房源(关联 properties 表;禁止级联删除,保留历史)
listing_type VARCHAR(20) NOT NULL -- 挂牌类型for_sale=出售挂牌 / for_rent=出租挂牌
CHECK (listing_type IN ('for_sale','for_rent')),
status VARCHAR(20) NOT NULL DEFAULT 'active' -- 挂牌状态active=挂牌中 / ended=已结束
CHECK (status IN ('active','ended')),
-- 价格快照
sale_price NUMERIC(12,2), -- 本次挂牌售价快照(万元;出售挂牌时记录)
rent_price NUMERIC(10,2), -- 本次挂牌租价快照(元/月;出租挂牌时记录)
sale_unit_price NUMERIC(10,2), -- 本次挂牌售价单价(元/m²由 sale_price ÷ area 计算后存储)
-- 交易信息快照
ownership_years VARCHAR(30), -- 本次挂牌时的房本年限快照(如"满2年"
is_only_house BOOLEAN, -- 本次挂牌时的唯一住房状态快照
tax_included VARCHAR(10), -- 本次挂牌时的包税费方式快照each_party=各付 / net=到手 / inclusive=包税)
sale_reason TEXT, -- 本次挂牌时的售房原因快照
-- 经纪人快照(防止人员变动后丢失历史数据)
seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 本次挂牌的出售经纪人(人员离职后置 NULL但 snapshot 保留)
seller_agent_snapshot JSONB, -- 出售经纪人快照({name, store_group, org_unit_name};防止人员变动后数据丢失)
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 本次挂牌开始时间
ended_at TIMESTAMPTZ, -- 本次挂牌结束时间NULL=当前仍在挂牌中
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 记录创建时间(系统自动)
-- 无 deleted_at此表记录不可删除
);
CREATE INDEX idx_listing_histories_property ON listing_histories(property_id);
CREATE INDEX idx_listing_histories_active ON listing_histories(property_id)
WHERE status = 'active';
```
---
### 4.4 price_changes调价记录
```sql
-- ============================================================
-- 调价记录支持折线图展示不可删除append-only
-- ============================================================
CREATE TABLE price_changes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, -- 所属房源(关联 properties 表;禁止级联删除,保留调价历史)
old_sale_price NUMERIC(12,2), -- 调价前挂牌售价万元NULL=首次定价)
new_sale_price NUMERIC(12,2), -- 调价后挂牌售价(万元)
old_bottom_price NUMERIC(12,2), -- 调价前售底价万元NULL=未设置)
new_bottom_price NUMERIC(12,2), -- 调价后售底价万元NULL=本次不变更底价)
old_record_price NUMERIC(12,2), -- 调价前备案/核验价万元NULL=未设置)
new_record_price NUMERIC(12,2), -- 调价后备案/核验价万元NULL=本次不变更)
old_rent_price NUMERIC(10,2), -- 调价前挂牌租价(元/月NULL=非出租类或未设置)
new_rent_price NUMERIC(10,2), -- 调价后挂牌租价(元/月)
change_reason TEXT NOT NULL, -- 调价原因必填最多200字如"业主主动降价"
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 调价操作时间
changed_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT -- 操作人(关联 staff 表;禁止置 NULL保留审计追溯
-- 无 deleted_at不可删除
);
CREATE INDEX idx_price_changes_property ON price_changes(property_id);
CREATE INDEX idx_price_changes_time ON price_changes(property_id, changed_at DESC);
```
---
### 4.5 follow_logs跟进日志
```sql
-- ============================================================
-- 跟进日志:系统最高写入频率的表
-- 6 种类型written / modified / sensitive_op / sensitive_view / other / system
-- sensitive_view 类型is_deletable=FALSE合规不可删
-- ============================================================
CREATE TABLE follow_logs (
id UUID NOT NULL DEFAULT gen_random_uuid(), -- 主键(与 created_at 组成复合主键,分区表要求)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(分区键,必须在最前声明;系统自动)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
log_type VARCHAR(30) NOT NULL -- 跟进日志类型written=经纪人主动写入 / modified=字段变更自动生成 / sensitive_op=敏感操作跟进 / sensitive_view=敏感信息查看(不可删)/ other=其他 / system=系统日志
CHECK (log_type IN ('written','modified','sensitive_op',
'sensitive_view','other','system')),
-- 写入跟进专用字段
purpose VARCHAR(50), -- 跟进目的(枚举值由 lookup_items 维护,如:电话/业主跟进/议价/带看;仅 written 类型使用)
content TEXT, -- 跟进内容最少6字最多500字仅 written 类型必填)
-- AI 辅助判断标签
ai_tag VARCHAR(20) -- AI 辅助标签ai_for_sale=AI判断业主在售 / ai_not_for_sale=AI判断业主不售由系统根据跟进内容智能分析后打标
CHECK (ai_tag IS NULL OR ai_tag IN ('ai_for_sale','ai_not_for_sale')),
-- 修改跟进专用字段
change_detail JSONB, -- 字段变更明细(格式:{\"field\": \"sale_price\", \"old\": 850, \"new\": 800, \"label\": \"售价\"}modified 类型使用)
-- 格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"}
-- 系统显示标签
log_tag VARCHAR(50), -- 前端展示标签(如:查看号码/图片下载/改状态/改价格/改等级/修改相关方;对应跟进日志时间线显示的【方括号标签】)
-- 如:查看号码/图片下载/改状态/改价格/改等级/修改相关方
-- 可见性控制
is_public BOOLEAN NOT NULL DEFAULT TRUE, -- 是否公开true=全员可见 / false=仅本人及上级可见
-- FALSE = 仅本人及上级可见
-- 操作人
operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 操作人(关联 staff 表;人员离职后置 NULL但 snapshot 保留)
operator_snapshot JSONB, -- 操作人快照({name, role, org_unit_name, store_group};防止人员离职后丢失显示信息)
-- 是否可删除sensitive_view = FALSE合规强制
is_deletable BOOLEAN NOT NULL DEFAULT TRUE, -- 是否可软删除false=敏感信息查看类型,合规要求不可删除
deleted_at TIMESTAMPTZ, -- 软删除时间戳;仅 is_deletable=TRUE 时可软删NULL=未删除
PRIMARY KEY (id, created_at) -- 分区表主键必须包含分区键
) PARTITION BY RANGE (created_at);
-- ── 按月自动建分区(由 partition_maintenance_task Celery 任务维护)──
-- 示例:初始建立当前月 + 下一个月的分区
CREATE TABLE follow_logs_2026_04 PARTITION OF follow_logs
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE follow_logs_2026_05 PARTITION OF follow_logs
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
-- 默认分区:兜底,防止超出已建分区范围导致写入失败
CREATE TABLE follow_logs_default PARTITION OF follow_logs DEFAULT;
-- 时间线展示(核心)
CREATE INDEX idx_follow_logs_property_time ON follow_logs(property_id, created_at DESC)
WHERE deleted_at IS NULL;
-- 6个 Tab 过滤
CREATE INDEX idx_follow_logs_type ON follow_logs(property_id, log_type, created_at DESC)
WHERE deleted_at IS NULL;
-- 操作员过滤
CREATE INDEX idx_follow_logs_operator ON follow_logs(operator_id, created_at DESC)
WHERE deleted_at IS NULL;
-- 合规审计(敏感类型专用)
CREATE INDEX idx_follow_logs_sensitive ON follow_logs(property_id, created_at DESC)
WHERE log_type IN ('sensitive_view','sensitive_op');
```
---
### 4.6 follow_log_attachments跟进附件
```sql
CREATE TABLE follow_log_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, -- 所属跟进日志(关联 follow_logs 表;日志删除时联级删除)
file_key TEXT NOT NULL, -- 图片存储路径Cloudflare R2 对象路径)
file_name VARCHAR(255) NOT NULL, -- 原始文件名(用户上传时的文件名)
file_size INTEGER NOT NULL, -- 文件大小bytes最大 20MB = 20971520
file_type VARCHAR(10) -- 文件格式bmp / jpg / png / svg / gifPRD 限定格式)
CHECK (file_type IN ('bmp','jpg','png','svg','gif')),
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(控制同一跟进附件的显示顺序)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 上传时间(系统自动)
);
CREATE INDEX idx_follow_attachments_log ON follow_log_attachments(follow_log_id);
```
---
### 4.7 follow_log_recordings跟进录音
```sql
CREATE TABLE follow_log_recordings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, -- 所属跟进日志(关联 follow_logs 表;日志删除时联级删除)
file_key TEXT NOT NULL, -- 录音文件存储路径Cloudflare R2 对象路径)
duration_seconds INTEGER, -- 录音时长(秒;可空,上传时若能解析则填写)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 上传时间(系统自动)
);
CREATE INDEX idx_follow_recordings_log ON follow_log_recordings(follow_log_id);
```
---
### 4.8 property_keys钥匙管理
```sql
CREATE TABLE property_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
key_type VARCHAR(20) NOT NULL -- 钥匙类型mechanical=机械钥匙 / password=密码(如密码门锁)
CHECK (key_type IN ('mechanical','password')),
-- 钥匙持有方
holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 持有人(关联 staff 表;人员离职后置 NULL
holder_snapshot JSONB, -- 持有人快照({name, store_group};防止人员离职后丢失显示信息)
storage_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, -- 保管部门(关联 org_units 表;钥匙存放在哪个部门)
-- 他司钥匙标记
is_other_agency BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为他中介公司的钥匙true=是他司钥匙 / false=本司钥匙
other_agency_info VARCHAR(30), -- 他司中介信息最多30字is_other_agency=true 时填写,如"链家"
remarks TEXT, -- 备注最多200字如密码内容等补充说明
is_active BOOLEAN NOT NULL DEFAULT TRUE, -- 是否有效true=在管中 / false=已归还或失效
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
created_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 创建人(操作员工)
);
CREATE INDEX idx_property_keys_property ON property_keys(property_id)
WHERE is_active = TRUE;
```
---
### 4.9 key_attachments钥匙附件
```sql
CREATE TABLE key_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
key_id UUID NOT NULL REFERENCES property_keys(id) ON DELETE CASCADE, -- 所属钥匙记录(关联 property_keys 表;钥匙删除时联级删除)
file_key TEXT NOT NULL, -- 附件存储路径Cloudflare R2 对象路径)
file_name VARCHAR(255) NOT NULL, -- 原始文件名
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 上传时间(系统自动)
);
CREATE INDEX idx_key_attachments_key ON key_attachments(key_id);
```
---
### 4.10 commissions委托管理
```sql
CREATE TABLE commissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
commission_type VARCHAR(50) NOT NULL, -- 委托类型(独家委托/非独家委托;由 lookup_items 维护)
period_start DATE NOT NULL, -- 委托开始日期
period_end DATE, -- 委托结束日期is_open_ended=true 时为 NULL
is_open_ended BOOLEAN NOT NULL DEFAULT FALSE, -- 是否无固定结束日期true=长期委托 / false=有截止日期
-- 委托经纪人
agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 委托经纪人(关联 staff 表;人员离职后置 NULL
agent_snapshot JSONB, -- 经纪人快照({name, store_group};防止人员变动后数据丢失)
signing_method VARCHAR(50), -- 签约方式(选择后动态展示委托书模板)
-- 委托人(产权人)信息
owner_type VARCHAR(20) NOT NULL DEFAULT 'owner' -- 委托人类型owner=产权人本人 / authorized_third=被授权第三方
CHECK (owner_type IN ('owner','authorized_third')),
property_owner_contact_id UUID REFERENCES property_contacts(id) ON DELETE SET NULL, -- 关联联系人property_contacts 表;若委托人已录入联系人)
owner_name VARCHAR(50), -- 委托人姓名
owner_id_type VARCHAR(20), -- 委托人证件类型(如:身份证/护照)
owner_id_number VARCHAR(50), -- 委托人证件号明文(仅供参考,加密版本见下方)
owner_id_number_enc BYTEA, -- 委托人证件号密文AES-256-GCM 加密)
remarks TEXT, -- 备注最多200字
status VARCHAR(20) NOT NULL DEFAULT 'active' -- 委托状态active=有效 / expired=已过期 / cancelled=已取消
CHECK (status IN ('active','expired','cancelled')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
created_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 创建人(操作员工)
);
CREATE INDEX idx_commissions_property ON commissions(property_id);
CREATE INDEX idx_commissions_active ON commissions(property_id)
WHERE status = 'active';
```
---
### 4.11 commission_attachments委托附件
```sql
CREATE TABLE commission_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
commission_id UUID NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, -- 所属委托(关联 commissions 表;委托删除时联级删除)
category VARCHAR(20) NOT NULL -- 附件分类id_card=身份证 / property_cert=产权证书 / commission_letter=委托书 / other=其他材料
CHECK (category IN ('id_card','property_cert',
'commission_letter','other')),
file_key TEXT NOT NULL, -- 附件存储路径Cloudflare R2 对象路径)
file_name VARCHAR(255) NOT NULL, -- 原始文件名
file_size INTEGER, -- 文件大小bytes
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(数值越小越靠前)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 上传时间(系统自动)
);
CREATE INDEX idx_commission_attachments_commission ON commission_attachments(commission_id);
```
---
### 4.12 field_surveys实勘管理
```sql
CREATE TABLE field_surveys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
status VARCHAR(10) NOT NULL DEFAULT 'draft' -- 实勘状态draft=草稿(未提交)/ submitted=已提交(已完成)
CHECK (status IN ('draft','submitted')),
-- GPS 定位(实勘打卡)
gps_latitude NUMERIC(10,7), -- GPS 纬度实勘打卡位置精度7位小数
gps_longitude NUMERIC(10,7), -- GPS 经度实勘打卡位置精度7位小数
gps_accuracy NUMERIC(6,2), -- GPS 精度(米;标注定位误差)
description TEXT, -- 实勘说明最多200字经纪人现场情况描述
submitted_at TIMESTAMPTZ, -- 提交时间status 变为 submitted 时记录NULL=尚未提交)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动)
created_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT -- 实勘人(操作员工;禁止置 NULL 保留审计)
);
CREATE INDEX idx_field_surveys_property ON field_surveys(property_id);
CREATE INDEX idx_field_surveys_submitted ON field_surveys(property_id)
WHERE status = 'submitted';
```
---
### 4.13 survey_photos实勘照片
```sql
CREATE TABLE survey_photos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
survey_id UUID NOT NULL REFERENCES field_surveys(id) ON DELETE CASCADE, -- 所属实勘(关联 field_surveys 表;实勘删除时联级删除)
category VARCHAR(20) NOT NULL -- 照片空间分类layout=户型图 / living_room=客厅 / dining_room=餐厅 / bedroom=卧室 / bathroom=卫生间 / kitchen=厨房 / entrance=门厅 / balcony=阳台 / study=书房 / indoor_other=室内其他 / outdoor=外景
CHECK (category IN ('layout','living_room','dining_room',
'bedroom','bathroom','kitchen',
'entrance','balcony','study',
'indoor_other','outdoor')),
file_key TEXT NOT NULL, -- 原图存储路径Cloudflare R2 对象路径)
thumbnail_key TEXT, -- 缩略图路径Cloudflare Images 自动生成)
file_size INTEGER, -- 文件大小bytes
width INTEGER, -- 图片宽度(像素;上传时解析)
height INTEGER, -- 图片高度(像素;上传时解析)
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(同一空间分类内,数值越小越靠前)
is_vr_screenshot BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为VR截图true=全景/VR截图区别于普通实拍照片
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 上传时间(系统自动)
);
CREATE INDEX idx_survey_photos_survey ON survey_photos(survey_id);
CREATE INDEX idx_survey_photos_category ON survey_photos(survey_id, category);
```
---
### 4.14 property_photos房源图片
```sql
-- ============================================================
-- 房源图片:经纪人自主上传和管理,与实勘照片分离存储
-- 封面限1张唯一约束全景类型单独处理
-- ============================================================
CREATE TABLE property_photos (
id UUID NOT NULL DEFAULT gen_random_uuid(), -- 主键(与 created_at 组成复合主键,分区表要求)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 上传时间(分区键;系统自动)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
category VARCHAR(20) NOT NULL -- 照片分类cover=封面 / entrance=门厅 / living_room=客厅 / dining_room=餐厅 / bedroom=卧室 / bathroom=卫生间 / kitchen=厨房 / balcony=阳台 / study=书房 / indoor_other=室内其他 / outdoor=外景 / panorama=全景
CHECK (category IN ('cover','entrance','living_room',
'dining_room','bedroom','bathroom',
'kitchen','balcony','study',
'indoor_other','outdoor','panorama')),
file_key TEXT NOT NULL, -- 原图存储路径Cloudflare R2/S3 对象路径)
thumbnail_key TEXT, -- 缩略图路径Cloudflare Images 自动生成)
file_name VARCHAR(255), -- 原始文件名
file_size INTEGER, -- 文件大小bytes
width INTEGER, -- 图片宽度(像素;上传时解析)
height INTEGER, -- 图片高度(像素;上传时解析)
is_cover BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为封面图true=封面;每套房源只能有一张封面(唯一约束保证)
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(同一房源内,数值越小越靠前)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 最后更新时间(系统自动)
created_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 上传人(操作员工)
PRIMARY KEY (id, created_at) -- 分区表主键必须包含分区键
) PARTITION BY RANGE (created_at);
CREATE TABLE property_photos_2026_04 PARTITION OF property_photos
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
CREATE TABLE property_photos_2026_05 PARTITION OF property_photos
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
CREATE TABLE property_photos_default PARTITION OF property_photos DEFAULT;
CREATE INDEX idx_property_photos_property ON property_photos(property_id);
CREATE INDEX idx_property_photos_cover ON property_photos(property_id)
WHERE is_cover = TRUE;
CREATE INDEX idx_property_photos_category ON property_photos(property_id, category);
-- 每套房源只能有一张封面
CREATE UNIQUE INDEX idx_property_photos_unique_cover
ON property_photos(property_id)
WHERE is_cover = TRUE;
```
---
### 4.15 property_attachments房源附件
```sql
CREATE TABLE property_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
category VARCHAR(20) NOT NULL DEFAULT 'other' -- 附件分类id_card=身份证 / property_cert=产权证书 / commission_letter=委托书 / other=其他材料
CHECK (category IN ('id_card','property_cert',
'commission_letter','other')),
file_key TEXT NOT NULL, -- 附件存储路径Cloudflare R2 对象路径)
file_name VARCHAR(255) NOT NULL, -- 原始文件名
file_size INTEGER NOT NULL, -- 文件大小bytes
file_type VARCHAR(50), -- MIME 类型(如 application/pdf、image/jpeg
sort_order SMALLINT NOT NULL DEFAULT 0, -- 排序权重(控制同一房源附件的显示顺序)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 上传时间(系统自动)
created_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 上传人(操作员工)
);
CREATE INDEX idx_property_attachments_property ON property_attachments(property_id);
CREATE INDEX idx_property_attachments_category ON property_attachments(property_id, category);
```
---
### 4.16 property_marketing营销信息
```sql
-- 1:1 扩展表,营销标题/核心卖点/业主心态/户型介绍/小区介绍
CREATE TABLE property_marketing (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源1:1 关联 properties 表)
marketing_title VARCHAR(30), -- 营销标题0-30字前端发房时展示给买家的吸睛标题
core_selling_points TEXT, -- 核心卖点最多200字展示给买家的重点卖点说明
owner_attitude TEXT, -- 业主心态最多200字仅内部可见描述业主议价空间和心理状态
layout_description TEXT, -- 户型介绍最多200字房源户型特点描述面向买家展示
complex_description TEXT, -- 小区介绍最多200字楼盘/小区周边配套描述)
-- AI 生成标记
ai_generated_points BOOLEAN NOT NULL DEFAULT FALSE, -- 核心卖点是否由 AI 生成true=AI辅助生成经纪人确认后使用
ai_generated_attitude BOOLEAN NOT NULL DEFAULT FALSE, -- 业主心态是否由 AI 生成true=AI辅助生成
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 最后更新时间(系统自动)
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 最后修改人(操作员工)
);
```
---
### 4.17 property_certificates产证信息
```sql
-- 1:1 扩展表,存储产权证相关信息
CREATE TABLE property_certificates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源1:1 关联 properties 表)
owner_name VARCHAR(100), -- 产权人姓名(产权证书上登记的所有权人)
owner_id_number VARCHAR(50), -- 证件号码(身份证号/统一社会信用代码等)
owner_cert_type VARCHAR(20), -- 证件类型(如:身份证/护照/营业执照)
property_location VARCHAR(500), -- 房屋坐落产权证书上的完整地址最多500字
cert_status VARCHAR(30), -- 产证状态(如:已过户/抵押中/查封/正常)
cert_no VARCHAR(100), -- 产权证号(不动产权证书编号)
first_registered_at DATE, -- 首次登记时间(产权证上的初始登记日期)
ownership_nature VARCHAR(30), -- 权属性质(如:商品房/经济适用房/回迁房)
land_nature VARCHAR(30), -- 土地性质(如:国有/集体/划拨/出让)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 最后更新时间(系统自动)
updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 最后修改人(操作员工)
);
```
---
### 4.18 property_completeness维护完成度
```sql
-- ============================================================
-- 维护完成度快照1:1
-- 各维度得分由 Celery 任务异步计算后写入,非实时
-- properties.completeness_score 为冗余总分,供列表排序用
-- ============================================================
CREATE TABLE property_completeness (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源1:1 关联 properties 表)
score_core_info SMALLINT NOT NULL DEFAULT 0, -- 重点信息得分满分8包含房源核心字段完整度
score_attachment SMALLINT NOT NULL DEFAULT 0, -- 附件得分满分8身份证/产权证/委托书等材料上传情况)
score_survey SMALLINT NOT NULL DEFAULT 0, -- 实勘得分满分16实勘照片和报告完整度
score_vr SMALLINT NOT NULL DEFAULT 0, -- VR得分满分8VR/全景照片上传情况)
score_key SMALLINT NOT NULL DEFAULT 0, -- 钥匙得分满分10钥匙托管情况
score_commission SMALLINT NOT NULL DEFAULT 0, -- 委托得分满分10独家/普通委托书情况)
score_verification SMALLINT NOT NULL DEFAULT 0, -- 验证得分满分7房源信息核实情况
score_follow_up SMALLINT NOT NULL DEFAULT 0, -- 跟进得分满分8近期跟进记录情况
score_viewing SMALLINT NOT NULL DEFAULT 0, -- 带看得分满分8带看记录完整度
score_other SMALLINT NOT NULL DEFAULT 0, -- 其他得分满分7其他加分项
total_score SMALLINT NOT NULL DEFAULT 0, -- 维护完成度总分0-100供列表排序用与 properties.completeness_score 冗余)
calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 最近一次异步计算完成时间
);
```
---
### 4.19 property_tags / property_tag_relations标签
```sql
-- 标签字典(系统预置 + 运营自定义)
CREATE TABLE property_tags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
name VARCHAR(50) NOT NULL, -- 标签名称最多50字学区/地铁口/满五唯一)
color VARCHAR(7), -- 显示颜色HEX 色值,如 #FF5733前端标签徽章颜色
is_system BOOLEAN NOT NULL DEFAULT FALSE, -- 是否系统预置true=系统内置标签不可删除false=运营自定义标签可删
sort_order INTEGER NOT NULL DEFAULT 0, -- 排序权重(数值越小越靠前)
is_active BOOLEAN NOT NULL DEFAULT TRUE -- 是否启用false=已停用不再展示
);
-- 房源 ↔ 标签 多对多
CREATE TABLE property_tag_relations (
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
tag_id UUID NOT NULL REFERENCES property_tags(id) ON DELETE CASCADE, -- 所属标签(关联 property_tags 表)
PRIMARY KEY (property_id, tag_id)
);
CREATE INDEX idx_property_tags_property ON property_tag_relations(property_id);
CREATE INDEX idx_property_tags_tag ON property_tag_relations(tag_id);
```
---
### 4.20 property_favorites收藏
```sql
-- 经纪人收藏房源(快速访问)
CREATE TABLE property_favorites (
staff_id UUID NOT NULL REFERENCES staff(id) ON DELETE CASCADE, -- 收藏人(关联 staff 表;员工注销时删除收藏记录)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 收藏的房源(关联 properties 表)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 收藏时间(系统自动)
PRIMARY KEY (staff_id, property_id)
);
CREATE INDEX idx_property_favorites_staff ON property_favorites(staff_id);
```
---
### 4.21 property_protections保护房
```sql
-- 1:1标记某套房源是否受保护防止被他人抢单/公盘化)
CREATE TABLE property_protections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源1:1 关联 properties 表)
is_protected BOOLEAN NOT NULL DEFAULT FALSE, -- 是否处于保护状态true=受保护(防止被他人抢单/公盘化)/ false=未保护
reason TEXT, -- 保护原因(说明为何启用保护)
start_at TIMESTAMPTZ, -- 保护开始时间NULL=尚未生效)
end_at TIMESTAMPTZ, -- 保护到期时间NULL=长期保护)
set_by UUID REFERENCES staff(id) ON DELETE SET NULL, -- 设置人(关联 staff 表;人员离职后置 NULL
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 记录创建时间(系统自动)
);
```
---
### 4.22 number_holder_approvals号码方审批
```sql
-- 号码方变更审批流:经纪人申请,上级审批
CREATE TABLE number_holder_approvals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关)
property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, -- 所属房源(关联 properties 表)
contact_id UUID NOT NULL REFERENCES property_contacts(id) ON DELETE CASCADE, -- 申请变更的联系方(关联 property_contacts 表;即号码方)
applicant_id UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT, -- 申请人(关联 staff 表;提交号码方变更申请的经纪人;禁止置 NULL 保留审计)
approver_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 审批人(关联 staff 表;上级审批人;审批前为 NULL
status VARCHAR(20) NOT NULL DEFAULT 'pending' -- 审批状态pending=待审批 / approved=已通过 / rejected=已驳回
CHECK (status IN ('pending','approved','rejected')),
remarks TEXT, -- 审批备注(审批人填写的意见或驳回原因)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 申请提交时间(系统自动)
decided_at TIMESTAMPTZ -- 审批决定时间NULL=尚未审批)
);
CREATE INDEX idx_number_holder_approvals_status ON number_holder_approvals(status)
WHERE status = 'pending';
CREATE INDEX idx_number_holder_approvals_property ON number_holder_approvals(property_id);
```
---
## 5 触发器
### 5.1 房源全文搜索向量自动维护
```sql
CREATE OR REPLACE FUNCTION update_property_search_vector()
RETURNS TRIGGER AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('simple', COALESCE(NEW.block_no, '') ||
' ' || COALESCE(NEW.unit_no, '') ||
' ' || COALESCE(NEW.room_no, '')), 'A') ||
setweight(to_tsvector('simple', COALESCE(NEW.remarks, '')), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_property_search_vector
BEFORE INSERT OR UPDATE OF block_no, unit_no, room_no, remarks
ON properties
FOR EACH ROW EXECUTE FUNCTION update_property_search_vector();
```
### 5.2 last_followed_at 自动维护
```sql
-- 写入跟进日志时,自动更新 properties.last_followed_at加速超时检测排序
CREATE OR REPLACE FUNCTION update_property_last_followed()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.log_type = 'written' THEN
UPDATE properties
SET last_followed_at = NEW.created_at,
updated_at = NOW()
WHERE id = NEW.property_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_update_last_followed
AFTER INSERT ON follow_logs
FOR EACH ROW EXECUTE FUNCTION update_property_last_followed();
```
---
## 6 查询模式参考
### 6.1 房源列表页(高频)
```sql
-- 出售公盘,按挂牌时间倒序
SELECT p.id, p.status, p.attribute, p.sale_price, p.area,
p.bedroom_count, p.floor, p.total_floors, p.completeness_score,
p.listed_at
FROM properties p
WHERE p.status = 'for_sale'
AND p.attribute = 'public'
AND p.deleted_at IS NULL
ORDER BY p.listed_at DESC NULLS LAST
LIMIT 20 OFFSET 0;
-- 命中索引idx_properties_list_default
```
### 6.2 区域筛选(楼盘 + 商圈下钻)
```sql
-- 某商圈下所有出售房源
SELECT p.*
FROM properties p
JOIN complexes c ON c.id = p.complex_id
JOIN complex_business_areas cba ON cba.complex_id = c.id
WHERE cba.business_area_id = :business_area_id
AND p.status IN ('for_sale','for_sale_rent')
AND p.deleted_at IS NULL
ORDER BY p.listed_at DESC NULLS LAST;
-- 命中索引idx_properties_complex + complex_business_areas 索引
```
### 6.3 与我相关(经纪人仪表板)
```sql
SELECT * FROM properties
WHERE seller_agent_id = :my_staff_id
AND status NOT IN ('sold','unlisted')
AND deleted_at IS NULL
ORDER BY listed_at DESC NULLS LAST;
-- 命中索引idx_properties_my_properties
```
### 6.4 重复房源检测(录入前必查)
```sql
-- 通过联系人手机号哈希检测
SELECT pc.property_id, p.status, p.deleted_at
FROM property_contacts pc
JOIN properties p ON p.id = pc.property_id
WHERE pc.phone_hash = SHA256(:input_phone)
AND pc.deleted_at IS NULL;
-- 命中索引idx_contacts_phone_hash
```
### 6.5 跟进日志时间线(详情页)
```sql
-- 房源详情页跟进 Tab全部类型时间倒序
SELECT fl.*, fla.file_key, fla.file_name
FROM follow_logs fl
LEFT JOIN follow_log_attachments fla ON fla.follow_log_id = fl.id
WHERE fl.property_id = :property_id
AND fl.deleted_at IS NULL
ORDER BY fl.created_at DESC;
-- 命中索引idx_follow_logs_property_time
```
---
## 7 禁止操作
| 操作 | 影响表 | 原因 |
|------|--------|------|
| `DELETE FROM listing_histories` | `listing_histories` | 挂牌历史不可删除,合规审计要求 |
| `DELETE FROM price_changes` | `price_changes` | 调价记录不可删除 |
| `UPDATE follow_logs SET deleted_at = NOW() WHERE is_deletable = FALSE` | `follow_logs` | 敏感信息查看类型日志不可删除 |
| 直接修改 `properties.completeness_score` | `properties` | 完成度分数只由 Celery 任务计算更新,禁止 Application 层直接写入 |
| 直接修改 `properties.last_followed_at` | `properties` | 由触发器 `trg_update_last_followed` 自动维护 |
| 删除 `property_tags WHERE is_system = TRUE` | `property_tags` | 系统预置标签不可删除 |
| 明文存储手机号 | `property_contacts` | 手机号必须 AES-256-GCM 加密后存 `phone_enc`,同时更新 `phone_hash` |
---
*DATA_MODEL_PROPERTY.md — Fonrey 房产经纪管理系统房源模块数据模型 v1.0*
*下一步API 接口规范(房源模块 URL 设计 + Request/Response Schema*