Pular para conteúdo

ECD — Bloco I (Lançamentos Contábeis)

Finalidade do Bloco

O Bloco I é o coração da ECD, contendo:

  • Plano de contas completo da empresa (I050/I051/I052)
  • Balancetes mensais com saldos periódicos (I150/I155)
  • Lançamentos contábeis — Livro Diário digitalizado (I200/I250)
  • Saldos de contas de resultado antes do encerramento (I350/I355)

Do ponto de vista contábil, o Bloco I substitui integralmente o Livro Diário e o Livro Razão em papel.


Registro I001 — Abertura do Bloco I

Finalidade

Indicador de presença de dados no Bloco I.

Destino no ArquivoDigital

Gravado diretamente em _arq._blocos['I'].registro_abertura.IND_DAD — não cria um novo registro.

Campos

# Campo Valor Significado
1 REG I001 Identificador
2 IND_DAD '0' 🔒 Fixo — bloco com dados (0=com dados, 1=sem dados)

Referência técnica

enviar_registro_I001() — linha 279


Registro I010 — Identificação da Escrituração Contábil

Finalidade

Define o tipo de escrituração e a versão do leiaute utilizado. Esses valores são fundamentais para o PVA interpretar corretamente os registros subsequentes.

Campos — detalhamento

# Campo SPED Valor Origem Significado legal Impacto
1 IND_ESC self.ind_esc ⚙️ Wizard (Aba Parâmetros) Tipo do livro contábil. Determina quais registros são obrigatórios/facultativos Reflete em I030 (NAT_LIVR) e J900 (NAT_LIVRO)
2 COD_VER_LC self.cod_ver_lc ⚙️ Wizard (Aba Parâmetros), default '9.00' Versão do leiaute contábil conforme Manual de Orientação do Leiaute da ECD publicado pela RFB Deve corresponder ao leiaute vigente no ano da escrituração

Valores de IND_ESC e seus efeitos

Código Descrição completa (gravada em I030/J900) Cenário de uso
G 'Livro Diário (Completo sem escrituração auxiliar).' Mais comum — empresa com escrituração única e completa
R 'Livro Diário com Escrituração Resumida (com escrituração auxiliar).' Empresa que mantém livros auxiliares separados
A 'Livro Diário Auxiliar ao Diário com Escrituração Resumida.' Complemento ao tipo R — o livro auxiliar em si
B 'Livro Balancetes Diários e Balanços.' Instituições financeiras obrigadas ao COSIF
Z 'Razão Auxiliar (Livro Contábil Auxiliar conforme leiaute definido nos registros I500 a I555).' Uso parametrizável — registros I500–I555 (não implementados)

Referência técnica

enviar_registro_I010() — linha 286


Registro I030 — Termo de Abertura do Livro Contábil

Finalidade

Formaliza a abertura do livro contábil eletrônico com dados de identificação da empresa, data de arquivamento e a natureza do livro. Equivale ao Termo de Abertura que antigamente era feito em cartório.

Campos — detalhamento completo

# Campo SPED Tipo Valor no código Origem Significado legal Recalculado?
1 REG Fixo I030 Identificador
2 DNRC_ABERT Fixo 'TERMO DE ABERTURA' CampoFixo na classe Texto padrão conforme instrução normativa DNRC
3 NUM_ORD Int 1 🔒 Fixo Número de ordem do instrumento de escrituração. Sempre 1 — primeiro livro
4 NAT_LIVR Alfanum. IND_ESC_DESCR[ind_esc] 🧮 Derivado Natureza do livro — texto da descrição do tipo de escrituração. Fallback: 'Livro Diário (Completo sem escrituração auxiliar).'
5 QTD_LIN Int 0 (inicial) 🧮 Recalculado Quantidade total de linhas do arquivo. Inicialmente gravado como 0, mas recalculado por ArquivoDigital.prepare() no momento da serialização, que seta o valor para reg_count (total de todos os registros + 2) prepare()
6 NOME Alfanum. company.name 🏢 Empresa Razão social
7 NIRE Alfanum. company.l10n_br_nire or '' 🏢 Empresa Número de Identificação do Registro de Empresas na Junta Comercial. Opcional — se vazio, grava string vazia
8 CNPJ Alfanum. _format_cnpj_cpf(company.l10n_br_cnpj) 🏢 Empresa CNPJ sem formatação (14 dígitos)
9 DT_ARQ Data company.l10n_br_data_abertura 🏢 Empresa Data de arquivamento dos atos constitutivos. Formato DDMMAAAA. Se vazio, pode gerar None — o PVA pode rejeitar
10 DT_ARQ_CONV Não preenchido Data de arquivamento do ato de conversão de SCP em sociedade. Campo de conversão — não usado
11 DESC_MUN Alfanum. company.l10n_br_municipio_id.name 🏢 Empresa Nome do município da sede. Pego do objeto municipio, não do partner
12 DT_EX_SOCIAL Data self.date_fim ⚙️ Wizard Data de encerramento do exercício social. Tipicamente 31/12 do ano escriturado

Mecanismo de recálculo do QTD_LIN

O campo QTD_LIN é gravado como 0 neste método, mas é recalculado pelo ArquivoDigital.prepare() (em sped/ecd/arquivos.py, linha 68):

encerramentoI = [x for x in self._blocos['I'].registros
                 if isinstance(x, RegistroI030)]
encerramentoI[0].QTD_LIN = reg_count  # Total de registros do arquivo inteiro

O reg_count é calculado assim:

reg_count = 2  # abertura (0000) + encerramento (9999)
for bloco in self._blocos.values():
    reg_count += len(bloco.registros)

Referência técnica

enviar_registro_I030() — linha 298


Registro I050 — Plano de Contas da Entidade

Finalidade

Registra todas as contas contábeis (sintéticas e analíticas) que tiveram saldo ou movimentação no período. É o catálogo completo do plano de contas da empresa para a ECD.

Quando é gerado

Para cada grupo contábil e para cada conta desse grupo que pertence ao conjunto retornado por _get_account_and_group_ids(). O sistema gera primeiro o registro do grupo (sintética, IND_CTA = 'S'), depois os registros das contas desse grupo (analíticas, IND_CTA = 'A').

Ordem de processamento

Para cada GRUPO em group_ids (order por search padrão do Odoo):
  1. Gera I050 do grupo (IND_CTA = 'S')
  2. Busca contas do grupo: account.search(group_id=grupo, id in inuse_accounts)
  3. Para cada CONTA do grupo:
     a. Gera I050 da conta (IND_CTA = 'A')
     b. Chama enviar_registro_I051() → gera se tem conta referencial
     c. Chama enviar_registro_I052() → gera sempre (código de aglutinação = grupo)

Validação bloqueante — contas sem conta referencial

Quando bloqueia: Antes de processar qualquer grupo, o sistema filtra:

account_ids_sem_referencial = account_ids.filtered(
    lambda account: not account.l10n_br_conta_referencial 
    and account.l10n_br_cod_nat not in ['05', '09']
)
Condição Efeito
Conta com l10n_br_cod_nat = '01' (Ativo) sem conta referencial Bloqueia
Conta com l10n_br_cod_nat = '02' (Passivo) sem conta referencial Bloqueia
Conta com l10n_br_cod_nat = '03' (PL) sem conta referencial Bloqueia
Conta com l10n_br_cod_nat = '04' (Resultado) sem conta referencial Bloqueia
Conta com l10n_br_cod_nat = '05' (Compensação) sem conta referencial ✅ Aceita (dispensada)
Conta com l10n_br_cod_nat = '09' (Outras) sem conta referencial ✅ Aceita (dispensada)
Conta sem l10n_br_cod_nat (vazio) sem conta referencial Bloqueia (vazio ∉ ['05','09'])

Tipo de erro: RedirectWarning
Mensagem: "Contas sem conta referencial. Informe a conta referencial para as contas abaixo ou arquive-as"
Botão: "IR PARA CONTAS" → abre a lista de contas problemáticas

Campos para contas SINTÉTICAS (grupos)

# Campo SPED Valor Cálculo/Origem Detalhes
1 DT_ALT group.create_date ORM Data de criação do grupo no Odoo. Não é a data de alteração real — é a create_date do registro
2 COD_NAT group.l10n_br_cod_nat or '' Cadastro Código da natureza. Se vazio, grava string vazia
3 IND_CTA 'S' 🔒 Fixo S=Sintética
4 NIVEL Calculado 🧮 Percorre parent_id até a raiz Nível 1 = raiz, 2 = filho, etc.
5 COD_CTA group.code_prefix_start Cadastro Código do grupo (ex: 1, 1.1, 1.1.01)
6 COD_CTA_SUP group.parent_id.code_prefix_start or '' Cadastro Código do grupo pai. Vazio se grupo raiz
7 CTA group.name Cadastro Nome do grupo

Campos para contas ANALÍTICAS

# Campo SPED Valor Cálculo/Origem Detalhes
1 DT_ALT account.create_date ORM Data de criação da conta
2 COD_NAT account.l10n_br_cod_nat or '' Cadastro Código da natureza da conta
3 IND_CTA 'A' 🔒 Fixo A=Analítica
4 NIVEL nivel_grupo + 1 🧮 Calculado Sempre um nível abaixo do grupo pai
5 COD_CTA account.code Cadastro Código da conta (ex: 1.1.01.001)
6 COD_CTA_SUP group.code_prefix_start Cadastro Código do grupo pai (a conta pertence a este grupo)
7 CTA account.name Cadastro Nome da conta

Cálculo detalhado do NIVEL

nivel = 1
current_group_id = group_id
while current_group_id.parent_id:
    nivel += 1
    current_group_id = current_group_id.parent_id
# Para conta analítica: nivel + 1

Exemplo prático:

Entidade code_prefix parent_id NIVEL
Ativo (grupo raiz) 1 1
Ativo Circulante 1.1 1 2
Disponível 1.1.01 1.1 3
Conta Caixa (analítica) 1.1.01.001 — (grupo: 1.1.01) 4

Referência técnica

enviar_registro_I050() — linha 323


Registro I051 — Plano de Contas Referencial

Finalidade

Vincula cada conta analítica ao plano de contas referencial da RFB. O plano referencial é definido pelo campo COD_PLAN_REF do registro 0000.

Quando é gerado

Chamado dentro do loop do I050, para cada conta analítica. O registro é suprimido silenciosamente se l10n_br_conta_referencial estiver vazio.

Lógica de supressão

if not account_id.l10n_br_conta_referencial:
    return  # Não gera I051 para esta conta

Campos

# Campo SPED Valor Detalhes
1 COD_CCUS '' 🔒 Fixo — sem centro de custo (I100 não implementado)
2 COD_CTA_REF account.l10n_br_conta_referencial ✅ Cadastro — código do plano referencial RFB

Como preencher a conta referencial

O código l10n_br_conta_referencial é um campo livre (Char) na conta contábil. Deve ser preenchido conforme a tabela referencial da RFB correspondente ao COD_PLAN_REF escolhido (ex: tabela LP para Lucro Presumido).

Referência técnica

enviar_registro_I051() — linha 381


Registro I052 — Indicação dos Códigos de Aglutinação

Finalidade

Vincula cada conta ao código de aglutinação usado nos demonstrativos contábeis (J100/J150). O código de aglutinação permite consolidar contas em linhas dos demonstrativos.

Quando é gerado

Chamado dentro do loop do I050, para cada conta analítica processada. O código de aglutinação é sempre o code_prefix_start do grupo da conta.

Campos

# Campo SPED Valor Detalhes
1 COD_CCUS '' 🔒 Fixo
2 COD_AGL group.code_prefix_start Cadastro — identificador do grupo para consolidação

Referência técnica

enviar_registro_I052() — linha 396


Registro I150 — Saldos Periódicos (Identificação do Período)

Finalidade

Identifica cada sub-período mensal dentro do período anual. Para cada I150, são gerados os registros I155 com os saldos de cada conta naquele mês.

Quando é gerado

Uma vez para cada mês dentro de [date_ini, date_fim].

Algoritmo de divisão mensal

def gerar_range_mensal(data_inicio, data_fim):
    resultado = []
    data_atual = data_inicio.replace(day=1)  # Garante dia 1
    while data_atual <= data_fim:
        ultimo_dia = calendar.monthrange(data_atual.year, data_atual.month)[1]
        primeiro_dia = data_atual
        ultimo_dia_mes = data_atual.replace(day=ultimo_dia)
        resultado.append((primeiro_dia, ultimo_dia_mes))
        # Avança para o próximo mês
        if data_atual.month == 12:
            data_atual = data_atual.replace(year=data_atual.year + 1, month=1)
        else:
            data_atual = data_atual.replace(month=data_atual.month + 1)
    return resultado

Comportamento: Se date_ini = 01/01/2025 e date_fim = 31/12/2025, gera 12 pares:

(01/01, 31/01), (01/02, 28/02), ..., (01/12, 31/12)

Edge case — date_ini não começa no dia 1: O algoritmo força day=1 via data_inicio.replace(day=1). Se date_ini = 15/03/2025, o primeiro sub-período será (01/03, 31/03) — o saldo anterior incluirá dados de 01/03 a 14/03 no cálculo de saldo inicial, o que pode causar divergências.

Campos

# Campo Valor Detalhes
1 DT_INI Primeiro dia do mês 🧮 Calculado
2 DT_FIN Último dia do mês 🧮 Calculado (via calendar.monthrange)

Referência técnica

enviar_registro_I150() — linha 437


Registro I155 — Detalhe dos Saldos Periódicos (Balancete Mensal)

Finalidade

Saldo de cada conta contábil mês a mês. É o balancete mensal do plano de contas, contendo saldo inicial, movimentação e saldo final.

Quando é gerado

Chamado dentro do I150, para cada conta da empresa. O sistema busca todas as contas da empresa (não apenas as do _get_account_and_group_ids) e calcula saldos individualmente.

Escopo de contas

account_ids = self.env['account.account'].search([
    ('company_id', '=', self.company_id.id)
])

Atenção: Diferente do I050 que usa o subconjunto de contas com saldo/movimentação, o I155 busca todas as contas da empresa e depois aplica o filtro de valores zerados. Isso pode ter impacto de performance em planos de contas grandes.

Validação bloqueante

Se qualquer conta com saldo não-zero no mês não tem group_id, a geração bloqueia:

if not account_id.group_id:
    account_ids_sem_grupo |= account_id
# ... ao final do loop:
if account_ids_sem_grupo:
    raise RedirectWarning(
        "Contas sem Grupo Sintético. Informe o grupo sintético para as contas abaixo ou arquive-as",
        action, "IR PARA CONTAS",
    )

Observação: O erro é lançado ao final do processamento de todos os meses, acumulando todas as contas problemáticas. Isso significa que o sistema processa todos os meses antes de bloquear.

Cálculo de saldos — passo a passo

1. Saldo inicial do mês:

saldo_inicial = read_group(
    domain = [company, account_id, date < data_inicio, posted],
    fields = ['debit', 'credit'],
    groupby = []  # Sem agrupamento — soma total
)
saldo_inicial_valor = (debit or 0) - (credit or 0)

2. Movimentação do mês:

movimentos_mes = read_group(
    domain = [company, account_id, date >= data_inicio, date <= data_fim, posted],
    fields = ['debit', 'credit'],
    groupby = []
)
total_debitos = debit or 0
total_creditos = credit or 0

3. Saldo final:

saldo_final_valor = saldo_inicial_valor + total_debitos - total_creditos

4. Filtro de supressão:

if saldo_inicial_valor == 0 and total_debitos == 0 and total_creditos == 0 and saldo_final_valor == 0:
    continue  # Conta ignorada neste mês

Campos

# Campo SPED Valor Cálculo Regra
1 COD_CTA account.code Cadastro Código da conta
2 COD_CCUS '' 🔒 Fixo Centro de custo (não implementado)
3 VL_SLD_INI abs(saldo_inicial_valor) abs(sum(debit) - sum(credit)) onde date < data_inicio AND posted Sempre valor absoluto
4 IND_DC_INI 'D' se saldo_inicial_valor > 0, 'C' se ≤ 0 Derivado Natureza devedora ou credora
5 VL_DEB abs(total_debitos) abs(sum(debit)) do mês Total dos débitos
6 VL_CRED abs(total_creditos) abs(sum(credit)) do mês Total dos créditos
7 VL_SLD_FIN abs(saldo_final_valor) abs(saldo_ini + debitos - creditos) Saldo final
8 IND_DC_FIN 'D' se saldo_final_valor > 0, 'C' se ≤ 0 Derivado Natureza do saldo final

Cenário de saldo zero

