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 COD_UF = { "AC": "12", "AL": "27", "AM": "13", "AP": "16", "BA": "29", "CE": "23", "DF": "53", "ES": "32", "GO": "52", "MA": "21", "MG": "31", "MS": "50", "MT": "51", "PA": "15", "PB": "25", "PE": "26", "PI": "22", "PR": "41", "RJ": "33", "RN": "24", "RO": "11", "RR": "14", "RS": "43", "SC": "42", "SE": "28", "SP": "35", "TO": "17", } 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(v: Decimal) -> str: return f"{v:.2f}" @dataclass class OperacaoSTDeSTDA: """Operação de ICMS-ST, DIFAL ou antecipação do Simples Nacional.""" uf_origem: str uf_destino: str tipo: str # "ST" | "DIFAL" | "ANTEC" base_calculo: Decimal aliquota: Decimal # percentual, ex: Decimal("18") valor_imposto: Decimal valor_pago: Decimal = Decimal("0") def __post_init__(self): if self.valor_pago == Decimal("0"): self.valor_pago = self.valor_imposto class GeradorDeSTDA: VERSAO = "1.1" def __init__( self, empresa: Empresa, periodo: str, # "YYYY-MM" operacoes: list[OperacaoSTDeSTDA], uf_declarante: str = "SP", retificadora: bool = False, ): self.empresa = empresa self.periodo = periodo self.operacoes = operacoes self.uf_declarante = uf_declarante.upper() self.retificadora = retificadora def data_vencimento(self) -> date: ano, mes = map(int, self.periodo.split("-")) if mes == 12: return date(ano + 1, 1, 20) return date(ano, mes + 1, 20) @property def _ops_st(self) -> list[OperacaoSTDeSTDA]: return [o for o in self.operacoes if o.tipo == "ST"] @property def _ops_difal(self) -> list[OperacaoSTDeSTDA]: return [o for o in self.operacoes if o.tipo == "DIFAL"] @property def _ops_antec(self) -> list[OperacaoSTDeSTDA]: return [o for o in self.operacoes if o.tipo == "ANTEC"] def _total(self, ops: list[OperacaoSTDeSTDA], campo: str) -> Decimal: return sum(getattr(o, campo) for o in ops) def gerar_xml(self) -> str: ano, mes = self.periodo.split("-") ultimo_dia = self._ultimo_dia(int(ano), int(mes)) root = ET.Element("DeSTDA") root.set("versao", self.VERSAO) root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") inf = _sub(root, "infDeSTDA") _sub(inf, "CNPJ", self.empresa.cnpj) _sub(inf, "cUF", COD_UF.get(self.uf_declarante, "35")) _sub(inf, "dtInicio", f"{ano}-{mes}-01") _sub(inf, "dtFim", f"{ano}-{mes}-{ultimo_dia}") _sub(inf, "indRetificadora", "S" if self.retificadora else "N") _sub(inf, "dtEnvio", date.today().strftime("%Y-%m-%d")) self._bloco_st(root) self._bloco_difal(root) self._bloco_antec(root) tot = _sub(root, "totais") vst = self._total(self._ops_st, "valor_imposto") vdifal = self._total(self._ops_difal, "valor_imposto") vantec = self._total(self._ops_antec, "valor_imposto") vpago = self._total(self.operacoes, "valor_pago") _sub(tot, "vST_Total", _fmt(vst)) _sub(tot, "vDIFAL_Total", _fmt(vdifal)) _sub(tot, "vANTEC_Total", _fmt(vantec)) _sub(tot, "vTotal", _fmt(vst + vdifal + vantec)) _sub(tot, "vPago_Total", _fmt(vpago)) ET.indent(root, space=" ") return '\n' + ET.tostring(root, encoding="unicode") def _bloco_st(self, root: ET.Element) -> None: for op in self._ops_st: el = _sub(root, "ST") _sub(el, "UF_ST", op.uf_destino) _sub(el, "vBC_ST", _fmt(op.base_calculo)) _sub(el, "pICMS_ST", _fmt(op.aliquota)) _sub(el, "vICMS_ST", _fmt(op.valor_imposto)) _sub(el, "vICMS_ST_Pago", _fmt(op.valor_pago)) def _bloco_difal(self, root: ET.Element) -> None: for op in self._ops_difal: el = _sub(root, "DIFAL") _sub(el, "UF_DIFAL", op.uf_destino) _sub(el, "vBC_DIFAL", _fmt(op.base_calculo)) _sub(el, "pICMS_DIFAL", _fmt(op.aliquota)) _sub(el, "vICMS_DIFAL", _fmt(op.valor_imposto)) _sub(el, "vICMS_DIFAL_Pago", _fmt(op.valor_pago)) def _bloco_antec(self, root: ET.Element) -> None: for op in self._ops_antec: el = _sub(root, "ANTEC") _sub(el, "UF_ANTEC", op.uf_origem) _sub(el, "vBC_ANTEC", _fmt(op.base_calculo)) _sub(el, "pICMS_ANTEC", _fmt(op.aliquota)) _sub(el, "vICMS_ANTEC", _fmt(op.valor_imposto)) _sub(el, "vICMS_ANTEC_Pago", _fmt(op.valor_pago)) def salvar(self, diretorio: str | Path = ".") -> Path: caminho = Path(diretorio) / f"DeSTDA_{self.empresa.cnpj}_{self.periodo}.xml" caminho.parent.mkdir(parents=True, exist_ok=True) caminho.write_text(self.gerar_xml(), encoding="utf-8") return caminho def relatorio_resumo(self) -> str: vst = self._total(self._ops_st, "valor_imposto") vdifal = self._total(self._ops_difal, "valor_imposto") vantec = self._total(self._ops_antec, "valor_imposto") total = vst + vdifal + vantec venc = self.data_vencimento() linhas = [ f"DeSTDA — {self.empresa.razao_social} — Período: {self.periodo}", "=" * 60, f"ICMS-ST: R$ {vst:>12,.2f} ({len(self._ops_st)} operação/ões)", f"DIFAL: R$ {vdifal:>12,.2f} ({len(self._ops_difal)} operação/ões)", f"Antecipação: R$ {vantec:>12,.2f} ({len(self._ops_antec)} operação/ões)", "=" * 60, f"TOTAL A PAGAR: R$ {total:>12,.2f}", f"Vencimento: {venc.strftime('%d/%m/%Y')}", ] return "\n".join(linhas) @staticmethod def _ultimo_dia(ano: int, mes: int) -> str: if mes == 12: return "31" from datetime import date as d dias = (d(ano, mes + 1, 1) - d(ano, mes, 1)).days return str(dias).zfill(2)