用Python自动化生成软件公司劳动合同:模板化、批量与校验
2026/9/18 21:06:22 网站建设 项目流程

简介:这是一份面向软件公司的劳动合同范文,供企业HR、法务人员及软件行业从业者下载使用,用作合同拟订依据、条款解读材料或人力资源培训参考。文档以《**技术有限公司劳动合同书》为底稿,完整呈现员工编号及合同双方信息,并围绕合同期限(固定期限、无固定期限、以完成生产工作为期限)、工时制度与休息休假、工资支付标准与最低工资保障、社会保险缴纳义务、劳动保护要求、合同变更与解除规则等核心板块进行分条说明。其中还明确了试用期工资不得低于约定工资的80%、加班工资计算基数、甲方可随时解除合同的六种情形以及乙方可随时解除合同的六种情形,为软件企业规范用工和员工维权提供了条理清晰的范本依据。资源为1个doc文件,大小仅46KB,便于下载后按需修改填写;已有499人学习下载,适合需要快速构建软企劳动合同文本或学习相关劳动法规的读者。

1. 软件公司劳动合同.doc 是公司里最该自动化的一份文档

每个软件公司都有一份「软件公司劳动合同.doc」:hr 每次招人就从网盘复制一份,改名字、薪资、日期,发给新员工签字。合同看着简单,实际是公司里并发改动最多、版本最容易漂移的文件:法律上它是雇佣关系的主契约,技术上却长期处于手工维护状态。工程师最在意的期权、竞业限制、知识产权归属都写在这里,而多数人签完再没读第二遍。下面把它当作数据模板处理:先定义合同的数据模型与条款版本,用脚本批量生成、自动校验,最后把模板纳入版本管理。适合负责 HR 系统或内部效率工具的工程师,也适合想看懂自己合同的程序员。

2. 先拆字段:软件公司劳动合同的数据模型与必备条款

2.1 从 .doc 的段落结构反推合同的数据模型

把一份软件公司劳动合同.doc 打开,去掉页眉页脚和签字页,剩下的正文可以切成五块:双方主体信息、合同期限与试用期、工作内容与地点、劳动报酬与社会保险,以及软件行业特有的知识产权、保密与竞业限制条款。这种划分对应到数据结构上就是五个分组,每组里就是模板中需要填充或条件判断的字段:

分组典型字段在模板中的形态
主体信息员工姓名、身份证号、住址;公司名称、统一社会信用代码正文首段的甲方乙方
期限与试用期合同起止日期、合同期限、试用期月数第二、三条,带日期和数字
工作内容部门、岗位、工作地点、汇报关系浮动条款,与部门强相关
报酬福利月薪、试用期工资、发薪日、社保公积金基数数字字段,最容易出错
软件行业条款知识产权归属、保密义务、竞业限制、离职交接固定条款,措辞几乎不变

这么拆的目的不是给 hr 看,而是为了后面写脚本:每个字段要么是「在模板里出现的字符串」,要么是「从入职数据里算出来的值」。对工程师来说,模板里的每一句话都要分清是常量还是变量。常量是所有合同都一样的措辞,比如保密条款、知识产权条款;变量是每次招聘都不同的数据,比如姓名、薪资、入职日期。此后所有自动化工作都围绕这个划分展开:常量交给模板维护,变量交给脚本注入。

2.2 知识产权与保密条款:软件公司合同里不能动的那几段

软件公司劳动合同和传统行业合同的最大差异在第三块:代码、文档、客户方案、内部工具这些智力成果到底归谁。常见做法是在合同正文里写「员工在任职期间利用公司资源或在工作时间内产生的职务成果,知识产权归公司所有」,同时配合一份独立的保密协议。这两段在模板里应当保持措辞不变,不要每次招聘时顺手改几个词,因为措辞变动会直接影响后续离职交接和纠纷判定时的依据。

