openpyxl处理excel表格文件;python窗体应用程序tkinter的使用;python进度条ProgressBar

该需求背景是有一个应收逾期表格,里面有很多部门的数据,要把表格按部门拆分成每个部门单独一个EXCEL表格文件,并把拆分出来的各部门文件邮件发送给各部门领导,涉及到的python知识点想当多,大致包括:

# python 图形界面程序tkinter的使用
# python messagebox的使用
# python 进度条ProgressBar的使用
# openpyxl 对Excel表格的操作
# python 正则表达式的使用
# python 发送带附件的邮件
# openpyxl 知道表格列号,获取列标字母的方法get_column_letter(int)
# openpyxl 获取表格有数据的区域的最大行号的方法
# openpyxl 遍历表格指定区域
# openpyxl 合并单元格
# openpyxl 新建、保存表格
# openpyxl 设置单元格数值格式
# openpyxl 遍历表格所有有数据的区域,并调整字体、字号、边框、列宽
# python 将小数格式调整为保留两位小数的百分数形式
# 这个程序是用于周钊铌每周拆分应收逾期表格文件,并发送邮件给各部门
# 营销中心各二级部门的数据不拆分成单独文件
# 大客户部专用
# v2022.10.19
import datetime
import smtplib
import re
import tkinter.filedialog
from email.header import Header
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from tkinter import *
from tkinter import messagebox, ttk
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Alignment, PatternFill, Font, Border, Side
from openpyxl.utils import get_column_letter

all_dept_arr = ["营销中心-华南区", "营销中心-西部区","海外事业部","金融及大客户部"]

sales_dept_arr = ["营销中心-华南区", "营销中心-西部区"]

key_customer_dept = ["战略大客户部","华东区大客户部","金融大客户部","金融及大客户部"]

to_list = {
    "营销中心-华南区": ["abc@123.com"],
    "营销中心-西部区": ["abc@123.com"],"金融及大客户部": ["abc@123.com"],"海外事业部": ["abc@123.com"],
}

name_list = {
    "营销中心-华南区": "周总",
    "营销中心-西部区": "胡总","金融及大客户部": "邓总","海外事业部": "王洋总",
    "营销中心": "陈阳总"
}

cc_list = {
    "营销中心-华南区": ["abc@123.com", "def@123.com"],
    "营销中心-西部区": ["abc@123.com", "def@123.com"],"金融及大客户部": ["abc@123.com", "def@123"],"营销中心": ["abc@123.com", "abc@def.com"]
}

dept_arr = []
current_sales_dept = []
dept_arr_mail = []
addr_dict = dict()
ar_balance = dict()
overdue_amount = dict()
overdue_percent = dict()
overdue_date = ""

root = Tk()
root.title("Python小助手@lidi v1.0")
root.wm_geometry('500x450+500+250')
root.resizable(False, False)

l_frame_1 = LabelFrame(root, text="拆分文件", padx=10, pady=10)
l_frame_1.pack(pady=20)

label_1 = Label(l_frame_1, text="请选择待拆分文件:")
text_box = Entry(l_frame_1, bd=2)

label_1.grid(row=0, column=0, padx=5, pady=10)
text_box.grid(row=0, column=1, padx=5, pady=10)


def btn_view_click():
    text_box.delete(0, END)
    text_box.insert(0, tkinter.filedialog.askopenfilename())


btn_view = Button(l_frame_1, text='浏览...', command=btn_view_click)
btn_view.grid(row=0, column=2, padx=5, pady=10)

pgrs_bar = ttk.Progressbar(root)

l_frame_2 = LabelFrame(root, text="发送邮件", padx=30, pady=10)
label_name = Label(l_frame_2, text="发件人邮箱")
label_pwd = Label(l_frame_2, text='发件人密码')
label_addr_list = Label(l_frame_2, text='收件人地址簿')
entry_name = Entry(l_frame_2, bd=2, state='readonly')
entry_pwd = Entry(l_frame_2, bd=2, show='*', state='readonly')
entry_addr_list = Entry(l_frame_2, bd=2, state='readonly')


def btn_view2_click():
    entry_addr_list.delete(0, END)
    entry_addr_list.insert(0, tkinter.filedialog.askopenfilename())


