From b5a617d04412825dbb99f6fb9ffc857f01d4ec07 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 13 Sep 2026 22:16:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(gerp):=20=E5=AF=B9=E9=BD=90=E9=81=97?= =?UTF-8?q?=E7=95=99=E5=BA=93=E5=88=97=E5=90=8D=E5=B9=B6=E6=B8=85=E7=90=86?= =?UTF-8?q?=20Asset=20=E8=A1=A8=20Flask=20=E6=9C=9F=E9=81=97=E7=95=99?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit models.py:CompanyInfo 三个身份证列与 PersonalPage.QQ 补 db_column,匹配库中 *IdCard / Qq 实际列名;新增 0044 仅状态迁移(SeparateDatabaseAndState,零 DDL)记录该对齐。 新增 0045:条件删除 Asset 表 8 个 Flask 期文件资产列(FileName/FileSHA1/FileSize/MimeType/StoragePath/UploaderUUID/RefCount/CreateTime)。这些列 NOT NULL 且无默认值,导致新增企业资产必然报 1364;删除前先解除 UploaderUUID 上的遗留外键 asset_ibfk_1。按 information_schema 判定存在性,幂等可重入。 --- .../0044_align_legacy_column_names.py | 46 +++++++++++ .../0045_purge_asset_legacy_file_columns.py | 76 +++++++++++++++++++ gvsds/apps/gerp/models.py | 8 +- 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 gvsds/apps/gerp/migrations/0044_align_legacy_column_names.py create mode 100644 gvsds/apps/gerp/migrations/0045_purge_asset_legacy_file_columns.py diff --git a/gvsds/apps/gerp/migrations/0044_align_legacy_column_names.py b/gvsds/apps/gerp/migrations/0044_align_legacy_column_names.py new file mode 100644 index 00000000..b0b74a0b --- /dev/null +++ b/gvsds/apps/gerp/migrations/0044_align_legacy_column_names.py @@ -0,0 +1,46 @@ +"""对齐遗留库列名:CompanyInfo 三个身份证列 + PersonalPage.QQ + +背景:CompanyInfo / PersonalPage 为 Flask 时期建表,实际列名为 `LegalPersonIdCard` / +`OperatorIdCard` / `FinanceManagerIdCard` / `Qq`(IdCard、Qq),而模型原先按 +`IDCard` / `QQ` 推导列名。MySQL 列名大小写不敏感,故线上读写无异常,但 +`manage.py gvsdsdk_check_schema` 会报 schema 漂移。 + +本迁移仅更新 Django 迁移状态以匹配模型新增的 db_column 声明,**不产生任何 DDL**: +真实表列名已与模型 db_column 一致,无需重命名。 +""" +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gerp', '0043_remove_userroleworkgroup_position'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AlterField( + model_name='companyinfo', + name='legalpersonidcard', + field=models.CharField(db_column='LegalPersonIdCard', default='', max_length=32, verbose_name='法人身份证'), + ), + migrations.AlterField( + model_name='companyinfo', + name='operatoridcard', + field=models.CharField(db_column='OperatorIdCard', default='', max_length=32, verbose_name='经办人身份证'), + ), + migrations.AlterField( + model_name='companyinfo', + name='financemanageridcard', + field=models.CharField(db_column='FinanceManagerIdCard', default='', max_length=32, verbose_name='财务身份证'), + ), + migrations.AlterField( + model_name='personalpage', + name='qq', + field=models.CharField(db_column='Qq', default='', max_length=256, verbose_name='QQ'), + ), + ], + database_operations=[], + ), + ] diff --git a/gvsds/apps/gerp/migrations/0045_purge_asset_legacy_file_columns.py b/gvsds/apps/gerp/migrations/0045_purge_asset_legacy_file_columns.py new file mode 100644 index 00000000..3e1418e7 --- /dev/null +++ b/gvsds/apps/gerp/migrations/0045_purge_asset_legacy_file_columns.py @@ -0,0 +1,76 @@ +"""清理 Asset 表 Flask 时期遗留的 8 个文件资产列 + +背景:`Asset` 表最初由 Flask 时期作为文件资产表创建(FileName/FileSHA1/FileSize/ +MimeType/StoragePath/UploaderUUID/RefCount/CreateTime),后经 ALTER 追加了 9 个 +企业资产列。这 8 个文件列均为 NOT NULL 且无默认值,导致经 Django 新增企业资产时 +必然报错 1364(Field 'FileName' doesn't have a default value)。 + +文件资产已由 `FileAsset` 表承载,此处条件删除这 8 个遗留列:先解除这些列上的遗留 +外键(如 UploaderUUID → user.UserUUID),再逐列删除。按 information_schema 判定 +存在性,幂等,兼容已清理 / 全新建库 / 上次中断的环境。 + +反向迁移为尽力恢复:按原类型重加为可空列,不恢复 NOT NULL、唯一索引与外键。 +""" +from django.db import migrations + + +LEGACY_COLUMNS = { + 'FileName': 'VARCHAR(256)', + 'FileSHA1': 'VARCHAR(40)', + 'FileSize': 'INT', + 'MimeType': 'VARCHAR(128)', + 'StoragePath': 'VARCHAR(512)', + 'UploaderUUID': 'BINARY(16)', + 'RefCount': 'INT', + 'CreateTime': 'DATETIME', +} + + +def _ExistingColumns(cursor, table): + """返回表当前实际存在的列名集合。""" + cursor.execute( + 'SELECT COLUMN_NAME FROM information_schema.COLUMNS ' + 'WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', + [table], + ) + return {row[0] for row in cursor.fetchall()} + + +def _DropLegacyColumns(apps, schema_editor): + """先解除遗留列上的外键,再按存在性逐列删除。""" + connection = schema_editor.connection + with connection.cursor() as cursor: + existing = _ExistingColumns(cursor, 'Asset') + cursor.execute( + 'SELECT CONSTRAINT_NAME, COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE ' + 'WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s ' + 'AND REFERENCED_TABLE_NAME IS NOT NULL', + ['Asset'], + ) + for constraint, column in cursor.fetchall(): + if column in LEGACY_COLUMNS and column in existing: + cursor.execute(f'ALTER TABLE `Asset` DROP FOREIGN KEY `{constraint}`') + for column in LEGACY_COLUMNS: + if column in existing: + cursor.execute(f'ALTER TABLE `Asset` DROP COLUMN `{column}`') + + +def _RestoreLegacyColumns(apps, schema_editor): + """反向:按原类型重加为可空列(尽力恢复)。""" + connection = schema_editor.connection + with connection.cursor() as cursor: + existing = _ExistingColumns(cursor, 'Asset') + for column, column_type in LEGACY_COLUMNS.items(): + if column not in existing: + cursor.execute(f'ALTER TABLE `Asset` ADD COLUMN `{column}` {column_type} NULL') + + +class Migration(migrations.Migration): + + dependencies = [ + ('gerp', '0044_align_legacy_column_names'), + ] + + operations = [ + migrations.RunPython(_DropLegacyColumns, _RestoreLegacyColumns), + ] diff --git a/gvsds/apps/gerp/models.py b/gvsds/apps/gerp/models.py index 1509ec10..679d0584 100644 --- a/gvsds/apps/gerp/models.py +++ b/gvsds/apps/gerp/models.py @@ -99,7 +99,7 @@ class CompanyInfo(QModel): SocialSecurityBank = models.CharField(max_length=128, default='', verbose_name='社保银行') SocialSecurityAccount = models.CharField(max_length=64, default='', verbose_name='社保账号') LegalPersonName = models.CharField(max_length=64, default='', verbose_name='法人姓名') - LegalPersonIDCard = models.CharField(max_length=32, default='', verbose_name='法人身份证') + LegalPersonIDCard = models.CharField(max_length=32, default='', db_column='LegalPersonIdCard', verbose_name='法人身份证') LegalPersonGender = models.CharField(max_length=16, default='', verbose_name='法人性别') LegalPersonBirthday = models.CharField(max_length=32, default='', verbose_name='法人生日') LegalPersonNationality = models.CharField(max_length=32, default='', verbose_name='法人国籍') @@ -111,7 +111,7 @@ class CompanyInfo(QModel): LegalPersonShareRatio = models.CharField(max_length=16, default='', verbose_name='法人持股比例') LegalPersonAppointment = models.CharField(max_length=32, default='', verbose_name='法人任职') OperatorName = models.CharField(max_length=64, default='', verbose_name='经办人姓名') - OperatorIDCard = models.CharField(max_length=32, default='', verbose_name='经办人身份证') + OperatorIDCard = models.CharField(max_length=32, default='', db_column='OperatorIdCard', verbose_name='经办人身份证') OperatorPhone = models.CharField(max_length=32, default='', verbose_name='经办人电话') OperatorEmail = models.CharField(max_length=128, default='', verbose_name='经办人邮箱') OperatorDepartment = models.CharField(max_length=128, default='', verbose_name='经办人部门') @@ -119,7 +119,7 @@ class CompanyInfo(QModel): FinanceManagerName = models.CharField(max_length=64, default='', verbose_name='财务负责人') FinanceManagerPhone = models.CharField(max_length=32, default='', verbose_name='财务电话') FinanceManagerEmail = models.CharField(max_length=128, default='', verbose_name='财务邮箱') - FinanceManagerIDCard = models.CharField(max_length=32, default='', verbose_name='财务身份证') + FinanceManagerIDCard = models.CharField(max_length=32, default='', db_column='FinanceManagerIdCard', verbose_name='财务身份证') Remark = models.TextField(default='', verbose_name='备注') ReviewStatus = models.SmallIntegerField(default=0, verbose_name='审核状态') ReviewRemark = models.CharField(max_length=512, default='', verbose_name='审核备注') @@ -980,7 +980,7 @@ class PersonalPage(QModel): Classification = models.CharField(max_length=256, default='', verbose_name='分类') Github = models.CharField(max_length=256, default='', verbose_name='GitHub') Wechat = models.CharField(max_length=256, default='', verbose_name='微信') - QQ = models.CharField(max_length=256, default='', verbose_name='QQ') + QQ = models.CharField(max_length=256, default='', db_column='Qq', verbose_name='QQ') SelfIntroduction = models.TextField(default='', verbose_name='自我介绍') ProjectExperience = models.CharField(max_length=256, default='', verbose_name='项目经验') SatisfiedCustomers = models.CharField(max_length=256, default='', verbose_name='满意客户')