Pular para o conteúdo

Odoo 19 Community: conjuntos de produtos no orçamento como secção – o nosso módulo open source Sale Product Set Layout

2 de novembro de 2026 | Odoo 19 Community | Desenvolvimento próprio
2 de novembro de 2026 por
Odoo 19 Community: conjuntos de produtos no orçamento como secção – o nosso módulo open source Sale Product Set Layout

Módulo Odoo 19 Sale Product Set Layout: conjuntos de produtos como secção no orçamento com modo de impressão, linhas ocultas e preço fixo

Quem vende pacotes no Odoo, por exemplo um aparelho de medição com acessórios e montagem, um posto de trabalho com hardware e configuração, ou um pacote de manutenção composto por vários serviços, precisa de duas coisas em simultâneo no orçamento: a composição tem de constar da encomenda, para que o armazém, a equipa técnica e a faturação saibam o que é fornecido. Na impressão para o cliente, porém, nem sempre deve aparecer cada posição individual com cada preço individual.

O módulo OCA sale_product_set permite inserir conjuntos de produtos (listas de materiais para a venda) num orçamento com um só clique. Não decide, porém, sobre o aspeto da impressão. Para isso desenvolvemos o módulo Sale Product Set Layout (sale_product_set_layout). Está escrito para o Odoo 19 e está disponível gratuitamente no GitHub como software livre sob a licença AGPL-3:

github.com/Alex0176/odoo-sale-product-set-layout

O problema: conjuntos de produtos sem influência no aspeto da impressão

No Odoo, um conjunto de produtos é uma lista preparada de produtos com quantidades. Em vez de procurar dez artigos um a um, escolhe-se no orçamento Adicionar conjunto e obtêm-se todas as linhas de uma só vez. Isto poupa tempo e evita posições esquecidas. O módulo sale_product_set do repositório OCA/sale-workflow faz precisamente isso; os próprios conjuntos provêm de product_set, no repositório OCA/product-attribute.

Na prática, subsistem três lacunas:

  • Nenhum agrupamento visível. As linhas do conjunto ficam no orçamento como posições soltas. Depois de inseridas, já não se vê quais as linhas que pertencem umas às outras, e o total do conjunto não consta de lado nenhum.
  • Nenhuma influência na impressão. Umas vezes o cliente deve ver cada posição com preço, outras vezes apenas o preço do pacote, outras ainda uma única linha. Esta é uma decisão por conjunto ou por cliente, não algo a fazer de novo à mão em cada orçamento.
  • As correções de preço são desagradáveis. Se um pacote deve ter um preço fixo que difere da soma dos preços de tabela, é necessária uma linha de correção. Esta pertence ao cálculo, mas não necessariamente ao papel do cliente.

O Odoo 19 traz os componentes para resolver isto de forma limpa: secções e subsecções nas linhas de orçamento com as opções Ocultar preços e Ocultar composição (campos collapse_prices e collapse_composition). O módulo liga o conjunto de produtos a estas opções padrão, em vez de inventar um formato de impressão próprio.

O que o módulo faz

  1. Inserir o conjunto como secção. O assistente Adicionar conjunto cria uma linha de secção com o nome do conjunto, por exemplo 2 × Kit de medição Standard, e coloca as linhas do conjunto por baixo. As secções dentro do conjunto passam a subsecções.
  2. Modo de impressão por conjunto. No conjunto define-se uma só vez como é impresso. Ao inserir, o modo pode ser alterado no assistente para esse caso concreto.
  3. Ocultar linhas. Linhas individuais podem ser marcadas como Não imprimir (no conjunto: Oculto). Contam para o total, mas não são impressas nem apresentadas no portal do cliente. A marcação passa do orçamento para a fatura.
  4. Preço fixo por linha do conjunto. Uma linha do conjunto pode ter um preço unitário fixo que substitui o preço da lista de preços, por exemplo para uma correção de preço oculta.
Sem o módulo (apenas sale_product_set)Com sale_product_set_layout
Linhas do conjunto como posições soltasLinha de secção com o nome do conjunto, quantidade no nome, linhas por baixo
As secções no conjunto continuam a ser secções e quebram a estruturaAs secções no conjunto passam a subsecções (line_subsection)
Aspeto de impressão sempre igualModo de impressão por conjunto, alterável ao inserir
Correção de preço visível no orçamento e na faturaLinha ocultável, mas conta para o total
Preço apenas a partir da lista de preçosPreço fixo opcional por linha do conjunto
O layout termina no orçamentoO layout e a ocultação aplicam-se ao orçamento, à encomenda, à fatura e ao portal do cliente

Os três modos de impressão em pormenor

O modo de impressão é um campo de seleção print_mode no conjunto de produtos. Ao inserir, é convertido nos dois interruptores padrão da linha de secção. Para isso, o módulo contém um único mapeamento, fácil de ler:

