# enviar_email_excel.py import smtplib import ssl import pyodbc 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 = pyodbc.connect( f"DRIVER={{SQL Server}};" f"SERVER={config['banco']['host']},1433;" f"DATABASE=GINSENG;" f"UID={config['banco']['user']};" f"PWD={config['banco']['password']}") # 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 Draft where dayswithoutsales > 40 and stock_actual > 0 and isproductdeactivated <> 1 and currentcyclesales = 0 ''' df = pd.read_sql(query, conn) 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() hoje = datetime.today().strftime("%d/%m/%Y") # 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'] = hoje 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 às regiões de Alagoas (AL), Bahia (BA), Sergipe (SE), Vitória da Conquista (VDC) e Iracê.
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: