import pandas as pd
import dash
from dash import dcc, html, dash_table
from dash.dependencies import Input, Output, State
from sqlalchemy import create_engine, text
# Criação da instância do Dash
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# Configuração da conexão com o banco de dados Protheus12 usando SQLAlchemy
server = 'xxxxxxxxx'
database = 'xxxxxxxxxxx'
username = 'xxxxxxxxxxxx'
password = 'xxxxxxxxxxxxxx'
driver = 'ODBC Driver 18 for SQL Server'
# Conectando ao banco de dados usando SQLAlchemy
connection_string = (
f'mssql+pyodbc://{username}:{password}@{server}/{database}?'
f'driver={driver}&Encrypt=yes&TrustServerCertificate=yes'
)
engine = create_engine(connection_string)
# Definindo a consulta SQL
query = '''
SELECT
CASE
WHEN (SELECT TOP 1 [Aprovador] FROM VW_MN_PEDIDOS_COMPRA_EM_APROVACAO
WHERE [Pedido] = PD.[Num.PC]) IS NOT NULL
THEN (SELECT TOP 1 [Aprovador] FROM VW_MN_PEDIDOS_COMPRA_EM_APROVACAO
WHERE [Pedido] = PD.[Num.PC])
WHEN (SELECT TOP 1 [Aprovador] FROM VW_MN_PEDIDOS_COMPRA_EM_APROVACAO
WHERE [Pedido] = PD.[Num.PC]) IS NULL AND PD.[Num.PC] IS NOT NULL THEN 'Aprovado'
ELSE 'Em Cotação'
END [Status],
PD.[Num.PC], SC.[NÚM SC] AS [Numero da SC],
ISNULL(PD.[Solicitante], SC.[SOLICITANTE]) AS [Solicitante],
ISNULL(PD.[Observacoes], SC.[OBSERVAÇÃO]) AS [Observacoes],
PD.[Fornecedor], PD.[Loja], PD.[Razao Social],
ISNULL(PD.[Produto], SC.[PRODUTO]) AS [Produto],
ISNULL(PD.[Descricao], SC.[DESCRIÇÃO]) AS [Descricao],
CONVERT(VARCHAR, CONVERT(DATE, ISNULL(PD.[Emissao], SC.[EMISSÃO])), 103) AS [Emissao],
[Entrega],
ISNULL(PD.[UM], SC.[UOM]) AS [UM],
ISNULL(PD.[Quantidade], SC.[QUANT ORIGINAL]) AS [Qtd. Original],
[Prc Unitario], [Vlr.Total], [Qtd.Entregue],
ISNULL(PD.[Quant.Receber], SC.[SALDO COMPRAR]) AS [Saldo],
[Saldo Receber],
ISNULL(PD.[Tipo], SC.[TIPO]) AS [TIPO],
ISNULL(PD.[C Custo], SC.[C Custo]) AS [C Custo],
ISNULL(PD.[DEPARTAMENTO], SC.[DEPARTAMENTO]) AS [Departamento],
[Cta Contabil], [Moeda], [Tx Dolar]
FROM VW_MN_PEDIDOS_COMPRA_EM_ABERTO AS PD
FULL JOIN VW_MN_SC_COMPRA_EM_ABERTO AS SC
ON PD.[Item SC] = SC.[ITEM SC] AND PD.[Numero da SC] = SC.[NÚM SC]
AND SC.[EMISSÃO] >= '20230101'
'''
# Executando a consulta e armazenando o resultado em um DataFrame do pandas
with engine.connect() as connection:
df = pd.read_sql(text(query), connection)
df['Emissao'] = pd.to_datetime(df['Emissao'])
# Limpar a coluna 'C Custo' antes de converter para inteiros
df['C Custo'] = df['C Custo'].astype(str).str.strip() # Remove espaços em branco
df['C Custo'] = pd.to_numeric(df['C Custo'], errors='coerce') # Converte para numérico, transformando valores inválidos em NaN
df.dropna(subset=['C Custo'], inplace=True) # Remove linhas com NaN na coluna 'C Custo'
df['C Custo'] = df['C Custo'].astype(int) # Converte para inteiros
df = df.sort_values(by=['Emissao'], ascending=[False])
df['Emissao'] = df["Emissao"].dt.strftime('%d/%m/%Y')
# Mapeamento de C Custo para seus nomes ou descrições
ccusto_mapping = {
'1011': 'PRESIDENCIA',
'1012': 'CORPORATIVO',
'1021': 'PLANEJAMENTO',
'1022': 'GERENCIA - PLANEJAMENTO',
'1023': 'PLANEJAMENTO',
'1031': 'T.I.',
'1032': 'GERENCIA - TI',
'1033': 'TI AM',
'1041': 'RECRUTAMENTO E SELECAO',
'1042': 'MANUTENCAO',
'1043': 'SERVICOS GERAIS',
'1044': 'DP. JURIDICO',
'1045': 'ADMINISTRACAO DE PESSOAL AM',
'1046': 'ADMINISTRACAO DE PESSOAL',
'1047': 'APRENDIZ',
'1048': 'LOJA GAMA',
'1049': 'GERENCIA - RH',
'1051': 'ADM - LOGISTICA',
'1052': 'ESTOQUE',
'1053': 'IMPORTACAO',
'1054': 'POS VENDA - SAC',
'1055': 'POS VENDAS - POSTOS',
'1056': 'ASSIST TECNICA',
'1057': 'GERENCIA - LOGISTICA',
'1058': 'RETRABALHO - LOGISTICA',
'1059': 'LOGISTICA - SC',
'1061': 'COMPRAS',
'1071': 'TRIBUTOS',
'1072': 'REC/DESP FINANCEIRA',
'1081': 'CONTAS A PAGAR',
'1082': 'CREDITO E COBRANCA',
'1083': 'GERENCIA - FINANCEIRO',
'1091': 'CONTROLADORIA',
'1092': 'APOIO ADM E LEGAL',
'1093': 'CONSULTOR EXTERNO',
'1094': 'GERENCIA - CONTROLADORIA',
'1095': 'CONTROLADORIA AM',
'1111': 'DIRETORIA COMERCIAL',
'1112': 'NORTE/ NORDESTE',
'1113': 'ELETRO',
'1114': 'BELEZA',
'1115': 'HUMMER',
'1116': 'DIRETORIA COMERCIAL',
'1117': 'TELEVENDAS',
'1118': 'GERENCIA - ADM DE VENDAS',
'1119': 'ADM. VENDAS',
'1121': 'MARKETING',
'1122': 'IGI',
'1123': 'EDUCACAO PROFISSIONAL',
'1124': 'TRADE MARKETING (EXTERNO)',
'1125': 'MKT - PROFISSIONAL',
'1126': 'LOJA GAMA',
'1127': 'CERTIFICACOES',
'1128': 'TRADE MARKETING INTERNO',
'1129': 'RETRABALHO MKT',
'1131': 'CONTAS ESPECIAIS',
'1132': 'GERENCIA - CONT ESPECIAIS',
'1133': 'NOVOS NEGOCIOS',
'1134': 'INTELIGENCIA DE MERCADO',
'1135': 'GERENCIA COMERCIAL',
'1136': 'E-COMMERCE',
'1141': 'REGIONAIS',
'1142': 'TRADE MARKETING (INTERNO)',
'1143': 'COMERCIAL - PROFISSIONAL',
'1144': 'CONTAS-CHAVE 4',
'1145': 'REGIONAL BA+SE+NORTE',
'1146': 'REGIONAL NORDESTE',
'1147': 'REGIONAL RJ+ES',
'1148': 'REGIONAL SP INTERIOR',
'1149': 'REGIONAL SP CAPITAL',
'1150': 'REGIONAL MG+TO',
'1151': 'BRAUN',
'1152': 'REGIONAL SUL',
'1153': 'CANAL BELEZA',
'1161': 'TRANSITORIA AM',
'1200': 'CONTROLADORIA',
'1201': 'ALMOXARIFADO',
'1202': 'QUALIDADE',
'1203': 'ENGENHARIA DE PROCESSOS',
'1204': 'MANUTENCAO',
'1205': 'COMPRAS',
'1206': 'FERRAMENTARIA',
'1207': 'TECNOLOGIA DA INFORMACAO',
'1208': 'ENGENHARIA DE PRODUTOS',
'1209': 'GERENCIA INDUSTRIAL',
'1210': 'RECURSOS HUMANOS',
'1211': 'PCPM',
'1212': 'SESMT',
'1213': 'FACILITIES',
'1214': 'MOD IQ PERFETTO',
'1215': 'MOI IQ PERFETTO',
'1216': 'PCP',
'1217': 'FERRAMENTARIA',
'1218': 'PRODUCAO INJ PLASTICA',
'1219': 'PRODUTO ACABADO PRANCHA',
'1220': 'SALA DE PINTURA',
'1221': 'PRODUTO ACABADO SECADOR',
'1222': 'SETOR TAMPOGRAFIA',
'1223': 'MOI PRANCHA',
'1224': 'MOI SECADOR',
'1225': 'SETOR CALEFATOR',
'1226': 'PLACA SECADOR',
'1227': 'SUBCONJUNTO PATIM',
'1228': 'SETOR ESTAMPARIA',
'1229': 'SETOR PINTURA',
'1230': 'MOI INJ PLASTICA',
'1231': 'MOI TAMPOGRAFIA',
'1232': 'MOI PINTURA',
'1233': 'TECNOLOGIA DA INFORMACAO',
'1234': 'JOVEM APRENDIZ',
'1300': 'PRODUCAO INJ PLASTICA',
'1301': 'PRODUTO ACABADO PRANCHA',
'1302': 'PRODUTO ACABADO SECADOR',
'1303': 'TAMPOGRAFIA',
'1304': 'MOI PRANCHA',
'1305': 'MOI SECADOR',
'1306': 'ESTAMPARIA',
'1307': 'PINTURA',
'1308': 'MOI INJ PLASTICA',
'1309': 'MOI TAMPOGRAFIA',
'1310': 'MOI PINTURA',
'1311': 'CALEFATOR',
'1312': 'MOI CALEFATOR',
'1313': 'DESP COM CONDOMINIO',
'1314': 'MOI IQ PERFETTO',
'1511': 'MDS - MEN DRY SAVING (BARBEADORES)',
'1512': 'FEHR-FEMALE ELETRIC HAIR REMOVAL -DEPILD',
'1513': 'ETB-ELETRIC TOOTH BRUSHES-ESCV DENTE ELE',
'1514': 'IRONS (PRANCHAS)',
'1515': 'HAIR DRYERS (SECADORES)',
'1516': 'HOUSE HOLD APPLIANCES (ELETRODOMESTICOS)',
'1521': 'OTHER EXPENSES (OUTRAS DESPESAS)',
'1601': 'ISSUE - OUTRAS DESPESAS',
'2000': 'AUTO SERVICOS',
'2001': 'CATALOGOS PREMIOS',
'2002': 'DEPARTAMENTOS',
'2003': 'ESPECIALIDADE',
'2004': 'FARMACIA',
'2005': 'INDIRETO PROFISSIONAL',
'2006': 'GAMA ITALY DO BRASIL',
'2007': 'GM DUNA INVERSIONES',
'2008': 'DUNA ENTERPRISES S.L',
'2009': 'SAFERY S.A',
'2010': 'DISTRI BEAUTY S.A',
'2011': 'ARIMEX IMPORTADORA S.A',
'2012': 'GAMA CHILE S.A',
'2013': 'ITALIAN BEAUTY S.A.C',
'2014': 'ITALIAN GROUP S.A DE C.V',
'2015':
'GA.MA COLOMBIA LTDA',
'7902': 'GIF PA PRANCHA',
'7903': 'GIF PA SECADOR',
'7904': 'GIF CALEFATOR',
'7907': 'GIF ESTAMPARIA',
'7910': 'GIF PLACA SECADOR',
'7911': 'GIF SALA DE PINTURA',
'7912': 'GIF TAMPOGRAFIA',
'7913': 'GIF INJ PLASTICA',
'7914': 'GIF CALEFATOR',
'7915': 'GIF MOLA',
'8902': 'MOI PA PRANCHA',
'8903': 'MOI PA SECADOR',
'8904': 'MOI CALEFATOR',
'8907': 'MOI ESTAMPARIA',
'8910': 'MOI PLACA SECADOR',
'8911': 'MOI SALA DE PINTURA',
'8912': 'MOI TAMPOGRAFIA',
'8913': 'MOI INJ PLASTICA',
'8914': 'MOI CALEFATOR',
'8915': 'MOI MOLA',
'9901': 'PROD. S.A. PCS INJETADAS',
'9902': 'PRODUTO ACABADO PRANCHA',
'9903': 'PRODUTO ACABADOR SECADOR',
'9904': 'MONTAGEM CALEFATOR',
'9905': 'INJECAO PLASTICA',
'9906': 'PATIN',
'9907': 'ESTAMPARIA',
'9908': 'PROD. S.A. PATIN',
'9909': 'PROD. S.A. PLACA',
'9910': 'MONTAGEM PLACA',
'9911': 'MOD SALA DE PINTURA',
'9912': 'MOD TAMPOGRAFIA',
'9913': 'INJECAO PLASTICA',
'9914': 'MOD CALEFATOR',
'9915': 'MOD MOLA',
}
# Dicionário de mapeamento de C Custo para senhas
ccusto_passwords = {
'1012': '9584', '1031': '6934', '1044': '9546', '1046': '9271',
'1051': '9164', '1082': '3964', '1091': '2591', '1125': '3964',
'1129': '5612', '1133': '7894', '1136': '5294', '1141': '1265',
'1153': '9856', '1200': '7486', '1201': '9632', '1202': '7412',
'1203': '8521', '1204': '6412', '1206': '5973', '1207': '8462',
'1208': '5971', '1210': '3519', '1212': '2587', '1213': '2589',
'1300': '9651', '1301': '7541', '1302': '4567', '1307': '7823',
'1311': '0235'
}
# Função para criar o dropdown de "C Custo" com nome e número
def create_ccusto_dropdown(ccusto_list):
return dcc.Dropdown(
id='ccusto-dropdown',
options=[{'label': f'{c} - {ccusto_mapping.get(str(c), "Desconhecido")}', 'value': c} for c in ccusto_list],
placeholder='Selecione o C Custo',
style={'width': '50%', 'marginBottom': '20px'}
)
# Layout do login
login_layout = html.Div(style={'display': 'flex', 'alignItems': 'center', 'justifyContent': 'center', 'height': '100vh', 'backgroundColor': '#f0f0f0'}, children=[
html.Div(style={'width': '300px', 'padding': '20px', 'borderRadius': '10px', 'boxShadow': '0px 4px 8px rgba(0, 0, 0, 0.1)', 'backgroundColor': '#ffffff'}, children=[
html.H2('Login', style={'textAlign': 'center', 'color': '#003f7f'}),
dcc.Input(id='username', type='text', placeholder='C Custo', style={'width': '95%', 'padding': '10px', 'marginTop': '10px', 'marginBottom': '10px', 'borderRadius': '5px', 'border': '1px solid #ddd'}),
dcc.Input(id='password', type='password', placeholder='Senha', style={'width': '95%', 'padding': '10px', 'marginBottom': '10px', 'borderRadius': '5px', 'border': '1px solid #ddd'}),
html.Button('Entrar', id='login-button', style={'width': '100%', 'padding': '10px', 'borderRadius': '5px', 'backgroundColor': '#003f7f', 'color': 'white', 'border': 'none', 'cursor': 'pointer'})
])
])
# Layout do dashboard
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content', children=login_layout)
])
# Função para renderizar o dashboard
def render_dashboard(filtered_df, ccusto_options):
return html.Div([
html.H1('Pedidos de Compra em Aberto', style={'fontSize': '18px', 'color': '#003f7f', 'textAlign': 'center'}),
create_ccusto_dropdown(ccusto_options),
dash_table.DataTable(
id='table',
columns=[
{"name": "Num.PC", "id": "Num.PC"},
{"name": "Numero da SC", "id": "Numero da SC"},
{"name": "Status", "id": "Status"},
{"name": "Solicitante", "id": "Solicitante"},
{"name": "Fornecedor", "id": "Fornecedor"},
{"name": "Loja", "id": "Loja"},
{"name": "Razao Social", "id": "Razao Social"}
],
data=filtered_df[['Num.PC', 'Numero da SC', 'Status', 'Solicitante', 'Fornecedor', 'Loja', 'Razao Social']].drop_duplicates().to_dict('records'),
page_size=10,
style_table={'overflowX': 'auto', 'border': '1px solid #ddd'},
style_cell={'height': 'auto', 'minWidth': '100px', 'width': '100px', 'maxWidth': '100px',
'whiteSpace': 'normal', 'textAlign': 'left', 'border': '1px solid #ddd', 'fontSize': '10px', 'backgroundColor': '#ffffff'},
style_header={'border': '1px solid #ddd', 'backgroundColor': '#003f7f', 'color': 'white', 'fontSize': '12px'},
row_selectable='single', selected_rows=[],
style_data_conditional=[
{
'if': {'column_id': 'Status', 'filter_query': '{Status} = "Aprovado"'},
'backgroundColor': '#d4edda',
'color': 'black',
},
{
'if': {'column_id': 'Status', 'filter_query': '{Status} != "Aprovado"'},
'backgroundColor': '#fff3cd',
'color': 'black',
},
]
),
html.Div(id='details-section', style={'marginTop': '10px', 'border': '1px solid #ddd', 'padding': '5px',
'borderRadius': '5px', 'boxShadow': '0px 4px 8px rgba(0, 0, 0, 0.1)',
'fontSize': '12px', 'backgroundColor': '#ffffff'})
])
# Callback para autenticação e redirecionamento
@app.callback(
Output('page-content', 'children'),
[Input('login-button', 'n_clicks')],
[State('username', 'value'), State('password', 'value')]
)
def login(n_clicks, username, password):
if n_clicks is None:
return login_layout
if ((username == 'Master' and password == '1234') or
(username == '1206' and password == '5973')):
ccusto_options = [int(c) for c in df['C Custo'].astype(str).unique()]
return render_dashboard(df, ccusto_options)
if username in ccusto_passwords and password == ccusto_passwords[username]:
ccusto = int(username)
filtered_df = df[df['C Custo'].astype(int) == ccusto]
ccusto_options = [ccusto]
return render_dashboard(filtered_df, ccusto_options)
return html.Div("Credenciais inválidas. Tente novamente.", style={'textAlign': 'center', 'color': 'red'})
# Callback para atualizar a tabela com base no dropdown de C Custo
@app.callback(
Output('table', 'data'),
[Input('ccusto-dropdown', 'value')]
)
def update_table(ccusto_selected):
if ccusto_selected is None:
return []
if ccusto_selected == 'Master':
return df[['Num.PC', 'Status', 'Numero da SC', 'Solicitante', 'Fornecedor', 'Loja', 'Razao Social']].drop_duplicates().to_dict('records')
return df[df['C Custo'] == ccusto_selected][['Num.PC', 'Status', 'Numero da SC', 'Solicitante', 'Fornecedor', 'Loja', 'Razao Social']].drop_duplicates().to_dict('records')
# Callback para atualizar os detalhes do pedido selecionado
@app.callback(
Output('details-section', 'children'),
[Input('table', 'selected_rows')],
[State('table', 'data')]
)
def update_details(selected_rows, data):
if not selected_rows:
return html.Div("Selecione um pedido para ver os detalhes.")
selected_row_index = selected_rows[0]
selected_num_pc = data[selected_row_index]['Num.PC']
pedido_detalhado = df[df['Num.PC'] == selected_num_pc]
if pedido_detalhado.empty:
selected_sc = data[selected_row_index]['Numero da SC']
pedido_detalhado = df[df['Numero da SC'] == selected_sc]
if pedido_detalhado.empty:
return html.Div(f"Nenhum detalhe encontrado para Num.PC: {selected_num_pc}.")
columns_to_show = [col for col in pedido_detalhado.columns if col not in ['Status', 'Solicitante', 'Fornecedor', 'Loja', 'Razao Social']]
if 'Status' not in columns_to_show:
columns_to_show.append('Status')
return html.Table(style={'width': '100%', 'borderCollapse': 'collapse', 'border': '1px solid #ddd'}, children=[
html.Thead(html.Tr([html.Th(col, style={'border': '1px solid #ddd', 'padding': '8px', 'backgroundColor': '#003f7f', 'color': 'white', 'fontSize': '10px'})
for col in columns_to_show])),
html.Tbody([html.Tr([html.Td(pedido_detalhado.iloc[0][col], style={'border': '1px solid #ddd', 'padding': '8px', 'fontSize': '10px'})
for col in columns_to_show])])
])
# Configuração do servidor para tornar o aplicativo acessível em outras máquinas
if __name__ == '__main__':
app.run_server(host='127.0.0.1', port=8050, debug=False)