Wie in Odoo pakketten verkoopt, bijvoorbeeld een meetapparaat met accessoires en montage, een werkplek met hardware en inrichting of een onderhoudspakket uit meerdere diensten, heeft in de offerte twee dingen tegelijk nodig: de samenstelling moet in de order staan, zodat magazijn, techniek en facturatie weten wat er wordt geleverd. Op de afdruk voor de klant moet echter niet in elk geval elke afzonderlijke post met elke afzonderlijke prijs verschijnen.
De OCA-module sale_product_set maakt het mogelijk om productsets (stuklijsten voor de verkoop) met één klik in een offerte in te voegen. Over het afdrukbeeld beslist hij niet. Daarvoor hebben wij de module Sale Product Set Layout (sale_product_set_layout) ontwikkeld. Hij is geschreven voor Odoo 19 en staat als vrije software onder de AGPL-3-licentie gratis op GitHub ter beschikking:
github.com/Alex0176/odoo-sale-product-set-layout
Inhoud van dit artikel
- Het probleem: productsets zonder invloed op het afdrukbeeld
- Wat de module doet
- De drie afdrukmodi in detail
- Verborgen regels en vaste prijs
- Technische uitvoering
- Installatie zonder ongewenste neveneffecten
- Gebruik stap voor stap
- Tests
- Beperkingen en juridische opmerking
- Veelgestelde vragen
- Ondersteuning door Biricon IT Services
Het probleem: productsets zonder invloed op het afdrukbeeld
Een productset is in Odoo een vooraf samengestelde lijst van producten met aantallen. In plaats van tien artikelen afzonderlijk te zoeken, kiest u in de offerte Set toevoegen en krijgt u alle regels in één keer. Dat bespaart tijd en voorkomt vergeten posten. De module sale_product_set uit de repository OCA/sale-workflow doet precies dat; de sets zelf komen uit product_set in de repository OCA/product-attribute.
In de praktijk blijven er drie hiaten:
- Geen herkenbare groepering. De setregels belanden als losse posten in de offerte. Na het invoegen ziet u niet meer welke regels bij elkaar horen, en het totaal van de set staat nergens.
- Geen invloed op de afdruk. De ene keer moet de klant elke post met prijs zien, de andere keer alleen de pakketprijs, of slechts één enkele regel. Dat is een beslissing per set of per klant, niet bij elke offerte opnieuw handmatig.
- Prijscorrecties zijn onelegant. Moet een pakket een vaste prijs hebben die afwijkt van de som van de catalogusprijzen, dan is een correctieregel nodig. Die hoort in de berekening, maar niet per se op het papier van de klant.
Odoo 19 levert de bouwstenen mee om dit netjes op te lossen: secties en subsecties in offerteregels met de opties Prijzen verbergen en Samenstelling verbergen (velden collapse_prices en collapse_composition). De module koppelt de productset aan deze standaardopties in plaats van een eigen afdrukformaat te bedenken.
Wat de module doet
- Set als sectie invoegen. De wizard Set toevoegen maakt een sectieregel aan met de naam van de set, bijvoorbeeld 2 × Meetkit Standaard, en plaatst de setregels eronder. Secties binnen de set worden subsecties.
- Afdrukmodus per set. In de set wordt eenmalig vastgelegd hoe hij wordt afgedrukt. Bij het invoegen kan de modus in de wizard voor dat ene geval worden gewijzigd.
- Regels verbergen. Afzonderlijke regels kunnen als Niet afdrukken (bij de set: Verborgen) worden gemarkeerd. Ze tellen mee in het totaal, maar worden niet afgedrukt en ook niet getoond in het klantenportaal. De markering gaat van de offerte over naar de factuur.
- Vaste prijs per setregel. Een setregel kan een vaste eenheidsprijs hebben die de prijs uit de prijslijst vervangt, bijvoorbeeld voor een verborgen prijscorrectie.
Zonder de module (alleen sale_product_set) | Met sale_product_set_layout |
|---|---|
| Setregels als losse posten | Sectieregel met setnaam, aantal in de naam, regels eronder |
| Secties in de set blijven secties en doorbreken de structuur | Secties in de set worden subsecties (line_subsection) |
| Afdrukbeeld altijd gelijk | Afdrukmodus per set, te wijzigen bij het invoegen |
| Prijscorrectie zichtbaar op offerte en factuur | Regel te verbergen, maar telt wel mee in het totaal |
| Prijs alleen uit de prijslijst | Optioneel vaste prijs per setregel |
| Opmaak eindigt bij de offerte | Opmaak en verbergen gelden voor offerte, order, factuur en klantenportaal |
De drie afdrukmodi in detail
De afdrukmodus is een keuzeveld print_mode bij de productset. Bij het invoegen wordt hij vertaald naar de twee standaardschakelaars van de sectieregel. Daarvoor staat in de module één enkele, goed leesbare toewijzing:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Modus (print_mode) | Weergave in de set | Standaardoptie in Odoo | Afdrukbeeld |
|---|---|---|---|
detailed | Elke regel met prijs | geen | Sectie, alle regels met bedrag |
total_only | Regels zonder prijzen, alleen setsom | collapse_prices (Prijzen verbergen) | Sectie met som, regels alleen met omschrijving en aantal |
summary | Slechts één regel met setsom | collapse_composition (Samenstelling verbergen) | Alleen de sectieregel met de som |
Omdat de module de standaardopties van Odoo 19 gebruikt en geen eigen afdrukformaat, werken offerte, orderbevestiging, factuur en klantenportaal zonder aangepaste rapportsjablonen. Wie eigen rapportsjablonen heeft die rekening houden met de sectieopties, krijgt hetzelfde gedrag.
Verborgen regels en vaste prijs
Een veelvoorkomend geval: een pakket heeft een pakketprijs die onder de som van de afzonderlijke prijzen ligt. In plaats van de afzonderlijke prijzen te verbuigen, neemt u een correctieregel met vaste prijs (bijvoorbeeld −50,00 € per set) en verbergt u die. In de backend is de factuur navolgbaar, op de afdruk staat de pakketprijs.
Belangrijk om te begrijpen: de markering hide_in_report verandert niets aan de berekening. Aantal, prijs, korting, belasting, totalen en boekingen ontstaan ongewijzigd via standaard Odoo. De regel ontbreekt alleen op de afdruk en in het portaal.
- Bij de productset: setregel met Verborgen en optioneel Vaste prijs inclusief Eenheidsprijs.
- Bij offerte- en factuurregels: kolom Niet afdrukken (
hide_in_report), ook voor gewone regels zonder set. - Doorgeven: Bij het aanmaken van de factuur uit de order wordt de markering overgenomen naar de factuurregel. Bij het kopiëren van een offerte blijft ze behouden.
Technische uitvoering
De module is bewust klein: hij breidt bestaande modellen uit en overschrijft alleen die plekken waar Odoo beslist welke regels worden afgedrukt en hoe een set wordt ingevoegd. Er zijn geen nieuwe rapportsjablonen, geen nieuwe modellen en geen SQL-ingrepen.
Uitgebreide modellen en velden
| Model | Aanvulling |
|---|---|
product.set | Velden insert_as_section (Boolean, standaard aan) en print_mode (keuzelijst, verplicht veld, standaard detailed) |
product.set.line | Velden hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() geeft ze door aan de offerteregel |
sale.product.set.wizard | Velden insert_as_section, section_name, print_mode; _prepare_order_lines() bouwt de sectieregel op |
sale.order / sale.order.line | Veld hide_in_report (met copy=True); _get_order_lines_to_report() slaat verborgen regels over; _prepare_invoice_line() geeft de markering door |
account.move / account.move.line | Veld hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() en _get_subsection_lines() filteren verborgen regels; _get_aml_vals() neemt de markering op in de rapportgegevens |
Verbergen in de offerte
Odoo 19 bepaalt de af te drukken offerteregels in _get_order_lines_to_report(). De module hangt daar een filter aan. Een ingreep in het rapportsjabloon is niet nodig: zowel het rapportsjabloon van de offerte (sale/report/ir_actions_report_templates.xml) als het portaalsjabloon (sale/views/sale_portal_templates.xml) roept deze methode aan:
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
)
Doorgeven aan de factuur
Bij het aanmaken van de factuurregel uit de offerteregel (_prepare_invoice_line()) wordt de markering gekopieerd. Aan de factuurkant zijn er in Odoo 19 meerdere wegen waarlangs regels in het rapport terechtkomen: de hoofdlijst, de regels van een sectie en de regels van een subsectie. Daarom filtert de module op meerdere plekken (_get_move_lines_to_report() bij het document, _get_detailed_lines() en _get_subsection_lines() bij de regel); anders zou een verborgen regel via een van de andere wegen weer kunnen opduiken:
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")]
De wizard: sectie in plaats van losse regels
De wizard sale.product.set.wizard uit sale_product_set maakt de offerteregels aan in _prepare_order_lines(). De module overschrijft deze methode. Is Als sectie invoegen uitgeschakeld, dan roept hij gewoon de oorspronkelijke methode aan en blijft het gedrag zoals in de OCA-module. Anders ontstaat eerst de sectieregel met de twee standaardschakelaars, daarna volgen de setregels. Secties binnen de set worden subsecties, zodat ze de hoofdstructuur niet doorbreken:
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
Beslissingen die u niet ziet
- Gescheiden berekeningsmethoden in de wizard. Sectienaam, afdrukmodus en Als sectie invoegen hebben elk een eigen
compute-methode (store=True,readonly=False). Als alle drie dezelfde methode hadden, zou een expliciet doorgegeven waarde voor het ene veld de andere terugzetten. Dat valt vooral op wanneer de wizard vanuit code of tests met waarden wordt aangemaakt. - Sectienaam met aantal. Bij een aantal ongelijk aan 1 staat het in de naam (2 × Meetkit Standaard, met komma als decimaalteken), zodat op de afdruk te herkennen is dat het om twee sets gaat en niet om één set met een dubbel aantal.
- Vaste prijs alleen bij productregels.
prepare_sale_order_line_values()zethide_in_reportenprice_unitalleen als de setregel geen sectie- of notitieregel is. - Standaard Odoo in plaats van eigen bouw. Afdrukmodus en verbergen gebruiken bestaande hooks en velden van Odoo 19. Dat houdt updates beheersbaar: wijzigt Odoo het rapportsjabloon, dan blijft de module geldig zolang de methoden bestaan.
Installatie zonder ongewenste neveneffecten
Vereisten: Odoo 19.0, sale_product_set (OCA/sale-workflow, branch 19.0) en product_set (OCA/product-attribute, branch 19.0). De module zelf is afhankelijk van sale_management, sale_product_set en account.
De auto_install-valkuil bij OCA-modules. Veel OCA-repositories bevatten modules met auto_install. Zodra hun afhankelijkheden aanwezig zijn, installeert Odoo ze bij de eerstvolgende installatie of update ongevraagd mee. Wie daarom de complete OCA-repository in het addons_path van een productie-instantie plaatst, krijgt mogelijk modules die niemand wilde hebben. Bij OCA/product-attribute geldt dat bijvoorbeeld voor purchase_product_template_tags. Onze aanpak:
- De beide OCA-repositories ondiep klonen (
git clone --depth 1 --branch 19.0). - In een eigen map alleen de twee benodigde modules via symlink koppelen en deze map opnemen in het
addons_path, niet de volledige repositories. - Op een testdatabase installeren en controleren welke modules daarbij de status geïnstalleerd krijgen.
- Pas daarna in productie installeren, met
--workers=0, zodat de aanroep niet met de draaiende service om de HTTP-poort concurreert.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nieuwe velden en weergaven staan na de installatie in de database. Draait de service in meerprocesmodus, dan laden de workers de gewijzigde registry via het databasesignaal opnieuw. Wijzigingen in Python-bestanden van de module worden pas na een herstart van de service actief.
Gebruik stap voor stap
- Set aanmaken: Verkoop → Producten → Productsets. De set een naam geven, Als sectie invoegen en Afdrukmodus kiezen en de producten met aantallen invullen. Correctieregels voorzien van Verborgen en Vaste prijs.
- In de offerte: Op Set toevoegen klikken, set en aantal kiezen. Sectienaam en afdrukmodus kunnen vóór het invoegen worden gewijzigd.
- Afzonderlijke regels verbergen: In de offerte of in de factuur de kolom Niet afdrukken activeren.
- Controleren: Voorbeeld of pdf openen. Dezelfde weergave zien klanten in het portaal.
Tests
De module bevat zes geautomatiseerde tests die wij tegen een testdatabase uitvoeren voordat iets in productie gaat:
- Invoegen als sectie inclusief afdrukmodus en subsectie
- Verborgen regel ontbreekt in de af te drukken regels
- Modus Eén regel (
summary) - Invoegen zonder sectie, het oorspronkelijke gedrag
- Factuur neemt de markering
hide_in_reportover, de regel ontbreekt in_get_move_lines_to_report(), de sectiesom bevat haar nog steeds - Factuur in de modus Alleen totaalprijs toont de verborgen regel niet in de sectieregels, de sectiesom blijft volledig
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
De Duitse vertaling staat in i18n/de.po.
Beperkingen en juridische opmerking
- Alleen opmaak. De module verandert niet hoe Odoo prijzen, kortingen of belastingen berekent. Hij verandert alleen wat wordt afgedrukt en in het portaal wordt getoond.
- Verborgen regels zijn zichtbaar in de backend. De markering betreft afdruk en portaal, niet de schermweergave van de medewerkers.
- Factuurgegevens controleren. Verborgen regels tellen mee in het totaal, maar staan niet op het document. Of dat verenigbaar is met de eisen aan facturen in uw land, bijvoorbeeld met de voorgeschreven omschrijving van de geleverde goederen of verrichte diensten, moet u vóór gebruik op facturen met uw belastingadviseur afstemmen. Op offertes is het verbergen minder problematisch. Dit artikel is geen juridisch advies.
- Getest voor Odoo 19. Wij hebben de module ontwikkeld en getest voor Odoo 19.0 Community. Hij gebruikt sectieopties die in deze vorm in Odoo 19 aanwezig zijn. Voor andere versies hebben wij hem niet gecontroleerd.
Veelgestelde vragen
Waarvoor heb ik de module nodig als Odoo 19 al secties kent?
De sectieopties bestaan in Odoo zelf. Wat ontbrak, was de koppeling met de productset: een set moest automatisch als sectie worden ingevoegd, met de in de set vastgelegde afdrukmodus. Zonder de module moet u na elk invoegen de sectieregel handmatig aanmaken en configureren.
Wat is het verschil tussen “Prijzen verbergen” en “Samenstelling verbergen”?
“Prijzen verbergen” (collapse_prices) laat de regels staan, maar toont geen bedragen, alleen de som bij de sectie. “Samenstelling verbergen” (collapse_composition) verbergt de regels volledig; de klant ziet alleen de sectie met de som.
Telt een verborgen regel mee in het factuurtotaal?
Ja. Verborgen regels blijven deel uitmaken van het totaal, de belasting en de boeking. De markering betreft alleen de weergave op de afdruk en in het klantenportaal.
Kan ik de afdrukmodus bij het invoegen nog wijzigen?
Ja. De modus komt als voorinstelling uit de set; in de wizard kan hij voor het betreffende invoegen worden gewijzigd. De set zelf blijft daarbij ongewijzigd.
Wat gebeurt er als ik Als sectie invoegen uitschakel?
Dan gedraagt de wizard zich zoals in de OCA-module sale_product_set: de setregels worden zonder sectie ingevoegd.
Installeert de module onverwacht andere modules?
De module zelf niet. Bij OCA-repositories kan het echter tot auto_install-modules komen als u hele repositories in het addons_path plaatst. Koppel daarom alleen de twee benodigde modules (zie het gedeelte Installatie).
Werkt de module met Odoo Enterprise?
De module is alleen afhankelijk van Community-modules (sale_management, account) en de twee OCA-modules. Wij gebruiken hem op Odoo 19 Community. Op Enterprise hebben wij hem niet getest.
Onder welke licentie staat de module?
Onder de AGPL-3. U mag hem gebruiken, aanpassen en doorgeven, zolang wijzigingen onder dezelfde licentie blijven. Fouten en verbetervoorstellen zijn als issue in de GitHub-repository welkom.
Ondersteuning door Biricon IT Services
Wij beheren Odoo-installaties voor kleine en middelgrote bedrijven: van de invoering via aanpassingen en migraties tot de lopende exploitatie. Wilt u productsets, offertesjablonen of rapporten in Odoo 19 aan uw werkprocessen aanpassen, of wilt u een module zoals deze laten controleren en veilig in uw productie-instantie laten brengen, dan helpen wij u graag. Meer eigen ontwikkelingen vindt u in onze blog, bijvoorbeeld Conceptfacturen samenvoegen en Bankbetalingen automatisch aan facturen koppelen.
Stand per 2 november 2026. Moduleversie 19.0.1.0.0. De voorbeeldgegevens in de afbeeldingen zijn vrij gekozen, bedragen exclusief btw.