Den som säljer paket i Odoo, till exempel en mätare med tillbehör och montering, en arbetsplats med hårdvara och installation eller ett underhållspaket med flera tjänster, behöver två saker samtidigt i offerten: Sammansättningen måste finnas i ordern, så att lager, teknik och fakturering vet vad som ska levereras. På utskriften till kunden ska dock inte alltid varje enskild post visas med sitt enskilda pris.
OCA-modulen sale_product_set gör det möjligt att med ett klick lägga in produktset (stycklistor för försäljning) i en offert. Hur utskriften ser ut avgör den inte. För det har vi utvecklat modulen Sale Product Set Layout (sale_product_set_layout). Den är skriven för Odoo 19 och finns som fri programvara under AGPL-3-licensen gratis på GitHub:
github.com/Alex0176/odoo-sale-product-set-layout
Innehåll i det här inlägget
- Problemet: Produktset utan inverkan på utskriften
- Vad modulen gör
- De tre utskriftslägena i detalj
- Dolda rader och fast pris
- Teknisk implementering
- Installation utan oönskade biverkningar
- Användning steg för steg
- Tester
- Begränsningar och juridisk anmärkning
- Vanliga frågor
- Stöd från Biricon IT Services
Problemet: Produktset utan inverkan på utskriften
Ett produktset är i Odoo en förberedd lista med produkter och kvantiteter. I stället för att söka efter tio artiklar var för sig väljer man i offerten Lägg till set och får alla rader på en gång. Det sparar tid och förhindrar att poster glöms bort. Modulen sale_product_set från repositoryt OCA/sale-workflow gör precis detta; själva seten kommer från product_set i repositoryt OCA/product-attribute.
I praktiken återstår tre luckor:
- Ingen synlig gruppering. Setets rader hamnar som lösa poster i offerten. Efter inläggningen går det inte längre att se vilka rader som hör ihop, och setets totalsumma finns ingenstans.
- Ingen inverkan på utskriften. Ibland ska kunden se varje post med pris, ibland bara paketpriset, ibland bara en enda rad. Det är ett beslut per set eller per kund, inte något som ska göras om för hand i varje offert.
- Priskorrigeringar är fula. Om ett paket ska ha ett fast pris som avviker från summan av listpriserna behövs en korrigeringsrad. Den hör hemma i beräkningen, men inte nödvändigtvis på kundens papper.
Odoo 19 har byggstenarna för att lösa detta på ett rent sätt: avsnitt och underavsnitt i offertrader med alternativen Dölj priser och Dölj sammansättning (fälten collapse_prices och collapse_composition). Modulen kopplar produktsetet till dessa standardalternativ i stället för att uppfinna ett eget utskriftsformat.
Vad modulen gör
- Lägg in set som avsnitt. Guiden Lägg till set skapar en avsnittsrad med setets namn, till exempel 2 × Mätkit Standard, och placerar setets rader under den. Avsnitt inom setet blir underavsnitt.
- Utskriftsläge per set. I setet anges en gång hur det ska skrivas ut. Vid inläggningen kan läget ändras i guiden för just det tillfället.
- Dölja rader. Enskilda rader kan markeras som Skriv inte ut (på setet: Dold). De räknas med i summan, men skrivs varken ut eller visas i kundportalen. Markeringen följer med från offerten till fakturan.
- Fast pris per setrad. En setrad kan ha ett fast enhetspris som ersätter priset från prislistan, till exempel för en dold priskorrigering.
Utan modulen (endast sale_product_set) | Med sale_product_set_layout |
|---|---|
| Setrader som lösa poster | Avsnittsrad med setnamn, kvantitet i namnet, rader under |
| Avsnitt i setet förblir avsnitt och sliter sönder strukturen | Avsnitt i setet blir underavsnitt (line_subsection) |
| Utskriften alltid likadan | Utskriftsläge per set, kan ändras vid inläggning |
| Priskorrigering synlig på offert och faktura | Raden kan döljas men räknas med i summan |
| Pris endast från prislistan | Valfritt fast pris per setrad |
| Layouten slutar vid offerten | Layout och döljning gäller för offert, order, faktura och kundportal |
De tre utskriftslägena i detalj
Utskriftsläget är ett urvalsfält print_mode på produktsetet. Vid inläggningen översätts det till avsnittsradens två standardreglage. För det finns i modulen en enda, lättläst tilldelning:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Läge (print_mode) | Visning i setet | Standardalternativ i Odoo | Utskriftsbild |
|---|---|---|---|
detailed | Varje rad med pris | inga | Avsnitt, alla rader med belopp |
total_only | Rader utan priser, bara setets summa | collapse_prices (Dölj priser) | Avsnitt med summa, rader endast med benämning och kvantitet |
summary | Bara en rad med setets summa | collapse_composition (Dölj sammansättning) | Bara avsnittsraden med summan |
Eftersom modulen använder standardalternativen i Odoo 19 och inget eget utskriftsformat fungerar offert, orderbekräftelse, faktura och kundportal utan anpassade rapportmallar. Den som har egna rapportmallar som tar hänsyn till avsnittsalternativen får samma beteende.
Dolda rader och fast pris
Ett vanligt fall: Ett paket har ett paketpris som ligger under summan av de enskilda priserna. I stället för att förvränga de enskilda priserna tar man en korrigeringsrad med fast pris (till exempel −50,00 € per set) och döljer den. I backend är fakturan spårbar, på utskriften står paketpriset.
Viktigt för förståelsen: Markeringen hide_in_report ändrar ingenting i beräkningen. Kvantitet, pris, rabatt, skatt, summor och bokföringsposter uppstår oförändrade genom standard-Odoo. Raden saknas bara i utskriften och i portalen.
- På produktsetet: Setrad med Dold och eventuellt Fast pris inklusive Enhetspris.
- På offert- och fakturarader: Kolumnen Skriv inte ut (
hide_in_report), även för vanliga rader utan set. - Vidareföring: När fakturan skapas från ordern överförs markeringen till fakturaraden. När en offert kopieras finns den kvar.
Teknisk implementering
Modulen är medvetet liten: Den utökar befintliga modeller och skriver bara över de ställen där Odoo bestämmer vilka rader som skrivs ut och hur ett set läggs in. Det finns inga nya rapportmallar, inga nya modeller och inga SQL-ingrepp.
Utökade modeller och fält
| Modell | Tillägg |
|---|---|
product.set | Fälten insert_as_section (Boolean, som standard på) och print_mode (urval, obligatoriskt fält, standard detailed) |
product.set.line | Fälten hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() för dem vidare till offertraden |
sale.product.set.wizard | Fälten insert_as_section, section_name, print_mode; _prepare_order_lines() bygger avsnittsraden |
sale.order / sale.order.line | Fältet hide_in_report (med copy=True); _get_order_lines_to_report() hoppar över dolda rader; _prepare_invoice_line() för markeringen vidare |
account.move / account.move.line | Fältet hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() och _get_subsection_lines() filtrerar bort dolda rader; _get_aml_vals() för in markeringen i rapportdata |
Dölja i offerten
Odoo 19 fastställer de offertrader som ska skrivas ut i _get_order_lines_to_report(). Modulen lägger till ett filter där. Något ingrepp i rapportmallen behövs inte: Både offertens rapportmall (sale/report/ir_actions_report_templates.xml) och portalmallen (sale/views/sale_portal_templates.xml) anropar den här metoden:
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
)
Vidareföring till fakturan
När fakturaraden skapas från offertraden (_prepare_invoice_line()) kopieras markeringen. På fakturasidan finns det i Odoo 19 flera vägar genom vilka rader kommer in i rapporten: huvudlistan, raderna i ett avsnitt och raderna i ett underavsnitt. Därför filtrerar modulen på flera ställen (_get_move_lines_to_report() på dokumentet, _get_detailed_lines() och _get_subsection_lines() på raden), annars skulle en dold rad kunna dyka upp igen via någon av de andra vägarna:
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")]
Guiden: avsnitt i stället för lösa rader
Guiden sale.product.set.wizard från sale_product_set skapar offertraderna i _prepare_order_lines(). Modulen skriver över den metoden. Är Lägg in som avsnitt avstängt anropar den helt enkelt den ursprungliga metoden, och beteendet förblir som i OCA-modulen. I annat fall skapas först avsnittsraden med de två standardreglagen, därefter följer setets rader. Avsnitt inom setet blir underavsnitt, så att de inte sliter sönder huvudstrukturen:
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
Beslut som man inte ser
- Separata beräkningsmetoder i guiden. Avsnittsnamn, utskriftsläge och Lägg in som avsnitt har var sin
compute-metod (store=True,readonly=False). Om alla tre hade samma metod skulle ett uttryckligen angivet värde för ett fält återställa de andra. Det märks framför allt när guiden skapas med värden från kod eller tester. - Avsnittsnamn med kvantitet. Vid en kvantitet som skiljer sig från 1 står den i namnet (2 × Mätkit Standard, med komma som decimaltecken), så att man på utskriften ser att det rör sig om två set och inte ett set med dubbel kvantitet.
- Fast pris endast på produktrader.
prepare_sale_order_line_values()sätterhide_in_reportochprice_unitendast om setraden inte är en avsnitts- eller anteckningsrad. - Standard-Odoo i stället för egen konstruktion. Utskriftsläge och döljning använder befintliga hooks och fält i Odoo 19. Det gör uppdateringar hanterbara: Ändrar Odoo rapportmallen förblir modulen giltig så länge metoderna finns kvar.
Installation utan oönskade biverkningar
Förutsättningar: Odoo 19.0, sale_product_set (OCA/sale-workflow, gren 19.0) och product_set (OCA/product-attribute, gren 19.0). Själva modulen är beroende av sale_management, sale_product_set och account.
auto_install-fällan hos OCA-moduler. Många OCA-repositorier innehåller moduler med auto_install. Så snart deras beroenden finns installerar Odoo dem ofrågat vid nästa installation eller uppdatering. Den som därför lägger hela OCA-repositoryt i addons_path för en produktionsinstans får kanske moduler som ingen ville ha. För OCA/product-attribute gäller det till exempel purchase_product_template_tags. Vårt tillvägagångssätt:
- Klona de båda OCA-repositorierna ytligt (
git clone --depth 1 --branch 19.0). - Länka i en egen katalog bara de två nödvändiga modulerna med symlänk och lägg till den katalogen i
addons_path, inte hela repositorierna. - Installera på en testdatabas och kontrollera vilka moduler som får status installerad.
- Först därefter installera i produktion, med
--workers=0, så att anropet inte konkurrerar med den körande tjänsten om HTTP-porten.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nya fält och vyer finns i databasen efter installationen. Om tjänsten körs i flerprocessläge laddar workers om den ändrade registryn via databassignalen. Ändringar i modulens Python-filer börjar gälla först efter en omstart av tjänsten.
Användning steg för steg
- Skapa set: Försäljning → Produkter → Produktset. Namnge setet, välj Lägg in som avsnitt och Utskriftsläge, ange produkterna med kvantiteter. Förse korrigeringsrader med Dold och Fast pris.
- I offerten: Klicka på Lägg till set, välj set och kvantitet. Avsnittsnamn och utskriftsläge kan ändras innan inläggningen.
- Dölja enskilda rader: Aktivera kolumnen Skriv inte ut i offerten eller fakturan.
- Kontrollera: Öppna förhandsgranskningen eller PDF:en. Samma framställning ser kunderna i portalen.
Tester
Modulen levereras med sex automatiserade tester som vi kör mot en testdatabas innan något tas i produktion:
- Inläggning som avsnitt inklusive utskriftsläge och underavsnitt
- Dold rad saknas bland raderna som ska skrivas ut
- Läget En rad (
summary) - Inläggning utan avsnitt, det ursprungliga beteendet
- Fakturan övertar markeringen
hide_in_report, raden saknas i_get_move_lines_to_report(), avsnittssumman innehåller den fortfarande - Faktura i läget Endast totalpris visar inte den dolda raden bland avsnittsraderna, avsnittssumman förblir fullständig
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Den tyska översättningen finns i i18n/de.po.
Begränsningar och juridisk anmärkning
- Endast layout. Modulen ändrar inte hur Odoo beräknar priser, rabatter eller skatter. Den ändrar bara vad som skrivs ut och visas i portalen.
- Dolda rader syns i backend. Markeringen gäller utskrift och portal, inte medarbetarnas skärmvy.
- Kontrollera fakturauppgifterna. Dolda rader räknas med i summan men står inte på dokumentet. Om det är förenligt med kraven på fakturor i ditt land, till exempel med den föreskrivna beskrivningen av levererade varor eller utförda tjänster, bör du klargöra innan du använder det på fakturor med din skatterådgivare. På offerter är döljning mindre problematiskt. Inlägget är ingen juridisk rådgivning.
- Testad för Odoo 19. Vi har utvecklat och testat modulen för Odoo 19.0 Community. Den använder avsnittsalternativ som finns i den här formen i Odoo 19. För andra versioner har vi inte kontrollerat den.
Vanliga frågor
Varför behöver jag modulen om Odoo 19 redan kan avsnitt?
Avsnittsalternativen finns i Odoo självt. Det som saknades var kopplingen till produktsetet: Ett set skulle automatiskt läggas in som avsnitt, med det utskriftsläge som angetts i setet. Utan modulen måste man efter varje inläggning skapa och konfigurera avsnittsraden för hand.
Hur skiljer sig ”Dölj priser” och ”Dölj sammansättning” åt?
”Dölj priser” (collapse_prices) låter raderna stå kvar men visar inga belopp, bara summan på avsnittet. ”Dölj sammansättning” (collapse_composition) döljer raderna helt, kunden ser bara avsnittet med summan.
Räknas en dold rad med i fakturasumman?
Ja. Dolda rader finns kvar i summa, skatt och bokföring. Markeringen gäller bara framställningen på utskriften och i kundportalen.
Kan jag fortfarande ändra utskriftsläget vid inläggningen?
Ja. Läget kommer som förval från setet, och i guiden kan det ändras för den aktuella inläggningen. Setet självt förblir oförändrat.
Vad händer om jag stänger av Lägg in som avsnitt?
Då beter sig guiden som i OCA-modulen sale_product_set: Setets rader läggs in utan avsnitt.
Installerar modulen oväntat ytterligare moduler?
Själva modulen gör det inte. Med OCA-repositorier kan det dock uppstå auto_install-moduler om man lägger hela repositorier i addons_path. Länka därför bara de två nödvändiga modulerna (se avsnittet Installation).
Fungerar modulen med Odoo Enterprise?
Modulen är bara beroende av Community-moduler (sale_management, account) och de båda OCA-modulerna. Vi använder den på Odoo 19 Community. På Enterprise har vi inte testat den.
Under vilken licens står modulen?
Under AGPL-3. Du får använda, anpassa och vidareförmedla den, så länge ändringar förblir under samma licens. Fel och förbättringsförslag är välkomna som issue i GitHub-repositoryt .
Stöd från Biricon IT Services
Vi sköter Odoo-installationer för små och medelstora företag: från införande via anpassningar och migreringar till löpande drift. Om du vill anpassa produktset, offertmallar eller rapporter i Odoo 19 efter dina arbetsflöden, eller om du vill granska en modul som den här och på ett säkert sätt ta den i produktion i din produktionsinstans, hjälper vi dig gärna. Fler egenutvecklingar hittar du i vår blogg, till exempel Slå ihop fakturautkast och Matcha bankbetalningar automatiskt mot fakturor.
Läget per den 2 november 2026. Modulversion 19.0.1.0.0. Exempeldata i bilderna är fritt valda, belopp netto.