2026-10-06 · 喵窝资源铺
行政每月最烦的活之一:值班表排完,谁也说不清「10月3号白班是不是排重了」「张三怎么同一天排了两回」。表在几个部门手里各存一份,人眼逐行核对必漏。这篇给一个 openpyxl 脚本:扫整个文件夹的值班表,自动揪出两类冲突——同一日期同一班次排了多个人、同一个人同一天被排了多次——输出到一张冲突明细表。代码实跑验证,真实输出贴在下面。
构造 2 个文件的测试目录:主值班表 11 行,故意埋了 3 人挤一个白班、张三被重复排夜班、无姓名脏行、班次纯空格;另一个「外出登记」表有姓名和日期但没有班次列。跑完:
扫描 2 个文件,发现 3 处冲突,已写入 冲突检查.xlsx 跳过: 外出登记.xlsx/Sheet: 缺班次列,无法判断冲突 冲突类型 日期 班次 涉及人员 同班次 2 人 2026-10-02 白班 张三、王五 同班次 3 人 2026-10-03 白班 李四、王五、赵六 同一人重复排班 2026-10-04 夜班、夜班 张三
注意第一版和修复版的差别:第一版把「张三同一天两个夜班」报成了两遍——既算「同班次 2 人:张三、张三」又算「重复排班」;「外出登记」表因为没有班次列,所有行被静默跳过,跳过清单里根本不出现。这两个坑修完后连跑三次 md5 完全一致。
python duty_conflict.py 值班表文件夹 输出冲突检查.xlsx
判定规则:有「姓名」+「日期」两列才算值班表(自动跳过表头前的说明行);没有「班次」列的表会明确列进跳过清单,不静默;缺姓名/日期/班次的脏行跳过;日期格式 4 层兜底(真日期、2026-10-1、2026/10/1、10月1日 都认)。
# -*- coding: utf-8 -*-
"""值班表冲突检测:扫一个文件夹里的值班表,按「日期+班次」找冲突(同班次超过1人、同一人被重复排班)。"""
import os, sys, glob
from openpyxl import load_workbook, Workbook
import datetime as dt
folder, dst = sys.argv[1], sys.argv[2]
def cell_text(v):
if v is None:
return ""
if isinstance(v, dt.datetime):
return v.strftime("%Y-%m-%d")
return str(v).strip()
# 找表头行:必须同时有「姓名」和「日期」列才算值班表
def find_header(ws):
for r in range(1, min(ws.max_row, 20) + 1):
vals = [cell_text(c.value) for c in ws[r]]
low = [v.lower() for v in vals]
if "姓名" in low and "日期" in low:
return r, vals
return None, []
# 日期统一成 YYYY-MM-DD,四层兜底(datetime / 2026-10-1 / 2026/10/1 / 10月1日)
def norm_date(v):
s = cell_text(v)
if not s:
return ""
if hasattr(v, "year") and hasattr(v, "month"):
return f"{v.year:04d}-{v.month:02d}-{v.day:02d}"
s2 = s.replace("年", "-").replace("月", "-").replace("日", "").replace("/", "-").replace(".", "-")
parts = [p for p in s2.split("-") if p]
if len(parts) == 3:
try:
y, m, d = (int(p) for p in parts)
return f"{y:04d}-{m:02d}-{d:02d}"
except ValueError:
return s
return s
shifts = {} # (日期, 班次) -> [(姓名, 来源)]
person_days = {} # (姓名, 日期) -> [班次]
skipped = []
files = sorted(glob.glob(os.path.join(folder, "*.xlsx")))
for fp in files:
fname = os.path.basename(fp)
try:
wb = load_workbook(fp, data_only=True)
except Exception as e:
skipped.append(f"{fname}: 打不开({e})")
continue
for wsname in wb.sheetnames:
ws = wb[wsname]
hr, header = find_header(ws)
if hr is None:
skipped.append(f"{fname}/{wsname}: 不是值班表(缺姓名/日期列)")
continue
col = {h.lower(): i for i, h in enumerate(header) if h}
i_name, i_date = col["姓名"], col["日期"]
i_shift = col.get("班次")
if i_shift is None:
skipped.append(f"{fname}/{wsname}: 缺班次列,无法判断冲突")
continue
for row in ws.iter_rows(min_row=hr + 1, values_only=True):
name = cell_text(row[i_name] if i_name < len(row) else None)
date = norm_date(row[i_date] if i_date < len(row) else None)
shift = cell_text(row[i_shift] if i_shift < len(row) else None)
if not name or not date or not shift:
continue # 脏行:缺姓名/日期/班次的跳过
shifts.setdefault((date, shift), []).append((name, fname))
person_days.setdefault((name, date), []).append(shift)
out = Workbook()
now = dt.datetime(2026, 1, 1)
out.properties.created = now
out.properties.modified = now # 固定元数据时间戳,重跑 md5 才稳定
s1 = out.active
s1.title = "冲突明细"
s1.append(["冲突类型", "日期", "班次", "涉及人员", "来源文件"])
n_conflict = 0
for (date, shift), lst in sorted(shifts.items()):
names = sorted(set(n for n, _ in lst)) # 去重:同一人排两次不算「多人」,留给重复排班检查
if len(names) > 1:
n_conflict += 1
srcs = sorted(set(f for _, f in lst))
s1.append([f"同班次 {len(names)} 人", date, shift, "、".join(names), "、".join(srcs)])
for (name, date), sl in sorted(person_days.items()):
if len(sl) > 1:
n_conflict += 1
s1.append(["同一人重复排班", date, "、".join(sl), name, ""])
s2 = out.create_sheet("统计")
s2.append(["扫描文件数", len(files)])
s2.append(["跳过", len(skipped)])
for x in skipped:
s2.append(["", x])
s2.append(["冲突数", n_conflict])
out.save(dst)
print(f"扫描 {len(files)} 个文件,发现 {n_conflict} 处冲突,已写入 {dst}")
for x in skipped:
print("跳过:", x)
set 去重,同一人排两次不算「多人」,只留给重复排班检查。col.get("班次") 返回 None 后所有行直接 continue——文件像不存在一样,跳过清单里一个字都没有。表少的时候你不会发现,表一多必漏。修法:识别出值班表但没有班次列时,明确写进跳过清单(「缺班次列,无法判断冲突」)。另有一个上篇说过的老坑这次提前防了:openpyxl 写出的元数据时间戳每次运行都不同,导致输出文件 md5 不稳定——在 Workbook() 创建后把 properties.created/modified 固定住,才能验证「重跑幂等」。
continue 改成往 skipped 里加一条即可。懒得自己跑?把值班表发我(后台留言),AI 跑完人工核对后把冲突清单发回给你,9.9 元/次,虚拟物品交付后不退款、结果不符免费重做。更多现成脚本与代做服务 →
相关阅读:多 Excel 文件按关键字搜客户 · 考勤月度汇总表一键生成 · 打卡记录揪考勤异常 · 值班表×请假单跨表核对