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"), ]