# enviar_email_excel.py import smtplib import ssl import psycopg2 import configparser import pandas as pd import matplotlib.pyplot as plt import matplotlib.ticker as mtick import seaborn as sns from email.message import EmailMessage from email.utils import make_msgid from email.mime.image import MIMEImage from pathlib import Path from datetime import datetime, time config = configparser.ConfigParser() config.read(r"C:\Users\joao.herculano\Documents\Enviador de email\credenciais.ini") print(config['banco']['host'],config['banco']['user'],config['banco']['password']) # Conexão com o banco conn = psycopg2.connect( host=config['banco']['host'], # ou IP do servidor port="5432", # padrão do PostgreSQL database="ginseng", user=config['banco']['user'], password= config['banco']['password'] ) # Criar um cursor para executar comandos SQL cur = conn.cursor() # 1. Criar dados fictícios e gerar Excel query = ''' select *, CASE WHEN dayswithoutsales BETWEEN 40 AND 59 THEN 'mais de 40 dias' WHEN dayswithoutsales BETWEEN 60 AND 79 THEN 'mais de 60 dias' WHEN dayswithoutsales BETWEEN 80 AND 99 THEN 'mais de 80 dias' WHEN dayswithoutsales >= 100 THEN 'acima de 100 dias' ELSE 'menos de 40 dias' end as status_venda, pricesellin * (stock_actual + stock_intransit) AS valor_estoque_parado from "public"."draft" where dayswithoutsales > 40 and deactivation = '' and stock_actual > 0 and isproductdeactivated is not null and currentcyclesales = 0 ''' df = pd.read_sql(query, conn) cur.close() conn.close() remetente = config['credenciais']['remetente'] senha = config['credenciais']['senha'] destinatarios = [email.strip() for email in config['email']['destinatarios'].split(',')] assunto = config['email']['assunto'] print(remetente,senha,destinatarios,assunto) pdvs = pd.read_excel(r"C:\Users\joao.herculano\Documents\PDV_ATT.xlsx") df['loja_id'] = df['loja_id'].astype('Int64') pdvs['PDV'] = pdvs['PDV'].astype('Int64') df2= pd.merge(left=df,right=pdvs[['PDV','UF']],left_on='loja_id',right_on='PDV',how='inner') # Dicionário de renomeação colunas_traduzidas = { "loja_id": "id_loja", "code": "código", "description": "descrição", "launch": "lançamento", "deactivation": "desativação", "thirdtolastcyclesales": "venda_terceiro_ciclo_passado", "secondtolastcyclesales": "venda_penúltimo_ciclo", "lastcyclesales": "venda_último_ciclo", "currentcyclesales": "venda_ciclo_atual", "nextcycleprojection": "projeção_próximo_ciclo", "secondtonextcycleprojection": "projeção_segundo_próximo_ciclo", "stock_actual": "estoque_atual", "stock_intransit": "estoque_em_transito", "purchasesuggestion": "sugestao_compra", "smartpurchase_purchasesuggestioncycle": "compra_inteligente_ciclo", "smartpurchase_nextcyclepurchasesuggestion": "compra_inteligente_prox_ciclo", "pendingorder": "pedido_pendente", "salescurve": "curva_vendas", "promotions_description": "descrição_promocao", "promotions_discountpercent": "desconto_promocao_percentual", "pricesellin": "preço_sellin", "businessunit": "unidade_negócio", "codcategory": "código_categoria", "criticalitem_dtprovidedregularization": "dt_regularização_item_critico", "criticalitem_blockedwallet": "carteira_bloqueada_item_critico", "criticalitem_iscritical": "é_item_critico", "codsubcategory": "código_subcategoria", "isproductdeactivated": "produto_desativado", "brandgroupcode": "código_grupo_marca", "dayswithoutsales": "dias_sem_venda", "coveragedays": "dias_cobertura", "hascoverage": "tem_cobertura" } # Renomeando as colunas do DataFrame df2 = df2.rename(columns=colunas_traduzidas) excel_path = Path("relatorio.xlsx") df2.to_excel(excel_path, index=False) # 2. Criar e salvar gráfico plot_df = df2.groupby('UF')['valor_estoque_parado'].sum().reset_index() plt.figure(figsize=(6, 4)) ax = sns.barplot(data=plot_df, x='UF', y='valor_estoque_parado', errorbar=None) ax.yaxis.set_major_formatter(mtick.StrMethodFormatter('R${x:,.2f}')) for p in ax.patches: valor = p.get_height() ax.annotate(f'R$ {valor:,.2f}', (p.get_x() + p.get_width() / 2, valor), ha='center', va='bottom', fontsize=9) plt.title("Estoque parado por UF") plt.ylabel("Valor em Reais") plt.xlabel("UF") plt.tight_layout() plt.savefig("grafico.png") plt.close() # Obtém a hora atual agora = datetime.now().time() # Define os intervalos de tempo manhã_inicio = time(5, 0) manhã_fim = time(12, 0) tarde_inicio = time(12, 1) tarde_fim = time(18, 0) # noite é dividida em dois intervalos por causa da virada do dia noite_inicio = time(18, 1) noite_fim = time(4, 59) # Verifica em qual intervalo a hora atual está if manhã_inicio <= agora <= manhã_fim: boa = "Bom dia!" elif tarde_inicio <= agora <= tarde_fim: boa = "Boa tarde!" else: boa = "Boa noite!" df2['DATA'] = agora df3 = df2.groupby('DATA', as_index=False)['valor_estoque_parado'].sum() path2 = r'C:\Users\joao.herculano\OneDrive - GRUPO GINSENG\Documentos\acompanhamentos\40D sem Venda\acompanhamento40DSV.xlsx' # Tenta abrir e escrever com append with pd.ExcelWriter(path2, mode='a', engine='openpyxl', if_sheet_exists='overlay') as writer: # Encontra a última linha preenchida book = writer.book sheet = writer.sheets['Sheet1'] if 'Sheet1' in writer.sheets else writer.book.active start_row = sheet.max_row # Escreve sem cabeçalho se não for a primeira linha df3.to_excel(writer, index=False, header=not start_row > 1, startrow=start_row) # 3. Criar e-mail com imagem embutida grafico_cid = make_msgid()[1:-1] # remove < > msg = EmailMessage() msg['From'] = remetente msg['To'] = ', '.join(destinatarios) msg['Subject'] = assunto # 4. Conteúdo do e-mail html_email = f"""

