2024年10月5日 星期六 考勤打卡数据分析 案例

一颗大白菜1年前我的技能918

原始表格:

image.png

一、软件概述

本软件是一个考勤管理系统,主要用于处理和展示员工的考勤数据。通过导入特定格式的 Excel 文件,可以对员工的考勤情况进行统计和分析,并能够查看特定员工的详细考勤记录。


二、使用要求

导入表格必须为 .xlsx 格式,表格内容应包含员工的工号、姓名、部门以及 1 到 31 天的考勤记录(可包含打卡时间或者空白表示未打卡)。

表格中,前四行数据会被跳过读取。


三、考勤时间判断规则


默认班次:

上班时间为 08:00,下班时间为 17:00。

若员工打卡时间晚于 08:00,则记为迟到。

若员工下班打卡时间早于 17:00,记为缺卡。


学员服务中心_早班:

上班时间为 08:00,下班时间为 17:00。

判断早班情况:

若员工打卡时间晚于 08:00,或者在 08:00 但分钟数大于 0,则记为迟到。

若最后一次打卡时间不在 17:00 之后,则记为缺卡。


学员服务中心_晚班:

上班时间为 10:00,下班时间为 19:00。

若员工打卡时间在 10:00 之前或者 19:00 之后,则认为是晚班。


四、功能介绍

导入文件:

通过点击 “选择文件” 按钮,可以选择要导入的考勤数据 Excel 文件。

导入后,系统会自动读取文件内容,并在树形控件中展示考勤概要。


显示考勤概要:

树形控件展示的考勤概要包括工号、姓名、部门、迟到次数、缺卡次数、正常休息天数、额外休息天数和晚班次数。

可以点击 “部门” 列标题对员工进行排序,但导出统计数据时不会根据排序后的结果导出。


导出统计数据:

点击 “导出统计数据” 按钮,可以将统计后的考勤数据导出为一个新的 Excel 文件,文件名为 “考勤统计汇总.xlsx”。


五、注意事项


软件在运行过程中可能会出现错误,如无法加载文件等情况,请检查文件格式和内容是否正确。


在处理考勤数据时,系统会根据设定的规则进行统计,但可能会存在一些特殊情况无法准确判断,需要人工进行核实。


导出的统计数据仅为当前导入文件的统计结果,不会保留历史数据。若需要多次统计,每次都需要重新导入文件。


软件由“一颗大白菜”设计开发,如遇到问题可联系本人调整。微信、QQ:35515105 电话:18686626826 。


成品文件:

image.png

image.png

import pandas as pd
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog

# 全局变量
filepath = None
attendance_summary = {}

def load_attendance_data(filepath):
    """加载考勤数据"""
    try:
        df = pd.read_excel(filepath, header=None, skiprows=4, 
                           names=['工号', '姓名', '部门'] + [str(i) for i in range(1, 32)])
        print(f"读取的DataFrame列名: {df.columns.tolist()}")
        return df
    except Exception as e:
        print(f"加载文件时出现错误:{e}")
        return None

