Ves al contingut

Odoo 19 Community: conjunts de productes com a seccions als pressupostos amb el nostre mòdul de codi obert Sale Product Set Layout

2 de novembre de 2026 | Odoo 19 Community | Desenvolupament propi
2 de novembre de 2026 per
Odoo 19 Community: conjunts de productes com a seccions als pressupostos amb el nostre mòdul de codi obert Sale Product Set Layout

Mòdul Sale Product Set Layout per a Odoo 19: conjunts de productes com a seccions al pressupost, amb mode d’impressió, línies ocultes i preu fix

Qui ven paquets amb Odoo, com ara un aparell de mesura amb accessoris i muntatge, un lloc de treball amb maquinari i configuració o un paquet de manteniment amb diversos serveis, necessita dues coses alhora en el pressupost: la composició ha de constar a la comanda perquè els equips de magatzem, tècnic i de facturació sàpiguen què es lliura. Ara bé, a la còpia impresa per al client no sempre hi han d’aparèixer totes les línies amb els preus individuals.

El mòdul de l’OCA sale_product_set permet afegir conjunts de productes (llistes de materials per a la venda) a un pressupost amb un sol clic. Però no determina com s’imprimeixen. Per això hem desenvolupat el mòdul Sale Product Set Layout (sale_product_set_layout). Està escrit per a Odoo 19 i està disponible gratuïtament a GitHub com a programari lliure amb la llicència AGPL-3:

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

El problema: conjunts de productes sense control sobre la presentació impresa

A Odoo, un conjunt de productes és una llista predefinida de productes amb les quantitats corresponents. En lloc de cercar deu articles un per un, seleccioneu Afegeix un conjunt al pressupost i hi obteniu totes les línies de cop. Això estalvia temps i evita que us deixeu cap article. El mòdul sale_product_set del repositori OCA/sale-workflow fa exactament això; els conjunts mateixos provenen de product_set, al repositori OCA/product-attribute.

A la pràctica, queden tres mancances:

  • No hi ha una agrupació clara. Les línies del conjunt s’afegeixen al pressupost com a línies independents. Un cop inserides, ja no es veu quines formen un conjunt, i l’import total del conjunt no apareix enlloc.
  • No es pot controlar la impressió. De vegades el client ha de veure cada línia amb el seu preu; d’altres, només el preu del paquet, i d’altres, una única línia. Aquesta decisió s’ha de poder prendre per a cada conjunt o client, sense haver de tornar-la a aplicar manualment a cada pressupost.
  • Els ajustos de preu queden poc elegants. Si un paquet ha de tenir un preu fix diferent de la suma dels preus de tarifa, cal una línia d’ajust. Aquesta línia ha d’intervenir en el càlcul, però no necessàriament ha d’aparèixer al document que rep el client.

Odoo 19 ja incorpora els elements necessaris per resoldre-ho adequadament: seccions i subseccions a les línies del pressupost, amb les opcions Amaga els preus i Amaga la composició (camps collapse_prices i collapse_composition). El mòdul vincula el conjunt de productes a aquestes opcions estàndard, en lloc de crear un format d’impressió propi.

Què fa el mòdul

  1. Insereix el conjunt com a secció. L’assistent Afegeix un conjunt crea una línia de secció amb el nom del conjunt, per exemple 2 × Kit de mesura estàndard, i hi col·loca les línies del conjunt a sota. Les seccions internes del conjunt es converteixen en subseccions.
  2. Mode d’impressió per conjunt. Es defineix una vegada al conjunt com s’ha d’imprimir. En inserir-lo, es pot canviar el mode a l’assistent per a aquell cas concret.
  3. Amaga línies. Es poden marcar línies individuals com a No imprimeixis (al conjunt: Oculta). Es tenen en compte en l’import total, però no s’imprimeixen ni es mostren al portal del client. La marca es trasllada del pressupost a la factura.
  4. Preu fix per línia del conjunt. Una línia del conjunt pot tenir un preu unitari fix que substitueixi el de la tarifa, per exemple per a un ajust de preu ocult.
Sense el mòdul (només sale_product_set)Amb sale_product_set_layout
Línies del conjunt com a línies independentsLínia de secció amb el nom del conjunt, quantitat inclosa al nom i línies a sota
Les seccions del conjunt es mantenen com a seccions i trenquen l’estructuraLes seccions del conjunt es converteixen en subseccions (line_subsection)
Presentació impresa sempre igualMode d’impressió per conjunt, modificable en inserir-lo
Ajust de preu visible al pressupost i a la facturaLa línia es pot ocultar, però es té en compte en l’import total
Preu obtingut únicament de la tarifaPreu fix opcional per línia del conjunt
El format només s’aplica al pressupostEl format i l’ocultació de línies s’apliquen al pressupost, la comanda, la factura i el portal del client