# Modo de impressão -> (collapse_prices, collapse_composition) da linha de secção
PRINT_MODE_FLAGS = {
    "detailed":   (False, False),
    "total_only": (True,  False),
    "summary":    (False, True),
}

Três aspetos de impressão do mesmo conjunto de produtos: cada linha com preço, apenas o preço total, uma única linha

Modo (print_mode)Apresentação no conjuntoOpção padrão no OdooAspeto de impressão
detailedCada linha com preçonenhumaSecção, todas as linhas com montante
total_onlyLinhas sem preços, apenas o total do conjuntocollapse_prices (Ocultar preços)Secção com total, linhas apenas com designação e quantidade
summaryApenas uma linha com o total do conjuntocollapse_composition (Ocultar composição)Apenas a linha de secção com o total

Como o módulo utiliza as opções padrão do Odoo 19 e não um formato de impressão próprio, o orçamento, a confirmação de encomenda, a fatura e o portal do cliente funcionam sem modelos de relatório personalizados. Quem tiver modelos de relatório próprios que tenham em conta as opções de secção obtém igualmente este comportamento.

Linhas ocultas e preço fixo

Um caso frequente: um pacote tem um preço de pacote inferior à soma dos preços individuais. Em vez de distorcer os preços individuais, utiliza-se uma linha de correção com preço fixo (por exemplo −50,00 € por conjunto) e oculta-se. No backend, a fatura é rastreável; na impressão consta o preço do pacote.

Correção de preço visível e marcada no backend, não visível na impressão do orçamento, do PDF, da fatura e do portal, total idêntico

Importante para a compreensão: a marcação hide_in_report não altera nada no cálculo. Quantidade, preço, desconto, imposto, totais e lançamentos contabilísticos resultam inalterados do Odoo padrão. A linha apenas falta na impressão e no portal.

  • No conjunto de produtos: linha do conjunto com Oculto e, opcionalmente, Preço fixo com Preço unitário.
  • Nas linhas de orçamento e de fatura: coluna Não imprimir (hide_in_report), também para linhas normais sem conjunto.
  • Transmissão: ao criar a fatura a partir da encomenda, a marcação é transferida para a linha da fatura. Ao copiar um orçamento, é mantida.

Implementação técnica

O módulo é deliberadamente pequeno: estende modelos existentes e substitui apenas os pontos em que o Odoo decide que linhas são impressas e como um conjunto é inserido. Não há novos modelos de relatório, nem novos modelos de dados, nem intervenções em SQL.

Fluxo de dados: conjunto de produtos, assistente Adicionar conjunto, linhas de orçamento, linhas de fatura, PDF e portal do cliente

Modelos e campos estendidos

ModeloComplemento
product.setCampos insert_as_section (booleano, ativo por predefinição) e print_mode (seleção, obrigatório, predefinição detailed)
product.set.lineCampos hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() transmite-os à linha de orçamento
sale.product.set.wizardCampos insert_as_section, section_name, print_mode; _prepare_order_lines() constrói a linha de secção
sale.order / sale.order.lineCampo hide_in_report (com copy=True); _get_order_lines_to_report() ignora as linhas ocultas; _prepare_invoice_line() transmite a marcação
account.move / account.move.lineCampo hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() e _get_subsection_lines() filtram as linhas ocultas; _get_aml_vals() inclui a marcação nos dados do relatório

Ocultar no orçamento

O Odoo 19 determina as linhas de orçamento a imprimir em _get_order_lines_to_report(). O módulo acrescenta aí um filtro. Não é necessária qualquer intervenção no modelo de relatório: tanto o modelo de relatório do orçamento (sale/report/ir_actions_report_templates.xml) como o modelo do portal (sale/views/sale_portal_templates.xml) invocam este método:

class SaleOrder(models.Model):
    _inherit = "sale.order"

    def _get_order_lines_to_report(self):
        return super()._get_order_lines_to_report().filtered(
            lambda line: not line.hide_in_report
        )

Transmissão para a fatura

Ao gerar a linha de fatura a partir da linha de orçamento (_prepare_invoice_line()), a marcação é copiada. Do lado da fatura, o Odoo 19 tem vários caminhos pelos quais as linhas chegam ao relatório: a lista principal, as linhas de uma secção e as linhas de uma subsecção. Por isso, o módulo filtra em vários pontos (_get_move_lines_to_report() no documento, _get_detailed_lines() e _get_subsection_lines() na linha); caso contrário, uma linha oculta poderia reaparecer por um dos outros caminhos:

