|
|
|
|
| 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()
|
|
|