Python搭建一套会议待办事项自动提醒程序

本文介绍了一个使用Python编写的程序,该程序通过读取共享Excel文件中的会议待办事项,自动发送邮件提醒相关人员完成任务。系统包括环境配置、主程序代码,并提供了自动化配置的步骤,旨在优化团队协作效率。
摘要由CSDN通过智能技术生成

在日程工作中,会议经常会有待办事项需要完成,通常需要安排专人每天关注完成情况并催促相关人员完成,如果会议较多,则会耗费很多时间和精力来完成。基于此需求,本文开发了一套自动提醒相关人员完成待办事项的程序,可多人同时使用,只需owner自己去配置相关参数即可实现。

一、环境需求以及准备工作

1. 建立一个相关所有人员可以访问的共享文件夹

2. 建一个Excel文件,设置为共享(可以多人同时更新内容),Excel内容包含四个sheet:

会议待办、人员邮箱、邮件发送维护、邮件通知记录

会议待办:记录会议的待办事项主表

序号会议记录人提出人提出日期待办事项答复重要程度责任人截止日期状态
1小组例会张三王五6月12日销售数据大数据分析 紧急李四6月13日Open
2小组例会张三王五6月12日产品一报告准备 一般王五6月17日Close
3小组例会张三王五6月14日产品二报告准备 一般李四6月20日Open
4项目周会李四老六6月14日项目中期报告准备 重要王五6月15日Open
5项目周会李四老六6月14日项目周报准备 重要王五6月14日Open

人员邮箱:维护姓名和邮箱的对应关系

姓名部门科室邮箱
忽略要忽略的姓名ALL,-
张三部门一科室一zhangsan@qq.com
李四部门一科室二lisi@qq.com
王五部门一 wangwu@qq.com
老刘部门二 laoliu@qq.com

邮件发送维护:维护邮件提醒通知规则

会议名称:必须与会议待办中完全相同(包括大小写)每周的哪几天发送,输入纯数字,用英文逗号隔开每天发送的时间点,仅支持维护到小时,每小时的0分发送邮件,用英文逗号隔开添加抄送人,用英文逗号隔开; 如果提出人和记录人不在维护中,也会发送默认发送给责任人(不用维护),如需添加其他人,则添加发送人,用英文逗号隔开如果有找不到邮箱的姓名,通知的人,如有多个用英文逗号隔开,默认会发送给记录人当天提出的待办默认都会提醒,之前的待办在到期前多少天提醒,不填默认不限制天数
会议WEEKDAYHOUR抄送发送通知提醒天数
小组例会1,2,3,4,5,08,11,17,20,15,16王五张三
项目周会1,2,3,4,514,15,16王五,老六张三

邮件通知记录:记录邮件提醒日志

TASK_NAMESTART_TIMEEND_TIMETASK_TIMEOWNERCOMMENTSINTERFACE_TIMETASK_PATH
会议待办提醒2023-06-14 15:49:422023-06-14 15:49:475JOGARY小组例会会议提醒已成功发送;项目周会会议提醒已成功发送;2023-06-14 15:49:4710.10.10.11->E:\python\Project_email_TEST.py
会议待办提醒2023-06-14 15:51:352023-06-14 15:51:404JOGARY小组例会会议提醒已成功发送;项目周会会议提醒已成功发送;2023-06-14 15:51:4010.10.10.11->E:\python\Project_email_TEST.py
会议待办提醒2023-06-14 16:19:262023-06-14 16:19:314JOGARY小组例会会议提醒已成功发送;项目周会会议提醒已成功发送;2023-06-14 16:19:3110.10.10.11->E:\python\Project_email_TEST.py
会议待办提醒2023-06-14 16:26:412023-06-14 16:26:464JOGARY小组例会会议提醒已成功发送;项目周会会议提醒已成功发送;2023-06-14 16:26:4610.10.10.11->E:\python\Project_email_TEST.exe

二、主程序

