Speed / src /generators /esocial.py
LesterCerioli's picture
Building Speed LLM
d9d7b41
Raw
History Blame Contribute Delete
15.9 kB
from __future__ import annotations
import re
from dataclasses import dataclass, field
from datetime import date, datetime
from decimal import Decimal
from pathlib import Path
from typing import Optional
from xml.etree import ElementTree as ET
NS_ESOCIAL = "http://www.esocial.gov.br/schema/evt"
VERSAO = "S-1.2"
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, casas: int = 2) -> str:
return f"{v:.{casas}f}"
@dataclass
class Trabalhador:
cpf: str
nome: str
data_nascimento: date
sexo: str = "M" # M=Masculino, F=Feminino
raca_cor: str = "1" # 1=Branca
grau_instrucao: str = "07" # 07=Fundamental Completo
pis_pasep: str = ""
endereco: str = ""
municipio: str = ""
uf: str = "SP"
cep: str = ""
email: str = ""
telefone: str = ""
nacionalidade: str = "10" # 10=Brasileiro
@dataclass
class VinculoEmpregaticio:
trabalhador: Trabalhador
matricula: str
data_admissao: date
tipo_regime: str = "1" # 1=CLT
tipo_vinculo: str = "10" # 10=Empregado
cargo: str = ""
funcao: str = ""
salario_base: Decimal = Decimal("0")
tipo_salario: str = "11" # 11=Mensal
categoria: str = "101" # 101=Empregado geral
cbo: str = "2521-05"
jornada_semanal: Decimal = Decimal("44")
tipo_jornada: str = "2" # 2=12x36
fgts_optante: bool = True
data_opcao_fgts: Optional[date] = None
@dataclass
class EventoRescisao:
trabalhador: Trabalhador
matricula: str
data_desligamento: date
motivo: str = "01" # 01=Rescisão sem justa causa
data_aviso_previo: Optional[date] = None
verba_saldo_salario: Decimal = Decimal("0")
verba_ferias_prop: Decimal = Decimal("0")
verba_13_prop: Decimal = Decimal("0")
verba_aviso_previo: Decimal = Decimal("0")
verba_multa_fgts: Decimal = Decimal("0")
inss_retido: Decimal = Decimal("0")
irrf_retido: Decimal = Decimal("0")
@dataclass
class RubricaFolha:
codigo: str
descricao: str
natureza: str # 1=Vencimento, 2=Desconto, 3=Informativo
tipo_incidencia_cp: str = "11"
tipo_incidencia_irrf: str = "11"
tipo_incidencia_fgts: str = "11"
@dataclass
class ItemFolha:
rubrica: RubricaFolha
valor: Decimal
quantidade: Decimal = Decimal("0")
@dataclass
class FolhaPagamento:
trabalhador: Trabalhador
matricula: str
periodo: str # YYYY-MM
itens: list[ItemFolha] = field(default_factory=list)
base_cp: Decimal = Decimal("0")
base_irrf: Decimal = Decimal("0")
base_fgts: Decimal = Decimal("0")
valor_cp: Decimal = Decimal("0")
valor_irrf: Decimal = Decimal("0")
valor_fgts: Decimal = Decimal("0")
@property
def total_vencimentos(self) -> Decimal:
return sum(i.valor for i in self.itens if i.rubrica.natureza == "1")
@property
def total_descontos(self) -> Decimal:
return sum(i.valor for i in self.itens if i.rubrica.natureza == "2")
@property
def liquido(self) -> Decimal:
return self.total_vencimentos - self.total_descontos
class GeradorESocial:
"""
Gera eventos do e-Social em XML.
Eventos implementados:
- S-1000: Informações do empregador/contribuinte
- S-1010: Tabela de rubricas
- S-2200: Cadastramento inicial de vínculo (admissão)
- S-2206: Alteração de contrato de trabalho
- S-2299: Desligamento
- S-1200: Remuneração do trabalhador (folha de pagamento)
- S-1299: Fechamento dos eventos periódicos
"""
def __init__(self, cnpj: str, ambiente: str = "2"):
self.cnpj = re.sub(r"\D", "", cnpj)
self.ambiente = ambiente
def _id_evento(self, nr: int) -> str:
ts = datetime.now().strftime("%Y%m%d%H%M%S")
return f"ID1{self.cnpj}{ts}{str(nr).zfill(5)}"
def _ide_empregador(self, pai: ET.Element) -> None:
ide = _sub(pai, "ideEmpregador")
_sub(ide, "tpInsc", "1") # 1=CNPJ
_sub(ide, "nrInsc", self.cnpj[:8])
def _ide_evento(self, pai: ET.Element, nr: int, periodo: str = "") -> None:
ide = _sub(pai, "ideEvento")
_sub(ide, "indRetif", "1") # 1=Original
_sub(ide, "nrRec", "")
if periodo:
_sub(ide, "perApur", periodo)
_sub(ide, "tpAmb", self.ambiente)
_sub(ide, "procEmi", "1")
_sub(ide, "verProc", "1.0")
def gerar_s1000(self, razao_social: str, data_inicio: date) -> str:
"""S-1000: Informações do empregador/contribuinte."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
evt = _sub(root, "evtInfoEmpregador")
evt.set("Id", self._id_evento(1))
self._ide_evento(evt, 1)
self._ide_empregador(evt)
info_emp = _sub(evt, "infoEmpregador")
inclusao = _sub(info_emp, "inclusao")
ide_emp = _sub(inclusao, "ideEmpregador")
_sub(ide_emp, "tpInsc", "1")
_sub(ide_emp, "nrInsc", self.cnpj)
dados_emp = _sub(inclusao, "dadosEmpregador")
_sub(dados_emp, "razaoSocial", razao_social[:100])
_sub(dados_emp, "classTrib", "99")
_sub(dados_emp, "natJurid", "2062") # Sociedade Empresária Limitada
_sub(dados_emp, "indOptRegEletron", "0")
_sub(dados_emp, "cnpjOrgaoPublico", "")
_sub(dados_emp, "dtIni", data_inicio.strftime("%Y-%m-%d"))
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def gerar_s1010(self, rubricas: list[RubricaFolha]) -> str:
"""S-1010: Tabela de rubricas da folha de pagamento."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
evt = _sub(root, "evtTabRubrica")
evt.set("Id", self._id_evento(10))
self._ide_evento(evt, 10)
self._ide_empregador(evt)
for rub in rubricas:
rubrica = _sub(evt, "rubrica")
ide = _sub(rubrica, "ideRubrica")
_sub(ide, "codRubr", rub.codigo)
_sub(ide, "ideTabRubr", "S") # S=Própria
_sub(ide, "dtIniValid", date.today().strftime("%Y-%m"))
dados = _sub(rubrica, "dadosRubrica")
_sub(dados, "dscRubr", rub.descricao[:100])
_sub(dados, "natRubr", rub.natureza)
_sub(dados, "tpRubr", "1") # 1=Proventos/descontos
_sub(dados, "codIncCP", rub.tipo_incidencia_cp)
_sub(dados, "codIncIRRF", rub.tipo_incidencia_irrf)
_sub(dados, "codIncFGTS", rub.tipo_incidencia_fgts)
_sub(dados, "codIncSIND", "00")
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def gerar_s2200(self, vinculo: VinculoEmpregaticio) -> str:
"""S-2200: Cadastramento inicial do vínculo — admissão."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
evt = _sub(root, "evtAdmissao")
evt.set("Id", self._id_evento(2200))
self._ide_evento(evt, 2200)
self._ide_empregador(evt)
trab = vinculo.trabalhador
ide_vinculo = _sub(evt, "ideVinculo")
_sub(ide_vinculo, "cpfTrab", re.sub(r"\D", "", trab.cpf))
_sub(ide_vinculo, "matricula", vinculo.matricula)
dados_trab = _sub(evt, "trabalhador")
_sub(dados_trab, "nmTrab", trab.nome[:70])
_sub(dados_trab, "sexo", trab.sexo)
_sub(dados_trab, "racaCor", trab.raca_cor)
_sub(dados_trab, "estCiv", "0") # 0=Não informado
_sub(dados_trab, "grauInstr", trab.grau_instrucao)
_sub(dados_trab, "nmSoc", "")
nascimento = _sub(dados_trab, "nascimento")
_sub(nascimento, "dtNascto", trab.data_nascimento.strftime("%Y-%m-%d"))
_sub(nascimento, "codMunic", "3550308")
_sub(nascimento, "uf", trab.uf)
_sub(nascimento, "paisNascto", "105") # Brasil
_sub(nascimento, "paisNac", "105")
doc_trab = _sub(dados_trab, "documentos")
ctps = _sub(doc_trab, "ctps")
_sub(ctps, "nrCtps", "000001")
_sub(ctps, "dvCtps", "0")
_sub(ctps, "serieCTPS", "001")
_sub(ctps, "ufCTPS", trab.uf)
_sub(ctps, "dtExped", trab.data_nascimento.replace(year=trab.data_nascimento.year + 18).strftime("%Y-%m-%d"))
vinc = _sub(evt, "vinculo")
ide_empregador_vinc = _sub(vinc, "ideEmpregador")
_sub(ide_empregador_vinc, "tpInsc", "1")
_sub(ide_empregador_vinc, "nrInsc", self.cnpj)
_sub(ide_empregador_vinc, "vlrDtAdm", vinculo.data_admissao.strftime("%Y-%m-%d"))
dados_vinc = _sub(vinc, "dadosVinculo")
_sub(dados_vinc, "matricula", vinculo.matricula)
_sub(dados_vinc, "tpRegTrab", vinculo.tipo_regime)
_sub(dados_vinc, "tpRegPrev", "1") # 1=RGPS
_sub(dados_vinc, "dtAdm", vinculo.data_admissao.strftime("%Y-%m-%d"))
_sub(dados_vinc, "tpAdmissao", "1") # 1=Admissão
_sub(dados_vinc, "indicAdmissao", "1")
_sub(dados_vinc, "nrProcTrab", "")
_sub(dados_vinc, "tpRegJor", vinculo.tipo_jornada)
_sub(dados_vinc, "natAtividade", "1") # 1=Trabalho urbano
_sub(dados_vinc, "dtPrevTerm", "")
cargo_func = _sub(dados_vinc, "cargoFuncao")
_sub(cargo_func, "codCargo", "0001")
_sub(cargo_func, "codFuncao", "0001")
_sub(cargo_func, "CBOCargo", re.sub(r"\D", "", vinculo.cbo)[:6])
_sub(cargo_func, "CBOFuncao", re.sub(r"\D", "", vinculo.cbo)[:6])
_sub(cargo_func, "natAtividade", "1")
_sub(cargo_func, "dtIniCargo", vinculo.data_admissao.strftime("%Y-%m-%d"))
rem = _sub(dados_vinc, "remuneracao")
_sub(rem, "vrSalFx", _fmt_dec(vinculo.salario_base))
_sub(rem, "undSalFixo", vinculo.tipo_salario)
_sub(rem, "dscSalVar", "")
fgts = _sub(dados_vinc, "FGTS")
_sub(fgts, "dtOpcFGTS", (vinculo.data_opcao_fgts or vinculo.data_admissao).strftime("%Y-%m-%d"))
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def gerar_s2299(self, rescisao: EventoRescisao) -> str:
"""S-2299: Desligamento (rescisão de contrato)."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
evt = _sub(root, "evtDeslig")
evt.set("Id", self._id_evento(2299))
self._ide_evento(evt, 2299)
self._ide_empregador(evt)
ide_vinculo = _sub(evt, "ideVinculo")
_sub(ide_vinculo, "cpfTrab", re.sub(r"\D", "", rescisao.trabalhador.cpf))
_sub(ide_vinculo, "matricula", rescisao.matricula)
info_deslig = _sub(evt, "infoDeslig")
_sub(info_deslig, "dtDeslig", rescisao.data_desligamento.strftime("%Y-%m-%d"))
_sub(info_deslig, "mtvDeslig", rescisao.motivo)
_sub(info_deslig, "dtProjFimAPI", (rescisao.data_aviso_previo or rescisao.data_desligamento).strftime("%Y-%m-%d"))
_sub(info_deslig, "indPensBR", "N")
_sub(info_deslig, "indCessao", "N")
_sub(info_deslig, "pensAlim", "N")
_sub(info_deslig, "dtIniAfast", "")
verba = _sub(info_deslig, "verbasResc")
_sub(verba, "dtEfetDeslig", rescisao.data_desligamento.strftime("%Y-%m-%d"))
_sub(verba, "vrSalAnt", _fmt_dec(rescisao.verba_saldo_salario))
_sub(verba, "vrSalFer", _fmt_dec(rescisao.verba_ferias_prop))
_sub(verba, "vr13", _fmt_dec(rescisao.verba_13_prop))
_sub(verba, "vrAvPrevio", _fmt_dec(rescisao.verba_aviso_previo))
_sub(verba, "vrMtFGTS", _fmt_dec(rescisao.verba_multa_fgts))
_sub(verba, "vrINSS", _fmt_dec(rescisao.inss_retido))
_sub(verba, "vrIRRF", _fmt_dec(rescisao.irrf_retido))
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def gerar_s1200(self, folha: FolhaPagamento) -> str:
"""S-1200: Remuneração do trabalhador (folha mensal)."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
evt = _sub(root, "evtRemun")
evt.set("Id", self._id_evento(1200))
self._ide_evento(evt, 1200, folha.periodo)
self._ide_empregador(evt)
ide_vinculo = _sub(evt, "ideVinculo")
_sub(ide_vinculo, "cpfTrab", re.sub(r"\D", "", folha.trabalhador.cpf))
_sub(ide_vinculo, "matricula", folha.matricula)
rem = _sub(evt, "remunPerApur")
ide_estab = _sub(rem, "ideEstabLot")
_sub(ide_estab, "tpInsc", "1")
_sub(ide_estab, "nrInsc", self.cnpj)
_sub(ide_estab, "codLotacao", "01")
for item in folha.itens:
verba = _sub(ide_estab, "verbasRemuneracaoOuDescontos")
_sub(verba, "codRubr", item.rubrica.codigo)
_sub(verba, "ideTabRubr", "S")
_sub(verba, "qtdRubr", _fmt_dec(item.quantidade, 2))
_sub(verba, "fatorRubr", "")
_sub(verba, "vrRubr", _fmt_dec(item.valor))
_sub(verba, "indApurIR", "0")
info_cp = _sub(rem, "infoSaudeColetiva")
_sub(info_cp, "vrBcCp00", _fmt_dec(folha.base_cp))
_sub(info_cp, "vrBcCpMen", _fmt_dec(folha.base_cp))
_sub(info_cp, "vrBcFgts", _fmt_dec(folha.base_fgts))
_sub(info_cp, "vrFgts", _fmt_dec(folha.valor_fgts))
_sub(info_cp, "vrBcFgtsGuia", _fmt_dec(folha.base_fgts))
_sub(info_cp, "vrFgtsGuia", _fmt_dec(folha.valor_fgts))
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def gerar_s1299(self, periodo: str) -> str:
"""S-1299: Fechamento dos eventos periódicos."""
root = ET.Element("eSocial")
root.set("xmlns", NS_ESOCIAL)
evt = _sub(root, "evtFechamento")
evt.set("Id", self._id_evento(1299))
self._ide_evento(evt, 1299, periodo)
self._ide_empregador(evt)
_sub(evt, "evtRemun", "S")
_sub(evt, "evtPgtos", "N")
_sub(evt, "evtAqProd", "N")
_sub(evt, "evtComProd", "N")
_sub(evt, "evtContratAvNP", "N")
_sub(evt, "evtInfoComplPer", "N")
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode", xml_declaration=True)
def salvar_folha(
self,
folha: FolhaPagamento,
diretorio: str | Path,
) -> list[Path]:
d = Path(diretorio)
d.mkdir(parents=True, exist_ok=True)
arquivos = []
p = d / f"S1200_{self.cnpj}_{folha.periodo}_{re.sub(r'\\D', '', folha.trabalhador.cpf)}.xml"
p.write_text(self.gerar_s1200(folha), encoding="utf-8")
arquivos.append(p)
return arquivos
# Rubricas padrão CLT
RUBRICAS_PADRAO: list[RubricaFolha] = [
RubricaFolha("0001", "SALÁRIO BASE", "1", "11", "11", "11"),
RubricaFolha("0002", "HORA EXTRA 50%", "1", "11", "11", "11"),
RubricaFolha("0003", "HORA EXTRA 100%", "1", "11", "11", "11"),
RubricaFolha("0010", "ADICIONAL NOTURNO", "1", "11", "11", "11"),
RubricaFolha("0020", "INSALUBRIDADE", "1", "11", "11", "11"),
RubricaFolha("0030", "PERICULOSIDADE", "1", "11", "11", "11"),
RubricaFolha("0040", "VALE ALIMENTAÇÃO", "3", "00", "00", "00"),
RubricaFolha("0050", "VALE TRANSPORTE", "3", "00", "00", "00"),
RubricaFolha("0100", "DESCONTO INSS", "2", "00", "00", "00"),
RubricaFolha("0101", "DESCONTO IRRF", "2", "00", "00", "00"),
RubricaFolha("0102", "DESCONTO VT", "2", "00", "00", "00"),
RubricaFolha("0103", "DESCONTO VA/VR", "2", "00", "00", "00"),
RubricaFolha("0200", "13º SALÁRIO", "1", "11", "11", "11"),
RubricaFolha("0201", "FÉRIAS", "1", "11", "11", "00"),
RubricaFolha("0202", "1/3 FÉRIAS", "1", "11", "11", "00"),
RubricaFolha("0300", "AVISO PRÉVIO TRABALHADO", "1", "11", "11", "11"),
]