| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- """按最新 DMS 范围重新比对 15 类模板字段,生成补充数据清单。
- 最新 DMS 范围:
- 1. 申勤物业数字化/申勤物业项目清单、申勤员工
- 2. AMS枢元/申勤数字化改造(全部模型)
- 输出:
- - data/manual_fill/DMS补数字段清单.csv
- - output/dms_scoped_template_mapping.csv
- - docs/DMS字段映射_最新范围.md
- """
- from __future__ import annotations
- import csv
- import json
- import re
- import sys
- from pathlib import Path
- from openpyxl import Workbook, load_workbook
- from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
- from openpyxl.utils import get_column_letter
- sys.stdout.reconfigure(encoding="utf-8")
- ROOT = Path(r"E:\CODE\knowledge_agent")
- DMS_JSON = ROOT / "output" / "dms_scoped_models_fields.json"
- OUT_XLSX = ROOT / "data" / "manual_fill" / "DMS补数字段清单.csv"
- OUT_CSV = ROOT / "output" / "dms_scoped_template_mapping.csv"
- OUT_DOC = ROOT / "docs" / "DMS字段映射_最新范围.md"
- TEMPLATES: dict[str, list[str]] = {
- "项目信息": ["项目编号", "项目名称", "项目简称", "上级项目编号", "续签前项目编号", "项目起止时间",
- "省份", "市(区)", "细分业态", "汇总业态", "委托方式", "计费方式", "合同面积",
- "合同金额", "年化合同额", "合同额具体信息", "服务期限", "合同状况", "首次服务日期",
- "年化合同收入", "客户满意度", "客户投诉", "甲方名称", "项目地址", "项目负责人",
- "项目负责人工号", "实际毛利率", "服务状态", "备注"],
- "项目月度财务": ["项目编号", "项目名称", "年月", "开票金额", "收款金额", "口径说明",
- "财务经办人工号", "财务经办人", "备注"],
- "月度快照": ["项目编号", "项目名称", "年月", "在岗人数", "排班人次", "考勤人次",
- "开票金额", "收款金额", "备注"],
- "科目余额": ["项目编号", "项目名称", "期间", "科目编码", "科目名称",
- "借方发生额", "贷方发生额", "期末余额", "备注"],
- "投标记录": ["项目编号", "项目名称", "年份", "投标类型", "中标结果", "中标日期", "投标金额",
- "文档路径", "投标经办人工号", "投标经办人", "备注"],
- "人员信息": ["姓名", "工号", "组织名称", "岗位名称", "当前服务项目编号", "服务起始年月",
- "服务结束年月", "职级", "员工层级", "所属组织名称", "入职日期", "离职日期", "司龄",
- "性别", "出生日期", "政治面貌", "联系电话", "备注"],
- "考勤与人员财务": ["姓名", "工号", "项目编号", "项目名称", "年月", "1-31日", "上班天数",
- "平时加班小时", "国定加班小时", "餐费补助金额", "加班超时费金额",
- "国定加班费金额", "值班费金额", "税后工资", "备注"],
- "排班月报": ["姓名", "工号", "项目编号", "项目名称", "年月", "1-31日班次", "备注"],
- "岗位编制": ["项目编号", "项目名称", "岗位", "预算编制", "项目编制", "标准工时人数", "在岗人数", "备注"],
- "设备信息": ["项目编号", "项目名称", "设备编号", "设备类型", "名称", "规格型号参数", "制造厂商",
- "安装位置", "出厂日期", "启用日期", "入项目日期", "退项目日期", "出厂编号", "完好状况",
- "原值", "设备责任人工号", "设备责任人", "备注"],
- "采购与维保": ["记录类型", "项目编号", "项目名称", "供应商名称", "供应商编号", "类别",
- "名称/合同名称", "数量", "单位", "单价", "金额", "合同年限", "月份/日期",
- "当前状态", "经办人工号", "经办人", "备注"],
- "检查记录": ["项目编号", "项目名称", "检查类型", "检查日期", "检查得分", "问题描述",
- "整改情况", "整改完成日期", "检查人工号", "检查人", "备注"],
- "片区信息": ["片区编号", "片区名称", "片区负责人工号", "片区负责人", "备注"],
- "项目片区关系": ["项目编号", "项目名称", "片区编号", "片区名称", "备注"],
- "人员证书": ["工号", "姓名", "专业类别", "证书类别", "证书名称", "备注"],
- }
- # 仅使用最新范围内的 DMS 模型。key = (模板, 模板字段)
- DMS_SOURCES: dict[tuple[str, str], str] = {
- ("项目信息", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("项目信息", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("项目信息", "项目简称"): "shenqin_project_annual_stat.c_project_short_name(项目简称)",
- ("项目信息", "项目起止时间"): "shenqin_project_annual_stat.c_contract_period(合同起止日期) / sq_project_period.c_c_date_range_text(日期区间)",
- ("项目信息", "省份"): "ams_sq_project.c_sfzxs(省份/直辖市)",
- ("项目信息", "市(区)"): "ams_sq_project.c_sq(市/区)",
- ("项目信息", "细分业态"): "ams_sq_project.c_xfyt(细分业态)",
- ("项目信息", "汇总业态"): "ams_sq_project.c_hzyt(汇总业态)",
- ("项目信息", "委托方式"): "ams_sq_project.c_wtfs(委托方式)",
- ("项目信息", "计费方式"): "ams_sq_project.c_jffs(计费方式)",
- ("项目信息", "合同面积"): "shenqin_project_annual_stat.c_building_area_sqm(建筑面积) / ams_sq_project.c_htmj(合同面积)",
- ("项目信息", "合同金额"): "shenqin_project_annual_stat.c_contract_amount(合同金额)",
- ("项目信息", "年化合同额"): "shenqin_project_annual_stat.c_annualized_amount(年化合同额)",
- ("项目信息", "合同额具体信息"): "shenqin_project_annual_stat.c_current_period_amount / c_stock_amount / c_new_amount / c_estimated_amount",
- ("项目信息", "服务期限"): "shenqin_project_annual_stat.c_service_period(服务期限)",
- ("项目信息", "合同状况"): "shenqin_project_annual_stat.c_contract_status_online / c_contract_status_offline",
- ("项目信息", "首次服务日期"): "ams_sq_project.c_scfwrq(首次服务日期)",
- ("项目信息", "年化合同收入"): "ams_sq_project.c_nhhtsr(年化合同收入)",
- ("项目信息", "甲方名称"): "shenqin_project_annual_stat.c_client_name(甲方名称)",
- ("项目信息", "项目地址"): "shenqin_project_annual_stat.c_project_address(项目地址) / ams_sq_project.c_xmdz(项目地址)",
- ("项目信息", "项目负责人"): "ams_sq_project.c_xmdzfzr(项目点状负责人)",
- ("项目信息", "实际毛利率"): "ams_sq_project.c_xmdnsjmll(项目当年实际毛利率)",
- ("项目月度财务", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("项目月度财务", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("月度快照", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("月度快照", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("科目余额", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("科目余额", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("投标记录", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("投标记录", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("投标记录", "中标结果"): "ams_sq_project.c_zbjgrzbdrhxsj(中标结果)",
- ("投标记录", "中标日期"): "ams_sq_project.c_xmzbrq(项目中标日期)",
- ("投标记录", "文档路径"): "sq_project_file.c_c_abs_path(绝对路径) / sq_bid_archive_scan.c_c_abs_path(标书扫描绝对路径)",
- ("人员信息", "姓名"): "sq_employee.c_xm(姓名)",
- ("人员信息", "工号"): "sq_employee.c_ygbm(员工编码)",
- ("人员信息", "组织名称"): "sq_employee.c_sszzmc(所属组织名称)",
- ("人员信息", "岗位名称"): "sq_employee.c_gwmc(岗位名称)",
- ("人员信息", "服务起始年月"): "sq_employee.c_htksrq(合同开始日期,口径需确认)",
- ("人员信息", "服务结束年月"): "sq_employee.c_htjsrq(合同结束日期,口径需确认)",
- ("人员信息", "职级"): "sq_employee.c_zj(职级)",
- ("人员信息", "员工层级"): "sq_employee.c_ygcj(员工层级)",
- ("人员信息", "所属组织名称"): "sq_employee.c_sszzmc(所属组织名称)",
- ("人员信息", "入职日期"): "sq_employee.c_rzrq(入职日期)",
- ("人员信息", "司龄"): "sq_employee.c_sl(司龄)",
- ("人员信息", "性别"): "sq_employee.c_xb(性别)",
- ("人员信息", "出生日期"): "sq_employee.c_csrq(出生日期)",
- ("人员信息", "政治面貌"): "sq_employee.c_zzmm(政治面貌)",
- ("人员信息", "联系电话"): "sq_employee.c_sjhm(手机号码)",
- ("人员信息", "备注"): "sq_employee.c_bz(备注)",
- ("考勤与人员财务", "姓名"): "sq_employee.c_xm(姓名)",
- ("考勤与人员财务", "工号"): "sq_employee.c_ygbm(员工编码)",
- ("考勤与人员财务", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("考勤与人员财务", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / sq_employee.c_xmmc(项目名称)",
- ("排班月报", "姓名"): "sq_employee.c_xm(姓名)",
- ("排班月报", "工号"): "sq_employee.c_ygbm(员工编码)",
- ("排班月报", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("排班月报", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / sq_employee.c_xmmc(项目名称)",
- ("岗位编制", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("岗位编制", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("岗位编制", "岗位"): "sq_position_salary.c_c_position_name(岗位名称) / sq_employee.c_gwmc(岗位名称)",
- ("岗位编制", "在岗人数"): "ams_sq_project.c_xmygrs(项目员工人数,需按岗位拆分)",
- ("设备信息", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("设备信息", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("采购与维保", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("采购与维保", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("检查记录", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("检查记录", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("片区信息", "片区名称"): "ams_sq_project.c_glqynbkj(管理区域内部口径) / sq_shenqin_project.c_c_region(区域)",
- ("片区信息", "片区负责人"): "ams_sq_project.c_pjfzr(片区负责人)",
- ("项目片区关系", "项目编号"): "shenqin_project_annual_stat.c_kingdee_code(金蝶编码) / sq_shenqin_project.c_c_project_code(项目编号)",
- ("项目片区关系", "项目名称"): "shenqin_project_annual_stat.c_project_name(项目名称) / ams_sq_project.c_xmmc(项目名称)",
- ("项目片区关系", "片区名称"): "ams_sq_project.c_glqynbkj(管理区域内部口径) / sq_shenqin_project.c_c_region(区域)",
- ("人员证书", "工号"): "sq_personnel_certificate.c_c_person_id(人员ID,需与 sq_employee.c_ygbm 核对映射)",
- ("人员证书", "姓名"): "sq_personnel_material.c_c_name(姓名) / sq_employee.c_xm(姓名)",
- ("人员证书", "证书类别"): "sq_personnel_certificate.c_c_cert_type(证书类型)",
- }
- # 与 DMS/其他补充表对齐的主连接键。即使 DMS 已有,也必须在补充 Excel 中保留。
- JOIN_KEYS: dict[str, set[str]] = {
- "项目信息": {"项目编号"},
- "项目月度财务": {"项目编号", "年月"},
- "月度快照": {"项目编号", "年月"},
- "科目余额": {"项目编号", "期间", "科目编码"},
- "投标记录": {"项目编号", "年份"},
- "人员信息": {"工号"},
- "考勤与人员财务": {"工号", "项目编号", "年月"},
- "排班月报": {"工号", "项目编号", "年月"},
- "岗位编制": {"项目编号", "岗位"},
- "设备信息": {"项目编号", "设备编号"},
- "采购与维保": {"项目编号", "名称/合同名称", "月份/日期"},
- "检查记录": {"项目编号", "检查日期"},
- "片区信息": {"片区编号"},
- "项目片区关系": {"项目编号", "片区编号"},
- "人员证书": {"工号", "证书名称"},
- }
- # 非 DMS 字段中,可从已有源 Excel 搬运的字段
- SOURCE_EXCEL_FIELDS: set[tuple[str, str]] = {
- ("项目信息", "上级项目编号"),
- ("项目信息", "续签前项目编号"),
- ("项目信息", "客户满意度"),
- ("项目信息", "客户投诉"),
- ("项目月度财务", "年月"),
- ("项目月度财务", "开票金额"),
- ("项目月度财务", "收款金额"),
- ("月度快照", "年月"),
- ("月度快照", "在岗人数"),
- ("月度快照", "排班人次"),
- ("月度快照", "考勤人次"),
- ("月度快照", "开票金额"),
- ("月度快照", "收款金额"),
- ("科目余额", "期间"),
- ("科目余额", "科目编码"),
- ("科目余额", "科目名称"),
- ("科目余额", "借方发生额"),
- ("科目余额", "贷方发生额"),
- ("科目余额", "期末余额"),
- ("投标记录", "年份"),
- ("投标记录", "投标金额"),
- ("人员信息", "离职日期"),
- ("考勤与人员财务", "年月"),
- ("考勤与人员财务", "1-31日"),
- ("考勤与人员财务", "上班天数"),
- ("考勤与人员财务", "平时加班小时"),
- ("考勤与人员财务", "国定加班小时"),
- ("考勤与人员财务", "餐费补助金额"),
- ("考勤与人员财务", "加班超时费金额"),
- ("考勤与人员财务", "国定加班费金额"),
- ("考勤与人员财务", "值班费金额"),
- ("考勤与人员财务", "税后工资"),
- ("考勤与人员财务", "备注"),
- ("排班月报", "年月"),
- ("排班月报", "1-31日班次"),
- ("岗位编制", "预算编制"),
- ("岗位编制", "项目编制"),
- ("岗位编制", "标准工时人数"),
- ("设备信息", "设备编号"),
- ("设备信息", "设备类型"),
- ("设备信息", "名称"),
- ("设备信息", "规格型号参数"),
- ("设备信息", "制造厂商"),
- ("设备信息", "安装位置"),
- ("设备信息", "出厂日期"),
- ("设备信息", "启用日期"),
- ("设备信息", "出厂编号"),
- ("设备信息", "完好状况"),
- ("设备信息", "原值"),
- ("采购与维保", "供应商名称"),
- ("采购与维保", "类别"),
- ("采购与维保", "名称/合同名称"),
- ("采购与维保", "数量"),
- ("采购与维保", "单位"),
- ("采购与维保", "单价"),
- ("采购与维保", "金额"),
- ("采购与维保", "合同年限"),
- ("采购与维保", "月份/日期"),
- ("采购与维保", "当前状态"),
- ("检查记录", "检查类型"),
- ("检查记录", "检查日期"),
- ("检查记录", "检查得分"),
- ("检查记录", "问题描述"),
- ("检查记录", "整改情况"),
- ("检查记录", "整改完成日期"),
- ("人员证书", "专业类别"),
- ("人员证书", "证书名称"),
- }
- DEFAULT_SOURCES: dict[str, str] = {
- "项目信息": "市场部项目管理表 / 业务大表",
- "项目月度财务": "财务部收费进程表(1-12月物业费/期内开票收款)",
- "月度快照": "由考勤/排班/项目月度财务汇总生成(可参考 scripts/sample_snapshot.py)",
- "科目余额": "财务部科目余额表(科目编码/名称/借贷发生额/期末余额)",
- "投标记录": "业务大表中标情况 / 档案目录",
- "人员信息": "人事部员工花名册",
- "考勤与人员财务": "人事部考勤表(13份考勤 xls/xlsx)",
- "排班月报": "人事部排班文件(每日班次代码)",
- "岗位编制": "运营部 9-项目岗位编制汇总.xlsx",
- "设备信息": "运营部设备台账(青浦工业园区/徐汇环境监测中心)",
- "采购与维保": "采购部耗材汇总/维保合同台账/固定资产台账",
- "检查记录": "运营部品质巡检及整改记录台账",
- "片区信息": "业务大表「管理区域」/ 8-项目信息汇总表",
- "项目片区关系": "业务大表「管理区域」推导",
- "人员证书": "合景悠活职称及资格证书管理登记表",
- }
- SPECIAL_NOTES: dict[tuple[str, str], str] = {
- ("项目信息", "项目负责人工号"): "DMS 有项目负责人姓名;工号需按姓名在员工花名册中匹配",
- ("项目信息", "上级项目编号"): "可由业务大表层级/委托方式推导后回填",
- ("项目信息", "续签前项目编号"): "可由续签链/档案目录推导后回填",
- ("人员信息", "当前服务项目编号"): "DMS sq_employee 只有项目名称/GUID,编号需按项目名称映射",
- ("人员信息", "离职日期"): "DMS 只有合同结束日期,不是离职日期,需人工补",
- ("月度快照", "在岗人数"): "优先按考勤明细不同工号数统计,并与岗位编制在岗人数核对",
- ("月度快照", "排班人次"): "排班月报按项目+年月汇总行数",
- ("月度快照", "考勤人次"): "考勤明细按项目+年月汇总行数",
- ("科目余额", "科目编码"): "财务科目编码,同一项目同一期间内唯一",
- ("科目余额", "科目名称"): "与科目编码配套的中文科目名称",
- ("考勤与人员财务", "税后工资"): "源考勤表无该列名,部分文件金额填在表头为「员工签字」的列",
- ("考勤与人员财务", "1-31日"): "值为 常/日/夜/日1 等班次标记,原样搬运",
- ("排班月报", "1-31日班次"): "值为班次代码,如 OFF/A003/SQ-104/M11-033;OFF=休息",
- ("设备信息", "设备责任人工号"): "模板必填;源台账可能有责任人姓名,工号需匹配花名册",
- ("采购与维保", "供应商编号"): "用于关联供应商主数据;DMS 范围内无供应商模型,需新编或另建",
- ("片区信息", "片区编号"): "最新 DMS 范围内没有 xzbm 片区模型,需人工统一编码",
- ("项目片区关系", "片区编号"): "同上,需与片区信息表保持一致",
- ("人员证书", "证书名称"): "DMS 证书表只有证书类型/证书号掩码,证书全称需补充",
- }
- def norm_header(h: str, tpl: str) -> str:
- h = str(h or "").strip()
- h = re.sub(r"\*$", "", h)
- h = re.sub(r"([^)]*)$", "", h)
- if re.fullmatch(r"\d{1,2}日", h):
- return "1-31日班次" if tpl == "排班月报" else "1-31日"
- return h
- def read_required() -> dict[str, set[str]]:
- required: dict[str, set[str]] = {}
- for f in sorted((ROOT / "data" / "templates").glob("*.csv")):
- tpl = f.stem
- req: set[str] = set()
- with f.open("r", encoding="utf-8-sig", newline="") as fh:
- reader = csv.reader(fh)
- header = next(reader, [])
- for h in header:
- if h and "*" in str(h):
- req.add(norm_header(str(h), tpl))
- required[tpl] = req
- return required
- def build_rows() -> list[dict]:
- required = read_required()
- rows: list[dict] = []
- for tpl, fields in TEMPLATES.items():
- req = required.get(tpl, set())
- for i, fld in enumerate(fields, start=1):
- has_dms = (tpl, fld) in DMS_SOURCES
- is_join = fld in JOIN_KEYS.get(tpl, set())
- include = (not has_dms) or is_join
- if has_dms and not is_join:
- kind = "DMS已有"
- elif has_dms and is_join:
- kind = "连接字段(DMS已有,Excel需带)"
- elif is_join:
- kind = "连接字段(需补充)"
- elif (tpl, fld) in SOURCE_EXCEL_FIELDS:
- kind = "业务字段(源Excel补充)"
- else:
- kind = "业务字段(新增/人工确认)"
- note = SPECIAL_NOTES.get((tpl, fld), "")
- if not note and not has_dms and (tpl, fld) in SOURCE_EXCEL_FIELDS:
- note = DEFAULT_SOURCES.get(tpl, "")
- elif not note and not has_dms:
- note = "DMS 范围内无对应字段,需人工确认"
- elif not note and has_dms:
- note = DMS_SOURCES[(tpl, fld)]
- rows.append({
- "template": tpl,
- "seq": i,
- "field": fld,
- "has_dms": has_dms,
- "dms_source": DMS_SOURCES.get((tpl, fld), ""),
- "kind": kind,
- "is_join": is_join,
- "include": include,
- "required": "是" if fld in req else "否",
- "note": note,
- })
- return rows
- def write_xlsx(rows: list[dict]) -> None:
- wb = Workbook()
- header_fill = PatternFill("solid", fgColor="2F5597")
- header_font = Font(name="微软雅黑", size=10, bold=True, color="FFFFFF")
- body_font = Font(name="微软雅黑", size=10)
- thin = Side(style="thin", color="BFBFBF")
- border = Border(left=thin, right=thin, top=thin, bottom=thin)
- # Sheet 1:全量字段比对
- ws = wb.active
- ws.title = "字段比对总表"
- headers = ["模板", "序号", "模板字段", "DMS是否有", "DMS来源(模型.字段)", "补充类型",
- "是否连接字段", "是否需放入补充Excel", "模板必填", "数据来源/填报说明"]
- widths = [16, 6, 18, 9, 46, 24, 11, 16, 8, 52]
- for c, (h, w) in enumerate(zip(headers, widths), start=1):
- cell = ws.cell(row=1, column=c, value=h)
- cell.font = header_font
- cell.fill = header_fill
- cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
- cell.border = border
- ws.column_dimensions[get_column_letter(c)].width = w
- ws.row_dimensions[1].height = 28
- r = 2
- for row in rows:
- vals = [
- row["template"], row["seq"], row["field"],
- "是" if row["has_dms"] else "否",
- row["dms_source"], row["kind"],
- "是" if row["is_join"] else "否",
- "是" if row["include"] else "否",
- row["required"], row["note"],
- ]
- for c, v in enumerate(vals, start=1):
- cell = ws.cell(row=r, column=c, value=v)
- cell.font = body_font
- cell.border = border
- cell.alignment = Alignment(vertical="top", wrap_text=True)
- if row["kind"].startswith("连接字段"):
- ws.cell(row=r, column=6).fill = PatternFill("solid", fgColor="FFF2CC")
- elif row["has_dms"]:
- ws.cell(row=r, column=6).fill = PatternFill("solid", fgColor="EDEDED")
- else:
- ws.cell(row=r, column=6).fill = PatternFill("solid", fgColor="FCE4D6")
- if row["required"] == "是":
- ws.cell(row=r, column=9).fill = PatternFill("solid", fgColor="FCE4D6")
- r += 1
- ws.freeze_panes = "A2"
- ws.auto_filter.ref = f"A1:J{r - 1}"
- # Sheet 2:待补充字段清单
- ws2 = wb.create_sheet("待补充字段清单")
- for c, (h, w) in enumerate(zip(headers, widths), start=1):
- cell = ws2.cell(row=1, column=c, value=h)
- cell.font = header_font
- cell.fill = header_fill
- cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
- cell.border = border
- ws2.column_dimensions[get_column_letter(c)].width = w
- ws2.row_dimensions[1].height = 28
- r2 = 2
- for row in rows:
- if not row["include"]:
- continue
- vals = [
- row["template"], row["seq"], row["field"],
- "是" if row["has_dms"] else "否",
- row["dms_source"], row["kind"],
- "是" if row["is_join"] else "否",
- "是", row["required"], row["note"],
- ]
- for c, v in enumerate(vals, start=1):
- cell = ws2.cell(row=r2, column=c, value=v)
- cell.font = body_font
- cell.border = border
- cell.alignment = Alignment(vertical="top", wrap_text=True)
- if row["kind"].startswith("连接字段"):
- ws2.cell(row=r2, column=6).fill = PatternFill("solid", fgColor="FFF2CC")
- else:
- ws2.cell(row=r2, column=6).fill = PatternFill("solid", fgColor="FCE4D6")
- r2 += 1
- ws2.freeze_panes = "A2"
- ws2.auto_filter.ref = f"A1:J{r2 - 1}"
- # Sheet 3:连接字段清单
- ws3 = wb.create_sheet("连接字段清单")
- h3 = ["模板", "连接字段", "DMS是否有", "DMS来源", "连接说明"]
- w3 = [16, 20, 9, 46, 46]
- for c, (h, w) in enumerate(zip(h3, w3), start=1):
- cell = ws3.cell(row=1, column=c, value=h)
- cell.font = header_font
- cell.fill = header_fill
- cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
- cell.border = border
- ws3.column_dimensions[get_column_letter(c)].width = w
- ws3.row_dimensions[1].height = 24
- r3 = 2
- for tpl, keys in JOIN_KEYS.items():
- for k in sorted(keys):
- has_dms = (tpl, k) in DMS_SOURCES
- src = DMS_SOURCES.get((tpl, k), "")
- note = f"{tpl} 补充表与 DMS 数据按该字段对齐"
- vals = [tpl, k, "是" if has_dms else "否", src, note]
- for c, v in enumerate(vals, start=1):
- cell = ws3.cell(row=r3, column=c, value=v)
- cell.font = body_font
- cell.border = border
- cell.alignment = Alignment(vertical="top", wrap_text=True)
- if not has_dms:
- ws3.cell(row=r3, column=3).fill = PatternFill("solid", fgColor="FCE4D6")
- r3 += 1
- ws3.freeze_panes = "A2"
- ws3.auto_filter.ref = f"A1:E{r3 - 1}"
- # Sheet 4:统计汇总
- ws4 = wb.create_sheet("统计汇总")
- ws4.column_dimensions["A"].width = 34
- ws4.column_dimensions["B"].width = 12
- ws4.column_dimensions["C"].width = 52
- total = len(rows)
- dms_count = sum(1 for x in rows if x["has_dms"])
- missing_count = total - dms_count
- source_excel_count = sum(1 for x in rows if (not x["has_dms"]) and (x["template"], x["field"]) in SOURCE_EXCEL_FIELDS)
- new_count = missing_count - source_excel_count
- join_count = sum(1 for x in rows if x["is_join"])
- join_dms = sum(1 for x in rows if x["is_join"] and x["has_dms"])
- join_missing = join_count - join_dms
- supplement_count = missing_count + join_dms
- summary = [
- ("统计项", "数值", "说明"),
- ("最新范围 DMS 模型数", 24, "申勤物业项目清单/申勤员工 2 个 + AMS枢元/申勤数字化改造 22 个"),
- ("15 类模板字段总数", total, "1-31日 按 1 个字段统计(实际是 31 列)"),
- ("DMS 已有字段数", dms_count, "可从本次范围内 DMS 模型自动获取"),
- ("DMS 无字段数(需业务补充)", missing_count, "DMS 范围内无对应字段"),
- ("其中:源Excel可搬运", source_excel_count, "已有源 Excel,但 DMS 未建模型,需搬运"),
- ("其中:需新增/人工确认", new_count, "DMS 与源 Excel 均无,需人工确认/新建"),
- ("连接字段数", join_count, "用于补充 Excel 与 DMS/其他表对齐"),
- ("其中:DMS已有但Excel需带", join_dms, "如项目编号、工号;补充表也必须保留"),
- ("其中:连接字段需补充", join_missing, "如年月、设备编号、片区编号等"),
- ("补充 Excel 需包含字段总数", supplement_count, "= DMS 无字段数 + DMS 已有的连接字段数"),
- ]
- for r, row in enumerate(summary, start=1):
- for c, v in enumerate(row, start=1):
- cell = ws4.cell(row=r, column=c, value=v)
- cell.border = border
- cell.alignment = Alignment(vertical="top", wrap_text=True)
- if r == 1:
- cell.font = header_font
- cell.fill = header_fill
- else:
- cell.font = body_font
- if c == 1:
- cell.font = Font(name="微软雅黑", size=10, bold=True)
- ws4.freeze_panes = "A2"
- wb.save(OUT_XLSX)
- def write_csv(rows: list[dict]) -> None:
- with OUT_CSV.open("w", newline="", encoding="utf-8-sig") as f:
- w = csv.writer(f)
- w.writerow(["模板", "序号", "模板字段", "DMS是否有", "DMS来源(模型.字段)",
- "补充类型", "是否连接字段", "是否需放入补充Excel", "模板必填", "数据来源/填报说明"])
- for row in rows:
- w.writerow([
- row["template"], row["seq"], row["field"],
- "是" if row["has_dms"] else "否", row["dms_source"],
- row["kind"], "是" if row["is_join"] else "否",
- "是" if row["include"] else "否", row["required"], row["note"],
- ])
- def write_markdown(rows: list[dict]) -> None:
- total = len(rows)
- dms_count = sum(1 for x in rows if x["has_dms"])
- missing_count = total - dms_count
- source_excel_count = sum(1 for x in rows if (not x["has_dms"]) and (x["template"], x["field"]) in SOURCE_EXCEL_FIELDS)
- new_count = missing_count - source_excel_count
- join_count = sum(1 for x in rows if x["is_join"])
- join_dms = sum(1 for x in rows if x["is_join"] and x["has_dms"])
- join_missing = join_count - join_dms
- supplement_count = missing_count + join_dms
- lines = [
- "# DMS 数据库字段映射(最新范围)",
- "",
- "> 依据本次指定的 DMS 数据范围重新生成:",
- "> 1. 申勤物业数字化:申勤物业项目清单、申勤员工",
- "> 2. AMS枢元 -- 申勤数字化改造(全部模型)",
- "",
- "## 总览",
- "",
- f"- 范围内 DMS 模型数:24",
- f"- 15 类模板字段总数:{total}",
- f"- DMS 已有字段:{dms_count}",
- f"- DMS 无字段(需业务补充):{missing_count}(源Excel可搬运 {source_excel_count} / 需新增或人工确认 {new_count})",
- f"- 连接字段:{join_count}(DMS已有但 Excel 需带 {join_dms} / 需补充 {join_missing})",
- f"- 补充 Excel 需包含字段总数:{supplement_count}",
- "",
- "## 覆盖概览",
- "",
- "| 模板 | 字段数 | DMS已有 | DMS无 | 需放入补充Excel |",
- "|---|---:|---:|---:|---:|",
- ]
- for tpl, fields in TEMPLATES.items():
- trows = [x for x in rows if x["template"] == tpl]
- has = sum(1 for x in trows if x["has_dms"])
- inc = sum(1 for x in trows if x["include"])
- lines.append(f"| {tpl} | {len(fields)} | {has} | {len(fields) - has} | {inc} |")
- lines += ["", "## 字段级映射", ""]
- for tpl, fields in TEMPLATES.items():
- lines.append(f"### {tpl}")
- lines.append("| 模板字段 | DMS是否有 | DMS来源 | 补充类型 | 连接字段 | 说明 |")
- lines.append("|---|---|---|---|---|---|")
- for x in rows:
- if x["template"] != tpl:
- continue
- lines.append(
- f"| {x['field']} | {'是' if x['has_dms'] else '否'} | "
- f"{x['dms_source'] or '—'} | {x['kind']} | "
- f"{'是' if x['is_join'] else '否'} | {x['note']} |"
- )
- lines.append("")
- OUT_DOC.write_text("\n".join(lines), encoding="utf-8")
- def main() -> None:
- rows = build_rows()
- OUT_XLSX.parent.mkdir(parents=True, exist_ok=True)
- write_csv(rows)
- OUT_XLSX.write_bytes(OUT_CSV.read_bytes())
- write_markdown(rows)
- print("输出:", OUT_XLSX)
- print("输出:", OUT_CSV)
- print("输出:", OUT_DOC)
- if __name__ == "__main__":
- main()
|