Files
shanxia2.0/backend/scripts/generate_dict_reimport_sql.py
T
2026-06-01 08:21:21 +08:00

357 lines
10 KiB
Python

#!/usr/bin/env python3
"""Merge zkxw-dict.sql + sx-dict.sql into shanxia2.0 sys_dict_* INSERT script."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
ZKXW_SQL = ROOT / "docs/sql/zkxw-dict.sql"
SX_SQL = ROOT / "docs/sql/sx-dict.sql"
SHANXIA_SQL = ROOT / "docs/sql/shanxia2.0.sql"
OUT_SQL = ROOT / "docs/sql/reimport-dict-from-zkxw-sx.sql"
TENANT = "shanxia2.0"
# sx 源库部分 CRM 字典无时间戳;目标表 create_time/update_time 为 NOT NULL
DEFAULT_DT = "2026-05-30 00:00:00"
# shanxia2.0 门户内置字典(不在 zkxw-dict / sx-dict 中,从基线 SQL 保留)
PORTAL_BASELINE_TYPES = frozenset(
{
"sys_status",
"sys_sex",
"sys_data_scope",
"sys_visible",
"menu_type",
"leave_status",
"wf_process_status",
"wf_node_type",
"wf_approve_type",
"user_role",
}
)
DICT_TYPE_RE = re.compile(r"^[a-z][a-z0-9_]*$")
TAG_TYPE_MAP = {
"default": "",
"primary": "primary",
"success": "success",
"danger": "danger",
"warning": "warning",
"info": "info",
}
def split_sql_values(values_part: str) -> list:
parts: list[str] = []
cur: list[str] = []
in_str = False
i = 0
while i < len(values_part):
c = values_part[i]
if in_str:
if c == "\\" and i + 1 < len(values_part):
cur.append(c)
cur.append(values_part[i + 1])
i += 2
continue
if c == "'":
if i + 1 < len(values_part) and values_part[i + 1] == "'":
cur.append("''")
i += 2
continue
in_str = False
cur.append(c)
else:
cur.append(c)
else:
if c == "'":
in_str = True
cur.append(c)
elif c == ",":
parts.append("".join(cur).strip())
cur = []
else:
cur.append(c)
i += 1
if cur:
parts.append("".join(cur).strip())
return parts
def unquote(val: str):
val = val.strip()
if val.upper() == "NULL":
return None
if val.startswith("'") and val.endswith("'"):
return val[1:-1].replace("''", "'")
if re.fullmatch(r"-?\d+", val):
return int(val)
return val
def parse_insert(line: str) -> tuple[str, list[str], list] | None:
m = re.match(
r"INSERT INTO `(\w+)` \(([^)]+)\) VALUES \((.+)\);\s*$",
line.strip(),
)
if not m:
return None
table = m.group(1)
cols = [c.strip().strip("`") for c in m.group(2).split(",")]
vals = [unquote(v) for v in split_sql_values(m.group(3))]
return table, cols, vals
def sql_literal(val) -> str:
if val is None:
return "NULL"
if isinstance(val, int):
return str(val)
s = str(val).replace("\\", "\\\\").replace("'", "''")
return f"'{s}'"
def is_valid_dict_type(code: str | None) -> bool:
return bool(code) and bool(DICT_TYPE_RE.match(code))
def map_tag_type(list_class: str | None) -> str:
if not list_class:
return ""
key = str(list_class).strip().lower()
return TAG_TYPE_MAP.get(key, key if key in TAG_TYPE_MAP.values() else "")
def sx_type_id(dict_id: int) -> str:
return f"SXDT{dict_id:016X}"
def sx_data_id(dict_code: int) -> str:
return f"SXDC{dict_code:016X}"
def normalize_row(row: dict) -> dict:
out = dict(row)
ct = out.get("create_time")
ut = out.get("update_time")
if not ct:
out["create_time"] = ut or DEFAULT_DT
if not out.get("update_time"):
out["update_time"] = out["create_time"] or DEFAULT_DT
return out
def row_to_insert(table: str, cols: list[str], row: dict) -> str:
row = normalize_row(row)
ordered = [row[c] for c in cols]
vals = ", ".join(sql_literal(v) for v in ordered)
col_sql = ", ".join(f"`{c}`" for c in cols)
return f"INSERT INTO `{table}` ({col_sql}) VALUES ({vals});"
def load_portal_baseline():
types: dict[str, dict] = {}
data: list[dict] = []
for line in SHANXIA_SQL.read_text(encoding="utf-8").splitlines():
parsed = parse_insert(line)
if not parsed:
continue
table, cols, vals = parsed
row = dict(zip(cols, vals))
if table == "sys_dict_type":
code = row.get("dict_type")
if code not in PORTAL_BASELINE_TYPES or row.get("del_flag", 0) != 0:
continue
types[code] = row
elif table == "sys_dict_data":
code = row.get("dict_type")
if code not in PORTAL_BASELINE_TYPES or row.get("del_flag", 0) != 0:
continue
row.setdefault("tag_type", "")
data.append(row)
return types, data
def load_zkxw():
types: dict[str, dict] = {}
data: list[dict] = []
for line in ZKXW_SQL.read_text(encoding="utf-8").splitlines():
parsed = parse_insert(line)
if not parsed:
continue
table, cols, vals = parsed
row = dict(zip(cols, vals))
if row.get("del_flag", 0) != 0:
continue
row["tenant_no"] = TENANT
if table == "sys_dict_type":
code = row.get("dict_type")
if is_valid_dict_type(code):
types[code] = row
elif table == "sys_dict_data":
code = row.get("dict_type")
if not is_valid_dict_type(code):
continue
row["tenant_no"] = TENANT
row.setdefault("tag_type", "")
data.append(row)
return types, data
def load_sx():
types: dict[str, dict] = {}
data: list[dict] = []
for line in SX_SQL.read_text(encoding="utf-8").splitlines():
parsed = parse_insert(line)
if not parsed:
continue
table, cols, vals = parsed
row = dict(zip(cols, vals))
if table == "sys_dict_type":
dict_id = row.get("dict_id")
code = row.get("dict_type")
name = row.get("dict_name")
if not is_valid_dict_type(code):
continue
if str(name) in ("000000", TENANT, "zkxw", "0"):
continue
types[code] = {
"id": sx_type_id(int(dict_id)),
"dict_name": name,
"dict_type": code,
"tenant_no": TENANT,
"order_num": 0,
"remark": row.get("remark") or "",
"create_by": None,
"create_dept_id": "",
"create_time": row.get("create_time"),
"update_by": None,
"update_time": row.get("update_time") or row.get("create_time"),
"del_flag": 0,
}
elif table == "sys_dict_data":
dict_code = row.get("dict_code")
code = row.get("dict_type")
label = row.get("dict_label")
value = row.get("dict_value")
if dict_code is None or not is_valid_dict_type(code):
continue
if not label or not str(label).strip():
continue
if str(label) in ("planned", "zsjy_visit_status") and str(value) in (
"zsjy_visit_status",
"planned",
):
continue
create_by = row.get("create_by")
data.append(
{
"id": sx_data_id(int(dict_code)),
"dict_label": label,
"dict_value": value,
"tag_type": map_tag_type(row.get("list_class")),
"dict_type": code,
"tenant_no": TENANT,
"order_num": row.get("dict_sort") or 0,
"remark": row.get("remark") or "",
"create_by": str(create_by) if create_by is not None else None,
"create_dept_id": "",
"create_time": row.get("create_time"),
"update_by": None,
"update_time": row.get("update_time") or row.get("create_time"),
"del_flag": 0,
}
)
return types, data
def main():
portal_types, portal_data = load_portal_baseline()
zkxw_types, zkxw_data = load_zkxw()
sx_types, sx_data = load_sx()
merged_types = dict(portal_types)
merged_types.update(zkxw_types)
merged_types.update(sx_types)
type_cols = [
"id",
"dict_name",
"dict_type",
"tenant_no",
"order_num",
"remark",
"create_by",
"create_dept_id",
"create_time",
"update_by",
"update_time",
"del_flag",
]
data_cols = [
"id",
"dict_label",
"dict_value",
"tag_type",
"dict_type",
"tenant_no",
"order_num",
"remark",
"create_by",
"create_dept_id",
"create_time",
"update_by",
"update_time",
"del_flag",
]
# portal baseline -> zkxw -> sx
seen_data_ids: set[str] = set()
merged_data: list[dict] = []
for row in portal_data + zkxw_data + sx_data:
rid = row["id"]
if rid in seen_data_ids:
continue
seen_data_ids.add(rid)
if row["dict_type"] not in merged_types:
continue
merged_data.append(row)
lines = [
"-- Reimport dictionary data for shanxia2.0",
"-- Sources: shanxia2.0 portal baseline + zkxw-dict.sql + sx-dict.sql",
"-- Generated by: backend/scripts/generate_dict_reimport_sql.py",
"",
"SET NAMES utf8mb4;",
"SET FOREIGN_KEY_CHECKS = 0;",
"",
"DELETE FROM `sys_dict_data`;",
"DELETE FROM `sys_dict_type`;",
"",
"BEGIN;",
"",
"-- sys_dict_type",
]
for code in sorted(merged_types.keys()):
lines.append(row_to_insert("sys_dict_type", type_cols, merged_types[code]))
lines.append("")
lines.append("-- sys_dict_data")
for row in merged_data:
lines.append(row_to_insert("sys_dict_data", data_cols, row))
lines.extend(["", "COMMIT;", "", "SET FOREIGN_KEY_CHECKS = 1;", ""])
OUT_SQL.write_text("\n".join(lines), encoding="utf-8")
print(
f"Wrote {OUT_SQL.relative_to(ROOT)}: "
f"{len(merged_types)} types, {len(merged_data)} data rows "
f"(portal={len(portal_types)}, zkxw={len(zkxw_types)}, sx={len(sx_types)})"
)
if __name__ == "__main__":
main()