| |
|
| |
|
| | import streamlit as st
|
| | from datetime import datetime, date
|
| | from banco import SessionLocal
|
| | from models import Equipamento
|
| | from sqlalchemy import distinct
|
| | from log import registrar_log
|
| |
|
| |
|
| |
|
| |
|
| | MODAL_LISTA = ["", "AÉREO", "MARÍTIMO", "EXPRESSO"]
|
| | INCLUSAO_EXCLUSAO_LISTA = ["", "INCLUSÃO", "EXCLUSÃO"]
|
| | RESP_ERRO_LISTA = ["", "Sim", "Não"]
|
| | D_LISTA = ["", "D1", "D2", "D3"]
|
| |
|
| |
|
| |
|
| |
|
| | def layout_padrao(titulo: str):
|
| | st.title(titulo)
|
| | st.divider()
|
| |
|
| |
|
| |
|
| |
|
| | def _safe_index(options, value, default=0):
|
| | try:
|
| | return options.index(value)
|
| | except Exception:
|
| | return default
|
| |
|
| | def _ss_get(key, default=None):
|
| | return st.session_state.get(key, default)
|
| |
|
| | def _ss_set(key, value):
|
| | st.session_state[key] = value
|
| |
|
| |
|
| | def _parse_date_any(v):
|
| | if isinstance(v, date):
|
| | return v
|
| | if isinstance(v, datetime):
|
| | return v.date()
|
| | if isinstance(v, str):
|
| | s = v.strip()
|
| |
|
| | try:
|
| | return date.fromisoformat(s)
|
| | except Exception:
|
| | pass
|
| |
|
| | try:
|
| | return datetime.strptime(s, "%d/%m/%Y").date()
|
| | except Exception:
|
| | pass
|
| |
|
| | try:
|
| | return datetime.strptime(s, "%Y/%m/%d").date()
|
| | except Exception:
|
| | pass
|
| | return None
|
| |
|
| | def _build_prefill_from_record(r: Equipamento) -> dict:
|
| |
|
| | dc = _parse_date_any(getattr(r, "data_coleta", None))
|
| | return {
|
| | "fpso1": r.fpso1 or "",
|
| | "fpso": r.fpso or "",
|
| | "data_coleta": dc,
|
| | "especialista": r.especialista or "",
|
| | "conferente": r.conferente or "",
|
| | "osm": r.osm or "",
|
| | "modal": r.modal or "",
|
| | "quant_equip": int(r.quant_equip or 0),
|
| | "mrob": r.mrob or "",
|
| | "linhas_osm": int(r.linhas_osm or 0),
|
| | "linhas_mrob": int(r.linhas_mrob or 0),
|
| | "linhas_erros": int(r.linhas_erros or 0),
|
| | "erro_storekeeper": r.erro_storekeeper or "",
|
| | "erro_operacao": r.erro_operacao or "",
|
| | "erro_especialista": r.erro_especialista or "",
|
| | "erro_outros": r.erro_outros or "",
|
| | "inclusao_exclusao": r.inclusao_exclusao or "",
|
| | "po": r.po or "",
|
| | "part_number": r.part_number or "",
|
| | "material": r.material or "",
|
| | "solicitante": r.solicitante or "",
|
| | "requisitante": r.requisitante or "",
|
| | "nota_fiscal": r.nota_fiscal or "",
|
| | "impacto": r.impacto or "",
|
| | "dimensao": r.dimensao or "",
|
| | "motivo": getattr(r, "motivo", "") or "",
|
| | "observacoes": r.observacoes or "",
|
| | "dia_inclusao": r.dia_inclusao or "",
|
| | "_origem_id": r.id,
|
| | }
|
| |
|
| | @st.cache_data
|
| | def get_distinct(_campo):
|
| | db = SessionLocal()
|
| | try:
|
| | rows = db.query(distinct(_campo)).filter(_campo.isnot(None)).filter(_campo != "").all()
|
| | return sorted([r[0] for r in rows])
|
| | finally:
|
| | db.close()
|
| |
|
| | def get_fpsos():
|
| | return [""] + get_distinct(Equipamento.fpso)
|
| |
|
| | def get_fpsos1():
|
| | return [""] + get_distinct(Equipamento.fpso1)
|
| |
|
| | def get_notas_fiscais():
|
| | return sorted([str(x) for x in get_distinct(Equipamento.nota_fiscal)])
|
| |
|
| | def _apply_prefill_to_state(prefill: dict, debug=False):
|
| | """
|
| | Aplica o prefill uma única vez por origem (ID), antes de criar widgets.
|
| | Se o valor não existir nas listas, move para o campo '➕ Novo ...' + text_input.
|
| | """
|
| | if not prefill:
|
| | if debug: st.info("DEBUG: prefill vazio. Nada a aplicar.")
|
| | return
|
| |
|
| | origem = prefill.get("_origem_id")
|
| | if _ss_get("__prefill_applied__") == origem:
|
| | if debug: st.info(f"DEBUG: prefill já aplicado para origem {origem}.")
|
| | return
|
| |
|
| |
|
| | for k, v in prefill.items():
|
| | _ss_set(f"w_{k}", v)
|
| |
|
| |
|
| | _ss_set("w_fpso1_text", prefill.get("fpso1", ""))
|
| | _ss_set("w_fpso_text", prefill.get("fpso", ""))
|
| |
|
| |
|
| | def _norm(key, allowed):
|
| | v = _ss_get(key, "")
|
| | if v not in allowed:
|
| | _ss_set(key, allowed[0])
|
| |
|
| | _norm("w_modal", MODAL_LISTA)
|
| | _norm("w_erro_storekeeper", RESP_ERRO_LISTA)
|
| | _norm("w_erro_operacao", RESP_ERRO_LISTA)
|
| | _norm("w_erro_especialista", RESP_ERRO_LISTA)
|
| | _norm("w_erro_outros", RESP_ERRO_LISTA)
|
| | _norm("w_inclusao_exclusao", INCLUSAO_EXCLUSAO_LISTA)
|
| | _norm("w_dia_inclusao", D_LISTA)
|
| |
|
| |
|
| | _ss_set("w_quant_equip", int(_ss_get("w_quant_equip", 0) or 0))
|
| | _ss_set("w_linhas_osm", int(_ss_get("w_linhas_osm", 0) or 0))
|
| | _ss_set("w_linhas_mrob", int(_ss_get("w_linhas_mrob", 0) or 0))
|
| | _ss_set("w_linhas_erros", int(_ss_get("w_linhas_erros", 0) or 0))
|
| |
|
| |
|
| |
|
| |
|
| | _ss_set("__prefill_applied__", origem)
|
| | if debug: st.success(f"DEBUG: prefill aplicado. origem={origem}")
|
| |
|
| |
|
| |
|
| |
|
| | def main():
|
| | layout_padrao(
|
| | "📋 CONTROLE DE OSM – Inclusões / Exclusões • Visão por linha (D3 a partir das 16h)"
|
| | )
|
| |
|
| | debug = st.toggle("🔍 Modo debug", value=False)
|
| |
|
| |
|
| |
|
| |
|
| | with st.expander("🧾 Pré-carregar via Nota Fiscal (clonar como nova entrada)", expanded=False):
|
| | col1, col2, col3 = st.columns([2, 2, 1.2])
|
| | nf_digitado = col1.text_input("Digite a Nota Fiscal", key="nf_lookup")
|
| | nf_select = col2.selectbox("Ou selecione", [""] + get_notas_fiscais(), key="nf_select")
|
| | marcar_origem = col3.checkbox("Marcar origem", value=True, key="nf_marcar_origem")
|
| |
|
| | c1, c2 = st.columns(2)
|
| | buscar = c1.button("🔎 Buscar NF", key="btn_buscar_nf")
|
| | limpar = c2.button("🧹 Limpar pré-preenchimento", key="btn_limpar_prefill")
|
| |
|
| | if limpar:
|
| |
|
| | for k in list(st.session_state.keys()):
|
| | if k.startswith("w_"):
|
| | del st.session_state[k]
|
| | st.session_state.pop("form_prefill", None)
|
| | st.session_state.pop("__prefill_applied__", None)
|
| | st.session_state.pop("__nf_busca_result__", None)
|
| | st.session_state.pop("__nf_busca_opts__", None)
|
| | st.session_state.pop("sel_registro_base", None)
|
| | st.success("Pré-preenchimento limpo.")
|
| | st.rerun()
|
| |
|
| | if buscar:
|
| | nf = (nf_digitado or nf_select or "").strip()
|
| | if not nf:
|
| | st.warning("Informe ou selecione uma Nota Fiscal.")
|
| | else:
|
| | db = SessionLocal()
|
| | try:
|
| | regs = (
|
| | db.query(Equipamento)
|
| | .filter(Equipamento.nota_fiscal == nf)
|
| | .order_by(Equipamento.id.desc())
|
| | .all()
|
| | )
|
| | except Exception as e:
|
| | regs = []
|
| | st.error(f"Erro ao buscar NF: {e}")
|
| | finally:
|
| | db.close()
|
| |
|
| | if not regs:
|
| | st.info("Nenhum registro encontrado para a NF informada.")
|
| | st.session_state.pop("__nf_busca_result__", None)
|
| | st.session_state.pop("__nf_busca_opts__", None)
|
| | else:
|
| |
|
| | result = [
|
| | {"id": r.id, "data_coleta": str(r.data_coleta), "fpso": r.fpso or "—", "osm": r.osm or "—"}
|
| | for r in regs
|
| | ]
|
| | opts = [f"ID {x['id']} | {x['data_coleta']} | FPSO {x['fpso']} | OSM {x['osm']}" for x in result]
|
| |
|
| | st.session_state["__nf_busca_result__"] = result
|
| | st.session_state["__nf_busca_opts__"] = opts
|
| |
|
| | st.session_state["sel_registro_base"] = opts[0] if opts else None
|
| |
|
| |
|
| | if st.session_state.get("__nf_busca_opts__"):
|
| | escolha = st.selectbox(
|
| | "Selecione o registro base",
|
| | st.session_state["__nf_busca_opts__"],
|
| | key="sel_registro_base"
|
| | )
|
| |
|
| | if st.button("📥 Usar este registro como base", key="btn_usar_base"):
|
| | try:
|
| |
|
| | chosen_id = int(escolha.split()[1])
|
| | except Exception:
|
| | st.error("Falha ao identificar o ID do registro selecionado.")
|
| | chosen_id = None
|
| |
|
| | if chosen_id:
|
| |
|
| | db = SessionLocal()
|
| | try:
|
| | base = db.query(Equipamento).filter(Equipamento.id == chosen_id).first()
|
| | except Exception as e:
|
| | base = None
|
| | st.error(f"Erro ao buscar o registro por ID: {e}")
|
| | finally:
|
| | db.close()
|
| |
|
| | if not base:
|
| | st.error("Registro não encontrado. Tente buscar novamente.")
|
| | else:
|
| | pre = _build_prefill_from_record(base)
|
| | if marcar_origem:
|
| | pre["observacoes"] = (pre.get("observacoes") or "") + \
|
| | f" [Clonado do ID {base.id} em {datetime.now():%d/%m/%Y %H:%M}]"
|
| | st.session_state["form_prefill"] = pre
|
| | st.session_state.pop("__prefill_applied__", None)
|
| | st.success("Pré-preenchimento carregado! Role até o formulário para revisar.")
|
| | st.rerun()
|
| |
|
| |
|
| |
|
| |
|
| | prefill = _ss_get("form_prefill", {})
|
| | _apply_prefill_to_state(prefill, debug=debug)
|
| |
|
| | if debug:
|
| | with st.expander("🔎 DEBUG • session_state (parcial)"):
|
| | keys = [k for k in st.session_state.keys() if k.startswith("w_") or k in ("form_prefill", "__prefill_applied__", "__nf_busca_result__", "__nf_busca_opts__", "sel_registro_base")]
|
| | dump = {k: st.session_state[k] for k in sorted(keys)}
|
| | st.write(dump)
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | st.subheader("🚢 Identificação FPSO")
|
| | c1, c2 = st.columns(2)
|
| |
|
| | lista_fpso1 = get_fpsos1() + ["➕ Novo FPSO1"]
|
| | lista_fpso = get_fpsos() + ["➕ Novo FPSO"]
|
| |
|
| |
|
| | if _ss_get("w_fpso1", "") not in lista_fpso1:
|
| | _ss_set("w_fpso1", "➕ Novo FPSO1" if _ss_get("w_fpso1_text") else "")
|
| | if _ss_get("w_fpso", "") not in lista_fpso:
|
| | _ss_set("w_fpso", "➕ Novo FPSO" if _ss_get("w_fpso_text") else "")
|
| |
|
| | with c1:
|
| | st.selectbox(
|
| | "FPSO1 *",
|
| | lista_fpso1,
|
| | index=_safe_index(lista_fpso1, _ss_get("w_fpso1", "")),
|
| | key="w_fpso1"
|
| | )
|
| | if _ss_get("w_fpso1") == "➕ Novo FPSO1":
|
| | st.text_input("Digite o novo FPSO1 *", key="w_fpso1_text")
|
| | fpso1 = _ss_get("w_fpso1_text", "").strip()
|
| | else:
|
| | _ss_set("w_fpso1_text", _ss_get("w_fpso1"))
|
| | fpso1 = _ss_get("w_fpso1")
|
| |
|
| | with c2:
|
| | st.selectbox(
|
| | "FPSO *",
|
| | lista_fpso,
|
| | index=_safe_index(lista_fpso, _ss_get("w_fpso", "")),
|
| | key="w_fpso"
|
| | )
|
| | if _ss_get("w_fpso") == "➕ Novo FPSO":
|
| | st.text_input("Digite o novo FPSO *", key="w_fpso_text")
|
| | fpso = _ss_get("w_fpso_text", "").strip()
|
| | else:
|
| | _ss_set("w_fpso_text", _ss_get("w_fpso"))
|
| | fpso = _ss_get("w_fpso")
|
| |
|
| | st.divider()
|
| |
|
| |
|
| | st.subheader("📦 Dados Operacionais")
|
| | c1, c2, c3 = st.columns(3)
|
| |
|
| | with c1:
|
| |
|
| | st.date_input(
|
| | "Data de Coleta na ARM *",
|
| | value=_ss_get("w_data_coleta") or date.today(),
|
| | key="w_data_coleta"
|
| | )
|
| | st.text_input("Especialista Responsável *", key="w_especialista")
|
| | st.text_input("Conferente Responsável *", key="w_conferente")
|
| | st.text_input("OSM *", key="w_osm")
|
| |
|
| | with c2:
|
| | st.selectbox("Modal *", MODAL_LISTA,
|
| | index=_safe_index(MODAL_LISTA, _ss_get("w_modal", "")),
|
| | key="w_modal")
|
| | st.number_input("Quantidade de Equipamentos *", min_value=0, value=_ss_get("w_quant_equip", 0), key="w_quant_equip")
|
| | st.text_input("MROB *", key="w_mrob")
|
| |
|
| | with c3:
|
| | st.number_input("Total de Linhas OSM", min_value=0, value=_ss_get("w_linhas_osm", 0), key="w_linhas_osm")
|
| | st.number_input("Total de Linhas MROB", min_value=0, value=_ss_get("w_linhas_mrob", 0), key="w_linhas_mrob")
|
| | st.number_input("Total de Linhas com Erro", min_value=0, value=_ss_get("w_linhas_erros", 0), key="w_linhas_erros")
|
| |
|
| | st.divider()
|
| |
|
| |
|
| | st.subheader("⚠️ Análise de Erros")
|
| | c1, c2, c3, c4 = st.columns(4)
|
| | c1.selectbox("Storekeeper", RESP_ERRO_LISTA, index=_safe_index(RESP_ERRO_LISTA, _ss_get("w_erro_storekeeper", "")), key="w_erro_storekeeper")
|
| | c2.selectbox("Operação WH", RESP_ERRO_LISTA, index=_safe_index(RESP_ERRO_LISTA, _ss_get("w_erro_operacao", "")), key="w_erro_operacao")
|
| | c3.selectbox("Especialista WH", RESP_ERRO_LISTA, index=_safe_index(RESP_ERRO_LISTA, _ss_get("w_erro_especialista", "")), key="w_erro_especialista")
|
| | c4.selectbox("Outros", RESP_ERRO_LISTA, index=_safe_index(RESP_ERRO_LISTA, _ss_get("w_erro_outros", "")), key="w_erro_outros")
|
| |
|
| | st.selectbox(
|
| | "Inclusão / Exclusão",
|
| | INCLUSAO_EXCLUSAO_LISTA,
|
| | index=_safe_index(INCLUSAO_EXCLUSAO_LISTA, _ss_get("w_inclusao_exclusao", "")),
|
| | key="w_inclusao_exclusao"
|
| | )
|
| |
|
| | st.divider()
|
| |
|
| |
|
| | st.subheader("🧾 Dados Administrativos")
|
| | ca, cb, cc = st.columns(3)
|
| | with ca:
|
| | st.text_input("PO", key="w_po")
|
| | st.text_input("Part Number", key="w_part_number")
|
| | with cb:
|
| | st.text_input("Material", key="w_material")
|
| | st.text_input("Nota Fiscal *", key="w_nota_fiscal")
|
| | with cc:
|
| | st.text_input("Solicitante *", key="w_solicitante")
|
| | st.text_input("Requisitante *", key="w_requisitante")
|
| |
|
| | st.text_input("Impacto *", key="w_impacto")
|
| | st.text_input("Dimensão *", key="w_dimensao")
|
| | st.text_input("Motivo da Inclusão / Exclusão", key="w_motivo")
|
| |
|
| | st.text_area("Observações", key="w_observacoes", height=120)
|
| |
|
| |
|
| | st.subheader("🗓️ Dia de Inclusão (D)")
|
| | st.selectbox(
|
| | "Selecione o dia",
|
| | D_LISTA,
|
| | index=_safe_index(D_LISTA, _ss_get("w_dia_inclusao", "")),
|
| | key="w_dia_inclusao"
|
| | )
|
| |
|
| |
|
| |
|
| |
|
| | if st.button("💾 Salvar Registro", type="primary"):
|
| |
|
| | data_coleta = _ss_get("w_data_coleta")
|
| | especialista = _ss_get("w_especialista", "").strip()
|
| | conferente = _ss_get("w_conferente", "").strip()
|
| | osm = _ss_get("w_osm", "").strip()
|
| | modal = _ss_get("w_modal", "")
|
| | quant_equip = int(_ss_get("w_quant_equip", 0) or 0)
|
| | mrob = _ss_get("w_mrob", "").strip()
|
| | linhas_osm = int(_ss_get("w_linhas_osm", 0) or 0)
|
| | linhas_mrob = int(_ss_get("w_linhas_mrob", 0) or 0)
|
| | linhas_erros = int(_ss_get("w_linhas_erros", 0) or 0)
|
| | erro_storekeeper = _ss_get("w_erro_storekeeper", "")
|
| | erro_operacao = _ss_get("w_erro_operacao", "")
|
| | erro_especialista = _ss_get("w_erro_especialista", "")
|
| | erro_outros = _ss_get("w_erro_outros", "")
|
| | inclusao_exclusao = _ss_get("w_inclusao_exclusao", "")
|
| | po = _ss_get("w_po", "").strip()
|
| | part_number = _ss_get("w_part_number", "").strip()
|
| | material = _ss_get("w_material", "").strip()
|
| | solicitante = _ss_get("w_solicitante", "").strip()
|
| | requisitante = _ss_get("w_requisitante", "").strip()
|
| | nota_fiscal = _ss_get("w_nota_fiscal", "").strip()
|
| | impacto = _ss_get("w_impacto", "").strip()
|
| | dimensao = _ss_get("w_dimensao", "").strip()
|
| | motivo = _ss_get("w_motivo", "").strip()
|
| | observacoes = _ss_get("w_observacoes", "").strip()
|
| | dia_inclusao = _ss_get("w_dia_inclusao", "")
|
| |
|
| |
|
| | fpso1 = _ss_get("w_fpso1_text", "").strip() if _ss_get("w_fpso1") == "➕ Novo FPSO1" else _ss_get("w_fpso1", "").strip()
|
| | fpso = _ss_get("w_fpso_text", "").strip() if _ss_get("w_fpso") == "➕ Novo FPSO" else _ss_get("w_fpso", "").strip()
|
| |
|
| | obrigatorios = {
|
| | "FPSO1": fpso1,
|
| | "FPSO": fpso,
|
| | "Especialista": especialista,
|
| | "Conferente": conferente,
|
| | "OSM": osm,
|
| | "Modal": modal,
|
| | "MROB": mrob,
|
| | "Solicitante": solicitante,
|
| | "Requisitante": requisitante,
|
| | "Nota Fiscal": nota_fiscal,
|
| | "Impacto": impacto,
|
| | "Dimensão": dimensao,
|
| | "Dia de Inclusão (D)": dia_inclusao,
|
| | }
|
| | faltantes = [k for k, v in obrigatorios.items() if not v]
|
| | if faltantes:
|
| | st.error("❌ Campos obrigatórios não preenchidos: " + ", ".join(faltantes))
|
| | return
|
| |
|
| | db = SessionLocal()
|
| | try:
|
| | novo = Equipamento(
|
| | fpso1=fpso1,
|
| | fpso=fpso,
|
| | data_coleta=data_coleta,
|
| | especialista=especialista,
|
| | conferente=conferente,
|
| | osm=osm,
|
| | modal=modal,
|
| | quant_equip=quant_equip,
|
| | mrob=mrob,
|
| | linhas_osm=linhas_osm,
|
| | linhas_mrob=linhas_mrob,
|
| | linhas_erros=linhas_erros,
|
| | erro_storekeeper=erro_storekeeper,
|
| | erro_operacao=erro_operacao,
|
| | erro_especialista=erro_especialista,
|
| | erro_outros=erro_outros,
|
| | inclusao_exclusao=inclusao_exclusao,
|
| | po=po,
|
| | part_number=part_number,
|
| | material=material,
|
| | solicitante=solicitante,
|
| | motivo=motivo,
|
| | requisitante=requisitante,
|
| | nota_fiscal=nota_fiscal,
|
| | impacto=impacto,
|
| | dimensao=dimensao,
|
| | observacoes=observacoes,
|
| | dia_inclusao=dia_inclusao,
|
| | data_hora_input=datetime.now(),
|
| | )
|
| | db.add(novo)
|
| | db.commit()
|
| |
|
| | registrar_log(
|
| | usuario=_ss_get("usuario", "desconhecido"),
|
| | acao="INSERIR",
|
| | tabela="equipamentos",
|
| | registro_id=novo.id
|
| | )
|
| |
|
| | st.success("✅ Registro salvo com sucesso!")
|
| | st.rerun()
|
| |
|
| | except Exception as e:
|
| | db.rollback()
|
| | st.error(f"❌ Erro ao salvar: {e}")
|
| | finally:
|
| | db.close()
|
| |
|
| | if __name__ == "__main__":
|
| | main()
|
| |
|