> **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_floors;CheckConstraint 校验) 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, -- 建筑面积(m²,含公摊;录入必填) inner_area NUMERIC(8,2), -- 套内面积(m²,不含公摊;选填,编辑页专属字段) -- ── 价格 ── 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-256;phone2_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 / gif(PRD 限定格式) 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得分(满分8;VR/全景照片上传情况) 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)*