publicando mudanças
This commit is contained in:
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
@@ -0,0 +1,298 @@
|
||||
import smtplib
|
||||
import ssl
|
||||
import pyodbc
|
||||
import configparser
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
from email.message import EmailMessage
|
||||
from email.utils import make_msgid
|
||||
from pathlib import Path
|
||||
from datetime import datetime, time
|
||||
|
||||
|
||||
from email.mime.image import MIMEImage
|
||||
|
||||
hoje = datetime.today().strftime("%d/%m/%Y")
|
||||
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(r"C:\Users\joao.herculano\Documents\Enviador de email\credenciais.ini")
|
||||
|
||||
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']}"
|
||||
)
|
||||
|
||||
calendario = pd.read_excel(r"C:\Users\joao.herculano\GRUPO GINSENG\Assistência Suprimentos - 2025\SUPRIMENTOS\BD_LANÇAMENTOS\BASE DE DADOS LANÇAMENTO\BOT\CICLO 9\CALENDARIO_CICLO\Ciclo_Expandido_com_Datas.xlsx")
|
||||
calendario.columns = calendario.columns.str.lower()
|
||||
calendario['date'] = pd.to_datetime(calendario['date'])
|
||||
today = pd.Timestamp("today").normalize()
|
||||
calendario = calendario[calendario['marca'] == "BOTICARIO"]
|
||||
calendario['num_ciclo'] = calendario['ciclo'].str[-2:].astype(int)
|
||||
calendario['ano_ciclo'] = calendario['ciclo'].str[0:5]
|
||||
calendario['ciclomais2'] = calendario['ano_ciclo'].astype(str) + (calendario['num_ciclo'] + 0).astype(str).str.zfill(2)
|
||||
ciclo_mais2 = calendario[calendario['date'].dt.normalize() == today]['ciclomais2'].iloc[0]
|
||||
filtered_calendario = calendario[calendario['ciclo'] == ciclo_mais2][:1].copy()
|
||||
filtered_calendario['dias_ate_fim'] = (filtered_calendario['fim ciclo'].iloc[0] - today).days
|
||||
print(filtered_calendario[['duração', 'dias_ate_fim']])
|
||||
|
||||
query = '''
|
||||
SELECT
|
||||
businessunit AS marca,
|
||||
codcategory AS categoria,
|
||||
loja_id AS pdv,
|
||||
code AS sku,
|
||||
description AS descricao_produto,
|
||||
salescurve AS curva,
|
||||
CASE WHEN promotions_description IS NULL THEN 'REGULAR' ELSE 'PROMOÇÃO' END AS tipo_promocao,
|
||||
COALESCE(stock_actual, 0) AS estoque,
|
||||
stock_intransit AS transito,
|
||||
pendingorder AS pendente,
|
||||
nextcycleprojection AS pv_mar,
|
||||
currentcyclesales AS venda_atual,
|
||||
CASE WHEN criticalitem_iscritical = 0 THEN 'REGULAR' ELSE 'CRITICO' END AS status_item,
|
||||
( COALESCE(thirdtolastcyclesales, 0) +
|
||||
COALESCE(secondtolastcyclesales, 0) +
|
||||
COALESCE(lastcyclesales, 0) +
|
||||
COALESCE(nextcycleprojection, 0)
|
||||
) / 4.0 AS media_vendas
|
||||
FROM Draft
|
||||
WHERE isproductdeactivated = 0 AND codcategory NOT IN ('SUPORTE A VENDA','EMBALAGENS')
|
||||
'''
|
||||
df = pd.read_sql(query, conn)
|
||||
conn.close()
|
||||
df.columns = df.columns.str.lower()
|
||||
|
||||
filtered_calendario.columns = filtered_calendario.columns.str.lower()
|
||||
df['ddv'] = df['pv_mar'] / filtered_calendario['duração'].values[0]
|
||||
df['estoque_seguranca'] = np.ceil((df['media_vendas']/20)*14 - (df['ddv']*14)).astype(int) #media de vendas realizada+projetada
|
||||
df['estoque_seguranca'] = np.where(df['estoque_seguranca'] <1,1,df['estoque_seguranca'])
|
||||
df['risco_ruptura'] = np.where(df['estoque_seguranca'] > df['estoque'], "SIM", "NÃO")
|
||||
df['quantidade_ruptura'] = df['estoque_seguranca'] - df['estoque']
|
||||
df['excesso'] = np.where(df['estoque'] - df['estoque_seguranca'] > 0, df['estoque'] - df['estoque_seguranca'], 0)
|
||||
|
||||
remetente = config['credenciais']['remetente']
|
||||
senha = config['credenciais']['senha']
|
||||
destinatarios = [email.strip() for email in config['email_ruptura']['destinatarios'].split(',')]
|
||||
assunto = config['email_ruptura']['assunto']
|
||||
|
||||
df_rpt = pd.read_excel(r"C:\Users\joao.herculano\Downloads\Ruptura Cliente CP GINSENG - 09.06.xlsx")
|
||||
df_rpt.columns = df_rpt.columns.str.lower()
|
||||
|
||||
|
||||
df['pdv'] = df['pdv'].astype('Int64')
|
||||
df['sku'] = df['sku'].astype('Int64')
|
||||
|
||||
df_rpt['cod_pdv'] = df_rpt['cod_pdv'].astype('Int64')
|
||||
df_rpt['sku1'] = df_rpt['sku1'].astype('Int64')
|
||||
|
||||
df = pd.merge(df, df_rpt[['sku1','cod_pdv','estoque livre?']], left_on=['pdv','sku'], right_on=['cod_pdv','sku1'], how='left')
|
||||
df.drop(columns=['cod_pdv'], inplace=True)
|
||||
|
||||
pdvs = pd.read_excel(r"C:\Users\joao.herculano\Documents\PDV_ATT.xlsx")
|
||||
pdvs.columns = pdvs.columns.str.lower()
|
||||
df['pdv'] = df['pdv'].astype('Int64')
|
||||
pdvs['pdv'] = pdvs['pdv'].astype('Int64')
|
||||
df2 = pd.merge(df, pdvs[['pdv','uf','canal','analista']], on='pdv', how='inner')
|
||||
|
||||
idx = df2.groupby(['uf', 'sku'])['excesso'].idxmax()
|
||||
pdvs_maior_excesso = df2.loc[idx, ['uf', 'sku', 'pdv', 'excesso']].copy()
|
||||
pdvs_maior_excesso.columns = ['uf', 'sku', 'pdv_maior_excesso', 'maior_excesso_por_uf']
|
||||
pdvs_maior_excesso.set_index(['uf', 'sku'], inplace=True)
|
||||
df2 = df2.join(pdvs_maior_excesso, on=['uf', 'sku'])
|
||||
df2['maior excesso na uf'] = df2['maior_excesso_por_uf'].apply(lambda x: 'não tem excesso no uf' if x == 0 else None)
|
||||
df2['maior excesso na uf'] = df2['maior excesso na uf'].combine_first(df2['pdv_maior_excesso'])
|
||||
df2['quantidade_ruptura'] = df2['quantidade_ruptura'].clip(lower=0)
|
||||
|
||||
df2.drop(columns=['pdv_maior_excesso','sku1'], inplace=True)
|
||||
|
||||
df2['estoque livre?'] = np.where(
|
||||
df2['estoque livre?'].isna() & (df2['status_item'] == 'CRITICO'),
|
||||
'Não',
|
||||
df2['estoque livre?']
|
||||
)
|
||||
|
||||
df2['estoque livre?'] = np.where(
|
||||
df2['estoque livre?'].isna() & (df2['status_item'] == 'REGULAR'),
|
||||
'Sim',
|
||||
df2['estoque livre?']
|
||||
)
|
||||
|
||||
excel_path = Path("relatorio.xlsx")
|
||||
|
||||
colunas_ordenadas = [
|
||||
'uf',
|
||||
'canal',
|
||||
'analista',
|
||||
'marca',
|
||||
'categoria',
|
||||
'pdv',
|
||||
'sku',
|
||||
'descricao_produto',
|
||||
'curva',
|
||||
'tipo_promocao',
|
||||
'estoque',
|
||||
'transito',
|
||||
'pendente',
|
||||
'pv_mar',
|
||||
'venda_atual',
|
||||
'status_item',
|
||||
'ddv',
|
||||
'estoque_seguranca',
|
||||
'risco_ruptura',
|
||||
'quantidade_ruptura',
|
||||
'excesso',
|
||||
'estoque livre?',
|
||||
'maior_excesso_por_uf',
|
||||
'maior excesso na uf'
|
||||
]
|
||||
|
||||
df2 = df2[colunas_ordenadas]
|
||||
|
||||
df3 = df2[df2['canal'] != "LJ"]
|
||||
|
||||
df3 = df3.groupby(['uf', 'canal','pdv'])['quantidade_ruptura'].sum().sort_values(ascending=False).reset_index()
|
||||
|
||||
df4 = df3.groupby('uf', as_index=False)['quantidade_ruptura'].sum()
|
||||
|
||||
df4['data'] = hoje
|
||||
|
||||
df4['data'] = pd.to_datetime(df4['data'], dayfirst=True)
|
||||
|
||||
path2 = r'C:\Users\joao.herculano\OneDrive - GRUPO GINSENG\Documentos\acompanhamentos\estudo ruptura\AcompanhamentoRuptura.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
|
||||
df4.to_excel(writer, index=False, header=not start_row > 1, startrow=start_row)
|
||||
|
||||
de_effi = pd.read_excel(r"C:\Users\joao.herculano\OneDrive - GRUPO GINSENG\Documentos\acompanhamentos\estudo ruptura\AcompanhamentoRuptura.xlsx")
|
||||
|
||||
de_effi['data'] = pd.to_datetime(de_effi['data'], errors='coerce')
|
||||
|
||||
# Step 2: Group and sort
|
||||
grouped = (
|
||||
de_effi.groupby('data')['quantidade_ruptura']
|
||||
.sum()
|
||||
.sort_index()
|
||||
)
|
||||
|
||||
# Step 3: Plot
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.plot(grouped.index, grouped.values, marker='o', linestyle='-', color='cornflowerblue', linewidth=2)
|
||||
|
||||
# Step 4: Format numbers with dot ('.') separator, no decimals
|
||||
for x, y in zip(grouped.index, grouped.values):
|
||||
label = f"{y:,.0f}".replace(",", ".") # format like 75.063
|
||||
plt.text(x, y + max(grouped.values) * 0.015, label, ha='center', fontsize=9)
|
||||
|
||||
# Step 5: Format chart
|
||||
plt.title('Evolução de Quantidade de Ruptura por Data', fontsize=14)
|
||||
plt.xlabel('Data', fontsize=8)
|
||||
plt.ylabel('Quantidade de Ruptura', fontsize=12)
|
||||
plt.grid(True, linestyle='--', alpha=0.6)
|
||||
|
||||
# ✅ Set xticks to match the actual data points
|
||||
ax = plt.gca()
|
||||
ax.set_xticks(grouped.index)
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m/%Y'))
|
||||
plt.xticks(rotation=45)
|
||||
|
||||
# Format x-axis as dd/mm/yyyy
|
||||
plt.gca().set_xticklabels([d.strftime('%d/%m/%Y') for d in grouped.index])
|
||||
plt.tight_layout()
|
||||
plt.savefig("grafico2.png")
|
||||
plt.close()
|
||||
|
||||
with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
|
||||
df2.to_excel(writer, sheet_name='Detalhado', index=False)
|
||||
df3.to_excel(writer, sheet_name='Resumo', index=False)
|
||||
|
||||
ruptura_total = df2['quantidade_ruptura'].sum()
|
||||
ruptura_por_uf_pct = (
|
||||
df2.groupby('uf')['quantidade_ruptura'].sum()
|
||||
.sort_values(ascending=True)
|
||||
.apply(lambda x: (x / ruptura_total) * 100)
|
||||
)
|
||||
print(ruptura_por_uf_pct)
|
||||
ax = ruptura_por_uf_pct.plot(kind='barh', figsize=(10, 6), color='skyblue')
|
||||
for i, v in enumerate(ruptura_por_uf_pct):
|
||||
ax.text(v + 0.3, i, f"{v:.1f}%", va='center')
|
||||
plt.xlabel('% da ruptura total')
|
||||
plt.title('Distribuição percentual de ruptura projetada por UF')
|
||||
plt.tight_layout()
|
||||
plt.savefig("grafico.png")
|
||||
plt.close()
|
||||
|
||||
agora = datetime.now().time()
|
||||
if time(5, 0) <= agora <= time(12, 0):
|
||||
boa = "Bom dia!"
|
||||
elif time(12, 1) <= agora <= time(18, 0):
|
||||
boa = "Boa tarde!"
|
||||
else:
|
||||
boa = "Boa noite!"
|
||||
|
||||
grafico_cid = make_msgid()[1:-1]
|
||||
grafico2_cid = make_msgid()[1:-1]
|
||||
msg = EmailMessage()
|
||||
msg['From'] = remetente
|
||||
msg['To'] = ', '.join(destinatarios)
|
||||
msg['Subject'] = assunto
|
||||
html_email = f"""
|
||||
<html>
|
||||
<body>
|
||||
<p>{boa}</p>
|
||||
<p>Compartilhamos o relatório de ruptura projetada, com o objetivo de monitorar os itens com maior risco de ruptura nos próximos dias.</p>
|
||||
<p><strong>Atualizamos o relatório, e agora mandaremos diariamente às 10:00 automaticamente. Pedimos paciência nessa etapa e contamos com o feedback de vocês.</strong></p>
|
||||
<p>O relatório a seguir apresenta as seguintes informações:</p>
|
||||
<ul>
|
||||
<li>Estoque atual;</li>
|
||||
<li>Trânsito;</li>
|
||||
<li>Ruptura projetada;</li>
|
||||
<li>Maior excesso por estado, assim como o PDV que o possui.</li>
|
||||
</ul>
|
||||
<p>Além disso, o material destaca as VDs com maior criticidade, permitindo uma atuação direcionada para mitigar impactos e priorizar ações de abastecimento e transferência.</p>
|
||||
<p><strong>Importante:</strong></p>
|
||||
<p>O relatório está em processo de desenvolvimento e pode sofrer mudanças futuras no layout. Ficamos à disposição para esclarecer quaisquer dúvidas.</p>
|
||||
<p>Foram adicionados ao relatório os PDVs da região de Irecê e Jacobina.</p>
|
||||
|
||||
<img src="cid:{grafico_cid}">
|
||||
<img src="cid:{grafico2_cid}">
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
msg.set_content("Seu e-mail precisa de um visualizador HTML.")
|
||||
msg.add_alternative(html_email, subtype='html')
|
||||
|
||||
with open("grafico.png", 'rb') as img:
|
||||
msg.get_payload()[1].add_related(img.read(), 'image', 'png', cid=grafico_cid)
|
||||
|
||||
with open("grafico2.png", 'rb') as img:
|
||||
msg.get_payload()[1].add_related(img.read(), 'image', 'png', cid=grafico2_cid)
|
||||
|
||||
with open(excel_path, 'rb') as f:
|
||||
msg.add_attachment(
|
||||
f.read(),
|
||||
maintype='application',
|
||||
subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
filename=excel_path.name
|
||||
)
|
||||
|
||||
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.")
|
||||
Reference in New Issue
Block a user