Els tres modes d’impressió en detall

El mode d’impressió és un camp de selecció print_mode del conjunt de productes. En inserir el conjunt, es tradueix en les dues opcions estàndard de la línia de secció. Per fer-ho, el mòdul utilitza una única correspondència, fàcil de llegir:

# Mode d'impressió -> (collapse_prices, collapse_composition) de la línia de secció
PRINT_MODE_FLAGS = {
    "detailed":   (False, False),
    "total_only": (True,  False),
    "summary":    (False, True),
}

Tres presentacions impreses del mateix conjunt de productes: cada línia amb el preu, només el preu total i una sola línia

Mode (print_mode)Visualització al conjuntOpció estàndard d'OdooPresentació impresa
detailedCada línia amb el preucapSecció amb totes les línies i els imports
total_onlyLínies sense preus, només el total del conjuntcollapse_prices (Amaga els preus)Secció amb el total i línies només amb la descripció i la quantitat
summaryUna sola línia amb el total del conjuntcollapse_composition (Amaga la composició)Només la línia de secció amb el total

Com que el mòdul utilitza les opcions estàndard d'Odoo 19 i no un format d'impressió propi, el pressupost, la confirmació de comanda, la factura i el portal del client funcionen sense haver d'adaptar les plantilles dels informes. Les plantilles d'informe pròpies que tinguin en compte les opcions de secció també ofereixen aquest comportament.

Línies ocultes i preu fix

Un cas habitual: un paquet té un preu inferior a la suma dels preus individuals. En lloc d'alterar els preus individuals, es fa servir una línia d'ajust amb preu fix (per exemple, −50,00 € per conjunt) i s'amaga. A la interfície de gestió es pot comprovar el càlcul; al document imprès hi apareix el preu del paquet.

Ajust de preu visible i marcat a la interfície de gestió, ocult al pressupost imprès, al PDF, a la factura i al portal, amb el mateix total

Cal tenir present que l'indicador hide_in_report no modifica el càlcul. La quantitat, el preu, el descompte, l'impost, els totals i els assentaments comptables es generen amb el funcionament estàndard d'Odoo, sense canvis. La línia només s'omet al document imprès i al portal.

  • Al conjunt de productes: línia del conjunt amb Oculta i, opcionalment, Preu fix amb el Preu unitari corresponent.
  • A les línies del pressupost i de la factura: columna No imprimeixis (hide_in_report), també disponible per a línies normals que no pertanyen a cap conjunt.
  • Propagació: en crear la factura a partir de la comanda, l'indicador es transfereix a la línia de factura. En copiar un pressupost, es conserva.

Implementació tècnica

Hem mantingut el mòdul petit expressament: amplia els models existents i només sobreescriu els punts en què Odoo decideix quines línies s'imprimeixen i com s'insereix un conjunt. No incorpora plantilles d'informe noves, models nous ni modificacions mitjançant SQL.

Flux de dades: conjunt de productes, assistent per afegir conjunts, línies de pressupost, línies de factura, PDF i portal del client

Models i camps ampliats

ModelAmpliació
product.setCamps insert_as_section (booleà, activat per defecte) i print_mode (selecció, obligatori, valor per defecte detailed)
product.set.lineCamps hide_in_report, fixed_price i price_unit; prepare_sale_order_line_values() els transfereix a la línia de pressupost
sale.product.set.wizardCamps insert_as_section, section_name i print_mode; _prepare_order_lines() crea la línia de secció
sale.order / sale.order.lineCamp hide_in_report (amb copy=True); _get_order_lines_to_report() omet les línies ocultes; _prepare_invoice_line() transfereix l'indicador
account.move / account.move.lineCamp hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() i _get_subsection_lines() filtren les línies ocultes; _get_aml_vals() incorpora l'indicador a les dades de l'informe

Ocultar línies al pressupost

Odoo 19 determina les línies de pressupost que s'han d'imprimir a _get_order_lines_to_report(). El mòdul hi afegeix un filtre. No cal modificar la plantilla de l'informe: tant la plantilla de l'informe del pressupost (sale/report/ir_actions_report_templates.xml) com la del portal (sale/views/sale_portal_templates.xml) criden aquest mètode:

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
        )

Transferència a la factura

