Quienes venden paquetes en Odoo, como un equipo de medición con accesorios e instalación, un puesto de trabajo con hardware y configuración o un paquete de mantenimiento que incluye varios servicios, necesitan dos cosas a la vez en el presupuesto: la composición del paquete debe figurar en el pedido para que el almacén, el equipo técnico y el departamento de facturación sepan qué se va a entregar. Sin embargo, en la versión impresa para el cliente no siempre conviene mostrar cada partida con su precio individual.
El módulo de OCA sale_product_set permite añadir conjuntos de productos (listas de materiales para ventas) a un presupuesto con un clic. No determina cómo se presentan al imprimirlos. Para eso hemos desarrollado el módulo Sale Product Set Layout (sale_product_set_layout). Está diseñado 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 control sobre la presentación impresa
- 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
- Uso paso a paso
- Pruebas
- Limitaciones y aviso legal
- Preguntas frecuentes
- Asistencia de Biricon IT Services
El problema: conjuntos de productos sin control sobre la presentación impresa
En Odoo, un conjunto de productos es una lista predefinida de productos con sus cantidades. En lugar de buscar diez artículos uno por uno, basta con seleccionar Añadir conjunto en el presupuesto para incorporar todas las líneas de una vez. Así se ahorra tiempo y se evita olvidar artículos. El módulo sale_product_set del repositorio OCA/sale-workflow hace precisamente eso; los propios conjuntos proceden de product_set, del repositorio OCA/product-attribute.
En la práctica, quedan tres carencias:
- No hay una agrupación clara. Las líneas del conjunto se añaden al presupuesto como líneas independientes. Una vez insertadas, ya no se distingue cuáles pertenecen al mismo conjunto, y el importe total del conjunto no aparece en ninguna parte.
- No hay control sobre la impresión. A veces interesa que el cliente vea cada línea con su precio; otras, solo el precio del paquete o una única línea. Es una decisión que debe poder definirse por conjunto o por cliente, sin tener que configurarla manualmente en cada presupuesto.
- Los ajustes de precio quedan 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 ajuste. Esta debe intervenir en el cálculo, pero no necesariamente aparecer en el documento que recibe el cliente.
Odoo 19 incluye los elementos necesarios para resolverlo de forma sencilla: 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 crear 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 que contiene el conjunto se convierten en subsecciones.
- Modo de impresión por conjunto. La forma de imprimirlo se configura una sola vez en el conjunto. Al insertarlo, se puede cambiar el modo en el asistente para ese caso concreto.
- Ocultar líneas. Se pueden marcar líneas individuales como No imprimir (en el conjunto: Oculta). Se incluyen en el total, pero no se imprimen ni se muestran en el portal del cliente. La marca se traslada del presupuesto a la factura.
- Precio fijo por línea del conjunto. Una línea del conjunto puede tener un precio unitario fijo que sustituya al de la tarifa, por ejemplo, para un ajuste de precio oculto.
Sin el módulo (solo sale_product_set) | Con sale_product_set_layout |
|---|---|
| Líneas del conjunto como líneas independientes | Línea de sección con el nombre del conjunto y la cantidad en el nombre, y las líneas debajo |
| Las secciones del conjunto siguen siendo secciones y rompen la estructura | Las secciones del conjunto se convierten en subsecciones (line_subsection) |
| Presentación impresa siempre igual | Modo de impresión por conjunto, modificable al insertarlo |
| Ajuste de precio visible en el presupuesto y en la factura | La línea se puede ocultar y sigue contando en el total |
| Precio obtenido únicamente de la tarifa | Precio fijo opcional por línea del conjunto |
| El formato solo se aplica al presupuesto | El formato y la ocultación de líneas se aplican al presupuesto, al pedido, a la factura y al portal del cliente |
Los tres modos de impresión en detalle
El modo de impresión es un campo de selección, print_mode, del conjunto de productos. Al insertarlo, se traduce en los dos controles estándar de la línea de sección. Para ello, el módulo utiliza una única correspondencia fácil de leer:
# Modo de impresión -> (collapse_prices, collapse_composition) de la línea de sección
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Modo (print_mode) | Etiqueta en el conjunto | Opción estándar de Odoo | Presentación impresa |
|---|---|---|---|
detailed | Cada línea con su precio | ninguna | Sección y todas las líneas con su importe |
total_only | Líneas sin precios, solo el total del conjunto | collapse_prices (Ocultar precios) | Sección con el total y líneas solo con descripción y cantidad |
summary | Una única línea con el total del conjunto | collapse_composition (Ocultar composición) | Solo la línea de sección con el total |
Como el módulo utiliza las opciones estándar de Odoo 19, sin un formato de impresión propio, funciona en presupuestos, confirmaciones de pedido, facturas y el portal del cliente sin adaptar las plantillas de informes. Este comportamiento también se aplica a las plantillas personalizadas que tengan en cuenta las opciones de sección.
Líneas ocultas y precio fijo
Un caso habitual: un paquete tiene un precio inferior a la suma de los precios individuales. En lugar de alterar estos precios, se añade una línea de ajuste con precio fijo (por ejemplo, −50,00 € por conjunto) y se oculta. En el backend se puede comprobar el cálculo y en el documento impreso aparece el precio del paquete.
Conviene tenerlo claro: la marca hide_in_report no modifica el cálculo. Odoo sigue calculando de la forma habitual las cantidades, los precios, los descuentos, los impuestos, los totales y los asientos contables. La línea solo se oculta en la impresión y en el portal.
- En el conjunto de productos: línea del conjunto con la opción Oculta y, opcionalmente, Precio fijo junto con el Precio unitario.
- En las líneas de presupuesto y de factura: columna No imprimir (
hide_in_report), también disponible para líneas normales que no pertenecen a ningún conjunto. - Transferencia: al crear la factura a partir del pedido, la marca se transfiere a la línea de factura. También se conserva al copiar un presupuesto.
Implementación técnica
El módulo se ha mantenido pequeño de forma deliberada: amplía los 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 incorpora nuevas plantillas de informes ni nuevos modelos, ni realiza modificaciones mediante SQL.
Modelos y campos ampliados
| Modelo | Ampliación |
|---|---|
product.set | Campos insert_as_section (booleano, activado por defecto) y print_mode (selección, obligatorio, valor predeterminado: detailed) |
product.set.line | Campos hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() los transfiere a la línea de presupuesto |
sale.product.set.wizard | Campos insert_as_section, section_name, print_mode; _prepare_order_lines() crea 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() transfiere la marca |
account.move / account.move.line | Campo hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() y _get_subsection_lines() excluyen las líneas ocultas; _get_aml_vals() incorpora la marca a los datos del informe |
Ocultar líneas en el presupuesto
Odoo 19 determina las líneas del presupuesto que se deben imprimir en _get_order_lines_to_report(). El módulo añade un filtro en ese punto. No hace falta modificar la plantilla del informe: tanto la plantilla del presupuesto (sale/report/ir_actions_report_templates.xml) como la 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
)
Transferencia 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 Odoo 19, las líneas pueden llegar al informe de factura por varias vías: la lista principal, las líneas de una sección y las de una subsección. Por eso el módulo aplica filtros en varios puntos (_get_move_lines_to_report() en el documento, y _get_detailed_lines() y _get_subsection_lines() en la línea). De lo contrario, una línea oculta podría volver a aparecer por otra de esas 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: una sección en lugar de líneas sueltas
El asistente sale.product.set.wizard de sale_product_set genera las líneas del presupuesto mediante _prepare_order_lines(). El módulo sobrescribe este método. Si Insertar como sección está desactivado, simplemente llama al método original y mantiene el comportamiento del módulo de OCA. En caso contrario, crea primero la línea de sección con los dos controles estándar y después añade las líneas del conjunto. Las secciones del conjunto se convierten en 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 independientes 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 proporcionado explícitamente para un campo restablecería los demás. Esto se aprecia sobre todo al crear el asistente con valores desde código o desde pruebas. - Nombre de sección con cantidad. Cuando la cantidad es distinta de 1, se incluye en el nombre (2 × Kit de medición estándar, con coma como separador decimal), para que en el documento impreso quede claro que se trata de dos conjuntos y no de uno con el doble de unidades.
- Precio fijo solo en líneas de producto.
prepare_sale_order_line_values()solo establecehide_in_reportyprice_unitsi la línea del conjunto no es una línea de sección ni de nota. - Funciones estándar de Odoo en lugar de soluciones propias. El modo de impresión y la ocultación utilizan los puntos de extensión y los campos existentes de Odoo 19. Esto facilita las actualizaciones: si Odoo cambia la plantilla del informe, el módulo seguirá siendo válido mientras se mantengan los métodos.
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 de OCA. Muchos repositorios de OCA contienen módulos con auto_install. En cuanto sus dependencias están disponibles, Odoo los instala también, sin preguntar, en la siguiente instalación o actualización. Por tanto, añadir el repositorio completo de OCA al addons_path de una instancia de producción puede acabar incorporando módulos que nadie quería. En OCA/product-attribute, esto afecta, por ejemplo, a purchase_product_template_tags. Nuestro procedimiento:
- Clonar los dos repositorios de OCA con un historial reducido (
git clone --depth 1 --branch 19.0). - Crear enlaces simbólicos únicamente a los dos módulos necesarios en un directorio propio y añadir ese directorio al
addons_path, en lugar de los repositorios completos. - Instalar en una base de datos de pruebas y comprobar qué módulos pasan al estado instalado.
- Solo entonces, instalar en producción con
--workers=0, para que la ejecución no compita con el servicio en marcha por el puerto HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Tras la instalación, los nuevos campos y vistas ya están en la base de datos. Si el servicio funciona con varios procesos, los workers cargan el registro actualizado mediante la señal de la base de datos. Los cambios en los archivos Python del módulo solo surten efecto después de reiniciar el servicio.
Uso paso a paso
- Crear un conjunto: Ventas → Productos → Conjuntos de productos. Asignar un nombre al conjunto, configurar Insertar como sección y Modo de impresión, y añadir los productos con sus cantidades. Marcar las líneas de ajuste como Oculta y Precio fijo.
- En el presupuesto: hacer clic en Añadir conjunto y seleccionar el conjunto y la cantidad. El nombre de la sección y el modo de impresión se pueden cambiar antes de insertarlo.
- Ocultar líneas individuales: activar la columna No imprimir en el presupuesto o en la factura.
- Comprobar el resultado: 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 en una base de datos de pruebas antes de llevar cualquier cambio 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 van a imprimir
- Modo Una línea (
summary) - Inserción sin sección, con el comportamiento original
- La factura recibe la marca
hide_in_report, la línea no aparece en_get_move_lines_to_report()y sigue incluida en el total de la sección - La factura en modo Solo precio total no muestra la línea oculta entre las líneas de la sección y mantiene íntegro el total de la sección
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.
Limitaciones y aviso legal
- Solo presentación. El módulo no modifica cómo calcula Odoo los precios, los descuentos o los impuestos. Solo cambia lo que se imprime y se muestra en el portal.
- Las líneas ocultas siguen visibles en el backend. La marca afecta a la impresión y al portal, no a lo que ve el personal en pantalla.
- Revisar los datos de la factura. Las líneas ocultas se incluyen en el total, pero no aparecen en el documento. Antes de utilizar esta función en facturas, consulte con su asesoría fiscal si esto es compatible con los requisitos de facturación de su país, por ejemplo, con la descripción obligatoria de los bienes entregados o los servicios prestados. Ocultar líneas en presupuestos plantea menos problemas. 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 disponibles de esta forma en Odoo 19. No lo hemos probado en otras versiones.
Preguntas frecuentes
¿Para qué necesito el módulo si Odoo 19 ya admite secciones?
Las opciones de sección ya existen en Odoo. Lo que faltaba era conectarlas con el conjunto de productos: el conjunto debía insertarse automáticamente como una sección, con el modo de impresión definido en él. Sin el módulo, hay que crear y configurar manualmente la línea de sección después de cada inserción.
¿Qué diferencia hay entre «Ocultar precios» y «Ocultar composición»?
«Ocultar precios» (collapse_prices) mantiene las líneas, pero no muestra sus importes, solo el total de la sección. «Ocultar composición» (collapse_composition) oculta las líneas por completo: el cliente solo ve la sección con su total.
¿Se incluye una línea oculta en el total de la factura?
Sí. Las líneas ocultas siguen incluidas en el total, en el cálculo de impuestos y en los asientos contables. La marca solo afecta a la presentación en el documento impreso y en el portal del cliente.
¿Puedo cambiar el modo de impresión al insertar el conjunto?
Sí. El modo definido en el conjunto se utiliza como valor predeterminado y se puede cambiar en el asistente para cada inserción. El propio conjunto permanece sin cambios.
¿Qué ocurre si desactivo Insertar como sección?
El asistente se comporta como en el módulo de 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 propio módulo no. Sin embargo, al añadir repositorios completos de OCA al addons_path, pueden instalarse módulos con auto_install. Por eso, cree enlaces únicamente a los dos módulos necesarios (véase el apartado de instalación).
¿Funciona el módulo con Odoo Enterprise?
El módulo solo depende de módulos de Community (sale_management, account) y de los dos módulos de OCA. Lo utilizamos en Odoo 19 Community. No lo hemos probado en Enterprise.
¿Qué licencia tiene el módulo?
Bajo la licencia AGPL-3. Puede utilizarlo, adaptarlo y distribuirlo siempre que las modificaciones se mantengan bajo la misma licencia. Los informes de errores y las sugerencias de mejora enviados como incidencias en el repositorio de GitHub serán bienvenidos.
Asistencia de Biricon IT Services
Gestionamos instalaciones de Odoo para pequeñas y medianas empresas: desde la implantación hasta el funcionamiento diario, pasando por adaptaciones y migraciones. Si desea adaptar los conjuntos de productos, las plantillas de presupuesto o los informes de Odoo 19 a sus procesos, o si quiere evaluar un módulo como este e incorporarlo de forma segura a su instancia de producción, estaremos encantados de ayudarle. Encontrará otros desarrollos propios en nuestro blog, por ejemplo, Combinar facturas en borrador y Asignar automáticamente los pagos bancarios a las facturas.
Actualizado el 2 de noviembre de 2026. Versión del módulo: 19.0.1.0.0. Los datos de ejemplo de las imágenes son ficticios y los importes no incluyen impuestos.