Tko u Odoou prodaje pakete, na primjer mjerni uređaj s priborom i montažom, radno mjesto s hardverom i postavljanjem ili paket održavanja od više usluga, u ponudi istodobno treba dvije stvari: Sastav mora biti naveden u narudžbi kako bi skladište, tehnika i fakturiranje znali što se isporučuje. Na ispisu za klijenta pak ne mora u svakom slučaju biti prikazana svaka pojedinačna stavka sa svakom pojedinačnom cijenom.
OCA modul sale_product_set omogućuje umetanje skupova proizvoda (sastavnica za prodaju) u ponudu jednim klikom. O izgledu ispisa ne odlučuje. Zato smo razvili modul Sale Product Set Layout (sale_product_set_layout). Napisan je za Odoo 19 i besplatno je dostupan na GitHubu kao slobodan softver pod licencijom AGPL-3:
github.com/Alex0176/odoo-sale-product-set-layout
Sadržaj ovog članka
Problem: skupovi proizvoda bez utjecaja na izgled ispisa
Skup proizvoda u Odoou unaprijed je pripremljen popis proizvoda s količinama. Umjesto da se deset artikala traži pojedinačno, u ponudi se odabere Dodaj skup i dobiju se sve stavke odjednom. To štedi vrijeme i sprječava zaboravljene stavke. Modul sale_product_set iz repozitorija OCA/sale-workflow radi upravo to; sami skupovi potječu iz modula product_set u repozitoriju OCA/product-attribute.
U praksi ostaju tri nedostatka:
- Nema prepoznatljivog grupiranja. Stavke skupa završavaju kao samostalne stavke u ponudi. Nakon umetanja više se ne vidi koje stavke pripadaju zajedno, a ukupni iznos skupa nigdje ne stoji.
- Nema utjecaja na ispis. Ponekad klijent treba vidjeti svaku stavku s cijenom, ponekad samo cijenu paketa, ponekad samo jednu jedinu stavku. To je odluka po skupu ili po klijentu, a ne nešto što se za svaku ponudu iznova rješava ručno.
- Ispravci cijena su neugodni. Ako paket treba imati fiksnu cijenu koja odstupa od zbroja cjenikovskih cijena, potrebna je stavka za korekciju. Ona pripada izračunu, ali ne nužno i na papir za klijenta.
Odoo 19 donosi gradivne elemente za čisto rješenje: odjeljke i pododjeljke u stavkama ponude s opcijama Sakrij cijene i Sakrij sastav (polja collapse_prices i collapse_composition). Modul povezuje skup proizvoda s tim standardnim opcijama umjesto da izmišlja vlastiti format ispisa.
Što modul radi
- Umetanje skupa kao odjeljka. Čarobnjak Dodaj skup stvara stavku odjeljka s nazivom skupa, na primjer 2 × Messkit Standard, a stavke skupa postavlja ispod nje. Odjeljci unutar skupa postaju pododjeljci.
- Način ispisa po skupu. U skupu se jednom određuje kako će se ispisivati. Pri umetanju se način u čarobnjaku može promijeniti za taj jedan slučaj.
- Skrivanje stavki. Pojedine stavke mogu se označiti kao Ne ispisuj (u skupu: Skriveno). Ulaze u zbroj, ali se ne ispisuju niti prikazuju u klijentskom portalu. Oznaka se s ponude prenosi na račun.
- Fiksna cijena po stavci skupa. Stavka skupa može imati fiksnu jediničnu cijenu koja zamjenjuje cijenu iz cjenika, na primjer za skriveni ispravak cijene.
Bez modula (samo sale_product_set) | S modulom sale_product_set_layout |
|---|---|
| Stavke skupa kao samostalne stavke | Stavka odjeljka s nazivom skupa, količina u nazivu, stavke ispod |
| Odjeljci u skupu ostaju odjeljci i razbijaju strukturu | Odjeljci u skupu postaju pododjeljci (line_subsection) |
| Izgled ispisa uvijek isti | Način ispisa po skupu, promjenjiv pri umetanju |
| Ispravak cijene vidljiv na ponudi i računu | Stavku je moguće sakriti, a ipak ulazi u zbroj |
| Cijena samo iz cjenika | Opcionalno fiksna cijena po stavci skupa |
| Izgled završava na ponudi | Izgled i skrivanje vrijede za ponudu, narudžbu, račun i klijentski portal |
Tri načina ispisa podrobno
Način ispisa je polje za odabir print_mode na skupu proizvoda. Pri umetanju se prevodi u dva standardna prekidača stavke odjeljka. Za to u modulu postoji jedno jednostavno i lako čitljivo pridruživanje:
# način ispisa -> (collapse_prices, collapse_composition) stavke odjeljka
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Način (print_mode) | Prikaz u skupu | Standardna opcija u Odoou | Izgled ispisa |
|---|---|---|---|
detailed | Svaka stavka s cijenom | nema | Odjeljak, sve stavke s iznosom |
total_only | Stavke bez cijena, samo zbroj skupa | collapse_prices (Sakrij cijene) | Odjeljak sa zbrojem, stavke samo s nazivom i količinom |
summary | Samo jedna stavka sa zbrojem skupa | collapse_composition (Sakrij sastav) | Samo stavka odjeljka sa zbrojem |
Budući da modul koristi standardne opcije Odoo 19, a ne vlastiti format ispisa, ponuda, potvrda narudžbe, račun i klijentski portal rade bez prilagođenih predložaka izvješća. Tko ima vlastite predloške izvješća koji uzimaju u obzir opcije odjeljaka, dobit će isto ponašanje.
Skrivene stavke i fiksna cijena
Čest slučaj: paket ima cijenu paketa koja je niža od zbroja pojedinačnih cijena. Umjesto da se pojedinačne cijene izobličuju, uzima se korekcijska stavka s fiksnom cijenom (na primjer −50,00 € po skupu) i sakriva se. U backendu je račun razumljiv, a na ispisu stoji cijena paketa.
Važno za razumijevanje: oznaka hide_in_report ne mijenja ništa u izračunu. Količina, cijena, popust, porez, zbrojevi i knjiženja nastaju nepromijenjeno u standardnom Odoou. Stavka samo nedostaje na ispisu i u portalu.
- Na skupu proizvoda: stavka skupa s opcijom Skriveno i po želji Fiksna cijena zajedno s Jediničnom cijenom.
- Na stavkama ponude i računa: stupac Ne ispisuj (
hide_in_report), također za obične stavke bez skupa. - Prijenos: pri izradi računa iz narudžbe oznaka se prenosi na stavku računa. Pri kopiranju ponude ostaje sačuvana.
Tehnička izvedba
Modul je namjerno malen: proširuje postojeće modele i nadjačava samo ona mjesta na kojima Odoo odlučuje koje se stavke ispisuju i kako se skup umeće. Nema novih predložaka izvješća, novih modela ni SQL zahvata.
Prošireni modeli i polja
| Model | Dopuna |
|---|---|
product.set | Polja insert_as_section (Boolean, prema zadanom uključeno) i print_mode (izbor, obavezno polje, zadano detailed) |
product.set.line | Polja hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() prosljeđuje ih stavci ponude |
sale.product.set.wizard | Polja insert_as_section, section_name, print_mode; _prepare_order_lines() gradi stavku odjeljka |
sale.order / sale.order.line | Polje hide_in_report (s copy=True); _get_order_lines_to_report() preskače skrivene stavke; _prepare_invoice_line() prosljeđuje oznaku |
account.move / account.move.line | Polje hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() i _get_subsection_lines() filtriraju skrivene stavke; _get_aml_vals() prenosi oznaku u podatke izvješća |
Skrivanje u ponudi
Odoo 19 stavke ponude za ispis određuje u metodi _get_order_lines_to_report(). Modul na tom mjestu dodaje filtar. Zahvat u predložak izvješća nije potreban: i predložak izvješća ponude (sale/report/ir_actions_report_templates.xml) i predložak portala (sale/views/sale_portal_templates.xml) pozivaju tu metodu:
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
)
Prijenos na račun
Pri izradi stavke računa iz stavke ponude (_prepare_invoice_line()) oznaka se kopira. Na strani računa Odoo 19 ima više putova kojima stavke dospijevaju u izvješće: glavni popis, stavke odjeljka i stavke pododjeljka. Zato modul filtrira na više mjesta (_get_move_lines_to_report() na dokumentu, _get_detailed_lines() i _get_subsection_lines() na stavci), inače bi se skrivena stavka mogla ponovno pojaviti nekim od drugih putova:
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")]
Čarobnjak: odjeljak umjesto samostalnih stavki
Čarobnjak sale.product.set.wizard iz modula sale_product_set stvara stavke ponude u metodi _prepare_order_lines(). Modul tu metodu nadjačava. Ako je Umetni kao odjeljak isključeno, jednostavno poziva izvornu metodu, a ponašanje ostaje kao u OCA modulu. U suprotnom najprije nastaje stavka odjeljka s dva standardna prekidača, a zatim slijede stavke skupa. Odjeljci unutar skupa postaju pododjeljci kako ne bi razbijali glavnu 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
Odluke koje se ne vide
- Odvojene metode izračuna u čarobnjaku. Naziv odjeljka, način ispisa i Umetni kao odjeljak imaju svaki svoju
computemetodu (store=True,readonly=False). Kad bi sva tri dijelila istu metodu, izričito proslijeđena vrijednost jednog polja poništila bi ostala. To se uočava osobito kad se čarobnjak stvara iz koda ili testova s vrijednostima. - Naziv odjeljka s količinom. Ako je količina različita od 1, navodi se u nazivu (2 × Messkit Standard, sa zarezom kao decimalnim separatorom), kako bi se na ispisu prepoznalo da se radi o dva skupa, a ne o jednom skupu s dvostrukom količinom.
- Fiksna cijena samo na stavkama proizvoda.
prepare_sale_order_line_values()postavljahide_in_reportiprice_unitsamo ako stavka skupa nije stavka odjeljka ni bilješke. - Standardni Odoo umjesto vlastite izrade. Način ispisa i skrivanje koriste postojeće kuke i polja Odoo 19. To čini ažuriranja podnošljivima: ako Odoo promijeni predložak izvješća, modul ostaje valjan dok god te metode postoje.
Instalacija bez neželjenih nuspojava
Preduvjeti: Odoo 19.0, sale_product_set (OCA/sale-workflow, grana 19.0) i product_set (OCA/product-attribute, grana 19.0). Sam modul ovisi o modulima sale_management, sale_product_set i account.
Zamka auto_install kod OCA modula. Mnogi OCA repozitoriji sadrže module s opcijom auto_install. Čim su dostupne njihove ovisnosti, Odoo ih pri sljedećoj instalaciji ili ažuriranju bez pitanja instalira zajedno. Tko zato cijeli OCA repozitorij stavi u addons_path produkcijske instance, možda dobije module koje nitko nije želio. Kod OCA/product-attribute to se na primjer odnosi na purchase_product_template_tags. Naš postupak:
- Oba OCA repozitorija klonirati plitko (
git clone --depth 1 --branch 19.0). - U zasebnom direktoriju simboličkom vezom povezati samo dva potrebna modula i taj direktorij uključiti u
addons_path, a ne cijele repozitorije. - Instalirati na testnoj bazi podataka i provjeriti koji moduli pritom dobiju status instalirano.
- Tek tada instalirati produkcijski, s
--workers=0, kako poziv ne bi s pokrenutom uslugom konkurirao za HTTP port.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nova polja i prikazi nakon instalacije nalaze se u bazi podataka. Ako usluga radi u višeprocesnom načinu, workeri ponovno učitavaju promijenjeni registar putem signala baze podataka. Promjene u Python datotekama modula postaju djelotvorne tek nakon ponovnog pokretanja usluge.
Korištenje korak po korak
- Izrada skupa: Prodaja → Proizvodi → Skupovi proizvoda. Imenovati skup, odabrati Umetni kao odjeljak i Način ispisa, unijeti proizvode s količinama. Korekcijske stavke označiti opcijama Skriveno i Fiksna cijena.
- U ponudi: kliknuti na Dodaj skup, odabrati skup i količinu. Naziv odjeljka i način ispisa mogu se promijeniti prije umetanja.
- Skrivanje pojedinih stavki: u ponudi ili računu aktivirati stupac Ne ispisuj.
- Provjera: otvoriti pregled ili PDF. Isti prikaz klijenti vide u portalu.
Testovi
Modul donosi šest automatiziranih testova koje izvodimo na testnoj bazi podataka prije nego što bilo što ode u produkciju:
- Umetanje kao odjeljak uključujući način ispisa i pododjeljak
- Skrivena stavka nedostaje među stavkama za ispis
- Način Jedna stavka (
summary) - Umetanje bez odjeljka, izvorno ponašanje
- Račun preuzima oznaku
hide_in_report, stavka nedostaje u_get_move_lines_to_report(), zbroj odjeljka i dalje sadrži - Račun u načinu Samo ukupna cijena ne prikazuje skrivenu stavku među stavkama odjeljka, zbroj odjeljka ostaje potpun
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Njemački prijevod nalazi se u i18n/de.po.
Ograničenja i pravna napomena
- Samo izgled. Modul ne mijenja način na koji Odoo izračunava cijene, popuste ili poreze. Mijenja samo ono što se ispisuje i prikazuje u portalu.
- Skrivene stavke vidljive su u backendu. Oznaka se odnosi na ispis i portal, a ne na prikaz na zaslonu zaposlenika.
- Provjerite podatke na računu. Skrivene stavke ulaze u zbroj, ali ne stoje na dokumentu. Je li to u skladu sa zahtjevima za račune u vašoj zemlji, na primjer s propisanim opisom isporučenih predmeta ili pruženih usluga, trebali biste razjasniti sa svojim poreznim savjetnikom prije uporabe na računima. Na ponudama je skrivanje manje problematično. Članak nije pravni savjet.
- Testirano za Odoo 19. Modul smo razvili i testirali za Odoo 19.0 Community. Koristi opcije odjeljaka koje u ovom obliku postoje u Odoou 19. Za druge verzije nismo ga provjeravali.
Česta pitanja
Čemu mi služi modul ako Odoo 19 već ima odjeljke?
Opcije odjeljaka postoje u samom Odoou. Nedostajala je veza sa skupom proizvoda: skup bi se trebao automatski umetnuti kao odjeljak, s načinom ispisa pohranjenim u skupu. Bez modula nakon svakog umetanja stavku odjeljka treba ručno izraditi i konfigurirati.
Po čemu se razlikuju „Sakrij cijene“ i „Sakrij sastav“?
„Sakrij cijene“ (collapse_prices) ostavlja stavke, ali ne prikazuje iznose, samo zbroj na odjeljku. „Sakrij sastav“ (collapse_composition) potpuno skriva stavke, klijent vidi samo odjeljak sa zbrojem.
Ulazi li skrivena stavka u zbroj računa?
Da. Skrivene stavke ostaju u zbroju, porezu i knjiženju. Oznaka se odnosi samo na prikaz na ispisu i u klijentskom portalu.
Mogu li način ispisa još promijeniti pri umetanju?
Da. Način se preuzima kao zadana vrijednost iz skupa, a u čarobnjaku se može promijeniti za pojedino umetanje. Sam skup pritom ostaje nepromijenjen.
Što se događa ako isključim Umetni kao odjeljak?
Tada se čarobnjak ponaša kao u OCA modulu sale_product_set: stavke skupa umeću se bez odjeljka.
Instalira li modul neočekivano dodatne module?
Sam modul ne. No kod OCA repozitorija može doći do modula s auto_install ako se cijeli repozitoriji stave u addons_path. Zato povežite samo dva potrebna modula (vidi odjeljak Instalacija).
Radi li modul s Odoo Enterprise?
Modul ovisi samo o Community modulima (sale_management, account) i o dva OCA modula. Koristimo ga na Odoo 19 Community. Na Enterpriseu ga nismo testirali.
Pod kojom licencijom je modul?
Pod AGPL-3. Smijete ga koristiti, prilagođavati i dalje distribuirati, pod uvjetom da izmjene ostanu pod istom licencijom. Greške i prijedlozi poboljšanja dobrodošli su kao issue u GitHub repozitoriju .
Podrška tvrtke Biricon IT Services
Brinemo se za Odoo instalacije malih i srednjih poduzeća: od uvođenja, preko prilagodbi i migracija, do tekućeg rada. Ako želite prilagoditi skupove proizvoda, predloške ponuda ili izvješća u Odoou 19 svojim procesima ili ako želite provjeriti modul poput ovog i sigurno ga uvesti u svoju produkcijsku instancu, rado ćemo vas podržati. Dodatne vlastite razvoje pronaći ćete u našem blogu, na primjer Spajanje nacrta računa i Automatsko povezivanje bankovnih uplata s računima.
Stanje: 2. studenoga 2026. Verzija modula 19.0.1.0.0. Primjeri podataka na slikama proizvoljno su odabrani, iznosi su neto.