Introdução
Se já trabalhou com Odoo, provavelmente já viu campos cujo valor é gerado automaticamente. Um campo calculado armazenado vai além: o sistema calcula o valor e grava-o diretamente na base de dados, como se fosse uma coluna normal.
Essa diferença é mais relevante do que parece. Um campo só calculado à leitura não pode ser eficazmente pesquisado, filtrado, agrupado ou exportado pelo banco de dados. Um campo calculado armazenado permite essas operações porque o valor existe fisicamente na tabela.
Este guia explica, de forma prática, tudo o que precisa saber sobre campos calculados armazenados no Odoo: o seu comportamento no modelo de dados, como criá-los com o Studio ou em Python, exemplos reais de negócio e os escorregadelas a evitar.
O que é um campo calculado armazenado no Odoo
Na ORM do Odoo, cada campo representa um dado no registo. A maioria dos campos guarda o que o utilizador escreve; um campo calculado, em vez disso, obtém o seu valor por uma função Python que define a lógica do cálculo.
Um campo calculado armazenado é simplesmente um campo calculado com store=True. Sempre que as dependências mudam, o Odoo executa a função de cálculo e grava o resultado na coluna da base de dados, deixando o valor disponível como qualquer outro campo persistente.
Em termos gerais, o padrão em Python segue uma lógica de definir o campo, indicar a função de cálculo e declarar das dependências — depois a função preenche o valor para cada registo.
total_amount = fields.Float(
string='Valor Total',
compute='_compute_total_amount',
store=True,
)
@api.depends('quantity', 'unit_price')
def _compute_total_amount(self):
for record in self:
record.total_amount = record.quantity * record.unit_price
O parâmetro store=True é o que transforma um campo apenas calculado numa coluna persistente. Sem ele, o valor é reavaliado sempre que se lê o campo e nunca é gravado na base de dados.
No ecrã do Odoo, um campo calculado armazenado aparece exatamente como qualquer outro campo: em formulários, listas e relatórios. Os utilizadores podem filtrar, agrupar e exportar por esse campo sem perceber que o valor resulta de código.
Campos calculados: armazenados vs não armazenados
Perceber esta distinção é crucial para qualquer trabalho de desenvolvimento em Odoo:
- Campo calculado não armazenado: Avaliado no momento da leitura. Não pode ser usado em pesquisa, filtro ou group-by. Ocupa menos espaço, mas não participa eficazmente nas consultas SQL.
- Campo calculado armazenado: Recalculado quando as dependências mudam e gravado na base de dados. Pesquisável, filtrável e exportável, mas usa espaço e gera escritas quando atualizado.
A escolha entre os dois não é sobre qual é melhor em termos absolutos, mas sobre o que precisa: para mostrar um valor pontual num formulário, um campo não armazenado basta; para filtrar, ordenar ou agregar, é obrigatório armazenar.
Como funciona o campo
Ao definir um campo calculado armazenado, a ORM do Odoo cria gatilhos automáticos de recomputação com base nos campos listados no decorador @api.depends().
Quando uma das dependências muda num registo, o Odoo marca o registo para recomputação, executa a função compute e grava o resultado na coluna correspondente.
Ciclo de recomputação
O processo acontece desta forma, passo a passo:
- Um utilizador ou processo automático altera um dos campos referidos em
@api.depends(). - O Odoo identifica os registos que dependem desse campo e os marca para atualização.
- A função de cálculo (compute) é executada nesses registos.
- O valor calculado é escrito na coluna da base de dados.
- O campo passa a estar disponível para pesquisas, filtros e exportações com o valor actualizado.
Na maioria das situações a recomputação é imediata e faz parte da mesma transação. Em operações massivas, o Odoo pode adiar algumas recomputações e processá-las em segundo plano.
Dependências entre modelos relacionados
O decorador @api.depends() aceita caminhos com pontos para alcançar campos em modelos relacionados. Por exemplo:
@api.depends('partner_id.country_id.name')
def _compute_country_name(self):
for record in self:
record.country_name = record.partner_id.country_id.name or ''
Assim, o Odoo monitora alterações em partner_id, country_id e name em modelos diferentes. Se o nome do país mudar, todos os registos relacionados são atualizados automaticamente — uma funcionalidade potente da plataforma.
Impacto na base de dados
Como o campo fica armazenado, o Odoo cria uma coluna real na tabela PostgreSQL. Isso permite que o campo participe diretamente em consultas SQL. Pesquisas e filtros sobre campos armazenados são rápidas, tal como para campos normais.
Casos de uso empresariais
Exemplos práticos
Aqui ficam cinco exemplos concretos de como as empresas usam campos calculados armazenados no dia a dia.
1. Vendas: Percentagem de margem por linha de encomenda
A equipa de vendas quer ver a margem por linha sem cálculos manuais. Um campo armazenado pega no preço de venda e no custo, calcula a margem e grava-a na linha. A gestão pode depois filtrar linhas pouco lucrativas ou agrupar por intervalos de margem nos pivot tables.
2. CRM: Dias sem atividade num lead
Um campo armazenado no modelo de leads pode contar os dias desde a última atividade agendada. Com uma ação agendada que força a recomputação diariamente, a equipa comercial filtra leads por inatividade e identifica os que precisam de atenção sem trabalho manual.
3. Inventário: Quantidade líquida disponível
Para artigos com regras complexas de stock, um campo armazenado pode conter a quantidade disponível já deduzida das reservas. Assim, os responsáveis de produto podem ordenar e filtrar produtos por disponibilidade sem que o Odoo execute cálculos complexos por cada linha na vista.
4. Contabilidade: Número de faturas vencidas por cliente
No contacto do cliente, um campo armazenado pode contar as faturas vencidas. A equipa financeira pode ordenar contactos por esse número e priorizar cobranças — só possível porque o valor está persistido na base de dados.
5. Produção: Duração total estimada de um BOM
Como criar ou personalizar o campo
Numa lista de materiais, um campo armazenado pode somar as durações das operações. Planificadores podem filtrar e ordenar BOMs por tempo total, útil para planeamento de capacidade. Quando uma operação muda, o total atualiza-se automaticamente.
Como criar um campo calculado armazenado
Existem duas abordagens: usar Odoo Studio para casos simples ou escrever código Python num módulo para controlo total.
Usando o Odoo Studio
O Studio permite adicionar campos calculados sem programação. Ao criar um campo numérico pode ativar uma fórmula com expressão semelhante a Python. O Studio trata da deteção das dependências por trás dos bastidores.
O Studio é ideal para expressões aritméticas simples entre campos do mesmo registo: rápido e sem necessidade de ambiente de desenvolvimento. Mas quando a lógica envolve campos relacionados, condições complexas ou agregações, terá de recorrer a um módulo personalizado.
Esta distinção é útil ao planear personalizações: Studio acelera tarefas simples; Python oferece flexibilidade quando a lógica é abrangente.
Criando um módulo Python
Para lógica mais complexa, define-se o campo em Python num módulo. Exemplo: adicionar percentagem de margem em linhas de venda:
from odoo import models, fields, api
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
x_margin_pct = fields.Float(
string='Margin %',
compute='_compute_margin_pct',
store=True,
digits=(5, 2),
)
@api.depends('price_unit', 'purchase_price')
def _compute_margin_pct(self):
for line in self:
if line.price_unit:
line.x_margin_pct = (
(line.price_unit - line.purchase_price) / line.price_unit
) * 100
else:
line.x_margin_pct = 0.0
Após instalar o módulo, o Odoo cria a coluna x_margin_pct, executa o cálculo para os registos existentes e passa a acompanhar alterações em price_unit e purchase_price.
O prefixo x_ é a convenção para campos personalizados em Odoo, evitando colisões com campos do núcleo.
Tornar o campo editável pelo utilizador
Por padrão, campos calculados são só de leitura. Para permitir que um utilizador sobreponha o valor, defina um método inverse que se encarregue de propagar a alteração para os campos fonte. Esta técnica é útil quando o valor calculado é um bom padrão mas pode precisar de ajuste manual.
Boas práticas
Campos do Studio e a API XML-RPC
Equipas que gerem campos via API XML-RPC conseguem criar campos padrão através do modelo ir.model.fields. Contudo, para campos armazenados com lógica Python personalizada, o código de cálculo precisa residir no servidor. A API serve para automatizar a criação de campos simples, mas a lógica do compute requer um módulo instalado.
Boas práticas seguidas por consultores experientes
Declare todas as dependências com precisão
O decorador @api.depends() deve listar cada campo que a sua função de cálculo lê. Se esquecer algum, o campo não será atualizado quando essa dependência mudar. Revise linha a linha e assegure que cada acesso a um campo está declarado.
Mantenha funções de cálculo rápidas
A função de compute é executada para todos os registos afetados por uma alteração. Numa instância com muita atividade, isso pode significar milhares de registos. Evite consultas adicionais ao banco dentro do compute; use os campos já carregados sempre que possível.
Use store=True apenas quando necessário
Campos armazenados ocupam espaço e geram escritas sempre que recomputados. Se o campo serve só para visualização num formulário e não será usado em filtros ou group-by, prefira não armazenar.
Trate casos limite na função de cálculo
Previna divisões por zero, registos relacionados ausentes e valores nulos. Estes são motivos frequentes de erros silenciosos. Implemente verificações e defaults seguros quando não for possível completar o cálculo.
Planeie a recomputação inicial em tabelas grandes
Ao instalar um módulo que adiciona um campo armazenado, o Odoo recalcula-o para todos os registos existentes. Em tabelas com centenas de milhares de linhas, isso pode demorar. Teste a migração em ambiente de staging e prepare janelas de manutenção ou processamento em background para a produção.
Erros comuns
Evite dependências circulares
Se o campo A depende de B e B depende de A, o Odoo levantará um erro. Organize as dependências para que tenham um fluxo unidirecional.
Erros frequentes
Esquecer o store=True
Este é o erro mais comum. O campo parece funcionar no formulário durante os testes, mas falha quando alguém tenta filtrar ou incluir em relatórios. Antes de escrever a lógica, decida se o campo precisa ser pesquisável; em caso afirmativo, inclua store=True desde o início.
Esquecer uma dependência em @api.depends
Se a sua função lê partner_id.country_id mas o decorador só lista partner_id, o campo não será atualizado quando o país mudar. Liste cada passo do caminho em @api.depends.
Erros silenciosos na função de cálculo
Se a função levantar uma exceção para um registo, o Odoo costuma ignorar a recomputação desse registo e manter o valor antigo. O erro aparece nos logs do servidor, mas não para o utilizador. Teste a função com registos que contenham dados em falta ou anormais.
Degradação de performance em conjuntos de dados grandes
Uma função que corre bem em desenvolvimento pode tornar-se um gargalo em produção quando a tabela cresce. Verifique quantas consultas adicionais a função dispara por registo; uma consulta extra por registo multiplicada por milhares de registos é um problema.
Uso de sudo() dentro de computes
Conclusão
Recorrer a sudo() para contornar regras de acesso dentro de computes é um risco de segurança. Se o valor calculado expõe dados que o utilizador não deveria ver, isso quebra o modelo de permissões. Utilize sudo() apenas depois de avaliar cuidadosamente as implicações.
Expectativas sobre recomputação imediata
- Na interactuação normal a recomputação é síncrona, mas em importações em massa, jobs em background ou certas operações ORM com flags de contexto, o Odoo pode adiar recomputações. Não construa lógica empresarial que pressupõe sempre a atualização imediata; teste os cenários onde o campo será usado.
- Resumo — por que usar campos calculados armazenados
- Campos calculados armazenados são uma ferramenta poderosa no Odoo: automatizam cálculos, mantêm dados coerentes e tornam valores pesquisáveis e exportáveis sem intervenção manual.
- Pontos-chave a reter:
- Ative
store=Truequando o campo precisa ser pesquisável, filtrável ou exportável.
Declara todas as dependências em @api.depends(), incluindo caminhos entre modelos.
Otimize as funções de cálculo e trate explicitamente dos casos limite.
Para fórmulas simples, o Studio é uma opção rápida; para lógica complexa, desenvolva em Python.
Planeie a recomputação inicial ao desplegar em tabelas volumosas.