saldo_ini débitos créditos saldo_fin Gera I155?
0 0 0 0 ❌ Não
0 100 100 0 ✅ Sim (houve movimentação)
100 0 0 100 ✅ Sim (saldo inicial ≠ 0)
0 0 0 0.001 ✅ Sim (saldo final ≠ 0, arredondamento)

Referência técnica

enviar_registro_I155() — linha 474


Registro I200 — Lançamento Contábil

Finalidade

Cada registro I200 representa um lançamento contábil (account.move) postado no período. Equivale a uma entrada no Livro Diário.

Quando é gerado

Para cada account.move que satisfaz:

move_ids = account.move.search([
    ('company_id', '=', company),
    ('date', '>=', date_ini),
    ('date', '<=', date_fim),
    ('state', '=', 'posted'),
])

Adicionalmente, lançamentos com valor total zero são ignorados:

valor_total_lcto = sum(move_id.line_ids.mapped('debit'))
if valor_total_lcto == 0.00:
    continue

Validação bloqueante

Acumula contas sem group_id em linhas dos lançamentos — ao final do loop, bloqueia se houver:

account_ids_sem_grupo |= move_id.line_ids.filtered(
    lambda l: not l.account_id.group_id
).mapped('account_id')

Campos — detalhamento

# Campo SPED Valor Origem Detalhes
1 NUM_LCTO str(move.id) 📄 Automático ID interno do Odoo convertido para string. Não é o número sequencial (name) do lançamento — é o ID do banco de dados
2 DT_LCTO move.date 📄 Automático Data do lançamento. Formato DDMMAAAA na serialização
3 VL_LCTO abs(sum(line_ids.mapped('debit'))) 📄 Calculado Valor total do lançamento. Usa sum(debit) — em um lançamento balanceado, sum(debit) == sum(credit)
4 IND_LCTO 'N' ou 'E' 📄 Derivado 'N'=Normal se is_encerramento = False; 'E'=Encerramento se is_encerramento = True

Classificação Normal vs. Encerramento

IND_LCTO = 'N' if not move_id.is_encerramento else 'E'

O campo is_encerramento em account.move é um Boolean controlado pelo módulo de Encerramento Contábil. Lançamentos gerados pela rotina de zeramento de resultado recebem is_encerramento = True.

Referência técnica

enviar_registro_I200() — linha 543


Registro I250 — Partidas do Lançamento Contábil

Finalidade

Cada registro I250 representa uma linha contábil (account.move.line) dentro de um lançamento. Detalha conta, valor, indicador D/C e histórico.

Quando é gerado

Chamado dentro do loop do I200, para cada line_id do lançamento. Linhas com debit = 0 E credit = 0 são ignoradas:

if line_id.debit == 0.00 and line_id.credit == 0.00:
    continue

Campos — detalhamento completo

# Campo SPED Valor Origem Detalhes
1 COD_CTA line.account_id.code 🏦 Cadastro Código da conta contábil
2 COD_CCUS '' 🔒 Fixo Centro de custo — sempre vazio (I100 não implementado)
3 VL_DC line.debit or line.credit 📄 Automático Usa debit se > 0, senão credit. O valor é sempre positivo
4 IND_DC 'D' se debit > 0, 'C' se credit > 0 📄 Derivado Natureza da partida
5 NUM_ARQ '' 🔒 Fixo Número do sub-arquivo que contém o lançamento — não utilizado
6 COD_HIST_PAD '' 🔒 Fixo Código do histórico padronizado — I075 não implementado
7 HIST _format_desc(move.display_name + ': ' + line.name) 📄 Composto Histórico livre do lançamento
8 COD_PART '' 🔒 Fixo Código do participante — 0150 não implementado

Formatação do histórico (HIST)

def _format_desc(desc):
    return (desc or '').replace('\n', ' ').replace('|', '-')
Caractere Substituído por Motivo
\n (quebra de linha) (espaço) Linhas contábeis podem ter quebras
\| (pipe) - (traço) O pipe é o delimitador de campos no SPED

Composição do HIST:

{move.display_name}: {line.name}

Exemplo: MISC/2025/00145: Receita de vendas - Janeiro

Se move.display_name ou line.name forem None, são substituídos por string vazia via (... or '').

Referência técnica

