Przejdź do zawartości

Odoo 19 Community: zestawy produktów w ofercie jako sekcja – nasz moduł open source Sale Product Set Layout

2 listopada 2026 | Odoo 19 Community | Własne oprogramowanie
2 listopada 2026 przez
Odoo 19 Community: zestawy produktów w ofercie jako sekcja – nasz moduł open source Sale Product Set Layout

Moduł Odoo 19 Sale Product Set Layout: zestawy produktów jako sekcja w ofercie z trybem wydruku, ukrytymi wierszami i ceną stałą

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

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ł

  1. 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.
  2. 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.
  3. 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ę.
  4. 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 pozycjeWiersz 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 samTryb wydruku dla każdego zestawu, zmienialny przy wstawianiu
Korekta ceny widoczna w ofercie i na fakturzeWiersz można ukryć, a mimo to wlicza się do sumy
Cena wyłącznie z cennikaOpcjonalnie cena stała dla wiersza zestawu
Układ kończy się na ofercieUkł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),
}

Trzy wydruki tego samego zestawu produktów: każdy wiersz z ceną, tylko cena całkowita, jeden jedyny wiersz

Tryb (print_mode)Wyświetlanie w zestawieStandardowa opcja w OdooWygląd wydruku
detailedKażdy wiersz z cenąbrakSekcja, wszystkie wiersze z kwotą
total_onlyWiersze bez cen, tylko suma zestawucollapse_prices (Ukryj ceny)Sekcja z sumą, wiersze tylko z nazwą i ilością
summaryTylko jeden wiersz z sumą zestawucollapse_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.

Korekta ceny widoczna i oznaczona w backendzie, niewidoczna na wydruku oferty, w PDF, na fakturze i w portalu, suma bez zmian

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.

Przepływ danych: zestaw produktów, kreator Dodaj zestaw, wiersze oferty, wiersze faktury, PDF i portal klienta

Rozszerzone modele i pola

ModelRozszerzenie
product.setPola insert_as_section (Boolean, domyślnie włączone) i print_mode (wybór, pole wymagane, domyślnie detailed)
product.set.linePola hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() przekazuje je do wiersza oferty
sale.product.set.wizardPola insert_as_section, section_name, print_mode; _prepare_order_lines() buduje wiersz sekcji
sale.order / sale.order.linePole hide_in_report (z copy=True); _get_order_lines_to_report() pomija ukryte wiersze; _prepare_invoice_line() przekazuje oznaczenie dalej
account.move / account.move.linePole 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() ustawia hide_in_report i price_unit tylko 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:

  1. Sklonować oba repozytoria OCA płytko (git clone --depth 1 --branch 19.0).
  2. W osobnym katalogu podlinkować symlinkami tylko dwa potrzebne moduły i dodać ten katalog do addons_path, a nie całe repozytoria.
  3. Zainstalować na testowej bazie danych i sprawdzić, które moduły otrzymują przy tym status zainstalowany.
  4. 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

  1. 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łą.
  2. W ofercie: kliknąć Dodaj zestaw, wybrać zestaw i ilość. Nazwę sekcji i tryb wydruku można zmienić przed wstawieniem.
  3. Ukrywanie pojedynczych wierszy: w ofercie lub fakturze włączyć kolumnę Nie drukuj.
  4. 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.

Skontaktuj się z nami

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.