def getLastMonday():
    monday = datetime.date.today()
    # monday = datetime.date(2022, 6, 27)
    one_day = datetime.timedelta(days=1)
    while monday.weekday() != 0:
        monday -= one_day
    return datetime.datetime.strftime(monday, "%Y-%m-%d")


def btn_send_click():
    sender_account = "abc@456.com"
    sender_pwd = "123456"
    sender_name = "ABC<abc@456.com>"

    smtp = smtplib.SMTP_SSL(host="smtp.exmail.qq.com", port=465)
    smtp.login(sender_account, sender_pwd)
    pgrs_bar['value'] = 5
    root.update()

    global overdue_date
    try:
        pgrs_step = 0
        for dept in dept_arr_mail:
            pgrs_step = pgrs_step + 1

            msg = MIMEMultipart()
            msg['From'] = sender_name
            msg['To'] = ";".join(to_list[dept]) 
            msg['Cc'] = ";".join(cc_list[dept]) 
            msg['Subject'] = '逾期货款统计月报' + overdue_date
            message_tips = "<hr /><p>温馨提示:</p><p>本邮件由系统自动发出,有可能遇到附件名称出现乱码的情况,此时双击附件,会提示选择用什么程序打开,此时选择wps或者Office " \
                           "Excel即可正常打开。</p>" \
                           "<p>建议将Foxmail升级到最新版客户端,或者使用网页邮箱查看邮件,附件名称都会正常显示</p>" \
                           "<p>最新版Foxmail下载地址:<a href='https://www.foxmail.com/'>https://www.foxmail.com/</a></p>" \
                           "<p>网页版邮箱登录地址:<a href='https://exmail.qq.com/'>https://exmail.qq.com/</a></p>"
            message_sign = '<hr />Best regards<br/>' \
                           '李堤 营销财管部 财务BP' \
                           ' <br/>' \
                           '深圳市优博讯科技股份有限公司(股票代码:300531)<br/>' \
                           'UROVO TECHNOLOGY CO., LTD. (Stock Code: 300531.SZ)<br/>' \
                           '深圳市南山区学府路63号高新区联合总部大厦36-37楼<br/>' \
                           'Floor 36-37, Hi-tech Zone Union Tower,No.63 Xuefu Road, Nanshan District, Shenzhen,' \
                           'Guangdong,<br/>' \
                           'ChinaTel:+86-755-86186300,<br/>' \
                           'Web:http://www.urovo.com<br/>' \
                           '------------------------------------<br/>' \
                           '本邮件包含信息归优博讯所有,优博讯对该邮件拥有所有权利。请收件人注意保密,未经发件人书面许可,不得向任何第三方组织和个人透露本邮件所含全部或部分信息。<br/>' \
                           'CONFIDENTIALITY NOTICE. This message is intended exclusively for the named addressee and ' \
                           'may contain confidential information. Unless you are the named addressee (or authorised ' \
                           'to receive for the addressee) you may not copy, use or disclose this message. If this ' \
                           'e-mail was sent to you by mistake please notify the sender immediately and delete this ' \
                           'e-mail. '

            message = name_list[dept] + ':<br/><p>您好!截至' + overdue_date + '' + dept + '的应收账款为' + str(
                format(ar_balance[
                           dept], ",")) + '元,逾期金额为' + str(format(overdue_amount[dept], ",")) + '元,逾期率为' + str(
                overdue_percent[
                    dept]) + '</p><p>附件为应收款和逾期款明细,请查收,谢谢!</p><p>后续若有疑问,请及时与财务中心相关同事联系。</p><p>海外--李媛媛</p><p' \
                             '>国内--王博、段春雪、李堤。</p>' + message_sign
            msg.attach(MIMEText(message, 'html', 'utf-8'))

            # print(message)
            xlsx_file = MIMEApplication(open(dept + '.xlsx', 'rb').read())
            xlsx_file['Content-type'] = 'application/octet-stream'
            xlsx_file.add_header('Content-Disposition', 'attachment', filename=Header(dept + '.xlsx',
                                                                                      'utf-8').encode())  # 添加到header信息,此处filename必须用Header编码,不然会出现乱码

            msg.attach(xlsx_file)
            # 正式上线时修改为正式的收件人和抄送人
            # 此处sender_name参数的值必须包含发件人地址,否则会报错
            smtp.sendmail(sender_name, to_list[dept] + cc_list[dept], msg.as_string())
            pgrs_bar['value'] = 5 + pgrs_step / len(dept_arr) * 95
            root.update()
            pgrs_bar['value'] = 5 + pgrs_step / len(dept_arr_mail) * 95
            root.update()

        smtp.quit()
        messagebox.showinfo('发送成功', str(len(dept_arr_mail)) + '封邮件发送成功,请登录邮箱查看已发送邮件')
    except Exception as e:
        # smtp.quit()
        messagebox.showerror('错误', e)