class AccountMoveLine(models.Model):
    _inherit = "account.move.line"

    hide_in_report = fields.Boolean(string="Hidden in report")

    def _get_aml_vals(self, hide_taxes):
        res = super()._get_aml_vals(hide_taxes)
        res["hide_in_report"] = self.hide_in_report
        return res

    def _get_detailed_lines(self, children_lines, direct_children_lines, subsection_lines):
        res = super()._get_detailed_lines(children_lines, direct_children_lines, subsection_lines)
        return [vals for vals in res if not vals.get("hide_in_report")]

    def _get_subsection_lines(self, subsection_line, children_lines):
        res = super()._get_subsection_lines(subsection_line, children_lines)
        return [vals for vals in res if not vals.get("hide_in_report")]

O assistente: secção em vez de linhas soltas

O assistente sale.product.set.wizard de sale_product_set gera as linhas de orçamento em _prepare_order_lines(). O módulo substitui este método. Se Inserir como secção estiver desativado, limita-se a invocar o método original, e o comportamento mantém-se como no módulo OCA. Caso contrário, é criada primeiro a linha de secção com os dois interruptores padrão e, a seguir, as linhas do conjunto. As secções dentro do conjunto passam a subsecções, para não quebrarem a estrutura principal:

def _prepare_order_lines(self):
    if not self.insert_as_section:
        return super()._prepare_order_lines()
    sequence = self._get_max_sequence() + 1
    collapse_prices, collapse_composition = PRINT_MODE_FLAGS[self.print_mode or "detailed"]
    order_lines = [Command.create({
        "display_type": "line_section",
        "name": self.section_name or self.product_set_id.name,
        "sequence": sequence,
        "collapse_prices": collapse_prices,
        "collapse_composition": collapse_composition,
    })]
    for set_line in self._get_lines():
        sequence += 1
        values = set_line.prepare_sale_order_line_values(self.order_id, self.quantity, sequence)
        if values.get("display_type") == "line_section":
            values["display_type"] = "line_subsection"
        order_lines.append(Command.create(values))
    return order_lines

Decisões que não se veem

  • Métodos de cálculo separados no assistente. O nome da secção, o modo de impressão e Inserir como secção têm, cada um, o seu próprio método compute (store=True, readonly=False). Se os três partilhassem o mesmo método, um valor passado explicitamente para um campo repunha os outros. Isto nota-se sobretudo quando o assistente é criado com valores a partir de código ou de testes.
  • Nome da secção com quantidade. Quando a quantidade é diferente de 1, consta do nome (2 × Kit de medição Standard, com vírgula como separador decimal), para que na impressão se perceba que são dois conjuntos e não um conjunto com o dobro da quantidade.
  • Preço fixo apenas em linhas de produto. prepare_sale_order_line_values() define hide_in_report e price_unit apenas quando a linha do conjunto não é uma linha de secção ou de nota.
  • Odoo padrão em vez de solução própria. O modo de impressão e a ocultação utilizam ganchos e campos existentes do Odoo 19. Isto torna as atualizações controláveis: se o Odoo alterar o modelo de relatório, o módulo continua válido enquanto os métodos existirem.

Instalação sem efeitos secundários indesejados

Requisitos: Odoo 19.0, sale_product_set (OCA/sale-workflow, ramo 19.0) e product_set (OCA/product-attribute, ramo 19.0). O próprio módulo depende de sale_management, sale_product_set e account.

A armadilha do auto_install nos módulos OCA. Muitos repositórios OCA contêm módulos com auto_install. Assim que as respetivas dependências estão presentes, o Odoo instala-os sem perguntar na instalação ou atualização seguinte. Quem, por isso, colocar o repositório OCA completo no addons_path de uma instância de produção arrisca-se a ficar com módulos que ninguém queria. No caso de OCA/product-attribute, isto afeta, por exemplo, purchase_product_template_tags. O nosso procedimento:

  1. Clonar superficialmente os dois repositórios OCA (git clone --depth 1 --branch 19.0).
  2. Num diretório próprio, ligar por symlink apenas os dois módulos necessários e incluir esse diretório no addons_path, não os repositórios inteiros.
  3. Instalar numa base de dados de teste e verificar que módulos passam a ter o estado instalado.
  4. Só então instalar em produção, com --workers=0, para que a chamada não dispute a porta HTTP com o serviço em execução.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init

Os novos campos e vistas ficam na base de dados após a instalação. Se o serviço funcionar em modo multiprocesso, os workers recarregam o registo alterado através do sinal da base de dados. As alterações aos ficheiros Python do módulo só entram em vigor após o reinício do serviço.

Utilização passo a passo

  1. Criar um conjunto: Vendas → Produtos → Conjuntos de produtos. Dar um nome ao conjunto, escolher Inserir como secção e Modo de impressão, introduzir os produtos com quantidades. Atribuir Oculto e Preço fixo às linhas de correção.
  2. No orçamento: clicar em Adicionar conjunto, escolher o conjunto e a quantidade. O nome da secção e o modo de impressão podem ser alterados antes da inserção.
  3. Ocultar linhas individuais: no orçamento ou na fatura, ativar a coluna Não imprimir.
  4. Verificar: abrir a pré-visualização ou o PDF. Os clientes veem a mesma apresentação no portal.

