# -*- coding: utf-8 -*- from sqlalchemy import ( Column, Integer, String, Date, DateTime, Boolean, ForeignKey, Text ) from sqlalchemy.orm import relationship # If you face cyclic import issues, place Base here via declarative_base: # from sqlalchemy.orm import declarative_base # Base = declarative_base() from datetime import datetime from banco import Base from sqlalchemy.sql import func # server_default em AvisoGlobal # ===================================================== # TABELA EQUIPAMENTOS # ===================================================== class Equipamento(Base): __tablename__ = "equipamentos" id = Column(Integer, primary_key=True, index=True) # Identificação fpso1 = Column(String, index=True, nullable=False) fpso = Column(String, index=True, nullable=False) data_coleta = Column(String, index=True, nullable=False) # sugestão futura: Date # Responsáveis especialista = Column(String, nullable=False) conferente = Column(String, nullable=False) osm = Column(String, nullable=False) # Operacional modal = Column(String, index=True, nullable=False) quant_equip = Column(Integer, default=0) mrob = Column(String, nullable=False) # Métricas linhas_osm = Column(Integer, default=0) linhas_mrob = Column(Integer, default=0) linhas_erros = Column(Integer, default=0) # Erros erro_storekeeper = Column(String) erro_operacao = Column(String) erro_especialista = Column(String) erro_outros = Column(String) # Dados complementares inclusao_exclusao = Column(String) po = Column(String) part_number = Column(String) material = Column(String) solicitante = Column(String, nullable=False) motivo = Column(String) requisitante = Column(String, nullable=False) nota_fiscal = Column(String, nullable=False) impacto = Column(String, nullable=False) dimensao = Column(String, nullable=False) observacoes = Column(String) # Dia de inclusão dia_inclusao = Column(String, nullable=True, index=True) # Auditoria data_hora_input = Column(DateTime, default=datetime.utcnow, index=True) # ===================================================== # TABELA FPSOS # ===================================================== class FPSO(Base): __tablename__ = "fpsos" id = Column(Integer, primary_key=True) nome = Column(String, unique=True, nullable=False, index=True) ativo = Column(Boolean, default=True) data_cadastro = Column(DateTime, default=datetime.utcnow) # ===================================================== # LOG DE AUDITORIA (OFICIAL) # ===================================================== class LogAcesso(Base): __tablename__ = "log_acesso" id = Column(Integer, primary_key=True) usuario = Column(String, nullable=False, index=True) acao = Column(String, nullable=False) tabela = Column(String, nullable=True) registro_id = Column(Integer, nullable=True) data_hora = Column(DateTime, default=datetime.utcnow, index=True) # ===================================================== # USUÁRIOS # ===================================================== class Usuario(Base): __tablename__ = "usuarios" id = Column(Integer, primary_key=True) # login & segurança usuario = Column(String, unique=True, nullable=False, index=True) senha = Column(String, nullable=False) # Armazena o HASH (bcrypt) da senha # perfil & status perfil = Column(String, nullable=False) # admin | usuario | consulta ativo = Column(Boolean, default=True) # auditoria data_criacao = Column(DateTime, default=datetime.utcnow) # UI/contato nome = Column(String, nullable=True, index=True) email = Column(String, unique=True, index=True, nullable=True) # aniversário data_aniversario = Column(Date, nullable=True) # ---- Compatibilidade (alias) ---- @property def senha_hash(self) -> str | None: """ Alias compatível para códigos que esperam 'senha_hash'. Retorna o valor da coluna 'senha' (que deve conter o HASH bcrypt). Não altera o schema e não exige migration. """ return self.senha def __repr__(self) -> str: return f"" # ===================================================== # QUIZ - PERGUNTAS # ===================================================== class QuizPergunta(Base): __tablename__ = "quiz_perguntas" id = Column(Integer, primary_key=True) pergunta = Column(String, nullable=False) ativo = Column(Boolean, default=True) data_criacao = Column(DateTime, default=datetime.utcnow) respostas = relationship( "QuizResposta", back_populates="pergunta", cascade="all, delete-orphan" ) # ===================================================== # QUIZ - RESPOSTAS # ===================================================== class QuizResposta(Base): __tablename__ = "quiz_respostas" id = Column(Integer, primary_key=True) pergunta_id = Column(Integer, ForeignKey("quiz_perguntas.id"), nullable=False) texto = Column(String, nullable=False) correta = Column(Boolean, default=False) pergunta = relationship("QuizPergunta", back_populates="respostas") # ===================================================== # QUIZ - PONTUAÇÃO / RANKING # ===================================================== class QuizPontuacao(Base): __tablename__ = "quiz_pontuacoes" id = Column(Integer, primary_key=True) usuario = Column(String, nullable=False, index=True) pontos = Column(Integer, nullable=False, default=0) data = Column(DateTime, default=datetime.utcnow, index=True) # ===================================================== # VÍDEOS - CATEGORIAS # ===================================================== class VideoCategoria(Base): __tablename__ = "video_categorias" id = Column(Integer, primary_key=True) nome = Column(String, nullable=False, unique=True) ativo = Column(Boolean, default=True) data_criacao = Column(DateTime, default=datetime.utcnow) # ===================================================== # VÍDEOS # ===================================================== class Video(Base): __tablename__ = "videos" id = Column(Integer, primary_key=True) titulo = Column(String, nullable=False) descricao = Column(String) url = Column(String, nullable=False) categoria_id = Column(Integer, ForeignKey("video_categorias.id")) categoria = relationship("VideoCategoria") ativo = Column(Boolean, default=True) data_criacao = Column(DateTime, default=datetime.utcnow) # ===================================================== # CALENDÁRIO - EVENTOS / LEMBRETES # ===================================================== class EventoCalendario(Base): __tablename__ = "eventos_calendario" id = Column(Integer, primary_key=True) titulo = Column(String, nullable=False) descricao = Column(String) data_evento = Column(Date, nullable=False) data_lembrete = Column(Date) ativo = Column(Boolean, default=True) usuario_criacao = Column(String, nullable=False) data_criacao = Column(DateTime, default=datetime.utcnow) # ===================================================== # IOI-RUN - SUGESTÕES DO SISTEMA # ===================================================== class IOIRunSugestao(Base): __tablename__ = "ioirun_sugestao" id = Column(Integer, primary_key=True, index=True) # Identificação do autor usuario = Column(String, nullable=False, index=True) # Conteúdo area = Column(String, nullable=True, index=True) mensagem = Column(Text, nullable=False) # Resposta do time (admin) resposta = Column(Text, nullable=True) status = Column(String, default="pendente", nullable=False, index=True) # pendente | respondida responsavel = Column(String, nullable=True) # Auditoria data_envio = Column(DateTime, default=datetime.utcnow, nullable=False, index=True) data_resposta = Column(DateTime, nullable=True) # ===================================================== # RNC - REGISTRO DE NÃO CONFORMIDADES (FOR-SGQ-08) # ===================================================== class RNC(Base): __tablename__ = "rnc" id = Column(Integer, primary_key=True, index=True) # Identificação codigo = Column(String(20), unique=True, index=True) # ex.: RNC-2026-0001 titulo = Column(String(200), nullable=False) descricao = Column(Text, nullable=False) # Cabeçalho do formulário data_form = Column(Date, nullable=True, index=True) # Data do formulário emitente = Column(String(120), nullable=True, index=True) rnc_cliente_numero = Column(String(50), nullable=True) cliente_emitente = Column(String(120), nullable=True) area_solicitante = Column(String(120), nullable=True, index=True) area_notificada = Column(String(120), nullable=True, index=True) origem = Column(String(50), nullable=True, index=True) # Auditoria Interna/Externa/Outras # Envolvidos envolvido1_nome = Column(String(120), nullable=True) envolvido1_matricula = Column(String(50), nullable=True) envolvido1_funcao = Column(String(120), nullable=True) envolvido2_nome = Column(String(120), nullable=True) envolvido2_matricula = Column(String(50), nullable=True) envolvido2_funcao = Column(String(120), nullable=True) # Classificação tipo = Column(String(50), nullable=True) severidade = Column(String(20), nullable=True) prioridade = Column(String(20), nullable=True) # Status e prazos status = Column(String(30), default="Aberta", nullable=False, index=True) data_abertura = Column(DateTime, default=datetime.utcnow, index=True) prazo = Column(DateTime, nullable=True, index=True) encerrada_em = Column(DateTime, nullable=True, index=True) # Responsáveis responsavel = Column(String(120), nullable=True, index=True) criado_por = Column(String(120), nullable=False, index=True) # Complementares cliente = Column(String(120), nullable=True) local = Column(String(120), nullable=True) # Análise das causas metodologia = Column(String(120), nullable=True) # ex.: Ishikawa, 5 Porquês causa_raiz = Column(Text, nullable=True) # descrição da causa raiz ishikawa_json = Column(Text, nullable=True) # opcional: armazenar estrutura Ishikawa em JSON # Auditoria data_hora_input = Column(DateTime, default=datetime.utcnow, index=True) # Relacionamentos comentarios = relationship("RNCComentario", back_populates="rnc", cascade="all, delete-orphan") acoes = relationship("RNCAcaoCorretiva", back_populates="rnc", cascade="all, delete-orphan") anexos = relationship("RNCAnexo", back_populates="rnc", cascade="all, delete-orphan") # ===================================================== # RNC - COMENTÁRIOS / TIMELINE # ===================================================== class RNCComentario(Base): __tablename__ = "rnc_comentario" id = Column(Integer, primary_key=True, index=True) rnc_id = Column(Integer, ForeignKey("rnc.id"), nullable=False, index=True) data = Column(DateTime, default=datetime.utcnow, index=True) autor = Column(String(120), nullable=False, index=True) mensagem = Column(Text, nullable=False) status_novo = Column(String(30), nullable=True, index=True) prazo_novo = Column(DateTime, nullable=True, index=True) responsavel_novo = Column(String(120), nullable=True, index=True) rnc = relationship("RNC", back_populates="comentarios") # ===================================================== # RNC - AÇÕES CORRETIVAS / PREVENTIVAS # ===================================================== class RNCAcaoCorretiva(Base): __tablename__ = "rnc_acao" id = Column(Integer, primary_key=True, index=True) rnc_id = Column(Integer, ForeignKey("rnc.id"), nullable=False, index=True) descricao = Column(Text, nullable=False) responsavel = Column(String(120), nullable=True, index=True) prazo = Column(DateTime, nullable=True, index=True) status = Column(String(30), default="Planejada", nullable=False, index=True) eficacia = Column(String(30), nullable=True, index=True) conclusao_em = Column(DateTime, nullable=True, index=True) rnc = relationship("RNC", back_populates="acoes") # ===================================================== # RNC - ANEXOS # ===================================================== class RNCAnexo(Base): __tablename__ = "rnc_anexo" id = Column(Integer, primary_key=True, index=True) rnc_id = Column(Integer, ForeignKey("rnc.id"), nullable=False, index=True) nome_arquivo = Column(String(255), nullable=False) caminho = Column(String(500), nullable=False) conteudo_tipo = Column(String(120), nullable=True) enviado_por = Column(String(120), nullable=True, index=True) enviado_em = Column(DateTime, default=datetime.utcnow, index=True) rnc = relationship("RNC", back_populates="anexos") # ===================================================== # AVISO GLOBAL (banner superior) # ===================================================== class AvisoGlobal(Base): __tablename__ = "aviso_global" id = Column(Integer, primary_key=True, index=True) mensagem = Column(Text, nullable=False) # Estilo/visual bg_color = Column(String(32), default="#FFF3CD") text_color = Column(String(32), default="#664D03") largura = Column(String(16), default="100%") efeito = Column(String(16), default="marquee") velocidade = Column(Integer, default=20) font_size = Column(Integer, default=14) # Controle/estado ativo = Column(Boolean, default=True, index=True) # Auditoria created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) # ===================================================== # RECEBIMENTO — PLANILHA OFICIAL (UM REGISTRO POR LINHA) # Alinhado ao layout oficial de 37 colunas # ===================================================== class RecebimentoRegistro(Base): __tablename__ = "recebimento_registros" # PK interno + ID da planilha id = Column(Integer, primary_key=True, autoincrement=True, index=True) id_planilha = Column(Integer, nullable=True, index=True, unique=True) # ID da planilha, se houver # Datas data = Column(Date, nullable=True) data_emissao = Column(Date, nullable=True) # Horas (texto HH:MM:SS para compatibilidade) hora_chegada_portaria = Column(String(8), nullable=True) hora_chegada_ifs = Column(String(8), nullable=True) hora_saida_ifs_wms = Column(String(8), nullable=True) hora_liberacao_operacao = Column(String(8), nullable=True) hora_chegada_operacao = Column(String(8), nullable=True) hora_saida_operacao = Column(String(8), nullable=True) hora_retorno_operacao = Column(String(8), nullable=True) hora_liberacao_motorista = Column(String(8), nullable=True) # Dados principais placa_veiculo = Column(String(50), nullable=True) transportadora = Column(String(255), nullable=True) po = Column(String(60), nullable=True) incoterms = Column(String(30), nullable=True) qtd_sku = Column(Integer, nullable=True) nota_fiscal = Column(String(80), nullable=True) fornecedor = Column(String(255), nullable=True) # Bools (SIM/NÃO/N/A) quimicos = Column(Boolean, nullable=True) fds = Column(Boolean, nullable=True) repetro = Column(Boolean, nullable=True) aprovado = Column(Boolean, nullable=True) # Status/Texto natureza_operacao = Column(String(120), nullable=True) tipo_operacao = Column(String(120), nullable=True) barco = Column(String(80), nullable=True) # Campo alinhado com a coluna "DIVERGENCIA" do layout oficial divergencia = Column(String(200), nullable=True) ifs = Column(String(120), nullable=True) wms = Column(String(120), nullable=True) fotografia = Column(String(255), nullable=True) entrega = Column(String(120), nullable=True) projeto = Column(String(120), nullable=True) good_receipt = Column(String(120), nullable=True) divergencia_recebimento = Column(String(255), nullable=True) qualidade = Column(String(120), nullable=True) divergencia_qualidade = Column(String(255), nullable=True) observacao = Column(Text, nullable=True) agendamento = Column(String(120), nullable=True) responsavel = Column(String(120), nullable=True) # Novos campos (opcionais) para colunas adicionais do layout po_alt = Column(String(60), nullable=True) # mapeia "P.O" (alternativo) pn = Column(String(120), nullable=True) # mapeia "PN" lot_batch = Column(String(120), nullable=True) # mapeia "LOT BATCH" # Auditoria mínima created_by = Column(String(150), nullable=True) updated_by = Column(String(150), nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)