竞业限制是另一个容易踩坑的点。对普通开发人员,竞业限制条款并不是必选项,而且约定了竞业限制,公司就需要在员工离职后按月支付经济补偿。所以模板里更稳妥的写法是把竞业限制设计成可选项:默认勾选「不适用」,只有对核心岗位单独启用。这个开关落到数据模型里就是一个布尔字段has_non_compete,脚本生成时据此决定保留还是删除模板中的竞业限制段。千万别在模板里对所有岗位默认启用,否则离职谈判时每一份合同都会变成纠纷的引信,而人力成本是hr 在签合同那一刻完全意识不到的。

2.3 模板的版本管理:把 .doc 当代码来维护

我一般会把模板放进 git 仓库,目录结构大致是templates/software_company_contract_v1.docx加一个CHANGELOG.md,每次改条款必须写变更记录,commit message 里注明「知识产权条款第 2 句措辞调整」「竞业限制改为可选项」这类信息。这样最大的好处是:当 hr 拿着一份两年前的合同问你「现在的版本是哪份」时,你可以直接给出 commit hash 和 diff,而不是让 hr 从网盘里翻出七八个「最终版」「终极版」「新最终版」。git 对二进制 docx 的 diff 不友好,所以我会在每次提交前用 LibreOffice 把 docx 转出一份 pdf 放到preview/目录,审查时看 pdf,归档时用 docx,源头始终只维护一份模板文件。

3. 用 python-docx 批量生成软件公司劳动合同的落地脚本

3.1 最小可跑:占位符替换的完整脚本

首先把模板里的变量写成统一格式的占位符,例如${name}${monthly_salary}${probation_months}。这样做的原因很简单:占位符是机器可识别的锚点。手动改过的合同里「姓名」和「张三」混在一起,脚本无法判断该替换什么;用占位符之后,替换就是一次纯字符串映射,出问题也能精确报错到具体字段。

# generate_single.py from docx import Document import sys def replace_in_paragraph(para, mapping): for key, value in mapping.items(): if key in para.text: para.text = para.text.replace(key, str(value)) def fill_contract(template_path, data, output_path): doc = Document(template_path) # 正文段落替换 for para in doc.paragraphs: replace_in_paragraph(para, data) # 表格单元格替换:合同里报酬表和社保信息通常藏在表格里 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: replace_in_paragraph(para, data) doc.save(output_path) if __name__ == "__main__": data = { "${name}": sys.argv[1], "${monthly_salary}": sys.argv[2], "${probation_months}": sys.argv[3], } fill_contract("templates/software_company_contract_v1.docx", data, "output/out.docx")

这段代码里有两个细节值得注意。第一,para.text = para.text.replace(...)会丢掉段内原有 run 的格式,如果模板里某一段包含加粗字体或不同字号,替换后整段会变回默认格式。更稳妥的方案是遍历para.runs在 run 级别替换,但前提是占位符不能跨 run,否则一个占位符被 Word 拆进两个 run 里就替换不到。实际项目里我优先保证占位符不跨 run,并采用 run 级替换:格式优先,替换其次。第二,doc.tables必须处理,劳动合同里的薪资、社保这些关键数据几乎都在表格里,只遍历doc.paragraphs会静默漏掉,生成出来的合同看起来完整实际缺数据。

3.2 批量生成:从人员花名册一次性产出全部合同

招聘季一次入职十几个人时,逐条执行上一节的脚本太慢。常见做法是把入职信息维护成一张 Excel 花名册,每一行是一个新员工,字段包括姓名、身份证号、部门、岗位、入职日期、合同期限、月薪、试用期工资、试用期月数。批量脚本用 pandas 读取这张表,逐行调用同一个填充函数生成合同文件。

