# Generated by Django 4.2.16 on 2026-04-29 09:31 import django.contrib.postgres.fields from django.db import migrations, models import django.db.models.deletion import uuid class Migration(migrations.Migration): initial = True dependencies = [ ('org', '0001_initial'), ('fonrey_property', '0002_partitions_and_triggers'), ] operations = [ migrations.CreateModel( name='ClientFollowLog', fields=[ ('id', models.UUIDField(primary_key=True, serialize=False)), ('created_at', models.DateTimeField()), ('log_type', models.CharField(choices=[('written', '写入跟进'), ('modified', '修改跟进'), ('sensitive_view', '敏感查看'), ('other', '其他'), ('system', '系统')], max_length=30)), ('purpose', models.CharField(blank=True, default='', max_length=50)), ('content', models.TextField(blank=True, default='')), ('log_tag', models.CharField(blank=True, default='', max_length=50)), ('change_detail', models.JSONField(blank=True, null=True)), ('is_public', models.BooleanField(default=True)), ('is_deletable', models.BooleanField(default=True)), ('operator_snapshot', models.JSONField(blank=True, null=True)), ('deleted_at', models.DateTimeField(blank=True, null=True)), ], options={ 'db_table': 'client_follow_logs', 'managed': False, }, ), migrations.CreateModel( name='Client', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('deleted_at', models.DateTimeField(blank=True, db_index=True, null=True)), ('client_no', models.CharField(max_length=30, unique=True)), ('client_type', models.CharField(choices=[('private', '私客'), ('public', '公客'), ('transacted', '成交客')], default='private', max_length=20)), ('status', models.CharField(choices=[('buying', '求购'), ('renting', '求租'), ('buy_or_rent', '租购'), ('suspended', '暂缓'), ('bought', '已购'), ('rented_done', '已租'), ('public', '公客'), ('invalid', '无效')], default='buying', max_length=20)), ('grade', models.CharField(choices=[('A', 'A(急迫)'), ('B', 'B(较强)'), ('C', 'C(一般)'), ('D', 'D(较弱)'), ('E', 'E(暂不关注)')], default='C', max_length=5)), ('property_usage', models.CharField(choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('shop', '商铺'), ('office', '写字楼'), ('other', '其他')], default='residential', max_length=30)), ('buying_purpose', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('rigid', '刚需'), ('investment', '投资'), ('school_district', '学区'), ('upgrade', '改善'), ('commercial', '商用'), ('other', '其他')], max_length=20), blank=True, default=list, size=None)), ('payment_method', models.CharField(blank=True, choices=[('full', '全额'), ('mortgage', '商业贷款'), ('mortgage_fund', '商贷+公积金'), ('fund', '公积金')], default='', max_length=30)), ('properties_owned', models.CharField(blank=True, choices=[('none', '无'), ('local_none', '本地无/外地有'), ('local_has', '本地有')], default='', max_length=20)), ('has_loan_record', models.BooleanField(blank=True, null=True)), ('id_type', models.CharField(blank=True, choices=[('id_card', '身份证'), ('passport', '护照'), ('hk_macao', '港澳通行证'), ('other', '其他')], default='', max_length=20)), ('id_number_enc', models.BinaryField(blank=True, null=True)), ('source', models.CharField(blank=True, default='', max_length=50)), ('remarks', models.TextField(blank=True, default='')), ('is_starred', models.BooleanField(default=False)), ('is_pinned', models.BooleanField(default=False)), ('is_big_value', models.BooleanField(default=False)), ('is_protected', models.BooleanField(default=False)), ('prefers_new_house', models.BooleanField(blank=True, null=True)), ('transfer_to_public_type', models.CharField(blank=True, choices=[('manual', '手动转公'), ('auto', '自动转公'), ('marketing_jump', '营销客跳公'), ('resource_public', '资料客素公')], default='', max_length=20)), ('transferred_public_at', models.DateTimeField(blank=True, null=True)), ('invalid_reason', models.CharField(blank=True, choices=[('invalid_phone', '号码无效'), ('peer_agent', '同行'), ('ad', '广告推销'), ('no_intent', '无意向'), ('other', '其他')], default='', max_length=30)), ('invalidated_at', models.DateTimeField(blank=True, null=True)), ('transacted_at', models.DateField(blank=True, null=True)), ('transacted_price', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)), ('transacted_type', models.CharField(blank=True, choices=[('bought', '我购'), ('rented', '我租')], default='', max_length=20)), ('transacted_property_type', models.CharField(blank=True, choices=[('second_hand', '二手'), ('new_house', '新房')], default='', max_length=20)), ('activity_level', models.CharField(blank=True, choices=[('new_matched', '新配对'), ('active_7d', '7日活跃'), ('active_30d', '30日活跃'), ('active_90d', '90日活跃'), ('expiring', '即将过期'), ('frozen', '暂缓中'), ('invalid', '无效')], default='', max_length=20)), ('last_active_at', models.DateTimeField(blank=True, null=True)), ('last_follow_at', models.DateTimeField(blank=True, null=True)), ('commission_date', models.DateField(blank=True, null=True)), ('entrust_count', models.SmallIntegerField(default=1)), ('version', models.IntegerField(default=1)), ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_created', to='org.staff')), ('first_recorder', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='first_recorded_clients', to='org.staff')), ('org_unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clients', to='org.orgunit')), ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owned_clients', to='org.staff')), ('transacted_property', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transacted_clients', to='fonrey_property.property')), ('updated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_updated', to='org.staff')), ], options={ 'db_table': 'clients', }, ), migrations.CreateModel( name='ClientFavoriteFolder', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('name', models.CharField(max_length=10)), ('is_default', models.BooleanField(default=False)), ('sort_order', models.IntegerField(default=0)), ('created_at', models.DateTimeField(auto_now_add=True)), ('deleted_at', models.DateTimeField(blank=True, null=True)), ('staff', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorite_folders', to='org.staff')), ], options={ 'db_table': 'client_favorite_folders', }, ), migrations.CreateModel( name='ClientRequirement', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('requirement_type', models.CharField(choices=[('second_hand', '二手'), ('new_house', '新房'), ('rental', '租房')], max_length=20)), ('is_primary', models.BooleanField(default=True)), ('budget_min', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)), ('budget_max', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)), ('area_min', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True)), ('area_max', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True)), ('bedroom_counts', django.contrib.postgres.fields.ArrayField(base_field=models.SmallIntegerField(), blank=True, default=list, size=None)), ('floor_preferences', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('no_first', '不要一楼'), ('low', '低楼层'), ('mid', '中楼层'), ('high', '高楼层'), ('no_top', '不要顶楼')], max_length=20), blank=True, default=list, size=None)), ('orientations', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('east', '东'), ('south', '南'), ('west', '西'), ('north', '北')], max_length=10), blank=True, default=list, size=None)), ('decorations', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('rough', '毛坯'), ('plain', '清水'), ('simple', '简装'), ('medium', '中装'), ('fine', '精装'), ('luxury', '豪装')], max_length=10), blank=True, default=list, size=None)), ('building_age_ranges', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('within_5y', '5年内'), ('5_10y', '5-10年'), ('10_15y', '10-15年'), ('15_20y', '15-20年'), ('over_20y', '20年以上')], max_length=20), blank=True, default=list, size=None)), ('intent_district_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, size=None)), ('intent_business_area_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, size=None)), ('intent_complex_names', models.TextField(blank=True, default='')), ('transportation', models.CharField(blank=True, default='', max_length=50)), ('intent_school_names', models.TextField(blank=True, default='')), ('school_enrollment_date', models.DateField(blank=True, null=True)), ('traffic_preference', models.TextField(blank=True, default='')), ('requirement_notes', models.CharField(blank=True, default='', max_length=200)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requirements', to='fonrey_client.client')), ], options={ 'db_table': 'client_requirements', }, ), migrations.CreateModel( name='ClientPropertyMatch', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('match_source', models.CharField(choices=[('recorded', '录客配房'), ('system', '系统配房')], default='recorded', max_length=20)), ('match_group', models.CharField(blank=True, choices=[('quality_layout', '优质户型'), ('price_reduced', '降价'), ('hot', '热门'), ('newly_listed', '新上')], default='', max_length=30)), ('match_score', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True)), ('match_reasons', models.JSONField(blank=True, null=True)), ('status', models.CharField(choices=[('suggested', '待推送'), ('shared', '已分享'), ('rejected', '已反馈不合适'), ('viewed', '客户已查看')], default='suggested', max_length=20)), ('shared_at', models.DateTimeField(blank=True, null=True)), ('feedback', models.CharField(blank=True, default='', max_length=50)), ('calculated_at', models.DateTimeField(auto_now_add=True)), ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='property_matches', to='fonrey_client.client')), ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_matches', to='org.staff')), ('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='client_matches', to='fonrey_property.property')), ], options={ 'db_table': 'client_property_matches', }, ), migrations.CreateModel( name='ClientFollowLogAttachment', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('follow_log_id', models.UUIDField()), ('file_key', models.TextField()), ('file_name', models.CharField(max_length=255)), ('file_size', models.IntegerField()), ('file_type', models.CharField(blank=True, default='', max_length=10)), ('has_location', models.BooleanField(default=False)), ('sort_order', models.SmallIntegerField(default=0)), ('created_at', models.DateTimeField(auto_now_add=True)), ], options={ 'db_table': 'client_follow_log_attachments', 'indexes': [models.Index(fields=['follow_log_id'], name='idx_cfla_log')], }, ), migrations.CreateModel( name='ClientFolderItem', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('added_at', models.DateTimeField(auto_now_add=True)), ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='folder_items', to='fonrey_client.client')), ('folder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='fonrey_client.clientfavoritefolder')), ], options={ 'db_table': 'client_folder_items', }, ), migrations.CreateModel( name='ClientContact', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('sort_order', models.SmallIntegerField(default=0)), ('name', models.CharField(max_length=50)), ('gender', models.CharField(choices=[('male', '先生'), ('female', '女士')], default='male', max_length=10)), ('phone_enc', models.BinaryField()), ('phone_hash', models.CharField(max_length=64)), ('phone_country_code', models.CharField(default='+86', max_length=10)), ('phone_is_invalid', models.BooleanField(default=False)), ('phone2_enc', models.BinaryField(blank=True, null=True)), ('phone2_hash', models.CharField(blank=True, default='', max_length=64)), ('wechat', models.CharField(blank=True, default='', max_length=100)), ('qq', models.CharField(blank=True, default='', max_length=20)), ('remarks', models.CharField(blank=True, default='', max_length=200)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('deleted_at', models.DateTimeField(blank=True, null=True)), ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contacts', to='fonrey_client.client')), ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_client_contacts', to='org.staff')), ], options={ 'db_table': 'client_contacts', }, ), migrations.CreateModel( name='ClientViewing', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('viewing_type', models.CharField(choices=[('appointment', '预约'), ('viewing', '带看'), ('revisit', '复看'), ('empty', '空看')], default='viewing', max_length=20)), ('companion_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, size=None)), ('cooperator_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, size=None)), ('scheduled_at', models.DateTimeField(blank=True, null=True)), ('viewing_start_at', models.DateTimeField(blank=True, null=True)), ('viewing_end_at', models.DateTimeField(blank=True, null=True)), ('situation', models.TextField(blank=True, default='')), ('client_intent', models.CharField(blank=True, choices=[('interested', '感兴趣'), ('not_interested', '不感兴趣'), ('negotiating', '谈判中'), ('cancelled', '取消')], default='', max_length=20)), ('viewing_progress', models.SmallIntegerField(blank=True, null=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('deleted_at', models.DateTimeField(blank=True, null=True)), ('agent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='led_viewings', to='org.staff')), ('client', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='viewings', to='fonrey_client.client')), ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_client_viewings', to='org.staff')), ('property', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='client_viewings', to='fonrey_property.property')), ], options={ 'db_table': 'client_viewings', 'indexes': [models.Index(fields=['client', '-viewing_start_at'], name='idx_cv_client_time'), models.Index(fields=['property'], name='idx_cv_property'), models.Index(fields=['agent'], name='idx_cv_agent')], }, ), migrations.CreateModel( name='ClientStatusLog', fields=[ ('id', models.UUIDField(primary_key=True, serialize=False)), ('change_type', models.CharField(choices=[('status_change', '改状态'), ('grade_change', '改等级'), ('to_public', '转公客'), ('to_transacted', '转成交'), ('to_invalid', '转无效'), ('owner_change', '改归属人'), ('source_change', '改来源'), ('merge', '合并客源')], max_length=30)), ('old_value', models.JSONField(blank=True, null=True)), ('new_value', models.JSONField(blank=True, null=True)), ('reason', models.TextField(blank=True, default='')), ('operated_at', models.DateTimeField(auto_now_add=True)), ('client', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='status_logs', to='fonrey_client.client')), ('operator', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='client_status_changes', to='org.staff')), ], options={ 'db_table': 'client_status_logs', 'indexes': [models.Index(fields=['client', '-operated_at'], name='idx_csl_client'), models.Index(fields=['change_type', '-operated_at'], name='idx_csl_type')], }, ), migrations.CreateModel( name='ClientSchoolPreference', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('school_id', models.UUIDField(blank=True, null=True)), ('school_name', models.CharField(max_length=100)), ('created_at', models.DateTimeField(auto_now_add=True)), ('requirement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='school_preferences', to='fonrey_client.clientrequirement')), ], options={ 'db_table': 'client_school_preferences', 'indexes': [models.Index(fields=['requirement'], name='idx_csp_requirement')], }, ), migrations.AddIndex( model_name='clientrequirement', index=models.Index(fields=['client'], name='idx_creq_client'), ), migrations.AddIndex( model_name='clientrequirement', index=models.Index(fields=['requirement_type', 'client'], name='idx_creq_type'), ), migrations.AddIndex( model_name='clientrequirement', index=models.Index(fields=['budget_min', 'budget_max'], name='idx_creq_budget'), ), migrations.AddIndex( model_name='clientrequirement', index=models.Index(fields=['area_min', 'area_max'], name='idx_creq_area'), ), migrations.AddIndex( model_name='clientpropertymatch', index=models.Index(fields=['client', 'match_source', 'match_group'], name='idx_cpm_client_grp'), ), migrations.AddIndex( model_name='clientpropertymatch', index=models.Index(fields=['client', 'status'], name='idx_cpm_status'), ), migrations.AddConstraint( model_name='clientpropertymatch', constraint=models.UniqueConstraint(fields=('client', 'property'), name='uq_client_match_pair'), ), migrations.AddIndex( model_name='clientfolderitem', index=models.Index(fields=['client'], name='idx_cfi_client'), ), migrations.AddConstraint( model_name='clientfolderitem', constraint=models.UniqueConstraint(fields=('folder', 'client'), name='uq_cfi_folder_client'), ), migrations.AddIndex( model_name='clientfavoritefolder', index=models.Index(fields=['staff'], name='idx_cff_staff'), ), migrations.AddConstraint( model_name='clientfavoritefolder', constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True), ('is_default', True)), fields=('staff',), name='uq_cff_default_per_staff'), ), migrations.AddIndex( model_name='clientcontact', index=models.Index(fields=['phone_hash'], name='idx_cc_phone_hash'), ), migrations.AddIndex( model_name='clientcontact', index=models.Index(fields=['phone2_hash'], name='idx_cc_phone2_hash'), ), migrations.AddIndex( model_name='clientcontact', index=models.Index(fields=['client'], name='idx_cc_client'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['client_type', 'status'], name='idx_clients_type_stat'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['owner'], name='idx_clients_owner'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['org_unit'], name='idx_clients_org_unit'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['activity_level', '-last_active_at'], name='idx_clients_activity'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['grade'], name='idx_clients_grade'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['-transferred_public_at'], name='idx_clients_transferred'), ), migrations.AddIndex( model_name='client', index=models.Index(fields=['-last_follow_at'], name='idx_clients_last_follow'), ), ]