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

一颗大白菜3个月前我的技能118

原始表格:

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()


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

相关文章

参加“优秀经办人”第一次在线考试中,也是最后一次考试。

参加“优秀经办人”第一次在线考试中,也是最后一次考试。

您于二零二四年度在本平台参加“优秀经办人”第一次在线考试中,凭借出色的个人业务知识,取得优异成绩,并获 100 分。在职工保险业务经办知识方面表现突出,完全符合“第一期”结业标准。如果你也参加了考试,...

运维平台报修系统详细流程与数据统计

一、报修人下单报修人登录运维平台报修系统,进入报修申请页面。系统通过身份验证(如账号密码、手机号验证码等方式)确保报修信息的可追溯性。设备类型选择:报修人首先从预设的设备分类中选择,包括风冷水电器、信...

2024年12月19日 星期四 HOW TO BACK FILES

HelloYour data has been stolen and encryptedWe will delete the stolen data and help with the recover...

2024年9月24日 星期二 我的听歌软件

2024年9月24日 星期二 我的听歌软件

不知道你们都用的什么软件听歌。不过我还是支持付费听歌的,但是我真穷,只能白嫖。...

总体而言,在一定程度上反映了未来招聘求职的一种趋势。

随着人工智能技术的不断发展和普及,在未来的招聘求职中,对 AI 有一定的了解很可能会成为一种普遍的要求,就如同在 90 年代会打字、做基础表格对于求职者来说是基本技能一样。如今,AI 已经在各个领域展...

2024年8月30日 星期五 百度收录“优秀经办人”AI 知识库

2024年8月30日 星期五 百度收录“优秀经办人”AI 知识库

百度收录“优秀经办人”智能体,方便我平台用户了解吉林省保险政策及操作流程。如解答不如意,请联系系统管理员,一对一讲诉。AI知识库已接入“你好优秀经办人”微信公众号,也可直接在公众号内发送问题,AI为您...

发表评论    

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

服务热线

18686626826

微信客服

微信客服