Files
fonrey/apps/client/models/contacts.py
ishenwei e67b07a7c8 feat(client): add Chinese verbose_name and help_text to all client fields (Phase 4.1 part 2/9)
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).
2026-04-30 09:19:58 +08:00

319 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"),
]