Ves al contingut

Odoo 19 Community: conjunts de productes al pressupost com a secció – 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 al pressupost com a secció – 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 secció del pressupost amb mode d’impressió, línies amagades i preu fix

Qui ven paquets a Odoo, per exemple un aparell de mesura amb accessoris i muntatge, un lloc de treball amb maquinari i configuració o un paquet de manteniment format per diversos serveis, necessita dues coses alhora al pressupost: la composició ha de constar a la comanda perquè magatzem, tècnica i facturació sàpiguen què es lliura. Però a la impressió per al client no sempre han d’aparèixer totes les posicions individuals amb tots els preus individuals.

El mòdul OCA sale_product_set permet inserir conjunts de productes (llistes de materials per a la venda) en un pressupost amb un sol clic. No decideix res sobre el format d’impressió. Per això hem desenvolupat el mòdul Sale Product Set Layout (sale_product_set_layout). Està escrit per a Odoo 19 i es troba disponible gratuïtament a GitHub com a programari lliure sota la llicència AGPL-3:

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

El problema: conjunts de productes sense influència en el format d’impressió

A Odoo, un conjunt de productes és una llista preparada de productes amb quantitats. En lloc de cercar deu articles un per un, al pressupost se selecciona Afegeix un conjunt i s’obtenen totes les línies d’un sol cop. Això estalvia temps i evita posicions oblidades. El mòdul sale_product_set del repositori OCA/sale-workflow fa exactament això; els conjunts mateixos provenen de product_set del repositori OCA/product-attribute.

A la pràctica, queden tres llacunes:

  • Cap agrupació recognoscible. Les línies del conjunt acaben al pressupost com a posicions soltes. Després d’inserir-les ja no es veu quines línies van juntes, i el total del conjunt no apareix enlloc.
  • Cap influència en la impressió. De vegades el client ha de veure cada posició amb el seu preu, de vegades només el preu del paquet, de vegades una sola línia. Això és una decisió per conjunt o per client, no per haver-la de prendre a mà a cada pressupost.
  • Les correccions de preu són poc elegants. Si un paquet ha de tenir un preu fix que s’allunya de la suma dels preus de tarifa, cal una línia de correcció. Pertany al càlcul, però no necessàriament al paper del client.

Odoo 19 incorpora els elements per resoldre-ho bé: 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 connecta el conjunt de productes amb aquestes opcions estàndard, en lloc d’inventar un format d’impressió propi.

Què fa el mòdul

  1. Inserir 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 col·loca a sota les línies del conjunt. Les seccions dins del conjunt es converteixen en subseccions.
  2. Mode d’impressió per conjunt. Al conjunt es defineix una sola vegada com s’imprimeix. En inserir-lo, el mode es pot canviar a l’assistent per a aquell cas concret.
  3. Amagar línies. Les línies individuals es poden marcar com a No imprimir (al conjunt: Amagat). Compten a la suma, però ni s’imprimeixen ni es mostren al portal de clients. L’indicador passa 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 substitueix el preu de la tarifa, per exemple per a una correcció de preu amagada.
Sense el mòdul (només sale_product_set)Amb sale_product_set_layout
Línies del conjunt com a posicions soltesLínia de secció amb el nom del conjunt, la quantitat al nom i les línies a sota
Les seccions del conjunt continuen sent seccions i trenquen l’estructuraLes seccions del conjunt es converteixen en subseccions (line_subsection)
Format d’impressió sempre igualMode d’impressió per conjunt, modificable en inserir-lo
Correcció de preu visible al pressupost i a la facturaLínia que es pot amagar, però compta a la suma
Preu només a partir de la tarifaPreu fix opcional per línia del conjunt
El format s’acaba al pressupostEl format i l’ocultació valen per al pressupost, la comanda, la factura i el portal de clients

Els tres modes d’impressió al detall

El mode d’impressió és un camp de selecció print_mode al conjunt de productes. En inserir-lo es tradueix als dos interruptors estàndard de la línia de secció. Per a això, el mòdul conté una única correspondència fàcil de llegir:

# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
    "detailed":   (False, False),
    "total_only": (True,  False),
    "summary":    (False, True),
}

Tres formats d’impressió del mateix conjunt de productes: cada línia amb preu, només preu total, una sola línia

Mode (print_mode)Visualització al conjuntOpció estàndard d’OdooFormat d’impressió
detailedCada línia amb preucapSecció, totes les línies amb import
total_onlyLínies sense preus, només la suma del conjuntcollapse_prices (Amaga els preus)Secció amb suma, línies només amb designació i quantitat
summaryNomés una línia amb la suma del conjuntcollapse_composition (Amaga la composició)Només la línia de secció amb la suma

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 de clients funcionen sense plantilles d’informe adaptades. Qui tingui plantilles d’informe pròpies que tinguin en compte les opcions de secció també obtindrà aquest comportament.