# batch_generate.py import pandas as pd from pathlib import Path from generate_single import fill_contract def build_data(row): return { "${name}": row["姓名"], "${id_card}": row["身份证号"], "${department}": row["部门"], "${position}": row["岗位"], "${start_date}": row["入职日期"].strftime("%Y年%m月%d日"), "${contract_years}": str(row["合同期限(年)"]), "${monthly_salary}": f"{row['月薪']:.2f}", "${probation_salary}": f"{row['试用期工资']:.2f}", "${probation_months}": str(row["试用期(月)"]), } df = pd.read_excel("new_hire_list.xlsx", dtype={"身份证号": str}) output_dir = Path("output") output_dir.mkdir(exist_ok=True) for idx, row in df.iterrows(): data = build_data(row) out_path = output_dir / f"{row['姓名']}_{idx}_劳动合同.docx" fill_contract("templates/software_company_contract_v1.docx", data, out_path) print(f"已生成: {out_path}")

参数层面有三点要提前定好。dtype参数必须指定为str,否则身份证号会被 pandas 当成数值读成科学计数法,生成到合同里就是「1.10101E+17」这种残废文本。入职日期用strftime格式化成「2025年04月18日」的中文格式,而不是让 Excel 的序列化数字直接进模板,法务审查时看不懂 45800 这种数字。输出文件名里只放姓名和索引,不要放身份证号,避免合同文件在本地目录里通过文件名泄露敏感信息。上面代码里我加了idx防止重名覆盖,如果公司里确认没有同名的情况也可以去掉。

3.3 docx 与 doc 的兼容边界:LibreOffice 转换兜底

python-docx 只能读写 docx,读不了老式二进制 doc。如果公司现在用的还是原始的「软件公司劳动合同.doc」,第一步先用 LibreOffice 把模板转成 docx,后续所有流程都基于 docx 运行:

libreoffice --headless --convert-to docx 软件公司劳动合同.doc \ --outdir templates/

反过来,如果 hr 或法务要求最终交付 .doc 格式,在生成 docx 之后再转一次:

libreoffice --headless --convert-to "doc:MS Word 97" \ output/张三_0_劳动合同.docx --outdir final/

这里要提醒一句:重复经过 LibreOffice 转换,docx 里的样式和分页会有细微变化,所以上线前要拿样张对比一版,确认转换后的页眉页脚、签字栏位置没有错乱。常见的坑是文本框和印章图片在转换后位置偏移,这两类元素正式合同里经常出现,样张检查时优先看它们。转换链路稳定之后,docx 才是真实模板,doc 只是交付格式,不要在 doc 上直接改内容再传回来。

4. 合同自动化的两个硬校验:期限、试用期与薪资一致性

4.1 合同期限与试用期的上限约束

批量自动化的危险不在于生成快,而在于生成得快且错得一致——人工签十份合同可能错一份,脚本一跑错十份,而且错的是同一个字段。所以生成之后必须挂校验。期限与试用期的关系是第一个硬约束:试用期上限由合同期限决定,而不是由 hr 的习惯决定。

def check_probation(contract_years, probation_months): if contract_years < 0.25: # 合同期不满 3 个月,不得约定试用期 limit, msg = 0, "合同期不满3个月,不能设置试用期" elif contract_years < 1: # 满 3 个月不满 1 年,上限 1 个月 limit, msg = 1, "合同期1年以下,试用期不得超过1个月" elif contract_years < 3: # 满 1 年不满 3 年,上限 2 个月 limit, msg = 2, "合同期1年至3年,试用期不得超过2个月" else: # 3 年以上或无固定期限,上限 6 个月 limit, msg = 6, "合同期3年以上或无固定期限,试用期不得超过6个月" ok = probation_months <= limit return ok, (msg if ok else f"试用期 {probation_months} 个月超出上限 {limit} 个月")

这个函数判断的是「某一档期限对应的上限」,边界条件要写对:合同期刚好一年的属第二档,上限两个月;合同期不满三个月则根本不能写试用期。很多公司模板里默认写「试用期三个月」,对一年期合同直接违规,这类问题靠人眼很难逐份看出来,脚本跑一遍马上全部暴露。校验不通过时不要静默跳过,我一般把结果收集起来最后统一输出,避免中途抛出异常导致整个批次中断,反而更难定位是哪一行的问题。

4.2 薪资字段的 80% 底线检查

