arcadia-suite-sv/server/python/contabil_service.py

597 lines
25 KiB
Python

"""
Arcádia Contábil - Motor de Contabilidade
Serviço FastAPI para processamento contábil
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime, date
from decimal import Decimal
import json
import os
import re
app = FastAPI(
title="Arcádia Contábil",
description="Motor de Contabilidade - DRE, Balanço, Balancete, SPED ECD",
version="1.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=[os.getenv("APP_URL", "http://localhost:5000")],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ========== Models ==========
class ContaContabil(BaseModel):
codigo: str
descricao: str
tipo: str # ativo, passivo, patrimonio, receita, despesa
natureza: str # devedora, credora
nivel: int = 1
contaPai: Optional[int] = None
aceitaLancamento: bool = True
class Partida(BaseModel):
contaId: int
tipo: str # debito, credito
valor: float
historico: Optional[str] = None
centroCustoId: Optional[int] = None
class Lancamento(BaseModel):
dataLancamento: str
dataCompetencia: Optional[str] = None
tipoDocumento: Optional[str] = None
numeroDocumento: Optional[str] = None
historico: str
partidas: List[Partida]
class PeriodoRequest(BaseModel):
ano: int
mes: int
tenantId: Optional[int] = None
class RelatorioRequest(BaseModel):
tenantId: Optional[int] = None
dataInicio: str
dataFim: str
centroCustoId: Optional[int] = None
# ========== Plano de Contas Padrão (Simplificado) ==========
PLANO_CONTAS_PADRAO = [
# ATIVO
{"codigo": "1", "descricao": "ATIVO", "tipo": "ativo", "natureza": "devedora", "nivel": 1},
{"codigo": "1.1", "descricao": "ATIVO CIRCULANTE", "tipo": "ativo", "natureza": "devedora", "nivel": 2},
{"codigo": "1.1.01", "descricao": "Caixa e Equivalentes", "tipo": "ativo", "natureza": "devedora", "nivel": 3},
{"codigo": "1.1.01.001", "descricao": "Caixa Geral", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.1.01.002", "descricao": "Bancos Conta Movimento", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.1.02", "descricao": "Clientes", "tipo": "ativo", "natureza": "devedora", "nivel": 3},
{"codigo": "1.1.02.001", "descricao": "Duplicatas a Receber", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.1.03", "descricao": "Estoques", "tipo": "ativo", "natureza": "devedora", "nivel": 3},
{"codigo": "1.1.03.001", "descricao": "Mercadorias para Revenda", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.2", "descricao": "ATIVO NÃO CIRCULANTE", "tipo": "ativo", "natureza": "devedora", "nivel": 2},
{"codigo": "1.2.01", "descricao": "Imobilizado", "tipo": "ativo", "natureza": "devedora", "nivel": 3},
{"codigo": "1.2.01.001", "descricao": "Máquinas e Equipamentos", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.2.01.002", "descricao": "Móveis e Utensílios", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
{"codigo": "1.2.01.003", "descricao": "Veículos", "tipo": "ativo", "natureza": "devedora", "nivel": 4},
# PASSIVO
{"codigo": "2", "descricao": "PASSIVO", "tipo": "passivo", "natureza": "credora", "nivel": 1},
{"codigo": "2.1", "descricao": "PASSIVO CIRCULANTE", "tipo": "passivo", "natureza": "credora", "nivel": 2},
{"codigo": "2.1.01", "descricao": "Fornecedores", "tipo": "passivo", "natureza": "credora", "nivel": 3},
{"codigo": "2.1.01.001", "descricao": "Fornecedores Nacionais", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.02", "descricao": "Obrigações Trabalhistas", "tipo": "passivo", "natureza": "credora", "nivel": 3},
{"codigo": "2.1.02.001", "descricao": "Salários a Pagar", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.02.002", "descricao": "INSS a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.02.003", "descricao": "FGTS a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.02.004", "descricao": "IRRF a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.03", "descricao": "Obrigações Fiscais", "tipo": "passivo", "natureza": "credora", "nivel": 3},
{"codigo": "2.1.03.001", "descricao": "ICMS a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.03.002", "descricao": "PIS a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
{"codigo": "2.1.03.003", "descricao": "COFINS a Recolher", "tipo": "passivo", "natureza": "credora", "nivel": 4},
# PATRIMÔNIO LÍQUIDO
{"codigo": "3", "descricao": "PATRIMÔNIO LÍQUIDO", "tipo": "patrimonio", "natureza": "credora", "nivel": 1},
{"codigo": "3.1", "descricao": "Capital Social", "tipo": "patrimonio", "natureza": "credora", "nivel": 2},
{"codigo": "3.1.01", "descricao": "Capital Social Integralizado", "tipo": "patrimonio", "natureza": "credora", "nivel": 3},
{"codigo": "3.2", "descricao": "Lucros ou Prejuízos Acumulados", "tipo": "patrimonio", "natureza": "credora", "nivel": 2},
{"codigo": "3.2.01", "descricao": "Lucros Acumulados", "tipo": "patrimonio", "natureza": "credora", "nivel": 3},
{"codigo": "3.2.02", "descricao": "Prejuízos Acumulados", "tipo": "patrimonio", "natureza": "devedora", "nivel": 3},
# RECEITAS
{"codigo": "4", "descricao": "RECEITAS", "tipo": "receita", "natureza": "credora", "nivel": 1},
{"codigo": "4.1", "descricao": "Receita Operacional Bruta", "tipo": "receita", "natureza": "credora", "nivel": 2},
{"codigo": "4.1.01", "descricao": "Vendas de Mercadorias", "tipo": "receita", "natureza": "credora", "nivel": 3},
{"codigo": "4.1.02", "descricao": "Prestação de Serviços", "tipo": "receita", "natureza": "credora", "nivel": 3},
{"codigo": "4.2", "descricao": "Deduções da Receita", "tipo": "receita", "natureza": "devedora", "nivel": 2},
{"codigo": "4.2.01", "descricao": "Devoluções de Vendas", "tipo": "receita", "natureza": "devedora", "nivel": 3},
{"codigo": "4.2.02", "descricao": "Impostos sobre Vendas", "tipo": "receita", "natureza": "devedora", "nivel": 3},
# DESPESAS
{"codigo": "5", "descricao": "DESPESAS", "tipo": "despesa", "natureza": "devedora", "nivel": 1},
{"codigo": "5.1", "descricao": "Custos das Vendas", "tipo": "despesa", "natureza": "devedora", "nivel": 2},
{"codigo": "5.1.01", "descricao": "CMV - Custo das Mercadorias Vendidas", "tipo": "despesa", "natureza": "devedora", "nivel": 3},
{"codigo": "5.2", "descricao": "Despesas Operacionais", "tipo": "despesa", "natureza": "devedora", "nivel": 2},
{"codigo": "5.2.01", "descricao": "Despesas com Pessoal", "tipo": "despesa", "natureza": "devedora", "nivel": 3},
{"codigo": "5.2.01.001", "descricao": "Salários e Ordenados", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
{"codigo": "5.2.01.002", "descricao": "INSS Patronal", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
{"codigo": "5.2.01.003", "descricao": "FGTS", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
{"codigo": "5.2.02", "descricao": "Despesas Administrativas", "tipo": "despesa", "natureza": "devedora", "nivel": 3},
{"codigo": "5.2.02.001", "descricao": "Aluguel", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
{"codigo": "5.2.02.002", "descricao": "Energia Elétrica", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
{"codigo": "5.2.02.003", "descricao": "Telefone e Internet", "tipo": "despesa", "natureza": "devedora", "nivel": 4},
]
# ========== Endpoints ==========
@app.get("/")
async def root():
return {
"service": "Arcádia Contábil",
"version": "1.0.0",
"status": "running",
"modules": ["plano_contas", "lancamentos", "dre", "balanco", "balancete", "sped_ecd"]
}
@app.get("/health")
async def health():
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
@app.get("/plano-contas/padrao")
async def get_plano_contas_padrao():
"""Retorna o plano de contas padrão para importação"""
return {"planoContas": PLANO_CONTAS_PADRAO, "total": len(PLANO_CONTAS_PADRAO)}
@app.post("/validar-lancamento")
async def validar_lancamento(lancamento: Lancamento):
"""Valida se um lançamento está balanceado (débitos = créditos)"""
total_debitos = sum(p.valor for p in lancamento.partidas if p.tipo == "debito")
total_creditos = sum(p.valor for p in lancamento.partidas if p.tipo == "credito")
balanceado = abs(total_debitos - total_creditos) < 0.01
return {
"valido": balanceado,
"totalDebitos": round(total_debitos, 2),
"totalCreditos": round(total_creditos, 2),
"diferenca": round(total_debitos - total_creditos, 2),
"mensagem": "Lançamento balanceado" if balanceado else "Lançamento desbalanceado"
}
@app.post("/calcular-dre")
async def calcular_dre(request: RelatorioRequest):
"""Calcula a DRE (Demonstração do Resultado do Exercício)"""
# Estrutura padrão da DRE
dre = {
"periodo": {"inicio": request.dataInicio, "fim": request.dataFim},
"receitaBruta": 0,
"deducoes": {
"devolucoes": 0,
"impostos": 0,
"total": 0
},
"receitaLiquida": 0,
"custoVendas": 0,
"lucroBruto": 0,
"despesasOperacionais": {
"vendas": 0,
"administrativas": 0,
"pessoal": 0,
"total": 0
},
"resultadoOperacional": 0,
"receitasFinanceiras": 0,
"despesasFinanceiras": 0,
"resultadoAntesIR": 0,
"provisaoIR": 0,
"lucroLiquido": 0
}
return {
"dre": dre,
"observacao": "Valores serão calculados com base nos lançamentos do período"
}
@app.post("/calcular-balanco")
async def calcular_balanco(request: RelatorioRequest):
"""Calcula o Balanço Patrimonial"""
balanco = {
"dataBase": request.dataFim,
"ativo": {
"circulante": {
"caixaEquivalentes": 0,
"contasReceber": 0,
"estoques": 0,
"total": 0
},
"naoCirculante": {
"imobilizado": 0,
"intangivel": 0,
"total": 0
},
"total": 0
},
"passivo": {
"circulante": {
"fornecedores": 0,
"obrigacoesTrabalhistas": 0,
"obrigacoesFiscais": 0,
"total": 0
},
"naoCirculante": {
"emprestimos": 0,
"total": 0
},
"total": 0
},
"patrimonioLiquido": {
"capitalSocial": 0,
"reservas": 0,
"lucrosAcumulados": 0,
"total": 0
},
"totalPassivoPL": 0
}
return {
"balanco": balanco,
"equilibrado": balanco["ativo"]["total"] == balanco["totalPassivoPL"]
}
@app.post("/calcular-balancete")
async def calcular_balancete(request: PeriodoRequest):
"""Calcula o Balancete de Verificação"""
balancete = {
"periodo": {"ano": request.ano, "mes": request.mes},
"contas": [],
"totais": {
"saldoAnteriorDebito": 0,
"saldoAnteriorCredito": 0,
"movimentoDebito": 0,
"movimentoCredito": 0,
"saldoAtualDebito": 0,
"saldoAtualCredito": 0
}
}
return {"balancete": balancete}
@app.post("/gerar-razao")
async def gerar_razao(request: RelatorioRequest):
"""Gera o Livro Razão para uma conta específica"""
razao = {
"periodo": {"inicio": request.dataInicio, "fim": request.dataFim},
"conta": None,
"saldoAnterior": 0,
"lancamentos": [],
"saldoFinal": 0
}
return {"razao": razao}
@app.post("/gerar-diario")
async def gerar_diario(request: RelatorioRequest):
"""Gera o Livro Diário"""
diario = {
"periodo": {"inicio": request.dataInicio, "fim": request.dataFim},
"lancamentos": [],
"totalDebitos": 0,
"totalCreditos": 0
}
return {"diario": diario}
@app.get("/tabela-contas-referenciais")
async def get_tabela_contas_referenciais():
"""Retorna a tabela de contas referenciais do SPED ECD"""
# Tabela simplificada - L100 (Balanço) e L300 (DRE)
contas_referenciais = {
"L100": [ # Balanço Patrimonial
{"codigo": "1", "descricao": "ATIVO TOTAL"},
{"codigo": "1.01", "descricao": "Ativo Circulante"},
{"codigo": "1.02", "descricao": "Ativo Não Circulante"},
{"codigo": "2", "descricao": "PASSIVO TOTAL"},
{"codigo": "2.01", "descricao": "Passivo Circulante"},
{"codigo": "2.02", "descricao": "Passivo Não Circulante"},
{"codigo": "2.03", "descricao": "Patrimônio Líquido"},
],
"L300": [ # DRE
{"codigo": "3.01", "descricao": "Receita Líquida"},
{"codigo": "3.02", "descricao": "Custo dos Bens e Serviços Vendidos"},
{"codigo": "3.03", "descricao": "Resultado Bruto"},
{"codigo": "3.04", "descricao": "Despesas Operacionais"},
{"codigo": "3.05", "descricao": "Resultado Antes do IR"},
{"codigo": "3.06", "descricao": "Lucro Líquido do Exercício"},
]
}
return contas_referenciais
@app.post("/exportar-sped-ecd")
async def exportar_sped_ecd(request: PeriodoRequest):
"""Gera arquivo SPED ECD (Escrituração Contábil Digital)"""
# Estrutura do arquivo SPED ECD
sped_ecd = {
"registro0000": {
"tipo_escrit": "G", # G - Livro Diário Geral
"ind_sit_esp": "0", # 0 - Normal
"dt_ini": f"{request.ano}-01-01",
"dt_fin": f"{request.ano}-12-31",
},
"registroI010": {
"ind_esc": "G", # G - Diário Geral
},
"registroI050": [], # Plano de Contas
"registroI150": [], # Saldos Periódicos
"registroI200": [], # Lançamentos
"registroJ100": [], # Balanço
"registroJ150": [], # DRE
}
return {
"status": "estrutura_gerada",
"arquivo": f"SPED_ECD_{request.ano}.txt",
"estrutura": sped_ecd,
"observacao": "Arquivo será gerado com base nos lançamentos contábeis"
}
# ========== Integrações ==========
@app.post("/integrar-nfe")
async def integrar_nfe(nota: dict):
"""Gera lançamentos contábeis automáticos para NF-e"""
tipo_operacao = nota.get("tipoOperacao", "saida")
valor_total = float(nota.get("valorTotal", 0))
if tipo_operacao == "saida":
lancamento = {
"historico": f"NF-e {nota.get('numero')} - Venda de mercadorias",
"partidas": [
{"conta": "1.1.01.001", "tipo": "debito", "valor": valor_total, "descricao": "Caixa"},
{"conta": "4.1.01", "tipo": "credito", "valor": valor_total, "descricao": "Vendas"}
]
}
else:
lancamento = {
"historico": f"NF-e {nota.get('numero')} - Compra de mercadorias",
"partidas": [
{"conta": "1.1.03.001", "tipo": "debito", "valor": valor_total, "descricao": "Estoques"},
{"conta": "2.1.01.001", "tipo": "credito", "valor": valor_total, "descricao": "Fornecedores"}
]
}
return {"lancamentoSugerido": lancamento}
@app.post("/integrar-folha")
async def integrar_folha(folha: dict):
"""Gera lançamentos contábeis automáticos para folha de pagamento"""
total_bruto = float(folha.get("totalBruto", 0))
total_inss = float(folha.get("totalInss", 0))
total_irrf = float(folha.get("totalIrrf", 0))
total_fgts = float(folha.get("totalFgts", 0))
total_liquido = float(folha.get("totalLiquido", 0))
lancamento = {
"historico": f"Folha de Pagamento - Competência {folha.get('competencia')}",
"partidas": [
{"conta": "5.2.01.001", "tipo": "debito", "valor": total_bruto, "descricao": "Salários e Ordenados"},
{"conta": "5.2.01.002", "tipo": "debito", "valor": total_inss * 0.2, "descricao": "INSS Patronal"},
{"conta": "5.2.01.003", "tipo": "debito", "valor": total_fgts, "descricao": "FGTS"},
{"conta": "2.1.02.001", "tipo": "credito", "valor": total_liquido, "descricao": "Salários a Pagar"},
{"conta": "2.1.02.002", "tipo": "credito", "valor": total_inss, "descricao": "INSS a Recolher"},
{"conta": "2.1.02.003", "tipo": "credito", "valor": total_fgts, "descricao": "FGTS a Recolher"},
{"conta": "2.1.02.004", "tipo": "credito", "valor": total_irrf, "descricao": "IRRF a Recolher"},
]
}
return {"lancamentoSugerido": lancamento}
# ========== Importação de Balanço Patrimonial ==========
class BalancoImportRequest(BaseModel):
texto: str
dataBalanco: str
criarContas: bool = True
criarAbertura: bool = True
def parse_valor_contabil(valor_str: str) -> tuple:
"""Parseia valor contábil no formato brasileiro (ex: 1.234.567,89D ou 1.234.567,89C)"""
if not valor_str or valor_str.strip() == "0,00" or valor_str.strip() == "0":
return 0.0, None
valor_str = valor_str.strip()
natureza = None
if valor_str.endswith("D"):
natureza = "devedora"
valor_str = valor_str[:-1]
elif valor_str.endswith("C"):
natureza = "credora"
valor_str = valor_str[:-1]
valor_str = valor_str.replace(".", "").replace(",", ".").strip()
try:
valor = float(valor_str)
return valor, natureza
except:
return 0.0, None
def extrair_contas_balanco(texto: str) -> list:
"""Extrai estrutura hierárquica de contas do balanço patrimonial"""
linhas = texto.split("\n")
contas = []
nivel_map = {
"ATIVO": 1, "PASSIVO": 1, "PATRIMÔNIO LÍQUIDO": 1,
"ATIVO CIRCULANTE": 2, "ATIVO NÃO-CIRCULANTE": 2, "ATIVO NÃO CIRCULANTE": 2,
"PASSIVO CIRCULANTE": 2, "PASSIVO NÃO-CIRCULANTE": 2, "PASSIVO NÃO CIRCULANTE": 2,
}
tipo_map = {
"ATIVO": "ativo", "ATIVO CIRCULANTE": "ativo", "ATIVO NÃO-CIRCULANTE": "ativo",
"PASSIVO": "passivo", "PASSIVO CIRCULANTE": "passivo",
"PATRIMÔNIO LÍQUIDO": "patrimonio", "CAPITAL SOCIAL": "patrimonio",
"LUCROS OU PREJUÍZOS ACUMULADOS": "patrimonio",
}
codigo_contador = {"1": 0, "2": 0, "3": 0}
tipo_atual = "ativo"
nivel_atual = 1
codigo_pai = ""
for linha in linhas:
linha = linha.strip()
if not linha or "Folha:" in linha or "C.N.P.J" in linha or "Balanço encerrado" in linha:
continue
if "BALANÇO PATRIMONIAL" in linha or "Descrição" in linha or "______" in linha:
continue
if "Assinado digitalmente" in linha or "SERGIO SZWEC" in linha or "ELOIR" in linha:
continue
partes = linha.split()
if len(partes) < 2:
continue
descricao_partes = []
valor_2024 = None
valor_2023 = None
for i, parte in enumerate(partes):
if any(c.isdigit() for c in parte) and ("," in parte or parte.endswith("D") or parte.endswith("C")):
valor_2024, natureza = parse_valor_contabil(parte)
if i + 1 < len(partes):
valor_2023, _ = parse_valor_contabil(partes[i + 1])
break
else:
descricao_partes.append(parte)
descricao = " ".join(descricao_partes).strip()
if not descricao or len(descricao) < 2:
continue
descricao_upper = descricao.upper()
if descricao_upper in ["ATIVO", "PASSIVO"]:
tipo_atual = "ativo" if descricao_upper == "ATIVO" else "passivo"
nivel_atual = 1
base_codigo = "1" if tipo_atual == "ativo" else "2"
codigo = base_codigo
elif "PATRIMÔNIO LÍQUIDO" in descricao_upper:
tipo_atual = "patrimonio"
nivel_atual = 1
codigo = "3"
elif descricao_upper in nivel_map:
nivel_atual = nivel_map[descricao_upper]
if tipo_atual == "ativo":
codigo = f"1.{codigo_contador['1'] + 1}"
codigo_contador['1'] += 1
elif tipo_atual == "passivo":
codigo = f"2.{codigo_contador['2'] + 1}"
codigo_contador['2'] += 1
else:
codigo = f"3.{codigo_contador['3'] + 1}"
codigo_contador['3'] += 1
else:
espacos = len(linha) - len(linha.lstrip())
nivel = min(max(espacos // 2, 1), 5)
if nivel <= 2:
nivel = 3
base = "1" if tipo_atual == "ativo" else ("2" if tipo_atual == "passivo" else "3")
codigo_contador[base] = codigo_contador.get(base, 0) + 1
codigo = f"{base}.{nivel}.{codigo_contador[base]:02d}"
natureza_conta = "devedora" if tipo_atual == "ativo" else "credora"
if "(-)" in descricao or "DEPRECIAÇÃO" in descricao_upper or "AMORTIZAÇÃO" in descricao_upper:
natureza_conta = "credora" if tipo_atual == "ativo" else "devedora"
if valor_2024 and valor_2024 > 0:
contas.append({
"codigo": codigo,
"descricao": descricao,
"tipo": tipo_atual,
"natureza": natureza_conta,
"nivel": nivel_atual if descricao_upper in nivel_map else 4,
"saldo2024": valor_2024,
"saldo2023": valor_2023 or 0,
"aceitaLancamento": True
})
return contas
@app.post("/importar-balanco")
async def importar_balanco(request: BalancoImportRequest):
"""Importa balanço patrimonial e gera plano de contas + lançamentos de abertura"""
try:
contas = extrair_contas_balanco(request.texto)
if not contas:
texto_upper = request.texto.upper()
has_structure = any(x in texto_upper for x in ["ATIVO", "PASSIVO", "PATRIMÔNIO"])
has_values = bool(re.search(r'\d{1,3}(?:\.\d{3})*,\d{2}', request.texto))
error_msg = "Não foi possível extrair contas do balanço. "
if not has_structure:
error_msg += "O texto não contém estrutura de balanço (ATIVO, PASSIVO, PATRIMÔNIO LÍQUIDO). "
if not has_values:
error_msg += "Valores não encontrados no formato brasileiro (ex: 1.234,56). "
error_msg += "Verifique se o PDF contém os dados do balanço patrimonial."
raise HTTPException(status_code=400, detail=error_msg)
plano_contas = []
lancamentos_abertura = []
for conta in contas:
plano_contas.append({
"codigo": conta["codigo"],
"descricao": conta["descricao"],
"tipo": conta["tipo"],
"natureza": conta["natureza"],
"nivel": conta["nivel"],
"aceitaLancamento": conta["aceitaLancamento"],
"status": "ativo"
})
if request.criarAbertura and conta.get("saldo2024", 0) > 0:
tipo_lancamento = "debito" if conta["natureza"] == "devedora" else "credito"
lancamentos_abertura.append({
"conta": conta["codigo"],
"descricao": conta["descricao"],
"tipo": tipo_lancamento,
"valor": conta["saldo2024"],
"historico": f"Saldo de abertura - {conta['descricao']}"
})
total_debito = sum(l["valor"] for l in lancamentos_abertura if l["tipo"] == "debito")
total_credito = sum(l["valor"] for l in lancamentos_abertura if l["tipo"] == "credito")
return {
"success": True,
"planoContas": plano_contas,
"lancamentosAbertura": lancamentos_abertura,
"resumo": {
"totalContas": len(plano_contas),
"totalLancamentos": len(lancamentos_abertura),
"totalDebito": total_debito,
"totalCredito": total_credito,
"diferenca": abs(total_debito - total_credito)
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("CONTABIL_PORT", 8003))
uvicorn.run(app, host="0.0.0.0", port=port)