|
|
| 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" |
| raca_cor: str = "1" |
| grau_instrucao: str = "07" |
| pis_pasep: str = "" |
| endereco: str = "" |
| municipio: str = "" |
| uf: str = "SP" |
| cep: str = "" |
| email: str = "" |
| telefone: str = "" |
| nacionalidade: str = "10" |
|
|
|
|
| @dataclass |
| class VinculoEmpregaticio: |
| trabalhador: Trabalhador |
| matricula: str |
| data_admissao: date |
| tipo_regime: str = "1" |
| tipo_vinculo: str = "10" |
| cargo: str = "" |
| funcao: str = "" |
| salario_base: Decimal = Decimal("0") |
| tipo_salario: str = "11" |
| categoria: str = "101" |
| cbo: str = "2521-05" |
| jornada_semanal: Decimal = Decimal("44") |
| tipo_jornada: str = "2" |
| fgts_optante: bool = True |
| data_opcao_fgts: Optional[date] = None |
|
|
|
|
| @dataclass |
| class EventoRescisao: |
| trabalhador: Trabalhador |
| matricula: str |
| data_desligamento: date |
| motivo: str = "01" |
| 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 |
| 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 |
| 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") |
| _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") |
| _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") |
| _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") |
| _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") |
| _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") |
| _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") |
| _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") |
| _sub(dados_vinc, "dtAdm", vinculo.data_admissao.strftime("%Y-%m-%d")) |
| _sub(dados_vinc, "tpAdmissao", "1") |
| _sub(dados_vinc, "indicAdmissao", "1") |
| _sub(dados_vinc, "nrProcTrab", "") |
| _sub(dados_vinc, "tpRegJor", vinculo.tipo_jornada) |
| _sub(dados_vinc, "natAtividade", "1") |
| _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_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"), |
| ] |
|
|