Kto w Odoo sprzedaje pakiety, na przykład miernik z akcesoriami i montażem, stanowisko pracy ze sprzętem i konfiguracją albo pakiet serwisowy złożony z kilku usług, potrzebuje w ofercie jednocześnie dwóch rzeczy: skład musi znaleźć się w zleceniu, aby magazyn, dział techniczny i fakturowanie wiedziały, co jest dostarczane. Na wydruku dla klienta nie powinna jednak w każdym przypadku pojawiać się każda pozycja z każdą ceną jednostkową.
Moduł OCA sale_product_set pozwala jednym kliknięciem wstawić do oferty zestawy produktów (listy materiałowe dla sprzedaży). O wyglądzie wydruku nie decyduje. Dlatego opracowaliśmy moduł Sale Product Set Layout (sale_product_set_layout). Jest napisany dla Odoo 19 i jako wolne oprogramowanie na licencji AGPL-3 jest dostępny bezpłatnie na GitHubie:
github.com/Alex0176/odoo-sale-product-set-layout
Spis treści tego artykułu
- Problem: zestawy produktów bez wpływu na wygląd wydruku
- Co robi moduł
- Trzy tryby wydruku w szczegółach
- Ukryte wiersze i cena stała
- Realizacja techniczna
- Instalacja bez niepożądanych skutków ubocznych
- Obsługa krok po kroku
- Testy
- Ograniczenia i uwaga prawna
- Najczęstsze pytania
- Wsparcie ze strony Biricon IT Services
Problem: zestawy produktów bez wpływu na wygląd wydruku
Zestaw produktów w Odoo to przygotowana lista produktów z ilościami. Zamiast szukać dziesięciu artykułów pojedynczo, w ofercie wybiera się Dodaj zestaw i otrzymuje wszystkie wiersze naraz. Oszczędza to czas i zapobiega pominięciu pozycji. Moduł sale_product_set z repozytorium OCA/sale-workflow robi dokładnie to; same zestawy pochodzą z product_set w repozytorium OCA/product-attribute.
W praktyce pozostają trzy luki:
- Brak czytelnego grupowania. Wiersze zestawu trafiają do oferty jako luźne pozycje. Po wstawieniu nie widać już, które wiersze należą do siebie, a suma zestawu nie jest nigdzie pokazana.
- Brak wpływu na wydruk. Raz klient ma widzieć każdą pozycję z ceną, raz tylko cenę pakietu, a raz tylko jeden wiersz. To decyzja dla zestawu lub klienta, a nie coś, co trzeba za każdym razem ręcznie ustawiać w ofercie.
- Korekty cen są nieeleganckie. Jeśli pakiet ma mieć cenę stałą, odbiegającą od sumy cen katalogowych, potrzebny jest wiersz korygujący. Należy on do kalkulacji, ale niekoniecznie na papier klienta.
Odoo 19 oferuje elementy, które pozwalają rozwiązać to czysto: sekcje i podsekcje w wierszach oferty z opcjami Ukryj ceny i Ukryj skład (pola collapse_prices i collapse_composition). Moduł łączy zestaw produktów z tymi standardowymi opcjami, zamiast wymyślać własny format wydruku.
Co robi moduł
- Wstawianie zestawu jako sekcji. Kreator Dodaj zestaw tworzy wiersz sekcji z nazwą zestawu, na przykład 2 × Zestaw pomiarowy Standard, i umieszcza pod nim wiersze zestawu. Sekcje wewnątrz zestawu stają się podsekcjami.
- Tryb wydruku dla każdego zestawu. W zestawie określa się raz, jak ma być drukowany. Przy wstawianiu tryb można zmienić w kreatorze na potrzeby tego jednego przypadku.
- Ukrywanie wierszy. Poszczególne wiersze można oznaczyć jako Nie drukuj (w zestawie: Ukryty). Wliczają się do sumy, ale nie są ani drukowane, ani wyświetlane w portalu klienta. Oznaczenie przechodzi z oferty na fakturę.
- Cena stała dla wiersza zestawu. Wiersz zestawu może mieć stałą cenę jednostkową, która zastępuje cenę z cennika, na przykład dla ukrytej korekty ceny.
Bez modułu (tylko sale_product_set) | Z sale_product_set_layout |
|---|---|
| Wiersze zestawu jako luźne pozycje | Wiersz sekcji z nazwą zestawu, ilością w nazwie i wierszami pod spodem |
| Sekcje w zestawie pozostają sekcjami i rozbijają strukturę | Sekcje w zestawie stają się podsekcjami (line_subsection) |
| Wygląd wydruku zawsze taki sam | Tryb wydruku dla każdego zestawu, zmienialny przy wstawianiu |
| Korekta ceny widoczna w ofercie i na fakturze | Wiersz można ukryć, a mimo to wlicza się do sumy |
| Cena wyłącznie z cennika | Opcjonalnie cena stała dla wiersza zestawu |
| Układ kończy się na ofercie | Układ i ukrywanie obowiązują dla oferty, zamówienia, faktury i portalu klienta |
Trzy tryby wydruku w szczegółach
Tryb wydruku to pole wyboru print_mode w zestawie produktów. Przy wstawianiu jest tłumaczony na dwa standardowe przełączniki wiersza sekcji. Służy do tego w module jedno, łatwe do odczytania przyporządkowanie:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Tryb (print_mode) | Wyświetlanie w zestawie | Standardowa opcja w Odoo | Wygląd wydruku |
|---|---|---|---|
detailed | Każdy wiersz z ceną | brak | Sekcja, wszystkie wiersze z kwotą |
total_only | Wiersze bez cen, tylko suma zestawu | collapse_prices (Ukryj ceny) | Sekcja z sumą, wiersze tylko z nazwą i ilością |
summary | Tylko jeden wiersz z sumą zestawu | collapse_composition (Ukryj skład) | Tylko wiersz sekcji z sumą |
Ponieważ moduł korzysta ze standardowych opcji Odoo 19, a nie z własnego formatu wydruku, oferta, potwierdzenie zamówienia, faktura i portal klienta działają bez dostosowanych szablonów raportów. Kto ma własne szablony raportów uwzględniające opcje sekcji, otrzyma to samo zachowanie.
Ukryte wiersze i cena stała
Częsty przypadek: pakiet ma cenę pakietową niższą od sumy cen poszczególnych pozycji. Zamiast naginać ceny poszczególnych pozycji, stosuje się wiersz korygujący z ceną stałą (na przykład −50,00 € za zestaw) i ukrywa go. W backendzie faktura jest czytelna, a na wydruku widnieje cena pakietu.
Ważne dla zrozumienia: oznaczenie hide_in_report niczego nie zmienia w obliczeniach. Ilość, cena, rabat, podatek, sumy i zapisy księgowe powstają bez zmian w standardowym Odoo. Wiersza brakuje tylko na wydruku i w portalu.
- W zestawie produktów: wiersz zestawu z Ukryty i opcjonalnie Cena stała wraz z Ceną jednostkową.
- W wierszach oferty i faktury: kolumna Nie drukuj (
hide_in_report), także dla zwykłych wierszy bez zestawu. - Przekazywanie: przy tworzeniu faktury ze zlecenia oznaczenie jest przenoszone do wiersza faktury. Przy kopiowaniu oferty zostaje zachowane.
Realizacja techniczna
Moduł jest celowo mały: rozszerza istniejące modele i nadpisuje tylko te miejsca, w których Odoo decyduje, które wiersze są drukowane i jak wstawiany jest zestaw. Nie ma nowych szablonów raportów, nowych modeli ani ingerencji w SQL.
Rozszerzone modele i pola
| Model | Rozszerzenie |
|---|---|
product.set | Pola insert_as_section (Boolean, domyślnie włączone) i print_mode (wybór, pole wymagane, domyślnie detailed) |
product.set.line | Pola hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() przekazuje je do wiersza oferty |
sale.product.set.wizard | Pola insert_as_section, section_name, print_mode; _prepare_order_lines() buduje wiersz sekcji |
sale.order / sale.order.line | Pole hide_in_report (z copy=True); _get_order_lines_to_report() pomija ukryte wiersze; _prepare_invoice_line() przekazuje oznaczenie dalej |
account.move / account.move.line | Pole hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() i _get_subsection_lines() filtrują ukryte wiersze; _get_aml_vals() przenosi oznaczenie do danych raportu |
Ukrywanie w ofercie
Odoo 19 określa wiersze oferty do wydrukowania w _get_order_lines_to_report(). Moduł dołącza tam filtr. Ingerencja w szablon raportu nie jest potrzebna: zarówno szablon raportu oferty (sale/report/ir_actions_report_templates.xml), jak i szablon portalu (sale/views/sale_portal_templates.xml) wywołują tę metodę:
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
)
Przekazanie do faktury
Przy tworzeniu wiersza faktury z wiersza oferty (_prepare_invoice_line()) oznaczenie jest kopiowane. Po stronie faktury w Odoo 19 istnieje kilka dróg, którymi wiersze trafiają do raportu: lista główna, wiersze sekcji i wiersze podsekcji. Dlatego moduł filtruje w kilku miejscach (_get_move_lines_to_report() w dokumencie, _get_detailed_lines() i _get_subsection_lines() w wierszu), w przeciwnym razie ukryty wiersz mógłby pojawić się ponownie inną drogą:
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")]
Kreator: sekcja zamiast luźnych wierszy
Kreator sale.product.set.wizard z sale_product_set tworzy wiersze oferty w _prepare_order_lines(). Moduł nadpisuje tę metodę. Jeśli Wstaw jako sekcję jest wyłączone, po prostu wywołuje oryginalną metodę, a zachowanie pozostaje takie jak w module OCA. W przeciwnym razie najpierw powstaje wiersz sekcji z dwoma standardowymi przełącznikami, a następnie wiersze zestawu. Sekcje wewnątrz zestawu stają się podsekcjami, aby nie rozbijały głównej struktury:
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
Decyzje, których nie widać
- Oddzielne metody obliczeniowe w kreatorze. Nazwa sekcji, tryb wydruku i Wstaw jako sekcję mają każde własną metodę
compute(store=True,readonly=False). Gdyby wszystkie trzy miały tę samą metodę, jawnie przekazana wartość jednego pola resetowałaby pozostałe. Widać to przede wszystkim wtedy, gdy kreator jest tworzony z kodu lub w testach z podanymi wartościami. - Nazwa sekcji z ilością. Przy ilości różnej od 1 jest ona umieszczana w nazwie (2 × Zestaw pomiarowy Standard, z przecinkiem jako separatorem dziesiętnym), aby na wydruku było widać, że są to dwa zestawy, a nie jeden zestaw w podwójnej ilości.
- Cena stała tylko w wierszach produktów.
prepare_sale_order_line_values()ustawiahide_in_reportiprice_unittylko wtedy, gdy wiersz zestawu nie jest wierszem sekcji ani notatki. - Standardowe Odoo zamiast własnego rozwiązania. Tryb wydruku i ukrywanie korzystają z istniejących hooków i pól Odoo 19. Dzięki temu aktualizacje są łatwe do opanowania: jeśli Odoo zmieni szablon raportu, moduł pozostaje ważny, dopóki istnieją te metody.
Instalacja bez niepożądanych skutków ubocznych
Wymagania: Odoo 19.0, sale_product_set (OCA/sale-workflow, gałąź 19.0) i product_set (OCA/product-attribute, gałąź 19.0). Sam moduł zależy od sale_management, sale_product_set i account.
Pułapka auto_install w modułach OCA. Wiele repozytoriów OCA zawiera moduły z auto_install. Gdy tylko ich zależności są dostępne, Odoo instaluje je bez pytania przy następnej instalacji lub aktualizacji. Kto więc umieści całe repozytorium OCA w addons_path instancji produkcyjnej, może otrzymać moduły, których nikt nie chciał. W przypadku OCA/product-attribute dotyczy to na przykład purchase_product_template_tags. Nasze postępowanie:
- Sklonować oba repozytoria OCA płytko (
git clone --depth 1 --branch 19.0). - W osobnym katalogu podlinkować symlinkami tylko dwa potrzebne moduły i dodać ten katalog do
addons_path, a nie całe repozytoria. - Zainstalować na testowej bazie danych i sprawdzić, które moduły otrzymują przy tym status zainstalowany.
- Dopiero potem zainstalować produkcyjnie, z
--workers=0, aby wywołanie nie konkurowało z działającą usługą o port HTTP.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nowe pola i widoki znajdują się po instalacji w bazie danych. Jeśli usługa działa w trybie wieloprocesowym, workery doładowują zmieniony rejestr przez sygnał bazy danych. Zmiany w plikach Python modułu wchodzą w życie dopiero po restarcie usługi.
Obsługa krok po kroku
- Tworzenie zestawu: Sprzedaż → Produkty → Zestawy produktów. Nadać zestawowi nazwę, wybrać Wstaw jako sekcję i Tryb wydruku, wpisać produkty z ilościami. Wiersze korygujące oznaczyć jako Ukryty i nadać im Cenę stałą.
- W ofercie: kliknąć Dodaj zestaw, wybrać zestaw i ilość. Nazwę sekcji i tryb wydruku można zmienić przed wstawieniem.
- Ukrywanie pojedynczych wierszy: w ofercie lub fakturze włączyć kolumnę Nie drukuj.
- Sprawdzenie: otworzyć podgląd lub PDF. Taki sam widok mają klienci w portalu.
Testy
Moduł zawiera sześć zautomatyzowanych testów, które uruchamiamy na testowej bazie danych, zanim cokolwiek trafi do produkcji:
- Wstawianie jako sekcja wraz z trybem wydruku i podsekcją
- Ukryty wiersz nie występuje w wierszach do wydrukowania
- Tryb Jeden wiersz (
summary) - Wstawianie bez sekcji, czyli pierwotne zachowanie
- Faktura przejmuje oznaczenie
hide_in_report, wiersza brakuje w_get_move_lines_to_report(), suma sekcji nadal go zawiera - Faktura w trybie Tylko cena całkowita nie pokazuje ukrytego wiersza w wierszach sekcji, suma sekcji pozostaje pełna
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Niemieckie tłumaczenie znajduje się w i18n/de.po.
Ograniczenia i uwaga prawna
- Tylko układ. Moduł nie zmienia sposobu, w jaki Odoo oblicza ceny, rabaty ani podatki. Zmienia tylko to, co jest drukowane i wyświetlane w portalu.
- Ukryte wiersze są widoczne w backendzie. Oznaczenie dotyczy wydruku i portalu, a nie widoku ekranowego pracowników.
- Sprawdzić dane na fakturze. Ukryte wiersze wliczają się do sumy, ale nie widnieją w dokumencie. Czy jest to zgodne z wymogami dotyczącymi faktur w Państwa kraju, na przykład z wymaganym opisem dostarczonych towarów lub wykonanych usług, należy przed użyciem na fakturach wyjaśnić z doradcą podatkowym. W ofertach ukrywanie jest mniej problematyczne. Wpis nie stanowi porady prawnej.
- Przetestowane dla Odoo 19. Moduł opracowaliśmy i przetestowaliśmy dla Odoo 19.0 Community. Korzysta z opcji sekcji, które w tej formie istnieją w Odoo 19. Dla innych wersji go nie sprawdzaliśmy.
Najczęstsze pytania
Po co mi moduł, skoro Odoo 19 już obsługuje sekcje?
Opcje sekcji istnieją w samym Odoo. Brakowało połączenia z zestawem produktów: zestaw powinien być wstawiany automatycznie jako sekcja, z trybem wydruku zapisanym w zestawie. Bez modułu po każdym wstawieniu trzeba ręcznie tworzyć i konfigurować wiersz sekcji.
Czym różnią się „Ukryj ceny” i „Ukryj skład”?
„Ukryj ceny” (collapse_prices) pozostawia wiersze, ale nie pokazuje kwot, tylko sumę przy sekcji. „Ukryj skład” (collapse_composition) ukrywa wiersze całkowicie, klient widzi tylko sekcję z sumą.
Czy ukryty wiersz wlicza się do sumy faktury?
Tak. Ukryte wiersze pozostają w sumie, podatku i księgowaniu. Oznaczenie dotyczy tylko prezentacji na wydruku i w portalu klienta.
Czy mogę jeszcze zmienić tryb wydruku przy wstawianiu?
Tak. Tryb jest podpowiadany z zestawu, a w kreatorze można go zmienić dla danego wstawienia. Sam zestaw pozostaje przy tym niezmieniony.
Co się stanie, jeśli wyłączę Wstaw jako sekcję?
Wtedy kreator zachowuje się jak w module OCA sale_product_set: wiersze zestawu są wstawiane bez sekcji.
Czy moduł niespodziewanie instaluje dodatkowe moduły?
Sam moduł nie. W repozytoriach OCA mogą jednak występować moduły z auto_install, jeśli do addons_path dodaje się całe repozytoria. Dlatego należy podlinkować tylko dwa potrzebne moduły (zob. sekcję Instalacja).
Czy moduł działa z Odoo Enterprise?
Moduł zależy tylko od modułów Community (sale_management, account) i od dwóch modułów OCA. Używamy go w Odoo 19 Community. Na Enterprise go nie testowaliśmy.
Na jakiej licencji jest udostępniony moduł?
Na licencji AGPL-3. Mogą Państwo z niego korzystać, dostosowywać go i udostępniać dalej, o ile zmiany pozostają na tej samej licencji. Błędy i propozycje ulepszeń są mile widziane jako issue w repozytorium GitHub .
Wsparcie ze strony Biricon IT Services
Opiekujemy się instalacjami Odoo dla małych i średnich firm: od wdrożenia, przez dostosowania i migracje, aż po bieżącą eksploatację. Jeśli chcą Państwo dopasować zestawy produktów, szablony ofert lub raporty w Odoo 19 do swoich procesów albo sprawdzić taki moduł i bezpiecznie wdrożyć go na instancji produkcyjnej, chętnie Państwa wesprzemy. Kolejne własne rozwiązania znajdą Państwo na naszym blogu, na przykład Scalanie projektów faktur i Automatyczne przypisywanie płatności bankowych do faktur.
Stan na 2 listopada 2026 r. Wersja modułu 19.0.1.0.0. Dane przykładowe na ilustracjach zostały dobrane dowolnie, kwoty netto.