# 起因:
老婆大人:今天弄一天的公司的考勤报表,看的我眼睛都花了。这工作太难了,老公,我不想干了。。。
我:说说
老婆大人:公司用的钉钉,一个月会导出一份excel报表,让我统计每个人投入工时。你是不知道,我们公司是灵活上下班,而且有的不同的班次,更残忍的是大家打卡次数可以是多次,,,我现在挨个员工去掰着手指去算:是不是迟到、迟到几分钟、是不是早退、早退几分钟、加班多久。。。弄的我眼睛都花了。。。
我:要不我看看
。。。 a few minite later
我:你想要什么样的
老婆大人:这个是我辛辛苦苦整理的,要这样的
我:好的
# 需求分析:
目标:客户要统计每个员工当月的投入工时。
## 背景:
1.公司是灵活上下班
09:00-18:00
10:00-19:00
2.特殊班次
夜班:16:00-次日上班前
保洁:06:00-08:00(8点前打扫完即可走)
3.钉钉可打卡多次,每次都会记录到导出的表格
## 资料
原始表格
从钉钉导出的原始报表,命名格式如:XXXX公司_考勤报表_20200801-20200831.xlsx

目标表格

# 技术选型
由于是简单的表格处理,属于工具型,因此选择python实现。同时考虑到易用性和用户使用场景是在windows电脑端,最后将python工具转换成EXE。
# 实施
。。。
# 测试验收
用户非常满意
# 交付
https://download.csdn.net/download/xdstuhq/12852539
2023-3-20 补充
好久不发文章了,偶然点开自己的旧文章,竟然发现“我这样一个白嫖党”却要求别人用积分下载。。。顿感羞愧!!!
随补充源码,欢迎技术交流,批评指正,先致谢!
注:运行源码需要“python 操作excel”相关依赖,GitHub上就有,请提前就绪
# -*- coding: utf-8 -*-
import os
import threading
import time
import tkinter as tk
import xlrd
import xlwt
from datetime import datetime
from tkinter import Tk, Label, StringVar, Entry, Button, mainloop, W,filedialog,ttk,LEFT
date_pre = r'2016-12-21 '
date_pre_next = r'2016-12-22 '
date_suf = r':00'
chi_dao = r'迟到'
zao_tui = r'早退'
da_ka_yi_chang = r'打卡异常'
work_time_1 = r'09:00'
work_time_2 = r'10:00'
off_work_time_1 = r'18:00'
off_work_time_2 = r'19:00'
#界面相关
IN_FILE = ''
FILE_NAME = ''
FILE_BASE_DIR = ''
PROGRESS_VALUE = 5
root = Tk()
# monitoring thread
class MyThread(threading.Thread):
def __init__(self, value):
super(MyThread, self).__init__()
self.value = value
def run(self):
root = Tk()
Label(root, text='进度:').grid(row=1, column=0)
p1 = ttk.Progressbar(root, length=200, mode="determinate",
maximum=200,orient=tk.HORIZONTAL)
p1.grid(row=1,column=1)
while(True):
global PROGRESS_VALUE
p1["value"] = PROGRESS_VALUE
root.update()
if PROGRESS_VALUE >= 200:
break
time.sleep(0.5)
root.mainloop()
def showProgress():
#start a new thread for monitoring
t = MyThread(PROGRESS_VALUE)
t.start()
# check
def checkFileAndInit(inFile):
if not os.path.exists(inFile) or not os.path.isfile(inFile):
print("文件不存在")
exit(-1)
global FILE_NAME, FILE_BASE_DIR
FILE_NAME = os.path.basename(os.path.abspath(inFile))
FILE_BASE_DIR = os.path.dirname(os.path.abspath(inFile))
def getFirstTime(value):
if not value or len(value)<1:
return chi_dao
time_list = value.strip(' ').split('\n', 1)
new_value = time_list[0]
new_value = new_value.strip(' ').strip(r'外勤')
# 特殊处理保洁
if new_value <= work_time_1 and getLastTime(value) > work_time_2:
return work_time_1
elif new_value <= work_time_2 < getLastTime(value):
return work_time_2
else:
return new_value
def getLastTime(value):
if not value or len(value)<1:
return zao_tui
time_list = value.strip(' ').rsplit('\n', 1)
if not time_list or len(time_list) < 2:
return zao_tui
new_value = time_list[1]
print(new_value.strip(' ').strip(r'外勤'))
return new_value.strip(' ').strip(r'外勤')
def getWorkTime(first_time):
return date_pre + first_time + date_suf
def getOffWorkTime(first_tiem, last_time):
# 特殊处理保洁
if first_tiem > r'15:00' and r'00:00' < last_time < r'10:00':
return date_pre_next + last_time + date_suf
return date_pre + last_time + date_suf
def getHourFromSecond(second):
return int(second*10)/10
pass
def read_excel(file_name):
# 打开文件
workbook = xlrd.open_workbook(file_name)
# 获取所有sheet
print(workbook.sheet_names()) # [u'sheet1', u'sheet2']
#新建excel
data=xlwt.Workbook()
#新建表
des_table=data.add_sheet(r'打卡统计', cell_overwrite_ok=True)
# 根据sheet索引或者名称获取sheet内容
src_sheet = workbook.sheet_by_name('打卡时间')
for row_num in range(src_sheet.nrows):
for col_num in range(src_sheet.ncols):
global PROGRESS_VALUE
PROGRESS_VALUE = int((row_num + 1) * 100 / src_sheet.nrows) * 2
if row_num < 3 or col_num < 6:
des_table.write(row_num, col_num, src_sheet.cell(row_num, col_num).value)
continue
else:
print(src_sheet.cell(row_num, col_num).value)
if src_sheet.cell(row_num, col_num).value:
value = src_sheet.cell(row_num, col_num).value
first_time = getFirstTime(value)
last_time = getLastTime(value)
print(row_num, col_num, first_time, last_time)
if first_time == chi_dao and src_sheet.cell(2, col_num).value != r'六' and src_sheet.cell(2, col_num).value != r'日' :
des_table.write(row_num, col_num, src_sheet.cell(row_num, col_num).value + '\n' + chi_dao)
continue
if last_time == zao_tui and src_sheet.cell(2, col_num).value != r'六' and src_sheet.cell(2, col_num).value != r'日':
des_table.write(row_num, col_num, src_sheet.cell(row_num, col_num).value + '\n' + zao_tui)
continue
if first_time == chi_dao or last_time == zao_tui:
# des_table.write(row_num, col_num, da_ka_yi_chang)
continue
if row_num==37:
print(first_time, last_time)
work_time = getWorkTime(first_time)
off_work_time = getOffWorkTime(first_time, last_time)
t1 = datetime.strptime(work_time, "%Y-%m-%d %H:%M:%S")
t2 = datetime.strptime(off_work_time, "%Y-%m-%d %H:%M:%S")
print((t2 - t1).seconds/3600)
des_table.write(row_num, col_num, getHourFromSecond((t2 - t1).seconds/3600))
# elif src_sheet.cell(2, col_num).value != r'六' and src_sheet.cell(2, col_num).value != r'日':
# des_table.write(row_num, col_num, da_ka_yi_chang)
saveExcel(data)
def createNewExcel(sheet_name):
#新建excel
data=xlwt.Workbook()
#新建表
table=data.add_sheet(sheet_name, cell_overwrite_ok=True)
return table
def saveExcel(data):
data.save(FILE_BASE_DIR + '/工时统计.xls')
#gui
def inputParams():
global root
Label(root, text='Excel路径:').grid(row=0, column=0) # 对Label内容进行 表格式 布局
v1 = StringVar() # 设置变量 .
e1 = Entry(root, textvariable=v1) # 用于储存 输入的内容
e1.grid(row=0, column=1, padx=10, pady=5) # 进行表格式布局 .
def selectPath(path):
def wrapper(path=path):
#选择文件path_接收文件地址
path_select = tk.filedialog.askopenfilename()
#通过replace函数替换绝对文件地址中的/来使文件可被程序读取
#注意:\\转义后为\,所以\\\\转义后为\\
path_=path_select.replace("/","\\\\")
#path设置path_的值
path.set(path_select)
return wrapper
def initInputParams():
global IN_FILE
IN_FILE = e1.get()
print("图片路径: %s" % e1.get()) # get 变量内容
global root
root.destroy()
start_time = int(time.time())
justDoIt()
end_time = int(time.time())
print('总耗时:%s秒' % str(end_time - start_time))
exit(0)
#Button中command不能直接传参,需对函数进行特殊处理。详见selectPath(path)函数实现
Button(root, text = "路径选择", width=10, command = selectPath(v1)).grid(row = 0, column = 2, sticky=W, padx=15, pady=5)
Button(root, text='芝麻开门', width=10, command=initInputParams).grid(row=4, column=1, sticky=W, padx=15, pady=5) # 设置 button 指定 宽度 , 并且 关联 函数 , 使用表格式布局 .
# Button(root, text='退出', width=10, command=root.quit).grid(row=3, column=1, sticky=E, padx=10, pady=5)
mainloop()
def justDoIt():
# 参数校验和初始化
checkFileAndInit(IN_FILE)
# 显示进度
showProgress()
# 执行操作
read_excel(IN_FILE)
def getAbsPath(*arg):
path = ""
for str in arg:
path = os.path.abspath(os.path.join(path, str))
return path
if __name__ == '__main__':
inputParams()
个人公众号:
本文介绍了如何使用Python处理钉钉考勤报表,统计每个员工的工时。通过读取和分析钉钉导出的原始Excel表格,结合公司的不同班次规则,实现了自动计算工时并生成新的Excel报表。最后,将Python脚本打包成EXE,方便Windows用户使用。

3977

被折叠的 条评论
为什么被折叠?