Línies amagades i preu fix

Un cas freqüent: un paquet té un preu de paquet inferior a la suma dels preus individuals. En lloc de deformar els preus individuals, s’utilitza una línia de correcció amb preu fix (per exemple −50,00 € per conjunt) i s’amaga. Al backend la factura és traçable; a la impressió consta el preu del paquet.

Correcció de preu visible i marcada al backend, no visible a la impressió del pressupost, al PDF, a la factura ni al portal, amb la suma idèntica

Important per a la comprensió: l’indicador hide_in_report no canvia res al càlcul. Quantitat, preu, descompte, impost, sumes i assentaments comptables resulten sense canvis d’Odoo estàndard. La línia només falta a la impressió i al portal.

  • Al conjunt de productes: línia del conjunt amb Amagat i, opcionalment, Preu fix juntament amb Preu unitari.
  • A les línies de pressupost i de factura: columna No imprimir (hide_in_report), també per a línies normals sense conjunt.
  • Transmissió: 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

El mòdul és deliberadament petit: amplia models existents i només sobreescriu els punts on Odoo decideix quines línies s’imprimeixen i com s’insereix un conjunt. No hi ha plantilles d’informe noves, ni models nous, ni intervencions SQL.

Flux de dades: conjunt de productes, assistent Afegeix un conjunt, línies de pressupost, línies de factura, PDF i portal de clients

Models i camps ampliats

ModelAmpliació
product.setCamps insert_as_section (booleà, activat per defecte) i print_mode (selecció, camp obligatori, per defecte detailed)
product.set.lineCamps hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() els passa a la línia del pressupost
sale.product.set.wizardCamps insert_as_section, section_name, print_mode; _prepare_order_lines() construeix 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 amagades; _prepare_invoice_line() transmet 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 amagades; _get_aml_vals() porta l’indicador a les dades de l’informe

Amagar 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 intervenir a la plantilla d’informe: tant la plantilla d’informe del pressupost (sale/report/ir_actions_report_templates.xml) com la plantilla 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
        )

Transmissió a la factura

En generar la línia de factura a partir de la línia de pressupost (_prepare_invoice_line()), l’indicador es copia. Al costat de la factura, Odoo 19 té diverses vies per les quals les línies arriben a l’informe: 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); si no, una línia amagada podria tornar a aparèixer per una 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: secció en lloc de línies soltes

L’assistent sale.product.set.wizard de sale_product_set genera les línies del 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 el comportament continua sent el del mòdul OCA. En cas contrari, primer es crea la línia de secció amb els dos interruptors estàndard i després segueixen les línies del conjunt. Les seccions dins 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 el seu propi mètode compute (store=True, readonly=False). Si els tres tinguessin el mateix mètode, un valor passat explícitament per a un camp restabliria els altres. Això es nota sobretot quan l’assistent es crea amb valors des de codi o des de proves.
  • Nom de la secció amb quantitat. Si la quantitat és diferent d’1, consta al nom (2 × Kit de mesura estàndard, amb coma com a separador decimal), perquè a la impressió es vegi que són dos conjunts i no un conjunt amb quantitat doble.
  • Preu fix només a les línies de producte. prepare_sale_order_line_values() estableix hide_in_report i price_unit només si la línia del conjunt no és una línia de secció ni de nota.
  • Odoo estàndard en lloc de desenvolupament propi. El mode d’impressió i l’ocultació utilitzen els ganxos i camps existents d’Odoo 19. Això fa que les actualitzacions siguin manejables: si Odoo canvia la plantilla d’informe, el mòdul continua sent vàlid mentre els mètodes existeixin.

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.

La trampa d’auto_install als mòduls OCA. Molts repositoris OCA contenen mòduls amb auto_install. Tan bon punt hi són les seves dependències, Odoo els instal·la sense preguntar a la propera instal·lació o actualització. Qui, per tant, posa el repositori OCA sencer a l’addons_path d’una instància de producció, potser obté mòduls que ningú volia. A OCA/product-attribute això afecta, per exemple, purchase_product_template_tags. El nostre procediment:

  1. Clonar els dos repositoris OCA de manera superficial (git clone --depth 1 --branch 19.0).
  2. En un directori propi, enllaçar mitjançant enllaços simbòlics només els dos mòduls necessaris i afegir aquest directori a l’addons_path, no els repositoris sencers.
  3. Instal·lar en una base de dades de prova i comprovar quins mòduls obtenen l’estat instal·lat.
  4. Només aleshores instal·lar en producció, amb --workers=0, perquè la crida no competeixi amb el servei en funcionament pel port HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init

