portal_publico/portal_api/dashboard_contabil/conversao.py

57 lines
2.3 KiB
Python

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