Sync DATA_MODEL_PROPERTY.md field-level Chinese annotations to Django models across 23 property tables. Adds verbose_name= and help_text= to every field in core.py, follow_keys.py, listings.py, media.py. Pre-existing partitioned-table docstrings on FollowLog/PropertyPhoto retained (signal Django ORM treats parent as unmanaged, RunSQL managed).
279 lines
9.0 KiB
Python
279 lines
9.0 KiB
Python
from django.db import models
|
||
|
||
from core.enums import (
|
||
PropertyFollowAiTag,
|
||
PropertyFollowAttachmentFileType,
|
||
PropertyFollowLogType,
|
||
PropertyKeyType,
|
||
)
|
||
from core.models.base import UUIDPrimaryKeyModel
|
||
|
||
|
||
class FollowLog(models.Model):
|
||
"""Partitioned table (PARTITION BY RANGE created_at).
|
||
|
||
Managed via RunSQL; Django ORM treats parent as unmanaged.
|
||
"""
|
||
|
||
id = models.UUIDField(primary_key=True, verbose_name="主键")
|
||
created_at = models.DateTimeField(
|
||
verbose_name="创建时间",
|
||
help_text="分区键,必须在最前声明;系统自动",
|
||
)
|
||
property = models.ForeignKey(
|
||
"fonrey_property.Property",
|
||
on_delete=models.CASCADE,
|
||
related_name="follow_logs",
|
||
verbose_name="所属房源",
|
||
)
|
||
|
||
log_type = models.CharField(
|
||
max_length=30,
|
||
choices=PropertyFollowLogType.choices,
|
||
verbose_name="跟进日志类型",
|
||
help_text="written=经纪人主动写入/modified=字段变更自动生成/sensitive_op=敏感操作跟进/sensitive_view=敏感信息查看(不可删)/other=其他/system=系统日志",
|
||
)
|
||
purpose = models.CharField(
|
||
max_length=50,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="跟进目的",
|
||
help_text="枚举值由 lookup_items 维护,如:电话/业主跟进/议价/带看;仅 written 类型使用",
|
||
)
|
||
content = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="跟进内容",
|
||
help_text="最少 6 字,最多 500 字;仅 written 类型必填",
|
||
)
|
||
|
||
ai_tag = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
default="",
|
||
choices=PropertyFollowAiTag.choices,
|
||
verbose_name="AI 辅助标签",
|
||
help_text="ai_for_sale=AI判断业主在售/ai_not_for_sale=AI判断业主不售;由系统智能分析后打标",
|
||
)
|
||
|
||
change_detail = models.JSONField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="字段变更明细",
|
||
help_text='格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"};modified 类型使用',
|
||
)
|
||
log_tag = models.CharField(
|
||
max_length=50,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="前端展示标签",
|
||
help_text="如:查看号码/图片下载/改状态/改价格/改等级/修改相关方;对应跟进时间线显示的方括号标签",
|
||
)
|
||
|
||
is_public = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否公开",
|
||
help_text="true=全员可见/false=仅本人及上级可见",
|
||
)
|
||
|
||
operator = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
verbose_name="操作人",
|
||
help_text="人员离职后置 NULL,但 snapshot 保留",
|
||
)
|
||
operator_snapshot = models.JSONField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="操作人快照",
|
||
help_text="{name, role, org_unit_name, store_group};防止人员离职后丢失显示信息",
|
||
)
|
||
|
||
is_deletable = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否可软删除",
|
||
help_text="false=敏感信息查看类型,合规要求不可删除",
|
||
)
|
||
deleted_at = models.DateTimeField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="软删除时间戳",
|
||
help_text="仅 is_deletable=TRUE 时可软删;NULL=未删除",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "follow_logs"
|
||
verbose_name = "房源跟进日志"
|
||
verbose_name_plural = "房源跟进日志"
|
||
managed = False
|
||
unique_together = (("id", "created_at"),)
|
||
|
||
|
||
class FollowLogAttachment(UUIDPrimaryKeyModel):
|
||
follow_log_id = models.UUIDField(
|
||
verbose_name="所属跟进日志ID",
|
||
help_text="跨分区外键,未通过 Django FK 强约束;日志删除时联级删除",
|
||
)
|
||
file_key = models.TextField(
|
||
verbose_name="图片存储路径",
|
||
help_text="Cloudflare R2 对象路径",
|
||
)
|
||
file_name = models.CharField(
|
||
max_length=255,
|
||
verbose_name="原始文件名",
|
||
help_text="用户上传时的文件名",
|
||
)
|
||
file_size = models.IntegerField(
|
||
verbose_name="文件大小",
|
||
help_text="bytes;最大 20MB = 20971520",
|
||
)
|
||
file_type = models.CharField(
|
||
max_length=10,
|
||
blank=True,
|
||
default="",
|
||
choices=PropertyFollowAttachmentFileType.choices,
|
||
verbose_name="文件格式",
|
||
help_text="bmp/jpg/png/svg/gif(PRD 限定格式)",
|
||
)
|
||
sort_order = models.SmallIntegerField(
|
||
default=0,
|
||
verbose_name="排序权重",
|
||
help_text="控制同一跟进附件的显示顺序",
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
|
||
|
||
class Meta:
|
||
db_table = "follow_log_attachments"
|
||
verbose_name = "跟进附件"
|
||
verbose_name_plural = "跟进附件"
|
||
indexes = [models.Index(fields=["follow_log_id"], name="idx_fla_log")]
|
||
|
||
|
||
class FollowLogRecording(UUIDPrimaryKeyModel):
|
||
follow_log_id = models.UUIDField(
|
||
verbose_name="所属跟进日志ID",
|
||
help_text="跨分区外键,未通过 Django FK 强约束;日志删除时联级删除",
|
||
)
|
||
file_key = models.TextField(
|
||
verbose_name="录音文件存储路径",
|
||
help_text="Cloudflare R2 对象路径",
|
||
)
|
||
duration_seconds = models.IntegerField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="录音时长",
|
||
help_text="秒;可空,上传时若能解析则填写",
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
|
||
|
||
class Meta:
|
||
db_table = "follow_log_recordings"
|
||
verbose_name = "跟进录音"
|
||
verbose_name_plural = "跟进录音"
|
||
indexes = [models.Index(fields=["follow_log_id"], name="idx_flr_log")]
|
||
|
||
|
||
class PropertyKey(UUIDPrimaryKeyModel):
|
||
property = models.ForeignKey(
|
||
"fonrey_property.Property",
|
||
on_delete=models.CASCADE,
|
||
related_name="keys",
|
||
verbose_name="所属房源",
|
||
)
|
||
key_type = models.CharField(
|
||
max_length=20,
|
||
choices=PropertyKeyType.choices,
|
||
verbose_name="钥匙类型",
|
||
help_text="mechanical=机械钥匙/password=密码(如密码门锁)",
|
||
)
|
||
|
||
holder = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="held_keys",
|
||
verbose_name="持有人",
|
||
help_text="人员离职后置 NULL",
|
||
)
|
||
holder_snapshot = models.JSONField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name="持有人快照",
|
||
help_text="{name, store_group};防止人员离职后丢失显示信息",
|
||
)
|
||
storage_unit = models.ForeignKey(
|
||
"org.OrgUnit",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="stored_keys",
|
||
verbose_name="保管部门",
|
||
help_text="钥匙存放在哪个部门",
|
||
)
|
||
|
||
is_other_agency = models.BooleanField(
|
||
default=False,
|
||
verbose_name="是否他司钥匙",
|
||
help_text="true=是他中介公司的钥匙/false=本司钥匙",
|
||
)
|
||
other_agency_info = models.CharField(
|
||
max_length=30,
|
||
blank=True,
|
||
default="",
|
||
verbose_name="他司中介信息",
|
||
help_text='最多 30 字;is_other_agency=true 时填写,如"链家"',
|
||
)
|
||
remarks = models.TextField(
|
||
blank=True,
|
||
default="",
|
||
verbose_name="备注",
|
||
help_text="最多 200 字;如密码内容等补充说明",
|
||
)
|
||
|
||
is_active = models.BooleanField(
|
||
default=True,
|
||
verbose_name="是否有效",
|
||
help_text="true=在管中/false=已归还或失效",
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||
updated_at = models.DateTimeField(auto_now=True, verbose_name="最后更新时间")
|
||
created_by = models.ForeignKey(
|
||
"org.Staff",
|
||
null=True,
|
||
blank=True,
|
||
on_delete=models.SET_NULL,
|
||
related_name="created_property_keys",
|
||
verbose_name="创建人",
|
||
)
|
||
|
||
class Meta:
|
||
db_table = "property_keys"
|
||
verbose_name = "房源钥匙"
|
||
verbose_name_plural = "房源钥匙"
|
||
indexes = [models.Index(fields=["property"], name="idx_pk_property")]
|
||
|
||
|
||
class KeyAttachment(UUIDPrimaryKeyModel):
|
||
key = models.ForeignKey(
|
||
PropertyKey,
|
||
on_delete=models.CASCADE,
|
||
related_name="attachments",
|
||
verbose_name="所属钥匙记录",
|
||
help_text="钥匙删除时联级删除",
|
||
)
|
||
file_key = models.TextField(
|
||
verbose_name="附件存储路径",
|
||
help_text="Cloudflare R2 对象路径",
|
||
)
|
||
file_name = models.CharField(max_length=255, verbose_name="原始文件名")
|
||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
|
||
|
||
class Meta:
|
||
db_table = "key_attachments"
|
||
verbose_name = "钥匙附件"
|
||
verbose_name_plural = "钥匙附件"
|
||
indexes = [models.Index(fields=["key"], name="idx_ka_key")]
|