from django.contrib.postgres.fields import ArrayField from django.db import models from core.enums import ( ClientActivityLevel, ClientBuyingPurpose, ClientGrade, ClientIdType, ClientInvalidReason, ClientPaymentMethod, ClientPropertiesOwned, ClientPropertyUsage, ClientStatus, ClientTransactedPropertyType, ClientTransactedType, ClientTransferToPublicType, ClientType, ) from core.models.base import AuditedModel class Client(AuditedModel): client_no = models.CharField( max_length=30, unique=True, verbose_name="客源编号", help_text="系统生成的客源编号,格式由运营配置(如 KY20260424001)", ) client_type = models.CharField( max_length=20, choices=ClientType.choices, default=ClientType.PRIVATE, verbose_name="客源分类", help_text="private=私客 / public=公客 / transacted=成交客", ) status = models.CharField( max_length=20, choices=ClientStatus.choices, default=ClientStatus.BUYING, verbose_name="客源状态", help_text="buying=求购 / renting=求租 / buy_or_rent=租购 / suspended=暂缓 / bought=已购 / rented_done=已租 / public=公客 / invalid=无效(详见 ENUMS)", ) grade = models.CharField( max_length=5, choices=ClientGrade.choices, default=ClientGrade.C, verbose_name="客源等级", help_text="A=A急迫 / B=较强 / C=一般 / D=较弱 / E=暂不关注", ) property_usage = models.CharField( max_length=30, choices=ClientPropertyUsage.choices, default=ClientPropertyUsage.RESIDENTIAL, verbose_name="房屋用途", help_text="residential=住宅 / villa=别墅 / commercial_residential=商住 / shop=商铺 / office=写字楼 / other=其他", ) buying_purpose = ArrayField( models.CharField(max_length=20, choices=ClientBuyingPurpose.choices), blank=True, default=list, verbose_name="购房目的", help_text="多选:rigid=刚需 / investment=投资 / school_district=学区 / upgrade=改善 / commercial=商用 / other=其他", ) payment_method = models.CharField( max_length=30, choices=ClientPaymentMethod.choices, blank=True, default="", verbose_name="付款方式", help_text="full=全额 / mortgage=商业贷款 / mortgage_fund=商贷+公积金 / fund=公积金", ) properties_owned = models.CharField( max_length=20, choices=ClientPropertiesOwned.choices, blank=True, default="", verbose_name="名下房产", help_text="none=无 / local_none=本地无外地有 / local_has=本地有", ) has_loan_record = models.BooleanField( null=True, blank=True, verbose_name="有无贷款记录", ) id_type = models.CharField( max_length=20, choices=ClientIdType.choices, blank=True, default="", verbose_name="证件类型", help_text="id_card=身份证 / passport=护照 / hk_macao=港澳台 / other=其他", ) id_number_enc = models.BinaryField( null=True, blank=True, verbose_name="证件号码(加密)", help_text="AES 加密存储", ) source = models.CharField( max_length=50, blank=True, default="", verbose_name="客户来源", help_text="lookup_items 维护", ) remarks = models.TextField( blank=True, default="", verbose_name="备注", help_text="最多200字", ) is_starred = models.BooleanField( default=False, verbose_name="是否收藏", help_text="快速标记,详细收藏夹用 client_folder_items", ) is_pinned = models.BooleanField( default=False, verbose_name="是否置顶", help_text="列表顶部置顶", ) is_big_value = models.BooleanField( default=False, verbose_name="是否大价值客户", help_text="影响筛选展示", ) is_protected = models.BooleanField( default=False, verbose_name="是否保护客", help_text="影响转公逻辑", ) prefers_new_house = models.BooleanField( null=True, blank=True, verbose_name="偏好新房", help_text="用于筛选", ) transfer_to_public_type = models.CharField( max_length=20, choices=ClientTransferToPublicType.choices, blank=True, default="", verbose_name="转公客方式", help_text="manual=手动转公 / auto=自动转公(超时) / marketing_jump=营销客跳公 / resource_public=资料客素公", ) transferred_public_at = models.DateTimeField( null=True, blank=True, verbose_name="进入公客池时间", ) invalid_reason = models.CharField( max_length=30, choices=ClientInvalidReason.choices, blank=True, default="", verbose_name="无效原因", help_text="invalid_phone=号码无效 / peer_agent=同行 / ad=广告推销 / no_intent=无意向 / other=其他", ) invalidated_at = models.DateTimeField( null=True, blank=True, verbose_name="标记无效时间", ) transacted_at = models.DateField( null=True, blank=True, verbose_name="成交日期", ) transacted_property = models.ForeignKey( "fonrey_property.Property", null=True, blank=True, on_delete=models.SET_NULL, related_name="transacted_clients", verbose_name="成交房源", help_text="成交关联的房源", ) transacted_price = models.DecimalField( max_digits=12, decimal_places=2, null=True, blank=True, verbose_name="成交价格", help_text="单位:万元", ) transacted_type = models.CharField( max_length=20, choices=ClientTransactedType.choices, blank=True, default="", verbose_name="成交类型", help_text="bought=我购 / rented=我租", ) transacted_property_type = models.CharField( max_length=20, choices=ClientTransactedPropertyType.choices, blank=True, default="", verbose_name="成交房源类型", help_text="second_hand=二手 / new_house=新房", ) first_recorder = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="first_recorded_clients", verbose_name="首录人", ) owner = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="owned_clients", verbose_name="归属人", help_text="私客独占跟进人", ) org_unit = models.ForeignKey( "org.OrgUnit", null=True, blank=True, on_delete=models.SET_NULL, related_name="clients", verbose_name="归属部门", help_text="冗余字段,加速筛选", ) activity_level = models.CharField( max_length=20, choices=ClientActivityLevel.choices, blank=True, default="", verbose_name="活跃度", help_text="new_matched=新配偶 / active_7d / active_30d / active_90d / expiring / frozen / invalid(异步计算)", ) last_active_at = models.DateTimeField( null=True, blank=True, verbose_name="最后有效跟进时间", help_text="触发器维护", ) last_follow_at = models.DateTimeField( null=True, blank=True, verbose_name="最后跟进时间", help_text="冗余字段,列表排序用", ) commission_date = models.DateField( null=True, blank=True, verbose_name="委托日期", ) entrust_count = models.SmallIntegerField( default=1, verbose_name="委托次数", help_text="成交后再委托则累加", ) version = models.IntegerField( default=1, verbose_name="版本号", help_text="乐观锁;每次 UPDATE +1;应用层检测 0 行受影响时抛 ConflictError", ) class Meta: db_table = "clients" verbose_name = "客源" verbose_name_plural = "客源" indexes = [ models.Index(fields=["client_type", "status"], name="idx_clients_type_stat"), models.Index(fields=["owner"], name="idx_clients_owner"), models.Index(fields=["org_unit"], name="idx_clients_org_unit"), models.Index( fields=["activity_level", "-last_active_at"], name="idx_clients_activity", ), models.Index(fields=["grade"], name="idx_clients_grade"), models.Index( fields=["-transferred_public_at"], name="idx_clients_transferred" ), models.Index(fields=["-last_follow_at"], name="idx_clients_last_follow"), ]