第二个硬约束在报酬段:试用期工资不得低于合同约定正式工资的 80%,这是薪资条款里最常见的违规点。校验脚本可以和期限校验串在同一个循环里跑,对每一行花名册同时做两类检查:

def check_salary(monthly_salary, probation_salary): floor = monthly_salary * 0.8 ok = probation_salary >= floor return ok, f"试用期工资 {probation_salary:.2f} 低于底限 {floor:.2f}" def validate_all(df): errors = [] for idx, row in df.iterrows(): ok, msg = check_probation(row["合同期限(年)"], row["试用期(月)"]) if not ok: errors.append(f"第{idx}行 {row['姓名']}: {msg}") ok, msg = check_salary(row["月薪"], row["试用期工资"]) if not ok: errors.append(f"第{idx}行 {row['姓名']}: {msg}") return errors errors = validate_all(df) if errors: print("\n".join(errors)) raise SystemExit(1)

注意浮点比较的问题:试用期工资如果是通过「正式工资乘以 0.8」在 Excel 里算出来的,读进来可能是 4800.000000001 这种浮点尾巴。这里我用>=而不是绝对相等,并且把 80% 的计算收敛到两位小数之后再做比较,能过滤掉大部分精度噪声。还有一类常见误用是把社保基数、公积金基数也写进这个校验逻辑,但这两个基数有独立的核算规则,不属于 80% 约束的范畴,混在一起只会让错误报告难以解读,hr 看到一条报错还要分辨到底是哪种基数出了问题。

4.3 校验失败后的处理链路:先记录,后人工,别阻断全批次

批量校验的真正价值在于把「生成完再检查」改成「生成前拦截」。实际跑批时我不会让校验失败直接中断整个循环,而是把错误收集起来,生成一份validation_report.txt,同时把出错的合同单独放到output/error/目录。正确的做法是:批量正常生成,错误合同不删除,等 hr 人工修正花名册里对应的行之后,只针对那几行重新生成。这样每一份出错合同都有对应的输入数据和错误原因,不会出现「改了第 3 行的薪资,结果第 7 行的合同也跟着变」这种连带问题,因为每一行到每一份合同的映射是独立的,重跑哪一行完全可控。

5. 把 .doc 当接口:模板维护的四个长期习惯

到这一步,模板能生成、校验能跑通,还差最后一层:让这套东西活得久,而不是三个月后变成又一个无人维护的脚本。

第一,模板里禁止出现真实姓名、真实薪资和身份证号。模板必须是纯占位符文件,任何一次「直接在模板里改数据再另存」都是在制造新版本,等于绕过 git 和脚本。如果 hr 强烈要求在模板里看到示例数据,就在preview/目录放一份填充了假数据的 pdf,而不是污染模板本身。

第二,占位符用白名单集中管理。把所有允许出现在模板里的占位符写进一个placeholders.json,脚本生成前先扫描模板,发现不在白名单里的${...}直接报错。这能挡住两种事故:hr 手误把${name}打错成${naem},以及某段合同正文里恰好出现了类似${的字符导致误替换。误替换是比漏替换更隐蔽的问题,漏替换还能靠残留检查抓出来,误替换会直接把合同内容改成乱码。

第三,生成后做一次「占位符残留检查」。脚本最后遍历所有输出 docx,检查正文和表格里是否还有${字符,有就说明模板里的占位符和花名册字段没对齐。这条检查成本极低,跑一次不到一秒,但能抓到 90% 以上的「字段名写错」问题。检查脚本直接复用前面的replace_in_paragraph遍历逻辑,把替换改成匹配即可。

第四,把最终交付的 .doc 文件用哈希做版本对账。给每个输出文件算一个 sha256,记录在生成日志里;hr 拿回去改过再传回来时,比对这个哈希就知道文件是否被动过,避免「我发的是这版,hr 签的是另一版」的争议。最后一个具体技巧:在模板第一页加一行不可见的占位符${generated_at},脚本写入生成时间。以后任何人拿到一份合同,都能在 docx 的 document.xml 里反查它由哪版脚本在什么时间生成,这比在文件名里写日期可靠得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询