Ko 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, treba u ponudi istovremeno dvije stvari: Sastav mora stajati u narudžbi, kako bi skladište, tehnika i fakturisanje znali šta se isporučuje. Na štampanom primjerku za kupca pak ne mora u svakom slučaju biti prikazana svaka pojedinačna stavka sa svakom pojedinačnom cijenom.
OCA modul sale_product_set omogućava da se setovi proizvoda (sastavnice za prodaju) jednim klikom umetnu u ponudu. O izgledu štampe on 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 licencom AGPL-3:
github.com/Alex0176/odoo-sale-product-set-layout
Sadržaj ovog članka
Problem: setovi proizvoda bez utjecaja na izgled štampe
Set proizvoda je u Odoou unaprijed pripremljena lista proizvoda s količinama. Umjesto da se deset artikala traži pojedinačno, u ponudi se odabere Dodaj set i odmah se dobiju sve stavke. To štedi vrijeme i sprječava zaboravljene stavke. Modul sale_product_set iz repozitorija OCA/sale-workflow radi upravo to; sami setovi potiču iz product_set u repozitoriju OCA/product-attribute.
U praksi ostaju tri praznine:
- Nema prepoznatljivog grupisanja. Stavke seta završavaju kao slobodne pozicije u ponudi. Nakon umetanja više se ne vidi koje stavke pripadaju zajedno, a ukupan iznos seta nigdje ne stoji.
- Nema utjecaja na štampu. Ponekad kupac treba vidjeti svaku poziciju s cijenom, ponekad samo cijenu paketa, a ponekad samo jednu jedinu stavku. To je odluka po setu ili po kupcu, a ne nešto što se za svaku ponudu iznova rješava ručno.
- Korekcije cijene su neugodne. Ako paket treba imati fiksnu cijenu koja odstupa od zbira cijena iz cjenovnika, potrebna je korektivna stavka. Ona spada u obračun, ali ne nužno i na papir za kupca.
Odoo 19 donosi gradivne elemente da se to čisto riješi: odjeljci i pododjeljci u stavkama ponude s opcijama Sakrij cijene i Sakrij sastav (polja collapse_prices i collapse_composition). Modul povezuje set proizvoda s tim standardnim opcijama umjesto da izmišlja vlastiti format štampe.
Šta modul radi
- Umetanje seta kao odjeljka. Čarobnjak Dodaj set kreira stavku odjeljka s nazivom seta, na primjer 2 × Messkit Standard, i postavlja stavke seta ispod nje. Odjeljci unutar seta postaju pododjeljci.
- Način štampe po setu. U setu se jednom određuje kako će se štampati. Pri umetanju se način može u čarobnjaku promijeniti za taj jedan slučaj.
- Skrivanje stavki. Pojedine stavke mogu se označiti kao Ne štampaj (u setu: Skriveno). One se računaju u zbir, ali se ne štampaju niti prikazuju u portalu za kupce. Oznaka se prenosi s ponude na račun.
- Fiksna cijena po stavci seta. Stavka seta može nositi fiksnu jediničnu cijenu koja zamjenjuje cijenu iz cjenovnika, na primjer za skrivenu korekciju cijene.
Bez modula (samo sale_product_set) | Sa sale_product_set_layout |
|---|---|
| Stavke seta kao slobodne pozicije | Stavka odjeljka s nazivom seta, količinom u nazivu i stavkama ispod |
| Odjeljci u setu ostaju odjeljci i razbijaju strukturu | Odjeljci u setu postaju pododjeljci (line_subsection) |
| Izgled štampe uvijek isti | Način štampe po setu, može se promijeniti pri umetanju |
| Korekcija cijene vidljiva na ponudi i računu | Stavka se može sakriti, ali se računa u zbir |
| Cijena samo iz cjenovnika | Opcionalno fiksna cijena po stavci seta |
| Izgled završava na ponudi | Izgled i skrivanje vrijede za ponudu, narudžbu, račun i portal za kupce |
Tri načina štampe detaljno
Način štampe je polje za odabir print_mode na setu proizvoda. Pri umetanju se prevodi u dva standardna prekidača stavke odjeljka. Za to u modulu postoji jedno jedino, lako čitljivo preslikavanje:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Način (print_mode) | Prikaz u setu | Standardna opcija u Odoou | Izgled štampe |
|---|---|---|---|
detailed | Svaka stavka s cijenom | nijedna | Odjeljak, sve stavke s iznosom |
total_only | Stavke bez cijena, samo zbir seta | collapse_prices (Sakrij cijene) | Odjeljak sa zbirom, stavke samo s nazivom i količinom |
summary | Samo jedna stavka sa zbirom seta | collapse_composition (Sakrij sastav) | Samo stavka odjeljka sa zbirom |
Budući da modul koristi standardne opcije Odoo 19, a ne vlastiti format štampe, ponuda, potvrda narudžbe, račun i portal za kupce rade bez prilagođenih predložaka izvještaja. Ko ima vlastite predloške izvještaja koji uvažavaju opcije odjeljaka, dobiva isto ponašanje.
Skrivene stavke i fiksna cijena
Čest slučaj: paket ima cijenu paketa koja je ispod zbira pojedinačnih cijena. Umjesto da se pojedinačne cijene krive, uzima se korektivna stavka s fiksnom cijenom (na primjer −50,00 € po setu) i skriva se. U backendu je račun razumljiv, a na štampanom primjerku stoji cijena paketa.
Važno za razumijevanje: oznaka hide_in_report ne mijenja ništa u obračunu. Količina, cijena, popust, porez, zbirovi i knjiženja nastaju nepromijenjeno putem standardnog Odoa. Stavka samo nedostaje u štampi i u portalu.
- Na setu proizvoda: stavka seta s Skriveno i opcionalno Fiksna cijena zajedno s Jediničnom cijenom.
- Na stavkama ponude i računa: kolona Ne štampaj (
hide_in_report), i za obične stavke bez seta. - Prenošenje: pri kreiranju računa iz narudžbe oznaka se prenosi na stavku računa. Pri kopiranju ponude ona se zadržava.
Tehnička realizacija
Modul je namjerno mali: proširuje postojeće modele i nadjačava samo ona mjesta na kojima Odoo odlučuje koje se stavke štampaju i kako se set umeće. Nema novih predložaka izvještaja, novih modela ni SQL zahvata.
Prošireni modeli i polja
| Model | Dopuna |
|---|---|
product.set | Polja insert_as_section (Boolean, standardno uključeno) i print_mode (izbor, obavezno polje, standardno detailed) |
product.set.line | Polja hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() ih prosljeđuje 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 (sa 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() upisuje oznaku u podatke izvještaja |
Skrivanje u ponudi
Odoo 19 određuje stavke ponude za štampu u _get_order_lines_to_report(). Modul tamo dodaje filter. Zahvat u predložak izvještaja nije potreban: i predložak izvještaja ponude (sale/report/ir_actions_report_templates.xml) i predložak portala (sale/views/sale_portal_templates.xml) pozivaju ovu 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
)
Prenošenje na račun
Pri kreiranju stavke računa iz stavke ponude (_prepare_invoice_line()) oznaka se kopira. Na strani računa u Odoo 19 postoji više putova kojima stavke dospijevaju u izvještaj: glavna lista, 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 ponovo pojaviti nekim od drugih puteva:
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 slobodnih stavki
Čarobnjak sale.product.set.wizard iz sale_product_set kreira stavke ponude u _prepare_order_lines(). Modul nadjačava ovu metodu. Ako je Umetni kao odjeljak isključeno, jednostavno poziva izvornu metodu i ponašanje ostaje kao u OCA modulu. U suprotnom se prvo kreira stavka odjeljka s dva standardna prekidača, a zatim slijede stavke seta. Odjeljci unutar seta 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 štampe i Umetni kao odjeljak imaju svaki svoju
computemetodu (store=True,readonly=False). Kad bi sva tri dijelila istu metodu, eksplicitno proslijeđena vrijednost jednog polja poništila bi ostala. To se najviše primijeti kad se čarobnjak kreira iz koda ili testova s unaprijed zadanim vrijednostima. - Naziv odjeljka s količinom. Pri količini različitoj od 1 ona stoji u nazivu (2 × Messkit Standard, sa zarezom kao decimalnim separatorom), kako bi se u štampi prepoznalo da se radi o dva seta, a ne o jednom setu s dvostrukom količinom.
- Fiksna cijena samo na stavkama proizvoda.
prepare_sale_order_line_values()postavljahide_in_reportiprice_unitsamo ako stavka seta nije stavka odjeljka ili bilješke. - Standardni Odoo umjesto vlastite izrade. Način štampe i skrivanje koriste postojeće hookove i polja Odoo 19. To čini ažuriranja podnošljivim: ako Odoo promijeni predložak izvještaja, modul ostaje važeći sve dok 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 zavisi od sale_management, sale_product_set i account.
Zamka auto_install kod OCA modula. Mnogi OCA repozitoriji sadrže module s auto_install. Čim su njihove zavisnosti prisutne, Odoo ih pri sljedećoj instalaciji ili ažuriranju neupitano instalira zajedno s ostalim. Ko zato cijeli OCA repozitorij stavi u addons_path produktivne instance, možda dobije module koje niko nije htio. 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 povezati simboličkim linkom samo dva potrebna modula i taj direktorij uključiti u
addons_path, a ne cijele repozitorije. - Instalirati na testnu bazu podataka i provjeriti koji moduli pritom dobiju status instaliran.
- Tek tada instalirati produktivno, s
--workers=0, kako poziv ne bi sa 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 nalaze se u bazi podataka nakon instalacije. Ako usluga radi u višeprocesnom režimu, workeri ponovo učitavaju promijenjeni registar putem signala baze podataka. Izmjene Python fajlova modula postaju djelotvorne tek nakon ponovnog pokretanja usluge.
Rukovanje korak po korak
- Kreiranje seta: Prodaja → Proizvodi → Setovi proizvoda. Dati ime setu, odabrati Umetni kao odjeljak i Način štampe, unijeti proizvode s količinama. Korektivne stavke označiti sa Skriveno i Fiksna cijena.
- U ponudi: kliknuti na Dodaj set, odabrati set i količinu. Naziv odjeljka i način štampe mogu se promijeniti prije umetanja.
- Skrivanje pojedinih stavki: u ponudi ili računu aktivirati kolonu Ne štampaj.
- Provjera: otvoriti pregled ili PDF. Isti prikaz kupci vide u portalu.
Testovi
Modul sadrži šest automatizovanih testova koje izvršavamo na testnoj bazi podataka prije nego što bilo šta ide u produkciju:
- Umetanje kao odjeljak uključujući način štampe i pododjeljak
- Skrivena stavka nedostaje među stavkama za štampu
- 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(), zbir odjeljka ih i dalje uključuje - Račun u načinu Samo ukupna cijena ne prikazuje skrivenu stavku među stavkama odjeljka, zbir 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 prevod nalazi se u i18n/de.po.
Ograničenja i pravna napomena
- Samo izgled. Modul ne mijenja način na koji Odoo računa cijene, popuste ili poreze. Mijenja samo ono što se štampa i prikazuje u portalu.
- Skrivene stavke su vidljive u backendu. Oznaka se odnosi na štampu i portal, a ne na prikaz na ekranu za zaposlene.
- Provjerite podatke na računu. Skrivene stavke ulaze u zbir, ali ne stoje na dokumentu. Je li to usklađeno sa zahtjevima za račune u vašoj zemlji, na primjer s propisanim opisom isporučenih predmeta ili izvršenih usluga, trebali biste prije upotrebe na računima razjasniti sa svojim poreznim savjetnikom. 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 Odoo 19. Za druge verzije ga nismo provjeravali.
Česta pitanja
Čemu mi služi modul ako Odoo 19 već poznaje odjeljke?
Opcije odjeljaka postoje u samom Odoou. Nedostajala je veza sa setom proizvoda: set bi se trebao automatski umetati kao odjeljak, s načinom štampe pohranjenim u setu. Bez modula se nakon svakog umetanja stavka odjeljka mora ručno kreirati i konfigurisati.
Po čemu se razlikuju „Sakrij cijene“ i „Sakrij sastav“?
„Sakrij cijene“ (collapse_prices) ostavlja stavke, ali ne prikazuje iznose, samo zbir na odjeljku. „Sakrij sastav“ (collapse_composition) potpuno skriva stavke, kupac vidi samo odjeljak sa zbirom.
Računa li se skrivena stavka u zbir računa?
Da. Skrivene stavke ostaju u zbiru, porezu i knjiženju. Oznaka se odnosi samo na prikaz na štampanom primjerku i u portalu za kupce.
Mogu li način štampe još promijeniti pri umetanju?
Da. Način dolazi kao zadana vrijednost iz seta, a u čarobnjaku se može promijeniti za dotično umetanje. Sam set pritom ostaje nepromijenjen.
Šta se događa ako isključim Umetni kao odjeljak?
Tada se čarobnjak ponaša kao u OCA modulu sale_product_set: stavke seta se umeću bez odjeljka.
Instalira li modul neočekivano dodatne module?
Sam modul ne. No kod OCA repozitorija može doći do auto_install modula 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 zavisi samo od Community modula (sale_management, account) i od dva OCA modula. Koristimo ga na Odoo 19 Community. Na Enterprise ga nismo testirali.
Pod kojom licencom je modul?
Pod AGPL-3. Smijete ga koristiti, prilagođavati i dalje distribuirati, sve dok izmjene ostaju pod istom licencom. Greške i prijedlozi poboljšanja dobrodošli su kao issue u GitHub repozitoriju .
Podrška Biricon IT Services
Brinemo o Odoo instalacijama za mala i srednja preduzeća: od uvođenja, preko prilagodbi i migracija, do tekućeg rada. Ako želite da setove proizvoda, predloške ponuda ili izvještaje u Odoo 19 prilagodite svojim procesima, ili ako želite provjeriti modul poput ovog i sigurno ga postaviti u svoju produktivnu instancu, rado ćemo vas podržati. Daljnje vlastite razvojne projekte pronaći ćete u našem blogu, na primjer Spajanje nacrta računa i Automatsko dodjeljivanje bankovnih uplata računima.
Stanje na dan: 2. novembra 2026. Verzija modula 19.0.1.0.0. Primjeri podataka na slikama su proizvoljno odabrani, iznosi su neto.