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).
This commit is contained in:
2026-04-30 09:19:58 +08:00
parent 3638fc0302
commit e67b07a7c8
5 changed files with 678 additions and 121 deletions

View File

@@ -14,15 +14,25 @@ from core.models.base import UUIDPrimaryKeyModel
class ClientViewing(UUIDPrimaryKeyModel):
client = models.ForeignKey(
"fonrey_client.Client", on_delete=models.RESTRICT, related_name="viewings"
"fonrey_client.Client",
on_delete=models.RESTRICT,
related_name="viewings",
verbose_name="所属客源",
help_text="带看记录仅软删除,不随客源删除",
)
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.RESTRICT,
related_name="client_viewings",
verbose_name="带看房源",
help_text="房源删除时保留带看记录",
)
viewing_type = models.CharField(
max_length=20, choices=ClientViewingType.choices, default=ClientViewingType.VIEWING
max_length=20,
choices=ClientViewingType.choices,
default=ClientViewingType.VIEWING,
verbose_name="带看类型",
help_text="appointment=预约 / viewing=带看 / revisit=复看 / empty=空看",
)
agent = models.ForeignKey(
@@ -31,28 +41,77 @@ class ClientViewing(UUIDPrimaryKeyModel):
blank=True,
on_delete=models.SET_NULL,
related_name="led_viewings",
verbose_name="主带看经纪人",
)
companion_ids = ArrayField(
models.UUIDField(),
blank=True,
default=list,
verbose_name="陪看人员",
help_text="员工 ID 数组最多5人",
)
cooperator_ids = ArrayField(
models.UUIDField(),
blank=True,
default=list,
verbose_name="合作带看人",
help_text="员工 ID 数组最多5人",
)
companion_ids = ArrayField(models.UUIDField(), blank=True, default=list)
cooperator_ids = ArrayField(models.UUIDField(), blank=True, default=list)
scheduled_at = models.DateTimeField(null=True, blank=True)
viewing_start_at = models.DateTimeField(null=True, blank=True)
viewing_end_at = models.DateTimeField(null=True, blank=True)
scheduled_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="预约时间",
)
viewing_start_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="实际带看开始时间",
)
viewing_end_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="带看结束时间",
)
situation = models.TextField(blank=True, default="")
situation = models.TextField(
blank=True,
default="",
verbose_name="带看情况",
help_text="必填≥6字",
)
client_intent = models.CharField(
max_length=20, choices=ClientViewingIntent.choices, blank=True, default=""
max_length=20,
choices=ClientViewingIntent.choices,
blank=True,
default="",
verbose_name="客户意向",
help_text="interested=感兴趣 / not_interested=不感兴趣 / negotiating=谈判中 / cancelled=取消",
)
viewing_progress = models.SmallIntegerField(
null=True,
blank=True,
verbose_name="带看进度",
help_text="1=一看2=二看…,冗余字段,触发器维护",
)
viewing_progress = models.SmallIntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
deleted_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name="创建时间",
)
deleted_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="删除时间",
help_text="软删除时间戳;带看记录可软删除",
)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_client_viewings",
verbose_name="创建人",
)
class Meta:
@@ -70,41 +129,79 @@ class ClientViewing(UUIDPrimaryKeyModel):
class ClientPropertyMatch(UUIDPrimaryKeyModel):
client = models.ForeignKey(
"fonrey_client.Client", on_delete=models.CASCADE, related_name="property_matches"
"fonrey_client.Client",
on_delete=models.CASCADE,
related_name="property_matches",
verbose_name="所属客源",
)
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.CASCADE,
related_name="client_matches",
verbose_name="匹配房源",
)
match_source = models.CharField(
max_length=20,
choices=ClientPropertyMatchSource.choices,
default=ClientPropertyMatchSource.RECORDED,
verbose_name="匹配来源",
help_text="recorded=录客配房(基于录入需求) / system=系统配房(算法推荐)",
)
match_group = models.CharField(
max_length=30, choices=ClientPropertyMatchGroup.choices, blank=True, default=""
max_length=30,
choices=ClientPropertyMatchGroup.choices,
blank=True,
default="",
verbose_name="匹配分组",
help_text="quality_layout=优质户型 / price_reduced=降价 / hot=热门 / newly_listed=新上",
)
match_score = models.DecimalField(
max_digits=5, decimal_places=2, null=True, blank=True
max_digits=5,
decimal_places=2,
null=True,
blank=True,
verbose_name="匹配度评分",
help_text="0-100",
)
match_reasons = models.JSONField(
null=True,
blank=True,
verbose_name="匹配原因详情",
help_text='格式:[{"key": "budget", "match": true}, ...]',
)
match_reasons = models.JSONField(null=True, blank=True)
status = models.CharField(
max_length=20,
choices=ClientPropertyMatchStatus.choices,
default=ClientPropertyMatchStatus.SUGGESTED,
verbose_name="状态",
help_text="suggested=待推送 / shared=已分享 / rejected=已反馈不合适 / viewed=客户已查看",
)
shared_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="分享时间",
)
feedback = models.CharField(
max_length=50,
blank=True,
default="",
verbose_name="反馈原因",
help_text="lookup_items 维护",
)
calculated_at = models.DateTimeField(
auto_now_add=True,
verbose_name="配房计算时间",
)
shared_at = models.DateTimeField(null=True, blank=True)
feedback = models.CharField(max_length=50, blank=True, default="")
calculated_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_matches",
verbose_name="创建人",
help_text="触发配房操作的员工录客配房时记录系统配房可为NULL",
)
class Meta:
@@ -128,20 +225,51 @@ class ClientPropertyMatch(UUIDPrimaryKeyModel):
class ClientStatusLog(models.Model):
"""Audit log; record-level immutable (no deleted_at)."""
id = models.UUIDField(primary_key=True)
id = models.UUIDField(
primary_key=True,
verbose_name="主键",
)
client = models.ForeignKey(
"fonrey_client.Client", on_delete=models.RESTRICT, related_name="status_logs"
"fonrey_client.Client",
on_delete=models.RESTRICT,
related_name="status_logs",
verbose_name="所属客源",
help_text="状态日志永久保留RESTRICT 防止删除客源",
)
change_type = models.CharField(
max_length=30, choices=ClientStatusLogChangeType.choices
max_length=30,
choices=ClientStatusLogChangeType.choices,
verbose_name="变更类型",
help_text="status_change=改状态 / grade_change=改等级 / to_public=转公客 / to_transacted=转成交 / to_invalid=转无效 / owner_change=改归属人 / source_change=改来源",
)
old_value = models.JSONField(
null=True,
blank=True,
verbose_name="变更前快照",
help_text='格式:{"status": "buying", "label": "求购"}',
)
new_value = models.JSONField(
null=True,
blank=True,
verbose_name="变更后快照",
)
reason = models.TextField(
blank=True,
default="",
verbose_name="变更理由",
help_text="改状态必填最多200字",
)
old_value = models.JSONField(null=True, blank=True)
new_value = models.JSONField(null=True, blank=True)
reason = models.TextField(blank=True, default="")
operator = models.ForeignKey(
"org.Staff", on_delete=models.RESTRICT, related_name="client_status_changes"
"org.Staff",
on_delete=models.RESTRICT,
related_name="client_status_changes",
verbose_name="操作人",
help_text="必填,状态变更审计用",
)
operated_at = models.DateTimeField(
auto_now_add=True,
verbose_name="操作时间",
)
operated_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "client_status_logs"