121 lines
4.7 KiB
Python
121 lines
4.7 KiB
Python
"""Leitura das duas planilhas mensais do Indicador de Desempenho (Fiscontábil):
|
|
"Serviços Tareffa" (uma linha por serviço executado) e "Honorários Por
|
|
Cliente" (honorário líquido por empresa). Pacote Python puro, sem depender
|
|
do ORM — mesmo espírito de `portal_api.planos_saude`."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
from dataclasses import dataclass
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
import openpyxl
|
|
|
|
SERVICOS_BALANCETE = {"Balancete Mensal", "Balancete Bimestral"}
|
|
SERVICO_LIBERACAO_FISCAL = "Liberação Fiscal"
|
|
SERVICO_CONCILIACAO_FINANCEIRA = "Conciliação Financeira"
|
|
SERVICOS_INDICADOR = SERVICOS_BALANCETE | {SERVICO_LIBERACAO_FISCAL, SERVICO_CONCILIACAO_FINANCEIRA}
|
|
|
|
STATUS_CANCELADO = "Cancelado"
|
|
|
|
|
|
@dataclass
|
|
class LinhaTareffa:
|
|
codigo_empresa: str
|
|
razao_social: str
|
|
responsavel: str
|
|
gerente: str
|
|
servico: str
|
|
status: str
|
|
data_meta: dt.date | None
|
|
data_entrega: dt.date | None
|
|
|
|
|
|
def _texto(valor: object) -> str:
|
|
return str(valor).strip() if valor is not None else ""
|
|
|
|
|
|
def _codigo(valor: object) -> str:
|
|
"""Normaliza um código de empresa pra string, tratando o caso comum de o
|
|
Excel/openpyxl devolver um float "inteiro" (ex.: 792.0) — sem isso, o
|
|
mesmo código viria como "792" de uma planilha e "792.0" de outra, e a
|
|
junção por código nunca bateria."""
|
|
if isinstance(valor, float) and valor.is_integer():
|
|
return str(int(valor))
|
|
return _texto(valor)
|
|
|
|
|
|
def _para_data(valor: object) -> dt.date | None:
|
|
if isinstance(valor, dt.datetime):
|
|
return valor.date()
|
|
if isinstance(valor, dt.date):
|
|
return valor
|
|
return None
|
|
|
|
|
|
def _indice_cabecalho(planilha) -> dict[str, int]:
|
|
primeira_linha = next(planilha.iter_rows(min_row=1, max_row=1, values_only=True))
|
|
return {_texto(nome): posicao for posicao, nome in enumerate(primeira_linha)}
|
|
|
|
|
|
def le_servicos_tareffa(caminho: str) -> list[LinhaTareffa]:
|
|
"""Lê só as linhas relevantes pro Indicador de Desempenho do Fiscontábil
|
|
(Balancete/Liberação Fiscal/Conciliação Financeira) — outros serviços
|
|
(ex.: departamentos "Rocket"/"Condomínio"/"Pessoa Física IRPF", fora do
|
|
escopo desta v1) são ignorados aqui, não em cada consumidor."""
|
|
workbook = openpyxl.load_workbook(caminho, data_only=True, read_only=True)
|
|
try:
|
|
planilha = workbook.worksheets[0]
|
|
indice = _indice_cabecalho(planilha)
|
|
|
|
linhas: list[LinhaTareffa] = []
|
|
for linha_planilha in planilha.iter_rows(min_row=2, values_only=True):
|
|
servico = _texto(linha_planilha[indice["servico"]])
|
|
if servico not in SERVICOS_INDICADOR:
|
|
continue
|
|
linhas.append(
|
|
LinhaTareffa(
|
|
codigo_empresa=_codigo(linha_planilha[indice["codigoempresa"]]),
|
|
razao_social=_texto(linha_planilha[indice["razaosocial"]]),
|
|
responsavel=_texto(linha_planilha[indice["responsavel"]]),
|
|
gerente=_texto(linha_planilha[indice["gerente"]]),
|
|
servico=servico,
|
|
status=_texto(linha_planilha[indice["status"]]),
|
|
data_meta=_para_data(linha_planilha[indice["datameta"]]),
|
|
data_entrega=_para_data(linha_planilha[indice["dataentrega"]]),
|
|
)
|
|
)
|
|
return linhas
|
|
finally:
|
|
# Modo read_only do openpyxl mantém o arquivo aberto (memory-mapped)
|
|
# até `close()` ser chamado — sem isso, o Windows bloqueia qualquer
|
|
# tentativa de apagar o upload depois (ex.: excluir a apuração).
|
|
workbook.close()
|
|
|
|
|
|
def le_honorarios(caminho: str) -> dict[str, Decimal]:
|
|
"""Devolve {código da empresa: honorário}, somando quando o mesmo código
|
|
aparecer em mais de uma linha (mais de um serviço faturado pro mesmo
|
|
cliente no mês)."""
|
|
workbook = openpyxl.load_workbook(caminho, data_only=True, read_only=True)
|
|
try:
|
|
planilha = workbook.worksheets[0]
|
|
indice = _indice_cabecalho(planilha)
|
|
coluna_codigo = indice["Cód"]
|
|
coluna_valor = indice["Total Líquido"]
|
|
|
|
honorarios: dict[str, Decimal] = {}
|
|
for linha_planilha in planilha.iter_rows(min_row=2, values_only=True):
|
|
codigo = _codigo(linha_planilha[coluna_codigo])
|
|
if not codigo:
|
|
continue
|
|
valor_bruto = linha_planilha[coluna_valor]
|
|
try:
|
|
valor = Decimal(str(valor_bruto)) if valor_bruto is not None else Decimal("0")
|
|
except InvalidOperation:
|
|
valor = Decimal("0")
|
|
honorarios[codigo] = honorarios.get(codigo, Decimal("0")) + valor
|
|
return honorarios
|
|
finally:
|
|
workbook.close()
|