def process_attendance(df, progress_var):
    """处理考勤数据并更新进度"""
    RULES = {
        'default': {'check_in': '08:00', 'check_out': '17:00'},
        '学员服务中心_早班': {'check_in': '08:00', 'check_out': '17:00'},
        '学员服务中心_晚班': {'check_in': '10:00', 'check_out': '19:00'},
        '新媒体_早班': {'check_in': '08:00', 'check_out': '17:00'},  # 添加新媒体规则
        '新媒体_晚班': {'check_in': '10:00', 'check_out': '19:00'},  # 添加新媒体规则
    }

    attendance_summary.clear()
    total_rows = len(df) if df is not None else 0

    for index, row in df.iterrows():
        if df is None:
            break
        try:
            employee_id = row['工号']
            name = row['姓名']
            department = row['部门']
            # 修改此处以支持新媒体部门
            if '早班' in str(department):
                shift = '早班'
                rules_key = f'{department}_{shift}' if department in ['学员服务中心', '新媒体'] else 'default'
            elif '晚班' in str(department):
                shift = '晚班'
                rules_key = f'{department}_{shift}' if department in ['学员服务中心', '新媒体'] else 'default'
            else:
                shift = 'default'
                rules_key = 'default'
            rules = RULES.get(rules_key, RULES['default'])
        except KeyError as e:
            print(f"缺少必要的列: {e}")
            continue

        attendance_summary[employee_id] = {
            'name': name,
            'department': department,
            'late': 0,
            'missed': 0,
            'rest': 0,
            'extra_rest': 0,
            'evening_shifts': 0,
            # 修改此处以设置新媒体部门的背景颜色
            'bg_color': '#b0dfe9' if department == '新媒体' else '#f0f0f0' if department == '学员服务中心' else '#ffffff'
        }

        days = list(filter(lambda x: isinstance(x, str) and x.isdigit(), row.keys()))
        for day in days:
            if pd.isnull(row[day]):
                if attendance_summary[employee_id]['rest'] < 4:
                    attendance_summary[employee_id]['rest'] += 1
                else:
                    attendance_summary[employee_id]['extra_rest'] += 1
                continue

            punches = row[day].split('\n')
            if len(punches) == 0:
                if attendance_summary[employee_id]['rest'] < 4:
                    attendance_summary[employee_id]['rest'] += 1
                else:
                    attendance_summary[employee_id]['extra_rest'] += 1
                continue

            valid_punches = [punch for punch in punches if ':' in punch]

            if department == '学员服务中心' and len(valid_punches) == 1:
                attendance_summary[employee_id]['missed'] += 1
                print(f"{employee_id} missed on day {day}: {valid_punches}")
                continue

            if len(valid_punches) < 2:
                attendance_summary[employee_id]['missed'] += 1
                print(f"{employee_id} missed on day {day}: {valid_punches}")
                continue

            first_punch = pd.to_datetime(valid_punches[0]).time()
            last_punch = pd.to_datetime(valid_punches[-1]).time()

            if department == '学员服务中心':
                if any(t.hour < 10 or t.hour >= 19 for t in (first_punch, last_punch)):
                    rules = RULES['学员服务中心_晚班']
                    shift = '晚班'
                    attendance_summary[employee_id]['evening_shifts'] += 1
                else:
                    # 判断早班情况
                    if shift == '早班':
                        if first_punch.hour > 8 or (first_punch.hour == 8 and first_punch.minute > 0):
                            attendance_summary[employee_id]['late'] += 1
                        # 判断最后一次打卡是否在 17:00 之后
                        if last_punch.hour > 17 or (last_punch.hour == 17 and last_punch.minute > 0):
                            # 这里标记为非有效考勤,但不影响迟到和缺卡统计
                            pass
                        else:
                            attendance_summary[employee_id]['missed'] += 1
                            print(f"{employee_id} missed on day {day}: {valid_punches}")

            # 对非学员服务中心的迟到情况也进行判断
            if first_punch.hour >= int(rules['check_in'].split(':')[0]) and department != '学员服务中心':
                attendance_summary[employee_id]['late'] += 1

            if last_punch.hour < int(rules['check_out'].split(':')[0]) and department != '学员服务中心':
                attendance_summary[employee_id]['missed'] += 1
                print(f"{employee_id} missed on day {day}: {valid_punches}")

        # 更新进度条
        progress_var.set(index / total_rows * 100)
        root.update_idletasks()

    return attendance_summary

def display_attendance_summary(summary, tree):
    """在树形控件中展示考勤概要"""
    for item in tree.get_children():
        tree.delete(item)

    for emp_id, info in summary.items():
        tree.insert("", "end", text=emp_id, values=(
            emp_id,
            info['name'],
            info['department'],
            info['late'],
            info['missed'],
            info['rest'],
            info['extra_rest'],
            info['evening_shifts']  
        ), tags=(info['bg_color'],))

    tree.tag_configure('#ffffff', background='#ffffff')
    tree.tag_configure('#f0f0f0', background='#f0f0f0')
    tree.tag_configure('#c4c4c4', background='#c4c4c4')

def browse_file():
    """选择文件对话框"""
    global filepath
    selected_filepath = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx")])
    if selected_filepath:
        filepath = selected_filepath
        attendance_df = load_attendance_data(filepath)
        global attendance_summary
        progress_var.set(0)  
        if attendance_df is not None:
            attendance_summary = process_attendance(attendance_df, progress_var)
            display_attendance_summary(attendance_summary, tree)
            progress_var.set(100)  
        else:
            print("无法加载文件,请检查文件格式或内容。")

def export_marked_data():
    """导出统计后的数据"""
    global attendance_summary
    if not attendance_summary:
        print("请先加载考勤数据")
        return

    export_data = {
        '工号': [],
        '姓名': [],
        '部门': [],
        '迟到次数': [],
        '缺卡次数': [],
        '正常休息天数': [],
        '额外休息天数': [],
        '晚班次数': []  
    }

    for emp_id, info in attendance_summary.items():
        export_data['工号'].append(emp_id)
        export_data['姓名'].append(info['name'])
        export_data['部门'].append(info['department'])
        export_data['迟到次数'].append(info['late'])
        export_data['缺卡次数'].append(info['missed'])
        export_data['正常休息天数'].append(info['rest'])
        export_data['额外休息天数'].append(info['extra_rest'])
        export_data['晚班次数'].append(info['evening_shifts'])  

    export_df = pd.DataFrame(export_data)
    marked_filepath = "考勤统计汇总.xlsx"
    export_df.to_excel(marked_filepath, index=False)
    print(f"已导出考勤统计数据到: {marked_filepath}")
    return marked_filepath

