from django.contrib.postgres.fields import ArrayField from django.db import models from core.enums import ( ClientBuildingAgeRange, ClientContactGender, ClientDecoration, ClientFloorPreference, ClientOrientation, ClientRequirementType, ) from core.models.base import UUIDPrimaryKeyModel class ClientContact(UUIDPrimaryKeyModel): client = models.ForeignKey( "fonrey_client.Client", on_delete=models.CASCADE, related_name="contacts", verbose_name="所属客源", help_text="联系人随客源级联删除", ) sort_order = models.SmallIntegerField( default=0, verbose_name="排序顺序", help_text="sort_order=0 为主联系人,姓名用于客源姓名显示", ) name = models.CharField( max_length=50, verbose_name="联系人姓名", ) gender = models.CharField( max_length=10, choices=ClientContactGender.choices, default=ClientContactGender.MALE, verbose_name="性别", help_text="male=先生 / female=女士", ) phone_enc = models.BinaryField( verbose_name="手机号(加密)", help_text="AES-256-GCM 加密手机号(电话1)", ) phone_hash = models.CharField( max_length=64, verbose_name="手机号哈希", help_text="SHA-256 哈希(重复检测)", ) phone_country_code = models.CharField( max_length=10, default="+86", verbose_name="国际区号", ) phone_is_invalid = models.BooleanField( default=False, verbose_name="号码是否无效", help_text="标记无效后该号码不再参与重复检测", ) phone2_enc = models.BinaryField( null=True, blank=True, verbose_name="备用电话2(加密)", ) phone2_hash = models.CharField( max_length=64, blank=True, default="", verbose_name="备用电话2哈希", help_text="SHA-256,用于重复检测", ) wechat = models.CharField( max_length=100, blank=True, default="", verbose_name="微信号", ) qq = models.CharField( max_length=20, blank=True, default="", verbose_name="QQ号", ) remarks = models.CharField( max_length=200, blank=True, default="", verbose_name="联系人备注", help_text="最多200字", ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="创建时间", ) updated_at = models.DateTimeField( auto_now=True, verbose_name="最后更新时间", ) deleted_at = models.DateTimeField( null=True, blank=True, verbose_name="删除时间", help_text="软删除时间戳;NULL=未删除(不影响客源本身)", ) created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="created_client_contacts", verbose_name="创建人", ) class Meta: db_table = "client_contacts" verbose_name = "客源联系人" verbose_name_plural = "客源联系人" indexes = [ models.Index(fields=["phone_hash"], name="idx_cc_phone_hash"), models.Index(fields=["phone2_hash"], name="idx_cc_phone2_hash"), models.Index(fields=["client"], name="idx_cc_client"), ] class ClientRequirement(UUIDPrimaryKeyModel): client = models.ForeignKey( "fonrey_client.Client", on_delete=models.CASCADE, related_name="requirements", verbose_name="所属客源", help_text="需求随客源级联删除", ) requirement_type = models.CharField( max_length=20, choices=ClientRequirementType.choices, verbose_name="需求类型", help_text="second_hand=二手 / new_house=新房 / rental=租房", ) is_primary = models.BooleanField( default=True, verbose_name="是否主需求", help_text="用于列表展示", ) budget_min = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="最低预算", help_text="单位:万元/元,依据需求类型", ) budget_max = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="最高预算", ) area_min = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, verbose_name="最小面积", help_text="单位:㎡", ) area_max = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, verbose_name="最大面积", help_text="单位:㎡", ) bedroom_counts = ArrayField( models.SmallIntegerField(), blank=True, default=list, verbose_name="可接受卧室数", help_text="多选,如 [2,3]", ) floor_preferences = ArrayField( models.CharField(max_length=20, choices=ClientFloorPreference.choices), blank=True, default=list, verbose_name="楼层偏好", help_text="多选:no_first=不要一层 / low=低楼层 / mid=中楼层 / high=高楼层 / no_top=不要顶层", ) orientations = ArrayField( models.CharField(max_length=10, choices=ClientOrientation.choices), blank=True, default=list, verbose_name="朝向偏好", help_text="多选:east=东 / south=南 / west=西 / north=北", ) decorations = ArrayField( models.CharField(max_length=10, choices=ClientDecoration.choices), blank=True, default=list, verbose_name="装修偏好", help_text="多选(枚举同 properties.decoration)", ) building_age_ranges = ArrayField( models.CharField(max_length=20, choices=ClientBuildingAgeRange.choices), blank=True, default=list, verbose_name="楼龄偏好", help_text="多选:within_5y / 5_10y / 10_15y / 15_20y / over_20y", ) intent_district_ids = ArrayField( models.UUIDField(), blank=True, default=list, verbose_name="意向行政区", help_text="行政区 ID 数组", ) intent_business_area_ids = ArrayField( models.UUIDField(), blank=True, default=list, verbose_name="意向商圈", help_text="商圈 ID 数组", ) intent_complex_names = models.TextField( blank=True, default="", verbose_name="意向小区", help_text="文本,逗号分隔,最多500字", ) transportation = models.CharField( max_length=50, blank=True, default="", verbose_name="交通要求", help_text="最多50字", ) intent_school_names = models.TextField( blank=True, default="", verbose_name="意向学校", help_text="文本,逗号分隔", ) school_enrollment_date = models.DateField( null=True, blank=True, verbose_name="入学时间", help_text="月份精度,取该月1日存储", ) traffic_preference = models.TextField( blank=True, default="", verbose_name="交通备注", ) requirement_notes = models.CharField( max_length=200, blank=True, default="", verbose_name="需求备注", help_text="最多200字", ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="创建时间", ) updated_at = models.DateTimeField( auto_now=True, verbose_name="最后更新时间", ) class Meta: db_table = "client_requirements" verbose_name = "客源需求" verbose_name_plural = "客源需求" indexes = [ models.Index(fields=["client"], name="idx_creq_client"), models.Index(fields=["requirement_type", "client"], name="idx_creq_type"), models.Index(fields=["budget_min", "budget_max"], name="idx_creq_budget"), models.Index(fields=["area_min", "area_max"], name="idx_creq_area"), ] class ClientSchoolPreference(UUIDPrimaryKeyModel): requirement = models.ForeignKey( ClientRequirement, on_delete=models.CASCADE, related_name="school_preferences", verbose_name="所属需求", help_text="意向学校随需求级联删除", ) school_id = models.UUIDField( null=True, blank=True, verbose_name="学校ID", help_text="从学校表选择,允许为 NULL(自由输入)", ) school_name = models.CharField( max_length=100, verbose_name="学校名称", help_text="当 school_id 为 NULL 时为手动输入", ) created_at = models.DateTimeField( auto_now_add=True, verbose_name="创建时间", ) class Meta: db_table = "client_school_preferences" verbose_name = "意向学校" verbose_name_plural = "意向学校" indexes = [ models.Index(fields=["requirement"], name="idx_csp_requirement"), ]