S-2200 admissão de colaborador no eSocial — tudo que o Odoo precisa enviar
Como o módulo l10n_br_hr_payroll_esocial monta o evento S-2200 de admissão no Odoo: dados obrigatórios, certificado A1 e transmissão via esociallib.
Luis Felipe Miléo
S-2200 é o evento mais denso do eSocial. É o registro completo de admissão de um colaborador CLT: dados pessoais, documentos, vínculo trabalhista, jornada, remuneração, dependentes, deficiência, treinamentos, alvarás. Um único XML pode passar de 200 nós. Implementar S-2200 errado significa que o colaborador não consegue ser admitido oficialmente — e a empresa fica em default trabalhista.
A folha CLT da KMEE doada à OCA (PR #277) inclui geração e transmissão completa do S-2200. Este post mostra como o Odoo monta esse evento.
Quando enviar
O S-2200 deve ser transmitido antes do início efetivo das atividades do colaborador. Atrasou? Multa por atraso. Errou dado? Tem que enviar S-2205 (alteração de cadastro) ou S-2206 (alteração de contrato).
Na prática, o Odoo gera o evento no momento em que o hr.employee muda de status draft para running (admitido), mas a transmissão só ocorre quando o usuário aciona explicitamente. Isso evita que empregados em rascunho vazem para a Receita.
Estrutura do evento
O S-2200 do leiaute S-1.3 tem grupos obrigatórios e opcionais. Os principais:
evtAdmissao
├── ideEvento (tpAmb, procEmi, verProc)
├── ideEmpregador (tpInsc, nrInsc)
├── trabalhador
│ ├── cpfTrab
│ ├── nmTrab
│ ├── sexo
│ ├── racaCor
│ ├── estCiv
│ ├── grauInstr
│ ├── nascimento (dtNascto, codMunic, uf, paisNascto, paisNac, nmMae)
│ ├── documentos (CTPS, RIC, RG, OC, OR, NIS, RNE)
│ ├── endereco
│ ├── trabImig (se imigrante)
│ ├── infoDeficiencia
│ ├── dependente (0..n)
│ └── contato
├── vinculo
│ ├── matricula
│ ├── tpRegTrab (1=CLT, 2=Estatutario)
│ ├── tpRegPrev (1=RGPS, 2=RPPS)
│ ├── infoRegimeTrab
│ │ ├── infoCeletista (dtAdm, tpAdmissao, indAdmissao, ...)
│ │ └── duracao (tpContr, dtTerm)
│ ├── infoContrato
│ │ ├── nmCargo, codCargo
│ │ ├── CBOFuncao
│ │ ├── vrSalFx (valor salario fixo)
│ │ ├── undSalFixo (1=hora, 5=mes)
│ │ ├── tpContr (tipo contrato — tempo det/indet)
│ │ ├── horContratual
│ │ └── filiacaoSindical
│ └── localTrabalho
└── observacoes (opcional)
Cada um desses nós tem regras de presença condicional e tipos próprios. O tpInsc é 1 para CNPJ e 2 para CPF. O sexo é M ou F. O racaCor é 1 a 6. Errar um código retorna rejeição com erro genérico tipo “campo inválido” — debugar é pesadelo.
Mapping no Odoo
A KMEE optou por traduzir essa estrutura para campos diretos no hr.employee brasileiro (l10n_br_hr):
class HrEmployee(models.Model):
_inherit = "hr.employee"
cpf = fields.Char(size=14)
pis_pasep = fields.Char(size=11)
rg_number = fields.Char()
rg_emissor = fields.Char(size=20)
rg_uf_id = fields.Many2one("res.country.state")
ctps_number = fields.Char()
ctps_serie = fields.Char()
ctps_uf_id = fields.Many2one("res.country.state")
raca_cor = fields.Selection([
("1", "Branca"), ("2", "Preta"), ("3", "Parda"),
("4", "Amarela"), ("5", "Indigena"), ("6", "Nao informado"),
])
grau_instrucao = fields.Selection(...) # codigos eSocial
estado_civil = fields.Selection(...)
nm_mae = fields.Char()
pais_nascto_id = fields.Many2one("res.country")
pais_nac_id = fields.Many2one("res.country")
deficiencia_ids = fields.One2many(...)
dependente_ids = fields.One2many("hr.dependent", "employee_id")
cargo_id = fields.Many2one("hr.job") # com codCargo + CBO
E o vínculo (contrato) em hr.contract ganha:
matricula_esocial = fields.Char()
tp_reg_trab = fields.Selection([("1", "CLT"), ("2", "Estatutario")])
tp_reg_prev = fields.Selection([("1", "RGPS"), ("9", "RPPS")])
tp_admissao = fields.Selection(...) # 1..8 conforme leiaute
ind_admissao = fields.Selection(...)
nr_proc_jud = fields.Char() # se admitido por decisao judicial
hor_contratual = fields.Char()
Geração do XML
A montagem do XML usa a esociallib — biblioteca Python da KMEE que gera, valida XSD e assina eventos eSocial. O l10n_br_hr_payroll_esocial consome:
from esociallib.s_2200 import EvtAdmissao
from esociallib.signer import sign_event
def gerar_s2200(self):
evt = EvtAdmissao(
ide_evento={"tpAmb": self.company_id.tp_amb, ...},
ide_empregador={"tpInsc": "1", "nrInsc": self.company_id.cnpj_cpf[:8]},
trabalhador=self._build_trabalhador(),
vinculo=self._build_vinculo(),
)
xml = evt.to_xml()
evt.validate() # valida contra XSD oficial
xml_assinado = sign_event(xml, self.company_id.certificate_id)
return xml_assinado
A validação XSD acontece antes da transmissão. Se algum campo está inconsistente, o erro aparece no log do Odoo com referência precisa do nó com problema, não como rejeição genérica do governo.
Certificado A1 (PFX)
A assinatura digital ICP-Brasil exige certificado A1 (ou A3 com módulo HSM, mais raro). O certificado é armazenado em res.company como campo binário criptografado:
certificate_file = fields.Binary(attachment=False)
certificate_password = fields.Char() # criptografado em repouso
certificate_subject = fields.Char(readonly=True)
certificate_valid_to = fields.Date(readonly=True)
A KMEE recomenda fortemente A1: A3 em token físico não escala (precisa estar plugado no servidor) e dificulta automação. A esociallib usa cryptography + signxml para assinar com RSA-SHA1 — sim, SHA1, porque é o que a Receita exige (não dá para “modernizar” para SHA256).
Transmissão e retorno
O envio é via SOAP com mTLS. A esociallib monta o envelope, faz POST no webservice oficial, e parseia o retorno:
from esociallib.transmissao import enviar_lote
resultado = enviar_lote(
eventos=[xml_assinado],
cert_path=path_pfx,
cert_password=senha,
ambiente="producao", # ou "homologacao"
)
# resultado.protocolo -> guardar para consultar
# resultado.status -> "100" recebido com sucesso
O retorno é assíncrono. A Receita devolve um protocolo, e o status final só está disponível minutos depois via consulta (S-1.3 obrigou polling desde julho/2024). O l10n_br_hr_payroll_esocial faz a consulta automática via cron a cada 5 minutos, atualizando o status do evento no Odoo.
O que pode dar errado
Os erros mais comuns que vemos em campo:
- CPF do trabalhador não cadastrado na CAGED/CTPS Digital — a Receita rejeita antes de processar.
- Data de admissão posterior ao envio — eSocial só aceita admissão no dia ou retroativa.
- CBO incompatível com cargo — campos
nmCargoeCBOFuncaoprecisam fazer sentido. - Dependente com CPF de pessoa já cadastrada como dependente em outra empresa — restrição da Receita.
- Falta de rubrica fixa S-1010 antes do S-2200 — se você prometer pagar uma rubrica que não existe na sua tabela, rejeita.
O Odoo aborda esses casos com validações pré-envio e mensagens claras no chatter do empregado.
Conclusão
S-2200 é o evento mais denso do eSocial e o que mais ensina sobre o leiaute. A KMEE tem implementação completa em produção desde o Odoo 8.0 e doou tudo à OCA no PR #277. Quem quiser ver o XML real de um evento S-2200 gerado pelo Odoo basta clonar o repositório e rodar os testes — eles geram XMLs assinados e validados em modo dev.
Próximos eventos cobertos no roadmap: S-1200 (remuneração) já implementado, S-1210 (pagamentos) em desenvolvimento, S-1299 (fechamento) na sequência. Veja mais sobre folha Odoo.
Sobre o autor
Luis Felipe Miléo
Desenvolvedor Odoo · KMEE
Desenvolvedor especializado em localização fiscal e projetos open source no ecossistema Odoo/OCA, com foco em integrações para o mercado latino-americano.
Ver perfil no LinkedInArtigos relacionados
Open Finance regulado vs APIs proprietárias: qual usar para cada caso
7 de jul. de 2026
Gestão EmpresarialDDA no Odoo: contas a pagar 100% automatizadas
30 de jun. de 2026
Gestão EmpresarialTOTVS está descontinuando sua API bancária — Odoo é a alternativa neutra
9 de jun. de 2026