Kdo v Odoo prodává balíčky, například měřicí přístroj s příslušenstvím a montáží, pracoviště s hardwarem a zprovozněním nebo servisní balíček z několika služeb, potřebuje v nabídce dvě věci současně: Složení musí být uvedeno v objednávce, aby sklad, technika a fakturace věděly, co se dodává. Na výtisku pro zákazníka se však nemá v každém případě objevit každá jednotlivá položka s každou jednotkovou cenou.
Modul OCA sale_product_set umožňuje jedním kliknutím vložit do nabídky produktové sady (kusovníky pro prodej). O vzhledu tisku však nerozhoduje. Proto jsme vyvinuli modul Sale Product Set Layout (sale_product_set_layout). Je napsán pro Odoo 19 a je zdarma k dispozici na GitHubu jako svobodný software pod licencí AGPL-3:
github.com/Alex0176/odoo-sale-product-set-layout
Obsah tohoto článku
Problém: Produktové sady bez vlivu na vzhled tisku
Produktová sada je v Odoo připravený seznam produktů s množstvími. Místo hledání deseti položek jednotlivě zvolíte v nabídce Přidat sadu a získáte všechny řádky najednou. To šetří čas a zabraňuje zapomenutým položkám. Modul sale_product_set z repozitáře OCA/sale-workflow dělá přesně toto; samotné sady pocházejí z product_set v repozitáři OCA/product-attribute.
V praxi zůstávají tři mezery:
- Žádné rozpoznatelné seskupení. Řádky sady skončí v nabídce jako volné položky. Po vložení už není vidět, které řádky k sobě patří, a celkový součet sady není nikde uveden.
- Žádný vliv na výtisk. Někdy má zákazník vidět každou položku s cenou, jindy jen cenu balíčku, jindy jen jediný řádek. To je rozhodnutí pro každou sadu nebo každého zákazníka, nikoli něco, co se má u každé nabídky nastavovat ručně.
- Cenové korekce jsou nevzhledné. Má-li mít balíček pevnou cenu odlišnou od součtu ceníkových cen, je potřeba korekční řádek. Ten patří do výpočtu, ale nemusí být nutně na papíře pro zákazníka.
Odoo 19 nabízí stavební kameny, které umožňují vyřešit to čistě: sekce a podsekce v řádcích nabídky s volbami Skrýt ceny a Skrýt složení (pole collapse_prices a collapse_composition). Modul propojuje produktovou sadu s těmito standardními volbami, místo aby vymýšlel vlastní formát tisku.
Co modul dělá
- Vložení sady jako sekce. Průvodce Přidat sadu vytvoří řádek sekce s názvem sady, například 2 × Měřicí sada Standard, a pod něj umístí řádky sady. Sekce uvnitř sady se stanou podsekcemi.
- Režim tisku pro každou sadu. V sadě se jednou určí, jak se má tisknout. Při vkládání lze režim v průvodci pro daný případ změnit.
- Skrytí řádků. Jednotlivé řádky lze označit jako Netisknout (v sadě: Skryto). Počítají se do součtu, ale nejsou ani vytištěny, ani zobrazeny v zákaznickém portálu. Příznak se z nabídky přenáší na fakturu.
- Pevná cena pro každý řádek sady. Řádek sady může mít pevnou jednotkovou cenu, která nahrazuje cenu z ceníku, například pro skrytou cenovou korekci.
Bez modulu (pouze sale_product_set) | S sale_product_set_layout |
|---|---|
| Řádky sady jako volné položky | Řádek sekce s názvem sady, množství v názvu, pod ním řádky |
| Sekce v sadě zůstávají sekcemi a rozbíjejí strukturu | Sekce v sadě se stávají podsekcemi (line_subsection) |
| Vzhled tisku je stále stejný | Režim tisku pro každou sadu, měnitelný při vkládání |
| Cenová korekce viditelná v nabídce i na faktuře | Řádek lze skrýt, ale počítá se do součtu |
| Cena pouze z ceníku | Volitelně pevná cena pro každý řádek sady |
| Rozvržení končí u nabídky | Rozvržení a skrytí platí pro nabídku, objednávku, fakturu a zákaznický portál |
Tři režimy tisku podrobně
Režim tisku je výběrové pole print_mode u produktové sady. Při vkládání se převádí na dva standardní přepínače řádku sekce. K tomu slouží v modulu jediné, snadno čitelné přiřazení:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Režim (print_mode) | Zobrazení v sadě | Standardní volba v Odoo | Vzhled tisku |
|---|---|---|---|
detailed | Každý řádek s cenou | žádná | Sekce, všechny řádky s částkou |
total_only | Řádky bez cen, pouze součet sady | collapse_prices (Skrýt ceny) | Sekce se součtem, řádky pouze s označením a množstvím |
summary | Pouze jeden řádek se součtem sady | collapse_composition (Skrýt složení) | Pouze řádek sekce se součtem |
Protože modul používá standardní volby Odoo 19 a nikoli vlastní formát tisku, fungují nabídka, potvrzení objednávky, faktura i zákaznický portál bez upravených šablon sestav. Kdo má vlastní šablony sestav, které respektují volby sekcí, získá totéž chování.
Skryté řádky a pevná cena
Častý případ: Balíček má cenu, která je nižší než součet jednotlivých cen. Místo upravování jednotlivých cen se použije korekční řádek s pevnou cenou (například −50,00 € za sadu), který se skryje. V backendu je faktura srozumitelná, na výtisku je uvedena cena balíčku.
Důležité pro pochopení: Příznak hide_in_report nic nemění na výpočtu. Množství, cena, sleva, daň, součty i účetní zápisy vznikají beze změny standardním Odoo. Řádek chybí pouze ve výtisku a v portálu.
- V produktové sadě: řádek sady s Skryto a volitelně Pevná cena včetně Jednotkové ceny.
- V řádcích nabídky a faktury: sloupec Netisknout (
hide_in_report), také pro běžné řádky bez sady. - Přenos: Při vytvoření faktury z objednávky se příznak přenese na řádek faktury. Při kopírování nabídky zůstane zachován.
Technické provedení
Modul je záměrně malý: rozšiřuje existující modely a přepisuje pouze místa, kde Odoo rozhoduje, které řádky se tisknou a jak se sada vkládá. Neobsahuje žádné nové šablony sestav, žádné nové modely a žádné zásahy do SQL.
Rozšířené modely a pole
| Model | Rozšíření |
|---|---|
product.set | Pole insert_as_section (Boolean, ve výchozím stavu zapnuto) a print_mode (výběr, povinné pole, výchozí detailed) |
product.set.line | Pole hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() je předává řádku nabídky |
sale.product.set.wizard | Pole insert_as_section, section_name, print_mode; _prepare_order_lines() sestavuje řádek sekce |
sale.order / sale.order.line | Pole hide_in_report (s copy=True); _get_order_lines_to_report() přeskakuje skryté řádky; _prepare_invoice_line() předává příznak dál |
account.move / account.move.line | Pole hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() a _get_subsection_lines() filtrují skryté řádky; _get_aml_vals() přenáší příznak do dat sestavy |
Skrytí v nabídce
Odoo 19 určuje řádky nabídky k tisku v _get_order_lines_to_report(). Modul tam připojuje filtr. Zásah do šablony sestavy není nutný: Šablona sestavy nabídky (sale/report/ir_actions_report_templates.xml) i šablona portálu (sale/views/sale_portal_templates.xml) tuto metodu volají:
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
)
Přenos na fakturu
Při vytváření řádku faktury z řádku nabídky (_prepare_invoice_line()) se příznak zkopíruje. Na straně faktury existuje v Odoo 19 několik cest, kterými se řádky dostávají do sestavy: hlavní seznam, řádky sekce a řádky podsekce. Modul proto filtruje na několika místech (_get_move_lines_to_report() u dokladu, _get_detailed_lines() a _get_subsection_lines() u řádku), jinak by se skrytý řádek mohl jednou z ostatních cest znovu objevit:
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")]
Průvodce: sekce místo volných řádků
Průvodce sale.product.set.wizard z modulu sale_product_set vytváří řádky nabídky v _prepare_order_lines(). Modul tuto metodu přepisuje. Je-li Vložit jako sekci vypnuto, jednoduše zavolá původní metodu a chování zůstává stejné jako v modulu OCA. Jinak nejprve vznikne řádek sekce se dvěma standardními přepínači a poté následují řádky sady. Sekce uvnitř sady se stanou podsekcemi, aby nerozbíjely hlavní strukturu:
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
Rozhodnutí, která není vidět
- Oddělené výpočetní metody v průvodci. Název sekce, režim tisku a Vložit jako sekci mají každý vlastní metodu
compute(store=True,readonly=False). Pokud by všechny tři měly stejnou metodu, explicitně předaná hodnota jednoho pole by resetovala ostatní. To je nejvíce patrné, když se průvodce vytváří z kódu nebo z testů s hodnotami. - Název sekce s množstvím. Je-li množství různé od 1, uvádí se v názvu (2 × Měřicí sada Standard, s čárkou jako desetinným oddělovačem), aby bylo na výtisku poznat, že jde o dvě sady, nikoli o jednu sadu s dvojitým množstvím.
- Pevná cena pouze u řádků produktů.
prepare_sale_order_line_values()nastavujehide_in_reportaprice_unitpouze tehdy, když řádek sady není řádkem sekce ani poznámky. - Standardní Odoo místo vlastního řešení. Režim tisku a skrývání využívají existující háčky a pole Odoo 19. To činí aktualizace zvládnutelnými: Pokud Odoo změní šablonu sestavy, modul zůstane platný, dokud metody existují.
Instalace bez nežádoucích vedlejších účinků
Předpoklady: Odoo 19.0, sale_product_set (OCA/sale-workflow, větev 19.0) a product_set (OCA/product-attribute, větev 19.0). Samotný modul závisí na sale_management, sale_product_set a account.
Past auto_install u modulů OCA. Mnoho repozitářů OCA obsahuje moduly s auto_install. Jakmile jsou k dispozici jejich závislosti, Odoo je při další instalaci nebo aktualizaci nainstaluje bez ptaní. Kdo proto vloží celý repozitář OCA do addons_path produkční instance, může získat moduly, které nikdo nechtěl. U OCA/product-attribute se to týká například purchase_product_template_tags. Náš postup:
- Oba repozitáře OCA naklonovat mělce (
git clone --depth 1 --branch 19.0). - Ve vlastním adresáři propojit symbolickými odkazy pouze dva potřebné moduly a tento adresář přidat do
addons_path, nikoli celé repozitáře. - Nainstalovat na testovací databázi a zkontrolovat, které moduly při tom získají stav nainstalováno.
- Teprve poté instalovat do produkce, s
--workers=0, aby volání nesoupeřilo s běžící službou o port HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nová pole a zobrazení jsou po instalaci v databázi. Pokud služba běží ve vícenásobném procesním režimu, workery načtou změněný registr prostřednictvím databázového signálu. Změny v souborech Pythonu modulu se projeví teprve po restartu služby.
Obsluha krok za krokem
- Vytvoření sady: Prodej → Produkty → Produktové sady. Sadu pojmenovat, zvolit Vložit jako sekci a Režim tisku, zadat produkty s množstvími. Korekční řádky opatřit Skryto a Pevná cena.
- V nabídce: Kliknout na Přidat sadu, zvolit sadu a množství. Název sekce a režim tisku lze před vložením změnit.
- Skrytí jednotlivých řádků: V nabídce nebo na faktuře aktivovat sloupec Netisknout.
- Kontrola: Otevřít náhled nebo PDF. Stejné zobrazení vidí zákazníci v portálu.
Testy
Modul obsahuje šest automatizovaných testů, které spouštíme proti testovací databázi, než cokoli nasadíme do produkce:
- Vložení jako sekce včetně režimu tisku a podsekce
- Skrytý řádek chybí v řádcích k tisku
- Režim Jeden řádek (
summary) - Vložení bez sekce, původní chování
- Faktura přebírá příznak
hide_in_report, řádek chybí v_get_move_lines_to_report(), součet sekce jej nadále obsahuje - Faktura v režimu Pouze celková cena nezobrazuje skrytý řádek v řádcích sekce, součet sekce zůstává úplný
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Německý překlad se nachází v i18n/de.po.
Omezení a právní upozornění
- Pouze rozvržení. Modul nemění, jak Odoo počítá ceny, slevy nebo daně. Mění pouze to, co se tiskne a zobrazuje v portálu.
- Skryté řádky jsou v backendu viditelné. Příznak se týká výtisku a portálu, nikoli zobrazení na obrazovce pro zaměstnance.
- Zkontrolujte údaje na faktuře. Skryté řádky se počítají do součtu, ale na dokladu nejsou uvedeny. Zda je to slučitelné s požadavky na faktury ve vaší zemi, například s předepsaným popisem dodaného zboží nebo poskytnutých služeb, byste měli před nasazením na fakturách objasnit se svým daňovým poradcem. Na nabídkách je skrývání méně problematické. Článek není právním poradenstvím.
- Testováno pro Odoo 19. Modul jsme vyvinuli a testovali pro Odoo 19.0 Community. Využívá volby sekcí, které jsou v této podobě v Odoo 19 k dispozici. Pro jiné verze jsme jej neověřovali.
Časté dotazy
K čemu potřebuji modul, když Odoo 19 už sekce umí?
Volby sekcí existují v samotném Odoo. Chybělo propojení s produktovou sadou: Sada se měla automaticky vkládat jako sekce s režimem tisku uloženým v sadě. Bez modulu je nutné po každém vložení řádek sekce ručně vytvořit a nastavit.
Jak se liší „Skrýt ceny“ a „Skrýt složení“?
„Skrýt ceny“ (collapse_prices) nechá řádky zobrazené, ale nezobrazí částky, pouze součet u sekce. „Skrýt složení“ (collapse_composition) řádky skryje úplně, zákazník vidí pouze sekci se součtem.
Počítá se skrytý řádek do součtu faktury?
Ano. Skryté řádky zůstávají v součtu, v dani i v účtování. Příznak se týká pouze zobrazení na výtisku a v zákaznickém portálu.
Mohu režim tisku při vkládání ještě změnit?
Ano. Režim se přebírá jako výchozí hodnota ze sady, v průvodci jej lze pro konkrétní vložení změnit. Samotná sada přitom zůstává beze změny.
Co se stane, když vypnu Vložit jako sekci?
Pak se průvodce chová jako v modulu OCA sale_product_set: Řádky sady se vloží bez sekce.
Nainstaluje modul neočekávaně další moduly?
Samotný modul ne. U repozitářů OCA však může dojít k modulům s auto_install, pokud se do addons_path vloží celé repozitáře. Propojte proto pouze oba potřebné moduly (viz část Instalace).
Funguje modul s Odoo Enterprise?
Modul závisí pouze na modulech Community (sale_management, account) a na obou modulech OCA. Používáme jej na Odoo 19 Community. Na Enterprise jsme jej netestovali.
Pod jakou licencí je modul vydán?
Pod AGPL-3. Smíte jej používat, upravovat a šířit, pokud změny zůstanou pod stejnou licencí. Chyby a návrhy na zlepšení jsou vítány formou issue v repozitáři GitHub .
Podpora od Biricon IT Services
Spravujeme instalace Odoo pro malé a střední podniky: od zavedení přes úpravy a migrace až po průběžný provoz. Pokud chcete přizpůsobit produktové sady, šablony nabídek nebo sestavy v Odoo 19 svým procesům, nebo pokud chcete takový modul prověřit a bezpečně nasadit do své produkční instance, rádi vám pomůžeme. Další vlastní vývoj najdete v našem blogu, například Slučování konceptů faktur a Automatické přiřazování bankovních plateb k fakturám.
Stav k 2. listopadu 2026. Verze modulu 19.0.1.0.0. Ukázková data na obrázcích jsou zvolena libovolně, částky netto.