Files
fonrey/apps/property/models/listings.py
ishenwei 3638fc0302 feat(property): add Chinese verbose_name and help_text to all property fields (Phase 4.1)
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).
2026-04-30 09:15:43 +08:00

446 lines
14 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.db import models
from core.enums import (
PropertyCommissionAttachmentCategory,
PropertyCommissionOwnerType,
PropertyCommissionStatus,
PropertyListingHistoryStatus,
PropertyListingType,
PropertyNumberHolderApprovalStatus,
)
from core.models.base import TimeStampedModel, UUIDPrimaryKeyModel
class ListingHistory(UUIDPrimaryKeyModel):
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.RESTRICT,
related_name="listing_histories",
verbose_name="所属房源",
help_text="禁止级联删除,保留历史",
)
listing_type = models.CharField(
max_length=20,
choices=PropertyListingType.choices,
verbose_name="挂牌类型",
help_text="for_sale=出售挂牌/for_rent=出租挂牌",
)
status = models.CharField(
max_length=10,
choices=PropertyListingHistoryStatus.choices,
default=PropertyListingHistoryStatus.ACTIVE,
verbose_name="挂牌状态",
help_text="active=挂牌中/ended=已结束",
)
sale_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="本次挂牌售价快照",
help_text="万元;出售挂牌时记录",
)
rent_price = models.DecimalField(
max_digits=10,
decimal_places=2,
null=True,
blank=True,
verbose_name="本次挂牌租价快照",
help_text="元/月;出租挂牌时记录",
)
sale_unit_price = models.DecimalField(
max_digits=10,
decimal_places=2,
null=True,
blank=True,
verbose_name="本次挂牌售价单价",
help_text="元/m²由 sale_price ÷ area 计算后存储",
)
ownership_years = models.CharField(
max_length=30,
blank=True,
default="",
verbose_name="房本年限快照",
help_text='本次挂牌时的房本年限,如"满2年"',
)
is_only_house = models.BooleanField(
null=True,
blank=True,
verbose_name="唯一住房状态快照",
help_text="本次挂牌时的唯一住房状态",
)
tax_included = models.CharField(
max_length=15,
blank=True,
default="",
verbose_name="包税费方式快照",
help_text="each_party=各付/net=到手/inclusive=包税",
)
sale_reason = models.TextField(
blank=True,
default="",
verbose_name="售房原因快照",
help_text="本次挂牌时的售房原因",
)
seller_agent = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name="出售经纪人",
help_text="本次挂牌的出售经纪人;人员离职后置 NULL但 snapshot 保留",
)
seller_agent_snapshot = models.JSONField(
null=True,
blank=True,
verbose_name="出售经纪人快照",
help_text="{name, store_group, org_unit_name};防止人员变动后数据丢失",
)
started_at = models.DateTimeField(
auto_now_add=False,
verbose_name="本次挂牌开始时间",
)
ended_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="本次挂牌结束时间",
help_text="NULL=当前仍在挂牌中",
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
class Meta:
db_table = "listing_histories"
verbose_name = "挂牌历史"
verbose_name_plural = "挂牌历史"
indexes = [
models.Index(fields=["property"], name="idx_lh_property"),
models.Index(fields=["property", "status"], name="idx_lh_active"),
]
class PriceChange(UUIDPrimaryKeyModel):
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.RESTRICT,
related_name="price_changes",
verbose_name="所属房源",
help_text="禁止级联删除,保留调价历史",
)
old_sale_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价前挂牌售价",
help_text="万元NULL=首次定价",
)
new_sale_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价后挂牌售价",
help_text="万元",
)
old_bottom_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价前售底价",
help_text="万元NULL=未设置",
)
new_bottom_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价后售底价",
help_text="万元NULL=本次不变更底价",
)
old_record_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价前备案/核验价",
help_text="万元NULL=未设置",
)
new_record_price = models.DecimalField(
max_digits=12,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价后备案/核验价",
help_text="万元NULL=本次不变更",
)
old_rent_price = models.DecimalField(
max_digits=10,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价前挂牌租价",
help_text="元/月NULL=非出租类或未设置",
)
new_rent_price = models.DecimalField(
max_digits=10,
decimal_places=2,
null=True,
blank=True,
verbose_name="调价后挂牌租价",
help_text="元/月",
)
change_reason = models.TextField(
verbose_name="调价原因",
help_text='必填,最多 200 字;如"业主主动降价"',
)
changed_at = models.DateTimeField(auto_now_add=True, verbose_name="调价操作时间")
changed_by = models.ForeignKey(
"org.Staff",
on_delete=models.RESTRICT,
verbose_name="操作人",
help_text="禁止置 NULL保留审计追溯",
)
class Meta:
db_table = "price_changes"
verbose_name = "调价记录"
verbose_name_plural = "调价记录"
indexes = [
models.Index(fields=["property"], name="idx_pchg_property"),
models.Index(fields=["property", "-changed_at"], name="idx_pchg_time"),
]
class Commission(TimeStampedModel):
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.CASCADE,
related_name="commissions",
verbose_name="所属房源",
)
commission_type = models.CharField(
max_length=50,
verbose_name="委托类型",
help_text="独家委托/非独家委托;由 lookup_items 维护",
)
period_start = models.DateField(verbose_name="委托开始日期")
period_end = models.DateField(
null=True,
blank=True,
verbose_name="委托结束日期",
help_text="is_open_ended=true 时为 NULL",
)
is_open_ended = models.BooleanField(
default=False,
verbose_name="是否无固定结束日期",
help_text="true=长期委托/false=有截止日期",
)
agent = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="commissions_as_agent",
verbose_name="委托经纪人",
help_text="人员离职后置 NULL",
)
agent_snapshot = models.JSONField(
null=True,
blank=True,
verbose_name="经纪人快照",
help_text="{name, store_group};防止人员变动后数据丢失",
)
signing_method = models.CharField(
max_length=50,
blank=True,
default="",
verbose_name="签约方式",
help_text="选择后动态展示委托书模板",
)
owner_type = models.CharField(
max_length=20,
choices=PropertyCommissionOwnerType.choices,
default=PropertyCommissionOwnerType.OWNER,
verbose_name="委托人类型",
help_text="owner=产权人本人/authorized_third=被授权第三方",
)
property_owner_contact = models.ForeignKey(
"fonrey_property.PropertyContact",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="commissions",
verbose_name="关联联系人",
help_text="若委托人已录入联系人则关联,否则填写下方姓名/证件",
)
owner_name = models.CharField(
max_length=50,
blank=True,
default="",
verbose_name="委托人姓名",
)
owner_id_type = models.CharField(
max_length=20,
blank=True,
default="",
verbose_name="委托人证件类型",
help_text="如:身份证/护照",
)
owner_id_number = models.CharField(
max_length=50,
blank=True,
default="",
verbose_name="委托人证件号明文",
help_text="仅供参考;加密版本见 owner_id_number_enc",
)
owner_id_number_enc = models.BinaryField(
null=True,
blank=True,
verbose_name="委托人证件号密文",
help_text="AES-256-GCM 加密",
)
remarks = models.TextField(
blank=True,
default="",
verbose_name="备注",
help_text="最多 200 字",
)
status = models.CharField(
max_length=20,
choices=PropertyCommissionStatus.choices,
default=PropertyCommissionStatus.ACTIVE,
verbose_name="委托状态",
help_text="active=有效/expired=已过期/cancelled=已取消",
)
created_by = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="created_commissions",
verbose_name="创建人",
)
class Meta:
db_table = "commissions"
verbose_name = "委托管理"
verbose_name_plural = "委托管理"
indexes = [
models.Index(fields=["property"], name="idx_commissions_property"),
models.Index(fields=["property", "status"], name="idx_commissions_active"),
]
class CommissionAttachment(UUIDPrimaryKeyModel):
commission = models.ForeignKey(
Commission,
on_delete=models.CASCADE,
related_name="attachments",
verbose_name="所属委托",
help_text="委托删除时联级删除",
)
category = models.CharField(
max_length=20,
choices=PropertyCommissionAttachmentCategory.choices,
verbose_name="附件分类",
help_text="id_card=身份证/property_cert=产权证书/commission_letter=委托书/other=其他材料",
)
file_key = models.TextField(
verbose_name="附件存储路径",
help_text="Cloudflare R2 对象路径",
)
file_name = models.CharField(max_length=255, verbose_name="原始文件名")
file_size = models.IntegerField(
null=True,
blank=True,
verbose_name="文件大小",
help_text="bytes",
)
sort_order = models.SmallIntegerField(
default=0,
verbose_name="排序权重",
help_text="数值越小越靠前",
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间")
class Meta:
db_table = "commission_attachments"
verbose_name = "委托附件"
verbose_name_plural = "委托附件"
indexes = [models.Index(fields=["commission"], name="idx_ca_commission")]
class NumberHolderApproval(UUIDPrimaryKeyModel):
property = models.ForeignKey(
"fonrey_property.Property",
on_delete=models.CASCADE,
related_name="number_holder_approvals",
verbose_name="所属房源",
)
contact = models.ForeignKey(
"fonrey_property.PropertyContact",
on_delete=models.CASCADE,
related_name="number_holder_approvals",
verbose_name="申请变更的联系方",
help_text="即号码方候选联系人",
)
applicant = models.ForeignKey(
"org.Staff",
on_delete=models.RESTRICT,
related_name="nh_applications",
verbose_name="申请人",
help_text="提交号码方变更申请的经纪人;禁止置 NULL 保留审计",
)
approver = models.ForeignKey(
"org.Staff",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="nh_approvals",
verbose_name="审批人",
help_text="上级审批人;审批前为 NULL",
)
status = models.CharField(
max_length=20,
choices=PropertyNumberHolderApprovalStatus.choices,
default=PropertyNumberHolderApprovalStatus.PENDING,
verbose_name="审批状态",
help_text="pending=待审批/approved=已通过/rejected=已驳回",
)
remarks = models.TextField(
blank=True,
default="",
verbose_name="审批备注",
help_text="审批人填写的意见或驳回原因",
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请提交时间")
decided_at = models.DateTimeField(
null=True,
blank=True,
verbose_name="审批决定时间",
help_text="NULL=尚未审批",
)
class Meta:
db_table = "number_holder_approvals"
verbose_name = "号码方审批"
verbose_name_plural = "号码方审批"
indexes = [
models.Index(fields=["status"], name="idx_nha_status"),
models.Index(fields=["property"], name="idx_nha_property"),
]