Els camps i les vistes nous són a la base de dades després de la instal·lació. Si el servei funciona en mode multiprocés, els workers tornen a carregar el registre 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. Crear un conjunt: Vendes → Productes → Conjunts de productes. Anomenar el conjunt, triar Insereix com a secció i Mode d’impressió, introduir els productes amb les quantitats. Dotar les línies de correcció de Amagat i Preu fix.
  2. Al pressupost: fer clic a Afegeix un conjunt, triar el conjunt i la quantitat. El nom de la secció i el mode d’impressió es poden canviar abans d’inserir-lo.
  3. Amagar línies individuals: al pressupost o a la factura, activar la columna No imprimir.
  4. Comprovar: obrir la vista prèvia o el PDF. Els clients veuen la mateixa presentació al portal.

Proves

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

  • Inserir com a secció, incloent-hi mode d’impressió i subsecció
  • Una línia amagada no consta a les línies que s’han d’imprimir
  • Mode Una línia (summary)
  • Inserir sense secció, el comportament original
  • La factura assumeix l’indicador hide_in_report, la línia no consta a _get_move_lines_to_report(), la suma de la secció continua incloent-la
  • La factura en mode Només preu total no mostra la línia amagada a les línies de la secció, la suma de la secció continua sent completa
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
         --test-enable --test-tags=/sale_product_set_layout --stop-after-init

La traducció alemanya es troba a i18n/de.po.

Límits i avís legal

  • Només format. El mòdul no canvia com Odoo calcula preus, descomptes o impostos. Només canvia el que s’imprimeix i es mostra al portal.
  • Les línies amagades són visibles al backend. L’indicador afecta la impressió i el portal, no la vista en pantalla del personal.
  • Comproveu les dades de la factura. Les línies amagades compten a la suma, però no consten al document. Si això és compatible amb els requisits que s’apliquen a les factures al vostre país, per exemple amb la descripció prescrita dels béns lliurats o dels serveis prestats, ho hauríeu d’aclarir amb el vostre assessor fiscal abans d’utilitzar-ho en factures. Als pressupostos, l’ocultació és menys problemàtica. L’article no constitueix assessorament jurídic.
  • Provat per a Odoo 19. Hem desenvolupat i provat el mòdul per a Odoo 19.0 Community. Utilitza opcions de secció que existeixen d’aquesta forma a Odoo 19. No l’hem comprovat per a altres versions.

Preguntes freqüents

Per què necessito el mòdul si Odoo 19 ja sap fer seccions?

Les opcions de secció existeixen a Odoo mateix. Faltava la connexió amb el conjunt de productes: un conjunt s’hauria d’inserir automàticament com a secció, amb el mode d’impressió desat al conjunt. Sense el mòdul, després de cada inserció cal crear i configurar la línia de secció a mà.

En què es diferencien «Amaga els preus» i «Amaga la composició»?

«Amaga els preus» (collapse_prices) deixa les línies, però no mostra imports, només la suma a la secció. «Amaga la composició» (collapse_composition) amaga les línies del tot; el client només veu la secció amb la suma.

Una línia amagada compta a la suma de la factura?

Sí. Les línies amagades continuen a la suma, a l’impost i a l’assentament. L’indicador només afecta la presentació a la impressió i al portal de clients.

Puc canviar el mode d’impressió en inserir-lo?

Sí. El mode ve com a valor per defecte del conjunt i, a l’assistent, es pot canviar per a cada inserció. El conjunt mateix no es modifica.

Què passa si desactivo Insereix com a secció?

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

El mòdul instal·la mòduls addicionals de manera inesperada?

El mòdul mateix no. Però amb els repositoris OCA pot haver-hi mòduls auto_install si es posen repositoris sencers a l’addons_path. Per això, enllaceu només els dos mòduls necessaris (vegeu la secció Instal·lació).

El mòdul funciona amb Odoo Enterprise?

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

Sota quina llicència es troba el mòdul?

Sota l’AGPL-3. Podeu utilitzar-lo, adaptar-lo i distribuir-lo, sempre que els canvis es mantinguin sota la mateixa llicència. Els errors i els suggeriments de millora són benvinguts com a issue al repositori de GitHub .

Suport de Biricon IT Services

Gestionem instal·lacions d’Odoo per a petites i mitjanes empreses: des de la implantació, passant per les adaptacions i les migracions, fins a l’explotació contínua. Si voleu adaptar conjunts de productes, plantilles de pressupost o informes d’Odoo 19 als vostres processos, o si voleu revisar un mòdul com aquest i posar-lo amb seguretat a la vostra instància de producció, us ajudarem amb gust. Trobareu més desenvolupaments propis al nostre blog, per exemple Combinar factures en esborrany i Assignar automàticament els pagaments bancaris a les factures.

Poseu-vos en contacte amb nosaltres

Data: 2 de novembre de 2026. Versió del mòdul 19.0.1.0.0. Les dades d’exemple de les il·lustracions són d’elecció lliure, imports nets.