{boa}

Segue o relatório semanal de estoque improdutivo referente aos estados de Alagoas (AL), Bahia (BA), Sergipe (SE) e região de Vitória da Conquista (VDC).

Este relatório contempla exclusivamente os itens que possuem saldo em estoque, mas que estão sem vendas há mais de 40 dias.

O objetivo é trazer visibilidade para os produtos parados e reforçar a importância de ações para estimular a sua saída, contribuindo assim para a redução da cobertura de estoque e otimização dos recursos.

Contamos com o apoio de todos para análise e tratativa dos itens listados. Sugestões de ações como campanhas, transferências ou ajustes de sortimento são bem-vindas para acelerar a movimentação dos produtos.

Para mais informações, favor consultar a planilha em anexo.

Segue resumo:

""" msg.set_content("Seu e-mail precisa de um visualizador HTML.") msg.add_alternative(html_email, subtype='html') # 4. Anexar gráfico inline with open("grafico.png", 'rb') as img: msg.get_payload()[1].add_related(img.read(), 'image', 'png', cid=grafico_cid) # 5. Anexar o arquivo Excel with open(excel_path, 'rb') as f: msg.add_attachment( f.read(), maintype='application', subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet', filename=excel_path.name ) # 6. Enviar o e-mail via SMTP Outlook com configurações fornecidas with smtplib.SMTP('smtp-mail.outlook.com', 587) as smtp: smtp.ehlo() smtp.starttls(context=ssl.create_default_context()) smtp.login(remetente, senha) smtp.send_message(msg) print("E-mail enviado com sucesso.")