from django.db import models from core.enums import ( PropertyAttachmentCategory, PropertyFieldSurveyStatus, PropertyPhotoCategory, PropertySurveyPhotoCategory, ) from core.models.base import UUIDPrimaryKeyModel class FieldSurvey(UUIDPrimaryKeyModel): property = models.ForeignKey( "fonrey_property.Property", on_delete=models.CASCADE, related_name="field_surveys", verbose_name="所属房源", ) status = models.CharField( max_length=10, choices=PropertyFieldSurveyStatus.choices, default=PropertyFieldSurveyStatus.DRAFT, verbose_name="实勘状态", help_text="draft=草稿(未提交)/submitted=已提交(已完成)", ) gps_latitude = models.DecimalField( max_digits=10, decimal_places=7, null=True, blank=True, verbose_name="GPS 纬度", help_text="实勘打卡位置;精度 7 位小数", ) gps_longitude = models.DecimalField( max_digits=10, decimal_places=7, null=True, blank=True, verbose_name="GPS 经度", help_text="实勘打卡位置;精度 7 位小数", ) gps_accuracy = models.DecimalField( max_digits=6, decimal_places=2, null=True, blank=True, verbose_name="GPS 精度", help_text="米;标注定位误差", ) description = models.TextField( blank=True, default="", verbose_name="实勘说明", help_text="最多 200 字;经纪人现场情况描述", ) submitted_at = models.DateTimeField( null=True, blank=True, verbose_name="提交时间", help_text="status 变为 submitted 时记录;NULL=尚未提交", ) 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", on_delete=models.RESTRICT, verbose_name="实勘人", help_text="禁止置 NULL 保留审计", ) class Meta: db_table = "field_surveys" verbose_name = "实勘记录" verbose_name_plural = "实勘记录" indexes = [ models.Index(fields=["property"], name="idx_fs_property"), models.Index(fields=["property", "status"], name="idx_fs_submitted"), ] class SurveyPhoto(UUIDPrimaryKeyModel): survey = models.ForeignKey( FieldSurvey, on_delete=models.CASCADE, related_name="photos", verbose_name="所属实勘", help_text="实勘删除时联级删除", ) category = models.CharField( max_length=20, choices=PropertySurveyPhotoCategory.choices, verbose_name="照片空间分类", help_text="layout=户型图/living_room=客厅/dining_room=餐厅/bedroom=卧室/bathroom=卫生间/kitchen=厨房/entrance=门厅/balcony=阳台/study=书房/indoor_other=室内其他/outdoor=外景", ) file_key = models.TextField( verbose_name="原图存储路径", help_text="Cloudflare R2 对象路径", ) thumbnail_key = models.TextField( blank=True, default="", verbose_name="缩略图路径", help_text="Cloudflare Images 自动生成", ) file_size = models.IntegerField( null=True, blank=True, verbose_name="文件大小", help_text="bytes", ) width = models.IntegerField( null=True, blank=True, verbose_name="图片宽度", help_text="像素;上传时解析", ) height = models.IntegerField( null=True, blank=True, verbose_name="图片高度", help_text="像素;上传时解析", ) sort_order = models.SmallIntegerField( default=0, verbose_name="排序权重", help_text="同一空间分类内,数值越小越靠前", ) is_vr_screenshot = models.BooleanField( default=False, verbose_name="是否为VR截图", help_text="true=全景/VR截图(区别于普通实拍照片)", ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") class Meta: db_table = "survey_photos" verbose_name = "实勘照片" verbose_name_plural = "实勘照片" indexes = [ models.Index(fields=["survey"], name="idx_sp_survey"), models.Index(fields=["survey", "category"], name="idx_sp_category"), ] class PropertyPhoto(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="photos", verbose_name="所属房源", ) category = models.CharField( max_length=20, choices=PropertyPhotoCategory.choices, verbose_name="照片分类", help_text="cover=封面/entrance=门厅/living_room=客厅/dining_room=餐厅/bedroom=卧室/bathroom=卫生间/kitchen=厨房/balcony=阳台/study=书房/indoor_other=室内其他/outdoor=外景/panorama=全景", ) file_key = models.TextField( verbose_name="原图存储路径", help_text="Cloudflare R2/S3 对象路径", ) thumbnail_key = models.TextField( blank=True, default="", verbose_name="缩略图路径", help_text="Cloudflare Images 自动生成", ) file_name = models.CharField( max_length=255, blank=True, default="", verbose_name="原始文件名", ) file_size = models.IntegerField( null=True, blank=True, verbose_name="文件大小", help_text="bytes", ) width = models.IntegerField( null=True, blank=True, verbose_name="图片宽度", help_text="像素;上传时解析", ) height = models.IntegerField( null=True, blank=True, verbose_name="图片高度", help_text="像素;上传时解析", ) is_cover = models.BooleanField( default=False, verbose_name="是否为封面图", help_text="true=封面;每套房源只能有一张封面(唯一约束保证)", ) sort_order = models.SmallIntegerField( default=0, verbose_name="排序权重", help_text="同一房源内,数值越小越靠前", ) updated_at = models.DateTimeField(auto_now=True, verbose_name="最后更新时间") created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="上传人", ) class Meta: db_table = "property_photos" verbose_name = "房源图片" verbose_name_plural = "房源图片" managed = False unique_together = (("id", "created_at"),) class PropertyAttachment(UUIDPrimaryKeyModel): property = models.ForeignKey( "fonrey_property.Property", on_delete=models.CASCADE, related_name="attachments", verbose_name="所属房源", ) category = models.CharField( max_length=20, choices=PropertyAttachmentCategory.choices, default=PropertyAttachmentCategory.OTHER, 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( verbose_name="文件大小", help_text="bytes", ) file_type = models.CharField( max_length=50, blank=True, default="", verbose_name="MIME 类型", help_text="如 application/pdf、image/jpeg", ) sort_order = models.SmallIntegerField( default=0, verbose_name="排序权重", help_text="控制同一房源附件的显示顺序", ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="上传时间") created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="上传人", ) class Meta: db_table = "property_attachments" verbose_name = "房源附件" verbose_name_plural = "房源附件" indexes = [ models.Index(fields=["property"], name="idx_pa_property"), models.Index(fields=["property", "category"], name="idx_pa_category"), ] class PropertyTag(UUIDPrimaryKeyModel): name = models.CharField( max_length=50, verbose_name="标签名称", help_text="最多 50 字;如:学区/地铁口/满五唯一", ) color = models.CharField( max_length=7, blank=True, default="", verbose_name="显示颜色", help_text="HEX 色值,如 #FF5733;前端标签徽章颜色", ) is_system = models.BooleanField( default=False, verbose_name="是否系统预置", help_text="true=系统内置标签不可删除;false=运营自定义标签可删", ) sort_order = models.IntegerField( default=0, verbose_name="排序权重", help_text="数值越小越靠前", ) is_active = models.BooleanField( default=True, verbose_name="是否启用", help_text="false=已停用不再展示", ) class Meta: db_table = "property_tags" verbose_name = "房源标签" verbose_name_plural = "房源标签" class PropertyTagRelation(models.Model): property = models.ForeignKey( "fonrey_property.Property", on_delete=models.CASCADE, related_name="tag_relations", verbose_name="所属房源", ) tag = models.ForeignKey( PropertyTag, on_delete=models.CASCADE, related_name="property_relations", verbose_name="所属标签", ) class Meta: db_table = "property_tag_relations" verbose_name = "房源标签关联" verbose_name_plural = "房源标签关联" constraints = [ models.UniqueConstraint(fields=["property", "tag"], name="uq_ptr_property_tag"), ] indexes = [ models.Index(fields=["property"], name="idx_ptr_property"), models.Index(fields=["tag"], name="idx_ptr_tag"), ] class PropertyFavorite(models.Model): staff = models.ForeignKey( "org.Staff", on_delete=models.CASCADE, related_name="favorite_properties", verbose_name="收藏人", help_text="员工注销时删除收藏记录", ) property = models.ForeignKey( "fonrey_property.Property", on_delete=models.CASCADE, related_name="favorited_by", verbose_name="收藏的房源", ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="收藏时间") class Meta: db_table = "property_favorites" verbose_name = "房源收藏" verbose_name_plural = "房源收藏" constraints = [ models.UniqueConstraint(fields=["staff", "property"], name="uq_pfav_staff_property"), ] indexes = [models.Index(fields=["staff"], name="idx_pfav_staff")]