from __future__ import annotations from dataclasses import dataclass, field from datetime import date from decimal import Decimal from pathlib import Path from xml.etree import ElementTree as ET from src.fiscal.entities import Empresa def _sub(pai: ET.Element, tag: str, texto: str = "") -> ET.Element: el = ET.SubElement(pai, tag) if texto: el.text = str(texto) return el def _fmt_dec(v: Decimal) -> str: return f"{v:.2f}" @dataclass class ReceitaMensalDEFIS: mes: int # 1-12 receita_bruta_total: Decimal receita_bruta_exportacao: Decimal = field(default_factory=lambda: Decimal("0")) receita_bruta_isenta: Decimal = field(default_factory=lambda: Decimal("0")) @dataclass class SocioDEFIS: cpf_cnpj: str nome: str percentual_capital: Decimal # ex: Decimal("50.00") = 50% tipo: str = "PF" # "PF" ou "PJ" pais_domicilio: str = "105" # 105=Brasil (código BACEN) @dataclass class EmpregadoDEFIS: competencia: str # "YYYY-MM" quantidade: int class GeradorDEFIS: """ Gera o arquivo DEFIS para declaração anual do Simples Nacional. Prazo: 31 de março do ano seguinte ao período de apuração. """ VERSAO = "1.0" def __init__( self, empresa: Empresa, ano_calendario: int, receitas_mensais: list[ReceitaMensalDEFIS], socios: list[SocioDEFIS], empregados: list[EmpregadoDEFIS] | None = None, retificadora: bool = False, numero_recibo_retificada: str = "", ): self.empresa = empresa self.ano_calendario = ano_calendario self.receitas_mensais = receitas_mensais self.socios = socios self.empregados = empregados or [] self.retificadora = retificadora self.numero_recibo_retificada = numero_recibo_retificada # ------------------------------------------------------------------ # Totais auxiliares # ------------------------------------------------------------------ @property def _total_receita(self) -> Decimal: return sum(r.receita_bruta_total for r in self.receitas_mensais) @property def _total_exportacao(self) -> Decimal: return sum(r.receita_bruta_exportacao for r in self.receitas_mensais) @property def _total_isenta(self) -> Decimal: return sum(r.receita_bruta_isenta for r in self.receitas_mensais) # ------------------------------------------------------------------ # Geração do XML # ------------------------------------------------------------------ def gerar_xml(self) -> str: """Gera o XML DEFIS para transmissão.""" root = ET.Element("DEFIS") root.set("versao", self.VERSAO) # Identificação do declarante ide = _sub(root, "ideDeclarante") _sub(ide, "CNPJ", self.empresa.cnpj) _sub(ide, "anoCalendario", str(self.ano_calendario)) _sub(ide, "indRetificadora", "S" if self.retificadora else "N") nrec = _sub(ide, "nrecRetificadora") if self.retificadora and self.numero_recibo_retificada: nrec.text = self.numero_recibo_retificada _sub(ide, "dtInicio", f"{self.ano_calendario}-01-01") _sub(ide, "dtFim", f"{self.ano_calendario}-12-31") inf = _sub(root, "infEmpresa") _sub(inf, "xNome", self.empresa.razao_social[:150]) _sub(inf, "xFantasia", self.empresa.nome_fantasia[:60] if self.empresa.nome_fantasia else "") _sub(inf, "fone", self.empresa.contato.telefone) _sub(inf, "email", self.empresa.contato.email) rec_bruta = _sub(root, "receitaBruta") for rm in sorted(self.receitas_mensais, key=lambda r: r.mes): mes_el = _sub(rec_bruta, "mes") _sub(mes_el, "nMes", str(rm.mes)) _sub(mes_el, "vlrRecBruta", _fmt_dec(rm.receita_bruta_total)) _sub(mes_el, "vlrRecBrutaExportacao", _fmt_dec(rm.receita_bruta_exportacao)) _sub(mes_el, "vlrRecBrutaIsenta", _fmt_dec(rm.receita_bruta_isenta)) total = _sub(root, "totalAnual") _sub(total, "vlrRecBrutaTotal", _fmt_dec(self._total_receita)) _sub(total, "vlrRecBrutaExportTotal", _fmt_dec(self._total_exportacao)) _sub(total, "vlrRecBrutaIsentaTotal", _fmt_dec(self._total_isenta)) qs = _sub(root, "quadroSocietario") for socio in self.socios: s_el = _sub(qs, "socio") _sub(s_el, "CPF_CNPJ", socio.cpf_cnpj) _sub(s_el, "xNome", socio.nome) _sub(s_el, "pctCapital", _fmt_dec(socio.percentual_capital)) _sub(s_el, "tpSocio", socio.tipo) _sub(s_el, "paisDomicilio", socio.pais_domicilio) emp_el = _sub(root, "empregados") for emp in self.empregados: comp_el = _sub(emp_el, "competencia") _sub(comp_el, "perApur", emp.competencia) _sub(comp_el, "qtdEmpregados", str(emp.quantidade)) ET.indent(root, space=" ") return '\n' + ET.tostring(root, encoding="unicode") # ------------------------------------------------------------------ # Persistência # ------------------------------------------------------------------ def salvar(self, diretorio: str | Path = ".") -> Path: """Salva como DEFIS_CNPJ_ANO.xml""" caminho = Path(diretorio) / f"DEFIS_{self.empresa.cnpj}_{self.ano_calendario}.xml" caminho.parent.mkdir(parents=True, exist_ok=True) caminho.write_text(self.gerar_xml(), encoding="utf-8") return caminho # ------------------------------------------------------------------ # Relatório # ------------------------------------------------------------------ def relatorio_resumo(self) -> str: """Resumo anual: receita total, maior mês, menores meses.""" linhas = [ f"DEFIS — {self.empresa.razao_social} — Ano: {self.ano_calendario}", "=" * 70, ] if not self.receitas_mensais: linhas.append("Sem receitas informadas.") return "\n".join(linhas) nomes_mes = [ "", "Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez", ] for rm in sorted(self.receitas_mensais, key=lambda r: r.mes): nome = nomes_mes[rm.mes] if 1 <= rm.mes <= 12 else str(rm.mes) linhas.append(f" {nome}: R$ {rm.receita_bruta_total:>12,.2f}") maior = max(self.receitas_mensais, key=lambda r: r.receita_bruta_total) menor = min(self.receitas_mensais, key=lambda r: r.receita_bruta_total) linhas += [ "=" * 70, f"Receita Total Anual: R$ {self._total_receita:>12,.2f}", f"Receita Exportação: R$ {self._total_exportacao:>12,.2f}", f"Receita Isenta: R$ {self._total_isenta:>12,.2f}", f"Maior mês: {nomes_mes[maior.mes]} — R$ {maior.receita_bruta_total:,.2f}", f"Menor mês: {nomes_mes[menor.mes]} — R$ {menor.receita_bruta_total:,.2f}", f"Prazo de entrega: 31/03/{self.ano_calendario + 1}", f"Retificadora: {'Sim' if self.retificadora else 'Não'}", ] return "\n".join(linhas)