def show_info():
    """显示说明信息"""
    info_window = tk.Toplevel(root)
    info_window.title("使用说明")
    info_window.geometry("1000x400")  # 窗口大小

    info_text = (
        "一、软件概述\n"
        "本软件是一个考勤管理系统,主要用于处理和展示员工的考勤数据。通过导入特定格式的 Excel 文件,可以对员工的考勤情况进行统计和分析,并能够查看特定员工的详细考勤记录。\n\n"
        "二、使用要求\n"
        "导入表格必须为 .xlsx 格式,表格内容应包含员工的工号、姓名、部门以及 1 到 31 天的考勤记录(可包含打卡时间或者空白表示未打卡)。\n"
        "表格中,前四行数据会被跳过读取。\n\n"
        "三、考勤时间判断规则\n\n"
        "默认班次:\n"
        "上班时间为 08:00,下班时间为 17:00。\n"
        "若员工打卡时间晚于 08:00,则记为迟到。\n"
        "若员工下班打卡时间早于 17:00,记为缺卡。\n\n"
        "学员服务中心_早班:\n"
        "上班时间为 08:00,下班时间为 17:00。\n"
        "判断早班情况:\n"
        "若员工打卡时间晚于 08:00,或者在 08:00 但分钟数大于 0,则记为迟到。\n"
        "若最后一次打卡时间不在 17:00 之后,则记为缺卡。\n\n"
        "学员服务中心_晚班:\n"
        "上班时间为 10:00,下班时间为 19:00。\n"
        "若员工打卡时间在 10:00 之前或者 19:00 之后,则认为是晚班。\n\n"
        "四、功能介绍\n"
        "导入文件:\n"
        "通过点击 “选择文件” 按钮,可以选择要导入的考勤数据 Excel 文件。\n"
        "导入后,系统会自动读取文件内容,并在树形控件中展示考勤概要。\n\n"
        "显示考勤概要:\n"
        "树形控件展示的考勤概要包括工号、姓名、部门、迟到次数、缺卡次数、正常休息天数、额外休息天数和晚班次数。\n"
        "可以点击 “部门” 列标题对员工进行排序,但导出统计数据时不会根据排序后的结果导出。\n\n"
        "导出统计数据:\n"
        "点击 “导出统计数据” 按钮,可以将统计后的考勤数据导出为一个新的 Excel 文件,文件名为 “考勤统计汇总.xlsx”。\n\n"
        "五、注意事项\n\n"
        "软件在运行过程中可能会出现错误,如无法加载文件等情况,请检查文件格式和内容是否正确。\n\n"
        "在处理考勤数据时,系统会根据设定的规则进行统计,但可能会存在一些特殊情况无法准确判断,需要人工进行核实。\n\n"
        "导出的统计数据仅为当前导入文件的统计结果,不会保留历史数据。若需要多次统计,每次都需要重新导入文件。\n\n"
        "软件由“一颗大白菜”设计开发,如遇到问题可联系本人调整。微信、QQ:35515105 电话:18686626826 。\n\n"
    )

    # 创建文本框
    text_box = tk.Text(info_window, wrap=tk.WORD)
    text_box.insert(tk.END, info_text)
    text_box.config(state=tk.DISABLED)  # 设置为只读
    text_box.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

# 创建 GUI
root = tk.Tk()
root.title("考勤管理系统")
root.geometry("800x600")  
root.minsize(800, 600)  

frame = tk.Frame(root)
frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

# 添加说明按钮
button_info = tk.Button(frame, text="说明", command=show_info)
button_info.grid(row=0, column=0, padx=5, pady=5)

button_browse = tk.Button(frame, text="选择文件", command=browse_file)
button_browse.grid(row=0, column=1, padx=5, pady=5)

button_export = tk.Button(frame, text="导出统计数据", command=export_marked_data)
button_export.grid(row=0, column=2, padx=5, pady=5)

# 添加进度条和标签
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(frame, orient=tk.HORIZONTAL, length=400, variable=progress_var, maximum=100)
progress_bar.grid(row=0, column=3, padx=5, pady=5)
progress_label = tk.Label(frame, text="进度")
progress_label.grid(row=0, column=4, padx=5, pady=5)

# 设置树形控件并定义列宽
tree = ttk.Treeview(frame, columns=("工号", "姓名", "部门", "迟到次数", "缺卡次数", "正常休息天数", "额外休息天数", "晚班次数"),  
                    show="headings")
