357 lines
10 KiB
Python
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()
|