enviar_registro_I250() — linha 584


Registro I350 — Saldos das Contas de Resultado Antes do Encerramento (Identificação)

Finalidade

Registro de identificação da data de referência para os saldos das contas de resultado antes do encerramento do exercício. Serve como "cabeçalho" para os registros I355.

Campos

# Campo Valor Detalhes
1 DT_RES self.date_fim Data de referência — fim do período

Referência técnica

enviar_registro_I350() — linha 610


Registro I355 — Detalhe dos Saldos das Contas de Resultado

Finalidade

Saldo acumulado de cada conta de resultado (l10n_br_cod_nat = '04') antes do encerramento do exercício. Este registro é essencial para demonstrar o resultado econômico antes da distribuição/alocação.

Lógica de exclusão de encerramentos — detalhada

O sistema busca lançamentos de encerramento e os exclui do cálculo:

Passo 1 — Identificar encerramentos:

encerramentos_ids = AccountMove.search([
    ('company_id', '=', company),
    ('date', '>=', date_ini),
    ('date', '<=', date_fim),
    ('is_encerramento', '=', True),
])

Passo 2 — Construir domain excluindo encerramentos:

domain = [
    ('company_id', '=', company),
    ('date', '<=', date_fim),               # Até o final do período
    ('move_id.state', '=', 'posted'),
    ('account_id.l10n_br_cod_nat', '=', '04'),  # Somente contas de resultado
]
if encerramentos_ids:
    domain.append(('move_id.id', 'not in', encerramentos_ids.ids))

Observação: O filtro de data usa date <= date_fim (sem date >= date_ini), o que significa que o saldo inclui tudo desde a criação da empresa até date_fim, excluindo encerramentos. Isso é correto para contas de resultado, que devem ser zeradas a cada exercício pelo encerramento.

Passo 3 — Agrupar por conta e calcular:

saldo_final = AccountMoveLine.read_group(
    domain=domain,
    fields=['account_id', 'debit', 'credit'],
    groupby=['account_id']
)

Condição de supressão

vl_cta = (debit or 0) - (credit or 0)
if vl_cta == 0:
    continue  # Conta com saldo zerado é ignorada

Campos

# Campo SPED Valor Cálculo
1 COD_CTA account.code Cadastro
2 COD_CCUS '' 🔒 Fixo
3 VL_CTA abs(debit - credit) 🧮 Agregado — todas as linhas da conta pré-encerramento
4 IND_DC 'D' se > 0, 'C' se ≤ 0 🧮 Derivado

Referência técnica

enviar_registro_I355() — linha 621


Registro I990 — Encerramento do Bloco I

Cálculo

QTD_LIN_I = len(_arq._blocos['I'].registros)

Conta todos os registros adicionados ao bloco I (I001, I010, I030, I050, I051, I052, I150, I155, I200, I250, I350, I355, I990).

Referência técnica

enviar_registro_I990() — linha 667


Registros Não Implementados (Bloco I)

Registro Classe Python Finalidade legal Status
I075 RegistroI075 Tabela de Histórico Padronizado. No código, está comentado com valores fixos '07' para COD_HIST e DESCR_HIST — funcionaria como tabela de referência para o campo COD_HIST_PAD do I250 Comentado (linhas 176, 408–418)
I100 RegistroI100 Centro de Custos. Implementado mas comentado — lista todas as contas analíticas (account.analytic.account) com code e name Comentado (linhas 178, 420–435)
I012 RegistroI012 Livros Auxiliares ao Diário Definido, nunca chamado
I015 RegistroI015 Identificação das Contas da Escrituração Resumida Definido, nunca chamado
I020 RegistroI020 Campos Adicionais Definido, nunca chamado
I300 RegistroI300 Balancetes Diários — Data Definido, nunca chamado
I310 RegistroI310 Balancetes Diários — Detalhe Definido, nunca chamado
I500 RegistroI500 Parâmetros do Razão Auxiliar Definido, nunca chamado (uso com IND_ESC = 'Z')
I510 RegistroI510 Definição dos Campos do Razão Auxiliar Definido, nunca chamado
I550 RegistroI550 Detalhe do Razão Auxiliar Definido, nunca chamado
I555 RegistroI555 Totais do Razão Auxiliar Definido, nunca chamado

Referências