| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- #!/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()
|