Sync DATA_MODEL_CLIENT.md field-level Chinese annotations to Django models across 11 client tables (Client, ClientContact, ClientRequirement, ClientSchoolPreference, ClientFavoriteFolder, ClientFolderItem, ClientFollowLog, ClientFollowLogAttachment, ClientViewing, ClientPropertyMatch, ClientStatusLog). Pre-existing docstrings retained on ClientFollowLog (partitioned parent treated as unmanaged) and ClientStatusLog (immutable audit log).
319 lines
9.2 KiB
Python
319 lines
9.2 KiB
Python
from django.contrib.postgres.fields import ArrayField
|
||
from django.db import models
|
||
|
||
from core.enums import (
|
||
ClientBuildingAgeRange,
|
||
ClientContactGender,
|
||
ClientDecoration,
|
||
ClientFloorPreference,
|
||
ClientOrientation,
|
||
ClientRequirementType,
|
||
)
|
||
from core.models.base import UUIDPrimaryKeyModel
|
||
|
||
|
||
class ClientContact(UUIDPrimaryKeyModel):
|
||
client = models.ForeignKey(
|
||
"fonrey_client.Client",
|
||
on_delete=models.CASCADE,
|
||
related_name="contacts",
|
||
verbose_name="所属客源",
|
||
help_text="联系人随客源级联删除",
|
||
)
|
||
sort_order = models.SmallIntegerField(
|
||
default=0,
|
||
verbose_name="排序顺序",
|
||
help_text="sort_order=0 为主联系人,姓名用于客源姓名显示",
|
||
)
|
||
name = models.CharField(
|
||
max_length=50,
|
||
verbose_name="联系人姓名",
|
||
)
|
||
gender = models.CharField(
|
||
max_length=10,
|
||
choices=ClientContactGender.choices,
|
||
default=ClientContactGender.MALE,
|
||
verbose_name="性别",
|
||
help_text="male=先生 / female=女士",
|
||
)
|
||
|
||
phone_enc = models.BinaryField(
|
||
verbose_name="手机号(加密)",
|
||
help_text="AES-256-GCM 加密手机号(电话1)",
|
||
)
|
||
phone_hash = models.CharField(
|
||
max_length=64,
|
||
verbose_name="手机号哈希",
|
||
help_text="SHA-256 哈希(重复检测)",
|
||
)
|
||
phone_country_code = models.CharField(
|
||
max_length=10,
|
||
default="+86",
|
||
verbose_name="国际区号",
|
||
)
|
||
phone_is_invalid = models.BooleanField(
|
||
default=False,
|
||
verbose_name="号码是否无效",
|
||
help_text="标记无效后该号码不再参与重复检测",
|
||
)
|
||
|
||
phone2_enc = models.BinaryField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="备用电话2(加密)",
|
||
)
|
||
phone2_hash = models.CharField(
|
||
max_length=64,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="备用电话2哈希",
|
||
help_text="SHA-256,用于重复检测",
|
||
)
|
||
|
||
wechat = models.CharField(
|
||
max_length=100,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="微信号",
|
||
)
|
||
qq = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="QQ号",
|
||
)
|
||
remarks = models.CharField(
|
||
max_length=200,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="联系人备注",
|
||
help_text="最多200字",
|
||
)
|
||
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
updated_at = models.DateTimeField(
|
||
auto_now=True,
|
||
verbose_name="最后更新时间",
|
||
)
|
||
deleted_at = models.DateTimeField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="删除时间",
|
||
help_text="软删除时间戳;NULL=未删除(不影响客源本身)",
|
||
)
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_client_contacts",
|
||
verbose_name="创建人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "client_contacts"
|
||
verbose_name = "客源联系人"
|
||
verbose_name_plural = "客源联系人"
|
||
indexes = [
|
||
models.Index(fields=["phone_hash"], name="idx_cc_phone_hash"),
|
||
models.Index(fields=["phone2_hash"], name="idx_cc_phone2_hash"),
|
||
models.Index(fields=["client"], name="idx_cc_client"),
|
||
]
|
||
|
||
|
||
class ClientRequirement(UUIDPrimaryKeyModel):
|
||
client = models.ForeignKey(
|
||
"fonrey_client.Client",
|
||
on_delete=models.CASCADE,
|
||
related_name="requirements",
|
||
verbose_name="所属客源",
|
||
help_text="需求随客源级联删除",
|
||
)
|
||
requirement_type = models.CharField(
|
||
max_length=20,
|
||
choices=ClientRequirementType.choices,
|
||
verbose_name="需求类型",
|
||
help_text="second_hand=二手 / new_house=新房 / rental=租房",
|
||
)
|
||
is_primary = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否主需求",
|
||
help_text="用于列表展示",
|
||
)
|
||
|
||
budget_min = models.DecimalField(
|
||
max_digits=12,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="最低预算",
|
||
help_text="单位:万元/元,依据需求类型",
|
||
)
|
||
budget_max = models.DecimalField(
|
||
max_digits=12,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="最高预算",
|
||
)
|
||
area_min = models.DecimalField(
|
||
max_digits=8,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="最小面积",
|
||
help_text="单位:㎡",
|
||
)
|
||
area_max = models.DecimalField(
|
||
max_digits=8,
|
||
decimal_places=2,
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="最大面积",
|
||
help_text="单位:㎡",
|
||
)
|
||
|
||
bedroom_counts = ArrayField(
|
||
models.SmallIntegerField(),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="可接受卧室数",
|
||
help_text="多选,如 [2,3]",
|
||
)
|
||
floor_preferences = ArrayField(
|
||
models.CharField(max_length=20, choices=ClientFloorPreference.choices),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="楼层偏好",
|
||
help_text="多选:no_first=不要一层 / low=低楼层 / mid=中楼层 / high=高楼层 / no_top=不要顶层",
|
||
)
|
||
orientations = ArrayField(
|
||
models.CharField(max_length=10, choices=ClientOrientation.choices),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="朝向偏好",
|
||
help_text="多选:east=东 / south=南 / west=西 / north=北",
|
||
)
|
||
decorations = ArrayField(
|
||
models.CharField(max_length=10, choices=ClientDecoration.choices),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="装修偏好",
|
||
help_text="多选(枚举同 properties.decoration)",
|
||
)
|
||
building_age_ranges = ArrayField(
|
||
models.CharField(max_length=20, choices=ClientBuildingAgeRange.choices),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="楼龄偏好",
|
||
help_text="多选:within_5y / 5_10y / 10_15y / 15_20y / over_20y",
|
||
)
|
||
|
||
intent_district_ids = ArrayField(
|
||
models.UUIDField(),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="意向行政区",
|
||
help_text="行政区 ID 数组",
|
||
)
|
||
intent_business_area_ids = ArrayField(
|
||
models.UUIDField(),
|
||
blank=True,
|
||
default=list,
|
||
verbose_name="意向商圈",
|
||
help_text="商圈 ID 数组",
|
||
)
|
||
intent_complex_names = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="意向小区",
|
||
help_text="文本,逗号分隔,最多500字",
|
||
)
|
||
transportation = models.CharField(
|
||
max_length=50,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="交通要求",
|
||
help_text="最多50字",
|
||
)
|
||
intent_school_names = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="意向学校",
|
||
help_text="文本,逗号分隔",
|
||
)
|
||
school_enrollment_date = models.DateField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="入学时间",
|
||
help_text="月份精度,取该月1日存储",
|
||
)
|
||
traffic_preference = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="交通备注",
|
||
)
|
||
requirement_notes = models.CharField(
|
||
max_length=200,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="需求备注",
|
||
help_text="最多200字",
|
||
)
|
||
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
updated_at = models.DateTimeField(
|
||
auto_now=True,
|
||
verbose_name="最后更新时间",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "client_requirements"
|
||
verbose_name = "客源需求"
|
||
verbose_name_plural = "客源需求"
|
||
indexes = [
|
||
models.Index(fields=["client"], name="idx_creq_client"),
|
||
models.Index(fields=["requirement_type", "client"], name="idx_creq_type"),
|
||
models.Index(fields=["budget_min", "budget_max"], name="idx_creq_budget"),
|
||
models.Index(fields=["area_min", "area_max"], name="idx_creq_area"),
|
||
]
|
||
|
||
|
||
class ClientSchoolPreference(UUIDPrimaryKeyModel):
|
||
requirement = models.ForeignKey(
|
||
ClientRequirement,
|
||
on_delete=models.CASCADE,
|
||
related_name="school_preferences",
|
||
verbose_name="所属需求",
|
||
help_text="意向学校随需求级联删除",
|
||
)
|
||
school_id = models.UUIDField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="学校ID",
|
||
help_text="从学校表选择,允许为 NULL(自由输入)",
|
||
)
|
||
school_name = models.CharField(
|
||
max_length=100,
|
||
verbose_name="学校名称",
|
||
help_text="当 school_id 为 NULL 时为手动输入",
|
||
)
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "client_school_preferences"
|
||
verbose_name = "意向学校"
|
||
verbose_name_plural = "意向学校"
|
||
indexes = [
|
||
models.Index(fields=["requirement"], name="idx_csp_requirement"),
|
||
]
|