dy-dashboard/migrate_db.py
2026-10-28 11:14:17 +08:00

545 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""数据库结构自动迁移:检查并补全缺失的列/表
用法:
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())