"""数据库结构自动迁移:检查并补全缺失的列/表 用法: venv/bin/python3 migrate_db.py 每次代码升级后自动执行,确保生产数据库结构与代码一致。 """ import sys import os # 切换到项目根目录 os.chdir(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.getcwd()) from services.db import query def table_exists(name): rows = query( "SELECT TABLE_NAME FROM information_schema.TABLES " "WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s", (name,) ) return len(rows or []) > 0 def column_exists(table, column): rows = query( "SELECT COLUMN_NAME FROM information_schema.COLUMNS " "WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s AND COLUMN_NAME=%s", (table, column) ) return len(rows or []) > 0 def index_exists(table, index_name): rows = query( "SELECT INDEX_NAME FROM information_schema.STATISTICS " "WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s AND INDEX_NAME=%s LIMIT 1", (table, index_name) ) return len(rows or []) > 0 def add_column(table, column, definition): sql = f"ALTER TABLE `{table}` ADD COLUMN `{column}` {definition}" query(sql) print(f" ✅ {table}.{column}") def rename_column(table, old_column, new_column): query(f"ALTER TABLE `{table}` RENAME COLUMN `{old_column}` TO `{new_column}`") print(f" ✅ {table}.{old_column} → {new_column}") def create_table(name, definition): query(f"CREATE TABLE `{name}` ({definition}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci") print(f" ✅ 新建表 {name}") # ─── 需要检查并自动创建的表 ─── TABLES = { 'contract_records': """ `id` bigint NOT NULL AUTO_INCREMENT, `contract_no` varchar(80) NOT NULL, `supplier_id` int DEFAULT NULL, `supplier_name` varchar(255) NOT NULL DEFAULT '', `buyer_name` varchar(255) NOT NULL DEFAULT '', `order_type` varchar(32) NOT NULL DEFAULT '', `template_id` int DEFAULT NULL, `template_name` varchar(100) NOT NULL DEFAULT '', `order_count` int NOT NULL DEFAULT 0, `product_count` int NOT NULL DEFAULT 0, `total_amount` decimal(18,2) NOT NULL DEFAULT 0, `status` varchar(32) NOT NULL DEFAULT '已生成', `signer` varchar(100) NOT NULL DEFAULT '', `signed_at` date DEFAULT NULL, `effective_date` date DEFAULT NULL, `expiry_date` date DEFAULT NULL, `payment_terms` text NULL, `delivery_terms` text NULL, `remark` text NULL, `order_ids_json` longtext NULL, `orders_json` longtext NULL, `current_file_path` varchar(500) NOT NULL DEFAULT '', `current_file_name` varchar(255) NOT NULL DEFAULT '', `created_by` int DEFAULT NULL, `created_by_name` varchar(100) NOT NULL DEFAULT '', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_by` int DEFAULT NULL, `updated_by_name` varchar(100) NOT NULL DEFAULT '', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `is_deleted` tinyint NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_contract_no` (`contract_no`), KEY `idx_contract_supplier` (`supplier_name`), KEY `idx_contract_status` (`status`, `created_at`), KEY `idx_contract_type` (`order_type`, `created_at`) """, 'contract_files': """ `id` bigint NOT NULL AUTO_INCREMENT, `contract_id` bigint NOT NULL, `version` int NOT NULL, `file_path` varchar(500) NOT NULL, `file_name` varchar(255) NOT NULL DEFAULT '', `generated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `generated_by` int DEFAULT NULL, `generated_by_name` varchar(100) NOT NULL DEFAULT '', `is_current` tinyint NOT NULL DEFAULT 0, `remark` varchar(500) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `uk_contract_file_version` (`contract_id`, `version`), KEY `idx_contract_file_current` (`contract_id`, `is_current`) """, 'contract_events': """ `id` bigint NOT NULL AUTO_INCREMENT, `contract_id` bigint NOT NULL, `action` varchar(80) NOT NULL, `detail` text NULL, `user_id` int DEFAULT NULL, `user_name` varchar(100) NOT NULL DEFAULT '', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_contract_event` (`contract_id`, `created_at`) """, 'planning_suggestion_snapshots': """ `id` bigint NOT NULL AUTO_INCREMENT, `snapshot_date` date NOT NULL COMMENT '快照日期', `product_code` varchar(100) NOT NULL COMMENT '货品编号', `product_name` varchar(300) NOT NULL DEFAULT '' COMMENT '货品名称', `suggested_qty` int NOT NULL DEFAULT 0 COMMENT '建议下单数量', `consecutive_days` int NOT NULL DEFAULT 0 COMMENT '连续建议天数', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_planning_snapshot_date_product` (`snapshot_date`, `product_code`), KEY `idx_planning_snapshot_product_date` (`product_code`, `snapshot_date`) """, 'spot_authorization_materials': """ `id` bigint NOT NULL AUTO_INCREMENT, `merchant_code` varchar(100) NOT NULL DEFAULT '' COMMENT '商家编码', `product_name` varchar(300) NOT NULL DEFAULT '' COMMENT '货品名称', `platform` varchar(100) NOT NULL DEFAULT '' COMMENT '平台', `operator` varchar(100) NOT NULL DEFAULT '' COMMENT '负责运营', `authorization_letter` varchar(255) NOT NULL DEFAULT '' COMMENT '授权书文件名', `testing_report` varchar(255) NOT NULL DEFAULT '' COMMENT '检测报告文件名', `factory_qualification` text NOT NULL COMMENT '工厂资质文件名(单个文件名或JSON数组)', `trademark` text NOT NULL COMMENT '商标文件名(单个文件名或JSON数组)', `remark` text NULL COMMENT '备注', `created_by` int DEFAULT NULL COMMENT '创建人账号ID', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_spot_authorization_merchant` (`merchant_code`), KEY `idx_spot_authorization_name` (`product_name`) """, 'sales_management_records': """ `id` bigint NOT NULL AUTO_INCREMENT, `c01` text NULL COMMENT '批次', `c02` text NULL COMMENT '货品编码', `c03` text NULL COMMENT '货品名称', `c04` text NULL COMMENT '确权人', `c05` text NULL COMMENT '确权平台', `c06` text NULL COMMENT '库存区间', `c07` text NULL COMMENT '建档时间', `c08` text NULL COMMENT '盘盈盘亏', `c09` text NULL COMMENT '差异合计', `c10` text NULL COMMENT '生产日期', `c11` text NULL COMMENT '过期时间', `c12` text NULL COMMENT '成本价', `c13` text NULL COMMENT '总库存', `c14` text NULL COMMENT '可发库存', `c15` text NULL COMMENT '可支撑天数', `c16` text NULL COMMENT '30天销量', `c17` text NULL COMMENT '仓库存合计', `c18` text NULL COMMENT '29青鸟海外仓', `c19` text NULL COMMENT '27启程仓', `c20` text NULL COMMENT '26海外抖音仓', `c21` text NULL COMMENT '25海外拼多多保税仓', `c22` text NULL COMMENT '1蝶雅仓', `c23` text NULL COMMENT '2青鸟仓', `c24` text NULL COMMENT '4百橙仓', `c25` text NULL COMMENT '5东升仓', `c26` text NULL COMMENT '6CK仓', `c27` text NULL COMMENT '9天猫菜鸟仓', `c28` text NULL COMMENT '10唯品会海外仓', `c29` text NULL COMMENT '京东海外11仓', `c30` text NULL COMMENT '京东海外12仓', `c31` text NULL COMMENT '京东13仓', `c32` text NULL COMMENT '京东14仓', `c33` text NULL COMMENT '16蝶雅南阳仓', `c34` text NULL COMMENT '分类', `c35` text NULL COMMENT '戴总确认意见', `c36` text NULL COMMENT '运营核实更改', `c37` text NULL COMMENT '运营核对备注', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_sales_management_code` (`c02`(100)), KEY `idx_sales_management_name` (`c03`(150)) """, 'brand_information': """ `id` int NOT NULL AUTO_INCREMENT, `brand` varchar(200) NOT NULL COMMENT '品牌名称', `category` varchar(20) NOT NULL DEFAULT '' COMMENT '国内/海外,空表示未分类', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_brand_information_brand` (`brand`) """, 'spot_applications': """ `id` int NOT NULL AUTO_INCREMENT, `applicant_user_id` int NOT NULL COMMENT '申请人账号ID', `application_date` date NOT NULL COMMENT '申请日期', `department` varchar(100) NOT NULL DEFAULT '' COMMENT '部门(组织架构导入后补充)', `brand` varchar(200) NOT NULL DEFAULT '' COMMENT '品牌', `product_name` varchar(300) NOT NULL COMMENT '产品名称', `manufacturer` varchar(300) NOT NULL DEFAULT '' COMMENT '厂家', `image_path` varchar(500) NOT NULL DEFAULT '' COMMENT '产品图片路径', `product_link` varchar(1000) NOT NULL DEFAULT '' COMMENT '产品链接', `sample_quantity` int NOT NULL DEFAULT 0 COMMENT '寄样数量', `barcode` varchar(100) NOT NULL DEFAULT '' COMMENT '条形码', `recipient_info` text COMMENT '收件信息', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_spot_application_user` (`applicant_user_id`), KEY `idx_spot_application_date` (`application_date`), CONSTRAINT `fk_spot_application_user` FOREIGN KEY (`applicant_user_id`) REFERENCES `dashboard_users` (`id`) """, 'spot_confirmations': """ `id` int NOT NULL AUTO_INCREMENT, `application_id` int NOT NULL COMMENT '现货申请ID', `sample_status` varchar(50) NOT NULL DEFAULT '' COMMENT '样品状态', `sample_remark` text COMMENT '样品备注', `oa_approval_no` varchar(100) NOT NULL DEFAULT '' COMMENT 'OA审批单号', `express_tracking_no` varchar(100) NOT NULL DEFAULT '' COMMENT '寄样快递单号', `arrival_date` date DEFAULT NULL COMMENT '到样日期', `barcode` varchar(100) NOT NULL DEFAULT '' COMMENT '可编辑条形码', `remark` text COMMENT '备注', `receiving_department` varchar(100) NOT NULL DEFAULT '' COMMENT '领样部门', `recipient` varchar(100) NOT NULL DEFAULT '' COMMENT '领样人', `sample_confirmed` varchar(50) NOT NULL DEFAULT '' COMMENT '样品确认', `sample_confirmation_reason` text COMMENT '样品确认原因', `sample_location` varchar(200) NOT NULL DEFAULT '' COMMENT '样品位置', `product_code` varchar(100) NOT NULL DEFAULT '' COMMENT '产品编码', `final_product_name` varchar(300) NOT NULL DEFAULT '' COMMENT '货品名称(最终名称)', `platform` varchar(200) NOT NULL DEFAULT '' COMMENT '平台', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_spot_confirmation_application` (`application_id`), KEY `idx_spot_confirmation_status` (`sample_status`), CONSTRAINT `fk_spot_confirmation_application` FOREIGN KEY (`application_id`) REFERENCES `spot_applications` (`id`) """, 'user_positions': """ `id` int NOT NULL AUTO_INCREMENT, `position_code` varchar(80) NOT NULL COMMENT '岗位编码', `position_name` varchar(100) NOT NULL COMMENT '岗位名称', `sort_order` int NOT NULL DEFAULT 0 COMMENT '排序值', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1=启用 0=停用', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_position_code` (`position_code`), UNIQUE KEY `uk_position_name` (`position_name`) """, 'user_column_widths': """ `id` int NOT NULL AUTO_INCREMENT, `user_id` int NOT NULL, `page` varchar(50) NOT NULL COMMENT '页面标识', `field_name` varchar(100) NOT NULL COMMENT '稳定字段名', `width` int NOT NULL COMMENT '列宽(px)', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_page_field` (`user_id`,`page`,`field_name`), KEY `idx_user_page` (`user_id`,`page`) """, } # ─── 需要保持与前端字段一致的列重命名 ─── COLUMN_RENAMES = { 'domestic_orders': [ ('编码', '货品编号'), ('名称', '货品名称'), ], } # ─── 需要检查的列 ─── SCHEMA = { 'spot_applications': [ ('status', "varchar(50) NOT NULL DEFAULT '待处理' COMMENT '申请状态'"), ('reason', "varchar(500) NOT NULL DEFAULT '' COMMENT '申请原因'"), ('applicant_name', "varchar(200) NOT NULL DEFAULT '' COMMENT '申请人姓名(历史导入文本)'"), ], 'dashboard_users': [ ('must_change_password', "tinyint DEFAULT 1 COMMENT '1=下次登录必须修改密码'"), ('department', "varchar(50) NOT NULL DEFAULT '' COMMENT '部门'"), ], 'spot_confirmations': [ ('barcode', "varchar(100) NOT NULL DEFAULT '' COMMENT '可编辑条形码'"), ], 'packaging_orders': [ ('计划下单时间', "varchar(50) DEFAULT NULL COMMENT '计划下单日期'"), ('新旧品', "varchar(20) DEFAULT NULL COMMENT '新品、旧品或无'"), ('是否审核价格', "varchar(50) DEFAULT '未审核' COMMENT '包材价格审核状态'"), ('来源产品编码', "varchar(100) DEFAULT '' COMMENT '来源成品编码'"), ], 'domestic_orders': [ ('金蝶入库', "varchar(50) DEFAULT '否' COMMENT '金蝶是否入库'"), ('未到货数', "int DEFAULT 0 COMMENT '未到货数量'"), ('完结时间', "date DEFAULT NULL COMMENT '订单完结日期'"), ], 'pending_orders': [ ('product_orderer', "varchar(200) DEFAULT '' COMMENT '成品订单下单人(国内/海外订单同步)'"), ('packaging_orderer', "varchar(200) DEFAULT '' COMMENT '包材订单下单人同步'"), ('sales_snapshot', "longtext DEFAULT NULL COMMENT '计划预测点击下单时的销量信息快照'"), ('sales_snapshot_at', "datetime DEFAULT NULL COMMENT '销量信息快照时间'"), ], 'arrival_batches': [ ('order_type', "varchar(20) NOT NULL DEFAULT 'domestic' COMMENT '订单类型'"), ('operator_name', "varchar(100) NOT NULL DEFAULT '' COMMENT '操作人'"), ('operated_at', 'datetime DEFAULT NULL COMMENT "操作时间"'), ], 'new_product_bom': [ ('usage_quantity', "decimal(12,4) NOT NULL DEFAULT '1.0000' COMMENT 'BOM用量'"), ], 'products': [ # (列名, 定义) ('deleted_at', 'datetime DEFAULT NULL COMMENT "软删除时间"'), ('link', "varchar(500) DEFAULT '' COMMENT '链接'"), ('sku_type', "varchar(50) DEFAULT '' COMMENT '产品类型(爆品/日销/新品)'"), ('remark', "varchar(500) DEFAULT '' COMMENT '批注'"), ], 'warehouses': [ ('warehouse_property', "varchar(100) DEFAULT NULL COMMENT '仓库属性(云仓,外仓)'"), ('category', "varchar(50) DEFAULT NULL COMMENT '仓库分类'"), ('disabled', "varchar(10) DEFAULT NULL COMMENT '是否禁用'"), ('remark', "varchar(500) DEFAULT NULL COMMENT '备注'"), ], 'new_products': [ ('procurement_planner', "varchar(100) NOT NULL DEFAULT '' COMMENT '采购计划'"), ], 'suppliers': [ ('data_status', "varchar(50) DEFAULT NULL COMMENT '数据状态'"), ('disabled_status', "varchar(50) DEFAULT NULL COMMENT '禁用状态'"), ('production_type', "varchar(200) DEFAULT NULL COMMENT '生产类型'"), ('review_date', "date DEFAULT NULL COMMENT '审核日期'"), ('reviewer', "varchar(100) DEFAULT NULL COMMENT '审核人'"), ('short_name', "varchar(200) DEFAULT NULL COMMENT '简称'"), ('supplier_group', "varchar(200) DEFAULT NULL COMMENT '供应商分组'"), ('procurement_follower', "varchar(100) DEFAULT NULL COMMENT '跟单小组'"), ('procurement_planner', "varchar(100) DEFAULT NULL COMMENT '采购计划'"), ], 'user_permissions': [ ('updated_at', "timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'"), ], 'role_permissions': [ ('updated_at', "timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'"), ], 'bom_packaging': [ ('pkg_remark', "varchar(500) DEFAULT '' COMMENT '包材备注'"), ], 'plan_hidden': [ ('created_at', "datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'"), ], 'contract_templates': [ ('supplier_id', 'int DEFAULT NULL COMMENT "供应商ID"'), ], } def main(): print("") print("数据库结构检查 & 自动迁移") print("=" * 40) print("") changed = 0 created = 0 for table, definition in TABLES.items(): if table_exists(table): continue try: create_table(table, definition) created += 1 except Exception as e: print(f" ❌ 表 {table} 创建失败: {e}") # 初始化岗位字典,并把历史用户中出现过但尚未进入字典的岗位补齐。 # 只新增字典记录,不修改或删除 dashboard_users 中的历史数据。 if table_exists('user_positions'): default_positions = [ ('procurement_planner', '采购计划', 10), ('procurement_follower', '采购跟单', 20), ('product_developer', '产品开发', 30), ('operator', '运营专员', 40), ('bp', 'BP', 50), ('procurement_trainee', '采购管培生', 60), ('finance_specialist', '财务专员', 70), ('采购开发', '采购开发', 80), ] for position_code, position_name, sort_order in default_positions: query( "INSERT IGNORE INTO user_positions " "(position_code, position_name, sort_order, status) VALUES (%s, %s, %s, 1)", (position_code, position_name, sort_order) ) if table_exists('dashboard_users'): query( "INSERT IGNORE INTO user_positions (position_code, position_name, sort_order, status) " "SELECT DISTINCT TRIM(position), TRIM(position), 0, 1 " "FROM dashboard_users WHERE position IS NOT NULL AND TRIM(position) <> ''" ) print(" ✅ 初始化岗位字典") for table, renames in COLUMN_RENAMES.items(): if not table_exists(table): print(f" ⚠️ 表 {table} 不存在,跳过字段重命名") continue for old_column, new_column in renames: if column_exists(table, new_column): continue if not column_exists(table, old_column): print(f" ⚠️ {table}.{old_column} 不存在,无法重命名") continue try: rename_column(table, old_column, new_column) changed += 1 except Exception as e: print(f" ❌ {table}.{old_column} 重命名失败: {e}") # 同步已有账号权限和字段方案中的旧字段名。 if table_exists('user_permissions'): permission_renames = [ ('field:编码:view', 'field:货品编号:view'), ('field:编码:edit', 'field:货品编号:edit'), ('field:名称:view', 'field:货品名称:view'), ('field:名称:edit', 'field:货品名称:edit'), ] for old_operation, new_operation in permission_renames: query("""INSERT IGNORE INTO user_permissions (user_id, page, operation, permission) SELECT user_id, page, %s, permission FROM user_permissions WHERE page='orders_domestic' AND operation=%s""", (new_operation, old_operation)) query("""UPDATE user_permissions AS target JOIN user_permissions AS source ON source.user_id=target.user_id AND source.page=target.page SET target.permission=GREATEST(target.permission, source.permission) WHERE target.page='orders_domestic' AND target.operation=%s AND source.operation=%s""", (new_operation, old_operation)) query("DELETE FROM user_permissions WHERE page='orders_domestic' AND operation=%s", (old_operation,)) if table_exists('user_column_schemes'): query("""UPDATE user_column_schemes SET columns_json=REPLACE(REPLACE(REPLACE(REPLACE(columns_json, '"field":"编码"', '"field":"货品编号"'), '"field": "编码"', '"field": "货品编号"'), '"field":"名称"', '"field":"货品名称"'), '"field": "名称"', '"field": "货品名称"') WHERE page='orders_domestic'""") added = 0 for table, columns in SCHEMA.items(): if not table_exists(table): print(f" ⚠️ 表 {table} 不存在,跳过") continue for col_name, col_def in columns: if column_exists(table, col_name): continue try: add_column(table, col_name, col_def) added += 1 except Exception as e: print(f" ❌ {table}.{col_name} 添加失败: {e}") # 国内订单的既有字段也要统一默认值和历史派生值,避免仅新增数据库生效。 if table_exists('domestic_orders'): query("ALTER TABLE domestic_orders MODIFY COLUMN `金蝶入库` varchar(50) DEFAULT '否'") query("UPDATE domestic_orders SET `金蝶入库`='否' WHERE `金蝶入库` IS NULL OR `金蝶入库`=''") query("""UPDATE domestic_orders SET `未到货数`=COALESCE(`数量`,0)-COALESCE(`实际到货数量`,0) WHERE `未到货数` IS NULL OR `未到货数`<>COALESCE(`数量`,0)-COALESCE(`实际到货数量`,0)""") print(" ✅ 国内订单默认值及未到货数已同步") # pending_orders 需支持同一商品多次下单(多条 ordered/rolled_back 历史), # 移除 product_code 唯一索引并改为普通索引,否则回退订单会因唯一约束冲突失败。 if table_exists('pending_orders'): if index_exists('pending_orders', 'uk_product_code'): try: query("ALTER TABLE pending_orders DROP INDEX uk_product_code") print(" ✅ pending_orders 移除唯一索引 uk_product_code") changed += 1 except Exception as e: print(f" ❌ 移除唯一索引 uk_product_code 失败: {e}") if not index_exists('pending_orders', 'idx_product_code'): try: query("ALTER TABLE pending_orders ADD INDEX idx_product_code (product_code)") print(" ✅ pending_orders 新增普通索引 idx_product_code") except Exception as e: print(f" ❌ 新增普通索引 idx_product_code 失败: {e}") # 产品业务编码允许在名称不同(成品/包材分别建档)时重复。 if table_exists('products'): if index_exists('products', 'uk_product_code'): try: query("ALTER TABLE products DROP INDEX uk_product_code") print(" ✅ products 移除唯一索引 uk_product_code") changed += 1 except Exception as e: print(f" ❌ 移除 products 唯一索引 uk_product_code 失败: {e}") if not index_exists('products', 'uk_product_code_name'): try: query("ALTER TABLE products ADD UNIQUE INDEX uk_product_code_name (product_code, product_name)") print(" ✅ products 新增唯一索引 uk_product_code_name") changed += 1 except Exception as e: print(f" ❌ 新增 products 组合唯一索引失败: {e}") if not index_exists('products', 'idx_product_code'): try: query("ALTER TABLE products ADD INDEX idx_product_code (product_code)") print(" ✅ products 新增普通索引 idx_product_code") changed += 1 except Exception as e: print(f" ❌ 新增 products 普通索引失败: {e}") print("") if created or changed or added: print(f"🎉 共新建 {created} 表,重命名 {changed} 列,新增 {added} 列") else: print("✅ 所有列已存在,无需迁移") print("") return 0 if created == 0 and changed == 0 and added == 0 else 1 # 1 表示有变更 if __name__ == '__main__': sys.exit(main())