# -*- coding: utf-8 -*- import collections import datetime import calendar import os from collections import defaultdict import numpy as np import pandas as pd from calendar import monthrange import time from docxtpl import DocxTemplate from openpyxl import Workbook from openpyxl.styles import NamedStyle, PatternFill, Font, Alignment, Border, Side, colors word_dir = '.\\word\\' info_dir = '.\\info\\' def new_round(_float, _len=2): """ Parameters ---------- _float: _len: int, 指定四舍五入需要保留的小数点后几位数为_len Returns ------- type ==> float, 返回四舍五入后的值 """ try: if isinstance(_float, np.float64): return new_round(float(_float), _len) elif isinstance(_float, float): if 'e' in str(_float).lower(): return round(_float, _len) elif str(_float)[::-1].find('.') <= _len: return _float elif str(_float)[-1] == '5': return round(float(str(_float)[:-1] + '6'), _len) else: return round(_float, _len) elif isinstance(_float, str): _float = _float.replace(',', '') return new_round(float(_float), _len) else: return round(_float, _len) except Exception as e: pass def from_filename_get_datetime(filename: str): dt = filename[-7:] date = datetime.datetime.strptime(dt, "%Y-%m") days = calendar.monthrange(date.year, date.month) later1dt = date + datetime.timedelta(days[1] + 1) later1 = later1dt.strftime("%Y-%m-%d") n_days = calendar.monthrange(later1dt.year, later1dt.month) later2dt = later1dt + datetime.timedelta(n_days[1]) later2 = later2dt.strftime("%Y-%m-%d") return dt, later1, later2 class GenerateWord(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def my_sum(self, data_dict, loc, length=2): # 模板内使用 count = 0.0 for item in data_dict.values(): x = self.reverse_fill(item[loc]) count += x a = new_round(count, length) return a @staticmethod def reverse_fill(value_str: float, length=6): return new_round(value_str, length) @staticmethod def get_bank(**kwargs): info_map = { 'ch bj': os.path.join(info_dir, 'cn_bank_bj.txt'), 'ch hk': os.path.join(info_dir, 'cn_bank_hk.txt'), 'en bj': os.path.join(info_dir, 'en_bank_bj.txt'), 'en hk': os.path.join(info_dir, 'en_bank_hk.txt') } return info_map.get(kwargs.get('bank')) or info_map['ch bj'] @staticmethod def get_region_display(data: dict): for i in data.keys(): if i == 'cn-north-1': data['北京区资源费'] = data.pop(i) elif i == 'cn-northwest-1': data['宁夏区资源费'] = data.pop(i) return data @staticmethod def get_date_array(_date: str): _array = monthrange(int(_date[:4]), int(_date[5:7])) return [ "%s-%s-01" % (_date[:4], _date[5:7]), "%s-%s-%2d" % (_date[:4], _date[5:7], _array[1]), ] @staticmethod def fill(value, length=2): temp = "{:,.%sf}" % length if isinstance(value, str): value = value.replace(',', '') return temp.format(float(value)) elif isinstance(value, int) or isinstance(value, float): return temp.format(value) else: return temp.format(0) @staticmethod def display_time(day_str, str_fmt="%Y-%m-%d %H:%M:%S"): # 设置中文编码格式 # import locale # locale.setlocale(locale.LC_CTYPE, 'chinese') zh_fmt = '%Y年%m月%d日' # en_fmt = '%B %d, %Y' return time.strftime(zh_fmt, time.strptime(str(day_str), str_fmt)) def get_currency(self): if self.kwargs.get("currency") == 'USD': return "$" return "¥" def get_temple_number(self, number): data = [ self.fill(number * 1.06), self.fill(number), self.fill(0), self.fill(number * 0.06), ] return data @staticmethod def check_path_creat(path): if not os.path.exists(path): os.mkdir(path) class Book: def __init__(self, *args, **kwargs): self.wb = Workbook() self.wb.remove(self.wb.active) self.highlight = None self.project = kwargs.get('project', '') self.tag = kwargs.get('tag', '') self.tagname = kwargs.get('tagname', '') self.currency = kwargs.get('currency', '¥') self.time = kwargs.get('time', '') self.subtotal_str = '小计:' if kwargs.get('language', None) else 'Subtotal:' self.total_str = '总计:' if kwargs.get('language', None) else 'Total:' def save(self, file_name='freeze.xlsx'): self.wb.save(file_name) def workbook(self, name=None, index=False): name = name.replace(' ', '_') if name is None or index is True: wb = self.wb.create_sheet('ResourcesUsage', ) wb.title = 'ResourcesUsage' else: wb = self.wb.create_sheet(name, ) # 在最后添加 wb.title = name return wb def format_font(self, background_color="FFFFFF", font_color='2B2B2B', bold=True): if not self.highlight: self.highlight = NamedStyle(name = "highlight") self.highlight.fill = PatternFill("solid", fgColor = background_color) # 背景填充 self.highlight.font = Font(name = '微软雅黑', size = 11, bold = bold, italic = False, strike = False, color = font_color) return self.highlight class Sheet: def __init__(self, _wb: Book, tag: str, table_first_data=None, table_second_data=None, table_data=None, index=False, **kwargs): self.border = None self.raw_wb = _wb self.wb = _wb.workbook(tag.replace(':', "_"), index=index) self.highlight = _wb.format_font self.tag = tag self.tagname = kwargs.get('tagname', None) self.title_first = [] self.table_first_data = table_first_data self.table_data = table_data self.title_second = [] self.table_second_data = table_second_data self.margin_left = 1 self.margin_top = 5 self.row_number = None def merge(self, left, right, top, down, color=None, value=None): """ :param left: :param right: :param top: :param down: :param color: :param value: :return: """ self.set_border(left, right, top, down) self.wb.merge_cells('%s%d:%s%d' % (chr(64 + left), top, chr(64 + right), down)) subtotal = self.wb.cell(row=self.row_number, column=left) subtotal.style = self.highlight(color) if value: subtotal.value = value if not isinstance(value, str): subtotal.number_format = f'{self.raw_wb.currency}#,##0.00;{self.raw_wb.currency}-#,##0.00' subtotal.alignment = Alignment(horizontal='center', vertical='center') self.wb.row_dimensions[self.row_number].height = 20 def fill_table(self, data: list, index_title: list, link=False, index=False): """ 填充数据 :param data: :param index_title: :return: """ for row in data: for k, value in enumerate(row): cell = self.wb.cell(row=self.row_number, column= k + self.margin_left + 1) cell.style = self.highlight(bold=False) cell.alignment = Alignment( horizontal = index_title[k].get('sub_horizontal', 'center'), vertical = 'center' ) cell.border = self.my_border('thin', 'thin', 'thin', 'thin') self.wb.row_dimensions[self.row_number].height = 20 if index_title[k].get('format'): cell.number_format = index_title[k].get('format') if k == 0 and link is True: cell.value = '=HYPERLINK("%s", "%s")' % (f'#{value.replace(" ", "_")}!B2', value) cell.font = Font(underline = 'single', color = '0563C1') else: cell.value = value # if index and k == 1: # cell.hyperlink = f'#{value.replace(" ", "_")}!B2' # cell.font = Font(underline = 'single', color = '0563C1') self.row_number += 1 def fill_footer(self, footer_data: list, color='A9D08E'): """ 填充角标 :return: """ total = self.margin_left + 1 for k, v in footer_data: # 合并每一个区域块 self.merge(total, total + k - 1, self.row_number, self.row_number, value = v, color = color) # 标注网格线 self.set_border(total, total + k - 1, self.row_number, self.row_number) total += k # 完成下移 # 空行 self.row_number += 1 for i in range(2): self.wb.append([]) self.row_number += 1 def format_title(self, index_data: list): for i, title in enumerate(index_data): self.wb.column_dimensions[chr(65 + i + 1)].width = title.get('width', 20) cell = self.wb.cell(row = self.row_number, column = i + self.margin_left + 1) title_info = index_data[i] cell.value = title_info['title'] cell.style = self.highlight( title_info['background_color'], font_color = title_info.get('font_color', '2B2B2B') ) cell.alignment = Alignment(horizontal = 'center', vertical = 'center') self.set_border(self.margin_left + 1, len(index_data) + self.margin_left, self.row_number, self.row_number) self.row_number += 1 def my_border(self, t_border, b_border, l_border, r_border): if self.border: return self.border else: self.border = Border(top=Side(border_style=t_border, color=colors.BLACK), bottom=Side(border_style=b_border, color=colors.BLACK), left=Side(border_style=l_border, color=colors.BLACK), right=Side(border_style=r_border, color=colors.BLACK)) return self.border def redirect_index(self): cell = self.wb.cell(row = 1, column = 1) cell.style = self.highlight(bold = False) cell.border = self.my_border('thin', 'thin', 'thin', 'thin') self.wb.row_dimensions[self.row_number].height = 20 cell.value = '=HYPERLINK("%s", "%s")' % (f'#ResourcesUsage!B2', "跳回首页") cell.font = Font(underline='single', color='0563C1') def set_border(self, left, right, top, down): for row in range(top, down + 1): for line in range(left, right + 1): cell = self.wb.cell(row = row, column = line) cell.border = self.my_border('thin', 'thin', 'thin', 'thin') cell.alignment = Alignment(horizontal = 'center', vertical = 'center') def fill_index(self, location='B2', title_index: list = None, title=None, bkg=None): if title: self.wb[location] = title else: self.wb[location] = self.raw_wb.project + ':' + self.raw_wb.time if bkg: self.wb[location].style = self.highlight(background_color = bkg) else: self.wb[location].style = self.highlight(background_color = 'D9D9D9') self.set_border(self.margin_left + 1, len(title_index) + self.margin_left, self.row_number, self.row_number) self.wb[location].border = self.my_border('thin', 'thin', 'thin', 'thin') self.wb[location].alignment = Alignment(horizontal = 'center', vertical = 'center') self.row_number += 1 def set_solid_border(self, area): s_column = area[0] s_index = area[1] e_column = area[2] e_index = area[3] # 设置左框线 for cell in self.wb[s_column][s_index - 1:e_index]: cell.border = Border(left=Side(border_style='thin', color=colors.BLACK)) # 设置右框线 for cell in self.wb[e_column][s_index - 1:e_index]: cell.border = Border(right=Side(border_style='thin', color=colors.BLACK)) # # 设置上框线 # for row in tuple(self.wb[s_column + str(s_index):e_column + str(s_index)]): # for cell in row: # cell.border = Border(top=Side(border_style='thin', color=colors.BLACK)) # # 设置下框线 # for row in tuple(self.wb[s_column + str(e_index):e_column + str(e_index)]): # for cell in row: # cell.border = Border(bottom=Side(border_style='thin', color=colors.BLACK)) class IndexSheet(Sheet): def __init__(self, *args, **kwargs): kwargs['index'] = True super().__init__(*args, **kwargs) self.title_index = [ {'title': 'NO.', 'background_color': "808080", "font_color": "FFFFFF", 'sub_horizontal': 'center', 'width': 26}, {'title': self.raw_wb.tag, 'background_color': "808080", "font_color": "FFFFFF", 'width': 35}, {'title': 'Total Cost', 'background_color': "808080", "font_color": "FFFFFF", 'sub_horizontal': 'center', 'format': f'{self.raw_wb.currency}#,##0;{self.raw_wb.currency}-#,##0', 'width': 35}, {'title': 'Percentage', 'background_color': "808080", "font_color": "FFFFFF", 'sub_horizontal': 'center', 'width': 30}, ] self.margin_top = 7 self.wb.sheet_view.showGridLines = False # 隐藏网络线 self.wb.merge_cells('B8:E8') self.row_number = self.margin_top + 1 self.wb.merge_cells('A1:G1') self.wb.merge_cells('B2:E4') b2 = self.wb.cell(row=2, column=2) b2.value = 'The Monthly Bill for AWS {}'.format('世纪富轩') fontObj2 = Font(bold=False, italic=False, size=20) b2.font = fontObj2 b2.alignment = Alignment(horizontal='center', vertical='center') b5 = self.wb.cell(row=5, column=2) b5.value = 'Billing duration:' b5.style = self.highlight(background_color='FFFF00', bold=False) c5 = self.wb.cell(row=5, column=3) c5.style = self.highlight(bold=False) b6 = self.wb.cell(row=6, column=2) b6.value = 'Tag=%s' % self.tag d5 = self.wb.cell(row=5, column=4) d5.value = self.raw_wb.time self.wb.sheet_properties.tabColor = "FF0000" def run(self): # 第二行 self.fill_index(location="B8", title_index=self.title_index) # title行 self.format_title(self.title_index) total_cost = sum([v for _, v in self.table_data.items()]) self.fill_data = [] num = 1 for k, v in self.table_data.items(): self.fill_data.append([num, k, v, "%.2f%%" % (v / total_cost * 100)]) num += 1 # 填充数据行 self.fill_table(data=self.fill_data, index_title=self.title_index, index=True) self.set_border(2, len(self.title_index) + self.margin_left, self.row_number, self.row_number) # 总计行 self.fill_footer([(2, self.raw_wb.total_str), (1, total_cost)]) class AppSheet(Sheet): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.title_second = [ {"title": 'NO.', 'background_color': "808080", "font_color": "FFFFFF", 'width': 5}, {"title": 'Name', 'background_color': "808080", "font_color": "FFFFFF", 'width': 30}, {"title": 'Product', 'background_color': "1F4E78", "font_color": "FFFFFF", 'width': 30}, # {"title": 'Resource Cost', 'background_color': "D9D9D9", 'width': 20, # 'format': f'{self.raw_wb.currency}#,##0.00;{self.raw_wb.currency}-#,##0.00', }, {"title": 'Total Cost', 'background_color': "D9D9D9", 'width': 30, 'format': f'{self.raw_wb.currency}#,##0.00;{self.raw_wb.currency}-#,##0.00', }, {"title": 'Tag', 'background_color': "808080", "font_color": "FFFFFF", 'width': 20}, ] self.wb.sheet_view.showGridLines = False # 隐藏网络线 # self.wb.freeze_panes = 'C4' # 冻结窗口 self.wb.merge_cells('B3:F5') b3 = self.wb.cell(row=3, column=2) b3.value = '{} program fee details'.format(self.tag) fontObj2 = Font(bold=False, italic=False, size=20) b3.font = fontObj2 b3.alignment = Alignment(horizontal='center', vertical='center') self.wb.merge_cells('B6:F6') self.row_number = self.margin_top + 1 def run(self): # 第二行 self.fill_index(location='B6', title_index=self.title_first) # title行 # self.format_title(self.title_first) # # 填充数据行 # total_1 = new_round(sum([item[-4] for item in self.table_first_data]), 6) # # 格式化 符号 # for i in range(len(self.table_first_data)): # self.table_first_data[i][3] = self.table_first_data[i][3] # self.table_first_data[i][5] = self.table_first_data[i][5] # self.table_first_data[i][6] = self.table_first_data[i][6] # self.table_first_data[i][7] = self.table_first_data[i][7] # self.fill_table(data = self.table_first_data, index_title = self.title_first) # self.set_border(2, len(self.title_first) + self.margin_left, self.row_number, self.row_number) # # 小计行 # self.fill_footer([(len(self.title_first) - 4, self.raw_wb.subtotal_str), (4, total_1)]) # 第二个表格 self.format_title(self.title_second) start = self.row_number total_2 = new_round(sum([item[3] for item in self.table_second_data]), 6) # 格式化 符号 for i in range(len(self.table_second_data)): self.table_second_data[i][3] = self.table_second_data[i][3] self.table_second_data[i][4] = self.table_second_data[i][4] self.fill_table(self.table_second_data, index_title = self.title_second) end = self.row_number - 1 # if end > start: # self.wb.merge_cells( # '%s%d:%s%d' % (chr(64 + self.margin_left + 2), start, chr(64 + self.margin_left + 2), end)) # 小计行 self.fill_footer([(3, self.raw_wb.subtotal_str), (1, total_2)]) thin_border = Border(left=Side(style='none'), right=Side(style='none'), top=Side(style='none'), bottom=Side(style='none')) self.wb.cell(row=6, column=2).border = thin_border # 总计行 # self.fill_footer([ # (len(self.title_first) - 2, self.raw_wb.total_str), # (2, new_round(float(total_1) + float(total_3), 2)) # ], color = 'FFC000') class Custom_Excel(object): def __init__(self, *args, **kwargs): self.tag = kwargs.get('tag', None) self.csv_list = kwargs.get('csv_list', []) def get_data(self): df = pd.DataFrame() for _csv in self.csv_list: if _csv: csv_data = pd.read_csv( _csv, sep=',', encoding='utf-8', skiprows=[0, ], # 默认要删除第一行 dtype={ 'InvoiceID': np.object, 'PayerAccountId': np.object, 'LinkedAccountId': np.object }, low_memory=False ) df = pd.concat([df, csv_data], axis=0) df = df[(df['RecordType'] == 'LinkedLineItem') & (df['ProductName'].notna())] df = df[(df['LinkedAccountId'] == '941124188381')] df.to_csv('941124188381-aws-cost-allocation-ACTS-{}.csv'.format(self.data_month), mode='w', encoding='UTF-8', index=False) df[self.tag] = df[self.tag].fillna('Other') df[self.tag] = df[self.tag].apply(lambda x: str(x).strip()) result = df.groupby(by=[self.tag, 'ProductName']).agg({'TotalCost': 'sum'}) result_dict = result.to_dict(orient='dict') return result_dict def get_period(self): data = self.csv_list[0] self.data_month = data[-11:-4] date = datetime.datetime.strptime(self.data_month, "%Y-%m") later = date.strftime("%Y-%m-%d") days = calendar.monthrange(date.year, date.month) later1dt = date + datetime.timedelta(days[1] - 1) later1 = later1dt.strftime("%Y-%m-%d") return later, later1 def create_excel(self): start_dt, end_dt = self.get_period() currency = '¥' sheet = Book( tag=self.tag, time='{}_{}'.format(start_dt, end_dt), currency=currency, project='世纪富轩' # language='zh' ) self.data = self.get_data()['TotalCost'] # index_dict = defaultdict(float) # name_dict = defaultdict(list) # for k, v in self.data.items(): # k_li = k[0].split('-') # if k_li[0] == 'prophet': # index_dict['prophet'] += float(v) # elif k_li[0] == 'myyshop': # index_dict['myyshop'] += float(v) # elif k_li[0] == 'suzano': # index_dict['suzano'] += float(v) # elif k_li[0] == 'emfund': # index_dict['emfund'] += float(v) # elif k_li[0] == 'mixcldpoc': # index_dict['mixcldpoc'] += float(v) # else: # index_dict[k[0]] += v # count = len(name_dict[k[0]]) # name_dict[k[0]].append([count+1, k[0], k[1], v, self.tag]) # result = collections.OrderedDict(sorted(index_dict.items(), key=lambda t: t[1], reverse=True)) # IndexSheet(sheet, self.tag, table_data=result).run() # for i in name_dict: # app = AppSheet(sheet, i, tagname=self.tag) # app.table_second_data = name_dict[i] # app.run() # sheet.save(os.path.join('./', '{}-{}账单.xlsx'.format('世纪富轩', self.data_month))) # print('生成完成') # 只出北京区 self.create_word() print('数据处理完成') def create_word(self): prophet_dict = defaultdict(float) pro_total = 0 other_dict = defaultdict(float) other_total = 0 for k, v in self.data.items(): k_li = k[0].split('-') if k_li[0] == 'prophet': pro_total += v prophet_dict[k[1]] += v else: other_total += v other_dict[k[1]] += v prophet_dict['InvoiceID'] = '' prophet_dict['TotalCost'] = pro_total other_dict['InvoiceID'] = '' other_dict['TotalCost'] = other_total filename, _type = os.path.splitext(self.csv_list[0]) SJFXGenerateWord('prophet', prophet_dict, filename).create_word() SJFXGenerateWord('other', other_dict, filename).create_word() class SJFXGenerateWord(GenerateWord): def __init__(self, linkedid, result, filename, *args, **kwargs): super().__init__(*args, **kwargs) self.linkedid = linkedid self.result = result self.filename = filename def create_word(self): filedate, noticedate, due_data = from_filename_get_datetime( self.filename) noticenumber = self.result.pop('InvoiceID') totalcost = self.result.pop('TotalCost') product_code_dict = defaultdict() # order_dict = dict(sorted(self.result.items(), key=lambda t: t[1], reverse=True)) # for k, v in order_dict.items(): for k, v in self.result.items(): product_code_dict[k] = self.get_temple_number(v) totalcost_li = self.get_temple_number(totalcost) date = self.get_date_array(filedate) for i in range(len(date)): date[i] = self.display_time(date[i], str_fmt="%Y-%m-%d") bank_info_list = [] with open(self.get_bank(), 'r', encoding='utf8') as f: for line in f.readlines(): if "{{company_email}}" in line: line = line.replace( '{{company_email}}', self.kwargs.get( 'company_email', '')) bank_info_list.append(line.strip('\n')) xx = self.get_template() template = DocxTemplate(xx) content = { 'storage': self.linkedid, 'linkedid': '941124188381', 'noticenumber': noticenumber, 'noticedate': noticedate, 'currency': self.get_currency(), 'region_display': '北京区资源费', 'totalcost': totalcost_li, 'product_code_dict': product_code_dict, 'date': date, 'workday': due_data, 'bank_info_list': bank_info_list, } file_name = '{}_AWS账单{}.docx'.format(self.linkedid, filedate) doc_file_path = os.path.join(word_dir, self.filename) self.check_path_creat(doc_file_path) doc_file_name = os.path.join(doc_file_path, file_name) template.render(context=content) template.save(doc_file_name) print('{}_AWS账单{}.docx 生成完成'.format(self.linkedid, filedate)) return [doc_file_name] @staticmethod def get_template(): return os.path.join(info_dir, 'china-template.docx') def get_temple_number(self, number): data = [ self.fill(number), self.fill(number / 1.06), self.fill(0), self.fill(number / 1.06 * 0.06), ] return data if __name__ == '__main__': csv_list = [ '785691964566-aws-cost-allocation-ACTS-2022-04.csv', ] tag = 'user:Name' Custom_Excel(csv_list=csv_list, tag=tag).create_excel()