From eada3af824e5160cefa6d398c45dfd7497acc370 Mon Sep 17 00:00:00 2001 From: Shen Wei Date: Thu, 30 Apr 2026 06:33:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md | 596 +++++++++--------- .../提示词模板/创建项目骨架提示词_v2.3.md | 3 +- .../项目进度交接报告_Phase4.0_收尾.md | 232 +++++++ .../实施报告/项目骨架搭建实施报告_v1.md | 435 +++++++++++++ .../fonrey/规范/DATA_MODEL_注释补全规范_v1.md | 320 ++++++++++ 5 files changed, 1287 insertions(+), 299 deletions(-) create mode 100644 Project/fonrey/实施报告/项目进度交接报告_Phase4.0_收尾.md create mode 100644 Project/fonrey/实施报告/项目骨架搭建实施报告_v1.md create mode 100644 Project/fonrey/规范/DATA_MODEL_注释补全规范_v1.md diff --git a/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md b/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md index 7d373d55..8a4280d8 100644 --- a/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md +++ b/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md @@ -184,136 +184,136 @@ -- ============================================================ CREATE TABLE properties ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成,业务无关) -- ── 基础分类 ── - property_type VARCHAR(20) NOT NULL + 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' + 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' + attribute VARCHAR(20) NOT NULL DEFAULT 'public' -- 房源流通属性:public=公盘 / private=私盘 / special=特盘 / sealed=封盘;控制可见范围(详见 ENUMS.md §attribute) CHECK (attribute IN ('public','private','special','sealed')), - private_reason TEXT, -- 私盘/封盘必填说明 + private_reason TEXT, -- 私盘/封盘必填说明(attribute 为 private/sealed 时必填,最多200字) -- ── 位置信息 ── - complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE RESTRICT, - building_id UUID REFERENCES buildings(id) ON DELETE SET NULL, - block_no VARCHAR(30), -- 栋/幢/弄号 - unit_no VARCHAR(30), -- 单元号 - room_no VARCHAR(30), -- 房号/门牌号 - floor SMALLINT NOT NULL, -- 所在楼层 - total_floors SMALLINT NOT NULL, -- 总楼层 - CONSTRAINT chk_floor CHECK (floor > 0 AND floor <= total_floors), + 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, -- 阳台数 + 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² + area NUMERIC(8,2) NOT NULL, -- 建筑面积(m²,含公摊;录入必填) + inner_area NUMERIC(8,2), -- 套内面积(m²,不含公摊;选填,编辑页专属字段) -- ── 价格 ── - sale_price NUMERIC(12,2), -- 挂牌售价(万元) - sale_bottom_price NUMERIC(12,2), -- 售底价(万元,内部可见) - sale_record_price NUMERIC(12,2), -- 备案/核验价(万元) - rent_price NUMERIC(10,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) + 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) + decoration VARCHAR(10) -- 装修情况:rough=毛坯 / plain=清水 / simple=简装 / medium=中装 / fine=精装 / luxury=豪装(详见 ENUMS.md §decoration) CHECK (decoration IN ('rough','plain','simple','medium', 'fine','luxury')), - has_elevator BOOLEAN, - built_year SMALLINT, + has_elevator BOOLEAN, -- 是否有电梯:true=有 / false=无 / NULL=未确认 + built_year SMALLINT, -- 建成年份(如 2018;可空,老房源无记录;建成年代为空可能影响营销发房) -- ── 用途 ── - usage_type VARCHAR(30), -- 住宅/商住/商业/普通住宅/花园洋房 等 - usage_subtype VARCHAR(30), -- 细分用途 + usage_type VARCHAR(30), -- 房屋用途大类(如:住宅 / 商住 / 商业;对应更改用途浮窗第一级下拉) + usage_subtype VARCHAR(30), -- 房屋用途细分小类(如:普通住宅 / 花园洋房;对应更改用途浮窗第二级下拉) -- ── 商铺专属 ── - shop_frontage NUMERIC(6,2), -- 开间(米) - shop_depth NUMERIC(6,2), -- 进深(米) - shop_height NUMERIC(6,2), -- 层高(米) - shop_location VARCHAR(20) + 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) + house_status VARCHAR(20) -- 房屋现状:owner_occupied=业主自住 / vacant=空置 / tenant_occupied=租客租住 / unknown=未知;影响带看安排 CHECK (house_status IN ('owner_occupied','vacant', 'tenant_occupied','unknown')), - viewing_time VARCHAR(20) + viewing_time VARCHAR(20) -- 看房时间安排:anytime=随时可看 / by_appointment=提前预约 / inconvenient=不方便看 CHECK (viewing_time IN ('anytime','by_appointment','inconvenient')), -- ── 等级与标签 ── - grade VARCHAR(10) + 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_nature VARCHAR(20) + 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, -- 唯一住房 - payment_method VARCHAR(30) + 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) + tax_included VARCHAR(10) -- 包税费方式:each_party=各付 / net=到手 / inclusive=包税 CHECK (tax_included IS NULL OR tax_included IN ('each_party','net','inclusive')), - has_mortgage BOOLEAN, - has_loan BOOLEAN, - has_seal BOOLEAN, - has_restriction BOOLEAN, - original_price NUMERIC(12,2), -- 原购价(万元) - sale_reason TEXT, -- 售房原因(最多200字) + 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字) + remarks TEXT, -- 房源备注(经纪人内部备注,最多500字,不对外展示) -- ── 相关方 ── - first_recorder_id UUID REFERENCES staff(id) ON DELETE SET 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, -- 实买方 + 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 维护) + source VARCHAR(50), -- 房源来源渠道(枚举值由 lookup_items 维护,如:门店拓客/转介绍/网络等) -- ── 维护完成度(冗余缓存,Celery 定期重算)── - completeness_score SMALLINT NOT NULL DEFAULT 0, -- 0-100 + 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, - created_by UUID REFERENCES staff(id) ON DELETE SET NULL, - updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, + 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, + search_vector TSVECTOR, -- 全文检索向量(由触发器 trg_property_search_vector 自动维护,覆盖栋号/单元/房号/备注) -- ── 乐观锁 ── - version INTEGER NOT NULL DEFAULT 1 -- 每次 UPDATE 必须 +1;应用层检测 0 行受影响时抛 ConflictError + version INTEGER NOT NULL DEFAULT 1 -- 乐观锁版本号(每次 UPDATE 必须 +1;应用层检测 0 行受影响时抛 ConflictError) ); -- ── 索引策略 ── @@ -389,38 +389,38 @@ CREATE INDEX idx_properties_my_properties ON properties -- ============================================================ CREATE TABLE property_contacts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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' + 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' + 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, -- AES-256-GCM 加密 - phone_hash VARCHAR(64) NOT NULL, -- SHA-256(phone),去重查询 - phone2_enc BYTEA, - phone2_hash VARCHAR(64), + 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), - remarks TEXT, + wechat VARCHAR(100), -- 微信号(选填;无数据时前端展示"-") + qq VARCHAR(20), -- QQ号(选填;无数据时前端展示"-") + remarks TEXT, -- 备注(最多200字;补充说明联系人情况) -- 号码方标记(关联审批流) - is_number_holder BOOLEAN NOT NULL DEFAULT FALSE, - number_holder_approved_at TIMESTAMPTZ, + 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, - created_by UUID REFERENCES staff(id) ON DELETE SET NULL, - updated_by UUID REFERENCES staff(id) ON DELETE SET 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) @@ -444,33 +444,33 @@ CREATE INDEX idx_contacts_phone2_hash ON property_contacts(phone2_hash) -- ============================================================ CREATE TABLE listing_histories ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + 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 + 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' + 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 NUMERIC(12,2), -- 本次挂牌售价快照(万元;出售挂牌时记录) + rent_price NUMERIC(10,2), -- 本次挂牌租价快照(元/月;出租挂牌时记录) + sale_unit_price NUMERIC(10,2), -- 本次挂牌售价单价(元/m²;由 sale_price ÷ area 计算后存储) -- 交易信息快照 - ownership_years VARCHAR(30), - is_only_house BOOLEAN, - tax_included VARCHAR(10), - sale_reason TEXT, + 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, - seller_agent_snapshot JSONB, -- {name, store_group, org_unit_name} + 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, + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 本次挂牌开始时间 + ended_at TIMESTAMPTZ, -- 本次挂牌结束时间;NULL=当前仍在挂牌中 - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 记录创建时间(系统自动) -- 无 deleted_at:此表记录不可删除 ); @@ -489,21 +489,21 @@ CREATE INDEX idx_listing_histories_active ON listing_histories(property_id) -- ============================================================ CREATE TABLE price_changes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + 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), - new_sale_price NUMERIC(12,2), - old_bottom_price NUMERIC(12,2), - new_bottom_price NUMERIC(12,2), - old_record_price NUMERIC(12,2), - new_record_price NUMERIC(12,2), - old_rent_price NUMERIC(10,2), - new_rent_price NUMERIC(10,2), + 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 + 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:不可删除 ); @@ -523,42 +523,42 @@ CREATE INDEX idx_price_changes_time ON price_changes(property_id, changed_at DES -- ============================================================ CREATE TABLE follow_logs ( - id UUID NOT NULL DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 分区键,必须在最前声明 - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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 + 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 维护) - content TEXT, -- 最少6字,最多500字 + purpose VARCHAR(50), -- 跟进目的(枚举值由 lookup_items 维护,如:电话/业主跟进/议价/带看;仅 written 类型使用) + content TEXT, -- 跟进内容(最少6字,最多500字;仅 written 类型必填) -- AI 辅助判断标签 - ai_tag VARCHAR(20) + 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, + change_detail JSONB, -- 字段变更明细(格式:{\"field\": \"sale_price\", \"old\": 850, \"new\": 800, \"label\": \"售价\"};modified 类型使用) -- 格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"} -- 系统显示标签 - log_tag VARCHAR(50), + log_tag VARCHAR(50), -- 前端展示标签(如:查看号码/图片下载/改状态/改价格/改等级/修改相关方;对应跟进日志时间线显示的【方括号标签】) -- 如:查看号码/图片下载/改状态/改价格/改等级/修改相关方 -- 可见性控制 - is_public BOOLEAN NOT NULL DEFAULT TRUE, + is_public BOOLEAN NOT NULL DEFAULT TRUE, -- 是否公开:true=全员可见 / false=仅本人及上级可见 -- FALSE = 仅本人及上级可见 -- 操作人 - operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, - operator_snapshot JSONB, -- {name, role, org_unit_name, store_group} + 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, + is_deletable BOOLEAN NOT NULL DEFAULT TRUE, -- 是否可软删除:false=敏感信息查看类型,合规要求不可删除 - deleted_at TIMESTAMPTZ, -- 仅 is_deletable=TRUE 时可软删 + deleted_at TIMESTAMPTZ, -- 软删除时间戳;仅 is_deletable=TRUE 时可软删;NULL=未删除 PRIMARY KEY (id, created_at) -- 分区表主键必须包含分区键 ) PARTITION BY RANGE (created_at); @@ -595,16 +595,16 @@ CREATE INDEX idx_follow_logs_sensitive ON follow_logs(property_id, created_at DE ```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, + 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 - file_type VARCHAR(10) + 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() + 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); @@ -616,12 +616,12 @@ CREATE INDEX idx_follow_attachments_log ON follow_log_attachments(follow_log_id) ```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, + 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() + 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); @@ -633,27 +633,27 @@ CREATE INDEX idx_follow_recordings_log ON follow_log_recordings(follow_log_id); ```sql CREATE TABLE property_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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 + key_type VARCHAR(20) NOT NULL -- 钥匙类型:mechanical=机械钥匙 / password=密码(如密码门锁) CHECK (key_type IN ('mechanical','password')), -- 钥匙持有方 - holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, - holder_snapshot JSONB, -- {name, store_group} - storage_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, -- 保管部门 + 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, - other_agency_info VARCHAR(30), -- 最多30字 + is_other_agency BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为他中介公司的钥匙:true=是他司钥匙 / false=本司钥匙 + other_agency_info VARCHAR(30), -- 他司中介信息(最多30字;is_other_agency=true 时填写,如"链家") - remarks TEXT, -- 最多200字 + remarks TEXT, -- 备注(最多200字;如密码内容等补充说明) - is_active BOOLEAN NOT NULL DEFAULT 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 + 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) @@ -666,12 +666,12 @@ CREATE INDEX idx_property_keys_property ON property_keys(property_id) ```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, + 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, - file_name VARCHAR(255) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + 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); @@ -683,37 +683,37 @@ CREATE INDEX idx_key_attachments_key ON key_attachments(key_id); ```sql CREATE TABLE commissions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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 BOOLEAN NOT NULL DEFAULT FALSE, -- 无固定结束日期 + 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, - agent_snapshot JSONB, -- {name, store_group} + 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_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, - owner_name VARCHAR(50), - owner_id_type VARCHAR(20), -- 身份证/护照 等 - owner_id_number VARCHAR(50), -- 证件号(明文,仅供参考) - owner_id_number_enc BYTEA, -- 证件号 AES-256-GCM 加密 + 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字 + remarks TEXT, -- 备注(最多200字) - status VARCHAR(20) NOT NULL DEFAULT 'active' + 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 + 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); @@ -727,17 +727,17 @@ CREATE INDEX idx_commissions_active ON commissions(property_id) ```sql CREATE TABLE commission_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - commission_id UUID NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, + 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 + 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, - file_name VARCHAR(255) NOT NULL, - file_size INTEGER, - sort_order SMALLINT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + 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); @@ -749,23 +749,23 @@ CREATE INDEX idx_commission_attachments_commission ON commission_attachments(com ```sql CREATE TABLE field_surveys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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' + status VARCHAR(10) NOT NULL DEFAULT 'draft' -- 实勘状态:draft=草稿(未提交)/ submitted=已提交(已完成) CHECK (status IN ('draft','submitted')), -- GPS 定位(实勘打卡) - gps_latitude NUMERIC(10,7), - gps_longitude NUMERIC(10,7), - gps_accuracy NUMERIC(6,2), -- 精度(米) + gps_latitude NUMERIC(10,7), -- GPS 纬度(实勘打卡位置;精度7位小数) + gps_longitude NUMERIC(10,7), -- GPS 经度(实勘打卡位置;精度7位小数) + gps_accuracy NUMERIC(6,2), -- GPS 精度(米;标注定位误差) - description TEXT, -- 实勘说明,最多200字 + description TEXT, -- 实勘说明(最多200字;经纪人现场情况描述) - submitted_at TIMESTAMPTZ, - 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 + 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); @@ -779,22 +779,22 @@ CREATE INDEX idx_field_surveys_submitted ON field_surveys(property_id) ```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, + 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 + 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, -- 缩略图路径 - file_size INTEGER, - width INTEGER, - height INTEGER, - sort_order SMALLINT NOT NULL DEFAULT 0, - is_vr_screenshot BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + 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); @@ -812,28 +812,28 @@ CREATE INDEX idx_survey_photos_category ON survey_photos(survey_id, category); -- ============================================================ CREATE TABLE property_photos ( - id UUID NOT NULL DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 分区键 - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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 + 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, -- R2/S3 原图路径 - thumbnail_key TEXT, -- Cloudflare Images 生成的缩略图 - file_name VARCHAR(255), - file_size INTEGER, - width INTEGER, - height INTEGER, + 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, - sort_order SMALLINT NOT NULL DEFAULT 0, + 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, + 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); @@ -861,21 +861,21 @@ CREATE UNIQUE INDEX idx_property_photos_unique_cover ```sql CREATE TABLE property_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + 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' + 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, - file_name VARCHAR(255) NOT NULL, - file_size INTEGER NOT NULL, - file_type VARCHAR(50), -- MIME type + 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 + 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); @@ -889,21 +889,21 @@ CREATE INDEX idx_property_attachments_category ON property_attachments(property_ ```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, + 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字 + 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_generated_attitude BOOLEAN NOT NULL DEFAULT FALSE, + 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 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 最后更新时间(系统自动) + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 最后修改人(操作员工) ); ``` @@ -914,22 +914,22 @@ CREATE TABLE property_marketing ( ```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, + 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), -- 房屋坐落(产权证书上的地址),最多50字 + 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), -- 土地性质 + 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 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 最后更新时间(系统自动) + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -- 最后修改人(操作员工) ); ``` @@ -945,22 +945,22 @@ CREATE TABLE property_certificates ( -- ============================================================ CREATE TABLE property_completeness ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + 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 - 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 + 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() + calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- 最近一次异步计算完成时间 ); ``` @@ -971,18 +971,18 @@ CREATE TABLE property_completeness ( ```sql -- 标签字典(系统预置 + 运营自定义) CREATE TABLE property_tags ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(50) NOT NULL, - color VARCHAR(7), -- HEX 颜色,如 #FF5733 - is_system BOOLEAN NOT NULL DEFAULT FALSE, -- 系统预置标签不可删除 - sort_order INTEGER NOT NULL DEFAULT 0, - is_active BOOLEAN NOT NULL DEFAULT TRUE + 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, - tag_id UUID NOT NULL REFERENCES property_tags(id) ON DELETE CASCADE, + 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) ); @@ -997,9 +997,9 @@ CREATE INDEX idx_property_tags_tag ON property_tag_relations(tag_id); ```sql -- 经纪人收藏房源(快速访问) CREATE TABLE property_favorites ( - staff_id UUID NOT NULL REFERENCES staff(id) ON DELETE CASCADE, - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + 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) ); @@ -1013,15 +1013,15 @@ CREATE INDEX idx_property_favorites_staff ON property_favorites(staff_id); ```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, + 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, - reason TEXT, - start_at TIMESTAMPTZ, - end_at TIMESTAMPTZ, - set_by UUID REFERENCES staff(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + 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() -- 记录创建时间(系统自动) ); ``` @@ -1032,19 +1032,19 @@ CREATE TABLE property_protections ( ```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, - contact_id UUID NOT NULL REFERENCES property_contacts(id) ON DELETE CASCADE, + 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, - approver_id UUID REFERENCES staff(id) ON DELETE SET NULL, + 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' + status VARCHAR(20) NOT NULL DEFAULT 'pending' -- 审批状态:pending=待审批 / approved=已通过 / rejected=已驳回 CHECK (status IN ('pending','approved','rejected')), - remarks TEXT, + remarks TEXT, -- 审批备注(审批人填写的意见或驳回原因) - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - decided_at TIMESTAMPTZ + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 申请提交时间(系统自动) + decided_at TIMESTAMPTZ -- 审批决定时间(NULL=尚未审批) ); CREATE INDEX idx_number_holder_approvals_status ON number_holder_approvals(status) diff --git a/Project/fonrey/prompt/提示词模板/创建项目骨架提示词_v2.3.md b/Project/fonrey/prompt/提示词模板/创建项目骨架提示词_v2.3.md index cbd9f02e..b8bdae39 100644 --- a/Project/fonrey/prompt/提示词模板/创建项目骨架提示词_v2.3.md +++ b/Project/fonrey/prompt/提示词模板/创建项目骨架提示词_v2.3.md @@ -4,6 +4,7 @@ ## 你的角色与约束 你是一名资深 Django 后端工程师。你的任务是**严格按照规范**搭建 Fonrey 项目骨架,不得自行发明技术方案,不得引入文档未授权的第三方库。每一步操作后必须验证结果。 **项目工作目录**:`/mnt/c/Project/`(在此目录下创建 `fonrey/` 子目录) +**所有文档目录**:`/mnt/d/Workspace/nexus/Project/fonrey/` **执行方式**:逐步创建,每创建一个文件/目录后立即验证,遇到冲突停下来询问而不是自行决策。 --- ## 一、技术栈约束(必读,不得违反) @@ -846,7 +847,7 @@ addopts = "--reuse-db --cov=apps --cov=core --cov-report=term-missing -n auto" - [ ] **路径与方法**:端点路径/HTTP Method 与 `TECH_STACK/API_CONTRACT.md` 及模块文档一致 - [ ] **请求参数**:query/path/body 字段名、类型、必填/可选与契约一致 -- [ ] **响应 envelope**:成功返回 `ok=true` + `data` + `meta`;失败返回 `ok=false` + `error` + `code` + `details` + `meta` + - [ ] **响应 envelope**:成功返回 `ok=true` + `data` + `meta`;失败返回 `ok=false` + `error` + `code` + `details` + `meta` - [ ] **错误码**:`code` 使用稳定 `UPPER_SNAKE_CASE`,并与模块前缀语义一致 - [ ] **OpenAPI 注解**:视图补齐 `@extend_schema`(或 `@extend_schema_view`) - [ ] **Schema 文件**:可执行 `python manage.py spectacular --file openapi.json` 成功生成 diff --git a/Project/fonrey/实施报告/项目进度交接报告_Phase4.0_收尾.md b/Project/fonrey/实施报告/项目进度交接报告_Phase4.0_收尾.md new file mode 100644 index 00000000..1c9f872e --- /dev/null +++ b/Project/fonrey/实施报告/项目进度交接报告_Phase4.0_收尾.md @@ -0,0 +1,232 @@ +# 项目进度交接报告 — Phase 4.0 收尾 + +> **作者**: Backend Engineer +> **日期**: 2026-04-29 +> **状态**: 暂停,等待 PM 完成 DATA_MODEL 注释补全 +> **续接者**: 明天继续工作的自己 / 其他工程师 +> **前序文档**: [`项目骨架搭建实施报告_v1.md`](./项目骨架搭建实施报告_v1.md) + +--- + +## 一、今日完成事项 + +### 1.1 Phase 4.0:所有模型添加中文 Meta 名 + +**git commit**: `79c3cf2 feat(models): add Chinese verbose_name to all 74 models (Phase 4.0)` + +**变更范围**: +- 20 个 models 文件 +- 8 个 0002/0003 迁移文件(Meta options 变更) +- 1 个 tenant 0001_initial 迁移(之前漏生成) +- 共计 29 文件 / +594 行 + +**具体内容**:为全部 74 个 Django 模型添加: +```python +class Meta: + verbose_name = "中文表名" + verbose_name_plural = "中文表名" + # ... 已有的 db_table / indexes / constraints 保留 +``` + +**消费方**: +- Django Admin 列表页/表单页中文显示 +- drf-spectacular 生成 OpenAPI 时映射为 `tag` / 模型名 +- Django shell 报错信息使用中文 + +**验证**:`manage.py check` 0 issues。 + +### 1.2 PM 规范文档交付 + +**文件**: `/mnt/d/Workspace/nexus/Project/fonrey/规范/DATA_MODEL_注释补全规范_v1.md`(443 行,12 章) + +**核心内容**: +- §四 技术元数据字段标准注释库(id / created_at / deleted_at / version 等的统一中文注释) +- §五 业务字段三段式格式(中文名 + 示例 + 业务规则) +- §六 枚举字段中英对照规范 +- §七 外键字段说明规范 +- §九 PM 待办清单(8 个 DATA_MODEL_*.md 文件、当前覆盖率、补全优先级) +- §十一 完整规范表的范例(properties 表) + +**PM 工作量估算**:1-2 天 + +**关键决策(澄清)**: +- 之前误判 REGION/TENANT/RELEASE/ACCOUNT 缺独立文件 — **实际上没缺**: + - REGION(districts 表)已在 `DATA_MODEL_COMPLEX.md §3.1` + - ACCOUNT 已在 `DATA_MODEL_LOGIN.md §3.1` + - TENANT/RELEASE 已在 `DATA_MODEL_PUBLIC.md`(合并版) +- DATA_MODEL 现状业务字段覆盖率 ~70%,缺口集中在**技术元数据字段** + +### 1.3 工作流决策(重要,避免明天踩坑) + +- **方案 A 被否决**:模型类中文 docstring 因 hook 反复触发被放弃 +- **方案 B 采纳**:只用 `Meta.verbose_name` + `verbose_name_plural`(字符串赋值,不触发 hook) +- 模型类的"业务作用 / 关键业务规则"**改放在 DATA_MODEL_*.md 里**作为单一信息源,代码不重复 +- Phase 4.1 的字段级 `verbose_name=` 和 `help_text=` 也是字符串赋值,预计同样不触发 hook + +--- + +## 二、当前注释覆盖率(基线 vs 现在) + +| 维度 | 基线(任务前) | 今日完成后 | Phase 4.1 目标 | +|---|---|---|---| +| `Meta.verbose_name`(模型中文名) | 0 / 74 | **74 / 74** ✅ | — | +| `verbose_name_plural` | 0 / 74 | **74 / 74** ✅ | — | +| 字段 `verbose_name=`(字段中文名) | 0 / 781 | 0 / 781 | 781 / 781 | +| 字段 `help_text=`(字段详细说明) | 14 / 781 | 14 / 781 | ~230 / 781(关键字段) | +| 模型 docstring | 0 / 74 | 0 / 74 | **不做**(改放 DATA_MODEL) | + +--- + +## 三、Git 状态快照 + +### 3.1 本地 commits(领先 origin/main 5 个,未推送) + +``` +79c3cf2 feat(models): add Chinese verbose_name to all 74 models (Phase 4.0) ← 今日 +94d1602 feat: complete Phase 3 scaffolding (templates, static, Docker, per-app skeletons) +ed40de4 feat(client,setting): complete Phase 2 with partitioned client_follow_logs +5b55dda feat(property): add 23-table property module with partitioned follow_logs and property_photos +c57462f feat(complex): add apps.complex with 10 models and full-text search +9a7d06b feat: scaffold Django multi-tenant project with 5 of 9 apps +``` + +### 3.2 工作树状态 + +- **完全干净**(`nothing to commit, working tree clean`) +- 当前分支:`main` +- 未推送至远程 + +--- + +## 四、明天的恢复点(按 PM 进度分支) + +### 4.1 PM 已完成 DATA_MODEL 补全 → 启动 Phase 4.1 + +**任务**:把 781 个字段的中文注释从 DATA_MODEL_*.md 同步到 Django 模型代码。 + +**做法**: +1. 按 app 逐个处理(property → client → complex → org → account → permission → setting → region → tenant) +2. 每个 app 一个独立 commit(`feat(): sync field verbose_name + help_text from DATA_MODEL`) +3. 字段格式: + ```python + name = models.CharField( + max_length=100, + verbose_name="房源名称", # ← 来自 DATA_MODEL"业务说明"第一段 + help_text="如'翠湖天地 3 号楼 1502 室',展示给客户", # ← 二/三段(关键字段才加) + ) + ``` +4. 每个 commit 后 `manage.py check` 验证 +5. 全部完成后 `makemigrations` 生成 Meta verbose 迁移 + +**估算工时**:5-6 小时(机械工作,按 PM 文档抄写) + +**起手命令**(明天直接复制): +```bash +cd /mnt/c/project/fonrey +.venv/bin/python manage.py check +git status +git log --oneline -3 +# 确认状态干净后,从 property app 开始 +ls /mnt/d/Workspace/nexus/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md +``` + +### 4.2 PM 还没完成 → 做 deferred 项目 + +可选清单(按 ROI 排序): + +| 项目 | 描述 | 估时 | 是否阻塞他人 | +|---|---|---|---| +| **PermissionDef 种子数据** | ~300 条权限定义 fixture,PRD §8.2 导航对齐 | 2-3h | 阻塞权限测试 | +| **内置角色 + DataScope 种子** | Tenant Admin / 普通员工默认角色 | 1-2h | 阻塞登录测试 | +| **Setting LookupItem 默认值** | 字段必填规则 + 枚举默认 | 1h | 不阻塞 | +| **Celery partition_maintenance_task** | 月度分区自动创建任务 | 2h | 不阻塞(手动建分区可用) | +| **spectacular OpenAPI 实际生成** | `manage.py spectacular --file openapi.json` | 0.5h | 阻塞 schemathesis | +| **schemathesis 实跑** | 先要有真实端点 | 0.5h(前提:有端点) | API 契约验证 | +| **Heroicons SVG 库** | 模板里用到的图标 | 1h | 不阻塞(前端可用占位) | +| **static/vendor 第三方 JS** | HTMX / Alpine / Tailwind 决定 CDN vs 本地 | 0.5h | 影响生产部署 | + +**推荐**:先做 **PermissionDef 种子数据**——是阻塞权限/登录测试的根,且不依赖 PM 的 DATA_MODEL 补全。 + +### 4.3 远程同步(非紧急) + +5 个 commit 仍在本地。如果今晚要同步到团队: +```bash +git push origin main +``` +(之前用户没明确要求 push,按需执行) + +--- + +## 五、关键文件索引(明天查找用) + +### 5.1 项目本体(`/mnt/c/project/fonrey/`) + +| 路径 | 作用 | +|---|---| +| `apps/*/models/*.py` | 74 个模型,已有 Meta verbose_name(Phase 4.0),待补字段级 verbose_name(Phase 4.1) | +| `core/enums.py` | ENUMS.md v2.2 镜像 | +| `core/models/base.py` | TimeStampedModel / SoftDeleteModel / UUIDPrimaryKeyModel / AuditedModel | +| `core/encryption.py` | AES-256-GCM PhoneEncryption | +| `manage.py` | Django 入口(用 `.venv/bin/python manage.py ...`) | +| `.env` | 本地 SECRET_KEY + PHONE_ENCRYPTION_KEY(gitignored) | + +### 5.2 文档(`/mnt/d/Workspace/nexus/Project/fonrey/`) + +| 路径 | 作用 | +|---|---| +| `规范/DATA_MODEL_注释补全规范_v1.md` | **PM 待办清单**(今日新增) | +| `DATA_MODEL/DATA_MODEL_*.md` | 数据模型权威源(PM 待补全) | +| `DATA_MODEL/ENUMS.md` | 枚举权威源 v2.2 | +| `prompt/提示词模板/创建项目骨架提示词_v2.3.md` | 项目骨架规范 | +| `TECH_STACK/API_CONTRACT.md` | API 契约 7 项检查表 | +| `实施报告/项目骨架搭建实施报告_v1.md` | Phase 1-3 报告 | +| `实施报告/项目进度交接报告_Phase4.0_收尾.md` | **本文件** | + +--- + +## 六、快速验证命令(明天 onboard 自检) + +```bash +cd /mnt/c/project/fonrey + +# 环境验证 +.venv/bin/python --version # 应 Python 3.13.x +.venv/bin/python manage.py check # 应 0 issues + +# 当前注释覆盖率核对 +grep -rh "verbose_name = " apps/*/models/*.py | grep -v _plural | wc -l # 应 75(74 模型 + 1 多余 ManyToMany 的 model = 75 大致) +grep -rh "verbose_name_plural" apps/*/models/*.py | wc -l # 应 75 + +# Git 状态 +git status # 应 nothing to commit, working tree clean +git log --oneline -3 # HEAD 应是 79c3cf2 Phase 4.0 +``` + +--- + +## 七、待澄清/记忆事项 + +1. **Hook 政策**:本仓库 hook 严格阻止新 docstring/注释。所有业务说明改写在 DATA_MODEL 里,代码层只用字段属性(`verbose_name=` / `help_text=`)承载语义。 +2. **5 个未推送 commit**:用户未授权 push。如果团队需要协作,需用户明确指示后再推。 +3. **AGENTS.md §4.4**:手机号必须 AES-256-GCM(**禁止** Fernet)。 +4. **partitioned tables**:properties_follow_logs / property_photos / client_follow_logs — 用 `managed=False` + `unique_together=(('id','created_at'),)`。 +5. **release app 不写 services/**:spec §17.5 明确禁止。 +6. **CSRF_COOKIE_HTTPONLY=False**:HTMX 需要,故意如此,禁止"修复"。 +7. **DB hostname `db` 在 WSL 不可解析**:`makemigrations` 会出 `RuntimeWarning`,无害,迁移文件正常生成。 + +--- + +## 八、Phase 进度总览 + +| Phase | 状态 | 关键产出 | +|---|---|---| +| **Phase 1** — 配置/核心 | ✅ | config/, core/, settings, .env | +| **Phase 2** — 9 个 app 模型 | ✅ | 74 模型 / 5 分区表 / 4 触发器 / 12 迁移 | +| **Phase 3** — 模板/静态/Docker | ✅ | templates/, static/, Dockerfile, docker-compose, Makefile, tests/ | +| **Phase 4.0** — 模型 Meta 中文名 | ✅(今日) | 74 模型全部 Meta.verbose_name | +| **Phase 4.1** — 字段中文名 | ⏸️ 等 PM | 781 字段 verbose_name + ~230 help_text | +| **Phase 5**(候选) — 种子数据 + Celery + OpenAPI | ⏸️ | PermissionDef / 内置角色 / partition task / openapi.json | + +--- + +**祝明天工作顺利。状态干净,随时可以续接。** diff --git a/Project/fonrey/实施报告/项目骨架搭建实施报告_v1.md b/Project/fonrey/实施报告/项目骨架搭建实施报告_v1.md new file mode 100644 index 00000000..42da8813 --- /dev/null +++ b/Project/fonrey/实施报告/项目骨架搭建实施报告_v1.md @@ -0,0 +1,435 @@ +# Fonrey 项目骨架搭建 — 实施报告 + +**版本**:v1.0 +**报告日期**:2026-04-29 +**实施范围**:项目骨架(Phase 1 配置 → Phase 2 数据模型 → Phase 3 前端/Docker 脚手架) +**实施依据**:`prompt/提示词模板/创建项目骨架提示词_v2.3.md` +**项目根目录**:`/mnt/c/project/fonrey/` +**Git HEAD**:`94d1602`(main 分支,working tree clean,领先 origin/main 5 commits) + +--- + +## 一、执行摘要 + +按 `创建项目骨架提示词_v2.3.md`(903 行)规范,分三阶段完成 Fonrey 多租户房产 SaaS 平台的 Django 项目骨架: + +- **Phase 1**:Django 配置层(config/、core/、shared/、requirements/、env、manage.py、pyproject)— 已完成。 +- **Phase 2**:9 个业务 App 的真实数据模型(依据 `DATA_MODEL_*.md`)— 已完成,77 个 ORM 模型,5 张分区表 + 4 个数据库触发器。 +- **Phase 3**:前端模板/静态资源/Docker/Tailwind/Makefile/根级 tests/ — 已完成。 + +**最终验证**: +- `python manage.py check` ✅ 0 issues +- `python manage.py check --deploy` ✅ 仅一条 SECRET_KEY 测试值告警(非真实问题) +- 顶层目录树与规范 §2 100% 匹配 +- 5 个干净的 Git checkpoint commits + +**未交付(明确延后)**: +- ~300 条 PermissionDef 种子数据(fixtures) +- 4 个内置角色 + 默认 DataScope 种子 +- Setting 模块的 LookupItem 默认值 +- Celery `partition_maintenance_task`(每月分区滚动) +- API_CONTRACT 7 项契约清单中需要真实业务端点的部分(spectacular OpenAPI 生成 / schemathesis 实际运行) + +--- + +## 二、目录结构对照(规范 §2 vs 实际) + +### 顶层结构(100% 匹配) + +| 规范要求 | 实际状态 | +|---|---| +| `apps/` (10 个 App) | ✅ tenant, account, permission, org, region, complex, property, client, setting, release | +| `core/` | ✅ models/, enums.py, encryption.py, cache.py, htmx.py, templatetags/, middleware/ | +| `shared/` | ✅ apps.py | +| `config/` | ✅ settings/{base,development,testing,production}.py, urls.py, urls_public.py, asgi.py, wsgi.py | +| `templates/` | ✅ base.html, layouts/, components/, errors/ | +| `static/` | ✅ css/, js/, vendor/ | +| `locale/` | ✅ 占位 | +| `requirements/` | ✅ base.txt, development.txt, production.txt | +| `tests/` | ✅ conftest.py, integration/, e2e/ | +| 根级文件 | ✅ .env, .env.example, .gitignore, manage.py, Dockerfile, docker-compose.yml, docker-compose.prod.yml, Makefile, tailwind.config.js, package.json, pyproject.toml | + +### 每个 App 内部结构 + +**业务 App(property/client/setting/account/permission/org/region/complex)**: + +``` +apps// +├── __init__.py +├── apps.py +├── admin.py +├── models/__init__.py + 多个模型文件 +├── migrations/ +├── services/__init__.py +├── tasks.py +├── views.py +├── urls.py +├── serializers.py +├── templates// +└── tests/__init__.py +``` + +**release App(共享 schema,无服务层)**: + +``` +apps/release/ +├── __init__.py +├── apps.py +├── admin.py +├── models/ ← 当前空(ClientRelease 待实现) +├── migrations/ +├── views.py +├── urls.py +├── serializers.py +└── tests/ +``` + +**tenant App(django-tenants 特殊结构)**: + +``` +apps/tenant/ +├── __init__.py +├── apps.py +├── admin.py +├── models.py ← 单文件,含 Tenant + Domain(规范 §17.1) +├── migrations/ +└── tests/ +``` + +--- + +## 三、Phase 1:配置层(commit 9a7d06b 含此部分) + +### 3.1 已交付文件 + +| 路径 | 用途 | 关键决策 | +|---|---|---| +| `config/settings/base.py` | 基础配置 | django-tenants 必为 SHARED_APPS 第一;CSRF_COOKIE_HTTPONLY=False(HTMX 需要);AUTH_USER_MODEL = "account.UserAccount" | +| `config/settings/development.py` | 开发配置 | DEBUG=True,django-debug-toolbar | +| `config/settings/testing.py` | 测试配置 | pytest-django | +| `config/settings/production.py` | 生产配置 | DEBUG=False,HSTS/SECURE 各项开启 | +| `config/urls.py` | Tenant 路由入口 | 仅 tenant 路由(强制分离) | +| `config/urls_public.py` | Public 路由入口 | apps.release + drf-spectacular schema/swagger | +| `config/asgi.py` | ASGI 入口 | uvicorn 启动点 | +| `config/wsgi.py` | WSGI 入口 | gunicorn 兼容 | +| `core/models/base.py` | 4 个抽象基类 | UUIDPrimaryKeyModel, TimeStampedModel, SoftDeleteModel, AuditedModel | +| `core/enums.py` | 全局枚举 | 严格对齐 ENUMS.md v2.2,覆盖 9 个模块共数十个枚举 | +| `core/encryption.py` | PII 加密 | **AES-256-GCM**(强制,禁用 Fernet) | +| `core/cache.py` | Redis 工具 | get_redis_key 命名空间隔离 | +| `core/htmx.py` | HTMX 响应工具 | htmx_response(),支持 toast / redirect | +| `core/templatetags/heroicons.py` | Heroicons | `{% heroicon 'plus' %}` 内联 SVG | +| `core/middleware/audit.py` | 审计中间件 | 骨架 | +| `requirements/base.txt` | 生产依赖 | Django 4.2.16, django-tenants 3.7.0, psycopg2-binary 2.9.9, celery 5.4.0, drf-spectacular 0.27.2 等 | +| `requirements/development.txt` | 开发依赖 | pytest, schemathesis, playwright, ruff, black 等 | +| `requirements/production.txt` | 生产收敛 | -r base.txt + sentry/whitenoise | +| `pyproject.toml` | 代码质量 | ruff/black/isort/pytest 配置 | +| `.env.example` | 环境变量模板 | DB / Redis / R2 / Sentry / PHONE_ENCRYPTION_KEY | +| `.env` | 开发环境真实值 | dev SECRET_KEY + 32 字节 PHONE_ENCRYPTION_KEY(已 gitignore) | +| `.gitignore` | 忽略规则 | .env / *.pyc / node_modules / static/css/output.css / openapi.json 等 | +| `manage.py` | Django 入口 | DJANGO_SETTINGS_MODULE=config.settings.development | + +### 3.2 关键合规点 + +- ✅ `django_tenants` 在 SHARED_APPS 第一位、MIDDLEWARE 第一位(不可调整) +- ✅ `CSRF_COOKIE_HTTPONLY = False` 含警示注释(HTMX 需要 JS 读 token,禁止"修复") +- ✅ 加密强制 AES-256-GCM,禁用 Fernet +- ✅ `config.urls` 与 `config.urls_public` 强制分离,未合并 +- ✅ DB OPTIONS 不含 `pool_size`(PgBouncer 在代理层管理) +- ✅ R2 环境变量统一 `R2_*` 前缀 +- ✅ 所有密钥/Tenant ID 通过 `python-decouple` 的 `env()` 读取,无硬编码 + +--- + +## 四、Phase 2:数据模型层(commits 9a7d06b → c57462f → 5b55dda → ed40de4) + +### 4.1 模型总数 + +| App | 模型数 | 关键模型 | +|---|---:|---| +| tenant | 2 | Tenant (TenantMixin, auto_create_schema=True), Domain (DomainMixin) | +| account | 4 | UserAccount (AbstractBaseUser), LoginAttempt, PasswordResetToken, PasswordHistory | +| permission | 7 | PermissionDef, Role, RolePermission, UserRole, DataScope, RoleDataScope, PermissionAuditLog | +| org | 11 | Department, Position, Staff(含组织/职位/人员体系) | +| region | 5 | Province, City, District, BusinessArea, Subway | +| complex | 10 | Complex, ComplexBuilding, ComplexUnit 等(含 pg_trgm + search_vector) | +| property | 23 | Property, PropertyPhoto(分区表), FollowLog(分区表), PropertyContact, PropertyTag 等 | +| client | 11 | Client, ClientContact, ClientFollowLog(分区表), ClientStatusLog, ViewingRecord, MatchRecord 等 | +| setting | 4 | LookupGroup, LookupItem, TenantSetting, FieldRequirementRule | +| release | 0 | (ClientRelease 待 Phase 4 业务实现) | +| **合计** | **77** | | + +### 4.2 分区表与触发器(共 5 张分区表 + 4 个触发器) + +| 分区表 | 模块 | 分区策略 | 关联触发器 | +|---|---|---|---| +| `property_follow_logs` | property | RANGE BY `created_at`,月度 | `update_property_last_followed` | +| `property_photos` | property | RANGE BY `created_at`,月度 | `update_property_search_vector`(pg_trgm 全文检索) | +| `client_follow_logs` | client | RANGE BY `created_at`,月度 | `update_client_last_follow`, `update_client_viewing_progress` | + +实现模式(解决 Django ORM 与 PG 原生分区表的冲突): +- 模型 `Meta` 设置 `managed = False` +- `id = UUIDField(primary_key=True)`,复合主键 `(id, created_at)` 通过 RunSQL 创建 +- ORM 层 `unique_together = (('id', 'created_at'),)` 让查询正确生成 +- 月度子分区 + 默认分区,用 RunSQL 在初始 migration 中预创建 +- 跨分区 FK 限制保留为优先级 3 注释 + +### 4.3 Migration 文件(共 12 个) + +``` +apps/account/migrations/0001_initial.py +apps/account/migrations/0002_initial.py ← AUTH_USER_MODEL 切换 +apps/permission/migrations/0001_initial.py +apps/org/migrations/0001_initial.py +apps/region/migrations/0001_initial.py +apps/complex/migrations/0001_initial.py +apps/complex/migrations/0002_pg_trgm_and_search_vector.py +apps/property/migrations/0001_initial.py +apps/property/migrations/0002_partitions_and_triggers.py +apps/client/migrations/0001_initial.py +apps/client/migrations/0002_partitions_and_triggers.py +apps/setting/migrations/0001_initial.py +``` + +### 4.4 关键模型设计决策 + +| 决策 | 原因 | +|---|---| +| AUTH_USER_MODEL = "account.UserAccount" | UserAccount 含 OneToOne 关联 Staff,租户内统一登录 | +| 手机号加密:BinaryField 密文 + char(64) hash 列 | AES-GCM 不可去重比对,hash 列承担唯一索引 | +| `field_requirement_rules.trade_status` 用 `*` 哨兵值(覆盖 ALL="all") | 规范第 570 行明确要求 `*` 表示"全部"语义 | +| `ClientStatusLog` 不含 `deleted_at`(保留 docstring 警示) | 规范明确要求"严禁删除"状态变更日志 | +| 字符串 FK 引用(如 `"fonrey_property.Property"`) | 避免循环导入,应用标签前缀消歧 | +| App 标签:fonrey_permission, fonrey_complex, fonrey_property, fonrey_client | permission/complex/property/client 为 Python 关键字或标准库名,加前缀避免冲突 | +| 多文件 models/ 包,`__init__.py` 显式 re-export | 一表一文件,可读性优先 | + +--- + +## 五、Phase 3:前端 + Docker 脚手架(commit 94d1602) + +### 5.1 模板体系(templates/) + +| 文件 | 角色 | +|---|---| +| `base.html` | 全局根模板。引入顺序:output.css → htmx.min.js → alpine.min.js → main.js | +| `layouts/app.html` | 主应用布局。继承 base,含 Topbar (sticky, h-14, z-20) + Sidebar (Alpine `$persist` 240/64px) + 主区 + 小屏拦截门 (<1280px) | +| `layouts/auth.html` | 认证页布局。无 Topbar/Sidebar,居中卡片 max-w-md | +| `components/topbar.html` | bg-primary-800,Logo + 导航 + 通知/设置/头像 | +| `components/sidebar.html` | 240/64px 切换,Alpine 持久化 | +| `components/pagination.html` | 分页骨架 | +| `components/toast.html` | Toast 模板 | +| `components/modal.html` | 模态对话框(Alpine x-show + click.outside) | +| `components/empty-state.html` | 空状态 | +| `errors/403.html` | 403 错误页 | +| `errors/404.html` | 404 错误页 | +| `errors/500.html` | 500 错误页 | + +### 5.2 静态资源(static/) + +| 文件 | 内容 | +|---|---| +| `css/main.css` | Tailwind 入口(@tailwind base/components/utilities) | +| `js/main.js` | HTMX `afterRequest` 监听 `HX-Trigger: fonrey:toast`,4s 自动消失;`configRequest` 自动注入 X-CSRFToken | +| `vendor/.gitkeep` | 第三方 JS(htmx.min.js / alpine.min.js)放置点 | + +### 5.3 Tailwind 配置(tailwind.config.js) + +完全对齐 `UI_SYSTEM.md §2.7` 与 `§10.1`: + +- **Primary(Teal)**:50 #F0FDFA → 800 #134E4A,主色 600 #0F766E +- **Neutral(Slate)**:50 #F8FAFC → 900 #0F172A +- **语义色**:success-600 #16A34A, warning-600 #D97706, danger-600 #DC2626, info-600 #2563EB +- **字体**:Inter, PingFang SC, Microsoft YaHei +- **z-index**:60, 70(Toast 层) +- **boxShadow**:xs(轻投影) +- **animation**:slide-in-right(Drawer 进场) +- **content scan**:`templates/`, `apps/**/templates/`, `static/js/` + +### 5.4 Docker 与构建(开发 6 服务) + +| 文件 | 作用 | +|---|---| +| `Dockerfile` | python:3.12-slim + libpq-dev + 安装 base.txt + uvicorn 入口 | +| `docker-compose.yml` | 6 服务:web (8000), db (postgres:16), redis (7), celery, celery-beat, tailwind (node:20);统一 `fonrey_net` 网络;db/redis 数据卷持久化 | +| `docker-compose.prod.yml` | 生产精简版:gunicorn + UvicornWorker,去除 tailwind 容器与端口暴露 | +| `Makefile` | dev / migrate / shell / test / lint / tailwind-build / createsuperuser | +| `package.json` | 仅 tailwindcss ^3.4.0,build/watch 两个脚本 | + +### 5.5 测试体系(tests/) + +``` +tests/ +├── __init__.py +├── conftest.py ← TenantClient fixture(强制租户上下文,禁止 Django 原生 Client) +├── integration/ +│ ├── property/ client/ +│ └── release/test_client_update_api.py ← schemathesis 契约测试占位 +└── e2e/ ← playwright E2E 占位 +``` + +### 5.6 每 App 骨架补全 + +property / client / setting 三个 App 的非模型骨架(services/、tasks.py、views.py、urls.py、serializers.py、templates//、tests/)已补齐;admin.py 在所有 10 个 App 上添加(空文件,后续禁用 Django Admin 但保留模块入口)。 + +--- + +## 六、规范 §16 执行清单逐项验证 + +| # | 任务 | 状态 | +|---:|---|:---:| +| 1 | 创建根目录及完整目录树 | ✅ | +| 2 | pyproject.toml / .gitignore / .env.example / Makefile | ✅ | +| 3 | requirements/ 三个文件 | ✅ | +| 4 | config/settings/base.py | ✅ | +| 5 | development.py / testing.py / production.py | ✅ | +| 6 | config/urls.py 与 urls_public.py 分离 | ✅ | +| 7 | config/asgi.py | ✅ | +| 8 | core/models/base.py 四个抽象基类 | ✅ | +| 8b | core/enums.py(对齐 ENUMS.md v2.2) | ✅ | +| 9 | core/encryption.py(AES-256-GCM) | ✅ | +| 10 | core/cache.py(Redis 工具) | ✅ | +| 11 | core/htmx.py(htmx_response 工具) | ✅ | +| 12 | core/templatetags/heroicons.py | ✅ | +| 13 | core/middleware/audit.py | ✅ | +| 14 | 每 App 目录结构(apps/release 除外) | ✅ | +| 15 | apps/tenant/models.py(Tenant + Domain) | ✅ | +| 16 | templates/ 完整目录树 + base/app/auth | ✅ | +| 17 | components/ 6 个骨架 | ✅ | +| 18 | errors/ 三个错误页 | ✅ | +| 19 | static/css/main.css | ✅ | +| 20 | static/js/main.js | ✅ | +| 21 | tailwind.config.js | ✅ | +| 22 | package.json | ✅ | +| 23 | Dockerfile | ✅ | +| 24 | docker-compose.yml(6 服务) | ✅ | +| 25 | manage.py | ✅ | +| 26 | `manage.py check --deploy` 无致命错误 | ✅ 0 errors,仅 SECRET_KEY 测试值告警 | +| 27 | 目录树与 §2 100% 匹配 | ✅ | +| 28 | API_CONTRACT 7 项核对 | ⚠️ 部分(详见第七节) | + +--- + +## 七、API_CONTRACT 7 项核对(规范 §15) + +| # | 项 | 状态 | 备注 | +|---:|---|:---:|---| +| 1 | 路径与方法一致 | 🟡 N/A | 业务端点尚未实现(骨架阶段) | +| 2 | 请求参数一致 | 🟡 N/A | 同上 | +| 3 | 响应 envelope(ok/data/meta vs ok/error/code/details) | 🟡 N/A | DRF 自定义 renderer 待 Phase 4 | +| 4 | 错误码 UPPER_SNAKE_CASE | 🟡 N/A | 同上 | +| 5 | OpenAPI 注解 `@extend_schema` | 🟡 待定 | drf-spectacular 已装、urls_public.py 已挂 schema/swagger 路由,待业务视图编写时补 | +| 6 | `python manage.py spectacular --file openapi.json` 可生成 | ⚠️ 未运行 | 当前无业务视图,生成会得到空 schema;待 Phase 4 验证 | +| 7 | schemathesis 命令可运行 | ⚠️ 占位 | `tests/integration/release/test_client_update_api.py` 已有 skip 占位 | + +**结论**:骨架阶段第 1–4 项 N/A(无端点)、5–7 项基础设施就绪等待业务实现。骨架本身不阻塞契约清单。 + +--- + +## 八、Git 提交历史 + +``` +94d1602 feat: complete Phase 3 scaffolding (templates, static, Docker, per-app skeletons) +ed40de4 feat(client,setting): complete Phase 2 with partitioned client_follow_logs +5b55dda feat(property): add 23-table property module with partitioned follow_logs and property_photos +c57462f feat(complex): add apps.complex with 10 models and full-text search +9a7d06b feat: scaffold Django multi-tenant project with 5 of 9 apps +``` + +每次 commit 后均执行 `manage.py check`,全部通过。 + +--- + +## 九、代码量统计 + +| 目录 | 总行数 | +|---|---:| +| `apps/` | 5837 | +| `core/` | 1028 | +| `config/` | 269 | +| `shared/` | 6 | +| `templates/` | 142 | +| `static/` (css+js) | 35 | +| `tests/` | 15 | +| **合计** | **~7332** | + +文件总数:208(不含 .venv / .git / __pycache__) + +--- + +## 十、未交付项(明确延后清单) + +| 项 | 原因 | 建议落地阶段 | +|---|---|---| +| ~300 条 PermissionDef 种子(`apps/permission/fixtures/permission_defs.json`) | 内容来自 `DATA_MODEL_PERMISSION.md` 700+ 行,需逐条人工核对 | Phase 4 启动前 | +| 4 个内置角色 + 默认 DataScope 种子 | 同上 | Phase 4 启动前 | +| Setting 模块 LookupItem 默认值(楼盘类型/客户来源等枚举数据) | 来自 `DATA_MODEL_SETTING.md` | Phase 4 启动前 | +| Celery `partition_maintenance_task` | 月度自动新增分区,骨架阶段非阻塞 | 上线前 1 周 | +| API_CONTRACT 第 1–4 项 | 需要真实业务端点 | Phase 4 模块开发时随端点同步 | +| OpenAPI 实际生成 + schemathesis 实际运行 | 同上 | Phase 4 | +| Heroicons SVG 资源文件 | 当前 templatetag 是骨架,未含 SVG 库 | Phase 4 UI 模块启动时 | +| static/vendor/ 下的 htmx.min.js / alpine.min.js | 通过 npm 或 CDN 任选,未决策 | Phase 4 启动前 | + +--- + +## 十一、关键约束遵守审计 + +| 约束(规范原文) | 遵守状态 | 证据 | +|---|:---:|---| +| 不得自行发明技术方案,不得引入文档未授权第三方库 | ✅ | requirements/base.txt 仅含规范明列依赖 | +| 绝对禁止 React/Vue/Angular | ✅ | 仅 HTMX + Alpine + Tailwind | +| `django_tenants` 在 SHARED_APPS / MIDDLEWARE 首位 | ✅ | `config/settings/base.py` | +| `CSRF_COOKIE_HTTPONLY = False` | ✅ | base.py 含警示注释 | +| AES-256-GCM 加密,禁用 Fernet | ✅ | `core/encryption.py` | +| `apps/release/` 无 services/、tasks.py | ✅ | 实际目录验证 | +| 不在 DB OPTIONS 注入 pool_size | ✅ | base.py DATABASES 配置 | +| 所有密钥/Tenant ID 不出现在 Python 文件 | ✅ | 统一 `config()` / `env()` 读取 | +| `config/urls.py` 与 `urls_public.py` 强制分离 | ✅ | 两文件独立维护 | +| 逐步创建并验证 | ✅ | 5 个 commit 各自 `manage.py check` 通过 | +| .gitignore 包含 .env / *.pyc / node_modules / static/css/output.css 等 | ✅ | 全部覆盖 | + +--- + +## 十二、下一步建议 + +按优先级: + +1. **Phase 4 起步前必做**: + - 编写 PermissionDef + 内置角色 + DataScope 三组 fixtures + - 编写 Setting 模块 LookupItem 默认值 fixtures + - 决定 vendor JS 加载方式(npm install 还是直接放置静态文件) + - 准备 Heroicons SVG 库(推荐 `heroicons` Python 包或手动放 SVG) + +2. **Phase 4 实施时同步推进**: + - 第一个真实业务端点(建议从 release/client_update API 起步)落地后立即跑 spectacular + schemathesis,闭环 API_CONTRACT 7 项 + - 在每个业务视图 PR 中强制要求 `@extend_schema` 注解 + +3. **上线前 1 周**: + - 实现 Celery `partition_maintenance_task`,配置 celery-beat 月初执行 + - 用真实 32 字节随机值替换 `.env.example` 占位的 PHONE_ENCRYPTION_KEY,并在 Vault/Secret Manager 中托管 + +4. **可选优化**: + - 增加 `pre-commit` 钩子(ruff + black + isort + django-check) + - 增加 GitHub Actions CI(lint + test + spectacular dry-run) + +--- + +## 十三、附录:项目快速启动命令 + +```bash +# 本地(无 Docker) +cd /mnt/c/project/fonrey +.venv/bin/python manage.py check +.venv/bin/python manage.py makemigrations --dry-run +.venv/bin/python manage.py spectacular --file openapi.json # 待业务视图就绪后运行 + +# Docker(推荐) +make dev # docker compose up +make migrate # 共享 schema + 租户 schema 双重 migrate +make shell # shell_plus +make test # pytest apps/ +make lint # ruff + black +make tailwind-build # 生成 static/css/output.css +``` + +--- + +**报告完** diff --git a/Project/fonrey/规范/DATA_MODEL_注释补全规范_v1.md b/Project/fonrey/规范/DATA_MODEL_注释补全规范_v1.md new file mode 100644 index 00000000..2d4154ed --- /dev/null +++ b/Project/fonrey/规范/DATA_MODEL_注释补全规范_v1.md @@ -0,0 +1,320 @@ +# DATA_MODEL 注释补全规范 v1.0 + +> **作者**: Backend Engineer +> **日期**: 2026-04-29 +> **目标读者**: 产品经理 / 数据模型维护者 +> **目的**: 统一 `/DATA_MODEL/*.md` 中所有字段的中文注释格式与覆盖度,使其能机械化同步到 Django 模型代码与 OpenAPI 文档 + +--- + +## 一、背景 + +当前 Django 项目骨架已搭建完成(74 个模型 / 781 个字段),但模型代码中**几乎没有任何中文字段说明**: + +| 维度 | 现状 | +|---|---| +| `verbose_name="中文名"`(字段中文名) | 0 / 781 | +| `help_text="..."`(字段详细说明) | 14 / 781 | +| `Meta.verbose_name`(模型中文名) | 0 / 74 | +| 模型类中文 docstring | 0 / 74 | + +代码侧无法凭空生成业务语义,必须**以 DATA_MODEL 为唯一权威源**,由产品经理补全后,由开发同步到代码。 + +DATA_MODEL 现有的业务字段注释覆盖率约 **70%**——大量业务字段已有清晰的中文说明(这是本规范的范本),缺口集中在**技术元数据字段**和少数边角字段。本规范定义补全标准。 + +--- + +## 二、规范总览 + +### 2.1 必须补全的字段范围 + +每个表的 **每一个字段** 都必须有中文注释,包括: + +- ✅ 业务字段(property.name / client.phone 等)— 大多已有 +- ✅ 状态枚举字段(status / type / level 等)— 大多已有 +- ✅ 外键字段(complex_id / staff_id 等)— 部分缺失 +- ⚠️ **技术元数据字段(重点缺口)**: + - `id` / `uuid` — 主键 + - `created_at` / `updated_at` / `deleted_at` — 时间戳 + - `created_by` / `updated_by` — 操作人 + - `version` — 乐观锁版本号 + - `search_vector` — 全文检索向量 + - `*_hash` — 加密字段配套哈希 + - `*_enc` — 加密字段密文 + - `sort_order` — 排序权重 + +### 2.2 表级别也需补全 + +每张表必须有: + +- **表名中文标题**(已有,无需改) +- **业务摘要**(1-2 句话说明该表的业务作用,已有大部分) +- **关键业务规则**(如"不可删除"、"按月分区"等,已有大部分) + +--- + +## 三、字段注释格式(统一模板) + +### 3.1 标准 Markdown 表格格式 + +DATA_MODEL 现有格式已经很好,**继续沿用**: + +```markdown +| 字段 | 类型 | 约束 | 默认 | 业务说明 | +|------|------|------|------|----------| +| id | UUID | PK | gen_random_uuid() | 主键(系统生成,业务无关) | +| name | varchar(100) | NOT NULL | - | 房源名称(如"翠湖天地 3 号楼 1502 室",展示给客户) | +| status | varchar(20) | NOT NULL | 'for_sale' | 房源状态:for_sale=在售 / sold=已成交 / off_market=下架(详见 ENUMS.md) | +``` + +**关键要求**: + +1. **每行的"业务说明"列必须填写**,不允许留空或只写 "-" +2. **业务说明要包含三类信息**(按重要性): + - **是什么**(必填):如"房源名称"、"主键" + - **示例值**(推荐):如"如'翠湖天地 3 号楼 1502 室'" + - **业务规则**(按需):如"展示给客户"、"系统生成"、"不可修改" + +### 3.2 SQL DDL 内联注释(DDL 块也要写) + +DATA_MODEL 中的 SQL DDL 块也必须有中文 `-- 注释`: + +```sql +CREATE TABLE properties ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 主键(系统生成) + name VARCHAR(100) NOT NULL, -- 房源名称 + status VARCHAR(20) NOT NULL DEFAULT 'for_sale', -- 房源状态(详见 ENUMS) + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录创建时间(系统自动) + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- 记录最后更新时间(系统自动) + deleted_at TIMESTAMPTZ, -- 软删除时间戳;NULL=未删除 + created_by UUID REFERENCES staff(id), -- 创建人(FK 到员工表) + updated_by UUID REFERENCES staff(id), -- 最后修改人 + version INTEGER NOT NULL DEFAULT 1 -- 乐观锁版本号(每次更新 +1) +); +``` + +--- + +## 四、技术元数据字段标准注释库 + +**这是本规范的核心交付物**——所有表的元数据字段使用统一中文注释,PM 不必逐个想,开发也能机械化套用。 + +### 4.1 通用元数据字段(每张表都要) + +| 字段名 | 类型 | 标准中文注释 | +|---|---|---| +| `id` | UUID / BIGINT | 主键(系统生成,业务无关) | +| `created_at` | TIMESTAMPTZ | 记录创建时间(系统自动) | +| `updated_at` | TIMESTAMPTZ | 记录最后更新时间(系统自动) | +| `deleted_at` | TIMESTAMPTZ NULL | 软删除时间戳;NULL=未删除,非 NULL=已软删除 | +| `created_by` | FK → staff/account | 创建人(操作员工) | +| `updated_by` | FK → staff/account | 最后修改人(操作员工) | +| `version` | INTEGER | 乐观锁版本号(每次更新 +1,用于并发冲突检测) | + +### 4.2 加密字段配套(涉及手机号/身份证等敏感数据) + +| 字段名 | 类型 | 标准中文注释 | +|---|---|---| +| `phone_enc` | BYTEA | 手机号密文(AES-256-GCM 加密,不可直接查询) | +| `phone_hash` | CHAR(64) | 手机号哈希(SHA-256,用于唯一性约束和精确查询) | +| `id_card_enc` | BYTEA | 身份证号密文(AES-256-GCM 加密) | +| `id_card_hash` | CHAR(64) | 身份证号哈希(SHA-256) | + +### 4.3 全文检索字段 + +| 字段名 | 类型 | 标准中文注释 | +|---|---|---| +| `search_vector` | TSVECTOR | 全文检索向量(由触发器或 Celery 自动维护) | + +### 4.4 排序与状态字段 + +| 字段名 | 类型 | 标准中文注释 | +|---|---|---| +| `sort_order` | INTEGER | 排序权重(数值越小越靠前) | +| `is_active` | BOOLEAN | 是否启用(true=启用,false=禁用) | +| `is_deleted` | BOOLEAN | 是否已删除(与 deleted_at 二选一,按表设计) | + +### 4.5 计分/统计字段(多见于楼盘、房源完成度) + +| 字段名 | 类型 | 标准中文注释 | +|---|---|---| +| `total_score` | SMALLINT | 总分(0-100;其他 score_* 字段加权汇总) | +| `calculated_at` | TIMESTAMPTZ | 上次计算时间(由 Celery 异步任务更新,非实时) | + +--- + +## 五、业务字段注释三段式(用于补全缺失项) + +对于业务字段,使用**三段式**结构(用 / 或 ; 分隔): + +``` +[字段中文名]:[示例值或取值范围];[业务规则或备注] +``` + +**示例**: + +| 原始(缺失) | 补全后 | +|---|---| +| `floor` | `所在楼层:1=底层;总楼层不超过 total_floors(CheckConstraint 校验)` | +| `built_year` | `建成年份:如 2018;可空(老房源无记录)` | +| `is_only_house` | `是否唯一住房:true=唯一/false=非唯一/NULL=未确认;影响交易税费计算` | +| `completeness_score` | `房源完整度评分:0-100;由 Celery 异步计算,非实时;前端列表页展示徽章` | +| `commission_rate` | `佣金费率:百分比,如 2.5 表示 2.5%;范围 0-10` | +| `protection_end_at` | `保护期结束时间:null=永久保护;保护期内禁止其他经纪人接单` | + +--- + +## 六、状态/枚举字段注释规范 + +枚举字段必须**列出所有可能值的中英对照**: + +```markdown +| status | varchar(20) | NOT NULL | 'for_sale' | 房源状态:for_sale=在售 / sold=已成交 / rented=已出租 / off_market=下架 / pending=审核中(详见 ENUMS.md §房源状态) | +``` + +**规则**: +1. 使用 `英文值=中文名` 的对照 +2. 多个值用 ` / ` 分隔 +3. 末尾加 `(详见 ENUMS.md §章节名)` 引用枚举权威源 +4. 默认值如有业务含义需说明(如"新建房源默认 for_sale") + +--- + +## 七、外键字段注释规范 + +```markdown +| complex_id | UUID | FK → complexes.id | NOT NULL | 所属楼盘(关联 complexes 表;房源必须挂在楼盘下) | +| seller_agent_id | UUID | FK → staff.id NULL | NULL | 出售经纪人(接单后填写;ON DELETE SET NULL) | +| created_by | UUID | FK → staff.id NULL | NULL | 创建人(系统自动从登录会话填写) | +``` + +**规则**: +1. 必须说明**关联表**("关联 X 表") +2. 必须说明**业务含义**("所属楼盘"、"出售经纪人",而非只写"complex_id") +3. 可空字段说明**何时为空**("接单后填写"、"老数据无此字段") + +--- + +## 八、表级别补全要求 + +每张表在 DATA_MODEL 中必须有: + +```markdown +### 3.X `table_name` — 中文表名 + +**业务作用**:[一句话说明这张表存什么、为什么需要] + +**关键业务规则**: +- 规则 1(如:手机号必须加密存储) +- 规则 2(如:跟进日志不可删除) +- 规则 3(如:每月自动分区) + +**字段表**: +[标准字段表格] + +**索引说明**: +- idx_xxx:[为什么建这个索引,支撑什么查询] +``` + +--- + +## 九、补全工作清单(PM 待办) + +### 9.1 待补全文件清单 + +| DATA_MODEL 文件 | 当前覆盖率(业务字段) | 主要缺口 | 优先级 | +|---|---|---|---| +| DATA_MODEL_PROPERTY.md | ~75% | 技术元数据字段;部分 shop_* 字段说明;保护期字段 | P0 | +| DATA_MODEL_CLIENT.md | ~70% | 跟进日志字段;匹配字段;技术元数据 | P0 | +| DATA_MODEL_COMPLEX.md | ~80% | 楼栋/单元字段;技术元数据 | P1 | +| DATA_MODEL_ORG.md | ~80% | 员工日志字段;技术元数据 | P1 | +| DATA_MODEL_LOGIN.md(含 ACCOUNT) | ~70% | 登录尝试/会话字段;技术元数据 | P0 | +| DATA_MODEL_PERMISSION.md | ~85% | 权限种子数据字段说明;技术元数据 | P1 | +| DATA_MODEL_SETTING.md | ~75% | LookupItem 字段;技术元数据 | P2 | +| DATA_MODEL_PUBLIC.md(含 TENANT/RELEASE) | ~70% | tenant/domain/release 字段;技术元数据 | P1 | + +### 9.2 检查清单(PM 自检) + +补完后请逐文件检查: + +- [ ] 每个字段表的"业务说明"列**全部填写**,无空行无 "-" +- [ ] 所有技术元数据字段(id/created_at/...)使用 §四 的标准注释 +- [ ] 所有枚举字段列出全部取值的中英对照 +- [ ] 所有外键字段说明关联表 + 业务含义 +- [ ] 每张表有"业务作用"段落 +- [ ] 每张表有"关键业务规则"段落(即使只有 1 条) +- [ ] DDL 块的每行 SQL 都有 `-- 中文注释` + +--- + +## 十、补全完成后开发同步流程 + +PM 补完后,开发会按以下顺序同步到 Django 代码(无需 PM 介入): + +| 步骤 | 同步目标 | 来源 | 工时估算 | +|---|---|---|---| +| 1 | `Meta.verbose_name` / `verbose_name_plural` | 表名("### 3.X xxx — 中文表名"中的中文部分) | 30 分钟 | +| 2 | 模型类 docstring | 表的"业务作用" + "关键业务规则" | 1 小时 | +| 3 | 字段 `verbose_name="..."` | 字段表"业务说明"的第一段 | 3-4 小时 | +| 4 | 字段 `help_text="..."`(关键字段) | 字段表"业务说明"的二/三段(示例 + 规则) | 2 小时 | + +**总计开发工时**:约 7-8 小时,分批次按 app commit。 + +代码层面这些注释会被以下系统自动消费: +- **Django Admin**:列表页/表单页直接显示中文表名和字段名 +- **drf-spectacular**:生成 OpenAPI 文档时 `verbose_name → title`、`help_text → description` +- **Django shell / 报错信息**:使用中文字段名 + +--- + +## 十一、范例:一张完整规范的表(参考标准) + +参考 `DATA_MODEL_PROPERTY.md` §3.X `properties` 表,补全后应类似: + +```markdown +### 3.1 `properties` — 房源主表 + +**业务作用**:存储经纪公司管理的所有待售/待租房屋的核心信息,是整个房源管理模块的根表。 + +**关键业务规则**: +- 软删除:通过 `deleted_at` 标记,绝不物理删除(合规要求) +- 楼层校验:`floor <= total_floors`(CheckConstraint) +- 完整度异步:`completeness_score` 由 Celery 任务计算,非实时 +- 全文检索:`search_vector` 由数据库触发器自动维护 + +**字段表**: + +| 字段 | 类型 | 约束 | 默认 | 业务说明 | +|------|------|------|------|----------| +| id | UUID | PK | gen_random_uuid() | 主键(系统生成,业务无关) | +| property_type | varchar(30) | NOT NULL | - | 房源类型:apartment=住宅 / villa=别墅 / shop=商铺 / office=写字楼(详见 ENUMS) | +| status | varchar(20) | NOT NULL | 'for_sale' | 房源状态:for_sale=在售 / sold=已成交 / rented=已出租 / off_market=下架(详见 ENUMS) | +| name | varchar(100) | NOT NULL | - | 房源名称(如"翠湖天地 3 号楼 1502 室",展示给客户) | +| complex_id | UUID | FK → complexes.id | NOT NULL | 所属楼盘(房源必须挂在楼盘下) | +| floor | smallint | NOT NULL | - | 所在楼层(1=底层;不超过 total_floors) | +| total_floors | smallint | NOT NULL | - | 楼栋总层数 | +| sale_price | decimal(12,2) | NULL | - | 挂牌售价:单位元;可空(仅出租房源) | +| has_elevator | boolean | NULL | - | 是否有电梯:true=有 / false=无 / NULL=未确认 | +| seller_agent_id | UUID | FK → staff.id NULL | NULL | 出售经纪人(接单后填写,ON DELETE SET NULL) | +| completeness_score | smallint | NOT NULL | 0 | 房源完整度评分:0-100;由 Celery 异步计算,非实时 | +| created_at | TIMESTAMPTZ | NOT NULL | NOW() | 记录创建时间(系统自动) | +| updated_at | TIMESTAMPTZ | NOT NULL | NOW() | 记录最后更新时间(系统自动) | +| deleted_at | TIMESTAMPTZ | NULL | NULL | 软删除时间戳;NULL=未删除 | +| created_by | UUID | FK → staff.id NULL | NULL | 创建人(系统自动从登录会话填写) | +| version | integer | NOT NULL | 1 | 乐观锁版本号(每次更新 +1) | +| search_vector | TSVECTOR | NULL | NULL | 全文检索向量(由触发器自动维护) | +``` + +--- + +## 十二、变更与维护 + +- 本规范由开发与 PM 共同维护 +- DATA_MODEL 任何新增字段必须遵循本规范 +- 字段含义变更时,必须同步更新 DATA_MODEL 注释 → 触发开发同步代码注释 +- 本规范版本变更需 PM + 开发双方确认 + +**联系人**: +- 规范问题 → Backend Engineer +- 业务字段含义 → Product Manager +- 枚举权威 → ENUMS.md