From d00ff12ba04c910ba8c2296527d2246eb4eef872 Mon Sep 17 00:00:00 2001 From: ishenwei Date: Thu, 30 Apr 2026 09:47:44 +0800 Subject: [PATCH] feat(migrations): add Phase 4.0+4.1 verbose_name/help_text migrations Generated by manage.py makemigrations after Phase 4.0 (model Meta verbose_name) and Phase 4.1 (field-level verbose_name/help_text) were committed across all 9 apps. Field-meta only (Alter field on Meta options); no schema changes. --- ...lter_loginattempt_attempted_at_and_more.py | 146 ++ ...04_alter_client_activity_level_and_more.py | 667 +++++++++ ...lt_year_alter_building_complex_and_more.py | 528 +++++++ ...003_alter_orgunit_address_city_and_more.py | 626 +++++++++ ...ter_permissionchangelog_action_and_more.py | 336 +++++ .../0004_alter_commission_agent_and_more.py | 1232 +++++++++++++++++ ...03_alter_businessarea_district_and_more.py | 154 +++ ...eldrequirementrule_entity_type_and_more.py | 160 +++ ...n_options_alter_tenant_options_and_more.py | 31 + 9 files changed, 3880 insertions(+) create mode 100644 apps/account/migrations/0004_alter_loginattempt_attempted_at_and_more.py create mode 100644 apps/client/migrations/0004_alter_client_activity_level_and_more.py create mode 100644 apps/complex/migrations/0004_alter_building_built_year_alter_building_complex_and_more.py create mode 100644 apps/org/migrations/0003_alter_orgunit_address_city_and_more.py create mode 100644 apps/permission/migrations/0003_alter_permissionchangelog_action_and_more.py create mode 100644 apps/property/migrations/0004_alter_commission_agent_and_more.py create mode 100644 apps/region/migrations/0003_alter_businessarea_district_and_more.py create mode 100644 apps/setting/migrations/0003_alter_fieldrequirementrule_entity_type_and_more.py create mode 100644 apps/tenant/migrations/0002_alter_domain_options_alter_tenant_options_and_more.py diff --git a/apps/account/migrations/0004_alter_loginattempt_attempted_at_and_more.py b/apps/account/migrations/0004_alter_loginattempt_attempted_at_and_more.py new file mode 100644 index 0000000..065a694 --- /dev/null +++ b/apps/account/migrations/0004_alter_loginattempt_attempted_at_and_more.py @@ -0,0 +1,146 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('org', '0003_alter_orgunit_address_city_and_more'), + ('account', '0003_alter_loginattempt_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='loginattempt', + name='attempted_at', + field=models.DateTimeField(auto_now_add=True, help_text='分区键,按月分区', verbose_name='尝试时间'), + ), + migrations.AlterField( + model_name='loginattempt', + name='failure_reason', + field=models.CharField(blank=True, choices=[('wrong_password', '用户名或密码错误'), ('wrong_captcha', '验证码错误'), ('account_locked', '账号锁定'), ('account_disabled', '账号停用'), ('tenant_not_found', '租户不存在')], help_text='wrong_password=密码错误 / wrong_captcha=验证码失败 / account_locked=账号锁定 / account_disabled=账号停用 / tenant_not_found=租户不存在', max_length=30, null=True, verbose_name='失败原因'), + ), + migrations.AlterField( + model_name='loginattempt', + name='ip_address', + field=models.GenericIPAddressField(help_text='支持 IPv4/IPv6', verbose_name='来源 IP'), + ), + migrations.AlterField( + model_name='loginattempt', + name='success', + field=models.BooleanField(verbose_name='是否登录成功'), + ), + migrations.AlterField( + model_name='loginattempt', + name='user_agent', + field=models.TextField(blank=True, help_text='Electron 版本信息', null=True, verbose_name='客户端 UA'), + ), + migrations.AlterField( + model_name='loginattempt', + name='username', + field=models.CharField(help_text='冗余存储,即使账号不存在也记录', max_length=30, verbose_name='登录用户名'), + ), + migrations.AlterField( + model_name='passwordhistory', + name='created_at', + field=models.DateTimeField(auto_now_add=True, help_text='密码修改时间', verbose_name='记录时间'), + ), + migrations.AlterField( + model_name='passwordhistory', + name='password_hash', + field=models.CharField(help_text='PBKDF2+SHA256 哈希值', max_length=128, verbose_name='密码哈希'), + ), + migrations.AlterField( + model_name='passwordhistory', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='password_histories', to=settings.AUTH_USER_MODEL, verbose_name='关联账号'), + ), + migrations.AlterField( + model_name='passwordresettoken', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='passwordresettoken', + name='expires_at', + field=models.DateTimeField(help_text='created_at + 30 分钟', verbose_name='过期时间'), + ), + migrations.AlterField( + model_name='passwordresettoken', + name='is_used', + field=models.BooleanField(default=False, help_text='使用后立即置 True,防止重放攻击', verbose_name='是否已使用'), + ), + migrations.AlterField( + model_name='passwordresettoken', + name='token', + field=models.CharField(help_text='secrets.token_urlsafe(64) 生成(86 字符),全局唯一', max_length=86, unique=True, verbose_name='令牌'), + ), + migrations.AlterField( + model_name='passwordresettoken', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reset_tokens', to=settings.AUTH_USER_MODEL, verbose_name='关联账号'), + ), + migrations.AlterField( + model_name='useraccount', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='useraccount', + name='created_by', + field=models.ForeignKey(blank=True, help_text='普通员工由 Tenant Admin 创建;Tenant Admin 由平台运营创建(可为 NULL)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_accounts', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AlterField( + model_name='useraccount', + name='email', + field=models.EmailField(blank=True, help_text='用于找回密码/用户名;为空则无法自助找回;同租户唯一', max_length=254, null=True, verbose_name='绑定邮箱'), + ), + migrations.AlterField( + model_name='useraccount', + name='is_initial_password', + field=models.BooleanField(default=True, help_text='True 时登录成功后强制跳转修改密码页,不可跳过', verbose_name='是否初始密码'), + ), + migrations.AlterField( + model_name='useraccount', + name='is_tenant_admin', + field=models.BooleanField(default=False, help_text='每个租户最多 1 个(应用层约束)', verbose_name='是否租户超管'), + ), + migrations.AlterField( + model_name='useraccount', + name='locked_until', + field=models.DateTimeField(blank=True, help_text='到期后应用层将 status 恢复 active', null=True, verbose_name='锁定到期时间'), + ), + migrations.AlterField( + model_name='useraccount', + name='phone_enc', + field=models.TextField(blank=True, help_text='AES-256-GCM 加密密文;普通员工必填', null=True, verbose_name='手机号(加密)'), + ), + migrations.AlterField( + model_name='useraccount', + name='phone_hash', + field=models.CharField(blank=True, help_text='SHA-256 哈希;用于唯一性校验和查询;不可反推原文', max_length=64, null=True, verbose_name='手机号哈希'), + ), + migrations.AlterField( + model_name='useraccount', + name='staff', + field=models.OneToOneField(blank=True, help_text='员工档案绑定(1:1);普通员工必须有值;Tenant Admin 可为空', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='account', to='org.staff', verbose_name='员工档案'), + ), + migrations.AlterField( + model_name='useraccount', + name='status', + field=models.CharField(choices=[('active', '启用'), ('disabled', '停用'), ('locked', '锁定')], default='active', help_text='active=正常 / disabled=停用 / locked=锁定(30 分钟自动恢复)', max_length=10, verbose_name='账号状态'), + ), + migrations.AlterField( + model_name='useraccount', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='useraccount', + name='username', + field=models.CharField(help_text='普通员工=手机号(11位数字) / Tenant Admin=自定义(字母开头6~30位);创建后不可更改', max_length=30, verbose_name='登录名'), + ), + ] diff --git a/apps/client/migrations/0004_alter_client_activity_level_and_more.py b/apps/client/migrations/0004_alter_client_activity_level_and_more.py new file mode 100644 index 0000000..066e8cc --- /dev/null +++ b/apps/client/migrations/0004_alter_client_activity_level_and_more.py @@ -0,0 +1,667 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('org', '0003_alter_orgunit_address_city_and_more'), + ('fonrey_property', '0004_alter_commission_agent_and_more'), + ('fonrey_client', '0003_alter_client_options_alter_clientcontact_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='client', + name='activity_level', + field=models.CharField(blank=True, choices=[('new_matched', '新配对'), ('active_7d', '7日活跃'), ('active_30d', '30日活跃'), ('active_90d', '90日活跃'), ('expiring', '即将过期'), ('frozen', '暂缓中'), ('invalid', '无效')], default='', help_text='new_matched=新配偶 / active_7d / active_30d / active_90d / expiring / frozen / invalid(异步计算)', max_length=20, verbose_name='活跃度'), + ), + migrations.AlterField( + model_name='client', + name='buying_purpose', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('rigid', '刚需'), ('investment', '投资'), ('school_district', '学区'), ('upgrade', '改善'), ('commercial', '商用'), ('other', '其他')], max_length=20), blank=True, default=list, help_text='多选:rigid=刚需 / investment=投资 / school_district=学区 / upgrade=改善 / commercial=商用 / other=其他', size=None, verbose_name='购房目的'), + ), + migrations.AlterField( + model_name='client', + name='client_no', + field=models.CharField(help_text='系统生成的客源编号,格式由运营配置(如 KY20260424001)', max_length=30, unique=True, verbose_name='客源编号'), + ), + migrations.AlterField( + model_name='client', + name='client_type', + field=models.CharField(choices=[('private', '私客'), ('public', '公客'), ('transacted', '成交客')], default='private', help_text='private=私客 / public=公客 / transacted=成交客', max_length=20, verbose_name='客源分类'), + ), + migrations.AlterField( + model_name='client', + name='commission_date', + field=models.DateField(blank=True, null=True, verbose_name='委托日期'), + ), + migrations.AlterField( + model_name='client', + name='entrust_count', + field=models.SmallIntegerField(default=1, help_text='成交后再委托则累加', verbose_name='委托次数'), + ), + migrations.AlterField( + model_name='client', + name='first_recorder', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='first_recorded_clients', to='org.staff', verbose_name='首录人'), + ), + migrations.AlterField( + model_name='client', + name='grade', + field=models.CharField(choices=[('A', 'A(急迫)'), ('B', 'B(较强)'), ('C', 'C(一般)'), ('D', 'D(较弱)'), ('E', 'E(暂不关注)')], default='C', help_text='A=A急迫 / B=较强 / C=一般 / D=较弱 / E=暂不关注', max_length=5, verbose_name='客源等级'), + ), + migrations.AlterField( + model_name='client', + name='has_loan_record', + field=models.BooleanField(blank=True, null=True, verbose_name='有无贷款记录'), + ), + migrations.AlterField( + model_name='client', + name='id_number_enc', + field=models.BinaryField(blank=True, help_text='AES 加密存储', null=True, verbose_name='证件号码(加密)'), + ), + migrations.AlterField( + model_name='client', + name='id_type', + field=models.CharField(blank=True, choices=[('id_card', '身份证'), ('passport', '护照'), ('hk_macao', '港澳通行证'), ('other', '其他')], default='', help_text='id_card=身份证 / passport=护照 / hk_macao=港澳台 / other=其他', max_length=20, verbose_name='证件类型'), + ), + migrations.AlterField( + model_name='client', + name='invalid_reason', + field=models.CharField(blank=True, choices=[('invalid_phone', '号码无效'), ('peer_agent', '同行'), ('ad', '广告推销'), ('no_intent', '无意向'), ('other', '其他')], default='', help_text='invalid_phone=号码无效 / peer_agent=同行 / ad=广告推销 / no_intent=无意向 / other=其他', max_length=30, verbose_name='无效原因'), + ), + migrations.AlterField( + model_name='client', + name='invalidated_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='标记无效时间'), + ), + migrations.AlterField( + model_name='client', + name='is_big_value', + field=models.BooleanField(default=False, help_text='影响筛选展示', verbose_name='是否大价值客户'), + ), + migrations.AlterField( + model_name='client', + name='is_pinned', + field=models.BooleanField(default=False, help_text='列表顶部置顶', verbose_name='是否置顶'), + ), + migrations.AlterField( + model_name='client', + name='is_protected', + field=models.BooleanField(default=False, help_text='影响转公逻辑', verbose_name='是否保护客'), + ), + migrations.AlterField( + model_name='client', + name='is_starred', + field=models.BooleanField(default=False, help_text='快速标记,详细收藏夹用 client_folder_items', verbose_name='是否收藏'), + ), + migrations.AlterField( + model_name='client', + name='last_active_at', + field=models.DateTimeField(blank=True, help_text='触发器维护', null=True, verbose_name='最后有效跟进时间'), + ), + migrations.AlterField( + model_name='client', + name='last_follow_at', + field=models.DateTimeField(blank=True, help_text='冗余字段,列表排序用', null=True, verbose_name='最后跟进时间'), + ), + migrations.AlterField( + model_name='client', + name='org_unit', + field=models.ForeignKey(blank=True, help_text='冗余字段,加速筛选', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clients', to='org.orgunit', verbose_name='归属部门'), + ), + migrations.AlterField( + model_name='client', + name='owner', + field=models.ForeignKey(blank=True, help_text='私客独占跟进人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owned_clients', to='org.staff', verbose_name='归属人'), + ), + migrations.AlterField( + model_name='client', + name='payment_method', + field=models.CharField(blank=True, choices=[('full', '全额'), ('mortgage', '商业贷款'), ('mortgage_fund', '商贷+公积金'), ('fund', '公积金')], default='', help_text='full=全额 / mortgage=商业贷款 / mortgage_fund=商贷+公积金 / fund=公积金', max_length=30, verbose_name='付款方式'), + ), + migrations.AlterField( + model_name='client', + name='prefers_new_house', + field=models.BooleanField(blank=True, help_text='用于筛选', null=True, verbose_name='偏好新房'), + ), + migrations.AlterField( + model_name='client', + name='properties_owned', + field=models.CharField(blank=True, choices=[('none', '无'), ('local_none', '本地无/外地有'), ('local_has', '本地有')], default='', help_text='none=无 / local_none=本地无外地有 / local_has=本地有', max_length=20, verbose_name='名下房产'), + ), + migrations.AlterField( + model_name='client', + name='property_usage', + field=models.CharField(choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('shop', '商铺'), ('office', '写字楼'), ('other', '其他')], default='residential', help_text='residential=住宅 / villa=别墅 / commercial_residential=商住 / shop=商铺 / office=写字楼 / other=其他', max_length=30, verbose_name='房屋用途'), + ), + migrations.AlterField( + model_name='client', + name='remarks', + field=models.TextField(blank=True, default='', help_text='最多200字', verbose_name='备注'), + ), + migrations.AlterField( + model_name='client', + name='source', + field=models.CharField(blank=True, default='', help_text='lookup_items 维护', max_length=50, verbose_name='客户来源'), + ), + migrations.AlterField( + model_name='client', + name='status', + field=models.CharField(choices=[('buying', '求购'), ('renting', '求租'), ('buy_or_rent', '租购'), ('suspended', '暂缓'), ('bought', '已购'), ('rented_done', '已租'), ('public', '公客'), ('invalid', '无效')], default='buying', help_text='buying=求购 / renting=求租 / buy_or_rent=租购 / suspended=暂缓 / bought=已购 / rented_done=已租 / public=公客 / invalid=无效(详见 ENUMS)', max_length=20, verbose_name='客源状态'), + ), + migrations.AlterField( + model_name='client', + name='transacted_at', + field=models.DateField(blank=True, null=True, verbose_name='成交日期'), + ), + migrations.AlterField( + model_name='client', + name='transacted_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:万元', max_digits=12, null=True, verbose_name='成交价格'), + ), + migrations.AlterField( + model_name='client', + name='transacted_property', + field=models.ForeignKey(blank=True, help_text='成交关联的房源', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transacted_clients', to='fonrey_property.property', verbose_name='成交房源'), + ), + migrations.AlterField( + model_name='client', + name='transacted_property_type', + field=models.CharField(blank=True, choices=[('second_hand', '二手'), ('new_house', '新房')], default='', help_text='second_hand=二手 / new_house=新房', max_length=20, verbose_name='成交房源类型'), + ), + migrations.AlterField( + model_name='client', + name='transacted_type', + field=models.CharField(blank=True, choices=[('bought', '我购'), ('rented', '我租')], default='', help_text='bought=我购 / rented=我租', max_length=20, verbose_name='成交类型'), + ), + migrations.AlterField( + model_name='client', + name='transfer_to_public_type', + field=models.CharField(blank=True, choices=[('manual', '手动转公'), ('auto', '自动转公'), ('marketing_jump', '营销客跳公'), ('resource_public', '资料客素公')], default='', help_text='manual=手动转公 / auto=自动转公(超时) / marketing_jump=营销客跳公 / resource_public=资料客素公', max_length=20, verbose_name='转公客方式'), + ), + migrations.AlterField( + model_name='client', + name='transferred_public_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='进入公客池时间'), + ), + migrations.AlterField( + model_name='client', + name='version', + field=models.IntegerField(default=1, help_text='乐观锁;每次 UPDATE +1;应用层检测 0 行受影响时抛 ConflictError', verbose_name='版本号'), + ), + migrations.AlterField( + model_name='clientcontact', + name='client', + field=models.ForeignKey(help_text='联系人随客源级联删除', on_delete=django.db.models.deletion.CASCADE, related_name='contacts', to='fonrey_client.client', verbose_name='所属客源'), + ), + migrations.AlterField( + model_name='clientcontact', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientcontact', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_client_contacts', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='clientcontact', + name='deleted_at', + field=models.DateTimeField(blank=True, help_text='软删除时间戳;NULL=未删除(不影响客源本身)', null=True, verbose_name='删除时间'), + ), + migrations.AlterField( + model_name='clientcontact', + name='gender', + field=models.CharField(choices=[('male', '先生'), ('female', '女士')], default='male', help_text='male=先生 / female=女士', max_length=10, verbose_name='性别'), + ), + migrations.AlterField( + model_name='clientcontact', + name='name', + field=models.CharField(max_length=50, verbose_name='联系人姓名'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone2_enc', + field=models.BinaryField(blank=True, null=True, verbose_name='备用电话2(加密)'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone2_hash', + field=models.CharField(blank=True, default='', help_text='SHA-256,用于重复检测', max_length=64, verbose_name='备用电话2哈希'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone_country_code', + field=models.CharField(default='+86', max_length=10, verbose_name='国际区号'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone_enc', + field=models.BinaryField(help_text='AES-256-GCM 加密手机号(电话1)', verbose_name='手机号(加密)'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone_hash', + field=models.CharField(help_text='SHA-256 哈希(重复检测)', max_length=64, verbose_name='手机号哈希'), + ), + migrations.AlterField( + model_name='clientcontact', + name='phone_is_invalid', + field=models.BooleanField(default=False, help_text='标记无效后该号码不再参与重复检测', verbose_name='号码是否无效'), + ), + migrations.AlterField( + model_name='clientcontact', + name='qq', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='QQ号'), + ), + migrations.AlterField( + model_name='clientcontact', + name='remarks', + field=models.CharField(blank=True, default='', help_text='最多200字', max_length=200, verbose_name='联系人备注'), + ), + migrations.AlterField( + model_name='clientcontact', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='sort_order=0 为主联系人,姓名用于客源姓名显示', verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='clientcontact', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='clientcontact', + name='wechat', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='微信号'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='deleted_at', + field=models.DateTimeField(blank=True, help_text='软删除时间戳;NULL=未删除', null=True, verbose_name='删除时间'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='is_default', + field=models.BooleanField(default=False, help_text='系统默认收藏夹,每个经纪人只能有一个', verbose_name='是否默认'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='name', + field=models.CharField(help_text='最多10字', max_length=10, verbose_name='收藏夹名称'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='sort_order', + field=models.IntegerField(default=0, help_text='升序排列', verbose_name='显示顺序'), + ), + migrations.AlterField( + model_name='clientfavoritefolder', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorite_folders', to='org.staff', verbose_name='所属经纪人'), + ), + migrations.AlterField( + model_name='clientfolderitem', + name='added_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='加入收藏夹时间'), + ), + migrations.AlterField( + model_name='clientfolderitem', + name='client', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='folder_items', to='fonrey_client.client', verbose_name='被收藏的客源'), + ), + migrations.AlterField( + model_name='clientfolderitem', + name='folder', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='fonrey_client.clientfavoritefolder', verbose_name='所属收藏夹'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='file_key', + field=models.TextField(help_text='R2/S3 存储路径', verbose_name='文件存储路径'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='file_name', + field=models.CharField(help_text='原始文件名(用于展示和下载)', max_length=255, verbose_name='文件名'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='file_size', + field=models.IntegerField(help_text='单位:bytes,最大 20MB', verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='file_type', + field=models.CharField(blank=True, default='', help_text='bmp / jpg / png / gif', max_length=10, verbose_name='文件类型'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='follow_log_id', + field=models.UUIDField(help_text='跨分区 FK;不通过 Django FK 强制约束', verbose_name='所属跟进日志ID'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='has_location', + field=models.BooleanField(default=False, help_text='是否含 GPS 位置信息', verbose_name='是否含位置信息'), + ), + migrations.AlterField( + model_name='clientfollowlogattachment', + name='sort_order', + field=models.SmallIntegerField(default=0, verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='calculated_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='配房计算时间'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='client', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='property_matches', to='fonrey_client.client', verbose_name='所属客源'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='created_by', + field=models.ForeignKey(blank=True, help_text='触发配房操作的员工(录客配房时记录,系统配房可为NULL)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_matches', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='feedback', + field=models.CharField(blank=True, default='', help_text='lookup_items 维护', max_length=50, verbose_name='反馈原因'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='match_group', + field=models.CharField(blank=True, choices=[('quality_layout', '优质户型'), ('price_reduced', '降价'), ('hot', '热门'), ('newly_listed', '新上')], default='', help_text='quality_layout=优质户型 / price_reduced=降价 / hot=热门 / newly_listed=新上', max_length=30, verbose_name='匹配分组'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='match_reasons', + field=models.JSONField(blank=True, help_text='格式:[{"key": "budget", "match": true}, ...]', null=True, verbose_name='匹配原因详情'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='match_score', + field=models.DecimalField(blank=True, decimal_places=2, help_text='0-100', max_digits=5, null=True, verbose_name='匹配度评分'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='match_source', + field=models.CharField(choices=[('recorded', '录客配房'), ('system', '系统配房')], default='recorded', help_text='recorded=录客配房(基于录入需求) / system=系统配房(算法推荐)', max_length=20, verbose_name='匹配来源'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='client_matches', to='fonrey_property.property', verbose_name='匹配房源'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='shared_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='分享时间'), + ), + migrations.AlterField( + model_name='clientpropertymatch', + name='status', + field=models.CharField(choices=[('suggested', '待推送'), ('shared', '已分享'), ('rejected', '已反馈不合适'), ('viewed', '客户已查看')], default='suggested', help_text='suggested=待推送 / shared=已分享 / rejected=已反馈不合适 / viewed=客户已查看', max_length=20, verbose_name='状态'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='area_max', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:㎡', max_digits=8, null=True, verbose_name='最大面积'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='area_min', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:㎡', max_digits=8, null=True, verbose_name='最小面积'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='bedroom_counts', + field=django.contrib.postgres.fields.ArrayField(base_field=models.SmallIntegerField(), blank=True, default=list, help_text='多选,如 [2,3]', size=None, verbose_name='可接受卧室数'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='budget_max', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, verbose_name='最高预算'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='budget_min', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:万元/元,依据需求类型', max_digits=12, null=True, verbose_name='最低预算'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='building_age_ranges', + field=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, help_text='多选:within_5y / 5_10y / 10_15y / 15_20y / over_20y', size=None, verbose_name='楼龄偏好'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='client', + field=models.ForeignKey(help_text='需求随客源级联删除', on_delete=django.db.models.deletion.CASCADE, related_name='requirements', to='fonrey_client.client', verbose_name='所属客源'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='decorations', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('rough', '毛坯'), ('plain', '清水'), ('simple', '简装'), ('medium', '中装'), ('fine', '精装'), ('luxury', '豪装')], max_length=10), blank=True, default=list, help_text='多选(枚举同 properties.decoration)', size=None, verbose_name='装修偏好'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='floor_preferences', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('no_first', '不要一楼'), ('low', '低楼层'), ('mid', '中楼层'), ('high', '高楼层'), ('no_top', '不要顶楼')], max_length=20), blank=True, default=list, help_text='多选:no_first=不要一层 / low=低楼层 / mid=中楼层 / high=高楼层 / no_top=不要顶层', size=None, verbose_name='楼层偏好'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='intent_business_area_ids', + field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, help_text='商圈 ID 数组', size=None, verbose_name='意向商圈'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='intent_complex_names', + field=models.TextField(blank=True, default='', help_text='文本,逗号分隔,最多500字', verbose_name='意向小区'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='intent_district_ids', + field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, help_text='行政区 ID 数组', size=None, verbose_name='意向行政区'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='intent_school_names', + field=models.TextField(blank=True, default='', help_text='文本,逗号分隔', verbose_name='意向学校'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='is_primary', + field=models.BooleanField(default=True, help_text='用于列表展示', verbose_name='是否主需求'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='orientations', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('east', '东'), ('south', '南'), ('west', '西'), ('north', '北')], max_length=10), blank=True, default=list, help_text='多选:east=东 / south=南 / west=西 / north=北', size=None, verbose_name='朝向偏好'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='requirement_notes', + field=models.CharField(blank=True, default='', help_text='最多200字', max_length=200, verbose_name='需求备注'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='requirement_type', + field=models.CharField(choices=[('second_hand', '二手'), ('new_house', '新房'), ('rental', '租房')], help_text='second_hand=二手 / new_house=新房 / rental=租房', max_length=20, verbose_name='需求类型'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='school_enrollment_date', + field=models.DateField(blank=True, help_text='月份精度,取该月1日存储', null=True, verbose_name='入学时间'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='traffic_preference', + field=models.TextField(blank=True, default='', verbose_name='交通备注'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='transportation', + field=models.CharField(blank=True, default='', help_text='最多50字', max_length=50, verbose_name='交通要求'), + ), + migrations.AlterField( + model_name='clientrequirement', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='clientschoolpreference', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientschoolpreference', + name='requirement', + field=models.ForeignKey(help_text='意向学校随需求级联删除', on_delete=django.db.models.deletion.CASCADE, related_name='school_preferences', to='fonrey_client.clientrequirement', verbose_name='所属需求'), + ), + migrations.AlterField( + model_name='clientschoolpreference', + name='school_id', + field=models.UUIDField(blank=True, help_text='从学校表选择,允许为 NULL(自由输入)', null=True, verbose_name='学校ID'), + ), + migrations.AlterField( + model_name='clientschoolpreference', + name='school_name', + field=models.CharField(help_text='当 school_id 为 NULL 时为手动输入', max_length=100, verbose_name='学校名称'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='change_type', + field=models.CharField(choices=[('status_change', '改状态'), ('grade_change', '改等级'), ('to_public', '转公客'), ('to_transacted', '转成交'), ('to_invalid', '转无效'), ('owner_change', '改归属人'), ('source_change', '改来源'), ('merge', '合并客源')], help_text='status_change=改状态 / grade_change=改等级 / to_public=转公客 / to_transacted=转成交 / to_invalid=转无效 / owner_change=改归属人 / source_change=改来源', max_length=30, verbose_name='变更类型'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='client', + field=models.ForeignKey(help_text='状态日志永久保留,RESTRICT 防止删除客源', on_delete=django.db.models.deletion.RESTRICT, related_name='status_logs', to='fonrey_client.client', verbose_name='所属客源'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='id', + field=models.UUIDField(primary_key=True, serialize=False, verbose_name='主键'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='new_value', + field=models.JSONField(blank=True, null=True, verbose_name='变更后快照'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='old_value', + field=models.JSONField(blank=True, help_text='格式:{"status": "buying", "label": "求购"}', null=True, verbose_name='变更前快照'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='operated_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='操作时间'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='operator', + field=models.ForeignKey(help_text='必填,状态变更审计用', on_delete=django.db.models.deletion.RESTRICT, related_name='client_status_changes', to='org.staff', verbose_name='操作人'), + ), + migrations.AlterField( + model_name='clientstatuslog', + name='reason', + field=models.TextField(blank=True, default='', help_text='改状态必填,最多200字', verbose_name='变更理由'), + ), + migrations.AlterField( + model_name='clientviewing', + name='agent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='led_viewings', to='org.staff', verbose_name='主带看经纪人'), + ), + migrations.AlterField( + model_name='clientviewing', + name='client', + field=models.ForeignKey(help_text='带看记录仅软删除,不随客源删除', on_delete=django.db.models.deletion.RESTRICT, related_name='viewings', to='fonrey_client.client', verbose_name='所属客源'), + ), + migrations.AlterField( + model_name='clientviewing', + name='client_intent', + field=models.CharField(blank=True, choices=[('interested', '感兴趣'), ('not_interested', '不感兴趣'), ('negotiating', '谈判中'), ('cancelled', '取消')], default='', help_text='interested=感兴趣 / not_interested=不感兴趣 / negotiating=谈判中 / cancelled=取消', max_length=20, verbose_name='客户意向'), + ), + migrations.AlterField( + model_name='clientviewing', + name='companion_ids', + field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, help_text='员工 ID 数组(最多5人)', size=None, verbose_name='陪看人员'), + ), + migrations.AlterField( + model_name='clientviewing', + name='cooperator_ids', + field=django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), blank=True, default=list, help_text='员工 ID 数组(最多5人)', size=None, verbose_name='合作带看人'), + ), + migrations.AlterField( + model_name='clientviewing', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='clientviewing', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_client_viewings', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='clientviewing', + name='deleted_at', + field=models.DateTimeField(blank=True, help_text='软删除时间戳;带看记录可软删除', null=True, verbose_name='删除时间'), + ), + migrations.AlterField( + model_name='clientviewing', + name='property', + field=models.ForeignKey(help_text='房源删除时保留带看记录', on_delete=django.db.models.deletion.RESTRICT, related_name='client_viewings', to='fonrey_property.property', verbose_name='带看房源'), + ), + migrations.AlterField( + model_name='clientviewing', + name='scheduled_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='预约时间'), + ), + migrations.AlterField( + model_name='clientviewing', + name='situation', + field=models.TextField(blank=True, default='', help_text='必填,≥6字', verbose_name='带看情况'), + ), + migrations.AlterField( + model_name='clientviewing', + name='viewing_end_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='带看结束时间'), + ), + migrations.AlterField( + model_name='clientviewing', + name='viewing_progress', + field=models.SmallIntegerField(blank=True, help_text='1=一看,2=二看…,冗余字段,触发器维护', null=True, verbose_name='带看进度'), + ), + migrations.AlterField( + model_name='clientviewing', + name='viewing_start_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='实际带看开始时间'), + ), + migrations.AlterField( + model_name='clientviewing', + name='viewing_type', + field=models.CharField(choices=[('appointment', '预约'), ('viewing', '带看'), ('revisit', '复看'), ('empty', '空看')], default='viewing', help_text='appointment=预约 / viewing=带看 / revisit=复看 / empty=空看', max_length=20, verbose_name='带看类型'), + ), + ] diff --git a/apps/complex/migrations/0004_alter_building_built_year_alter_building_complex_and_more.py b/apps/complex/migrations/0004_alter_building_built_year_alter_building_complex_and_more.py new file mode 100644 index 0000000..2040cca --- /dev/null +++ b/apps/complex/migrations/0004_alter_building_built_year_alter_building_complex_and_more.py @@ -0,0 +1,528 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +import django.contrib.postgres.fields +import django.contrib.postgres.search +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('org', '0003_alter_orgunit_address_city_and_more'), + ('region', '0003_alter_businessarea_district_and_more'), + ('fonrey_complex', '0003_alter_building_options_alter_complex_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='building', + name='built_year', + field=models.SmallIntegerField(blank=True, null=True, verbose_name='竣工年份'), + ), + migrations.AlterField( + model_name='building', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='buildings', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='building', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_buildings', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='building', + name='has_elevator', + field=models.BooleanField(blank=True, null=True, verbose_name='是否有电梯'), + ), + migrations.AlterField( + model_name='building', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE=已停用(楼栋被删除或合并)', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='building', + name='is_standard', + field=models.BooleanField(default=False, help_text='TRUE=已经运营核准', verbose_name='是否标准结构'), + ), + migrations.AlterField( + model_name='building', + name='land_use_years', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='土地使用年限'), + ), + migrations.AlterField( + model_name='building', + name='name', + field=models.CharField(help_text='如「1号楼」「A栋2单元」', max_length=50, verbose_name='楼栋名称'), + ), + migrations.AlterField( + model_name='building', + name='property_usage_type', + field=models.CharField(blank=True, choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('commercial', '商业'), ('office', '写字楼'), ('other', '其他')], default='', help_text='可与楼盘不同,如商住楼盘内有纯商铺楼栋', max_length=30, verbose_name='物业类型'), + ), + migrations.AlterField( + model_name='building', + name='school', + field=models.ForeignKey(blank=True, help_text='楼栋级别的学区差异', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='buildings', to='region.school', verbose_name='对口学校'), + ), + migrations.AlterField( + model_name='building', + name='total_floors', + field=models.SmallIntegerField(blank=True, null=True, verbose_name='总层数'), + ), + migrations.AlterField( + model_name='complex', + name='address', + field=models.CharField(blank=True, default='', help_text='不可在编辑页修改,需走纠错流程', max_length=500, verbose_name='详细地址'), + ), + migrations.AlterField( + model_name='complex', + name='address_summary', + field=models.CharField(blank=True, default='', help_text='如「海波路1000弄」,可编辑', max_length=100, verbose_name='概要地址'), + ), + migrations.AlterField( + model_name='complex', + name='building_structure', + field=models.CharField(blank=True, choices=[('unit_room', '单元-房号'), ('other', '其他')], default='', help_text='unit_room=单元-房号 / other=其他', max_length=30, verbose_name='楼栋结构'), + ), + migrations.AlterField( + model_name='complex', + name='building_type', + field=models.CharField(blank=True, choices=[('slab', '板楼'), ('tower', '塔楼'), ('slab_tower', '板塔结合')], default='', help_text='slab=板楼 / tower=塔楼 / slab_tower=板塔结合', max_length=20, verbose_name='建筑类型'), + ), + migrations.AlterField( + model_name='complex', + name='built_year', + field=models.SmallIntegerField(blank=True, help_text='可多选时存最早竣工年', null=True, verbose_name='竣工年份'), + ), + migrations.AlterField( + model_name='complex', + name='built_years', + field=django.contrib.postgres.fields.ArrayField(base_field=models.SmallIntegerField(), blank=True, default=list, help_text='楼盘分期竣工', size=None, verbose_name='竣工年份多值'), + ), + migrations.AlterField( + model_name='complex', + name='business_areas', + field=models.ManyToManyField(related_name='complexes', through='fonrey_complex.ComplexBusinessArea', to='region.businessarea', verbose_name='关联商圈'), + ), + migrations.AlterField( + model_name='complex', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_complexes', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='complex', + name='developer', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='开发商'), + ), + migrations.AlterField( + model_name='complex', + name='district', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='complexes', to='region.district', verbose_name='所属城区'), + ), + migrations.AlterField( + model_name='complex', + name='electricity_type', + field=models.CharField(blank=True, choices=[('civil', '民电'), ('commercial', '商电')], default='', help_text='civil=民电 / commercial=商电', max_length=10, verbose_name='电费类型'), + ), + migrations.AlterField( + model_name='complex', + name='green_rate', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:%', max_digits=5, null=True, verbose_name='绿化率'), + ), + migrations.AlterField( + model_name='complex', + name='has_central_heating', + field=models.BooleanField(blank=True, null=True, verbose_name='是否统一供暖'), + ), + migrations.AlterField( + model_name='complex', + name='has_gas', + field=models.BooleanField(blank=True, null=True, verbose_name='是否有燃气'), + ), + migrations.AlterField( + model_name='complex', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE=已停用楼盘', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='complex', + name='land_use_years', + field=models.CharField(blank=True, default='', help_text='如「70年」', max_length=30, verbose_name='土地使用年限'), + ), + migrations.AlterField( + model_name='complex', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='WGS84,完整度目标 ≥ 90%', max_digits=10, null=True, verbose_name='纬度'), + ), + migrations.AlterField( + model_name='complex', + name='lock_building', + field=models.BooleanField(default=False, help_text='锁定后不可增删楼栋', verbose_name='楼栋锁'), + ), + migrations.AlterField( + model_name='complex', + name='lock_info', + field=models.BooleanField(default=False, help_text='锁定后基本信息只读', verbose_name='信息锁'), + ), + migrations.AlterField( + model_name='complex', + name='lock_room', + field=models.BooleanField(default=False, verbose_name='房号锁'), + ), + migrations.AlterField( + model_name='complex', + name='lock_standard_room', + field=models.BooleanField(default=False, verbose_name='标准房号锁'), + ), + migrations.AlterField( + model_name='complex', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='WGS84', max_digits=10, null=True, verbose_name='经度'), + ), + migrations.AlterField( + model_name='complex', + name='metro_stations', + field=models.ManyToManyField(related_name='complexes', through='fonrey_complex.ComplexMetroStation', to='region.metrostation', verbose_name='周边地铁站'), + ), + migrations.AlterField( + model_name='complex', + name='name', + field=models.CharField(help_text='标准楼盘名称,不可在编辑页直接修改(需走合并/申请流程)', max_length=200, verbose_name='楼盘名称'), + ), + migrations.AlterField( + model_name='complex', + name='ownership_category', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), blank=True, default=list, help_text='多选(运营维护枚举)', size=None, verbose_name='权属类别'), + ), + migrations.AlterField( + model_name='complex', + name='parking_ratio', + field=models.CharField(blank=True, default='', help_text='如「100:63」', max_length=20, verbose_name='车位配比'), + ), + migrations.AlterField( + model_name='complex', + name='parking_total', + field=models.IntegerField(blank=True, null=True, verbose_name='车位总数'), + ), + migrations.AlterField( + model_name='complex', + name='parking_underground', + field=models.IntegerField(blank=True, null=True, verbose_name='地下车位数'), + ), + migrations.AlterField( + model_name='complex', + name='plot_area', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:m²', max_digits=12, null=True, verbose_name='小区占地面积'), + ), + migrations.AlterField( + model_name='complex', + name='plot_ratio', + field=models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='容积率'), + ), + migrations.AlterField( + model_name='complex', + name='property_company', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='物业公司'), + ), + migrations.AlterField( + model_name='complex', + name='property_fee', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:元/m²/月', max_digits=8, null=True, verbose_name='物业费'), + ), + migrations.AlterField( + model_name='complex', + name='property_phone', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='物业电话'), + ), + migrations.AlterField( + model_name='complex', + name='property_usage_types', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('commercial', '商业'), ('office', '写字楼'), ('other', '其他')], max_length=30), blank=True, default=list, help_text='多选:residential / villa / commercial_residential / commercial / office / other', size=None, verbose_name='物业类型'), + ), + migrations.AlterField( + model_name='complex', + name='remarks', + field=models.TextField(blank=True, default='', verbose_name='备注'), + ), + migrations.AlterField( + model_name='complex', + name='schools', + field=models.ManyToManyField(related_name='complexes', through='fonrey_complex.ComplexSchool', to='region.school', verbose_name='对口学校'), + ), + migrations.AlterField( + model_name='complex', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(blank=True, help_text='由触发器自动维护(name + alias + address)', null=True, verbose_name='全文检索向量'), + ), + migrations.AlterField( + model_name='complex', + name='total_floor_area', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:m²', max_digits=12, null=True, verbose_name='小区总建筑面积'), + ), + migrations.AlterField( + model_name='complex', + name='total_households', + field=models.IntegerField(blank=True, null=True, verbose_name='总户数'), + ), + migrations.AlterField( + model_name='complex', + name='total_units', + field=models.IntegerField(blank=True, null=True, verbose_name='单元总数'), + ), + migrations.AlterField( + model_name='complex', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_complexes', to='org.staff', verbose_name='最后更新人'), + ), + migrations.AlterField( + model_name='complex', + name='version', + field=models.IntegerField(default=1, help_text='乐观锁;UPDATE 时 +1;应用层检测 0 行受影响时抛 ConflictError', verbose_name='版本号'), + ), + migrations.AlterField( + model_name='complex', + name='water_type', + field=models.CharField(blank=True, choices=[('civil', '民水'), ('commercial', '商水')], default='', help_text='civil=民水 / commercial=商水', max_length=10, verbose_name='水费类型'), + ), + migrations.AlterField( + model_name='complexalias', + name='alias', + field=models.CharField(help_text='最多20字/条,多别名多行存储', max_length=200, verbose_name='别名'), + ), + migrations.AlterField( + model_name='complexalias', + name='complex', + field=models.ForeignKey(help_text='别名随楼盘级联删除', on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexalias', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='complexalias', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_complex_aliases', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='complexalias', + name='is_system', + field=models.BooleanField(default=False, help_text='TRUE=系统/标准别名(只读),FALSE=用户自定义', verbose_name='是否系统别名'), + ), + migrations.AlterField( + model_name='complexattachment', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='complexattachment', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_complex_attachments', to='org.staff', verbose_name='上传人'), + ), + migrations.AlterField( + model_name='complexattachment', + name='file_key', + field=models.TextField(help_text='R2/S3 存储路径', verbose_name='文件存储路径'), + ), + migrations.AlterField( + model_name='complexattachment', + name='file_name', + field=models.CharField(max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='complexattachment', + name='file_size', + field=models.IntegerField(blank=True, help_text='单位:bytes', null=True, verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='complexattachment', + name='file_type', + field=models.CharField(blank=True, default='', help_text='MIME type', max_length=50, verbose_name='文件类型'), + ), + migrations.AlterField( + model_name='complexattachment', + name='sort_order', + field=models.SmallIntegerField(default=0, verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='complexbusinessarea', + name='business_area', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_links', to='region.businessarea', verbose_name='关联商圈'), + ), + migrations.AlterField( + model_name='complexbusinessarea', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_business_areas', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexbusinessarea', + name='is_primary', + field=models.BooleanField(default=False, help_text='主商圈唯一,用于列表显示', verbose_name='是否主商圈'), + ), + migrations.AlterField( + model_name='complexmetrostation', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_metro_stations', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexmetrostation', + name='distance_meters', + field=models.IntegerField(blank=True, help_text='单位:米', null=True, verbose_name='步行距离'), + ), + migrations.AlterField( + model_name='complexmetrostation', + name='station', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_links', to='region.metrostation', verbose_name='关联地铁站'), + ), + migrations.AlterField( + model_name='complexphoto', + name='category', + field=models.CharField(choices=[('complex', '楼盘图'), ('layout', '户型图'), ('vr', 'VR图'), ('other', '其他')], help_text='complex=楼盘图 / layout=户型图 / vr=VR全景 / other=其他', max_length=20, verbose_name='照片类别'), + ), + migrations.AlterField( + model_name='complexphoto', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexphoto', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='complexphoto', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_complex_photos', to='org.staff', verbose_name='上传人'), + ), + migrations.AlterField( + model_name='complexphoto', + name='file_key', + field=models.TextField(help_text='R2/S3 路径', verbose_name='文件存储路径'), + ), + migrations.AlterField( + model_name='complexphoto', + name='file_name', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='complexphoto', + name='file_size', + field=models.IntegerField(blank=True, help_text='单位:bytes', null=True, verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='complexphoto', + name='height', + field=models.IntegerField(blank=True, help_text='单位:px', null=True, verbose_name='图片高度'), + ), + migrations.AlterField( + model_name='complexphoto', + name='is_cover', + field=models.BooleanField(default=False, help_text='楼盘封面图(每楼盘唯一)', verbose_name='是否封面图'), + ), + migrations.AlterField( + model_name='complexphoto', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='同类别内的排序顺序', verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='complexphoto', + name='thumbnail_key', + field=models.TextField(blank=True, default='', verbose_name='缩略图路径'), + ), + migrations.AlterField( + model_name='complexphoto', + name='width', + field=models.IntegerField(blank=True, help_text='单位:px', null=True, verbose_name='图片宽度'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='avg_sale_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:万元/套', max_digits=12, null=True, verbose_name='月均售价'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='avg_unit_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='单位:元/m²', max_digits=10, null=True, verbose_name='月均单价'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='price_trends', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='listing_count', + field=models.IntegerField(blank=True, null=True, verbose_name='当月挂牌套数'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='record_month', + field=models.DateField(help_text='统一存为该月1日,如 2026-04-01', verbose_name='月份'), + ), + migrations.AlterField( + model_name='complexpricetrend', + name='transaction_count', + field=models.IntegerField(blank=True, null=True, verbose_name='成交套数'), + ), + migrations.AlterField( + model_name='complexschool', + name='complex', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_schools', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='complexschool', + name='school', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='complex_links', to='region.school', verbose_name='对口学校'), + ), + migrations.AlterField( + model_name='complexschool', + name='zone_type', + field=models.CharField(blank=True, choices=[('guaranteed', '对口'), ('reference', '参考'), ('lottery', '摇号')], default='', help_text='guaranteed=对口(直升) / reference=参考(可能入读) / lottery=摇号', max_length=30, verbose_name='学区类型'), + ), + migrations.AlterField( + model_name='roomunit', + name='building', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='room_units', to='fonrey_complex.building', verbose_name='所属楼栋'), + ), + migrations.AlterField( + model_name='roomunit', + name='display_no', + field=models.CharField(blank=True, default='', help_text='展示用完整房号,如「3-1-101」', max_length=50, verbose_name='展示房号'), + ), + migrations.AlterField( + model_name='roomunit', + name='floor', + field=models.SmallIntegerField(help_text='实际层数,地下为负数', verbose_name='楼层'), + ), + migrations.AlterField( + model_name='roomunit', + name='floor_name', + field=models.CharField(blank=True, default='', help_text='如「1层」「B1层」', max_length=20, verbose_name='楼层名称'), + ), + migrations.AlterField( + model_name='roomunit', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE=已拆除/不存在', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='roomunit', + name='is_standard', + field=models.BooleanField(default=False, help_text='TRUE=已归一化为标准结构', verbose_name='是否标准化'), + ), + migrations.AlterField( + model_name='roomunit', + name='room_no', + field=models.CharField(help_text='如「01」「101」', max_length=30, verbose_name='房号'), + ), + ] diff --git a/apps/org/migrations/0003_alter_orgunit_address_city_and_more.py b/apps/org/migrations/0003_alter_orgunit_address_city_and_more.py new file mode 100644 index 0000000..e02cdf2 --- /dev/null +++ b/apps/org/migrations/0003_alter_orgunit_address_city_and_more.py @@ -0,0 +1,626 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('org', '0002_alter_orgunit_options_alter_staff_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='orgunit', + name='address_city', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='所在城市'), + ), + migrations.AlterField( + model_name='orgunit', + name='address_detail', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='详细地址'), + ), + migrations.AlterField( + model_name='orgunit', + name='address_district', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='所在县区'), + ), + migrations.AlterField( + model_name='orgunit', + name='attribute', + field=models.CharField(blank=True, choices=[('direct', '直营'), ('franchise', '加盟')], help_text='direct=直营 / franchise=加盟', max_length=10, null=True, verbose_name='经营属性'), + ), + migrations.AlterField( + model_name='orgunit', + name='depth', + field=models.SmallIntegerField(default=0, help_text='根=0,最大支持 8 层', verbose_name='节点深度'), + ), + migrations.AlterField( + model_name='orgunit', + name='established_at', + field=models.DateField(blank=True, null=True, verbose_name='成立时间'), + ), + migrations.AlterField( + model_name='orgunit', + name='ext_end', + field=models.IntegerField(blank=True, null=True, verbose_name='分机号结束'), + ), + migrations.AlterField( + model_name='orgunit', + name='ext_start', + field=models.IntegerField(blank=True, null=True, verbose_name='分机号起始'), + ), + migrations.AlterField( + model_name='orgunit', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE=已关闭部门,仍可在筛选中显示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='orgunit', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='部门定位针 WGS84', max_digits=10, null=True, verbose_name='纬度'), + ), + migrations.AlterField( + model_name='orgunit', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='部门定位针 WGS84', max_digits=10, null=True, verbose_name='经度'), + ), + migrations.AlterField( + model_name='orgunit', + name='manager', + field=models.ForeignKey(blank=True, help_text='循环依赖,Application 层维护', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='managed_org_units', to='org.staff', verbose_name='部门负责人'), + ), + migrations.AlterField( + model_name='orgunit', + name='name', + field=models.CharField(help_text='部门/组织名称', max_length=100, verbose_name='部门名称'), + ), + migrations.AlterField( + model_name='orgunit', + name='parent', + field=models.ForeignKey(blank=True, help_text='父节点,根节点为 NULL', null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='children', to='org.orgunit', verbose_name='父节点'), + ), + migrations.AlterField( + model_name='orgunit', + name='path', + field=models.TextField(help_text='/root_id/.../self_id/,用于子树查询', verbose_name='物化路径'), + ), + migrations.AlterField( + model_name='orgunit', + name='phone', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='部门联系电话'), + ), + migrations.AlterField( + model_name='orgunit', + name='sort_order', + field=models.IntegerField(default=0, help_text='同级排序', verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='orgunit', + name='type', + field=models.CharField(choices=[('company', '公司'), ('division', '事业部'), ('region', '大区'), ('area', '区域'), ('district', '片区'), ('store', '门店'), ('group', '店组'), ('functional', '职能部门')], help_text='company=公司 / division=事业部 / region=大区 / area=区域 / district=片区 / store=门店 / group=店组 / functional=职能', max_length=20, verbose_name='组织类型'), + ), + migrations.AlterField( + model_name='staff', + name='avatar_key', + field=models.TextField(blank=True, default='', help_text='R2/S3 头像路径', verbose_name='头像存储路径'), + ), + migrations.AlterField( + model_name='staff', + name='bank_account', + field=models.CharField(blank=True, default='', help_text='内部财务用', max_length=50, verbose_name='银行卡号'), + ), + migrations.AlterField( + model_name='staff', + name='bank_name', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='银行名称'), + ), + migrations.AlterField( + model_name='staff', + name='business_type', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='业务类型'), + ), + migrations.AlterField( + model_name='staff', + name='email', + field=models.EmailField(blank=True, default='', max_length=255, verbose_name='邮箱'), + ), + migrations.AlterField( + model_name='staff', + name='employee_no', + field=models.CharField(blank=True, help_text='系统自动生成或手动录入', max_length=30, null=True, unique=True, verbose_name='员工工号'), + ), + migrations.AlterField( + model_name='staff', + name='extension', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='分机号'), + ), + migrations.AlterField( + model_name='staff', + name='first_joined_at', + field=models.DateField(blank=True, help_text='计算工龄起点', null=True, verbose_name='首次入职日期'), + ), + migrations.AlterField( + model_name='staff', + name='industry_exp_years', + field=models.SmallIntegerField(blank=True, help_text='单位:年', null=True, verbose_name='行业经验'), + ), + migrations.AlterField( + model_name='staff', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE 时账号不可登录(联动 auth_user.is_active)', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='staff', + name='is_system_admin', + field=models.BooleanField(default=False, help_text='影响权限上限', verbose_name='是否系统管理员'), + ), + migrations.AlterField( + model_name='staff', + name='job_category', + field=models.CharField(blank=True, default='', help_text='如「置业顾问」(经纪人判定字段)', max_length=50, verbose_name='职务类别'), + ), + migrations.AlterField( + model_name='staff', + name='job_level', + field=models.SmallIntegerField(blank=True, null=True, verbose_name='职级'), + ), + migrations.AlterField( + model_name='staff', + name='job_title', + field=models.CharField(blank=True, default='', help_text='如「高级业务员」', max_length=100, verbose_name='职务名称'), + ), + migrations.AlterField( + model_name='staff', + name='joined_count', + field=models.SmallIntegerField(default=1, verbose_name='累计入职次数'), + ), + migrations.AlterField( + model_name='staff', + name='mentor', + field=models.ForeignKey(blank=True, help_text='带教员工', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mentees', to='org.staff', verbose_name='师傅'), + ), + migrations.AlterField( + model_name='staff', + name='name', + field=models.CharField(max_length=50, verbose_name='真实姓名'), + ), + migrations.AlterField( + model_name='staff', + name='nickname', + field=models.CharField(blank=True, default='', help_text='通讯录/显示名', max_length=50, verbose_name='昵称'), + ), + migrations.AlterField( + model_name='staff', + name='org_unit', + field=models.ForeignKey(help_text='当前所属组织节点(门店或店组)', on_delete=django.db.models.deletion.RESTRICT, related_name='staff_members', to='org.orgunit', verbose_name='所属组织节点'), + ), + migrations.AlterField( + model_name='staff', + name='partner_no', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='联号'), + ), + migrations.AlterField( + model_name='staff', + name='phone_enc', + field=models.BinaryField(blank=True, help_text='AES-256-GCM 加密手机号', null=True, verbose_name='手机号(加密)'), + ), + migrations.AlterField( + model_name='staff', + name='phone_hash', + field=models.CharField(blank=True, db_index=True, help_text='SHA-256 哈希,用于唯一性索引', max_length=64, null=True, verbose_name='手机号哈希'), + ), + migrations.AlterField( + model_name='staff', + name='phone_hide', + field=models.BooleanField(default=False, verbose_name='通讯录隐藏手机号'), + ), + migrations.AlterField( + model_name='staff', + name='recruit_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recruited_staff', to='org.staff', verbose_name='招聘人'), + ), + migrations.AlterField( + model_name='staff', + name='recruit_source', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='招聘来源'), + ), + migrations.AlterField( + model_name='staff', + name='referrer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='referred_staff', to='org.staff', verbose_name='转介人'), + ), + migrations.AlterField( + model_name='staff', + name='rejoined_at', + field=models.DateField(blank=True, null=True, verbose_name='最近复职日期'), + ), + migrations.AlterField( + model_name='staff', + name='resigned_at', + field=models.DateField(blank=True, null=True, verbose_name='最近离职日期'), + ), + migrations.AlterField( + model_name='staff', + name='role', + field=models.CharField(choices=[('agent', '经纪人'), ('store_manager', '店长'), ('area_manager', '区域经理'), ('admin', '系统管理员'), ('operator', '运营/行政'), ('system', '系统账号')], help_text='agent=经纪人 / store_manager=店长 / area_manager=区域经理 / admin=管理员 / operator=运营 / system=系统账号', max_length=30, verbose_name='系统角色'), + ), + migrations.AlterField( + model_name='staff', + name='status', + field=models.CharField(choices=[('active', '在职'), ('probation', '试用'), ('resigned', '离职'), ('frozen', '冻结')], default='active', help_text='active=在职 / probation=试用期 / resigned=已离职 / frozen=账号冻结', max_length=20, verbose_name='员工状态'), + ), + migrations.AlterField( + model_name='staff', + name='supervisor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subordinates', to='org.staff', verbose_name='直属上级'), + ), + migrations.AlterField( + model_name='staff', + name='user', + field=models.OneToOneField(blank=True, help_text='Django auth 登录账号', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff_profile', to=settings.AUTH_USER_MODEL, verbose_name='登录账号'), + ), + migrations.AlterField( + model_name='staffaccount', + name='account_no', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='账号/手机号'), + ), + migrations.AlterField( + model_name='staffaccount', + name='bound_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='绑定时间'), + ), + migrations.AlterField( + model_name='staffaccount', + name='is_bound', + field=models.BooleanField(default=False, verbose_name='是否已绑定'), + ), + migrations.AlterField( + model_name='staffaccount', + name='is_real_name_match', + field=models.BooleanField(blank=True, help_text='中国网络经纪人专用', null=True, verbose_name='实名信息一致'), + ), + migrations.AlterField( + model_name='staffaccount', + name='platform', + field=models.CharField(choices=[('fonrey', '房睿主账号'), ('58anjuke', '58安居客'), ('cnreic', '中国网络经纪人'), ('wechat_mp', '微信公众号')], help_text='fonrey=主账号 / 58anjuke=58安居客 / cnreic=中国网络经纪人 / wechat_mp=微信公众号', max_length=30, verbose_name='平台'), + ), + migrations.AlterField( + model_name='staffaccount', + name='staff', + field=models.ForeignKey(help_text='证件信息随员工关联', on_delete=django.db.models.deletion.CASCADE, related_name='external_accounts', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffeducation', + name='degree', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='学位'), + ), + migrations.AlterField( + model_name='staffeducation', + name='end_date', + field=models.DateField(blank=True, null=True, verbose_name='结束日期'), + ), + migrations.AlterField( + model_name='staffeducation', + name='enrollment_status', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='就读状态'), + ), + migrations.AlterField( + model_name='staffeducation', + name='major', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='专业'), + ), + migrations.AlterField( + model_name='staffeducation', + name='school', + field=models.CharField(max_length=200, verbose_name='学校'), + ), + migrations.AlterField( + model_name='staffeducation', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='educations', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffeducation', + name='stage', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='教育阶段'), + ), + migrations.AlterField( + model_name='staffeducation', + name='start_date', + field=models.DateField(blank=True, null=True, verbose_name='开始日期'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='birthdate', + field=models.DateField(blank=True, null=True, verbose_name='出生日期'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='name', + field=models.CharField(max_length=50, verbose_name='姓名'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='occupation', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='职业'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='phone_enc', + field=models.BinaryField(blank=True, help_text='AES-256-GCM 加密', null=True, verbose_name='电话(加密)'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='relation', + field=models.CharField(max_length=30, verbose_name='称谓'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='family_members', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='stafffamilymember', + name='work_unit', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='工作单位'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='birthdate', + field=models.DateField(blank=True, null=True, verbose_name='出生日期'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='domicile_address', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='户口所在地'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='domicile_type', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='户籍性质'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='education_level', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='最高学历'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='emergency_contact', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='紧急联系人'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='emergency_phone_enc', + field=models.BinaryField(blank=True, null=True, verbose_name='紧急联系人电话(加密)'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='ethnicity', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='民族'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='gender', + field=models.CharField(blank=True, choices=[('male', '男'), ('female', '女'), ('unknown', '未知')], default='', help_text='male=男 / female=女 / unknown=未知', max_length=10, verbose_name='性别'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='has_children', + field=models.BooleanField(blank=True, null=True, verbose_name='有无子女'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='id_number_enc', + field=models.BinaryField(blank=True, help_text='AES 加密', null=True, verbose_name='证件号码(加密)'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='id_number_hash', + field=models.CharField(blank=True, db_index=True, help_text='SHA-256 哈希,实名认证比对用', max_length=64, null=True, verbose_name='证件号码哈希'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='id_type', + field=models.CharField(blank=True, choices=[('id_card', '身份证'), ('passport', '护照'), ('other', '其他')], default='', help_text='id_card=身份证 / passport=护照 / other=其他', max_length=20, verbose_name='证件类型'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='id_verified', + field=models.BooleanField(default=False, verbose_name='是否实名认证'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='id_verified_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='认证时间'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='marital_status', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='婚姻状况'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='native_place', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='籍贯'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='political_status', + field=models.CharField(blank=True, default='', max_length=20, verbose_name='政治面貌'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='residence_address', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='居住地址'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='staff', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='personal_info', serialize=False, to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_personal_info', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='staffpersonalinfo', + name='work_start_date', + field=models.DateField(blank=True, null=True, verbose_name='参加工作时间'), + ), + migrations.AlterField( + model_name='staffremark', + name='content', + field=models.TextField(verbose_name='备注内容'), + ), + migrations.AlterField( + model_name='staffremark', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_remarks', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='staffremark', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='category', + field=models.CharField(help_text='枚举由 lookup_items 维护:org.reward_punish_category', max_length=50, verbose_name='奖惩类别'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_reward_punish', to='org.staff', verbose_name='录入人'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='name', + field=models.CharField(help_text='与类别联动', max_length=100, verbose_name='奖惩名称'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='remarks', + field=models.TextField(blank=True, default='', verbose_name='备注'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='rp_date', + field=models.DateField(verbose_name='奖惩日期'), + ), + migrations.AlterField( + model_name='staffrewardpunish', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reward_punish_records', to='org.staff', verbose_name='被奖惩员工'), + ), + migrations.AlterField( + model_name='stafftraining', + name='certificate', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='证书'), + ), + migrations.AlterField( + model_name='stafftraining', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trainings', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='stafftraining', + name='training_date', + field=models.DateField(blank=True, null=True, verbose_name='培训日期'), + ), + migrations.AlterField( + model_name='stafftraining', + name='training_name', + field=models.CharField(max_length=200, verbose_name='培训名称'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='new_value', + field=models.JSONField(blank=True, help_text='结构同 old_value', null=True, verbose_name='变动后值'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='old_value', + field=models.JSONField(blank=True, help_text='格式:{"field": "org_unit_id", "value": "...", "label": "门店A"}', null=True, verbose_name='变动前值'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='operated_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='系统操作时间'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='operator', + field=models.ForeignKey(help_text='必填,异动审计必须记录', on_delete=django.db.models.deletion.RESTRICT, related_name='operated_transfers', to='org.staff', verbose_name='操作人'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='remarks', + field=models.CharField(blank=True, default='', help_text='最多50字', max_length=50, verbose_name='备注'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='transfer_logs', to='org.staff', verbose_name='被操作员工'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='transfer_date', + field=models.DateField(help_text='可以是过去日期', verbose_name='异动生效日期'), + ), + migrations.AlterField( + model_name='stafftransferlog', + name='transfer_type', + field=models.CharField(choices=[('onboard', '入职'), ('transfer', '调动'), ('resign', '离职'), ('rejoin', '复职'), ('supervisor_change', '上级变更'), ('role_change', '角色变更'), ('freeze', '冻结账号'), ('unfreeze', '恢复账号')], help_text='onboard=入职 / transfer=调动 / resign=离职 / rejoin=复职 / supervisor_change=上级变动 / role_change=角色变更 / freeze=账号冻结 / unfreeze=账号恢复', max_length=30, verbose_name='异动类型'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='company', + field=models.CharField(max_length=200, verbose_name='公司名称'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='end_date', + field=models.DateField(blank=True, null=True, verbose_name='结束日期'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='job_title', + field=models.CharField(blank=True, default='', max_length=100, verbose_name='职位'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='reason', + field=models.CharField(blank=True, default='', max_length=200, verbose_name='离职原因'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='reference_name', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='证明人姓名'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='reference_phone', + field=models.CharField(blank=True, default='', max_length=30, verbose_name='证明人电话'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='staff', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='work_experiences', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffworkexperience', + name='start_date', + field=models.DateField(blank=True, null=True, verbose_name='开始日期'), + ), + ] diff --git a/apps/permission/migrations/0003_alter_permissionchangelog_action_and_more.py b/apps/permission/migrations/0003_alter_permissionchangelog_action_and_more.py new file mode 100644 index 0000000..53cb962 --- /dev/null +++ b/apps/permission/migrations/0003_alter_permissionchangelog_action_and_more.py @@ -0,0 +1,336 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('org', '0003_alter_orgunit_address_city_and_more'), + ('fonrey_permission', '0002_alter_permissionchangelog_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='permissionchangelog', + name='action', + field=models.CharField(choices=[('create', '创建'), ('update', '更新'), ('delete', '删除'), ('assign', '分配'), ('revoke', '撤销')], help_text='create / update / delete / assign / revoke', max_length=20, verbose_name='操作动作'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='new_value', + field=models.JSONField(blank=True, null=True, verbose_name='变更后快照'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='old_value', + field=models.JSONField(blank=True, null=True, verbose_name='变更前快照'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='operated_at', + field=models.DateTimeField(auto_now_add=True, help_text='append-only 流水,分区键', verbose_name='操作时间'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='operator', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='permission_changes_operated', to='org.staff', verbose_name='操作人'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='operator_ip', + field=models.GenericIPAddressField(blank=True, null=True, verbose_name='操作来源 IP'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='permission_code', + field=models.CharField(blank=True, default='', help_text='用 code 而非 FK,避免 PermissionDef 删除后日志丢失', max_length=150, verbose_name='权限编码'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='reason', + field=models.TextField(blank=True, default='', help_text='批量设置角色等场景强制填写', verbose_name='操作原因'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='role', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='change_logs', to='fonrey_permission.role', verbose_name='被影响角色'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='staff', + field=models.ForeignKey(blank=True, help_text='target 是 staff_role/staff_override/staff_scope 时必填', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='permission_change_logs_affecting', to='org.staff', verbose_name='被影响员工'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='target_id', + field=models.UUIDField(verbose_name='变更对象 ID'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='target_type', + field=models.CharField(choices=[('role', '角色'), ('role_permission', '角色权限'), ('staff_role', '员工角色'), ('staff_override', '员工权限覆盖'), ('staff_scope', '员工数据范围')], help_text='role / role_permission / staff_role / staff_override / staff_scope', max_length=30, verbose_name='变更对象类型'), + ), + migrations.AlterField( + model_name='permissionchangelog', + name='user_agent', + field=models.TextField(blank=True, default='', verbose_name='操作终端 UA'), + ), + migrations.AlterField( + model_name='permissiondef', + name='code', + field=models.CharField(help_text='规则:{module}.{sub_module}.{action}[.{qualifier}]', max_length=150, unique=True, verbose_name='权限编码'), + ), + migrations.AlterField( + model_name='permissiondef', + name='default_value', + field=models.JSONField(default=dict, help_text='系统最小默认值,格式 {"v": }', verbose_name='默认值'), + ), + migrations.AlterField( + model_name='permissiondef', + name='description', + field=models.TextField(blank=True, default='', verbose_name='权限作用描述'), + ), + migrations.AlterField( + model_name='permissiondef', + name='group_name', + field=models.CharField(help_text='如「私客基础权限」「联系人基础权限」', max_length=100, verbose_name='分组标题'), + ), + migrations.AlterField( + model_name='permissiondef', + name='integer_max', + field=models.IntegerField(blank=True, help_text='仅 INTEGER 类型有效;NULL=无上限(业务上 0 通常代表不限制)', null=True, verbose_name='最大值'), + ), + migrations.AlterField( + model_name='permissiondef', + name='integer_min', + field=models.IntegerField(blank=True, help_text='仅 INTEGER 类型有效', null=True, verbose_name='最小值'), + ), + migrations.AlterField( + model_name='permissiondef', + name='is_active', + field=models.BooleanField(default=True, help_text='下线权限项置 FALSE,历史记录保留', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='permissiondef', + name='is_deprecated', + field=models.BooleanField(default=False, help_text='不再推荐使用但保持兼容', verbose_name='是否废弃'), + ), + migrations.AlterField( + model_name='permissiondef', + name='max_allowed_categories', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, default=list, help_text='允许配置此权限的角色类别列表,空数组=所有类别均可', size=None, verbose_name='可配置角色类别'), + ), + migrations.AlterField( + model_name='permissiondef', + name='module', + field=models.CharField(choices=[('home', '首页'), ('property', '房源'), ('new_house', '新房'), ('client', '客源'), ('transaction', '交易'), ('data', '数据'), ('marketing', '营销'), ('hr', '人事OA'), ('contract', '合同'), ('trinet', '三网'), ('system', '系统'), ('mobile', '移动端'), ('smart_store', '智能门店'), ('recharge', '在线充值')], help_text='home/property/new_house/client/transaction/data/marketing/hr/contract/trinet/system/mobile/smart_store/recharge', max_length=50, verbose_name='一级模块'), + ), + migrations.AlterField( + model_name='permissiondef', + name='name', + field=models.CharField(max_length=200, verbose_name='显示名称'), + ), + migrations.AlterField( + model_name='permissiondef', + name='scope_choices', + field=models.JSONField(blank=True, default=list, help_text='仅 SCOPE 类型有效,可选枚举 code 列表,如 ["none","self","store","company"]', verbose_name='可选范围'), + ), + migrations.AlterField( + model_name='permissiondef', + name='sort_order', + field=models.PositiveIntegerField(default=0, help_text='分组内排序', verbose_name='排序顺序'), + ), + migrations.AlterField( + model_name='permissiondef', + name='sub_module', + field=models.CharField(blank=True, default='', help_text='如「二手&租赁」「商圈精耕」', max_length=50, verbose_name='二级模块'), + ), + migrations.AlterField( + model_name='permissiondef', + name='value_type', + field=models.CharField(choices=[('boolean', '开关型'), ('scope', '范围型'), ('integer', '数值型')], help_text='BOOLEAN=开关型 / SCOPE=范围型 / INTEGER=数值型', max_length=20, verbose_name='权限值类型'), + ), + migrations.AlterField( + model_name='permissiondef', + name='version', + field=models.PositiveIntegerField(default=1, help_text='变更时递增,用于缓存失效', verbose_name='定义版本'), + ), + migrations.AlterField( + model_name='role', + name='category', + field=models.CharField(choices=[('agent', '置业顾问'), ('store_manager', '店管'), ('director', '总经'), ('operator', '运营/行政'), ('custom', '自定义')], help_text='agent=置业顾问 / store_manager=店管 / director=总经 / operator=运营 / custom=自定义', max_length=30, verbose_name='角色类别'), + ), + migrations.AlterField( + model_name='role', + name='created_by', + field=models.ForeignKey(blank=True, help_text='角色类别只能由创建者修改', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='permission_roles_created', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='role', + name='description', + field=models.TextField(blank=True, default='', verbose_name='角色描述'), + ), + migrations.AlterField( + model_name='role', + name='is_active', + field=models.BooleanField(default=True, help_text='FALSE=禁用(员工无法继承该角色权限)', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='role', + name='is_system_builtin', + field=models.BooleanField(default=False, help_text='如「最大权限角色」,不可删除、不可改名', verbose_name='是否系统内置'), + ), + migrations.AlterField( + model_name='role', + name='name', + field=models.CharField(max_length=100, verbose_name='角色名称'), + ), + migrations.AlterField( + model_name='role', + name='template_role', + field=models.ForeignKey(blank=True, help_text='PRD「引用该角色配置」列', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='derived_roles', to='fonrey_permission.role', verbose_name='权限模板来源'), + ), + migrations.AlterField( + model_name='role', + name='updated_by', + field=models.ForeignKey(blank=True, help_text='权限管理审计用', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='permission_roles_updated', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='rolepermission', + name='permission_def', + field=models.ForeignKey(help_text='RESTRICT 防止删除仍被引用的权限项', on_delete=django.db.models.deletion.PROTECT, related_name='role_assignments', to='fonrey_permission.permissiondef', verbose_name='权限定义'), + ), + migrations.AlterField( + model_name='rolepermission', + name='role', + field=models.ForeignKey(help_text='稀疏存储:角色删除时级联清理权限值', on_delete=django.db.models.deletion.CASCADE, related_name='permissions', to='fonrey_permission.role', verbose_name='所属角色'), + ), + migrations.AlterField( + model_name='rolepermission', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='role_permissions_updated', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='rolepermission', + name='value', + field=models.JSONField(help_text='统一格式 {"v": }', verbose_name='权限值'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='expires_at', + field=models.DateTimeField(blank=True, null=True, verbose_name='临时授权失效时间'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='granted_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='授权时间'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='granted_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='data_scopes_granted', to='org.staff', verbose_name='授权操作人'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='is_readable', + field=models.BooleanField(default=True, verbose_name='可读'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='is_writable', + field=models.BooleanField(default=False, help_text='默认只读', verbose_name='可写'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='org_unit', + field=models.ForeignKey(blank=True, help_text='scope_type=custom_unit 时必填,其他类型为 NULL', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='data_scope_grants', to='org.orgunit', verbose_name='组织节点'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='reason', + field=models.TextField(blank=True, default='', verbose_name='授予原因'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='scope_type', + field=models.CharField(choices=[('self', '本人'), ('group', '本组'), ('store', '本门店'), ('area', '本区域'), ('region', '本大区'), ('company', '全公司'), ('custom_unit', '自定义组织单元')], help_text='self=本人 / group=本组 / store=本门店 / area=本区域 / region=本大区 / company=全公司 / custom_unit=指定节点', max_length=20, verbose_name='范围类型'), + ), + migrations.AlterField( + model_name='staffdatascope', + name='staff', + field=models.ForeignKey(help_text='员工删除时级联删除范围记录', on_delete=django.db.models.deletion.CASCADE, related_name='data_scopes', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='modified_at', + field=models.DateTimeField(auto_now=True, verbose_name='最近修改时间'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='modified_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff_overrides_modified', to='org.staff', verbose_name='修改人'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='override_mode', + field=models.CharField(choices=[('replace', '覆盖'), ('restrict', '限制'), ('grant', '授予')], default='replace', help_text='REPLACE=替换合并值 / RESTRICT=限制上限 / GRANT=仅扩展', max_length=10, verbose_name='覆盖模式'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='permission_def', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='staff_overrides', to='fonrey_permission.permissiondef', verbose_name='被覆盖权限项'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='reason', + field=models.TextField(blank=True, default='', help_text='管理员备注,建议强制填写以便审计', verbose_name='备注'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='staff', + field=models.ForeignKey(help_text='员工删除时级联删除覆盖记录', on_delete=django.db.models.deletion.CASCADE, related_name='permission_overrides', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffpermissionoverride', + name='value', + field=models.JSONField(help_text='统一格式 {"v": }', verbose_name='个人权限值'), + ), + migrations.AlterField( + model_name='staffrole', + name='assigned_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='分配时间'), + ), + migrations.AlterField( + model_name='staffrole', + name='assigned_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff_role_assignments_made', to='org.staff', verbose_name='分配操作人'), + ), + migrations.AlterField( + model_name='staffrole', + name='is_primary', + field=models.BooleanField(default=False, help_text='每个员工有且仅有一个主角色', verbose_name='是否主角色'), + ), + migrations.AlterField( + model_name='staffrole', + name='role', + field=models.ForeignKey(help_text='角色被员工引用时禁止删除', on_delete=django.db.models.deletion.PROTECT, related_name='staff_links', to='fonrey_permission.role', verbose_name='角色'), + ), + migrations.AlterField( + model_name='staffrole', + name='staff', + field=models.ForeignKey(help_text='员工删除时级联删除角色关联', on_delete=django.db.models.deletion.CASCADE, related_name='staff_roles', to='org.staff', verbose_name='所属员工'), + ), + migrations.AlterField( + model_name='staffrole', + name='valid_from', + field=models.DateField(blank=True, help_text='预留未来「定时生效」功能', null=True, verbose_name='生效日'), + ), + migrations.AlterField( + model_name='staffrole', + name='valid_until', + field=models.DateField(blank=True, null=True, verbose_name='失效日'), + ), + ] diff --git a/apps/property/migrations/0004_alter_commission_agent_and_more.py b/apps/property/migrations/0004_alter_commission_agent_and_more.py new file mode 100644 index 0000000..014e76d --- /dev/null +++ b/apps/property/migrations/0004_alter_commission_agent_and_more.py @@ -0,0 +1,1232 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +import django.contrib.postgres.search +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('fonrey_complex', '0004_alter_building_built_year_alter_building_complex_and_more'), + ('org', '0003_alter_orgunit_address_city_and_more'), + ('fonrey_property', '0003_alter_commission_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='commission', + name='agent', + field=models.ForeignKey(blank=True, help_text='人员离职后置 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='commissions_as_agent', to='org.staff', verbose_name='委托经纪人'), + ), + migrations.AlterField( + model_name='commission', + name='agent_snapshot', + field=models.JSONField(blank=True, help_text='{name, store_group};防止人员变动后数据丢失', null=True, verbose_name='经纪人快照'), + ), + migrations.AlterField( + model_name='commission', + name='commission_type', + field=models.CharField(help_text='独家委托/非独家委托;由 lookup_items 维护', max_length=50, verbose_name='委托类型'), + ), + migrations.AlterField( + model_name='commission', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_commissions', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='commission', + name='is_open_ended', + field=models.BooleanField(default=False, help_text='true=长期委托/false=有截止日期', verbose_name='是否无固定结束日期'), + ), + migrations.AlterField( + model_name='commission', + name='owner_id_number', + field=models.CharField(blank=True, default='', help_text='仅供参考;加密版本见 owner_id_number_enc', max_length=50, verbose_name='委托人证件号明文'), + ), + migrations.AlterField( + model_name='commission', + name='owner_id_number_enc', + field=models.BinaryField(blank=True, help_text='AES-256-GCM 加密', null=True, verbose_name='委托人证件号密文'), + ), + migrations.AlterField( + model_name='commission', + name='owner_id_type', + field=models.CharField(blank=True, default='', help_text='如:身份证/护照', max_length=20, verbose_name='委托人证件类型'), + ), + migrations.AlterField( + model_name='commission', + name='owner_name', + field=models.CharField(blank=True, default='', max_length=50, verbose_name='委托人姓名'), + ), + migrations.AlterField( + model_name='commission', + name='owner_type', + field=models.CharField(choices=[('owner', '产权人本人'), ('authorized_third', '授权第三方')], default='owner', help_text='owner=产权人本人/authorized_third=被授权第三方', max_length=20, verbose_name='委托人类型'), + ), + migrations.AlterField( + model_name='commission', + name='period_end', + field=models.DateField(blank=True, help_text='is_open_ended=true 时为 NULL', null=True, verbose_name='委托结束日期'), + ), + migrations.AlterField( + model_name='commission', + name='period_start', + field=models.DateField(verbose_name='委托开始日期'), + ), + migrations.AlterField( + model_name='commission', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='commissions', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='commission', + name='property_owner_contact', + field=models.ForeignKey(blank=True, help_text='若委托人已录入联系人则关联,否则填写下方姓名/证件', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='commissions', to='fonrey_property.propertycontact', verbose_name='关联联系人'), + ), + migrations.AlterField( + model_name='commission', + name='remarks', + field=models.TextField(blank=True, default='', help_text='最多 200 字', verbose_name='备注'), + ), + migrations.AlterField( + model_name='commission', + name='signing_method', + field=models.CharField(blank=True, default='', help_text='选择后动态展示委托书模板', max_length=50, verbose_name='签约方式'), + ), + migrations.AlterField( + model_name='commission', + name='status', + field=models.CharField(choices=[('active', '有效'), ('expired', '过期'), ('cancelled', '取消')], default='active', help_text='active=有效/expired=已过期/cancelled=已取消', max_length=20, verbose_name='委托状态'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='category', + field=models.CharField(choices=[('id_card', '身份证件'), ('property_cert', '产权证明'), ('commission_letter', '委托书'), ('other', '其他')], help_text='id_card=身份证/property_cert=产权证书/commission_letter=委托书/other=其他材料', max_length=20, verbose_name='附件分类'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='commission', + field=models.ForeignKey(help_text='委托删除时联级删除', on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='fonrey_property.commission', verbose_name='所属委托'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='附件存储路径'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='file_name', + field=models.CharField(max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='file_size', + field=models.IntegerField(blank=True, help_text='bytes', null=True, verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='commissionattachment', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='数值越小越靠前', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='created_by', + field=models.ForeignKey(help_text='禁止置 NULL 保留审计', on_delete=django.db.models.deletion.RESTRICT, to='org.staff', verbose_name='实勘人'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='description', + field=models.TextField(blank=True, default='', help_text='最多 200 字;经纪人现场情况描述', verbose_name='实勘说明'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='gps_accuracy', + field=models.DecimalField(blank=True, decimal_places=2, help_text='米;标注定位误差', max_digits=6, null=True, verbose_name='GPS 精度'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='gps_latitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='实勘打卡位置;精度 7 位小数', max_digits=10, null=True, verbose_name='GPS 纬度'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='gps_longitude', + field=models.DecimalField(blank=True, decimal_places=7, help_text='实勘打卡位置;精度 7 位小数', max_digits=10, null=True, verbose_name='GPS 经度'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='field_surveys', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='status', + field=models.CharField(choices=[('draft', '草稿'), ('submitted', '已提交')], default='draft', help_text='draft=草稿(未提交)/submitted=已提交(已完成)', max_length=10, verbose_name='实勘状态'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='submitted_at', + field=models.DateTimeField(blank=True, help_text='status 变为 submitted 时记录;NULL=尚未提交', null=True, verbose_name='提交时间'), + ), + migrations.AlterField( + model_name='fieldsurvey', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='图片存储路径'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='file_name', + field=models.CharField(help_text='用户上传时的文件名', max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='file_size', + field=models.IntegerField(help_text='bytes;最大 20MB = 20971520', verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='file_type', + field=models.CharField(blank=True, choices=[('bmp', 'BMP'), ('jpg', 'JPG'), ('png', 'PNG'), ('svg', 'SVG'), ('gif', 'GIF')], default='', help_text='bmp/jpg/png/svg/gif(PRD 限定格式)', max_length=10, verbose_name='文件格式'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='follow_log_id', + field=models.UUIDField(help_text='跨分区外键,未通过 Django FK 强约束;日志删除时联级删除', verbose_name='所属跟进日志ID'), + ), + migrations.AlterField( + model_name='followlogattachment', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='控制同一跟进附件的显示顺序', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='followlogrecording', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='followlogrecording', + name='duration_seconds', + field=models.IntegerField(blank=True, help_text='秒;可空,上传时若能解析则填写', null=True, verbose_name='录音时长'), + ), + migrations.AlterField( + model_name='followlogrecording', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='录音文件存储路径'), + ), + migrations.AlterField( + model_name='followlogrecording', + name='follow_log_id', + field=models.UUIDField(help_text='跨分区外键,未通过 Django FK 强约束;日志删除时联级删除', verbose_name='所属跟进日志ID'), + ), + migrations.AlterField( + model_name='keyattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='keyattachment', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='附件存储路径'), + ), + migrations.AlterField( + model_name='keyattachment', + name='file_name', + field=models.CharField(max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='keyattachment', + name='key', + field=models.ForeignKey(help_text='钥匙删除时联级删除', on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='fonrey_property.propertykey', verbose_name='所属钥匙记录'), + ), + migrations.AlterField( + model_name='listinghistory', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='listinghistory', + name='ended_at', + field=models.DateTimeField(blank=True, help_text='NULL=当前仍在挂牌中', null=True, verbose_name='本次挂牌结束时间'), + ), + migrations.AlterField( + model_name='listinghistory', + name='is_only_house', + field=models.BooleanField(blank=True, help_text='本次挂牌时的唯一住房状态', null=True, verbose_name='唯一住房状态快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='listing_type', + field=models.CharField(choices=[('for_sale', '出售挂牌'), ('for_rent', '出租挂牌')], help_text='for_sale=出售挂牌/for_rent=出租挂牌', max_length=20, verbose_name='挂牌类型'), + ), + migrations.AlterField( + model_name='listinghistory', + name='ownership_years', + field=models.CharField(blank=True, default='', help_text='本次挂牌时的房本年限,如"满2年"', max_length=30, verbose_name='房本年限快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='property', + field=models.ForeignKey(help_text='禁止级联删除,保留历史', on_delete=django.db.models.deletion.RESTRICT, related_name='listing_histories', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='listinghistory', + name='rent_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='元/月;出租挂牌时记录', max_digits=10, null=True, verbose_name='本次挂牌租价快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='sale_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;出售挂牌时记录', max_digits=12, null=True, verbose_name='本次挂牌售价快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='sale_reason', + field=models.TextField(blank=True, default='', help_text='本次挂牌时的售房原因', verbose_name='售房原因快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='sale_unit_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='元/m²;由 sale_price ÷ area 计算后存储', max_digits=10, null=True, verbose_name='本次挂牌售价单价'), + ), + migrations.AlterField( + model_name='listinghistory', + name='seller_agent', + field=models.ForeignKey(blank=True, help_text='本次挂牌的出售经纪人;人员离职后置 NULL,但 snapshot 保留', null=True, on_delete=django.db.models.deletion.SET_NULL, to='org.staff', verbose_name='出售经纪人'), + ), + migrations.AlterField( + model_name='listinghistory', + name='seller_agent_snapshot', + field=models.JSONField(blank=True, help_text='{name, store_group, org_unit_name};防止人员变动后数据丢失', null=True, verbose_name='出售经纪人快照'), + ), + migrations.AlterField( + model_name='listinghistory', + name='started_at', + field=models.DateTimeField(verbose_name='本次挂牌开始时间'), + ), + migrations.AlterField( + model_name='listinghistory', + name='status', + field=models.CharField(choices=[('active', '生效中'), ('ended', '已结束')], default='active', help_text='active=挂牌中/ended=已结束', max_length=10, verbose_name='挂牌状态'), + ), + migrations.AlterField( + model_name='listinghistory', + name='tax_included', + field=models.CharField(blank=True, default='', help_text='each_party=各付/net=到手/inclusive=包税', max_length=15, verbose_name='包税费方式快照'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='applicant', + field=models.ForeignKey(help_text='提交号码方变更申请的经纪人;禁止置 NULL 保留审计', on_delete=django.db.models.deletion.RESTRICT, related_name='nh_applications', to='org.staff', verbose_name='申请人'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='approver', + field=models.ForeignKey(blank=True, help_text='上级审批人;审批前为 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='nh_approvals', to='org.staff', verbose_name='审批人'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='contact', + field=models.ForeignKey(help_text='即号码方候选联系人', on_delete=django.db.models.deletion.CASCADE, related_name='number_holder_approvals', to='fonrey_property.propertycontact', verbose_name='申请变更的联系方'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='申请提交时间'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='decided_at', + field=models.DateTimeField(blank=True, help_text='NULL=尚未审批', null=True, verbose_name='审批决定时间'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='number_holder_approvals', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='remarks', + field=models.TextField(blank=True, default='', help_text='审批人填写的意见或驳回原因', verbose_name='审批备注'), + ), + migrations.AlterField( + model_name='numberholderapproval', + name='status', + field=models.CharField(choices=[('pending', '待审批'), ('approved', '已通过'), ('rejected', '已驳回')], default='pending', help_text='pending=待审批/approved=已通过/rejected=已驳回', max_length=20, verbose_name='审批状态'), + ), + migrations.AlterField( + model_name='pricechange', + name='change_reason', + field=models.TextField(help_text='必填,最多 200 字;如"业主主动降价"', verbose_name='调价原因'), + ), + migrations.AlterField( + model_name='pricechange', + name='changed_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='调价操作时间'), + ), + migrations.AlterField( + model_name='pricechange', + name='changed_by', + field=models.ForeignKey(help_text='禁止置 NULL,保留审计追溯', on_delete=django.db.models.deletion.RESTRICT, to='org.staff', verbose_name='操作人'), + ), + migrations.AlterField( + model_name='pricechange', + name='new_bottom_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;NULL=本次不变更底价', max_digits=12, null=True, verbose_name='调价后售底价'), + ), + migrations.AlterField( + model_name='pricechange', + name='new_record_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;NULL=本次不变更', max_digits=12, null=True, verbose_name='调价后备案/核验价'), + ), + migrations.AlterField( + model_name='pricechange', + name='new_rent_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='元/月', max_digits=10, null=True, verbose_name='调价后挂牌租价'), + ), + migrations.AlterField( + model_name='pricechange', + name='new_sale_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元', max_digits=12, null=True, verbose_name='调价后挂牌售价'), + ), + migrations.AlterField( + model_name='pricechange', + name='old_bottom_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;NULL=未设置', max_digits=12, null=True, verbose_name='调价前售底价'), + ), + migrations.AlterField( + model_name='pricechange', + name='old_record_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;NULL=未设置', max_digits=12, null=True, verbose_name='调价前备案/核验价'), + ), + migrations.AlterField( + model_name='pricechange', + name='old_rent_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='元/月;NULL=非出租类或未设置', max_digits=10, null=True, verbose_name='调价前挂牌租价'), + ), + migrations.AlterField( + model_name='pricechange', + name='old_sale_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='万元;NULL=首次定价', max_digits=12, null=True, verbose_name='调价前挂牌售价'), + ), + migrations.AlterField( + model_name='pricechange', + name='property', + field=models.ForeignKey(help_text='禁止级联删除,保留调价历史', on_delete=django.db.models.deletion.RESTRICT, related_name='price_changes', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='property', + name='area', + field=models.DecimalField(decimal_places=2, help_text='含公摊;录入必填', max_digits=8, verbose_name='建筑面积(m²)'), + ), + migrations.AlterField( + model_name='property', + name='attribute', + field=models.CharField(choices=[('public', '公盘'), ('private', '私盘'), ('special', '特盘'), ('sealed', '封盘')], default='public', help_text='public=公盘/private=私盘/special=特盘/sealed=封盘;控制可见范围', max_length=10, verbose_name='流通属性'), + ), + migrations.AlterField( + model_name='property', + name='balcony_count', + field=models.SmallIntegerField(default=0, help_text='0=无阳台', verbose_name='阳台数'), + ), + migrations.AlterField( + model_name='property', + name='bathroom_count', + field=models.SmallIntegerField(default=0, verbose_name='卫生间数(卫)'), + ), + migrations.AlterField( + model_name='property', + name='bedroom_count', + field=models.SmallIntegerField(default=0, verbose_name='卧室数(室)'), + ), + migrations.AlterField( + model_name='property', + name='block_no', + field=models.CharField(blank=True, default='', help_text="如'3栋'、'A幢'", max_length=30, verbose_name='栋/幢/弄号'), + ), + migrations.AlterField( + model_name='property', + name='building', + field=models.ForeignKey(blank=True, help_text='楼栋被删除时置 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='properties', to='fonrey_complex.building', verbose_name='所属楼栋'), + ), + migrations.AlterField( + model_name='property', + name='built_year', + field=models.SmallIntegerField(blank=True, help_text='如 2018;可空(老房源无记录),影响营销发房', null=True, verbose_name='建成年份'), + ), + migrations.AlterField( + model_name='property', + name='buyer_agent', + field=models.ForeignKey(blank=True, help_text='促成成交的买方经纪人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='buying_properties', to='org.staff', verbose_name='实买方'), + ), + migrations.AlterField( + model_name='property', + name='completeness_score', + field=models.SmallIntegerField(default=0, help_text='0-100;由 Celery 异步计算,非实时;前端列表页展示徽章', verbose_name='维护完成度评分'), + ), + migrations.AlterField( + model_name='property', + name='complex', + field=models.ForeignKey(help_text='房源必须挂在楼盘下,禁止级联删除', on_delete=django.db.models.deletion.RESTRICT, related_name='properties', to='fonrey_complex.complex', verbose_name='所属楼盘'), + ), + migrations.AlterField( + model_name='property', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_properties', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='property', + name='decoration', + field=models.CharField(blank=True, choices=[('rough', '毛坯'), ('plain', '清水'), ('simple', '简装'), ('medium', '中装'), ('fine', '精装'), ('luxury', '豪装')], default='', help_text='rough=毛坯/plain=清水/simple=简装/medium=中装/fine=精装/luxury=豪装', max_length=10, verbose_name='装修情况'), + ), + migrations.AlterField( + model_name='property', + name='first_recorder', + field=models.ForeignKey(blank=True, help_text='最初录入该房源的经纪人;人员离职后置 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='first_recorded_properties', to='org.staff', verbose_name='首录方'), + ), + migrations.AlterField( + model_name='property', + name='floor', + field=models.SmallIntegerField(help_text='正整数;不超过 total_floors(CheckConstraint 校验)', verbose_name='所在楼层'), + ), + migrations.AlterField( + model_name='property', + name='grade', + field=models.CharField(blank=True, choices=[('a', 'A(急迫)'), ('b', 'B(较强)'), ('c', 'C(一般)'), ('d', 'D(较弱)')], default='', help_text='A=急迫/B=较强/C=一般/D=较弱(业主出售意向)', max_length=2, verbose_name='房源等级'), + ), + migrations.AlterField( + model_name='property', + name='has_elevator', + field=models.BooleanField(blank=True, help_text='true=有/false=无/NULL=未确认', null=True, verbose_name='是否有电梯'), + ), + migrations.AlterField( + model_name='property', + name='has_loan', + field=models.BooleanField(blank=True, help_text='true=有/false=无/NULL=未确认', null=True, verbose_name='是否有贷款(未还清)'), + ), + migrations.AlterField( + model_name='property', + name='has_mortgage', + field=models.BooleanField(blank=True, help_text='true=有/false=无/NULL=未确认', null=True, verbose_name='是否有抵押'), + ), + migrations.AlterField( + model_name='property', + name='has_restriction', + field=models.BooleanField(blank=True, help_text='true=有/false=无/NULL=未确认', null=True, verbose_name='是否有其他限制'), + ), + migrations.AlterField( + model_name='property', + name='has_seal', + field=models.BooleanField(blank=True, help_text='true=有/false=无/NULL=未确认', null=True, verbose_name='是否被查封'), + ), + migrations.AlterField( + model_name='property', + name='house_status', + field=models.CharField(blank=True, choices=[('owner_occupied', '业主自住'), ('vacant', '空置'), ('tenant_occupied', '租客在住'), ('unknown', '未知')], default='', help_text='owner_occupied=业主自住/vacant=空置/tenant_occupied=租客租住/unknown=未知;影响带看安排', max_length=20, verbose_name='房屋现状'), + ), + migrations.AlterField( + model_name='property', + name='inner_area', + field=models.DecimalField(blank=True, decimal_places=2, help_text='不含公摊;选填,编辑页专属字段', max_digits=8, null=True, verbose_name='套内面积(m²)'), + ), + migrations.AlterField( + model_name='property', + name='is_only_house', + field=models.BooleanField(blank=True, help_text='true=唯一/false=非唯一/NULL=未确认;影响交易税费计算', null=True, verbose_name='是否唯一住房'), + ), + migrations.AlterField( + model_name='property', + name='kitchen_count', + field=models.SmallIntegerField(default=0, verbose_name='厨房数(厨)'), + ), + migrations.AlterField( + model_name='property', + name='last_followed_at', + field=models.DateTimeField(blank=True, help_text='冗余字段,由触发器自动维护,加速超时未跟进排序', null=True, verbose_name='最后跟进时间'), + ), + migrations.AlterField( + model_name='property', + name='listed_at', + field=models.DateTimeField(blank=True, help_text='每次重新挂牌时更新', null=True, verbose_name='最近一次挂牌时间'), + ), + migrations.AlterField( + model_name='property', + name='living_room_count', + field=models.SmallIntegerField(default=0, verbose_name='客厅/餐厅数(厅)'), + ), + migrations.AlterField( + model_name='property', + name='number_holder', + field=models.ForeignKey(blank=True, help_text='持有业主联系号码的经纪人;变更需走审批流', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='held_properties', to='org.staff', verbose_name='号码方'), + ), + migrations.AlterField( + model_name='property', + name='orientation', + field=models.CharField(blank=True, choices=[('east', '东'), ('south', '南'), ('west', '西'), ('north', '北'), ('southeast', '东南'), ('northeast', '东北'), ('east_west', '东西'), ('south_north', '南北'), ('northwest', '西北'), ('southwest', '西南')], default='', help_text='east=东/south=南/west=西/north=北/southeast=东南/northeast=东北/east_west=东西/south_north=南北/northwest=西北/southwest=西南', max_length=15, verbose_name='朝向'), + ), + migrations.AlterField( + model_name='property', + name='original_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='业主当年购入价,用于计算增值', max_digits=12, null=True, verbose_name='原购价(万元)'), + ), + migrations.AlterField( + model_name='property', + name='ownership_nature', + field=models.CharField(blank=True, choices=[('commercial', '商品房'), ('reform_housing', '房改房'), ('collective', '集资房'), ('economic', '经济适用房')], default='', help_text='commercial=商品房/reform_housing=房改房/collective=集资房/economic=经济活用房', max_length=20, verbose_name='产权性质'), + ), + migrations.AlterField( + model_name='property', + name='ownership_years', + field=models.CharField(blank=True, default='', help_text='不满2年/满2年/满5年 等(影响交易税费)', max_length=30, verbose_name='房本年限'), + ), + migrations.AlterField( + model_name='property', + name='ownership_years_detail', + field=models.CharField(blank=True, default='', help_text='满五/不满五(与 ownership_years 组合使用)', max_length=20, verbose_name='房本年限辅助说明'), + ), + migrations.AlterField( + model_name='property', + name='payment_method', + field=models.CharField(blank=True, choices=[('full', '全款'), ('mortgage', '按揭'), ('installment', '分期'), ('advance', '垫资')], default='', help_text='full=一次付清/mortgage=按揭付款/installment=分批次付款/advance=垫资解按', max_length=15, verbose_name='购房付款方式'), + ), + migrations.AlterField( + model_name='property', + name='private_reason', + field=models.TextField(blank=True, default='', help_text='attribute 为 private/sealed 时必填,最多 200 字', verbose_name='私盘/封盘原因'), + ), + migrations.AlterField( + model_name='property', + name='property_type', + field=models.CharField(choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('shop', '商铺'), ('office', '写字楼'), ('other', '其他')], help_text='residential=住宅/villa=别墅/commercial_residential=商住/shop=商铺/office=写字楼/other=其他(详见 ENUMS)', max_length=30, verbose_name='房源类型'), + ), + migrations.AlterField( + model_name='property', + name='remarks', + field=models.TextField(blank=True, default='', help_text='经纪人内部备注,最多 500 字,不对外展示', verbose_name='房源备注'), + ), + migrations.AlterField( + model_name='property', + name='rent_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='出租类房源使用', max_digits=10, null=True, verbose_name='挂牌租价(元/月)'), + ), + migrations.AlterField( + model_name='property', + name='room_no', + field=models.CharField(blank=True, default='', help_text="如'0301'、'1502'", max_length=30, verbose_name='房号/门牌号'), + ), + migrations.AlterField( + model_name='property', + name='sale_bottom_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='业主心理底价,仅内部可见,不对外展示', max_digits=12, null=True, verbose_name='售底价(万元)'), + ), + migrations.AlterField( + model_name='property', + name='sale_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='出售类房源必填,出租类可为 NULL', max_digits=12, null=True, verbose_name='挂牌售价(万元)'), + ), + migrations.AlterField( + model_name='property', + name='sale_reason', + field=models.TextField(blank=True, default='', help_text="业主出售理由,最多 200 字;如'置换'", verbose_name='售房原因'), + ), + migrations.AlterField( + model_name='property', + name='sale_record_price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='填写后同步至营销库', max_digits=12, null=True, verbose_name='备案/核验价(万元)'), + ), + migrations.AlterField( + model_name='property', + name='search_vector', + field=django.contrib.postgres.search.SearchVectorField(blank=True, help_text='由触发器自动维护,覆盖栋号/单元/房号/备注', null=True, verbose_name='全文检索向量'), + ), + migrations.AlterField( + model_name='property', + name='seller_agent', + field=models.ForeignKey(blank=True, help_text='负责出售跟进的经纪人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='selling_properties', to='org.staff', verbose_name='出售方'), + ), + migrations.AlterField( + model_name='property', + name='shop_depth', + field=models.DecimalField(blank=True, decimal_places=2, help_text='商铺专属', max_digits=6, null=True, verbose_name='进深(米)'), + ), + migrations.AlterField( + model_name='property', + name='shop_frontage', + field=models.DecimalField(blank=True, decimal_places=2, help_text='商铺专属,住宅类为 NULL', max_digits=6, null=True, verbose_name='开间(米)'), + ), + migrations.AlterField( + model_name='property', + name='shop_height', + field=models.DecimalField(blank=True, decimal_places=2, help_text='商铺专属', max_digits=6, null=True, verbose_name='层高(米)'), + ), + migrations.AlterField( + model_name='property', + name='shop_location', + field=models.CharField(blank=True, choices=[('street', '临街商铺'), ('mall', '商场'), ('residential', '住宅底商'), ('ground_floor', '底层'), ('complex', '综合体')], default='', help_text='street=沿街/mall=商场内/residential=住宅底商/ground_floor=楼栋底层/complex=综合体(商铺专属)', max_length=20, verbose_name='商铺位置类型'), + ), + migrations.AlterField( + model_name='property', + name='source', + field=models.CharField(blank=True, default='', help_text='枚举值由 lookup_items 维护,如:门店拓客/转介绍/网络等', max_length=50, verbose_name='房源来源渠道'), + ), + migrations.AlterField( + model_name='property', + name='status', + field=models.CharField(choices=[('for_sale', '出售'), ('for_rent', '出租'), ('for_sale_rent', '租售'), ('suspended', '暂缓'), ('sold_elsewhere', '他售'), ('rented_elsewhere', '他租'), ('sold', '成交'), ('unlisted', '未挂牌')], default='for_sale', help_text='for_sale=出售/for_rent=出租/for_sale_rent=租售/suspended=暂缓/sold_elsewhere=他售/rented_elsewhere=他租/sold=成交/unlisted=未挂牌(详见 ENUMS)', max_length=20, verbose_name='交易状态'), + ), + migrations.AlterField( + model_name='property', + name='tax_included', + field=models.CharField(blank=True, choices=[('each_party', '各付'), ('net', '净到手'), ('inclusive', '包税')], default='', help_text='each_party=各付/net=到手/inclusive=包税', max_length=15, verbose_name='包税费方式'), + ), + migrations.AlterField( + model_name='property', + name='total_floors', + field=models.SmallIntegerField(help_text='正整数', verbose_name='楼栋总层数'), + ), + migrations.AlterField( + model_name='property', + name='unit_no', + field=models.CharField(blank=True, default='', help_text="如'1单元'、'055'", max_length=30, verbose_name='单元号'), + ), + migrations.AlterField( + model_name='property', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_properties', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='property', + name='usage_subtype', + field=models.CharField(blank=True, default='', help_text='如:普通住宅/花园洋房;对应更改用途浮窗第二级下拉', max_length=30, verbose_name='房屋用途细分小类'), + ), + migrations.AlterField( + model_name='property', + name='usage_type', + field=models.CharField(blank=True, default='', help_text='如:住宅/商住/商业;对应更改用途浮窗第一级下拉', max_length=30, verbose_name='房屋用途大类'), + ), + migrations.AlterField( + model_name='property', + name='version', + field=models.IntegerField(default=1, help_text='每次 UPDATE 必须 +1;应用层检测 0 行受影响时抛 ConflictError', verbose_name='乐观锁版本号'), + ), + migrations.AlterField( + model_name='property', + name='viewing_time', + field=models.CharField(blank=True, choices=[('anytime', '随时看房'), ('by_appointment', '预约看房'), ('inconvenient', '不便看房')], default='', help_text='anytime=随时可看/by_appointment=提前预约/inconvenient=不方便看', max_length=20, verbose_name='看房时间安排'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='category', + field=models.CharField(choices=[('id_card', '身份证件'), ('property_cert', '产权证明'), ('commission_letter', '委托书'), ('other', '其他')], default='other', help_text='id_card=身份证/property_cert=产权证书/commission_letter=委托书/other=其他材料', max_length=20, verbose_name='附件分类'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='org.staff', verbose_name='上传人'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='附件存储路径'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='file_name', + field=models.CharField(max_length=255, verbose_name='原始文件名'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='file_size', + field=models.IntegerField(help_text='bytes', verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='file_type', + field=models.CharField(blank=True, default='', help_text='如 application/pdf、image/jpeg', max_length=50, verbose_name='MIME 类型'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertyattachment', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='控制同一房源附件的显示顺序', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='cert_no', + field=models.CharField(blank=True, default='', help_text='不动产权证书编号', max_length=100, verbose_name='产权证号'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='cert_status', + field=models.CharField(blank=True, default='', help_text='如:已过户/抵押中/查封/正常', max_length=30, verbose_name='产证状态'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='first_registered_at', + field=models.DateField(blank=True, help_text='产权证上的初始登记日期', null=True, verbose_name='首次登记时间'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='land_nature', + field=models.CharField(blank=True, default='', help_text='如:国有/集体/划拨/出让', max_length=30, verbose_name='土地性质'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='owner_cert_type', + field=models.CharField(blank=True, default='', help_text='如:身份证/护照/营业执照', max_length=20, verbose_name='产权人证件类型'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='owner_id_number', + field=models.CharField(blank=True, default='', help_text='身份证号/统一社会信用代码等', max_length=50, verbose_name='产权人证件号码'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='owner_name', + field=models.CharField(blank=True, default='', help_text='产权证书上登记的所有权人', max_length=100, verbose_name='产权人姓名'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='ownership_nature', + field=models.CharField(blank=True, default='', help_text='如:商品房/经济适用房/回迁房', max_length=30, verbose_name='权属性质'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='property', + field=models.OneToOneField(help_text='1:1 关联 properties 表', on_delete=django.db.models.deletion.CASCADE, related_name='certificate', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='property_location', + field=models.CharField(blank=True, default='', help_text='产权证书上的完整地址,最多 500 字', max_length=500, verbose_name='房屋坐落'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='propertycertificate', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='calculated_at', + field=models.DateTimeField(auto_now=True, help_text='最近一次 Celery 任务异步计算完成时间', verbose_name='最近计算时间'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='property', + field=models.OneToOneField(help_text='1:1 关联 properties 表', on_delete=django.db.models.deletion.CASCADE, related_name='completeness', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_attachment', + field=models.SmallIntegerField(default=0, help_text='满分 8;身份证/产权证/委托书等材料上传情况', verbose_name='附件得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_commission', + field=models.SmallIntegerField(default=0, help_text='满分 10;独家/普通委托书情况', verbose_name='委托得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_core_info', + field=models.SmallIntegerField(default=0, help_text='满分 8;包含房源核心字段完整度', verbose_name='重点信息得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_follow_up', + field=models.SmallIntegerField(default=0, help_text='满分 8;近期跟进记录情况', verbose_name='跟进得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_key', + field=models.SmallIntegerField(default=0, help_text='满分 10;钥匙托管情况', verbose_name='钥匙得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_other', + field=models.SmallIntegerField(default=0, help_text='满分 7;其他加分项', verbose_name='其他得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_survey', + field=models.SmallIntegerField(default=0, help_text='满分 16;实勘照片和报告完整度', verbose_name='实勘得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_verification', + field=models.SmallIntegerField(default=0, help_text='满分 7;房源信息核实情况', verbose_name='验证得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_viewing', + field=models.SmallIntegerField(default=0, help_text='满分 8;带看记录完整度', verbose_name='带看得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='score_vr', + field=models.SmallIntegerField(default=0, help_text='满分 8;VR/全景照片上传情况', verbose_name='VR得分'), + ), + migrations.AlterField( + model_name='propertycompleteness', + name='total_score', + field=models.SmallIntegerField(default=0, help_text='0-100;供列表排序用,与 properties.completeness_score 冗余', verbose_name='维护完成度总分'), + ), + migrations.AlterField( + model_name='propertycontact', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_property_contacts', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='propertycontact', + name='gender', + field=models.CharField(choices=[('male', '先生'), ('female', '女士')], default='male', help_text='male=先生/female=女士', max_length=10, verbose_name='性别'), + ), + migrations.AlterField( + model_name='propertycontact', + name='identity', + field=models.CharField(choices=[('owner', '业主'), ('contact', '联系人'), ('subletter', '转租人'), ('tenant', '租客'), ('agent', '代理人'), ('corporate', '企业法人')], default='contact', help_text='owner=业主/contact=联系人/subletter=二房东/tenant=租客/agent=代理人/corporate=企业法人', max_length=20, verbose_name='联系人身份'), + ), + migrations.AlterField( + model_name='propertycontact', + name='is_number_holder', + field=models.BooleanField(default=False, help_text='true=是号码方(审批通过)/false=否;号码方变更须走审批流', verbose_name='是否为号码方'), + ), + migrations.AlterField( + model_name='propertycontact', + name='name', + field=models.CharField(help_text="如'张先生';业主或其代理人的真实姓名", max_length=50, verbose_name='联系人姓名'), + ), + migrations.AlterField( + model_name='propertycontact', + name='number_holder_approved_at', + field=models.DateTimeField(blank=True, help_text='NULL=尚未成为号码方', null=True, verbose_name='号码方审批通过时间'), + ), + migrations.AlterField( + model_name='propertycontact', + name='phone2_enc', + field=models.BinaryField(blank=True, help_text='AES-256-GCM 加密;选填', null=True, verbose_name='手机号2密文'), + ), + migrations.AlterField( + model_name='propertycontact', + name='phone2_hash', + field=models.CharField(blank=True, default='', help_text='SHA-256;phone2_enc 存在时必填', max_length=64, verbose_name='手机号2哈希'), + ), + migrations.AlterField( + model_name='propertycontact', + name='phone_enc', + field=models.BinaryField(help_text='AES-256-GCM 加密,不可直接查询', verbose_name='手机号1密文'), + ), + migrations.AlterField( + model_name='propertycontact', + name='phone_hash', + field=models.CharField(help_text='SHA-256,用于重复房源检测和精确查询', max_length=64, verbose_name='手机号1哈希'), + ), + migrations.AlterField( + model_name='propertycontact', + name='property', + field=models.ForeignKey(help_text='房源删除时联级删除', on_delete=django.db.models.deletion.CASCADE, related_name='contacts', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertycontact', + name='qq', + field=models.CharField(blank=True, default='', help_text="选填;无数据时前端展示'-'", max_length=20, verbose_name='QQ号'), + ), + migrations.AlterField( + model_name='propertycontact', + name='remarks', + field=models.TextField(blank=True, default='', help_text='最多 200 字;补充说明联系人情况', verbose_name='备注'), + ), + migrations.AlterField( + model_name='propertycontact', + name='sort_order', + field=models.IntegerField(default=0, help_text='数值越小越靠前;控制联系人在面板中的显示顺序', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='propertycontact', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_property_contacts', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='propertycontact', + name='wechat', + field=models.CharField(blank=True, default='', help_text="选填;无数据时前端展示'-'", max_length=100, verbose_name='微信号'), + ), + migrations.AlterField( + model_name='propertyfavorite', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='收藏时间'), + ), + migrations.AlterField( + model_name='propertyfavorite', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorited_by', to='fonrey_property.property', verbose_name='收藏的房源'), + ), + migrations.AlterField( + model_name='propertyfavorite', + name='staff', + field=models.ForeignKey(help_text='员工注销时删除收藏记录', on_delete=django.db.models.deletion.CASCADE, related_name='favorite_properties', to='org.staff', verbose_name='收藏人'), + ), + migrations.AlterField( + model_name='propertykey', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='propertykey', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_property_keys', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='propertykey', + name='holder', + field=models.ForeignKey(blank=True, help_text='人员离职后置 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='held_keys', to='org.staff', verbose_name='持有人'), + ), + migrations.AlterField( + model_name='propertykey', + name='holder_snapshot', + field=models.JSONField(blank=True, help_text='{name, store_group};防止人员离职后丢失显示信息', null=True, verbose_name='持有人快照'), + ), + migrations.AlterField( + model_name='propertykey', + name='is_active', + field=models.BooleanField(default=True, help_text='true=在管中/false=已归还或失效', verbose_name='是否有效'), + ), + migrations.AlterField( + model_name='propertykey', + name='is_other_agency', + field=models.BooleanField(default=False, help_text='true=是他中介公司的钥匙/false=本司钥匙', verbose_name='是否他司钥匙'), + ), + migrations.AlterField( + model_name='propertykey', + name='key_type', + field=models.CharField(choices=[('mechanical', '机械钥匙'), ('password', '密码钥匙')], help_text='mechanical=机械钥匙/password=密码(如密码门锁)', max_length=20, verbose_name='钥匙类型'), + ), + migrations.AlterField( + model_name='propertykey', + name='other_agency_info', + field=models.CharField(blank=True, default='', help_text='最多 30 字;is_other_agency=true 时填写,如"链家"', max_length=30, verbose_name='他司中介信息'), + ), + migrations.AlterField( + model_name='propertykey', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='keys', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertykey', + name='remarks', + field=models.TextField(blank=True, default='', help_text='最多 200 字;如密码内容等补充说明', verbose_name='备注'), + ), + migrations.AlterField( + model_name='propertykey', + name='storage_unit', + field=models.ForeignKey(blank=True, help_text='钥匙存放在哪个部门', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stored_keys', to='org.orgunit', verbose_name='保管部门'), + ), + migrations.AlterField( + model_name='propertykey', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='ai_generated_attitude', + field=models.BooleanField(default=False, help_text='true=AI辅助生成', verbose_name='业主心态AI生成'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='ai_generated_points', + field=models.BooleanField(default=False, help_text='true=AI辅助生成(经纪人确认后使用)', verbose_name='核心卖点AI生成'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='complex_description', + field=models.TextField(blank=True, default='', help_text='最多 200 字;楼盘/小区周边配套描述', verbose_name='小区介绍'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='core_selling_points', + field=models.TextField(blank=True, default='', help_text='最多 200 字;展示给买家的重点卖点说明', verbose_name='核心卖点'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='layout_description', + field=models.TextField(blank=True, default='', help_text='最多 200 字;房源户型特点描述,面向买家展示', verbose_name='户型介绍'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='marketing_title', + field=models.CharField(blank=True, default='', help_text='0-30 字;前端发房时展示给买家的吸睛标题', max_length=30, verbose_name='营销标题'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='owner_attitude', + field=models.TextField(blank=True, default='', help_text='最多 200 字;仅内部可见,描述业主议价空间和心理状态', verbose_name='业主心态'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='property', + field=models.OneToOneField(help_text='1:1 关联 properties 表', on_delete=django.db.models.deletion.CASCADE, related_name='marketing', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='最后更新时间'), + ), + migrations.AlterField( + model_name='propertymarketing', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='end_at', + field=models.DateTimeField(blank=True, help_text='NULL=长期保护', null=True, verbose_name='保护到期时间'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='is_protected', + field=models.BooleanField(default=False, help_text='true=受保护(防止被他人抢单/公盘化)/false=未保护', verbose_name='是否处于保护状态'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='property', + field=models.OneToOneField(help_text='1:1 关联 properties 表', on_delete=django.db.models.deletion.CASCADE, related_name='protection', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='reason', + field=models.TextField(blank=True, default='', help_text='说明为何启用保护', verbose_name='保护原因'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='set_by', + field=models.ForeignKey(blank=True, help_text='人员离职后置 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, to='org.staff', verbose_name='设置人'), + ), + migrations.AlterField( + model_name='propertyprotection', + name='start_at', + field=models.DateTimeField(blank=True, help_text='NULL=尚未生效', null=True, verbose_name='保护开始时间'), + ), + migrations.AlterField( + model_name='propertytag', + name='color', + field=models.CharField(blank=True, default='', help_text='HEX 色值,如 #FF5733;前端标签徽章颜色', max_length=7, verbose_name='显示颜色'), + ), + migrations.AlterField( + model_name='propertytag', + name='is_active', + field=models.BooleanField(default=True, help_text='false=已停用不再展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='propertytag', + name='is_system', + field=models.BooleanField(default=False, help_text='true=系统内置标签不可删除;false=运营自定义标签可删', verbose_name='是否系统预置'), + ), + migrations.AlterField( + model_name='propertytag', + name='name', + field=models.CharField(help_text='最多 50 字;如:学区/地铁口/满五唯一', max_length=50, verbose_name='标签名称'), + ), + migrations.AlterField( + model_name='propertytag', + name='sort_order', + field=models.IntegerField(default=0, help_text='数值越小越靠前', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='propertytagrelation', + name='property', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_relations', to='fonrey_property.property', verbose_name='所属房源'), + ), + migrations.AlterField( + model_name='propertytagrelation', + name='tag', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='property_relations', to='fonrey_property.propertytag', verbose_name='所属标签'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='category', + field=models.CharField(choices=[('layout', '户型图'), ('living_room', '客厅'), ('dining_room', '餐厅'), ('bedroom', '卧室'), ('bathroom', '卫生间'), ('kitchen', '厨房'), ('entrance', '入户'), ('balcony', '阳台'), ('study', '书房'), ('indoor_other', '室内其他'), ('outdoor', '室外')], help_text='layout=户型图/living_room=客厅/dining_room=餐厅/bedroom=卧室/bathroom=卫生间/kitchen=厨房/entrance=门厅/balcony=阳台/study=书房/indoor_other=室内其他/outdoor=外景', max_length=20, verbose_name='照片空间分类'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='上传时间'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='file_key', + field=models.TextField(help_text='Cloudflare R2 对象路径', verbose_name='原图存储路径'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='file_size', + field=models.IntegerField(blank=True, help_text='bytes', null=True, verbose_name='文件大小'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='height', + field=models.IntegerField(blank=True, help_text='像素;上传时解析', null=True, verbose_name='图片高度'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='is_vr_screenshot', + field=models.BooleanField(default=False, help_text='true=全景/VR截图(区别于普通实拍照片)', verbose_name='是否为VR截图'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='sort_order', + field=models.SmallIntegerField(default=0, help_text='同一空间分类内,数值越小越靠前', verbose_name='排序权重'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='survey', + field=models.ForeignKey(help_text='实勘删除时联级删除', on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='fonrey_property.fieldsurvey', verbose_name='所属实勘'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='thumbnail_key', + field=models.TextField(blank=True, default='', help_text='Cloudflare Images 自动生成', verbose_name='缩略图路径'), + ), + migrations.AlterField( + model_name='surveyphoto', + name='width', + field=models.IntegerField(blank=True, help_text='像素;上传时解析', null=True, verbose_name='图片宽度'), + ), + ] diff --git a/apps/region/migrations/0003_alter_businessarea_district_and_more.py b/apps/region/migrations/0003_alter_businessarea_district_and_more.py new file mode 100644 index 0000000..cb0b820 --- /dev/null +++ b/apps/region/migrations/0003_alter_businessarea_district_and_more.py @@ -0,0 +1,154 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('region', '0002_alter_businessarea_options_alter_district_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='businessarea', + name='district', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='business_areas', to='region.district', verbose_name='所属城区'), + ), + migrations.AlterField( + model_name='businessarea', + name='is_active', + field=models.BooleanField(default=True, help_text='False=已停用,不在筛选项中展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='businessarea', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=7, max_digits=10, null=True, verbose_name='商圈中心纬度'), + ), + migrations.AlterField( + model_name='businessarea', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=7, max_digits=10, null=True, verbose_name='商圈中心经度'), + ), + migrations.AlterField( + model_name='businessarea', + name='name', + field=models.CharField(max_length=100, verbose_name='商圈名称'), + ), + migrations.AlterField( + model_name='businessarea', + name='sort_order', + field=models.IntegerField(default=0, verbose_name='排序'), + ), + migrations.AlterField( + model_name='district', + name='city', + field=models.CharField(help_text='支持多城市扩展,如「上海」「北京」', max_length=50, verbose_name='所属城市'), + ), + migrations.AlterField( + model_name='district', + name='is_active', + field=models.BooleanField(default=True, help_text='False=已停用,不在筛选项中展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='district', + name='name', + field=models.CharField(help_text='如「静安区」', max_length=50, verbose_name='行政区名称'), + ), + migrations.AlterField( + model_name='district', + name='short_name', + field=models.CharField(blank=True, default='', help_text='如「静安」', max_length=20, verbose_name='简称'), + ), + migrations.AlterField( + model_name='district', + name='sort_order', + field=models.IntegerField(default=0, help_text='列表展示排序', verbose_name='排序'), + ), + migrations.AlterField( + model_name='metroline', + name='city', + field=models.CharField(max_length=50, verbose_name='所属城市'), + ), + migrations.AlterField( + model_name='metroline', + name='color', + field=models.CharField(blank=True, default='', help_text='HEX 色值,如 #E3002B', max_length=7, verbose_name='线路颜色'), + ), + migrations.AlterField( + model_name='metroline', + name='is_active', + field=models.BooleanField(default=True, help_text='False=已停用,不在筛选项中展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='metroline', + name='name', + field=models.CharField(help_text='如「1号线」', max_length=50, verbose_name='线路名称'), + ), + migrations.AlterField( + model_name='metroline', + name='sort_order', + field=models.IntegerField(default=0, verbose_name='排序'), + ), + migrations.AlterField( + model_name='metrostation', + name='is_active', + field=models.BooleanField(default=True, help_text='False=已停用,不在筛选项中展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='metrostation', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=7, max_digits=10, null=True, verbose_name='站点纬度'), + ), + migrations.AlterField( + model_name='metrostation', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=7, max_digits=10, null=True, verbose_name='站点经度'), + ), + migrations.AlterField( + model_name='metrostation', + name='metro_line', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stations', to='region.metroline', verbose_name='所属线路'), + ), + migrations.AlterField( + model_name='metrostation', + name='name', + field=models.CharField(max_length=50, verbose_name='站名'), + ), + migrations.AlterField( + model_name='metrostation', + name='sort_order', + field=models.IntegerField(default=0, verbose_name='沿线排序'), + ), + migrations.AlterField( + model_name='school', + name='district', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='schools', to='region.district', verbose_name='所属城区'), + ), + migrations.AlterField( + model_name='school', + name='is_active', + field=models.BooleanField(default=True, help_text='False=已停用,不在筛选项中展示', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='school', + name='level', + field=models.CharField(blank=True, choices=[('normal', '普通'), ('key', '重点'), ('top', '名校')], default='', help_text='normal=普通 / key=重点 / top=名校', max_length=20, verbose_name='学校等级'), + ), + migrations.AlterField( + model_name='school', + name='name', + field=models.CharField(max_length=100, verbose_name='学校名称'), + ), + migrations.AlterField( + model_name='school', + name='nature', + field=models.CharField(blank=True, choices=[('public', '公立'), ('private', '私立'), ('international', '国际')], default='', help_text='public=公立 / private=私立 / international=国际学校', max_length=20, verbose_name='学校性质'), + ), + migrations.AlterField( + model_name='school', + name='type', + field=models.CharField(blank=True, choices=[('primary', '小学'), ('middle', '初中'), ('high', '高中'), ('k9', '九年一贯制'), ('k12', '十二年一贯制')], default='', help_text='primary=小学 / middle=初中 / high=高中 / k9=九年一贯制 / k12=十二年一贯制', max_length=20, verbose_name='学校类型'), + ), + ] diff --git a/apps/setting/migrations/0003_alter_fieldrequirementrule_entity_type_and_more.py b/apps/setting/migrations/0003_alter_fieldrequirementrule_entity_type_and_more.py new file mode 100644 index 0000000..20dce68 --- /dev/null +++ b/apps/setting/migrations/0003_alter_fieldrequirementrule_entity_type_and_more.py @@ -0,0 +1,160 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('org', '0003_alter_orgunit_address_city_and_more'), + ('setting', '0002_alter_fieldrequirementrule_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='fieldrequirementrule', + name='entity_type', + field=models.CharField(choices=[('residential', '住宅'), ('villa', '别墅'), ('commercial_residential', '商住'), ('shop', '商铺'), ('office', '写字楼'), ('other', '其他')], help_text='与 property.property_type 值域完全一致:residential/villa/commercial_residential/shop/office/other', max_length=50, verbose_name='实体类型'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='field_key', + field=models.CharField(help_text='如 orientation / decoration / floor / building_area', max_length=50, verbose_name='字段 key'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='module', + field=models.CharField(choices=[('property', '房源'), ('client', '客源')], help_text='property / client,MVP 仅 property', max_length=20, verbose_name='模块'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='requirement', + field=models.CharField(choices=[('required', '必填'), ('optional', '选填'), ('hidden', '隐藏')], help_text='required=必填 / optional=选填 / hidden=隐藏', max_length=10, verbose_name='规则'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='trade_status', + field=models.CharField(choices=[('sale', '出售'), ('rent', '出租'), ('sale_rent', '租售'), ('*', '全部')], help_text='sale=出售 / rent=出租 / sale_rent=租售 / *=全部(fallback 通配)', max_length=20, verbose_name='交易状态'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='更新时间'), + ), + migrations.AlterField( + model_name='fieldrequirementrule', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_field_rules', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='description', + field=models.TextField(blank=True, default='', help_text='前端 tooltip 使用', verbose_name='分组说明'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='key', + field=models.CharField(help_text='如 source / follow_purpose', max_length=100, verbose_name='分组 key'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='label_zh', + field=models.CharField(help_text='界面显示名称,如「客源来源」', max_length=50, verbose_name='分组中文名'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='module', + field=models.CharField(help_text='client / property', max_length=50, verbose_name='所属模块'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='sort_order', + field=models.SmallIntegerField(default=0, verbose_name='排序'), + ), + migrations.AlterField( + model_name='lookupgroup', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='更新时间'), + ), + migrations.AlterField( + model_name='lookupitem', + name='created_at', + field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'), + ), + migrations.AlterField( + model_name='lookupitem', + name='created_by', + field=models.ForeignKey(blank=True, help_text='系统预制时为 NULL', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_lookup_items', to='org.staff', verbose_name='创建人'), + ), + migrations.AlterField( + model_name='lookupitem', + name='group', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='setting.lookupgroup', verbose_name='所属分组'), + ), + migrations.AlterField( + model_name='lookupitem', + name='is_active', + field=models.BooleanField(default=True, help_text='False 后录入下拉不展示,历史记录保留并展示「(已停用)」后缀', verbose_name='是否启用'), + ), + migrations.AlterField( + model_name='lookupitem', + name='is_system', + field=models.BooleanField(default=False, help_text='True=系统预制不可物理删除,仅可停用', verbose_name='是否系统预制'), + ), + migrations.AlterField( + model_name='lookupitem', + name='label_zh', + field=models.CharField(help_text='如「上门」', max_length=50, verbose_name='显示文本'), + ), + migrations.AlterField( + model_name='lookupitem', + name='sort_order', + field=models.SmallIntegerField(default=0, verbose_name='排序'), + ), + migrations.AlterField( + model_name='lookupitem', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='更新时间'), + ), + migrations.AlterField( + model_name='lookupitem', + name='value', + field=models.CharField(help_text='英文 snake_case,如 door_to_door;写入后只读', max_length=100, verbose_name='存储值'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='category', + field=models.CharField(help_text='client / property / showroom', max_length=50, verbose_name='配置分类'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='key', + field=models.CharField(help_text='如 duplicate_check_scope', max_length=100, verbose_name='配置 key'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='更新时间'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='updated_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updated_tenant_settings', to='org.staff', verbose_name='最后修改人'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='value', + field=models.JSONField(help_text='JSONB,统一格式 {"v": }', verbose_name='配置值'), + ), + migrations.AlterField( + model_name='tenantsetting', + name='value_type', + field=models.CharField(choices=[('bool', '布尔'), ('int', '整数'), ('string', '字符串'), ('enum', '枚举')], help_text='bool / int / string / enum,用于前端渲染控件', max_length=20, verbose_name='值类型'), + ), + ] diff --git a/apps/tenant/migrations/0002_alter_domain_options_alter_tenant_options_and_more.py b/apps/tenant/migrations/0002_alter_domain_options_alter_tenant_options_and_more.py new file mode 100644 index 0000000..2d87951 --- /dev/null +++ b/apps/tenant/migrations/0002_alter_domain_options_alter_tenant_options_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.16 on 2026-04-30 01:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenant', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='domain', + options={'verbose_name': '域名', 'verbose_name_plural': '域名'}, + ), + migrations.AlterModelOptions( + name='tenant', + options={'verbose_name': '租户', 'verbose_name_plural': '租户'}, + ), + migrations.AlterField( + model_name='tenant', + name='created_on', + field=models.DateField(auto_now_add=True, verbose_name='创建日期'), + ), + migrations.AlterField( + model_name='tenant', + name='name', + field=models.CharField(help_text='租户公司名称', max_length=255, verbose_name='公司名称'), + ), + ]