# 填写作者名字和Excel地址,同一环境下更换会议管理,只需更改这两项即可
_owner = "JOGARY"
# address_excel = r'E:\python\会议待办通知管理.xlsx' #此处测试使用本地地址,如果要实现多人自动更新,需换成共享地址
import numpy as np
import pandas as pd
import openpyxl
import smtplib
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from sqlalchemy import create_engine
import html_self
import argparse
comment = ""
try:
    starttime = datetime.now()
    #获取Excel地址
    parser = argparse.ArgumentParser(description="Demo of argparse")
    #可在运行时带参数address,或使用默认值
    parser.add_argument('-a', '--address', default=r'.\会议待办通知管理.xlsx', help='Excel address')
    args = parser.parse_args()
    address_excel = args.address
    if not address_excel:
        raise ValueError("Excel地址为空")
    today = starttime.replace(hour=0,minute=0,second=0,microsecond=0)
    datenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    hour_now = datetime.now().strftime('%H').lstrip('0')
    weekday_now = datetime.now().strftime('%w')

    #读取姓名邮箱
    try:
        namelist = mail_info = pd.read_excel(address_excel,sheet_name='人员邮箱',header=0)
    except:
        comment += "Excel读取失败,请检查地址是否正确;"
        raise 
    # 重复姓名读取,并删除所在行
    name_duplicated = namelist[namelist['姓名'].duplicated(False)]['姓名'].unique()
    namelist.drop(namelist.loc[namelist['姓名'].isin(name_duplicated)].index, inplace=True)
    name_duplicated = ','.join(name_duplicated)
    # 生成字典
    maillist = namelist.set_index("姓名")["邮箱"].to_dict()
    #定义一个保持原有list顺序去重函数
    def remove_duplicates(input):
        output = []
        not_include = maillist['忽略'].split(',')+[''] if '忽略' in maillist.keys() else ['']
        [output.append(i) for i in input if not i in output + not_include]
        return output
    
    #读取邮件发送维护信息
    mail_info = pd.read_excel(address_excel,sheet_name='邮件发送维护',header=1)
    mail_info['提醒天数'].fillna(1000,inplace=True)
    mail_info.fillna('-',inplace=True)
    mail_info_list = mail_info.values 
    
    #读取所有待办事项
    artab = pd.read_excel(address_excel,sheet_name='会议待办')
    artab['剩余天数'] = artab['截止日期'].apply(lambda x: (x-today).days if not (x is pd.NaT or x is np.nan) else 0)
    artab['待办状态'] = artab.apply(lambda x: 'Delay' if x['状态']=='Open' and x['剩余天数']<0 else x['状态'], axis=1)
    
    #每个会议分别提醒,提醒即将到期的未完成待办和当天的待办
    for i in range(len(mail_info_list)):
        meeting,weekday,hour,cc_names,to_names,notice_names,notice_days = mail_info_list[i][:7]
        artab_meeting = artab[((artab['提出日期']==today) | ((artab['剩余天数']<notice_days) & (artab['状态']=='Open'))) & (artab['会议']==meeting)].copy()
        #不在设定发送时间,则不发送
        if not(weekday_now in weekday.split(',') and hour_now in hour.split(',')): 
            comment += '{}会议不在设定发送时间;'.format(meeting)
            continue
        #如果AR到期时间均>0天,则仅在前一天16点以后开始发送邮件
        if artab_meeting['剩余天数'].min() > 1 or (artab_meeting['剩余天数'].min() == 1 and int(hour_now) < 16): 
            comment += '{}会议没有需要提醒的待办;'.format(meeting)
            continue
        artab_meeting.fillna('-',inplace=True)
        proposer_names = remove_duplicates('/'.join(artab_meeting['提出人']).split('/'))
        recorder_names = remove_duplicates('/'.join(artab_meeting['记录人']).split('/'))
        artab_meeting.drop('状态', axis=1, inplace=True)
        artab_meeting['提出日期'] = artab_meeting['提出日期'].apply(lambda x: x if x=='-' else x.strftime('%Y-%m-%d'))
        artab_meeting['截止日期'] = artab_meeting['截止日期'].apply(lambda x: x if x=='-' else x.strftime('%Y-%m-%d'))
        #artab_meeting.drop('记录人',axis=1,inplace=True)

        ar_not_finish = html_self.data_to_html(artab_meeting.values,[list(artab_meeting.columns)],
                                             head=meeting,
                                             widths=[30,70,50,50,100,200,200,40,50,100,40,40],
                                             aligns=['center','center','center','center','center','left','left'],
                                             font=['微软雅黑','2','black','red',10,'blue'],
                                             font_title=['微软雅黑','2'],
                                             font_head=['微软雅黑','3'])
        cc_names = remove_duplicates(cc_names.split(',') + proposer_names + recorder_names)
        to_names = remove_duplicates(to_names.split(',') + '/'.join(artab_meeting['责任人'].tolist()).split('/'))
        notice_names = remove_duplicates(recorder_names + notice_names.split(','))

        #查找OWNER邮箱
        cc_emails = []
        to_emails = []
        notice_emails = []
        notfound = []
        for name in cc_names:
            if name in maillist.keys(): cc_emails.append(maillist[name])
            else: notfound.append(name)
        for name in to_names:
            if name in maillist.keys(): to_emails.append(maillist[name])
            else: notfound.append(name)
        for name in notice_names:
            if name in maillist.keys(): notice_emails.append(maillist[name])
            else: notfound.append(name)
        if notfound:
            to_emails += notice_emails
            notfound = ','.join(remove_duplicates(notfound))

        # 发送邮件
        if to_emails:
            SMTPClient = "mail.***.com" # for example "mail.gmail.com";
            SMTPPort = *** #邮箱端口号
            ssl = False
            tls = True

            eMailfrom = "*****@gmail.com" #发送邮件的邮箱
            fromAccount = "*****" #邮箱账号
            fromEmailPwd = "*****" #邮箱密码
            eMailTo = ','.join(to_emails)
            eMailCc = ','.join(cc_emails)

            msgRoot = MIMEMultipart('related')
            msgRoot['From'] = Header("Meeting Management", 'utf-8') #发件人名称显示,可不定义
            msgRoot['To'] = eMailTo
            msgRoot['Cc'] = eMailCc
            msgRoot['Subject'] = Header(meeting+"待办提醒", charset='UTF-8') #邮件主题
            msgAlternative = MIMEMultipart('alternative')
            msgRoot.attach(msgAlternative)


            # 邮件正文
            mail_msg = """<font face="微软雅黑">Dear Sirs,"""
            if len(artab_meeting) > 0:
                mail_msg += """<br /><strong>以下待办事项请及时处理:<a href="{0}">点击处理</a></strong><br />{1}""".format(address_excel,ar_not_finish)
            mail_msg += """<a href="{0}">点击处理</a> """.format(address_excel)
            mail_msg += """<p>如点击无效,手动查找路径如下:<br /><a href="{0}">{0}</a></p>""".format(address_temp)
            if notfound:
                mail_msg += """<p>以下人员未找到邮箱,请更新成员名单:<br /><font color="red">{}</font></p>""".format(notfound)
            if name_duplicated:
                mail_msg += """<p>以下人员邮箱姓名重复,请更新成员名单:<br /><font color="red">{}</font></p></font>""".format(name_duplicated)    
            msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

        #开始发送邮件
            if ssl:
                s = smtplib.SMTP_SSL(SMTPClient,SMTPPort)
            else:
                s = smtplib.SMTP(SMTPClient,SMTPPort)
                if tls:
                    s.starttls()##tls 方式验证
            s.login(fromAccount, fromEmailPwd)
            msgRoot = s.sendmail(eMailfrom, eMailToAct.split(',')+eMailCcAct.split(','), msgRoot.as_string())
            comment += "{}会议提醒已成功发送;".format(meeting)
        else:
            comment += "{}会议无提醒需发送;".format(meeting)