En generar la línia de factura a partir de la línia de pressupost (_prepare_invoice_line()), es copia l'indicador. A les factures d'Odoo 19, les línies poden arribar a l'informe per diverses vies: la llista principal, les línies d'una secció i les línies d'una subsecció. Per això el mòdul filtra en diversos punts (_get_move_lines_to_report() al document, _get_detailed_lines() i _get_subsection_lines() a la línia); altrament, una línia oculta podria tornar a aparèixer per alguna de les altres vies:

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")]

L'assistent: una secció en lloc de línies soltes

L'assistent sale.product.set.wizard de sale_product_set genera les línies de pressupost a _prepare_order_lines(). El mòdul sobreescriu aquest mètode. Si Insereix com a secció està desactivat, simplement crida el mètode original i manté el comportament del mòdul de l'OCA. En cas contrari, primer crea la línia de secció amb les dues opcions estàndard i, a continuació, les línies del conjunt. Les seccions internes del conjunt es converteixen en subseccions perquè no trenquin l'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

Decisions que no es veuen

  • Mètodes de càlcul separats a l'assistent. El nom de la secció, el mode d'impressió i Insereix com a secció tenen cadascun un mètode compute propi (store=True, readonly=False). Si tots tres compartissin el mateix mètode, un valor assignat explícitament a un camp restabliria els altres. Això es nota sobretot quan es crea l'assistent amb valors des del codi o des de les proves.
  • Nom de secció amb la quantitat. Si la quantitat és diferent d'1, s'inclou al nom (2 × Kit de mesura estàndard, amb coma com a separador decimal), perquè al document imprès es vegi que són dos conjunts i no un conjunt amb el doble de quantitat.
  • Preu fix només a les línies de producte. prepare_sale_order_line_values() només estableix hide_in_report i price_unit si la línia del conjunt no és una línia de secció ni de nota.
  • Funcions estàndard d'Odoo en lloc d'una solució pròpia. El mode d'impressió i l'ocultació utilitzen els punts d'extensió i els camps existents d'Odoo 19. Això facilita les actualitzacions: si Odoo canvia la plantilla de l'informe, el mòdul continua funcionant mentre es mantinguin els mètodes.

Instal·lació sense efectes secundaris no desitjats

Requisits: Odoo 19.0, sale_product_set (OCA/sale-workflow, branca 19.0) i product_set (OCA/product-attribute, branca 19.0). El mòdul mateix depèn de sale_management, sale_product_set i account.

El parany d'auto_install als mòduls de l'OCA. Molts repositoris de l'OCA contenen mòduls amb auto_install. Quan les seves dependències estan disponibles, Odoo també els instal·la sense preguntar durant la següent instal·lació o actualització. Per tant, si s'afegeix tot el repositori de l'OCA a l'addons_path d'una instància de producció, és possible que s'instal·lin mòduls que ningú no volia. A OCA/product-attribute, això afecta, per exemple, purchase_product_template_tags. Nosaltres seguim aquests passos:

  1. Fer una clonació superficial dels dos repositoris de l'OCA (git clone --depth 1 --branch 19.0).
  2. Crear en un directori propi enllaços simbòlics només als dos mòduls necessaris i afegir aquest directori a l'addons_path, en lloc dels repositoris sencers.
  3. Instal·lar en una base de dades de proves i comprovar quins mòduls passen a l'estat instal·lat.
  4. Només llavors, instal·lar a producció amb --workers=0, perquè l'ordre no competeixi pel port HTTP amb el servei en execució.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init

Després de la instal·lació, els camps i les vistes nous ja són a la base de dades. Si el servei funciona amb diversos processos, els processos de treball carreguen el registre de models modificat mitjançant el senyal de la base de dades. Els canvis als fitxers Python del mòdul només tenen efecte després de reiniciar el servei.

Ús pas a pas

  1. Creeu un conjunt: Vendes → Productes → Conjunts de productes. Poseu un nom al conjunt, seleccioneu Insereix com a secció i el Mode d'impressió, i introduïu els productes amb les quantitats corresponents. Marqueu les línies d'ajust amb Oculta i Preu fix.
  2. Al pressupost: feu clic a Afegeix un conjunt i seleccioneu el conjunt i la quantitat. Podeu canviar el nom de la secció i el mode d'impressió abans d'inserir-lo.
  3. Oculteu línies individuals: marqueu la casella de la columna No imprimeixis al pressupost o a la factura.
  4. Comproveu el resultat: obriu la previsualització o el PDF. Els clients veuen la mateixa presentació al portal.

Proves

