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"), ]