Quen vende paquetes en Odoo, por exemplo un instrumento de medida con accesorios e montaxe, un posto de traballo con hardware e configuración ou un paquete de mantemento composto por varios servizos, necesita no orzamento dúas cousas á vez: a composición ten que figurar no pedido para que o almacén, a área técnica e a facturación saiban o que se entrega. Pero na impresión para o cliente non sempre debe aparecer cada posición individual con cada prezo individual.
O módulo da OCA sale_product_set permite inserir conxuntos de produtos (listas de materiais para a venda) nun orzamento cun só clic. Non decide sobre o aspecto da impresión. Para iso desenvolvemos o módulo Sale Product Set Layout (sale_product_set_layout). Está escrito para Odoo 19 e está dispoñible de balde en GitHub como software libre baixo a licenza AGPL-3:
github.com/Alex0176/odoo-sale-product-set-layout
Contido desta publicación
- O problema: conxuntos de produtos sen influencia na impresión
- O que fai o módulo
- Os tres modos de impresión en detalle
- Liñas ocultas e prezo fixo
- Implementación técnica
- Instalación sen efectos secundarios non desexados
- Manexo paso a paso
- Probas
- Límites e aviso legal
- Preguntas frecuentes
- Apoio de Biricon IT Services
O problema: conxuntos de produtos sen influencia na impresión
Un conxunto de produtos en Odoo é unha lista preparada de produtos con cantidades. En vez de buscar dez artigos un a un, no orzamento escóllese Engadir conxunto e obtéñense todas as liñas de vez. Isto aforra tempo e evita esquecer posicións. O módulo sale_product_set do repositorio OCA/sale-workflow fai exactamente isto; os propios conxuntos proceden de product_set, no repositorio OCA/product-attribute.
Na práctica quedan tres carencias:
- Ningunha agrupación recoñecible. As liñas do conxunto quedan no orzamento como posicións soltas. Despois de inserilas xa non se ve que liñas van xuntas, e o total do conxunto non aparece en ningún sitio.
- Ningunha influencia na impresión. Ás veces o cliente debe ver cada posición co seu prezo, outras só o prezo do paquete, outras só unha única liña. Iso é unha decisión que se toma por conxunto ou por cliente, non que se repite a man en cada orzamento.
- As correccións de prezo son pouco elegantes. Se un paquete debe ter un prezo fixo que difire da suma dos prezos de tarifa, fai falta unha liña de corrección. Esta pertence ao cálculo, pero non necesariamente ao papel do cliente.
Odoo 19 trae os elementos para resolver isto con limpeza: seccións e subseccións nas liñas do orzamento coas opcións Ocultar prezos e Ocultar composición (campos collapse_prices e collapse_composition). O módulo conecta o conxunto de produtos con estas opcións estándar, en vez de inventar un formato de impresión propio.
O que fai o módulo
- Inserir o conxunto como sección. O asistente Engadir conxunto crea unha liña de sección co nome do conxunto, por exemplo 2 × Kit de medida Standard, e coloca debaixo as liñas do conxunto. As seccións dentro do conxunto convértense en subseccións.
- Modo de impresión por conxunto. No conxunto defínese unha vez como se imprime. Ao inserilo, o modo pode cambiarse no asistente para ese caso concreto.
- Ocultar liñas. Pódense marcar liñas individuais como Non imprimir (no conxunto: Oculta). Contan na suma, pero non se imprimen nin se mostran no portal do cliente. A marca pasa do orzamento á factura.
- Prezo fixo por liña do conxunto. Unha liña do conxunto pode levar un prezo unitario fixo que substitúe o prezo da tarifa, por exemplo para unha corrección de prezo oculta.
Sen o módulo (só sale_product_set) | Con sale_product_set_layout |
|---|---|
| Liñas do conxunto como posicións soltas | Liña de sección co nome do conxunto, a cantidade no nome e as liñas debaixo |
| As seccións do conxunto seguen sendo seccións e rompen a estrutura | As seccións do conxunto convértense en subseccións (line_subsection) |
| Aspecto da impresión sempre igual | Modo de impresión por conxunto, modificable ao inserilo |
| Corrección de prezo visible no orzamento e na factura | Liña ocultable, pero que conta na suma |
| Prezo só desde a tarifa | Prezo fixo opcional por liña do conxunto |
| O deseño remata no orzamento | O deseño e a ocultación valen para orzamento, pedido, factura e portal do cliente |
Os tres modos de impresión en detalle
O modo de impresión é un campo de selección print_mode no conxunto de produtos. Ao inserilo tradúcese nos dous interruptores estándar da liña de sección. Para iso o módulo ten unha única asignación, fácil de ler:
# Modo de impresión -> (collapse_prices, collapse_composition) da liña de sección
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Modo (print_mode) | Visualización no conxunto | Opción estándar de Odoo | Aspecto da impresión |
|---|---|---|---|
detailed | Cada liña co prezo | ningunha | Sección, todas as liñas co importe |
total_only | Liñas sen prezos, só a suma do conxunto | collapse_prices (Ocultar prezos) | Sección coa suma, liñas só con designación e cantidade |
summary | Só unha liña coa suma do conxunto | collapse_composition (Ocultar composición) | Só a liña de sección coa suma |
Como o módulo emprega as opcións estándar de Odoo 19 e non un formato de impresión propio, o orzamento, a confirmación do pedido, a factura e o portal do cliente funcionan sen modelos de informe personalizados. Quen teña modelos de informe propios que respecten as opcións de sección tamén obtén ese comportamento.
Liñas ocultas e prezo fixo
Un caso frecuente: un paquete ten un prezo de paquete inferior á suma dos prezos individuais. En vez de manipular os prezos individuais, úsase unha liña de corrección con prezo fixo (por exemplo −50,00 € por conxunto) e ocúltase. No backend a factura é doadamente comprensible; na impresión aparece o prezo do paquete.
Importante para a comprensión: a marca hide_in_report non cambia nada no cálculo. Cantidade, prezo, desconto, imposto, sumas e asentos contables xéranse sen cambios polo Odoo estándar. A liña só falta na impresión e no portal.
- No conxunto de produtos: liña do conxunto con Oculta e, opcionalmente, Prezo fixo xunto co Prezo unitario.
- Nas liñas de orzamento e de factura: columna Non imprimir (
hide_in_report), tamén para liñas normais sen conxunto. - Transmisión: ao crear a factura a partir do pedido, a marca transfírese á liña da factura. Ao copiar un orzamento, consérvase.
Implementación técnica
O módulo é deliberadamente pequeno: amplía modelos existentes e só sobrescribe os puntos nos que Odoo decide que liñas se imprimen e como se insire un conxunto. Non hai novos modelos de informe, nin novos modelos de datos, nin intervencións en SQL.
Modelos e campos ampliados
| Modelo | Ampliación |
|---|---|
product.set | Campos insert_as_section (booleano, activado por defecto) e print_mode (selección, obrigatorio, por defecto detailed) |
product.set.line | Campos hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() transmíteos á liña do orzamento |
sale.product.set.wizard | Campos insert_as_section, section_name, print_mode; _prepare_order_lines() constrúe a liña de sección |
sale.order / sale.order.line | Campo hide_in_report (con copy=True); _get_order_lines_to_report() omite as liñas ocultas; _prepare_invoice_line() transmite a marca |
account.move / account.move.line | Campo hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() e _get_subsection_lines() filtran as liñas ocultas; _get_aml_vals() leva a marca aos datos do informe |
Ocultar no orzamento
Odoo 19 determina as liñas de orzamento que se van imprimir en _get_order_lines_to_report(). O módulo engade alí un filtro. Non é necesario intervir no modelo de informe: tanto o modelo de informe do orzamento (sale/report/ir_actions_report_templates.xml) como o modelo do portal (sale/views/sale_portal_templates.xml) chaman a 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
)
Transmisión á factura
Ao xerar a liña da factura a partir da liña do orzamento (_prepare_invoice_line()) cópiase a marca. Do lado da factura hai en Odoo 19 varias vías polas que as liñas chegan ao informe: a lista principal, as liñas dunha sección e as liñas dunha subsección. Por iso o módulo filtra en varios puntos (_get_move_lines_to_report() no documento, _get_detailed_lines() e _get_subsection_lines() na liña); se non, unha liña oculta podería reaparecer por unha das outras vías:
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 asistente: sección en vez de liñas soltas
O asistente sale.product.set.wizard de sale_product_set xera as liñas do orzamento en _prepare_order_lines(). O módulo sobrescribe este método. Se Inserir como sección está desactivado, chama sen máis ao método orixinal e o comportamento queda como no módulo da OCA. En caso contrario créase primeiro a liña de sección cos dous interruptores estándar e despois seguen as liñas do conxunto. As seccións dentro do conxunto convértense en subseccións para non romper 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
Decisións que non se ven
- Métodos de cálculo separados no asistente. O nome da sección, o modo de impresión e Inserir como sección teñen cada un o seu propio método
compute(store=True,readonly=False). Se os tres compartisen o mesmo método, un valor pasado explicitamente para un campo restablecería os outros. Isto nótase sobre todo cando o asistente se crea con valores desde código ou desde probas. - Nome da sección coa cantidade. Cando a cantidade é distinta de 1, figura no nome (2 × Kit de medida Standard, con coma como separador decimal), para que na impresión se recoñeza que son dous conxuntos e non un conxunto con cantidade dobre.
- Prezo fixo só en liñas de produto.
prepare_sale_order_line_values()só establecehide_in_reporteprice_unitcando a liña do conxunto non é unha liña de sección nin de nota. - Odoo estándar en vez de desenvolvemento propio. O modo de impresión e a ocultación usan ganchos e campos existentes de Odoo 19. Isto fai as actualizacións manexables: se Odoo cambia o modelo de informe, o módulo segue sendo válido mentres os métodos existan.
Instalación sen efectos secundarios non desexados
Requisitos: Odoo 19.0, sale_product_set (OCA/sale-workflow, rama 19.0) e product_set (OCA/product-attribute, rama 19.0). O propio módulo depende de sale_management, sale_product_set e account.
A trampa de auto_install nos módulos da OCA. Moitos repositorios da OCA conteñen módulos con auto_install. En canto están presentes as súas dependencias, Odoo instálaos sen preguntar na seguinte instalación ou actualización. Quen coloque todo o repositorio da OCA no addons_path dunha instancia produtiva pode acabar con módulos que ninguén quería. En OCA/product-attribute isto afecta, por exemplo, a purchase_product_template_tags. O noso procedemento:
- Clonar os dous repositorios da OCA de forma superficial (
git clone --depth 1 --branch 19.0). - Nun directorio propio, ligar mediante enlace simbólico só os dous módulos necesarios e incluír ese directorio no
addons_path, non os repositorios enteiros. - Instalar nunha base de datos de proba e comprobar que módulos pasan a estado instalado.
- Só entón instalar en produción, con
--workers=0, para que a chamada non compita co servizo en execución polo porto HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Os novos campos e vistas están na base de datos despois da instalación. Se o servizo funciona en modo multiproceso, os workers recargan o rexistro modificado a través do sinal da base de datos. Os cambios nos ficheiros Python do módulo só teñen efecto despois de reiniciar o servizo.
Manexo paso a paso
- Crear un conxunto: Vendas → Produtos → Conxuntos de produtos. Dar nome ao conxunto, escoller Inserir como sección e Modo de impresión, introducir os produtos coas cantidades. Marcar as liñas de corrección con Oculta e Prezo fixo.
- No orzamento: facer clic en Engadir conxunto, escoller o conxunto e a cantidade. O nome da sección e o modo de impresión pódense cambiar antes de inserir.
- Ocultar liñas individuais: no orzamento ou na factura, activar a columna Non imprimir.
- Comprobar: abrir a vista previa ou o PDF. Os clientes ven a mesma presentación no portal.
Probas
O módulo inclúe seis probas automatizadas que executamos contra unha base de datos de proba antes de pasar nada a produción:
- Inserción como sección, incluídos o modo de impresión e a subsección
- A liña oculta non figura nas liñas que se van imprimir
- Modo Unha liña (
summary) - Inserción sen sección, o comportamento orixinal
- A factura asume a marca
hide_in_report, a liña non figura en_get_move_lines_to_report()e a suma da sección segue incluíndoa - A factura en modo Só prezo total non amosa a liña oculta nas liñas da sección e a suma da sección queda completa
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
A tradución ao alemán está en i18n/de.po.
Límites e aviso legal
- Só deseño. O módulo non cambia como Odoo calcula prezos, descontos ou impostos. Só cambia o que se imprime e se mostra no portal.
- As liñas ocultas son visibles no backend. A marca afecta á impresión e ao portal, non á vista en pantalla do persoal.
- Comprobar os datos da factura. As liñas ocultas contan na suma, pero non figuran no documento. Se isto é compatible cos requisitos que se aplican ás facturas no seu país, por exemplo coa descrición obrigatoria dos bens entregados ou dos servizos prestados, debería aclaralo coa súa asesoría fiscal antes de usalo en facturas. Nos orzamentos, ocultar é menos problemático. A publicación non é asesoramento xurídico.
- Probado para Odoo 19. Desenvolvemos e probamos o módulo para Odoo 19.0 Community. Usa opcións de sección que existen nesta forma en Odoo 19. Non o comprobamos para outras versións.
Preguntas frecuentes
Para que necesito o módulo se Odoo 19 xa ten seccións?
As opcións de sección existen en Odoo mesmo. Faltaba a conexión co conxunto de produtos: un conxunto debía inserirse automaticamente como sección, co modo de impresión gardado no conxunto. Sen o módulo hai que crear e configurar a man a liña de sección despois de cada inserción.
En que se diferencian “Ocultar prezos” e “Ocultar composición”?
“Ocultar prezos” (collapse_prices) deixa as liñas, pero non mostra importes, só a suma na sección. “Ocultar composición” (collapse_composition) oculta as liñas por completo, e o cliente só ve a sección coa suma.
Conta unha liña oculta na suma da factura?
Si. As liñas ocultas permanecen na suma, no imposto e no asento contable. A marca só afecta á presentación na impresión e no portal do cliente.
Podo cambiar o modo de impresión ao inserir?
Si. O modo vén por defecto desde o conxunto e no asistente pódese cambiar para cada inserción. O conxunto en si non se modifica.
Que pasa se desactivo Inserir como sección?
Entón o asistente compórtase como no módulo da OCA sale_product_set: as liñas do conxunto insírense sen sección.
Instala o módulo outros módulos de forma inesperada?
O módulo en si non. Pero cos repositorios da OCA pode haber módulos auto_install se se colocan repositorios enteiros no addons_path. Por iso ligue só os dous módulos necesarios (véxase a sección Instalación).
Funciona o módulo con Odoo Enterprise?
O módulo só depende de módulos Community (sale_management, account) e dos dous módulos da OCA. Usámolo en Odoo 19 Community. En Enterprise non o probamos.
Baixo que licenza está o módulo?
Baixo a AGPL-3. Pode usalo, adaptalo e distribuílo, sempre que os cambios permanezan baixo a mesma licenza. Os erros e as propostas de mellora son benvidos como issue no repositorio de GitHub .
Apoio de Biricon IT Services
Atendemos instalacións de Odoo para pequenas e medianas empresas: desde a implantación, pasando por adaptacións e migracións, ata a operación continua. Se quere adaptar aos seus procesos conxuntos de produtos, modelos de orzamento ou informes en Odoo 19, ou se quere revisar un módulo coma este e levalo con seguridade á súa instancia produtiva, axudarémolo con gusto. Pode atopar máis desenvolvementos propios no noso blog, por exemplo Fusionar facturas en bosquexo e Asignar pagamentos bancarios automaticamente a facturas.
Data: 2 de novembro de 2026. Versión do módulo 19.0.1.0.0. Os datos de exemplo das ilustracións están elixidos libremente, importes netos.