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