276 lines
10 KiB
Python
276 lines
10 KiB
Python
|
|
#!/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()
|