#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 从 mom系统数据库设计.xlsx 读取表与字段定义,更新 mom系统数据库设计-数据模型.md。 保留 md 的 1-3 节(概述、模块清单、公共字段)和 14 节及以后(枚举、ER、附录等), 用 Excel 内容更新 2.2 表清单总览 和 4-13 节各模块表结构。 """ import re from pathlib import Path try: import openpyxl except ImportError: raise SystemExit("请安装 openpyxl: pip install openpyxl") # 路径 BASE = Path(__file__).resolve().parent XLSX_PATH = BASE / "mom系统数据库设计.xlsx" MD_PATH = BASE / "mom系统数据库设计-数据模型.md" # Excel 表清单中的「模块」-> 文档中的 (节号, 模块标题, 表前缀) # 节号与原文一致: 4基础数据 5生产 6质量 7设备 8仓库 9排班 10IOT 11工具 12系统 13打印 MODULE_DOC = { "主数据管理": (4, "基础数据模块", "md_*"), "生产管理": (5, "生产管理模块", "pro_*"), "质量管理": (6, "质量管理模块", "qc_*"), "设备管理": (7, "设备管理模块", "dv_*"), "仓储管理": (8, "仓库管理模块", "wm_*"), "排班管理": (9, "排班管理模块", "cal_*"), "IoT数据采集": (10, "IoT数据采集模块", "iot_*"), "工装夹具管理": (11, "工具管理模块", "tm_*"), "系统管理": (12, "系统管理模块", "sys_*"), "打印机配置": (13, "打印管理模块", "print_*"), } # Excel 工作表名 -> 表清单中的模块名 SHEET_TO_MODULE = { "MD主数据": "主数据管理", "PRO生产管理": "生产管理", "CAL排班管理": "排班管理", "QC质量管理": "质量管理", "WM仓储管理": "仓储管理", "DV设备管理": "设备管理", "TM工装夹具管理": "工装夹具管理", "MO模具管理": None, "SYS系统管理": "系统管理", "PRINT打印管理": "打印机配置", "IOT数采": "IoT数据采集", } def load_table_list(wb): """从「表清单」sheet 加载 (表名, 模块, 表描述, 备注),并建 (模块, 表描述)->表名 映射""" ws = wb["表清单"] rows = list(ws.iter_rows(min_row=2, values_only=True)) table_list = [] desc_to_name = {} for row in rows: if not row or not row[1]: continue name = (row[1] or "").strip() module = (row[2] or "").strip() desc = (row[3] or "").strip() remark = (row[4] or "").strip() if len(row) > 4 else "" if not name: continue table_list.append((name, module, desc, remark)) if module and desc: key = (module, desc) if key not in desc_to_name: desc_to_name[key] = name return table_list, desc_to_name def parse_sheet_tables(ws, module_name, desc_to_name): """ 解析一个 sheet 中所有表块。 每个表块:一行 表名称 + 一行 中文名称 + 一行 列序号|列名|列中文名|列类型|列设置|备注 + 数据行。 返回 [(table_name, cn_name, remark, columns), ...] """ rows = list(ws.iter_rows(values_only=True)) result = [] i = 0 while i < len(rows): row = list(rows[i]) if rows[i] else [] # 找「表名称」在第二列 if len(row) >= 3 and row[1] == "表名称" and row[2]: excel_table_name = (row[2] or "").strip() cn_name = "" if i + 1 < len(rows): next_row = list(rows[i + 1]) if rows[i + 1] else [] if len(next_row) >= 3 and next_row[1] == "中文名称": cn_name = (next_row[2] or "").strip() # 表名解析:优先用 表清单 中 (模块, 中文名称) 对应的表名 if module_name and cn_name and (module_name, cn_name) in desc_to_name: table_name = desc_to_name[(module_name, cn_name)] else: table_name = excel_table_name if excel_table_name and len(excel_table_name) > 2 else (cn_name or excel_table_name) # 列头在 i+2 col_start = i + 2 if col_start >= len(rows): i += 1 continue header = list(rows[col_start]) if rows[col_start] else [] if len(header) < 5 or header[1] != "列序号": i += 1 continue columns = [] for j in range(col_start + 1, len(rows)): r = list(rows[j]) if rows[j] else [] if len(r) < 4: break col_no, col_name, col_cn, col_type, col_setting, col_remark = ( r[1], r[2] if len(r) > 2 else "", r[3] if len(r) > 3 else "", r[4] if len(r) > 4 else "", r[5] if len(r) > 5 else "", r[6] if len(r) > 6 else "" ) if col_name is None or not str(col_name).strip(): break col_name = str(col_name).strip() col_type = (col_type or "").strip() nullable = "N" if col_setting and "not null" in str(col_setting).lower() else "Y" desc = (col_cn or "").strip() if col_remark and str(col_remark).strip(): desc = desc + " " + str(col_remark).strip() columns.append({ "name": col_name.lower(), "type": col_type or "-", "nullable": nullable, "desc": desc or "-", }) result.append((table_name, cn_name, "", columns)) i = col_start + len(columns) + 1 continue i += 1 return result def collect_all_tables(wb, table_list, desc_to_name): """从各数据 sheet 收集表,按模块归类。返回 { 模块名: [(表名, 中文名, 备注, columns), ...] }""" by_module = {} for sheet_name in wb.sheetnames: if sheet_name == "表清单": continue module_name = SHEET_TO_MODULE.get(sheet_name) if not module_name or module_name not in MODULE_DOC: continue ws = wb[sheet_name] tables = parse_sheet_tables(ws, module_name, desc_to_name) for t in tables: by_module.setdefault(module_name, []).append(t) return by_module def table_name_to_lower(name): """将 Excel 表名转为小写(如 WM_WAREHOUSE -> wm_warehouse)""" if not name: return name return name.strip().lower() def gen_table_list_md(table_list): """生成 2.2 表清单总览 markdown""" module_order = list(MODULE_DOC.keys()) # 模块简写(用于表格第三列) module_short = { "主数据管理": "基础数据", "生产管理": "生产管理", "质量管理": "质量管理", "设备管理": "设备管理", "仓储管理": "仓库管理", "排班管理": "排班管理", "IoT数据采集": "IoT数据采集", "工装夹具管理": "工具管理", "系统管理": "系统管理", "打印机配置": "打印管理", } lines = [ "### 2.2 表清单总览", "", "| 序号 | 表名 | 中文名 | 模块 |", "|------|------|--------|------|", ] for idx, (name, module, desc, _) in enumerate(table_list, 1): short = module_short.get(module, module) cn_display = (desc or "").strip() or name # 表描述为空时用表名 lines.append(f"| {idx} | {table_name_to_lower(name)} | {cn_display} | {short} |") lines.append("") return "\n".join(lines) def gen_module_section(module_name, tables, section_num): """生成一个模块的 markdown(## 4. 基础数据模块 及下属 ### 4.x 表)""" if not tables: return "" title = MODULE_DOC[module_name][1] lines = [ f"## {section_num}. {title}", "", ] for idx, (table_name, cn_name, remark, columns) in enumerate(tables, 1): tname_lower = table_name_to_lower(table_name) lines.append(f"### {section_num}.{idx} {cn_name or table_name} ({tname_lower})") lines.append("") if remark: lines.append(f"> {remark}") lines.append("") lines.append("| 字段名 | 类型 | 允许空 | 说明 |") lines.append("|--------|------|--------|------|") for col in columns: lines.append(f"| {col['name']} | {col['type']} | {col['nullable']} | {col['desc']} |") lines.append("") lines.append("---") lines.append("") return "\n".join(lines) def main(): if not XLSX_PATH.exists(): raise SystemExit(f"Excel 文件不存在: {XLSX_PATH}") if not MD_PATH.exists(): raise SystemExit(f"Markdown 文件不存在: {MD_PATH}") wb = openpyxl.load_workbook(XLSX_PATH, read_only=True, data_only=True) table_list, desc_to_name = load_table_list(wb) by_module = collect_all_tables(wb, table_list, desc_to_name) wb.close() # 生成 2.2 表清单 table_list_md = gen_table_list_md(table_list) # 生成 4-12 节(按 MODULE_DOC 顺序) module_sections = [] for mod_name in MODULE_DOC: if mod_name not in by_module: continue num, _, _ = MODULE_DOC[mod_name] module_sections.append(gen_module_section(mod_name, by_module[mod_name], num)) new_content_4_13 = "\n".join(module_sections) # 读取现有 md md_text = MD_PATH.read_text(encoding="utf-8") # 替换 2.2 表清单总览:从 ### 2.2 表清单总览 到下一个 ## 之前 def replace_2_2(content): start = content.find("### 2.2 表清单总览") if start == -1: return content end = content.find("\n## ", start + 1) if end == -1: end = len(content) return content[:start] + table_list_md.rstrip() + "\n\n" + content[end:] md_text = replace_2_2(md_text) # 替换 ## 4. ... 到 ## 14. 之前 start_marker = "## 4. 基础数据模块" end_marker = "\n## 14. 枚举值定义" start = md_text.find(start_marker) end = md_text.find(end_marker) if start == -1: raise SystemExit("未找到「## 4. 基础数据模块」") if end == -1: end = len(md_text) md_text = md_text[:start] + new_content_4_13.rstrip() + "\n\n" + md_text[end:] MD_PATH.write_text(md_text, encoding="utf-8") print(f"已更新: {MD_PATH}") print(f" - 表清单: {len(table_list)} 张表") for mod_name, tbls in by_module.items(): print(f" - {mod_name}: {len(tbls)} 张表") if __name__ == "__main__": main()