IOI-RUN / importar_excel.py
Roudrigus's picture
Upload 82 files
0f0ef8d verified
raw
history blame
12.7 kB
import streamlit as st
import pandas as pd
from io import BytesIO
from datetime import datetime, date
from banco import SessionLocal
from models import Equipamento
from utils_auditoria import registrar_log
# =====================================================
# FUNÇÕES AUXILIARES
# =====================================================
def to_date(value):
"""
Converte pandas.Timestamp ou datetime para datetime.date
Necessário para compatibilidade com SQLite
"""
if value is None or pd.isna(value):
return None
if isinstance(value, pd.Timestamp):
return value.date()
if isinstance(value, datetime):
return value.date()
if isinstance(value, date):
return value
return None
def safe_value(value):
"""
Retorna 0 se o valor for vazio/NaN, senão retorna o próprio valor.
Usado para campos obrigatórios.
"""
if value is None or pd.isna(value):
return 0
return value
# =====================================================
# MÓDULO PRINCIPAL
# =====================================================
def main():
st.title("📥 Importação de Dados via Excel")
st.markdown(
"""
Este módulo permite:
- 📄 Baixar um **modelo Excel padrão**
- ✍️ Preencher os dados offline
- 🔍 Validar antes da gravação
- 💾 Importar os registros para o banco
"""
)
# =====================================================
# 1️⃣ GERAR MODELO EXCEL
# =====================================================
st.subheader("📄 Baixar modelo Excel")
colunas = [
"fpso1", "fpso", "data_coleta", "especialista", "conferente", "osm", "modal",
"quant_equip", "mrob", "linhas_osm", "linhas_mrob", "linhas_erros",
"erro_storekeeper", "erro_operacao", "erro_especialista", "erro_outros",
"inclusao_exclusao", "po", "part_number", "material", "solicitante", "motivo",
"requisitante", "nota_fiscal", "impacto", "dimensao", "observacoes", "dia_inclusao",
]
modelo_df = pd.DataFrame(columns=colunas)
buffer = BytesIO()
with pd.ExcelWriter(buffer, engine="openpyxl") as writer:
modelo_df.to_excel(writer, index=False, sheet_name="MODELO")
buffer.seek(0)
st.download_button(
label="⬇️ Baixar modelo Excel",
data=buffer,
file_name="modelo_importacao_load.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
st.divider()
# =====================================================
# 2️⃣ UPLOAD DO ARQUIVO
# =====================================================
st.subheader("📤 Importar arquivo preenchido")
arquivo = st.file_uploader(
"Selecione o arquivo Excel",
type=["xlsx"]
)
if not arquivo:
st.info("📌 Faça o upload de um arquivo para continuar.")
return
try:
df = pd.read_excel(arquivo)
except Exception as e:
st.error(f"❌ Erro ao ler o arquivo: {e}")
return
# Garante um identificador estável para cada linha durante as edições
if "_row_id" not in df.columns:
df["_row_id"] = range(len(df))
# Salva o DF bruto na sessão para persistência entre reruns
st.session_state["df_raw"] = df.copy()
st.success("✅ Arquivo carregado com sucesso!")
# =====================================================
# 3️⃣ PRÉVIA DOS DADOS
# =====================================================
st.subheader("🔍 Prévia dos dados")
st.dataframe(df, use_container_width=True)
# =====================================================
# 4️⃣ VERIFICAÇÃO DE DUPLICIDADE (com seleção de linhas a excluir)
# =====================================================
st.subheader("🧪 Verificação de duplicidade")
st.caption("Escolha as colunas que definem a duplicidade. Em seguida, marque o checkbox **_excluir** nas linhas que **não** deseja importar.")
# Sugerimos um conjunto padrão de chaves, mas só usamos as que existem no arquivo
sugestao_chaves = [c for c in ["fpso", "osm", "po", "part_number", "nota_fiscal", "data_coleta"] if c in df.columns]
chaves = st.multiselect(
"📌 Colunas para verificação de duplicidade:",
options=list(df.columns),
default=sugestao_chaves if len(sugestao_chaves) > 0 else []
)
# Prepara um DF de trabalho com colunas auxiliares
work_df = df.copy()
work_df["_duplicado"] = False
work_df["_excluir"] = False # será editado pelo usuário
if len(chaves) == 0:
st.info("Selecione ao menos **uma** coluna para verificar duplicidade.")
# Mesmo sem duplicidade definida, permitimos marcar exclusões manuais, se quiser:
with st.expander("🔧 (Opcional) Excluir linhas manualmente mesmo sem duplicidade"):
manual_view = work_df.set_index("_row_id")[
[c for c in work_df.columns if c not in ["_duplicado"]] + ["_excluir"]
]
edited_manual = st.data_editor(
manual_view,
use_container_width=True,
num_rows="fixed",
column_config={
"_excluir": st.column_config.CheckboxColumn("Excluir da importação")
}
)
# Aplica exclusões manuais
work_df = work_df.set_index("_row_id")
work_df["_excluir"] = edited_manual["_excluir"].reindex(work_df.index).fillna(False).astype(bool)
work_df = work_df.reset_index()
else:
# Marca duplicadas com base nas chaves selecionadas
mask_dup_any = work_df.duplicated(subset=chaves, keep=False)
work_df["_duplicado"] = mask_dup_any
# Sugerimos exclusão automática das ocorrências não-primárias (o usuário pode alterar)
mask_dup_not_first = work_df.duplicated(subset=chaves, keep="first")
work_df.loc[mask_dup_not_first, "_excluir"] = True
if mask_dup_any.any():
st.warning("⚠️ Foram encontradas linhas duplicadas com base nas chaves selecionadas:")
# Mostrar somente as duplicadas para facilitar a decisão
cols_para_mostrar = chaves + [c for c in ["_duplicado", "_excluir"] if c not in chaves]
# Evita colunas repetidas mantendo ordem
seen = set()
cols_para_mostrar = [c for c in cols_para_mostrar if not (c in seen or seen.add(c))]
dup_view = work_df.loc[mask_dup_any].set_index("_row_id")[cols_para_mostrar]
edited_dup = st.data_editor(
dup_view,
use_container_width=True,
num_rows="fixed",
column_config={
"_excluir": st.column_config.CheckboxColumn("Excluir da importação"),
"_duplicado": st.column_config.CheckboxColumn("Duplicado", disabled=True)
}
)
# Mescla de volta as escolhas do usuário
work_df = work_df.set_index("_row_id")
if "_excluir" in edited_dup.columns:
work_df.loc[edited_dup.index, "_excluir"] = (
edited_dup["_excluir"].reindex(work_df.index).fillna(work_df["_excluir"]).astype(bool)
)
work_df = work_df.reset_index()
st.info(
f"📊 Totais — Linhas: {len(work_df)} | Duplicadas: {mask_dup_any.sum()} | "
f"Marcadas para excluir: {int(work_df['_excluir'].sum())}"
)
else:
st.success("✅ Nenhuma duplicidade encontrada com as chaves selecionadas.")
st.divider()
# =====================================================
# 4.1️⃣ Prévia final do que será importado + download
# =====================================================
df_para_importar = work_df[~work_df["_excluir"]].drop(columns=["_duplicado", "_excluir"], errors="ignore")
st.session_state["df_para_importar"] = df_para_importar.copy()
st.subheader("🧾 Prévia do que será importado")
st.caption("A prévia abaixo desconsidera as linhas marcadas como **_excluir**.")
st.dataframe(df_para_importar.drop(columns=["_row_id"], errors="ignore"), use_container_width=True)
# Download da prévia para conferência
buf_prev = BytesIO()
with pd.ExcelWriter(buf_prev, engine="openpyxl") as writer:
df_para_importar.drop(columns=["_row_id"], errors="ignore").to_excel(writer, index=False, sheet_name="A_IMPORTAR")
buf_prev.seek(0)
st.download_button(
"⬇️ Baixar prévia (Excel)",
data=buf_prev,
file_name="previa_a_importar.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
help="Baixe a prévia do conjunto que será gravado."
)
# =====================================================
# 5️⃣ GRAVAÇÃO NO BANCO (usa o DataFrame filtrado)
# =====================================================
st.subheader("💾 Gravar dados no banco")
col1, col2 = st.columns(2)
if col1.button("💾 Salvar registros importados"):
df_import = st.session_state.get("df_para_importar", df)
if df_import.empty:
st.error("Não há registros para importar. Revise as exclusões.")
return
with SessionLocal() as db:
try:
for _, row in df_import.iterrows():
registro = Equipamento(
fpso1=safe_value(row.get("fpso1")),
fpso=safe_value(row.get("fpso")),
data_coleta=to_date(row.get("data_coleta")),
especialista=safe_value(row.get("especialista")),
conferente=safe_value(row.get("conferente")),
osm=safe_value(row.get("osm")),
modal=safe_value(row.get("modal")),
quant_equip=int(row["quant_equip"]) if not pd.isna(row.get("quant_equip")) else 0,
mrob=safe_value(row.get("mrob")),
linhas_osm=int(row["linhas_osm"]) if not pd.isna(row.get("linhas_osm")) else 0,
linhas_mrob=int(row["linhas_mrob"]) if not pd.isna(row.get("linhas_mrob")) else 0,
linhas_erros=int(row["linhas_erros"]) if not pd.isna(row.get("linhas_erros")) else 0,
erro_storekeeper=safe_value(row.get("erro_storekeeper")),
erro_operacao=safe_value(row.get("erro_operacao")),
erro_especialista=safe_value(row.get("erro_especialista")),
erro_outros=safe_value(row.get("erro_outros")),
inclusao_exclusao=safe_value(row.get("inclusao_exclusao")),
po=safe_value(row.get("po")),
part_number=safe_value(row.get("part_number")),
material=safe_value(row.get("material")),
solicitante=safe_value(row.get("solicitante")),
motivo=safe_value(row.get("motivo")),
requisitante=safe_value(row.get("requisitante")),
nota_fiscal=safe_value(row.get("nota_fiscal")),
impacto=safe_value(row.get("impacto")),
dimensao=safe_value(row.get("dimensao")),
observacoes=safe_value(row.get("observacoes")),
dia_inclusao=safe_value(row.get("dia_inclusao")),
)
db.add(registro)
db.commit()
registrar_log(
usuario=st.session_state.get("usuario"),
acao=f"IMPORTAÇÃO EXCEL ({len(df_import)} registros) - com filtro de duplicidade",
tabela="equipamentos",
registro_id=None
)
st.success(f"🎉 Importação concluída com sucesso! {len(df_import)} registros gravados.")
except Exception as e:
db.rollback()
st.error(f"❌ Erro ao gravar no banco: {e}")
if col2.button("❌ Cancelar importação"):
st.warning("Importação cancelada pelo usuário.")
if __name__ == "__main__":
main()