Files
fonrey/apps/client/models/core.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

292 lines
9.0 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 (
ClientActivityLevel,
ClientBuyingPurpose,
ClientGrade,
ClientIdType,
ClientInvalidReason,
ClientPaymentMethod,
ClientPropertiesOwned,
ClientPropertyUsage,
ClientStatus,
ClientTransactedPropertyType,
ClientTransactedType,
ClientTransferToPublicType,
ClientType,
)
from core.models.base import AuditedModel
class Client(AuditedModel):
client_no = models.CharField(
max_length=30,
unique=True,
verbose_name="客源编号",
help_text="系统生成的客源编号,格式由运营配置(如 KY20260424001",
)
client_type = models.CharField(
max_length=20,
choices=ClientType.choices,
default=ClientType.PRIVATE,
verbose_name="客源分类",
help_text="private=私客 / public=公客 / transacted=成交客",
)
status = models.CharField(
max_length=20,
choices=ClientStatus.choices,
default=ClientStatus.BUYING,
verbose_name="客源状态",
help_text="buying=求购 / renting=求租 / buy_or_rent=租购 / suspended=暂缓 / bought=已购 / rented_done=已租 / public=公客 / invalid=无效(详见 ENUMS",
)
grade = models.CharField(
max_length=5,
choices=ClientGrade.choices,
default=ClientGrade.C,
verbose_name="客源等级",
help_text="A=A急迫 / B=较强 / C=一般 / D=较弱 / E=暂不关注",
)
property_usage = models.CharField(
max_length=30,
choices=ClientPropertyUsage.choices,
default=ClientPropertyUsage.RESIDENTIAL,
verbose_name="房屋用途",
help_text="residential=住宅 / villa=别墅 / commercial_residential=商住 / shop=商铺 / office=写字楼 / other=其他",
)
buying_purpose = ArrayField(
models.CharField(max_length=20, choices=ClientBuyingPurpose.choices),
blank=True,
default=list,
verbose_name="购房目的",
help_text="多选rigid=刚需 / investment=投资 / school_district=学区 / upgrade=改善 / commercial=商用 / other=其他",
)
payment_method = models.CharField(
max_length=30,
choices=ClientPaymentMethod.choices,
blank=True,
default="",
verbose_name="付款方式",
help_text="full=全额 / mortgage=商业贷款 / mortgage_fund=商贷+公积金 / fund=公积金",
)
properties_owned = models.CharField(
max_length=20,
choices=ClientPropertiesOwned.choices,
blank=True,
default="",
verbose_name="名下房产",
help_text="none=无 / local_none=本地无外地有 / local_has=本地有",
)
has_loan_record = models.BooleanField(
null=True,
blank=True,
verbose_name="有无贷款记录",
)
id_type = models.CharField(
max_length=20,
choices=ClientIdType.choices,
blank=True,
default="",
verbose_name="证件类型",
help_text="id_card=身份证 / passport=护照 / hk_macao=港澳台 / other=其他",
)
id_number_enc = models.BinaryField(
null=True,
blank=True,
verbose_name="证件号码(加密)",
help_text="AES 加密存储",
)
source = models.CharField(
max_length=50,
blank=True,
default="",
verbose_name="客户来源",
help_text="lookup_items 维护",
)
remarks = models.TextField(
blank=True,
default="",
verbose_name="备注",
help_text="最多200字",
)
is_starred = models.BooleanField(
default=False,
verbose_name="是否收藏",
help_text="快速标记,详细收藏夹用 client_folder_items",
)
is_pinned = models.BooleanField(
default=False,
verbose_name="是否置顶",
help_text="列表顶部置顶",
)
is_big_value = models.BooleanField(
default=False,
verbose_name="是否大价值客户",
help_text="影响筛选展示",
)
is_protected = models.BooleanField(
default=False,
verbose_name="是否保护客",
help_text="影响转公逻辑",
)
prefers_new_house = models.BooleanField(
null=True,
blank=True,
verbose_name="偏好新房",
help_text="用于筛选",
)
transfer_to_public_type = models.CharField(
max_length=20,
choices=ClientTransferToPublicType.choices,
blank=True,
default="",
verbose_name="转公客方式",
help_text="manual=手动转公 / auto=自动转公(超时) / marketing_jump=营销客跳公 / resource_public=资料客素公",
)
transferred_public_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="进入公客池时间",
)
invalid_reason = models.CharField(
max_length=30,
choices=ClientInvalidReason.choices,
blank=True,
default="",
verbose_name="无效原因",
help_text="invalid_phone=号码无效 / peer_agent=同行 / ad=广告推销 / no_intent=无意向 / other=其他",
)
invalidated_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="标记无效时间",
)
transacted_at = models.DateField(
null=True,
blank=True,
verbose_name="成交日期",
)
transacted_property = models.ForeignKey(
"fonrey_property.Property",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="transacted_clients",
verbose_name="成交房源",
help_text="成交关联的房源",
)
transacted_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="成交价格",
help_text="单位:万元",
)
transacted_type = models.CharField(
max_length=20,
choices=ClientTransactedType.choices,
blank=True,
default="",
verbose_name="成交类型",
help_text="bought=我购 / rented=我租",
)
transacted_property_type = models.CharField(
max_length=20,
choices=ClientTransactedPropertyType.choices,
blank=True,
default="",
verbose_name="成交房源类型",
help_text="second_hand=二手 / new_house=新房",
)
first_recorder = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="first_recorded_clients",
verbose_name="首录人",
)
owner = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="owned_clients",
verbose_name="归属人",
help_text="私客独占跟进人",
)
org_unit = models.ForeignKey(
"org.OrgUnit",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="clients",
verbose_name="归属部门",
help_text="冗余字段,加速筛选",
)
activity_level = models.CharField(
max_length=20,
choices=ClientActivityLevel.choices,
blank=True,
default="",
verbose_name="活跃度",
help_text="new_matched=新配偶 / active_7d / active_30d / active_90d / expiring / frozen / invalid异步计算",
)
last_active_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="最后有效跟进时间",
help_text="触发器维护",
)
last_follow_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="最后跟进时间",
help_text="冗余字段,列表排序用",
)
commission_date = models.DateField(
null=True,
blank=True,
verbose_name="委托日期",
)
entrust_count = models.SmallIntegerField(
default=1,
verbose_name="委托次数",
help_text="成交后再委托则累加",
)
version = models.IntegerField(
default=1,
verbose_name="版本号",
help_text="乐观锁;每次 UPDATE +1应用层检测 0 行受影响时抛 ConflictError",
)
class Meta:
db_table = "clients"
verbose_name = "客源"
verbose_name_plural = "客源"
indexes = [
models.Index(fields=["client_type", "status"], name="idx_clients_type_stat"),
models.Index(fields=["owner"], name="idx_clients_owner"),
models.Index(fields=["org_unit"], name="idx_clients_org_unit"),
models.Index(
fields=["activity_level", "-last_active_at"],
name="idx_clients_activity",
),
models.Index(fields=["grade"], name="idx_clients_grade"),
models.Index(
fields=["-transferred_public_at"], name="idx_clients_transferred"
),
models.Index(fields=["-last_follow_at"], name="idx_clients_last_follow"),
]