Quien vende paquetes en Odoo, por ejemplo un equipo de medición con accesorios y montaje, un puesto de trabajo con hardware e instalación o un paquete de mantenimiento compuesto por varios servicios, necesita dos cosas a la vez en el presupuesto: la composición debe figurar en el pedido, para que almacén, técnica y facturación sepan qué se entrega. Pero en la impresión para el cliente no siempre debe aparecer cada posición con su precio individual.
El módulo OCA sale_product_set permite insertar conjuntos de productos (listas de materiales para la venta) en un presupuesto con un solo clic. Sin embargo, no decide nada sobre el formato de impresión. Para ello hemos desarrollado el módulo Sale Product Set Layout (sale_product_set_layout). Está escrito para Odoo 19 y está disponible gratuitamente en GitHub como software libre bajo la licencia AGPL-3:
github.com/Alex0176/odoo-sale-product-set-layout
Contenido de este artículo
- El problema: conjuntos de productos sin influencia en el formato de impresión
- Qué hace el módulo
- Los tres modos de impresión en detalle
- Líneas ocultas y precio fijo
- Implementación técnica
- Instalación sin efectos secundarios no deseados
- Manejo paso a paso
- Pruebas
- Límites y aviso legal
- Preguntas frecuentes
- Asistencia de Biricon IT Services
El problema: conjuntos de productos sin influencia en el formato de impresión
En Odoo, un conjunto de productos es una lista preparada de productos con cantidades. En lugar de buscar diez artículos uno por uno, en el presupuesto se elige Añadir conjunto y se obtienen todas las líneas de una vez. Esto ahorra tiempo y evita olvidar posiciones. El módulo sale_product_set del repositorio OCA/sale-workflow hace exactamente eso; los conjuntos proceden de product_set, del repositorio OCA/product-attribute.
En la práctica quedan tres carencias:
- Sin agrupación reconocible. Las líneas del conjunto acaban como posiciones sueltas en el presupuesto. Tras insertarlas, ya no se ve qué líneas van juntas, y el total del conjunto no aparece en ningún sitio.
- Sin influencia en la impresión. A veces el cliente debe ver cada posición con su precio, otras solo el precio del paquete y otras una única línea. Es una decisión que se toma por conjunto o por cliente, no algo que haya que hacer a mano en cada presupuesto.
- Las correcciones de precio resultan poco elegantes. Si un paquete debe tener un precio fijo distinto de la suma de los precios de tarifa, hace falta una línea de corrección. Esta pertenece al cálculo, pero no necesariamente al papel del cliente.
Odoo 19 incluye los componentes para resolverlo limpiamente: secciones y subsecciones en las líneas del presupuesto con las opciones Ocultar precios y Ocultar composición (campos collapse_prices y collapse_composition). El módulo conecta el conjunto de productos con estas opciones estándar en lugar de inventar un formato de impresión propio.
Qué hace el módulo
- Insertar el conjunto como sección. El asistente Añadir conjunto crea una línea de sección con el nombre del conjunto, por ejemplo 2 × Kit de medición estándar, y coloca debajo las líneas del conjunto. Las secciones dentro del conjunto pasan a ser subsecciones.
- Modo de impresión por conjunto. En el conjunto se define una sola vez cómo se imprime. Al insertarlo, el modo puede modificarse en el asistente para ese caso concreto.
- Ocultar líneas. Es posible marcar líneas individuales como No imprimir (en el conjunto: Oculto). Cuentan en el total, pero no se imprimen ni se muestran en el portal de clientes. La marca pasa del presupuesto a la factura.
- Precio fijo por línea del conjunto. Una línea del conjunto puede llevar un precio unitario fijo que sustituye al precio de la tarifa, por ejemplo para una corrección de precio oculta.
Sin el módulo (solo sale_product_set) | Con sale_product_set_layout |
|---|---|
| Líneas del conjunto como posiciones sueltas | Línea de sección con el nombre del conjunto, cantidad en el nombre y líneas debajo |
| Las secciones del conjunto siguen siendo secciones y rompen la estructura | Las secciones del conjunto pasan a ser subsecciones (line_subsection) |
| Formato de impresión siempre igual | Modo de impresión por conjunto, modificable al insertar |
| Corrección de precio visible en el presupuesto y la factura | Línea ocultable, pero cuenta en el total |
| Precio solo según la tarifa | Opcionalmente, precio fijo por línea del conjunto |
| El formato termina en el presupuesto | El formato y la ocultación se aplican al presupuesto, al pedido, a la factura y al portal de clientes |
Los tres modos de impresión en detalle
El modo de impresión es un campo de selección print_mode en el conjunto de productos. Al insertarlo se traduce a los dos interruptores estándar de la línea de sección. Para ello, el módulo contiene una única asignación fácil de leer:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Modo (print_mode) | Visualización en el conjunto | Opción estándar en Odoo | Formato de impresión |
|---|---|---|---|
detailed | Cada línea con precio | ninguna | Sección, todas las líneas con importe |
total_only | Líneas sin precios, solo el total del conjunto | collapse_prices (Ocultar precios) | Sección con total, líneas solo con descripción y cantidad |
summary | Solo una línea con el total del conjunto | collapse_composition (Ocultar composición) | Solo la línea de sección con el total |
Dado que el módulo utiliza las opciones estándar de Odoo 19 y no un formato de impresión propio, el presupuesto, la confirmación de pedido, la factura y el portal de clientes funcionan sin plantillas de informe personalizadas. Quien tenga plantillas de informe propias que tengan en cuenta las opciones de sección obtiene también ese comportamiento.
Líneas ocultas y precio fijo
Un caso frecuente: un paquete tiene un precio de paquete inferior a la suma de los precios individuales. En lugar de manipular los precios individuales, se utiliza una línea de corrección con precio fijo (por ejemplo −50,00 € por conjunto) y se oculta. En el backend la factura es trazable; en la impresión figura el precio del paquete.
Importante para entenderlo: la marca hide_in_report no cambia nada en el cálculo. Cantidad, precio, descuento, impuesto, totales y asientos contables se generan sin cambios mediante Odoo estándar. La línea solo falta en la impresión y en el portal.
- En el conjunto de productos: línea del conjunto con Oculto y, opcionalmente, Precio fijo junto con Precio unitario.
- En las líneas de presupuesto y de factura: columna No imprimir (
hide_in_report), también para líneas normales sin conjunto. - Transmisión: al crear la factura a partir del pedido, la marca se traspasa a la línea de factura. Al duplicar un presupuesto se conserva.
Implementación técnica
El módulo es deliberadamente pequeño: amplía modelos existentes y solo sobrescribe los puntos en los que Odoo decide qué líneas se imprimen y cómo se inserta un conjunto. No hay plantillas de informe nuevas, ni modelos nuevos, ni intervenciones en SQL.
Modelos y campos ampliados
| Modelo | Ampliación |
|---|---|
product.set | Campos insert_as_section (booleano, activado por defecto) y print_mode (selección, obligatorio, por defecto detailed) |
product.set.line | Campos hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() los transmite a la línea del presupuesto |
sale.product.set.wizard | Campos insert_as_section, section_name, print_mode; _prepare_order_lines() construye la línea de sección |
sale.order / sale.order.line | Campo hide_in_report (con copy=True); _get_order_lines_to_report() omite las líneas ocultas; _prepare_invoice_line() transmite la marca |
account.move / account.move.line | Campo hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() y _get_subsection_lines() filtran las líneas ocultas; _get_aml_vals() incorpora la marca a los datos del informe |
Ocultar en el presupuesto
Odoo 19 determina las líneas de presupuesto que se imprimen en _get_order_lines_to_report(). El módulo añade ahí un filtro. No es necesario intervenir en la plantilla del informe: tanto la plantilla del informe del presupuesto (sale/report/ir_actions_report_templates.xml) como la plantilla del portal (sale/views/sale_portal_templates.xml) llaman 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 a la factura
Al generar la línea de factura a partir de la línea de presupuesto (_prepare_invoice_line()), se copia la marca. En el lado de la factura, Odoo 19 ofrece varias vías por las que las líneas llegan al informe: la lista principal, las líneas de una sección y las líneas de una subsección. Por eso el módulo filtra en varios puntos (_get_move_lines_to_report() en el documento, _get_detailed_lines() y _get_subsection_lines() en la línea); de lo contrario, una línea oculta podría reaparecer por una de las otras 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")]
El asistente: sección en lugar de líneas sueltas
El asistente sale.product.set.wizard de sale_product_set genera las líneas del presupuesto en _prepare_order_lines(). El módulo sobrescribe este método. Si Insertar como sección está desactivado, simplemente llama al método original y el comportamiento sigue siendo el del módulo OCA. En caso contrario, primero se crea la línea de sección con los dos interruptores estándar y después siguen las líneas del conjunto. Las secciones dentro del conjunto pasan a ser subsecciones para no romper la estructura 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
Decisiones que no se ven
- Métodos de cálculo separados en el asistente. El nombre de la sección, el modo de impresión e Insertar como sección tienen cada uno su propio método
compute(store=True,readonly=False). Si los tres compartieran el mismo método, un valor pasado explícitamente para un campo restablecería los demás. Esto se nota sobre todo cuando el asistente se crea con valores desde código o pruebas. - Nombre de la sección con cantidad. Cuando la cantidad es distinta de 1, aparece en el nombre (2 × Kit de medición estándar, con coma como separador decimal), para que en la impresión se reconozca que son dos conjuntos y no un conjunto con cantidad doble.
- Precio fijo solo en líneas de producto.
prepare_sale_order_line_values()establecehide_in_reportyprice_unitsolo si la línea del conjunto no es una línea de sección ni de nota. - Odoo estándar en lugar de desarrollo propio. El modo de impresión y la ocultación utilizan ganchos y campos existentes de Odoo 19. Esto hace que las actualizaciones sean manejables: si Odoo cambia la plantilla del informe, el módulo sigue siendo válido mientras los métodos existan.
Instalación sin efectos secundarios no deseados
Requisitos: Odoo 19.0, sale_product_set (OCA/sale-workflow, rama 19.0) y product_set (OCA/product-attribute, rama 19.0). El propio módulo depende de sale_management, sale_product_set y account.
La trampa de auto_install en los módulos OCA. Muchos repositorios OCA contienen módulos con auto_install. En cuanto están presentes sus dependencias, Odoo los instala sin preguntar en la siguiente instalación o actualización. Quien coloque por ello el repositorio OCA completo en el addons_path de una instancia de producción puede acabar con módulos que nadie quería. En OCA/product-attribute afecta, por ejemplo, a purchase_product_template_tags. Nuestro procedimiento:
- Clonar de forma superficial los dos repositorios OCA (
git clone --depth 1 --branch 19.0). - En un directorio propio, enlazar mediante enlace simbólico solo los dos módulos necesarios e incluir ese directorio en el
addons_path, no los repositorios completos. - Instalar en una base de datos de prueba y comprobar qué módulos pasan a tener el estado instalado.
- Solo entonces instalar en producción, con
--workers=0, para que la llamada no compita con el servicio en ejecución por el puerto HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Los nuevos campos y vistas están en la base de datos tras la instalación. Si el servicio se ejecuta en modo multiproceso, los workers recargan el registro modificado mediante la señal de la base de datos. Los cambios en los archivos Python del módulo solo surten efecto tras reiniciar el servicio.
Manejo paso a paso
- Crear un conjunto: Ventas → Productos → Conjuntos de productos. Dar nombre al conjunto, elegir Insertar como sección y Modo de impresión e introducir los productos con sus cantidades. Marcar las líneas de corrección con Oculto y Precio fijo.
- En el presupuesto: hacer clic en Añadir conjunto y elegir el conjunto y la cantidad. El nombre de la sección y el modo de impresión pueden modificarse antes de insertar.
- Ocultar líneas individuales: en el presupuesto o en la factura, activar la columna No imprimir.
- Comprobar: abrir la vista previa o el PDF. Los clientes ven la misma presentación en el portal.
Pruebas
El módulo incluye seis pruebas automatizadas que ejecutamos contra una base de datos de prueba antes de pasar nada a producción:
- Inserción como sección, incluidos el modo de impresión y la subsección
- La línea oculta no figura entre las líneas que se imprimen
- Modo Una línea (
summary) - Inserción sin sección, el comportamiento original
- La factura adopta la marca
hide_in_report, la línea no figura en_get_move_lines_to_report(), el total de la sección sigue incluyéndola - La factura en modo Solo precio total no muestra la línea oculta en las líneas de la sección, el total de la sección permanece completo
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
La traducción al alemán se encuentra en i18n/de.po.
Límites y aviso legal
- Solo formato. El módulo no cambia la forma en que Odoo calcula precios, descuentos o impuestos. Solo cambia lo que se imprime y se muestra en el portal.
- Las líneas ocultas son visibles en el backend. La marca afecta a la impresión y al portal, no a la vista en pantalla de los empleados.
- Compruebe los datos de la factura. Las líneas ocultas cuentan en el total, pero no figuran en el documento. Si esto es compatible con los requisitos que se aplican a las facturas en su país, por ejemplo con la descripción obligatoria de los bienes entregados o de los servicios prestados, debería aclararlo con su asesoría fiscal antes de utilizarlo en facturas. En los presupuestos, ocultar líneas es menos problemático. Este artículo no constituye asesoramiento jurídico.
- Probado para Odoo 19. Hemos desarrollado y probado el módulo para Odoo 19.0 Community. Utiliza opciones de sección que existen de esta forma en Odoo 19. No lo hemos comprobado para otras versiones.
Preguntas frecuentes
¿Para qué necesito el módulo si Odoo 19 ya admite secciones?
Las opciones de sección existen en el propio Odoo. Lo que faltaba era la conexión con el conjunto de productos: un conjunto debería insertarse automáticamente como sección, con el modo de impresión definido en el conjunto. Sin el módulo, hay que crear y configurar a mano la línea de sección después de cada inserción.
¿En qué se diferencian «Ocultar precios» y «Ocultar composición»?
«Ocultar precios» (collapse_prices) deja las líneas, pero no muestra importes, solo el total en la sección. «Ocultar composición» (collapse_composition) oculta las líneas por completo; el cliente solo ve la sección con el total.
¿Cuenta una línea oculta en el total de la factura?
Sí. Las líneas ocultas permanecen en el total, el impuesto y la contabilización. La marca solo afecta a la presentación en la impresión y en el portal de clientes.
¿Puedo cambiar el modo de impresión al insertar?
Sí. El modo viene como valor predeterminado del conjunto y en el asistente puede cambiarse para cada inserción. El conjunto en sí no se modifica.
¿Qué ocurre si desactivo Insertar como sección?
En ese caso el asistente se comporta como en el módulo OCA sale_product_set: las líneas del conjunto se insertan sin sección.
¿El módulo instala otros módulos de forma inesperada?
El módulo en sí, no. Pero con los repositorios OCA pueden aparecer módulos auto_install si se colocan repositorios completos en el addons_path. Por eso, enlace únicamente los dos módulos necesarios (véase la sección Instalación).
¿Funciona el módulo con Odoo Enterprise?
El módulo solo depende de módulos Community (sale_management, account) y de los dos módulos OCA. Lo utilizamos en Odoo 19 Community. No lo hemos probado en Enterprise.
¿Bajo qué licencia se distribuye el módulo?
Bajo la AGPL-3. Puede utilizarlo, adaptarlo y redistribuirlo, siempre que las modificaciones se mantengan bajo la misma licencia. Los errores y las sugerencias de mejora son bienvenidos como issue en el repositorio de GitHub .
Asistencia de Biricon IT Services
Gestionamos instalaciones de Odoo para pequeñas y medianas empresas: desde la implantación, pasando por las adaptaciones y las migraciones, hasta la explotación diaria. Si desea adaptar a sus procesos los conjuntos de productos, las plantillas de presupuesto o los informes en Odoo 19, o si desea revisar un módulo como este e incorporarlo con seguridad a su instancia de producción, estaremos encantados de ayudarle. Encontrará más desarrollos propios en nuestro blog, por ejemplo Fusionar facturas en borrador y Asignar automáticamente los pagos bancarios a las facturas.
Situación a 2 de noviembre de 2026. Versión del módulo 19.0.1.0.0. Los datos de ejemplo de las ilustraciones son de libre elección; importes netos.