from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVectorField from django.db import models from core.enums import ( PropertyAttribute, PropertyContactGender, PropertyContactIdentity, PropertyDecoration, PropertyGrade, PropertyHouseStatus, PropertyOrientation, PropertyOwnershipNature, PropertyPaymentMethod, PropertyShopLocation, PropertyStatus, PropertyTaxIncluded, PropertyType, PropertyViewingTime, ) from core.models.base import SoftDeleteModel, TimeStampedModel, UUIDPrimaryKeyModel class Property(SoftDeleteModel): property_type = models.CharField(max_length=30, choices=PropertyType.choices) status = models.CharField( max_length=20, choices=PropertyStatus.choices, default=PropertyStatus.FOR_SALE, ) attribute = models.CharField( max_length=10, choices=PropertyAttribute.choices, default=PropertyAttribute.PUBLIC, ) private_reason = models.TextField(blank=True, default="") complex = models.ForeignKey( "fonrey_complex.Complex", on_delete=models.RESTRICT, related_name="properties", ) building = models.ForeignKey( "fonrey_complex.Building", null=True, blank=True, on_delete=models.SET_NULL, related_name="properties", ) block_no = models.CharField(max_length=30, blank=True, default="") unit_no = models.CharField(max_length=30, blank=True, default="") room_no = models.CharField(max_length=30, blank=True, default="") floor = models.SmallIntegerField() total_floors = models.SmallIntegerField() bedroom_count = models.SmallIntegerField(default=0) living_room_count = models.SmallIntegerField(default=0) bathroom_count = models.SmallIntegerField(default=0) kitchen_count = models.SmallIntegerField(default=0) balcony_count = models.SmallIntegerField(default=0) area = models.DecimalField(max_digits=8, decimal_places=2) inner_area = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True) sale_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) sale_bottom_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) sale_record_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) rent_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) orientation = models.CharField( max_length=15, blank=True, default="", choices=PropertyOrientation.choices ) decoration = models.CharField( max_length=10, blank=True, default="", choices=PropertyDecoration.choices ) has_elevator = models.BooleanField(null=True, blank=True) built_year = models.SmallIntegerField(null=True, blank=True) usage_type = models.CharField(max_length=30, blank=True, default="") usage_subtype = models.CharField(max_length=30, blank=True, default="") shop_frontage = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) shop_depth = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) shop_height = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) shop_location = models.CharField( max_length=20, blank=True, default="", choices=PropertyShopLocation.choices ) house_status = models.CharField( max_length=20, blank=True, default="", choices=PropertyHouseStatus.choices ) viewing_time = models.CharField( max_length=20, blank=True, default="", choices=PropertyViewingTime.choices ) grade = models.CharField(max_length=2, blank=True, default="", choices=PropertyGrade.choices) ownership_years = models.CharField(max_length=30, blank=True, default="") ownership_years_detail = models.CharField(max_length=20, blank=True, default="") ownership_nature = models.CharField( max_length=20, blank=True, default="", choices=PropertyOwnershipNature.choices ) is_only_house = models.BooleanField(null=True, blank=True) payment_method = models.CharField( max_length=15, blank=True, default="", choices=PropertyPaymentMethod.choices ) tax_included = models.CharField( max_length=15, blank=True, default="", choices=PropertyTaxIncluded.choices ) has_mortgage = models.BooleanField(null=True, blank=True) has_loan = models.BooleanField(null=True, blank=True) has_seal = models.BooleanField(null=True, blank=True) has_restriction = models.BooleanField(null=True, blank=True) original_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) sale_reason = models.TextField(blank=True, default="") remarks = models.TextField(blank=True, default="") first_recorder = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="first_recorded_properties", ) number_holder = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="held_properties", ) seller_agent = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="selling_properties", ) buyer_agent = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="buying_properties", ) source = models.CharField(max_length=50, blank=True, default="") completeness_score = models.SmallIntegerField(default=0) listed_at = models.DateTimeField(null=True, blank=True) last_followed_at = models.DateTimeField(null=True, blank=True) created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="created_properties", ) updated_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="updated_properties", ) search_vector = SearchVectorField(null=True, blank=True) version = models.IntegerField(default=1) class Meta: db_table = "properties" constraints = [ models.CheckConstraint( check=models.Q(floor__gt=0) & models.Q(floor__lte=models.F("total_floors")), name="chk_property_floor", ), ] indexes = [ GinIndex(fields=["search_vector"], name="idx_properties_search"), models.Index(fields=["complex"], name="idx_properties_complex"), models.Index(fields=["status"], name="idx_properties_status"), models.Index(fields=["sale_price"], name="idx_properties_sale_price"), models.Index(fields=["area"], name="idx_properties_area"), models.Index(fields=["listed_at"], name="idx_properties_listed_at"), models.Index(fields=["last_followed_at"], name="idx_properties_last_followed"), models.Index(fields=["bedroom_count"], name="idx_properties_bedroom"), models.Index(fields=["grade"], name="idx_properties_grade"), models.Index(fields=["completeness_score"], name="idx_properties_completeness"), models.Index(fields=["seller_agent"], name="idx_properties_seller_agent"), models.Index(fields=["number_holder"], name="idx_properties_number_holder"), models.Index( fields=["status", "attribute", "complex", "sale_price"], name="idx_properties_list_composite", ), models.Index( fields=["seller_agent", "status", "listed_at"], name="idx_properties_my_properties", ), ] class PropertyContact(SoftDeleteModel): property = models.ForeignKey( Property, on_delete=models.CASCADE, related_name="contacts" ) name = models.CharField(max_length=50) gender = models.CharField( max_length=10, choices=PropertyContactGender.choices, default=PropertyContactGender.MALE ) identity = models.CharField( max_length=20, choices=PropertyContactIdentity.choices, default=PropertyContactIdentity.CONTACT, ) phone_enc = models.BinaryField() phone_hash = models.CharField(max_length=64) phone2_enc = models.BinaryField(null=True, blank=True) phone2_hash = models.CharField(max_length=64, blank=True, default="") wechat = models.CharField(max_length=100, blank=True, default="") qq = models.CharField(max_length=20, blank=True, default="") remarks = models.TextField(blank=True, default="") is_number_holder = models.BooleanField(default=False) number_holder_approved_at = models.DateTimeField(null=True, blank=True) sort_order = models.IntegerField(default=0) created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="created_property_contacts", ) updated_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="updated_property_contacts", ) class Meta: db_table = "property_contacts" indexes = [ models.Index(fields=["property"], name="idx_pc_property"), models.Index(fields=["phone_hash"], name="idx_pc_phone_hash"), models.Index(fields=["phone2_hash"], name="idx_pc_phone2_hash"), ] class PropertyMarketing(UUIDPrimaryKeyModel): property = models.OneToOneField( Property, on_delete=models.CASCADE, related_name="marketing" ) marketing_title = models.CharField(max_length=30, blank=True, default="") core_selling_points = models.TextField(blank=True, default="") owner_attitude = models.TextField(blank=True, default="") layout_description = models.TextField(blank=True, default="") complex_description = models.TextField(blank=True, default="") ai_generated_points = models.BooleanField(default=False) ai_generated_attitude = models.BooleanField(default=False) updated_at = models.DateTimeField(auto_now=True) updated_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL ) class Meta: db_table = "property_marketing" class PropertyCertificate(UUIDPrimaryKeyModel): property = models.OneToOneField( Property, on_delete=models.CASCADE, related_name="certificate" ) owner_name = models.CharField(max_length=100, blank=True, default="") owner_id_number = models.CharField(max_length=50, blank=True, default="") owner_cert_type = models.CharField(max_length=20, blank=True, default="") property_location = models.CharField(max_length=500, blank=True, default="") cert_status = models.CharField(max_length=30, blank=True, default="") cert_no = models.CharField(max_length=100, blank=True, default="") first_registered_at = models.DateField(null=True, blank=True) ownership_nature = models.CharField(max_length=30, blank=True, default="") land_nature = models.CharField(max_length=30, blank=True, default="") updated_at = models.DateTimeField(auto_now=True) updated_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL ) class Meta: db_table = "property_certificates" class PropertyCompleteness(UUIDPrimaryKeyModel): property = models.OneToOneField( Property, on_delete=models.CASCADE, related_name="completeness" ) score_core_info = models.SmallIntegerField(default=0) score_attachment = models.SmallIntegerField(default=0) score_survey = models.SmallIntegerField(default=0) score_vr = models.SmallIntegerField(default=0) score_key = models.SmallIntegerField(default=0) score_commission = models.SmallIntegerField(default=0) score_verification = models.SmallIntegerField(default=0) score_follow_up = models.SmallIntegerField(default=0) score_viewing = models.SmallIntegerField(default=0) score_other = models.SmallIntegerField(default=0) total_score = models.SmallIntegerField(default=0) calculated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "property_completeness" class PropertyProtection(UUIDPrimaryKeyModel): property = models.OneToOneField( Property, on_delete=models.CASCADE, related_name="protection" ) is_protected = models.BooleanField(default=False) reason = models.TextField(blank=True, default="") start_at = models.DateTimeField(null=True, blank=True) end_at = models.DateTimeField(null=True, blank=True) set_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL ) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "property_protections"