except Exception as ex:
    comment += "邮件发送失败,错误类型:{}".format(ex)
#---------------------以下内容通常无需改动----------------------
#获取程序运行的信息
import sys
import cx_Oracle
import socket
import os
import time
pcname = socket.gethostname()
ip = socket.gethostbyname(pcname)
file_address = sys.argv[0]
modify_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(os.path.getmtime(file_address)))
_taskname = os.path.basename(file_address).rsplit('.',1)[0] #文件名作为任务名称
task_path = "{}({})->{}({}更新)".format(pcname,ip,file_address,modify_time) 

#写入日志表,以下写入数据库和写入Excel方式可二选一
#写入数据库
db = cx_Oracle.connect('username','password','localhost:1521/orcl') #数据库连接信息
cr = db.cursor()
endtime = datetime.now()
cr.execute('''INSERT INTO ETL_LOG_PYTHON(TASK_NAME,START_TIME,END_TIME,TASK_TIME,OWNER,COMMENTS,TASK_PATH) 
VALUES(:1,:1,:1,:1,:1,:1,:1)
''',[_taskname,starttime,endtime,(endtime-starttime).seconds,_owner,comment,task_path])
db.commit()
cr.close()
db.close()

#写入Excel
endtime = datetime.now()
logtabs = openpyxl.load_workbook(address_excel)
logtab = logtabs['邮件通知记录']
nrows = logtab.max_row # 获得行数
log_values = [_taskname,starttime,endtime,(endtime-starttime).seconds,_owner,comment,endtime,task_path]
for i in range(len(log_values)):
    logtab.cell(nrows+1,i+1).value = log_values[i]
logtabs.save(address_excel)

程序中用到了一个自定义模块html_self,具体定义和用法可参考Python自定义模块:转换生成HTML表格_jogarys的博客-CSDN博客

运行程序,邮件测试结果如下:

三、自动化配置

程序测试OK后,要实现自动化,还需要一些配置:

1. 需将程序打包成exe文件,方法参考Python打包exe文件(如何打包成较小文件)_jogarys的博客-CSDN博客

2. Windows系统中配置自动任务(最好在服务器配置,否则个人电脑关闭后就不能运行了),路径如下:

控制面板→系统和安全→管理工具→计划任务

点击右侧创建基本任务,选择1中打包好的exe文件,设置好运行频率即可

如果Excel不在程序所在文件夹,可在任务中添加Excel地址参数:

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值