Aq/aq-flex-api/scripts/migrate_basics_data.py

237 lines
7.7 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.

import pymysql
import json
from collections import defaultdict
from tqdm import tqdm
# 配置信息
SOURCE_DB = {
'host': 'localhost',
'user': 'root',
'password': 'root',
'database': '<db>',
'charset': 'utf8mb4'
}
TARGET_DB = {
'host': 'localhost',
'user': 'root',
'password': 'root',
'database': '<db>',
'charset': 'utf8mb4'
}
SOURCE_TENANT = '<tenantUuid>'
TARGET_TENANT = '<tenantUuid>'
# 表迁移顺序(按依赖关系排序)
TABLE_ORDER = [
'basics_factory',
'basics_equip_type',
'basics_factory_workshop',
'basics_factory_line',
'basics_factory_workshop_section',
'basics_factory_station',
'basics_wms_warehouse',
'basics_wms_area',
'basics_wms_location',
'basics_wms_shelf',
'basics_wms_position',
'basics_manpowersys_post',
'basics_manpowersys_staff',
'basics_hr_classes',
'basics_hr_workgroup',
'basics_hr_workgroup_staff',
'basics_material_group',
'basics_craft_process',
'basics_craft_route',
'basics_craft_material',
'basics_craft_bom',
'basics_craft_drawing_page',
'basics_equip_spare_part',
'basics_equip_component',
'basics_equip_ledger',
'basics_equip_ledger_spare',
'basics_quality_method',
'basics_quality_tool',
'basics_quality_sample',
'basics_quality_sample_details',
'basics_quality_category',
'basics_quality_plan',
'basics_quality_plan_item',
'basics_production_craftl_materiel',
'basics_production_craftl_route',
'basics_wms_bar_code',
'basics_wms_kn_rqgl',
'basics_file',
'conf_craft_route_material',
'conf_craft_route_process',
'conf_line_production',
'conf_line_station',
'conf_station'
]
# 外键关系映射
FOREIGN_KEYS = {
'basics_factory_workshop': {'factory_id': 'basics_factory'},
'basics_factory_line': {
'factory_id': 'basics_factory',
'workshop_id': 'basics_factory_workshop',
'section_id': 'basics_factory_workshop_section'
},
'basics_factory_workshop_section': {
'factory_id': 'basics_factory',
'workshop_id': 'basics_factory_workshop',
'line_id': 'basics_factory_line'
},
'basics_factory_station': {'factory_id': 'basics_factory'},
'basics_equip_ledger': {
'factory_id': 'basics_factory',
'workshop_id': 'basics_factory_workshop',
'line_id': 'basics_factory_line',
'workshop_section_id': 'basics_factory_workshop_section',
'equip_type_id': 'basics_equip_type'
},
'basics_equip_ledger_spare': {
'equip_ledger_id': 'basics_equip_ledger',
'spare_part_id': 'basics_equip_spare_part'
},
'basics_wms_warehouse': {
'factory_id': 'basics_factory'
},
'basics_wms_area': {
'factory_id': 'basics_factory',
'warehouse_id': 'basics_wms_warehouse'
},
'basics_wms_location': {
'factory_id': 'basics_factory',
'warehouse_id': 'basics_wms_warehouse',
'area_id': 'basics_wms_area'
},
'basics_wms_shelf': {
'factory_id': 'basics_factory',
'warehouse_id': 'basics_wms_warehouse',
'area_id': 'basics_wms_area',
'location_id': 'basics_wms_location'
},
'basics_wms_position': {
'factory_id': 'basics_factory',
'warehouse_id': 'basics_wms_warehouse',
'area_id': 'basics_wms_area',
'location_id': 'basics_wms_location',
'shelf_id': 'basics_wms_shelf'
},
'basics_hr_classes': {'line_id': 'basics_factory_line'},
'basics_hr_workgroup': {'line_id': 'basics_factory_line'},
'basics_hr_workgroup_staff': {
'workgroup_id': 'basics_hr_workgroup',
'staff_id': 'basics_manpowersys_staff'
},
'basics_material_group': {'factory_id': 'basics_factory'},
'basics_craft_drawing_page': {'craft_id': 'basics_craft_route'},
'basics_craft_bom': {'parent_id': 'basics_craft_bom'},
'basics_quality_category': {'parent_id': 'basics_quality_category'},
'basics_quality_plan_item': {'plan_code': 'basics_quality_plan'},
'basics_wms_bar_code': {'type_id': None} # 动态处理
}
def migrate_data():
# 连接数据库
source_conn = pymysql.connect(**SOURCE_DB)
target_conn = pymysql.connect(**TARGET_DB)
source_cursor = source_conn.cursor(pymysql.cursors.DictCursor)
target_cursor = target_conn.cursor(pymysql.cursors.DictCursor)
# ID映射字典 {table: {old_id: new_id}}
id_map = defaultdict(dict)
# 自关联表待处理队列 {table: [(new_id, old_parent_id)]}
self_ref_queue = defaultdict(list)
try:
for table in tqdm(TABLE_ORDER, desc="迁移表进度"):
# 查询源数据
source_cursor.execute(
f"SELECT * FROM {table} WHERE tenant_uuid = %s AND deleted = 0",
(SOURCE_TENANT,)
)
rows = source_cursor.fetchall()
if not rows:
continue
# 获取目标表所有字段(排除自增ID)
target_cursor.execute(f"DESCRIBE {table}")
columns = [col['Field'] for col in target_cursor.fetchall()
if col['Field'] != 'id']
# 处理每行数据
for row in tqdm(rows, desc=f"迁移 {table}"):
old_id = row['id']
del row['id']
# 更新租户UUID
row['tenant_uuid'] = TARGET_TENANT
# 处理外键映射
if table in FOREIGN_KEYS:
for fk_col, ref_table in FOREIGN_KEYS[table].items():
if fk_col in row and row[fk_col]:
# 动态类型表处理
if ref_table is None:
ref_table = row.get('type', 'unknown')
if ref_table in id_map:
row[fk_col] = id_map[ref_table].get(row[fk_col], row[fk_col])
# 处理JSON字段
if 'extra' in row and isinstance(row['extra'], str):
try:
row['extra'] = json.dumps(json.loads(row['extra']))
except:
pass
# 准备插入数据
values = [row.get(col) for col in columns]
placeholders = ', '.join(['%s'] * len(values))
# 执行插入
target_cursor.execute(
f"INSERT INTO {table} ({', '.join(columns)}) "
f"VALUES ({placeholders})",
values
)
new_id = target_cursor.lastrowid
# 保存ID映射
id_map[table][old_id] = new_id
# 处理自关联字段
if table in ['basics_craft_bom', 'basics_quality_category']:
parent_id = row.get('parent_id')
if parent_id:
self_ref_queue[table].append((new_id, parent_id))
# 处理自关联更新
if table in self_ref_queue:
for new_id, old_parent_id in self_ref_queue[table]:
new_parent_id = id_map[table].get(old_parent_id)
if new_parent_id:
target_cursor.execute(
f"UPDATE {table} SET parent_id = %s WHERE id = %s",
(new_parent_id, new_id)
)
# 所有表都成功后才提交
target_conn.commit()
print("数据迁移完成!")
except Exception as e:
print(f"迁移失败: {str(e)}")
target_conn.rollback()
finally:
source_cursor.close()
target_cursor.close()
source_conn.close()
target_conn.close()
if __name__ == "__main__":
migrate_data()