tree.heading("工号", text="工号")
tree.heading("姓名", text="姓名")
# 获取部门列的索引
department_index = tree['columns'].index('部门')
tree.heading("部门", text="部门", command=lambda: sort_treeview(tree, department_index))
tree.heading("迟到次数", text="迟到次数")
tree.heading("缺卡次数", text="缺卡次数")
tree.heading("正常休息天数", text="正常休息天数")
tree.heading("额外休息天数", text="额外休息天数")
tree.heading("晚班次数", text="晚班次数")  

# 设置各列的宽度
tree.column("工号", width=10)
tree.column("姓名", width=50)
tree.column("部门", width=50)
tree.column("迟到次数", width=20)
tree.column("缺卡次数", width=20)
tree.column("正常休息天数", width=30)
tree.column("额外休息天数", width=30)
tree.column("晚班次数", width=30)  

def show_missed_details(event):
    item_id = tree.identify_row(event.y)
    if item_id:
        # 获取选中行的工号、姓名、部门
        emp_id = tree.item(item_id, 'values')[0]
        name = tree.item(item_id, 'values')[1]
        department = tree.item(item_id, 'values')[2]
        if filepath:
            original_df = pd.read_excel(filepath, header=None, skiprows=4, 
                                        names=['工号', '姓名', '部门'] + [str(i) for i in range(1, 32)])
            details_df = original_df[(original_df['工号'] == emp_id) & (original_df['姓名'] == name) & (original_df['部门'] == department)]
            # 创建一个新的窗口展示明细表格
            detail_window = tk.Toplevel(root)
            detail_tree = ttk.Treeview(detail_window, columns=details_df.columns.tolist(), show="headings")
            for col in details_df.columns:
                detail_tree.heading(col, text=col)
            # 设置列宽
            for col in details_df.columns:
                if col in ['工号', '迟到次数', '缺卡次数', '晚班次数']:
                    detail_tree.column(col, width=20)
                elif col in ['姓名', '部门']:
                    detail_tree.column(col, width=50)
                else:
                    detail_tree.column(col, width=30)
            # 插入数据
            for index, row in details_df.iterrows():
                detail_tree.insert("", "end", values=tuple(row))
            detail_tree.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

tree.bind("<Double-1>", show_missed_details)

tree.grid(row=1, column=0, columnspan=4, padx=5, pady=5, sticky='nsew')

scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=tree.yview)
scrollbar.grid(row=1, column=4, sticky='ns')
tree.configure(yscrollcommand=scrollbar.set)

frame.columnconfigure(0, weight=1)  
frame.rowconfigure(1, weight=1)  

def sort_treeview(tree, column_index):
    """对树形控件进行排序"""
    items = [(tree.set(item, column_index), item) for item in tree.get_children('')]
    sorted_items = sorted(items, key=lambda x: x[0])
    for index, (_, item) in enumerate(sorted_items):
        tree.move(item, '', index)

root.mainloop()


免责声明
如果您对本文有异议,请先阅读本站《免责声明》,如仍保持您个人观点可与本人联系。

相关文章

离职补偿金计算器 V1.0

离职补偿金计算器 V1.0

仅供学习参考 ……[yk_WeChatFans]https://share.weiyun.com/ExUIGHP1[/yk_WeChatFans]...

长春地区 优秀经办人 AI 知识库大模型助力五险一金服务

长春地区 优秀经办人 AI 知识库大模型助力五险一金服务

        本系统由 “百度文心智能体平台” 搭建。文心智能体平台 AgentBuilder,是百度推出的基于文心大...

2024年9月4日 星期三 成龙新片《传说》

2024年9月4日 星期三 成龙新片《传说》

前世情缘,梦中寻觅。家国太平,英雄铸成!影片是《神话》的姊妹篇,讲述房教授(成龙 饰)在考古时挖掘出神秘的萨满古玉,它引领着房教授和助手王靖(张艺兴 饰)梦回西汉,化身骁勇善战的汉军将领,征战于无垠的...

Python 代码之 养老保险 个人缴费指数计算工具,3秒计算。

Python 代码之 养老保险 个人缴费指数计算工具,3秒计算。

演示代码不做最终输出效果。import pandas as pd import matplotlib.pyplot as plt fro...

退休年月批量计算工具(仅供学习).xlsm

员工号出生日期性别原法定     退休年龄新退休年月当年领取养老金     最低缴费年限100011976-10-03男602039年10月20年1000219...

《长春AI思维、应用交流群 邀请函》

《长春AI思维、应用交流群 邀请函》

尊敬的企业职工与管理者们:在科技飞速发展的当下,AI 正以前所未有的速度改变着我们的工作方式和生活节奏。为了更好地探索 AI 在办公领域的无限潜力,我们诚挚地邀请您加入我们全新创建的交流社群。这个社群...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

服务热线

18686626826

微信客服

微信客服

[优秀经办人]品牌
×