El mòdul inclou sis proves automatitzades que executem en una base de dades de proves abans de passar res a producció:

  • Inserció com a secció, amb mode d'impressió i subsecció
  • La línia oculta no apareix entre les línies que s'han d'imprimir
  • Mode Una línia (summary)
  • Inserció sense secció, amb el comportament original
  • La factura hereta l'indicador hide_in_report, la línia no apareix a _get_move_lines_to_report() i el total de la secció la continua incloent
  • La factura en mode Només el preu total no mostra la línia oculta entre les línies de la secció i manté íntegre el total de la secció
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
         --test-enable --test-tags=/sale_product_set_layout --stop-after-init

La traducció a l'alemany és a i18n/de.po.

Limitacions i avís legal

  • Només la presentació. El mòdul no modifica la manera com Odoo calcula els preus, els descomptes o els impostos. Només canvia què s'imprimeix i què es mostra al portal.
  • Les línies ocultes són visibles a la interfície de gestió. L'indicador afecta el document imprès i el portal, però no la vista en pantalla dels empleats.
  • Comproveu les dades de les factures. Les línies ocultes es comptabilitzen en el total, però no apareixen al document. Abans d'utilitzar aquesta funció a les factures, consulteu amb la vostra assessoria fiscal si és compatible amb els requisits de facturació del vostre país, com ara la descripció obligatòria dels béns lliurats o dels serveis prestats. Ocultar línies als pressupostos planteja menys problemes. Aquest article no constitueix assessorament jurídic.
  • Provat amb Odoo 19. Hem desenvolupat i provat el mòdul per a Odoo 19.0 Community. Utilitza opcions de secció disponibles amb aquesta forma a Odoo 19. No l'hem provat amb altres versions.

Preguntes freqüents

Per què necessito el mòdul si Odoo 19 ja permet crear seccions?

Odoo ja incorpora les opcions de secció. El que faltava era la connexió amb el conjunt de productes: el conjunt s'havia d'inserir automàticament com a secció, amb el mode d'impressió definit al conjunt. Sense el mòdul, cal crear i configurar manualment la línia de secció després de cada inserció.

Quina diferència hi ha entre «Amaga els preus» i «Amaga la composició»?

«Amaga els preus» (collapse_prices) manté les línies, però no en mostra els imports, només el total de la secció. «Amaga la composició» (collapse_composition) oculta les línies del tot: el client només veu la secció amb el total.

Una línia oculta es comptabilitza en el total de la factura?

Sí. Les línies ocultes es continuen incloent en el total, els impostos i la comptabilització. L'indicador només afecta la presentació al document imprès i al portal del client.

Puc canviar el mode d'impressió en inserir el conjunt?

Sí. El mode definit al conjunt es fa servir com a valor per defecte, però es pot canviar a l'assistent per a cada inserció. El conjunt mateix no es modifica.

Què passa si desactivo Insereix com a secció?

L'assistent es comporta com al mòdul de l'OCA sale_product_set: les línies del conjunt s'insereixen sense secció.

El mòdul instal·la altres mòduls inesperadament?

El mòdul mateix, no. Ara bé, si s'afegeixen repositoris sencers de l'OCA a l'addons_path, es poden instal·lar mòduls amb auto_install. Per això, creeu enllaços només als dos mòduls necessaris (vegeu l'apartat d'instal·lació).

El mòdul funciona amb Odoo Enterprise?

El mòdul només depèn de mòduls de Community (sale_management, account) i dels dos mòduls de l'OCA. Nosaltres el fem servir amb Odoo 19 Community. No l'hem provat amb Enterprise.

Quina llicència té el mòdul?

La llicència AGPL-3. El podeu utilitzar, adaptar i redistribuir sempre que les modificacions mantinguin la mateixa llicència. Els informes d'errors i els suggeriments de millora en forma d'incidència al repositori de GitHub són benvinguts.

Suport de Biricon IT Services

Ens ocupem d'instal·lacions d'Odoo per a petites i mitjanes empreses: des de la implantació fins al funcionament diari, passant per les adaptacions i les migracions. Si voleu adaptar els conjunts de productes, les plantilles de pressupost o els informes d'Odoo 19 als vostres processos, o si voleu avaluar un mòdul com aquest i incorporar-lo amb garanties a la vostra instància de producció, us podem ajudar. Trobareu altres desenvolupaments propis al nostre blog, com ara Fusiona factures en esborrany i Assigna automàticament els pagaments bancaris a les factures.

Poseu-vos en contacte amb nosaltres

Actualitzat el 2 de novembre de 2026. Versió del mòdul: 19.0.1.0.0. Les dades d'exemple de les imatges són fictícies; els imports no inclouen impostos.