Speed / src /generators /ecf.py
LesterCerioli's picture
Building Speed LLM
d9d7b41
Raw
History Blame Contribute Delete
17.3 kB
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import date
from decimal import Decimal
from pathlib import Path
from typing import Optional
from src.calculators.irpj_csll import (
ResultadoCSLL,
ResultadoIRPJ,
calcular_csll_lucro_presumido,
calcular_csll_lucro_real,
calcular_irpj_lucro_presumido,
calcular_irpj_lucro_real,
)
from src.fiscal.entities import Empresa, PeriodoApuracao, RegimeTributario
from src.generators.sped_writer import ArquivoSPED, criar_registro
VERSAO_LEIAUTE = "009"
@dataclass
class DadosLucroReal:
lucro_contabil: Decimal
adicoes: Decimal = Decimal("0")
exclusoes: Decimal = Decimal("0")
compensacoes_prejuizo: Decimal = Decimal("0")
incentivos_fiscais: Decimal = Decimal("0")
csll_indedutivel: Decimal = Decimal("0")
multas_indedutíveis: Decimal = Decimal("0")
provisoes_indedutíveis: Decimal = Decimal("0")
@dataclass
class DadosLucroPresumido:
receita_venda_mercadorias: Decimal = Decimal("0")
receita_prestacao_servicos: Decimal = Decimal("0")
receita_outras_atividades: Decimal = Decimal("0")
ganhos_capital: Decimal = Decimal("0")
rendimentos_aplicacoes: Decimal = Decimal("0")
outras_receitas: Decimal = Decimal("0")
atividade_principal: str = "venda_mercadorias"
@dataclass
class ConfigECF:
versao: str = VERSAO_LEIAUTE
finalidade: str = "0" # 0=Original, 1=Retificadora
situacao_especial: str = ""
ano_referencia: int = field(default_factory=lambda: date.today().year - 1)
class GeradorECF:
"""
Gera o arquivo ECF conforme leiaute da Receita Federal.
Obrigação anual: Lucro Real, Lucro Presumido e Lucro Arbitrado.
Prazo: último dia útil de julho do ano seguinte.
"""
def __init__(
self,
empresa: Empresa,
ano: int,
dados_lr: Optional[DadosLucroReal] = None,
dados_lp: Optional[DadosLucroPresumido] = None,
cfg: Optional[ConfigECF] = None,
contador: Optional[object] = None,
):
self.empresa = empresa
self.ano = ano
self.dados_lr = dados_lr
self.dados_lp = dados_lp
self.cfg = cfg or ConfigECF(ano_referencia=ano)
self.contador = contador
self._arquivo = ArquivoSPED(f"ECF_{empresa.cnpj}_{ano}.txt")
def _bloco_0(self) -> None:
emp = self.empresa
end = emp.endereco
self._arquivo.adicionar_linha_raw(criar_registro(
"0000",
self.cfg.versao,
self.cfg.finalidade,
emp.cnpj,
emp.regime_tributario.value,
emp.razao_social,
end.uf.value,
emp.ie,
end.cod_municipio,
self.cfg.situacao_especial,
"", # nr_rec_anterior (retificadora)
date(self.ano, 1, 1),
date(self.ano, 12, 31),
"N", # ind_grandes_contribuintes
self.cfg.finalidade,
"1", # ind_pub_soc_emp
))
self._arquivo.adicionar_linha_raw(criar_registro("0001", "0"))
# 0010 - Parâmetros da Tributação
ind_sit_ini = "0" # 0=Regular
if emp.regime_tributario == RegimeTributario.LUCRO_REAL:
forma_trib = "3" # 3=Lucro Real Anual com Estimativas
elif emp.regime_tributario == RegimeTributario.LUCRO_PRESUMIDO:
forma_trib = "6" # 6=Lucro Presumido
else:
forma_trib = "6"
self._arquivo.adicionar_linha_raw(criar_registro(
"0010",
ind_sit_ini,
forma_trib,
"0", # ind_calculo_csll
"N", # ind_part_consol
"N", # ind_part_spe
"N", # ind_rep_eco
"N", # ind_ativ_imob
"0", # ind_reidi
"N", # ind_outras_deducoes
"N", # ind_prev_privada
"N", # ind_pat
"N", # ind_simples_concomitante
))
# 0020 - Parâmetros complementares
self._arquivo.adicionar_linha_raw(criar_registro(
"0020",
"0", # ind_calc_pl_pj
"0", # ind_opc_irpj_ativ_rural
"0", # ind_cnpj_prep
"0", # ind_lucro_infl
"0", # ind_ativ_preponderante
"0", # ind_sat
"1", # ind_tipo_entidade
))
if self.contador:
c = self.contador
self._arquivo.adicionar_linha_raw(criar_registro(
"0930",
c.nome,
c.cpf,
c.crc,
c.cnpj_escritorio if hasattr(c, "cnpj_escritorio") else "",
c.contato.email if hasattr(c, "contato") else "",
c.contato.telefone if hasattr(c, "contato") else "",
))
self._arquivo.adicionar_linha_raw(criar_registro(
"0990", self._arquivo.total_linhas() + 1
))
def _bloco_c(self) -> None:
"""Bloco C — Identificação e Caracterização da PJ."""
self._arquivo.adicionar_linha_raw(criar_registro("C001", "0"))
emp = self.empresa
end = emp.endereco
# C010 - Identificação
self._arquivo.adicionar_linha_raw(criar_registro(
"C010",
emp.cnpj,
emp.razao_social,
emp.nome_fantasia or emp.razao_social,
end.logradouro,
end.numero,
end.complemento,
end.bairro,
end.cep,
end.municipio,
end.uf.value,
emp.contato.telefone,
emp.contato.email,
emp.ie,
end.cod_municipio,
))
# C050 - Atividade Econômica
self._arquivo.adicionar_linha_raw(criar_registro(
"C050",
"6201-5/00", # CNAE principal (exemplo: desenvolvimento software)
"1", # ind_ativ_principal
date(self.ano, 1, 1),
date(self.ano, 12, 31),
))
self._arquivo.adicionar_linha_raw(criar_registro(
"C990", self._arquivo.total_linhas()
))
def _bloco_e(self) -> None:
"""Bloco E — IRPJ e CSLL por tipo de receita."""
self._arquivo.adicionar_linha_raw(criar_registro("E001", "0"))
# E010 - Receitas por atividade
receita_total = Decimal("0")
if self.dados_lp:
receita_total = (
self.dados_lp.receita_venda_mercadorias
+ self.dados_lp.receita_prestacao_servicos
+ self.dados_lp.receita_outras_atividades
)
elif self.dados_lr:
pass # No Lucro Real, a receita vem da contabilidade
self._arquivo.adicionar_linha_raw(criar_registro(
"E010",
receita_total, # vl_rec_brt_ativ
Decimal("0"), # vl_rec_brt_ativ_nao_fin
Decimal("0"), # vl_dev_trib
Decimal("0"), # vl_dev_nao_trib
Decimal("0"), # vl_descto_trib
Decimal("0"), # vl_descto_nao_trib
receita_total, # vl_rec_liq_ativ
Decimal("0"), # vl_rec_liq_ativ_nao_fin
))
self._arquivo.adicionar_linha_raw(criar_registro(
"E990", self._arquivo.total_linhas()
))
def _bloco_l(self, irpj: ResultadoIRPJ, csll: ResultadoCSLL) -> None:
"""Bloco L — Lucro Real (LALUR/LACS)."""
self._arquivo.adicionar_linha_raw(criar_registro("L001", "0"))
if not self.dados_lr:
self._arquivo.adicionar_linha_raw(criar_registro("L990", "2"))
return
d = self.dados_lr
# L010 - Identificação do Período
self._arquivo.adicionar_linha_raw(criar_registro(
"L010",
date(self.ano, 1, 1),
date(self.ano, 12, 31),
"0", # ind_periodo
))
# L020 - IRPJ — Lucro Real (LALUR)
self._arquivo.adicionar_linha_raw(criar_registro(
"L020",
d.lucro_contabil, # VL_REC_LIQ
d.lucro_contabil, # VL_LUC_LIQ_ANT_CSLL
d.csll_indedutivel, # VL_CSLL_IND
d.lucro_contabil + d.csll_indedutivel, # VL_LUC_LIQ
d.adicoes, # VL_TOTAL_ADICOES
d.exclusoes, # VL_TOTAL_EXCLUSOES
irpj.lucro_real_ajustado, # VL_LUC_REAL_ANT_COMP
irpj.lucro_real_ajustado - irpj.base_calculo, # VL_COMP_PREJ_PERIODO
irpj.base_calculo, # VL_LUC_REAL
))
# L030 - CSLL — Lucro Real (LACS)
self._arquivo.adicionar_linha_raw(criar_registro(
"L030",
d.lucro_contabil,
d.adicoes,
d.exclusoes,
csll.base_calculo,
csll.base_calculo,
csll.base_calculo,
))
# L100 - Adições ao Lucro Líquido
if d.multas_indedutíveis > 0:
self._arquivo.adicionar_linha_raw(criar_registro(
"L100", "054", "Multas Indedutíveis", d.multas_indedutíveis
))
if d.provisoes_indedutíveis > 0:
self._arquivo.adicionar_linha_raw(criar_registro(
"L100", "012", "Provisões Indedutíveis", d.provisoes_indedutíveis
))
self._arquivo.adicionar_linha_raw(criar_registro(
"L990", self._arquivo.total_linhas()
))
def _bloco_n(self, irpj: ResultadoIRPJ, csll: ResultadoCSLL) -> None:
"""Bloco N — Cálculo do IRPJ e da CSLL."""
self._arquivo.adicionar_linha_raw(criar_registro("N001", "0"))
# N010 — IRPJ
self._arquivo.adicionar_linha_raw(criar_registro(
"N010",
date(self.ano, 1, 1),
date(self.ano, 12, 31),
))
self._arquivo.adicionar_linha_raw(criar_registro(
"N620",
irpj.base_calculo, # VL_BC_IRPJ
irpj.valor_base, # VL_IRPJ_ALIQ_15
irpj.base_adicional, # VL_BC_ADIC_IRPJ
irpj.valor_adicional, # VL_ADIC_IRPJ
irpj.valor_irpj_total, # VL_IRPJ_DEVIDO
irpj.incentivos_fiscais, # VL_IRPJ_INCENTIVOS
Decimal("0"), # VL_IRPJ_ESTIM_SUSP
irpj.irpj_a_recolher, # VL_IRPJ_A_REC
Decimal("0"), # VL_IRPJ_A_COMP
Decimal("0"), # VL_IRPJ_A_REC_FINAL
))
self._arquivo.adicionar_linha_raw(criar_registro(
"N630",
csll.base_calculo, # VL_BC_CSLL
csll.aliquota, # ALIQ_CSLL
csll.valor_csll, # VL_CSLL_DEVIDA
Decimal("0"), # VL_CSLL_ESTIM_SUSP
csll.csll_a_recolher, # VL_CSLL_A_REC
Decimal("0"), # VL_CSLL_A_COMP
csll.csll_a_recolher, # VL_CSLL_A_REC_FINAL
))
self._arquivo.adicionar_linha_raw(criar_registro(
"N990", self._arquivo.total_linhas()
))
def _bloco_p(self, irpj: ResultadoIRPJ, csll: ResultadoCSLL) -> None:
"""Bloco P — Lucro Presumido."""
self._arquivo.adicionar_linha_raw(criar_registro("P001", "0"))
if not self.dados_lp:
self._arquivo.adicionar_linha_raw(criar_registro("P990", "2"))
return
d = self.dados_lp
# Cada trimestre do Lucro Presumido
for trim, (di, df) in enumerate([
(date(self.ano, 1, 1), date(self.ano, 3, 31)),
(date(self.ano, 4, 1), date(self.ano, 6, 30)),
(date(self.ano, 7, 1), date(self.ano, 9, 30)),
(date(self.ano, 10, 1), date(self.ano, 12, 31)),
], 1):
# P010 - Período
self._arquivo.adicionar_linha_raw(criar_registro("P010", di, df))
fator = Decimal("0.25")
rec_merc = d.receita_venda_mercadorias * fator
rec_serv = d.receita_prestacao_servicos * fator
rec_outras = d.receita_outras_atividades * fator
self._arquivo.adicionar_linha_raw(criar_registro(
"P020",
rec_merc, # receita mercadorias (8%)
rec_serv, # receita serviços (32%)
rec_outras, # outras receitas (8%)
d.ganhos_capital * fator, # ganhos de capital
d.rendimentos_aplicacoes * fator, # rendimentos de aplicações
d.outras_receitas * fator, # outras receitas financeiras
))
lp_merc = rec_merc * Decimal("0.08")
lp_serv = rec_serv * Decimal("0.32")
lp_outras = rec_outras * Decimal("0.08")
lp_total = lp_merc + lp_serv + lp_outras + d.ganhos_capital * fator + d.rendimentos_aplicacoes * fator
self._arquivo.adicionar_linha_raw(criar_registro(
"P030",
lp_merc, # vl_rec_liq_ativ_3
lp_serv, # vl_rec_liq_ativ_32
lp_outras, # vl_rec_liq_ativ_8
lp_total, # vl_lp_irpj
lp_total, # vl_bc_irpj
))
lp_csll_merc = rec_merc * Decimal("0.12")
lp_csll_serv = rec_serv * Decimal("0.32")
lp_csll_total = lp_csll_merc + lp_csll_serv + d.ganhos_capital * fator
self._arquivo.adicionar_linha_raw(criar_registro(
"P100",
lp_csll_merc,
lp_csll_serv,
lp_csll_total,
lp_csll_total,
))
self._arquivo.adicionar_linha_raw(criar_registro(
"P990", self._arquivo.total_linhas()
))
def _bloco_y(self) -> None:
"""Bloco Y — Informações Gerais."""
self._arquivo.adicionar_linha_raw(criar_registro("Y001", "0"))
# Y520 — Bens e Direitos no Exterior (simplificado)
# Y540 — Dívidas no Exterior
# Y570 — Pagamentos/Remessas ao Exterior
# Y600 — Sócios/Titulares da PJ
self._arquivo.adicionar_linha_raw(criar_registro(
"Y600",
self.empresa.cnpj,
"", # CPF (quando PF)
"SOCIO", # nome
Decimal("100"), # percentual participação
date(self.ano, 1, 1),
date(self.ano, 12, 31),
))
self._arquivo.adicionar_linha_raw(criar_registro(
"Y990", self._arquivo.total_linhas()
))
def _bloco_9(self) -> None:
self._arquivo.adicionar_linha_raw(criar_registro("9001", "0"))
qt = self._arquivo.total_linhas() + 3
self._arquivo.adicionar_linha_raw(criar_registro("9900", "0000", "1"))
self._arquivo.adicionar_linha_raw(criar_registro("9990", qt))
self._arquivo.adicionar_linha_raw(criar_registro("9999", qt + 1))
def gerar(self, diretorio: str | Path = ".") -> Path:
emp = self.empresa
dados_lr = self.dados_lr
dados_lp = self.dados_lp
if emp.regime_tributario == RegimeTributario.LUCRO_REAL and dados_lr:
irpj = calcular_irpj_lucro_real(
lucro_antes_ir=dados_lr.lucro_contabil,
adicoes=dados_lr.adicoes,
exclusoes=dados_lr.exclusoes,
compensacoes_prejuizo=dados_lr.compensacoes_prejuizo,
incentivos_fiscais=dados_lr.incentivos_fiscais,
)
csll = calcular_csll_lucro_real(
lucro_antes_csll=dados_lr.lucro_contabil,
adicoes=dados_lr.adicoes,
exclusoes=dados_lr.exclusoes,
)
else:
d = dados_lp or DadosLucroPresumido()
receita = (
d.receita_venda_mercadorias
+ d.receita_prestacao_servicos
+ d.receita_outras_atividades
)
irpj = calcular_irpj_lucro_presumido(
receita_bruta=receita,
outras_receitas=d.ganhos_capital + d.rendimentos_aplicacoes + d.outras_receitas,
atividade=d.atividade_principal,
)
csll = calcular_csll_lucro_presumido(
receita_bruta=receita,
outras_receitas=d.ganhos_capital + d.rendimentos_aplicacoes + d.outras_receitas,
atividade="comercio_industria" if d.atividade_principal == "venda_mercadorias" else "servicos_em_geral",
)
self._bloco_0()
self._bloco_c()
self._bloco_e()
if emp.regime_tributario == RegimeTributario.LUCRO_REAL:
self._bloco_l(irpj, csll)
else:
self._arquivo.adicionar_linha_raw(criar_registro("L001", "1"))
self._arquivo.adicionar_linha_raw(criar_registro("L990", "2"))
self._bloco_p(irpj, csll)
self._bloco_n(irpj, csll)
self._bloco_y()
self._bloco_9()
return self._arquivo.salvar(diretorio)