btn_view2 = Button(l_frame_2, text='从文件导入...')
btn_send = Button(l_frame_2, text='发送邮件', command=btn_send_click)


def show_mail_area():
    l_frame_2.pack(pady=20)
    label_name.grid(row=2, column=0, padx=10, pady=10)
    label_pwd.grid(row=3, column=0, padx=10, pady=10)
    label_addr_list.grid(row=4, column=0, padx=10, pady=10)
    entry_name.grid(row=2, column=1, padx=10, pady=10)
    entry_pwd.grid(row=3, column=1, padx=10, pady=10)
    entry_addr_list.grid(row=4, column=1, padx=10, pady=10)
    btn_view2.grid(row=4, column=2, padx=10, pady=10)
    btn_send.grid(row=5, column=1, padx=10, pady=10)


def btn_split_click():
    file_name = text_box.get()
    if file_name != "":
        #try:
            # 显示进度条
            pgrs_bar.pack(padx=100, pady=10)
            pgrs_bar['length'] = 300
            pgrs_bar['maximum'] = 100
            pgrs_bar['value'] = 3
            root.update()

            wb = load_workbook(file_name, data_only=True) # 读取工作簿,只读模式
            # print(wb.sheetnames)
            sheet1 = wb["应收汇总"] # 获取工作表

            global overdue_date
            overdue_date = re.search("[0-9]+\.[0-9]+\.[0-9]+", sheet1.cell(1, 1).value).group()
            pgrs_bar['value'] = 5
            root.update()
            # print(sheet1.title)
            max_row_B = max(bb.row for bb in sheet1['B'] if bb.value) # 获取B列有数据最大行号
            pgrs_bar['value'] = 8
            root.update()
            # print(max_row_B)

            # 遍历工作表数据
            dept_dict = dict()
            for row in sheet1["B4:B" + str(max_row_B)]:
                for cell in row:
                    if cell.value in all_dept_arr:
                        dept_dict[cell.value] = ""
            for key in dept_dict.keys():
                dept_arr.append(key)
                if key in sales_dept_arr:
                    current_sales_dept.append(key)
                elif key in key_customer_dept:
                    dept_arr_mail.append(key)
            del dept_dict
            #dept_arr_mail.append("营销中心")
            # print(dept_arr)
            # print(current_sales_dept)
            # print(dept_arr_mail)

            pgrs_bar['value'] = 10
            root.update()

            pgrs_step = 0
            for dept in dept_arr_mail:
                pgrs_step = pgrs_step + 1
                new_wb = Workbook() # 新建工作簿
                new_ws = new_wb.active # 获取当前工作表
                new_ws.title = dept
                header1 = ["合并客户", "部门", "业务员", "年销售目标(否-不是代理商;有数字则是销售目标量)","应收金额(RMB)", "逾期(含发货超过90天", "逾期1:发货超过90天", "逾期2:超账期", "逾期期间", "", "",
                           "", "",
                           "9-10月份回款额(含售后)", "9月份回款额(含售后)", "10月W1", "10月W2", "10月W3","10月回款合计"]
                header2 = ["", "", "", "", "", "", "","", "1-30天", "31-90天", "91-180天", "181-365天", "1年以上"]
                new_ws.append(header1)  # 往表格中追加内容
                new_ws.append(header2)
                new_ws.merge_cells("A1:A2") # 合并单元格
                new_ws.merge_cells("B1:B2")
                new_ws.merge_cells("C1:C2")
                new_ws.merge_cells("D1:D2")
                new_ws.merge_cells("E1:E2")
                new_ws.merge_cells("F1:F2")
                new_ws.merge_cells("G1:G2")
                new_ws.merge_cells("H1:H2")
                new_ws.merge_cells("I1:M1")
                new_ws.merge_cells("N1:N2")
                new_ws.merge_cells("O1:O2")
                new_ws.merge_cells("P1:P2")
                new_ws.merge_cells("Q1:Q2")
                new_ws.merge_cells("R1:R2")
                new_ws.merge_cells("S1:S2")
                for row3 in new_ws["A1:S2"]:
                    for cell4 in row3:
                        cell4.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) # 居中
                        cell4.fill = PatternFill(start_color='C0C0C0', fill_type='solid') # 字体颜色
                if dept != "营销中心":
                    for row in sheet1["B4:B" + str(max_row_B)]:
                        for cell in row:
                            if cell.value == dept:
                                data_arr = []
                                for row_temp in sheet1["A" + str(cell.row) + ":S" + str(cell.row)]:
                                    for cell2 in row_temp:
                                        data_arr.append(cell2.value)
                                new_ws.append(data_arr)
                else:
                    for row in sheet1["B4:B" + str(max_row_B)]:
                        for cell in row:
                            if cell.value in current_sales_dept:
                                data_arr = []
                                for cell2 in sheet1[cell.row]:
                                    data_arr.append(cell2.value)
                                new_ws.append(data_arr)

                max_row_B2 = max(bb.row for bb in new_ws['B'] if bb.value)
                for row4 in new_ws["E" + str(max_row_B2 + 1) + ":S" + str(max_row_B2 + 1)]:
                    for cell5 in row4:
                        # get_column_letter,已知列号,获取列标字母,
                        cell5.value = '=sum(' + get_column_letter(cell5.column) + '3:' + get_column_letter(
                            cell5.column) + str(max_row_B2) + ')'
                new_ws.cell(max_row_B2 + 1, 1).value = '合计'

                # 计算部门的应收余额和逾期金额
                ar_balance[dept] = 0
                overdue_amount[dept] = 0
                # 累加D列的应收金额
                for row5 in new_ws["E3:E" + str(max_row_B2)]:
                    for cell6 in row5:
                        # 有些单元格为空,Value不能直接做加法,空值做加法会报错,所以做个判断
                        if cell6.value:
                            print(dept)
                            ar_balance[dept] += cell6.value

                # 累加E列的逾期金额
                for row6 in new_ws["F3:F" + str(max_row_B2)]:
                    for cell7 in row6:
                        # 有些单元格为空,Value不能直接做加法,空值做加法会报错,所以做个判断
                        if cell7.value:
                            overdue_amount[dept] += cell7.value
                ar_balance[dept] = round(ar_balance[dept], 2)
                overdue_amount[dept] = round(overdue_amount[dept], 2)
                print(dept)
                print(ar_balance[dept])
                # 计算逾期率,并将结果转化为保留两位小数的百分数
                overdue_percent[dept] = "%.2f%%" % round(overdue_amount[dept] / ar_balance[dept] * 100, 2)

                for row2 in new_ws[new_ws.dimensions]:
                    for cell3 in row2:
                        cell3.font = Font(size=9) # 字体
                        cell3.number_format = "#,##0.00" # 千分位格式
                        cell3.border = Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'),
                                              bottom=Side(style='thin')) # 边框
                        new_ws.column_dimensions[get_column_letter(cell3.column)].width = 15 #列宽
                new_ws.delete_cols(18) # 删除列
                new_ws.cell(max_row_B2 + 2, 6).value = '=F' + str(max_row_B2 + 1) + '/E' + str(max_row_B2 + 1)
                new_ws.cell(max_row_B2 + 2, 6).number_format = "0.00%" # 百分号格式
                new_ws.cell(max_row_B2 + 2, 6).font = Font(size=9) # 字号
                new_wb.save(dept + ".xlsx")

                pgrs_bar['value'] = 10 + pgrs_step / len(dept_arr_mail) * 90
                root.update()
            messagebox.showinfo('拆分成功', '文件已拆分成功,请查看程序所在文件夹')
            # print(ar_balance)
            # print(overdue_amount)
            # print(overdue_percent)
            show_mail_area()
            # pgrs_bar.destroy()
        #except Exception as e:
            #messagebox.showerror('错误', e)
            #pgrs_bar.destroy()

    else:
        messagebox.showerror('错误', '请先选择需要拆分的文件')


btn_split = Button(l_frame_1, text='开始拆分', command=btn_split_click)
btn_split.grid(row=0, column=3, padx=5, pady=10)

root.mainloop()

程序运行效果:

 

 
posted @ 2022-10-19 23:15  Levice  阅读(195)  评论(0编辑  收藏  举报