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

282 lines
8.6 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 (
ClientPropertyMatchGroup,
ClientPropertyMatchSource,
ClientPropertyMatchStatus,
ClientStatusLogChangeType,
ClientViewingIntent,
ClientViewingType,
)
from core.models.base import UUIDPrimaryKeyModel
class ClientViewing(UUIDPrimaryKeyModel):
client = models.ForeignKey(
"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,
verbose_name="带看类型",
help_text="appointment=预约 / viewing=带看 / revisit=复看 / empty=空看",
)
agent = models.ForeignKey(
"org.Staff",
null=True,
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人",
)
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="",
verbose_name="带看情况",
help_text="必填≥6字",
)
client_intent = models.CharField(
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=二看…,冗余字段,触发器维护",
)
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:
db_table = "client_viewings"
verbose_name = "带看记录"
verbose_name_plural = "带看记录"
indexes = [
models.Index(
fields=["client", "-viewing_start_at"], name="idx_cv_client_time"
),
models.Index(fields=["property"], name="idx_cv_property"),
models.Index(fields=["agent"], name="idx_cv_agent"),
]
class ClientPropertyMatch(UUIDPrimaryKeyModel):
client = models.ForeignKey(
"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="",
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,
verbose_name="匹配度评分",
help_text="0-100",
)
match_reasons = models.JSONField(
null=True,
blank=True,
verbose_name="匹配原因详情",
help_text='格式:[{"key": "budget", "match": 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="配房计算时间",
)
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:
db_table = "client_property_matches"
verbose_name = "智能配房"
verbose_name_plural = "智能配房"
constraints = [
models.UniqueConstraint(
fields=["client", "property"], name="uq_client_match_pair"
),
]
indexes = [
models.Index(
fields=["client", "match_source", "match_group"],
name="idx_cpm_client_grp",
),
models.Index(fields=["client", "status"], name="idx_cpm_status"),
]
class ClientStatusLog(models.Model):
"""Audit log; record-level immutable (no deleted_at)."""
id = models.UUIDField(
primary_key=True,
verbose_name="主键",
)
client = models.ForeignKey(
"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,
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字",
)
operator = models.ForeignKey(
"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="操作时间",
)
class Meta:
db_table = "client_status_logs"
verbose_name = "客源状态变更日志"
verbose_name_plural = "客源状态变更日志"
indexes = [
models.Index(fields=["client", "-operated_at"], name="idx_csl_client"),
models.Index(fields=["change_type", "-operated_at"], name="idx_csl_type"),
]