"""Conversões e erros compartilhados pelos parsers de PDF (`parser.py`, leiaute Questor, e `parser_contabit.py`, leiaute Contabit). Vivem aqui, e não em `parser.py`, só pra evitar import circular: `parser.py` despacha para `parser_contabit.py`, que precisa das mesmas conversões. `parser.py` reexporta `ExtracaoInvalidaError`, então quem já importava de lá continua funcionando.""" from __future__ import annotations from datetime import date from decimal import Decimal, InvalidOperation class ExtracaoInvalidaError(Exception): """PDF não tem o formato esperado (cabeçalho/tabela do Balancete/DRE não encontrados) — a view converte isso numa resposta 400 genérica, mesmo padrão de `planos_saude`/`indicadores`.""" class CodigoEmpresaAusenteError(ExtracaoInvalidaError): """O leiaute Contabit não traz o código da empresa no PDF, então ele vem do nome do arquivo ("1512 - Balancete 082026.pdf"). Subclasse própria pra a view mostrar uma mensagem específica em vez da genérica de formato inválido: o PDF em si está certo, só o nome do arquivo não segue o padrão.""" def para_decimal(texto: str) -> Decimal: texto = texto.strip() # Algumas empresas têm linhas de DRE negativas no PDF com só o parêntese # de abertura ("(1.114.989,29", sem o "()" de fechamento) — confirmado # direto nos caracteres brutos do PDF (não é um artefato de # _reconstroi_linhas), então o "(" sozinho já basta pra marcar negativo. negativo = texto.startswith("(") if negativo: texto = texto[1:] if texto.endswith(")"): texto = texto[:-1] texto = texto.replace(".", "").replace(",", ".") try: valor = Decimal(texto) except InvalidOperation as exc: raise ExtracaoInvalidaError(f"Valor monetário inválido: {texto!r}") from exc return -valor if negativo else valor def para_percentual(texto: str) -> Decimal: """Mesma conversão de `para_decimal`, só removendo o "%" antes — o sinal negativo continua expresso só pelo parêntese (ex. "(7,33%)") no Questor, ou pelo "-" no Contabit, igual ao valor monetário.""" return para_decimal(texto.replace("%", "")) def para_data(texto: str) -> date: dia, mes, ano = texto.split("/") return date(int(ano), int(mes), int(dia))