Testes

O módulo inclui seis testes automatizados, que executamos contra uma base de dados de teste antes de passar o que quer que seja para produção:

  • Inserção como secção, incluindo modo de impressão e subsecção
  • A linha oculta não consta das linhas a imprimir
  • Modo Uma linha (summary)
  • Inserção sem secção, o comportamento original
  • A fatura assume a marcação hide_in_report, a linha não consta de _get_move_lines_to_report(), o total da secção continua a incluí-la
  • A fatura no modo Apenas preço total não mostra a linha oculta nas linhas da secção, o total da secção permanece completo
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
         --test-enable --test-tags=/sale_product_set_layout --stop-after-init

A tradução alemã encontra-se em i18n/de.po.

Limitações e aviso legal

  • Apenas layout. O módulo não altera a forma como o Odoo calcula preços, descontos ou impostos. Altera apenas o que é impresso e apresentado no portal.
  • As linhas ocultas são visíveis no backend. A marcação diz respeito à impressão e ao portal, não ao ecrã dos colaboradores.
  • Verificar os dados da fatura. As linhas ocultas contam para o total, mas não constam do documento. Se isto é compatível com os requisitos aplicáveis às faturas no seu país, por exemplo com a descrição obrigatória dos bens fornecidos ou dos serviços prestados, deve esclarecê-lo com o seu consultor fiscal antes de utilizar em faturas. Nos orçamentos, a ocultação é menos problemática. O artigo não constitui aconselhamento jurídico.
  • Testado para o Odoo 19. Desenvolvemos e testámos o módulo para o Odoo 19.0 Community. Utiliza opções de secção que existem desta forma no Odoo 19. Não o verificámos para outras versões.

Perguntas frequentes

Para que preciso do módulo se o Odoo 19 já suporta secções?

As opções de secção existem no próprio Odoo. Faltava a ligação ao conjunto de produtos: um conjunto devia ser inserido automaticamente como secção, com o modo de impressão definido no conjunto. Sem o módulo, é preciso criar e configurar manualmente a linha de secção após cada inserção.

Qual é a diferença entre «Ocultar preços» e «Ocultar composição»?

«Ocultar preços» (collapse_prices) mantém as linhas, mas não mostra montantes, apenas o total na secção. «Ocultar composição» (collapse_composition) oculta as linhas por completo, e o cliente vê apenas a secção com o total.

Uma linha oculta conta para o total da fatura?

Sim. As linhas ocultas permanecem no total, no imposto e no lançamento contabilístico. A marcação diz respeito apenas à apresentação na impressão e no portal do cliente.

Posso ainda alterar o modo de impressão ao inserir?

Sim. O modo vem como predefinição do conjunto e pode ser alterado no assistente para a inserção em causa. O próprio conjunto permanece inalterado.

O que acontece se desativar Inserir como secção?

Nesse caso, o assistente comporta-se como no módulo OCA sale_product_set: as linhas do conjunto são inseridas sem secção.

O módulo instala inesperadamente outros módulos?

O próprio módulo não. Nos repositórios OCA, porém, pode haver módulos auto_install se se colocarem repositórios inteiros no addons_path. Por isso, ligue apenas os dois módulos necessários (ver a secção Instalação).

O módulo funciona com o Odoo Enterprise?

O módulo depende apenas de módulos Community (sale_management, account) e dos dois módulos OCA. Utilizamo-lo no Odoo 19 Community. No Enterprise, não o testámos.

Sob que licença está o módulo?

Sob a AGPL-3. Pode utilizá-lo, adaptá-lo e distribuí-lo, desde que as alterações permaneçam sob a mesma licença. Erros e sugestões de melhoria são bem-vindos como issue no repositório GitHub .

Apoio da Biricon IT Services

Damos assistência a instalações Odoo para pequenas e médias empresas: da implementação, passando por adaptações e migrações, até à operação corrente. Se pretende adaptar conjuntos de produtos, modelos de orçamento ou relatórios no Odoo 19 aos seus processos, ou se quer verificar um módulo como este e colocá-lo em segurança na sua instância de produção, teremos todo o gosto em apoiá-lo. Pode encontrar outros desenvolvimentos próprios no nosso blogue, por exemplo Unir faturas rascunho e Atribuir automaticamente pagamentos bancários a faturas.

Contacte-nos agora

Situação em: 2 de novembro de 2026. Versão do módulo 19.0.1.0.0. Os dados de exemplo nas figuras são de livre escolha, montantes líquidos.