"""生成 Excel 数据模板:每份文件只含一个数据表(+ 一份填写说明)。 设计原则: - 一份文件 = 一个数据域,避免人工跨表填写; - 关联字段用「项目名称/姓名」填写,编号由系统解析,人工无需查编码; - 参考字典(班次/岗位薪资/科目余额)由系统侧维护,不占用模板。 """ from __future__ import annotations import sys from pathlib import Path from openpyxl import Workbook from openpyxl.comments import Comment from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter from openpyxl.worksheet.datavalidation import DataValidation sys.stdout.reconfigure(encoding="utf-8") OUT_DIR = Path(r"E:\CODE\knowledge_agent\data\templates") OUT_DIR.mkdir(parents=True, exist_ok=True) SRC_MKT = r"7-其他数据\20260529_申勤提供数据\市场部\2026年4月项目管理当期年化数据统计(1).xlsx" SRC_BIZ = r"7-其他数据\业务大表-申勤物业202607123-龚天晓字段补充.xlsx" SRC_RECON = r"7-其他数据\收到的数据\申勤市场部项目梳理表-20260616.xlsx" SRC_EMP = r"7-其他数据\收到的数据\人事部\员工信息.xlsx" SRC_ATT_DIR = r"7-其他数据\20260529_申勤提供数据\人事部\青东片区 排班、考勤\考勤26年1月-4月" SRC_SHIFT = r"7-其他数据\20260529_申勤提供数据\人事部\青东片区 排班、考勤\排班:青浦-赵巷公园、赵巷文体中心、赵巷镇政府、赵巷品牌公司系统排班(2026年1-4月).xlsx" SRC_SHIFT_XJ = r"7-其他数据\20260529_申勤提供数据\人事部\青东片区 排班、考勤\排班:青浦-徐泾卫生中心系统排班(2026年1-4月).xlsx" SRC_SHIFT_ZQ = r"7-其他数据\20260529_申勤提供数据\人事部\青东片区 排班、考勤\排班:青浦-赵巷、重固城建中心系统排班(2026年1-4月).xlsx" SRC_FIN = r"7-其他数据\20260529_申勤提供数据\财务部\2026按合同物业收费进程26.5.17(1).xlsx" SRC_EQ_QD = r"7-其他数据\20260529_申勤提供数据\运营部\01 现场工单、设备维保业务台账\项目设备台账\青浦-工业园区设备台账.xlsx" SRC_EQ_XH = r"7-其他数据\20260529_申勤提供数据\运营部\01 现场工单、设备维保业务台账\项目设备台账\徐汇-环境监测中心台账.xlsx" SRC_STAFF = r"7-其他数据\收到的数据\运营部\9-项目岗位编制汇总.xlsx" SRC_PUR_DIR = r"7-其他数据\收到的数据\人事部\采购部常用表格" SRC_CONS = r"7-其他数据\收到的数据\人事部\采购部常用表格\2026年耗材汇总\2026年度耗材汇总.xlsx" SRC_MAINT = r"7-其他数据\收到的数据\人事部\采购部常用表格\维保合同台账.xls" SRC_FIX = r"7-其他数据\收到的数据\人事部\采购部常用表格\固定资产台账汇总表.xlsx" SRC_QC = r"7-其他数据\20260529_申勤提供数据\运营部\02 品质巡检及整改记录台账\申勤物业服务质量检查标准(青浦公共卫生中心)整改.xlsx" HEADER_FILL = PatternFill("solid", fgColor="2F5597") REQUIRED_FILL = PatternFill("solid", fgColor="C00000") HEADER_FONT = Font(name="微软雅黑", size=10, bold=True, color="FFFFFF") BODY_FONT = Font(name="微软雅黑", size=10) NOTE_FONT = Font(name="微软雅黑", size=9, color="808080") THIN = Side(style="thin", color="BFBFBF") BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN) def _make_file(filename: str, title: str, headers: list[str], widths: list[int], notes: list[str], validations: dict[int, list[str]] | None = None, comments: dict[int, str] | None = None, required_cols: list[int] | None = None, examples: list[list] | None = None, field_sources: list[tuple[str, str]] | None = None) -> None: """一份文件 = 一个数据 sheet + 一个填写说明 sheet。""" wb = Workbook() ws = wb.active ws.title = title 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 = REQUIRED_FILL if (required_cols and c in required_cols) else 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 = 30 if examples: example_fill = PatternFill("solid", fgColor="E2EFDA") for r, row in enumerate(examples, start=2): for c, v in enumerate(row, start=1): cell = ws.cell(row=r, column=c, value=v) cell.font = BODY_FONT cell.border = BORDER cell.fill = example_fill cell.alignment = Alignment(vertical="center", wrap_text=True) if validations: for col, values in validations.items(): letter = get_column_letter(col) dv = DataValidation(type="list", formula1='"' + ",".join(values) + '"', allow_blank=True, showDropDown=False) dv.error = "请从下拉列表中选择" ws.add_data_validation(dv) dv.add(f"{letter}2:{letter}500") if comments: for col, text in comments.items(): ws.cell(row=1, column=col).comment = Comment(text, "知识图谱构建") ws.freeze_panes = "A2" note_ws = wb.create_sheet("填写说明") note_ws.column_dimensions["A"].width = 115 note_ws.cell(row=1, column=1, value=f"【{title}】填写说明").font = Font(name="微软雅黑", size=12, bold=True) lines = [ "红色表头 = 必填;黑色表头 = 选填(有则填,没有留空)。", "项目编号为全局唯一关联键(必填);项目名称/姓名为可读性辅助列,由系统校验一致性。", "绿色行 = 实际数据示例(取自真实台账),可复制参考;正式填写前请删除/替换。", "黄色示例数据见 data/test_data 对应文件,可复制参考后删除。", ] + notes for i, n in enumerate(lines, start=2): cell = note_ws.cell(row=i, column=1, value=f"· {n}") cell.font = NOTE_FONT cell.alignment = Alignment(wrap_text=True, vertical="top") if field_sources: start = len(lines) + 3 note_ws.cell(row=start, column=1, value="【字段依据】").font = Font(name="微软雅黑", size=11, bold=True) h1 = note_ws.cell(row=start + 1, column=1, value="字段") h2 = note_ws.cell(row=start + 1, column=2, value="依据数据地址(相对 E:\\申勤物业项目资料)") for h in (h1, h2): h.font = Font(name="微软雅黑", size=10, bold=True) h.fill = PatternFill("solid", fgColor="D9E2F3") h.border = BORDER note_ws.column_dimensions["B"].width = 105 for i, (f, s) in enumerate(field_sources, start=start + 2): c1 = note_ws.cell(row=i, column=1, value=f) c2 = note_ws.cell(row=i, column=2, value=s) c1.font = NOTE_FONT c2.font = NOTE_FONT c2.alignment = Alignment(wrap_text=True, vertical="top") c1.border = c2.border = BORDER wb.save(OUT_DIR / filename) def main() -> None: day_headers = [f"{i}日" for i in range(1, 32)] day_widths = [5] * 31 # 1. 项目信息 _make_file( "项目信息.xlsx", "项目主表", ["项目编号*", "项目名称*", "项目简称(选填)", "上级项目编号", "续签前项目编号", "项目起止时间", "省份", "市(区)", "细分业态", "汇总业态", "委托方式", "计费方式", "合同面积(㎡)", "合同金额(元)", "年化合同额(元)", "合同额具体信息", "服务期限", "合同状况", "首次服务日期", "年化合同收入(元)", "客户满意度(0-100)", "客户投诉(条)", "甲方名称", "项目地址", "项目负责人", "项目负责人工号*", "实际毛利率(%)", "服务状态", "备注"], [26, 18, 20, 22, 22, 24, 10, 10, 12, 12, 12, 12, 14, 16, 16, 20, 18, 12, 14, 16, 16, 12, 28, 24, 14, 16, 14, 12, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", "青浦-区图书馆", "", "", "2026-02-28~2027-02-27", "上海", "青浦", "场馆", "公众物业", "多项单委", "包干", 16562, 3199128, 3199110.84, "当期合同额 3199110.84", "服务期一年(公开)", "已签", "2020-01-01", 3199110.84, "", "", "上海市青浦区图书馆", "青浦区青龙路60号", "张某某", "", "", "服务中", "示例"], ], field_sources=[ ("项目编号", f"{SRC_MKT} →「金蝶编码」;无编码项目用档案目录编号(融合)"), ("项目名称", f"{SRC_BIZ}(表1_2026总表)→「项目名称」"), ("项目简称", f"{SRC_MKT} →「项目简称」"), ("上级项目编号", "新增(融合):由业务大表层级序号推导从属关系"), ("续签前项目编号", "新增(融合):由档案目录续签链推导(scripts/analyze_renewal_folders.py)"), ("项目起止时间", f"{SRC_MKT} →「合同起止日期」(如 2026.02.28-2027.02.27)"), ("省份 / 市(区)", f"{SRC_BIZ}(表1_2026总表)→「省份(直辖市)」「市(区)」"), ("细分业态 / 汇总业态", f"{SRC_BIZ}(表1_2026总表)→「细分业态(建议使用点选方式)」「汇总业态(建议使用点选方式)」"), ("委托方式", f"{SRC_BIZ}(表1_2026总表)→「委托方式(建议使用点选方式)」"), ("计费方式", f"{SRC_BIZ}(表1_2026总表)→「计费方式(建议使用点选方式)」"), ("合同面积", f"{SRC_BIZ}(表1_2026总表)→「合同面积(万平米)」(模板统一为㎡,注意换算)"), ("合同金额 / 年化合同额", f"{SRC_MKT} →「合同金额」「年化合同额」"), ("合同额具体信息", f"{SRC_MKT} →「当期合同额(元)/预估合同额(元)/存量合同额(元)/新增合同额(元)」"), ("服务期限", f"{SRC_MKT} →「服务期限」"), ("合同状况", f"{SRC_MKT} →「合同状况」(子列:线上/线下)"), ("首次服务日期", f"{SRC_BIZ}(表1_2026总表)→「首次服务日期(建议精确到月)」"), ("年化合同收入", f"{SRC_BIZ}(表1_2026总表)→「年化合同收入(万元)(含税)」(模板统一为元,注意换算)"), ("客户满意度 / 客户投诉", "7-其他数据\\20260529_申勤提供数据\\运营部\\03 客户满意度及投诉记录\\客户满意度\\申勤物业服务满意度评价问卷报告(2025年度).docx →「满意度得分/回收答卷数」(聚合为项目属性);投诉来自 …\\客户投诉\\SHSQ-JGJL-02-0301客户投诉受理单.doc 等"), ("甲方名称", f"{SRC_MKT} →「甲方名称」"), ("项目地址", f"{SRC_MKT} →「项目地址」"), ("项目负责人", f"{SRC_BIZ}(表1_2026总表)→「项目点状负责人」"), ("项目负责人工号", "新增(融合):必填,人员唯一标识,避免重名;工号来源于员工信息.xlsx「员工编码」"), ("实际毛利率", f"{SRC_BIZ}(表1_2026总表)→「项目当年实际毛利率」"), ("服务状态", f"新增(融合):{SRC_RECON} →「服务中项目/历史项目」分类"), ("备注", "—"), ], notes=[ "一行 = 一个项目;项目编号是全局唯一关联键,必填——续签项目的名称/简称可能相同,但编号一定不同。", "上级项目编号:子项目填父项目编号,生成 (父)-[:包含]->(子) 边;顶层项目留空。", "续签前项目编号:续签项目填上一期项目编号(生成 新-续签自-旧 边);非续签留空。", "项目编号建议沿用金蝶编码(如 XMSQ0102);无金蝶编码的可按 XMSQ-<层级序号> 或自定义唯一编号。", "项目所属片区不在此表填写,见「项目片区关系.xlsx」(一个项目可属于多个片区);片区负责人见「片区信息.xlsx」。", "合同面积统一用 ㎡;金额统一用 元(来源表是万㎡/万元的注意换算)。", "服务状态取值:服务中/即将到期/已到期/历史。", ], validations={12: ["全委", "多项单委", "单一单委"], 19: ["线上", "线下"], 29: ["服务中", "即将到期", "已到期", "历史"]}, comments={1: "必填,全局唯一关联键(续签项目名称相同但编号不同)", 4: "父项目编号,生成包含边", 5: "上一期项目编号,生成续签自边"}, required_cols=[1, 2, 26], ) # 1.5 片区信息 + 项目片区关系 _make_file( "片区信息.xlsx", "片区主表", ["片区编号*", "片区名称*", "片区负责人工号*", "片区负责人(选填)", "备注"], [16, 20, 16, 14, 20], examples=[["PQ-001", "青东片区", "KWL2699022", "李片区", "示例"]], field_sources=[ ("片区编号", "新增(融合):片区唯一标识(如 PQ-001)"), ("片区名称", f"{SRC_BIZ}(表1_2026总表)→「管理区域(内部口径)」/ 8-项目信息汇总表 →「区域」"), ("片区负责人工号", "员工信息.xlsx →「员工编码」;必填,片区管理责任到人"), ("片区负责人", "新增(融合):可读性核对列,由工号解析"), ("备注", "—"), ], notes=[ "一行 = 一个片区;片区是区域管理维度,项目通过「项目片区关系.xlsx」关联到片区。", "片区编号为唯一键(如 PQ-001),项目片区关系表引用它。", ], required_cols=[1, 2, 3], ) _make_file( "项目片区关系.xlsx", "项目片区关系", ["项目编号*", "项目名称(选填)", "片区编号*", "片区名称(选填)", "备注"], [20, 26, 16, 20, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", "PQ-003", "青中2区", "示例"], ["XMSQ0146", "青浦区赵巷公园", "PQ-001", "青东片区", "示例(一个项目可属于多个片区)"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填)"), ("项目名称", "新增(融合):可读性核对列"), ("片区编号", "关联「片区信息」表(必填)"), ("片区名称", "新增(融合):可读性核对列"), ("备注", "—"), ], notes=[ "一行 = 一个项目与一个片区的归属关系(M:N);一个项目可多行(属于多个片区),一个片区可对应多个项目。", "导入后生成 (项目)-[:属于片区]->(片区) 边。", ], required_cols=[1, 3], ) # 13. 人员证书 _make_file( "人员证书.xlsx", "人员证书", ["工号*", "姓名(选填)", "专业类别", "证书类别", "证书名称*", "备注"], [16, 14, 14, 14, 32, 20], examples=[ ["KWL2306998", "陆雷雷", "安防", "从业资格证", "保安员(三级)", "示例"], ["KWL2699003", "测试丙", "工程", "从业资格证", "高压电工作业", "示例"], ], field_sources=[ ("工号", "员工信息.xlsx →「员工编码」;必填,人员唯一键(登记表申勤 sheet 缺工号,需按姓名在花名册补全)"), ("姓名", "新增(融合):可读性核对列,由工号解析"), ("专业类别", "合景悠活职称及资格证书管理登记表(2026年第一季度)→「专业类别」(安防/工程/综合管理/其他)"), ("证书类别", "同一登记表 →「证书类别」(从业资格证/职称证/执业资格证/其他)"), ("证书名称", "同一登记表 →「证书名称」(严格按证书填写,不能缩写或错字;一人可多行)"), ("备注", "—"), ], notes=[ "一行 = 一人持有的一张证书;一人可多行(多张证书)。", "证书名称必填且尽量规范(如 低压电工作业、物业管理师);同人同证书不重复填写。", "生成 (人员)-[:持有]->(证书) 边,支持“某人有哪些证/持某证的人/某项目持证情况”查询。", ], required_cols=[1, 5], ) # 2. 项目月度财务 _make_file( "项目月度财务.xlsx", "项目财务明细", ["项目编号*", "项目名称(选填)", "年月(YYYY-MM)*", "开票金额(元)", "收款金额(元)", "口径说明", "财务经办人工号*", "财务经办人(选填)", "备注"], [20, 26, 16, 16, 16, 30, 16, 14, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", "2026-03", 266594, 266594, "示例口径", "KWL2699012", "财务员甲", "示例"], ["XMSQ0153", "青浦工业园区大楼", "2026-01", 150000, 140000, "示例口径", "KWL2699012", "财务员甲", "示例"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填)"), ("项目名称", "新增(融合):可读性核对列"), ("年月", "新增(时间键,格式 YYYY-MM)"), ("开票金额", f"{SRC_FIN} →「期内开票」或「1月物业费…12月物业费」"), ("收款金额", f"{SRC_FIN} →「期内收款」或「1月物业费…12月物业费」"), ("口径说明", "新增(融合):标注 应收/实收 等口径"), ("财务经办人工号", "员工信息.xlsx →「员工编码」;必填,用于追溯财务当事人"), ("财务经办人", "新增(融合):可读性核对列,由工号解析"), ("备注", "—"), ], notes=[ "一行 = 一个项目一个月的财务记录,与项目通过「项目编号」关联;项目名称作核对。", "年月格式:YYYY-MM(如 2026-03)。", "开票金额、收款金额分开填写;口径(应收/实收)在「口径说明」列注明。", "同一项目同一月份不要重复填写,重复会导致金额翻倍。", ], comments={3: "时间键,格式 YYYY-MM"}, required_cols=[1, 3, 7], ) # 3. 投标记录 _make_file( "投标记录.xlsx", "投标记录", ["项目编号*", "项目名称(选填)", "年份*", "投标类型", "中标结果", "中标日期", "投标金额(元)", "文档路径", "投标经办人工号*", "投标经办人(选填)", "备注"], [20, 26, 10, 14, 12, 14, 16, 50, 16, 14, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", 2026, "中标通知书", "中标", "2026-02-01", 3199128, "1-青浦区域/102-青浦区图书馆", "KWL2699010", "陈某某", "示例"], ["XMSQ0108", "国家税务总局上海市青浦区税务局", 2026, "中标通知书", "中标", "2025-12-20", 7943908, "1-青浦区域/108-青浦区税务局", "KWL2699010", "陈某某", "示例"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填)"), ("项目名称", "新增(融合):可读性核对列"), ("年份", f"{SRC_BIZ}(表2 中标情况)→「招标年份」;或档案目录合同期文件夹名(如 2026.2.28-2027.2.27)"), ("投标类型", "新增(融合):档案文档类型 招标公告/投标文件/中标公告/中标通知书"), ("中标结果", f"{SRC_BIZ}(表2 中标情况)→「是否中标」"), ("中标日期", "E:\\申勤物业项目资料\\…\\中标通知书(如 …\\102-青浦区图书馆\\…\\中标(成交)结果公告.pdf)"), ("投标金额", f"{SRC_BIZ}(表2 中标情况)或 {SRC_MKT} →「合同金额」"), ("文档路径", "E:\\申勤物业项目资料\\(原始文件相对路径)"), ("投标经办人工号", "员工信息.xlsx →「员工编码」;必填,用于追溯投标当事人"), ("投标经办人", "新增(融合):可读性核对列,由工号解析"), ("备注", "—"), ], notes=[ "一行 = 一个项目一个年份的投标事件;同一年可多条(招标公告/投标/中标公告/中标通知书)。", "中标结果取值:中标/未中标/未知。", "文档路径指向档案目录(E:\\申勤物业项目资料)中的原始文件,用于溯源。", ], validations={3: ["招标公告", "投标文件", "中标公告", "中标通知书"], 4: ["中标", "未中标", "未知"]}, comments={1: "关联项目编号", 8: "档案目录相对路径"}, required_cols=[1, 3, 9], ) # 4. 人员信息 _make_file( "人员信息.xlsx", "人员主表", ["姓名*", "工号*", "组织名称", "岗位名称*", "当前服务项目编号(选填)", "服务起始年月", "服务结束年月", "职级", "员工层级", "所属组织名称", "入职日期", "离职日期", "司龄", "性别", "出生日期", "政治面貌", "联系电话", "备注"], [14, 16, 14, 20, 22, 14, 14, 10, 16, 14, 14, 14, 10, 8, 14, 12, 16, 20], examples=[ ["陆雷雷", "KWL2306998", "保安部", "保安_领班", "XMSQ0146", "2026-01", "2026-12", "P3", "一线员工", "保安部", "2025-01-01", "", "1.5年", "男", "1980-05-12", "汉族", "", "示例"], ["董一青", "KWL2307024", "管理处", "项目经理", "XMSQ01141", "2026-01", "2026-04", "P5", "项目管理人员", "管理处", "2023-02-01", "", "3年", "男", "1978-11-20", "汉族", "", "示例(1-2月赵巷镇政府,3-4月北斗园区)"], ], field_sources=[ ("姓名", f"{SRC_EMP} →「姓名」"), ("工号", f"{SRC_EMP} →「员工编码」(KWL 前缀);或考勤表 →「员工编码」"), ("组织名称", f"{SRC_SHIFT} →「组织名称(Organization Name)」(如 保洁部/保安部)"), ("岗位名称", f"{SRC_EMP} →「岗位名称」;或考勤表 →「岗位」;与「岗位编制」表的岗位用同一规范名(如 保安_领班)"), ("当前服务项目编号", "新增(融合):可用员工信息「五级公司」映射项目编号"), ("服务起始/结束年月", "新增(融合):填了直接生成服务于边;不填由考勤/排班明细推导"), ("职级", f"{SRC_EMP} →「职级」(如 P1/P3/P5)"), ("员工层级", f"{SRC_EMP} →「员工层级」(如 一线员工/项目管理人员)"), ("所属组织名称", f"{SRC_EMP} →「所属组织名称」(如 保洁部/管理处)"), ("入职日期", f"{SRC_EMP} →「入职日期」(如 20171113)"), ("离职日期", "新增(融合):选填,员工信息/花名册如有则填"), ("司龄", f"{SRC_EMP} →「司龄」(如 8.4年)"), ("性别 / 出生日期 / 政治面貌", f"{SRC_EMP} →「性别」「出生日期」「民族」「政治面貌」"), ("联系电话", f"{SRC_EMP}(花名册,如含电话列)"), ("备注", "—"), ], notes=[ "一行 = 一名员工;工号是全局唯一关联键(必填,KWL 前缀,来自人事花名册/考勤表)。", "岗位名称 = 实际工作岗位(必填),与「岗位编制」表的岗位用同一套规范名;员工层级为该岗位所属分类(一线员工/项目管理人员等),由岗位字典映射校验。", "服务起始年月/服务结束年月:填了就直接生成 (人员)-[:服务于]->(项目) 边,跨项目人员可填多行(同一年月范围一行)。", "服务期不填时,系统用考勤/排班明细自动推导。", "组织名称取值:保洁部/保安部/会务部/管理处/工程部/其他。", ], validations={3: ["保洁部", "保安部", "会务部", "管理处", "工程部", "其他"]}, comments={1: "姓名", 2: "必填,全局唯一关联键", 4: "当前服务项目编号", 5: "服务起始年月,格式 YYYY-MM"}, required_cols=[1, 2, 4], ) # 5. 考勤与人员财务(与原始考勤表同构) _make_file( "考勤与人员财务.xlsx", "考勤与人员财务", ["姓名*", "工号*", "项目编号*", "项目名称(选填)", "年月(YYYY-MM)*", *day_headers, "上班天数", "平时加班小时", "国定加班小时", "餐费补助金额(元)", "加班超时费金额(元)", "国定加班费金额(元)", "值班费金额(元)", "税后工资(元)", "备注"], [14, 16, 20, 26, 16, *day_widths, 10, 12, 12, 16, 18, 18, 16, 16, 20], examples=[ ["陆雷雷", "KWL2306998", "XMSQ0146", "青浦区赵巷公园", "2026-01", *["日1" if i <= 22 else "" for i in range(1, 32)], 22, 74, 0, 308, 1748.25, 0, 4896.25, "", "示例"], ], field_sources=[ ("姓名 / 工号", f"{SRC_ATT_DIR}\\青浦-赵巷公园-员工岗位考勤表2026年1月-4月员工岗位考勤表.xls →「姓名」「员工编码」"), ("项目编号", "关联「项目信息」表(必填);可由考勤表表头“项目:青浦-北斗园区”推导"), ("项目名称", "新增(融合):可读性核对列"), ("年月", "考勤表 sheet 名(如“1月”)+ 表头年份(新增时间键)"), ("1-31日", "考勤表 → 每日列(1日…31日),值如 常/夜/日1/日值/OFF/缺勤/请假"), ("上班天数", "考勤表 →「上班天数」"), ("平时加班小时 / 国定加班小时", "考勤表 →「平时加班小时」「国定加班小时」"), ("餐费补助金额", "考勤表 →「餐费补助金额」"), ("加班超时费金额 / 国定加班费金额 / 值班费金额", "考勤表 →「加班超时费金额」「国定加班费金额」「值班费金额」"), ("税后工资", "考勤表 →「备注」列中如含税后工资则填(如 税后工资5270.32)"), ("备注", "—"), ], notes=[ "一行 = 一名员工一个月的考勤 + 当月工资/补贴构成,与原始考勤表结构一致。", "1-31日列填每日状态:常=正常出勤;夜=夜班;日1=日班;日值=日值班;OFF=休息;缺勤/请假按实际填写,无记录留空。", "工号 + 项目编号 + 年月 三者定位一条记录;跨项目流动 = 不同月份填不同项目编号。", "上班天数、加班小时与每日状态需一致;金额列按实填写。", "服务期(服务于边)由本表连续月份自动推导。", ], comments={3: "该项目在当月实际服务的项目编号", 4: "项目名称,作核对"}, required_cols=[1, 2, 3, 5], ) # 6. 排班明细 _make_file( "排班明细.xlsx", "排班明细", ["姓名*", "工号*", "项目编号*", "项目名称(选填)", "年月(YYYY-MM)*", *day_headers, "备注"], [14, 16, 20, 26, 16, *day_widths, 20], examples=[ ["陆雷雷", "KWL2306998", "XMSQ0146", "青浦区赵巷公园", "2026-01", *["A003" if i <= 22 else "OFF" for i in range(1, 32)], "示例"], ], field_sources=[ ("姓名 / 工号", f"{SRC_SHIFT} →「姓名(Name)」「工号(Employee ID)」"), ("项目编号", "关联「项目信息」表(必填);由排班文件名 + 组织代码×考勤交叉推断(scripts/analyze_shift_mapping.py)"), ("项目名称", "新增(融合):可读性核对列"), ("年月", "排班表 sheet 名(如“1月”)+ 表头年份(新增时间键)"), ("1-31日", f"排班文件 → 每日列「班次代码(Shift Code)」;代码字典见同文件「班次列表」sheet。文件:{SRC_SHIFT_XJ}(班次列表23条)、{SRC_SHIFT_ZQ}(班次列表753条)、{SRC_SHIFT}"), ("备注", "—"), ], notes=[ "一行 = 一名员工一个月的排班;1-31日列填班次代码。", "常用班次代码对照:A003=不考勤班(无餐费);常=常班;日1=日班;日值=日值班;夜=夜班;OFF=休息;SQ-C13=常8(8小时);SQ-C16=C8;SQ-Z03=早11:00-19:00(餐费7);SQ-C21=常9;SQ-Z9=中班9小时;SQ-RZ03=日值11小时;SQ-Y19=夜12(无餐费);C8-096/065/087=常班8小时。", "完整班次字典见排班文件「班次列表」sheet(代码→名称→时间段);新增代码直接填写并在备注注明含义,系统自动收录。", ], comments={3: "该项目在当月实际服务的项目编号", 4: "项目名称,作核对"}, required_cols=[1, 2, 3, 5], ) # 7. 岗位编制 _make_file( "岗位编制.xlsx", "岗位编制", ["项目编号*", "项目名称(选填)", "岗位", "预算编制", "项目编制", "标准工时人数", "在岗人数", "备注"], [20, 26, 20, 10, 10, 14, 12, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", "保安_领班", 1, 1, 1, 1, "示例"], ["XMSQ0102", "上海市青浦区图书馆", "保洁_主管", 2, 2, 2, 2, "示例"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填)"), ("项目名称", "新增(融合):可读性核对列"), ("岗位", f"{SRC_STAFF} → 岗位列(如 保安_领班、保洁_主管)"), ("预算编制", f"{SRC_STAFF} → 行“预算编制”"), ("项目编制", f"{SRC_STAFF} → 行“项目编制”"), ("标准工时人数", f"{SRC_STAFF} → 行“标准工时人数”"), ("在岗人数", f"{SRC_STAFF} → 行“项目在岗人数”"), ("备注", "—"), ], notes=[ "一行 = 项目 × 岗位 的编制与在岗情况,支持编制 vs 在岗差异分析。", "岗位按实际岗位名填写(如 保安主管、保洁_领班、工程_强电、会务_接待)。", ], required_cols=[1], ) # 8. 设备信息 _make_file( "设备信息.xlsx", "设备主表", ["项目编号*", "项目名称(选填)", "设备编号*", "设备类型", "名称", "规格型号参数", "制造厂商", "安装位置", "出厂日期", "启用日期", "入项目日期", "退项目日期", "出厂编号", "完好状况", "原值(元)", "设备责任人工号*", "设备责任人(选填)", "备注"], [20, 26, 20, 12, 16, 26, 24, 22, 14, 14, 14, 14, 16, 14, 14, 16, 14, 20], examples=[ ["XMSQ0153", "青浦工业园区大楼", "XF-PLB-01-B1-01", "消防", "喷淋泵", "200T/H 110M 55KW", "上海熊猫", "主楼地下水泵房", "2004-12", "2005-09", "2005-09", "", "10379", "使用正常", "", "KWL2699013", "设备员甲", "示例"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填);可由设备台账表头“管理处:”推导"), ("项目名称", "新增(融合):可读性核对列"), ("设备编号", f"{SRC_EQ_QD} →「编号」"), ("设备类型", f"{SRC_EQ_QD} →「设备类别」(如 消防/电梯/给排水/配电/空调)"), ("名称", f"{SRC_EQ_QD} →「名称」"), ("规格型号参数", f"{SRC_EQ_QD} →「型号、规格 / 主要参数」;{SRC_EQ_XH} →「型号、规格、主要参数」"), ("制造厂商", f"{SRC_EQ_QD} →「制造厂名(国名)」"), ("安装位置", f"{SRC_EQ_QD} →「安装位置」"), ("出厂日期 / 启用日期", f"{SRC_EQ_QD} →「出厂日期」「启用日期」"), ("出厂编号", f"{SRC_EQ_QD} →「出厂编号」;{SRC_EQ_XH} →「产品编号、设备代码」"), ("完好状况", f"{SRC_EQ_QD} →「完好状况」(如 使用正常)"), ("原值", f"{SRC_EQ_QD} →「原值(必要时,填写)」"), ("设备责任人工号", "新增(融合):设备管理责任到人;工号来源于员工信息.xlsx「员工编码」"), ("设备责任人", "新增(融合):可读性核对列,由工号解析"), ("入项目日期 / 退项目日期", "新增(时间建模):入项目可用启用日期代替,退项目选填"), ("备注", "—"), ], notes=[ "一行 = 一台设备;项目名称关联项目,设备编号项目内唯一。", "入项目日期是设备-项目时间关系的起点(无确切日期可用启用日期代替);退项目日期选填。", "日期格式:YYYY-MM-DD。", ], validations={3: ["消防", "电梯", "给排水", "配电", "空调"], 13: ["使用正常", "维修中", "停用", "报废"]}, comments={1: "关联项目编号", 3: "项目内唯一"}, required_cols=[1, 3, 16], ) # 9. 采购与维保(供应商/维保合同/耗材采购/固定资产 共用一张表) _make_file( "采购与维保.xlsx", "采购与维保", ["记录类型*", "项目编号*", "项目名称(选填)", "供应商名称", "供应商编号(选填)", "类别", "名称/合同名称", "数量", "单位", "单价(元)", "金额(元)", "合同年限", "月份/日期", "当前状态", "经办人工号*", "经办人(选填)", "备注"], [14, 20, 26, 24, 14, 16, 24, 10, 8, 12, 14, 10, 14, 12, 16, 14, 20], examples=[ ["供应商", "", "", "上海熊猫", "SUP0001", "设备厂家", "", "", "", "", "", "", "", "", "KWL2699011", "采购员甲", "示例"], ["维保合同", "XMSQ0153", "青浦工业园区大楼", "上海熊猫", "SUP0001", "电梯维保", "电梯维保合同", "", "", "", 125000, 3, "2026-01", "", "KWL2699011", "采购员甲", "示例"], ["耗材采购", "XMSQ0102", "上海市青浦区图书馆", "洁力清洁用品", "SUP0005", "保洁耗材", "垃圾袋", 500, "卷", 2.5, 1250, "", "2026-02", "", "KWL2699011", "采购员甲", "示例"], ], field_sources=[ ("记录类型", "新增(融合):供应商/维保合同/耗材采购/固定资产"), ("项目编号", "关联「项目信息」表(必填;供应商记录留空)"), ("项目名称", "新增(融合):可读性核对列"), ("供应商名称", f"{SRC_CONS} →「供应商」;{SRC_MAINT} →「维保单位」"), ("供应商编号", "新增(融合):供应商唯一标识,避免同名供应商;编号规则 SUP+序号,须与供应商记录行一致"), ("类别", f"{SRC_CONS} → 品类分类(保洁/工程/办公/秩序/绿化耗材);{SRC_MAINT} →「维保类别」"), ("名称/合同名称", f"{SRC_PUR_DIR}\\2026年耗材汇总\\月度\\2026年度保洁耗材1-2月.xlsx →「耗材品类名称」;{SRC_MAINT} →「合同名称」"), ("数量 / 单位 / 单价 / 金额", f"{SRC_PUR_DIR}\\2026年耗材汇总\\月度\\2026年度保洁耗材1-2月.xlsx →「数量」「单价」「金额」"), ("合同年限", f"{SRC_MAINT} →「合同年限」"), ("月份/日期", f"{SRC_CONS} →「月份」;{SRC_FIX} →「采购日期」"), ("当前状态", f"{SRC_FIX} →「当前状态」(在用/闲置/报废/调拨)"), ("经办人工号", "员工信息.xlsx →「员工编码」;必填,用于追溯采购/维保/固定资产当事人(供应商行填登记人)"), ("经办人", "新增(融合):可读性核对列,由工号解析"), ("备注", "—"), ], notes=[ "一张表记录四类采购/维保业务,按「记录类型」区分,只填对应列:", "· 供应商:供应商名称、类别(项目编号/项目名称留空);", "· 维保合同:项目编号、供应商名称、类别、名称/合同名称、金额、合同年限、月份/日期;", "· 耗材采购:项目编号、供应商名称、类别、名称、数量、单位、单价、金额、月份/日期;", "· 固定资产:项目编号、名称、数量、单位、金额、月份/日期(采购日期)、当前状态。", "供应商名称全局唯一,同一供应商多项目采购时名称保持一致。", ], validations={1: ["供应商", "维保合同", "耗材采购", "固定资产"], 12: ["在用", "闲置", "报废", "调拨"]}, required_cols=[1, 2, 15], ) # 10. 检查记录 _make_file( "检查记录.xlsx", "检查记录", ["项目编号*", "项目名称(选填)", "检查类型", "检查日期", "检查得分(0-100)", "问题描述", "整改情况", "整改完成日期", "检查人工号*", "检查人(选填)", "备注"], [20, 26, 14, 14, 16, 40, 30, 14, 16, 12, 20], examples=[ ["XMSQ0102", "上海市青浦区图书馆", "品质检查", "2026-03-15", 92, "大厅地面清洁不及时", "已整改", "2026-03-20", "KWL2699005", "张三", "示例"], ], field_sources=[ ("项目编号", "关联「项目信息」表(必填)"), ("项目名称", "新增(融合):可读性核对列"), ("检查类型", "新增(分类):品质/安全/防汛/消防"), ("检查得分", f"{SRC_QC}(评估汇总表)→「各项目检查总得分/分项得分」"), ("检查日期", f"{SRC_QC} →「检查日期」;或 5-节前安全自查表 → 检查时间"), ("问题描述", f"{SRC_QC} →「检查存在的问题」;安全自查表 →「问题描述」"), ("整改情况 / 整改完成日期", f"{SRC_QC} →「整改完成情况(管理处填写)→ 整改情况/未完成」"), ("检查人工号", "员工信息.xlsx →「员工编码」;必填,用于关联人员节点避免重名"), ("检查人", "新增(融合):可读性核对列,由工号解析"), ("备注", "—"), ], notes=[ "一行 = 一次检查(品质/安全/防汛/消防等),用于问答项目品质与整改情况。", "得分无则留空;问题描述尽量具体,整改完成后填整改情况与完成日期。", ], validations={2: ["品质检查", "安全检查", "防汛检查", "消防检查", "满意度调查"]}, required_cols=[1, 9], ) print("模板已生成(每份 = 1 个数据表 + 1 个填写说明):") for f in sorted(OUT_DIR.glob("*.xlsx")): print(f" {f.name}") if __name__ == "__main__": main()