Kun Odoossa myydään paketteja, esimerkiksi mittalaite lisävarusteineen ja asennuksineen, työpiste laitteistoineen ja käyttöönottoineen tai huoltopaketti, joka koostuu useista palveluista, tarjoukselta tarvitaan kahta asiaa yhtä aikaa: Koostumuksen on näyttävä tilauksessa, jotta varasto, tekniikka ja laskutus tietävät, mitä toimitetaan. Asiakkaalle menevässä tulosteessa ei kuitenkaan aina haluta näyttää jokaista yksittäistä riviä jokaisen yksittäisen hinnan kera.
OCA-moduuli sale_product_set mahdollistaa tuoteseteiden (myynnin osaluettelot) lisäämisen tarjoukseen yhdellä napsautuksella. Tulostusasuun se ei vaikuta. Sitä varten kehitimme moduulin Sale Product Set Layout (sale_product_set_layout). Se on kirjoitettu Odoo 19:lle ja on vapaasti ja ilmaiseksi saatavilla GitHubissa vapaana ohjelmistona AGPL-3-lisenssillä:
github.com/Alex0176/odoo-sale-product-set-layout
Tämän artikkelin sisältö
- Ongelma: tuotesetit ilman vaikutusta tulostusasuun
- Mitä moduuli tekee
- Kolme tulostustilaa tarkemmin
- Piilotetut rivit ja kiinteä hinta
- Tekninen toteutus
- Asennus ilman ei-toivottuja sivuvaikutuksia
- Käyttö vaihe vaiheelta
- Testit
- Rajoitukset ja oikeudellinen huomautus
- Usein kysytyt kysymykset
- Biricon IT Servicesin tuki
Ongelma: tuotesetit ilman vaikutusta tulostusasuun
Tuoteset on Odoossa valmisteltu luettelo tuotteista ja määristä. Kymmenen tuotteen etsimisen sijaan tarjouksessa valitaan Lisää setti, ja kaikki rivit tulevat kerralla. Se säästää aikaa ja estää unohtuneet rivit. Tämän tekee moduuli sale_product_set arkistosta OCA/sale-workflow ; setit itse tulevat moduulista product_set arkistossa OCA/product-attribute.
Käytännössä jäljelle jää kolme puutetta:
- Ei tunnistettavaa ryhmittelyä. Setin rivit päätyvät tarjoukseen irrallisina riveinä. Lisäyksen jälkeen ei enää näe, mitkä rivit kuuluvat yhteen, eikä setin loppusummaa näy missään.
- Ei vaikutusta tulosteeseen. Joskus asiakkaan pitäisi nähdä jokainen rivi hintoineen, joskus vain pakettihinta, joskus vain yksi ainoa rivi. Tämä on päätös setti- tai asiakaskohtaisesti, ei jokaisessa tarjouksessa uudelleen käsin.
- Hinnankorjaukset ovat kömpelöitä. Jos paketilla on kiinteä hinta, joka poikkeaa luettelohintojen summasta, tarvitaan korjausrivi. Se kuuluu laskentaan, mutta ei välttämättä asiakkaan paperille.
Odoo 19 tarjoaa rakennuspalikat tämän siistiin ratkaisuun: tarjousriveillä osiot ja alaosiot sekä vaihtoehdot Piilota hinnat ja Piilota koostumus (kentät collapse_prices ja collapse_composition). Moduuli yhdistää tuotesetin näihin vakiovaihtoehtoihin sen sijaan, että keksisi oman tulostusmuodon.
Mitä moduuli tekee
- Setin lisääminen osiona. Ohjattu toiminto Lisää setti luo osiorivin setin nimellä, esimerkiksi 2 × Mittaussarja Standard, ja asettaa setin rivit sen alle. Setin sisällä olevat osiot muuttuvat alaosioiksi.
- Tulostustila setkohtaisesti. Setissä määritetään kerran, miten se tulostetaan. Lisäyksen yhteydessä tilaa voi muuttaa ohjatussa toiminnossa kyseistä tapausta varten.
- Rivien piilotus. Yksittäiset rivit voi merkitä arvolla Älä tulosta (setissä: Piilotettu). Ne lasketaan mukaan summaan, mutta niitä ei tulosteta eikä näytetä asiakasportaalissa. Merkintä siirtyy tarjouksesta laskulle.
- Kiinteä hinta setin riville. Setin rivillä voi olla kiinteä yksikköhinta, joka korvaa hintaluettelon hinnan, esimerkiksi piilotettua hinnankorjausta varten.
Ilman moduulia (vain sale_product_set) | Moduulilla sale_product_set_layout |
|---|---|
| Setin rivit irrallisina riveinä | Osiorivi setin nimellä, määrä nimessä, rivit sen alla |
| Setin osiot pysyvät osioina ja rikkovat rakenteen | Setin osiot muuttuvat alaosioiksi (line_subsection) |
| Tulostusasu aina sama | Tulostustila setkohtaisesti, muutettavissa lisättäessä |
| Hinnankorjaus näkyvissä tarjouksessa ja laskulla | Rivi piilotettavissa, mutta lasketaan mukaan summaan |
| Hinta vain hintaluettelosta | Valinnainen kiinteä hinta setin riville |
| Asettelu päättyy tarjoukseen | Asettelu ja piilotus koskevat tarjousta, tilausta, laskua ja asiakasportaalia |
Kolme tulostustilaa tarkemmin
Tulostustila on tuotesetin valintakenttä print_mode. Lisättäessä se muunnetaan osioriviltä kahdeksi vakiokytkimeksi. Moduulissa on tätä varten yksi helposti luettava vastaavuustaulukko:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Tila (print_mode) | Näkymä setissä | Odoon vakiovaihtoehto | Tulostusasu |
|---|---|---|---|
detailed | Jokainen rivi hintoineen | ei mitään | Osio, kaikki rivit summineen |
total_only | Rivit ilman hintoja, vain setin summa | collapse_prices (Piilota hinnat) | Osio summineen, rivit vain nimikkeellä ja määrällä |
summary | Vain yksi rivi setin summalla | collapse_composition (Piilota koostumus) | Vain osiorivi summineen |
Koska moduuli käyttää Odoo 19:n vakiovaihtoehtoja eikä omaa tulostusmuotoa, tarjous, tilausvahvistus, lasku ja asiakasportaali toimivat ilman mukautettuja raporttipohjia. Myös ne, joilla on omia raporttipohjia, jotka huomioivat osioiden vaihtoehdot, saavat saman toiminnan.
Piilotetut rivit ja kiinteä hinta
Yleinen tapaus: paketin pakettihinta on pienempi kuin yksittäishintojen summa. Sen sijaan että yksittäishintoja väänneltäisiin, käytetään korjausriviä kiinteällä hinnalla (esimerkiksi −50,00 € setiltä) ja piilotetaan se. Taustajärjestelmässä lasku on jäljitettävissä, ja tulosteessa näkyy pakettihinta.
Ymmärtämisen kannalta tärkeää: merkintä hide_in_report ei muuta laskentaa mitenkään. Määrä, hinta, alennus, vero, summat ja kirjaukset syntyvät muuttumattomina Odoon vakiotoiminnoilla. Rivi puuttuu vain tulosteesta ja portaalista.
- Tuotesetissä: setin rivi, jossa Piilotettu ja valinnaisesti Kiinteä hinta yhdessä yksikköhinnan kanssa.
- Tarjous- ja laskuriveillä: sarake Älä tulosta (
hide_in_report), myös tavallisille riveille ilman settiä. - Siirtyminen eteenpäin: kun lasku luodaan tilauksesta, merkintä siirretään laskun riville. Tarjousta kopioitaessa se säilyy.
Tekninen toteutus
Moduuli on tarkoituksella pieni: se laajentaa olemassa olevia malleja ja ylikirjoittaa vain ne kohdat, joissa Odoo päättää, mitkä rivit tulostetaan ja miten setti lisätään. Uusia raporttipohjia, uusia malleja tai SQL-toimenpiteitä ei ole.
Laajennetut mallit ja kentät
| Malli | Lisäys |
|---|---|
product.set | Kentät insert_as_section (Boolean, oletuksena päällä) ja print_mode (valinta, pakollinen kenttä, oletus detailed) |
product.set.line | Kentät hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() välittää ne tarjousriville |
sale.product.set.wizard | Kentät insert_as_section, section_name, print_mode; _prepare_order_lines() rakentaa osioriviä |
sale.order / sale.order.line | Kenttä hide_in_report (arvolla copy=True); _get_order_lines_to_report() ohittaa piilotetut rivit; _prepare_invoice_line() välittää merkinnän eteenpäin |
account.move / account.move.line | Kenttä hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() ja _get_subsection_lines() suodattavat piilotetut rivit; _get_aml_vals() vie merkinnän raporttitietoihin |
Piilotus tarjouksessa
Odoo 19 määrittää tulostettavat tarjousrivit metodissa _get_order_lines_to_report(). Moduuli liittää siihen suodattimen. Raporttipohjaan ei tarvitse puuttua: sekä tarjouksen raporttipohja (sale/report/ir_actions_report_templates.xml) että portaalipohja (sale/views/sale_portal_templates.xml) kutsuvat tätä metodia:
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
)
Välittäminen laskulle
Kun laskurivi luodaan tarjousrivistä (_prepare_invoice_line()), merkintä kopioidaan. Laskun puolella Odoo 19:ssä on useita reittejä, joita pitkin rivit päätyvät raporttiin: pääluettelo, osion rivit ja alaosion rivit. Siksi moduuli suodattaa useassa kohdassa (_get_move_lines_to_report() asiakirjassa, _get_detailed_lines() ja _get_subsection_lines() rivillä), muuten piilotettu rivi voisi ilmestyä uudelleen jotakin muuta reittiä pitkin:
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")]
Ohjattu toiminto: osio irrallisten rivien sijaan
Ohjattu toiminto sale.product.set.wizard moduulista sale_product_set luo tarjousrivit metodissa _prepare_order_lines(). Moduuli ylikirjoittaa tämän metodin. Jos Lisää osiona on pois päältä, se kutsuu yksinkertaisesti alkuperäistä metodia, ja toiminta pysyy samana kuin OCA-moduulissa. Muuten syntyy ensin osiorivi kahdella vakiokytkimellä, ja sen jälkeen setin rivit. Setin sisällä olevat osiot muuttuvat alaosioiksi, jotta ne eivät riko päärakennetta:
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
Päätökset, joita ei näe
- Erilliset laskentametodit ohjatussa toiminnossa. Osion nimellä, tulostustilalla ja kohdalla Lisää osiona on kullakin oma
compute-metodinsa (store=True,readonly=False). Jos kaikilla kolmella olisi sama metodi, yhdelle kentälle erikseen annettu arvo nollaisi muut. Tämä huomataan lähinnä silloin, kun ohjattu toiminto luodaan koodista tai testeistä arvojen kanssa. - Osion nimi määrän kanssa. Jos määrä on muu kuin 1, se on mukana nimessä (2 × Mittaussarja Standard, desimaalierottimena pilkku), jotta tulosteesta näkee, että kyse on kahdesta setistä eikä yhdestä setistä kaksinkertaisella määrällä.
- Kiinteä hinta vain tuoteriveillä.
prepare_sale_order_line_values()asettaahide_in_reportjaprice_unitvain, jos setin rivi ei ole osio- tai muistiinpanorivi. - Odoon vakiotoiminnot omatekoisen sijaan. Tulostustila ja piilotus käyttävät Odoo 19:n olemassa olevia koukkuja ja kenttiä. Se pitää päivitykset hallittavina: jos Odoo muuttaa raporttipohjaa, moduuli pysyy käyttökelpoisena, kunhan metodit säilyvät.
Asennus ilman ei-toivottuja sivuvaikutuksia
Edellytykset: Odoo 19.0, sale_product_set (OCA/sale-workflow, haara 19.0) ja product_set (OCA/product-attribute, haara 19.0). Moduuli itse riippuu moduuleista sale_management, sale_product_set ja account.
OCA-moduulien auto_install-ansa. Monet OCA-arkistot sisältävät moduuleja, joilla on auto_install. Heti kun niiden riippuvuudet ovat olemassa, Odoo asentaa ne kysymättä seuraavan asennuksen tai päivityksen yhteydessä. Jos siis koko OCA-arkiston laittaa tuotantoinstanssin addons_path-polkuun, saattaa saada moduuleja, joita kukaan ei halunnut. Arkistossa OCA/product-attribute tämä koskee esimerkiksi moduulia purchase_product_template_tags. Toimintatapamme:
- Kloonataan molemmat OCA-arkistot matalina (
git clone --depth 1 --branch 19.0). - Linkitetään omaan hakemistoon symbolisella linkillä vain kaksi tarvittavaa moduulia ja lisätään tämä hakemisto polkuun
addons_path, ei kokonaisia arkistoja. - Asennetaan testitietokantaan ja tarkistetaan, mitkä moduulit saavat samalla tilan asennettu.
- Vasta sitten asennetaan tuotantoon valinnalla
--workers=0, jotta komento ei kilpaile käynnissä olevan palvelun kanssa HTTP-portista.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Uudet kentät ja näkymät ovat tietokannassa asennuksen jälkeen. Jos palvelu toimii monen prosessin tilassa, workerit lataavat muuttuneen rekisterin tietokannan signaalin kautta. Moduulin Python-tiedostoihin tehdyt muutokset tulevat voimaan vasta palvelun uudelleenkäynnistyksen jälkeen.
Käyttö vaihe vaiheelta
- Setin luominen: Myynti → Tuotteet → Tuotesetit. Nimeä setti, valitse Lisää osiona ja Tulostustila, syötä tuotteet määrineen. Merkitse korjausrivit valinnoilla Piilotettu ja Kiinteä hinta.
- Tarjouksessa: napsauta Lisää setti, valitse setti ja määrä. Osion nimeä ja tulostustilaa voi muuttaa ennen lisäämistä.
- Yksittäisten rivien piilotus: ota tarjouksessa tai laskussa käyttöön sarake Älä tulosta.
- Tarkistus: avaa esikatselu tai PDF. Saman näkymän asiakkaat näkevät portaalissa.
Testit
Moduulin mukana tulee kuusi automaattista testiä, jotka ajamme testitietokantaa vasten ennen kuin mitään viedään tuotantoon:
- Lisäys osiona tulostustilan ja alaosion kanssa
- Piilotettu rivi puuttuu tulostettavista riveistä
- Tila Yksi rivi (
summary) - Lisäys ilman osiota, alkuperäinen toiminta
- Lasku perii merkinnän
hide_in_report, rivi puuttuu metodista_get_move_lines_to_report(), osion summa sisältää sen edelleen - Lasku tilassa Vain kokonaishinta ei näytä piilotettua riviä osion riveillä, osion summa pysyy täydellisenä
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Saksankielinen käännös on tiedostossa i18n/de.po.
Rajoitukset ja oikeudellinen huomautus
- Vain asettelu. Moduuli ei muuta sitä, miten Odoo laskee hinnat, alennukset tai verot. Se muuttaa vain sitä, mitä tulostetaan ja näytetään portaalissa.
- Piilotetut rivit näkyvät taustajärjestelmässä. Merkintä koskee tulostetta ja portaalia, ei työntekijöiden näyttönäkymää.
- Tarkista laskun pakolliset tiedot. Piilotetut rivit lasketaan mukaan summaan, mutta ne eivät ole asiakirjalla. Sen, onko tämä sopusoinnussa maanne laskuvaatimusten kanssa, esimerkiksi toimitettujen tavaroiden tai suoritettujen palvelujen vaaditun kuvauksen osalta, tulisi selvittää ennen laskuilla käyttöä veroneuvojanne kanssa. Tarjouksissa piilottaminen on ongelmattomampaa. Artikkeli ei ole oikeudellista neuvontaa.
- Testattu Odoo 19:lle. Olemme kehittäneet ja testanneet moduulin Odoo 19.0 Communitylle. Se käyttää osioiden vaihtoehtoja, jotka ovat tässä muodossa olemassa Odoo 19:ssä. Muille versioille emme ole sitä tarkistaneet.
Usein kysytyt kysymykset
Miksi tarvitsen moduulin, jos Odoo 19 osaa jo osiot?
Osioiden vaihtoehdot ovat Odoossa itsessään. Puuttui yhteys tuotesettiin: setti pitäisi lisätä automaattisesti osiona, setissä määritellyllä tulostustilalla. Ilman moduulia osiorivi on luotava ja määritettävä käsin jokaisen lisäyksen jälkeen.
Miten ”Piilota hinnat” ja ”Piilota koostumus” eroavat toisistaan?
”Piilota hinnat” (collapse_prices) jättää rivit näkyviin, mutta ei näytä summia, vain osion kokonaissumman. ”Piilota koostumus” (collapse_composition) piilottaa rivit kokonaan, ja asiakas näkee vain osion summineen.
Lasketaanko piilotettu rivi mukaan laskun summaan?
Kyllä. Piilotetut rivit pysyvät summassa, verossa ja kirjauksessa. Merkintä koskee vain esitystä tulosteella ja asiakasportaalissa.
Voinko vielä muuttaa tulostustilaa lisättäessä?
Kyllä. Tila tulee oletuksena setistä, ja ohjatussa toiminnossa sitä voi muuttaa kyseistä lisäystä varten. Itse setti pysyy tällöin muuttumattomana.
Mitä tapahtuu, jos poistan valinnan Lisää osiona?
Silloin ohjattu toiminto käyttäytyy kuten OCA-moduulissa sale_product_set: setin rivit lisätään ilman osiota.
Asentaako moduuli odottamatta muita moduuleja?
Moduuli itse ei. OCA-arkistojen kanssa auto_install-moduuleja voi kuitenkin tulla, jos kokonaisia arkistoja lisätään polkuun addons_path. Linkittäkää siksi vain kaksi tarvittavaa moduulia (ks. osio Asennus).
Toimiiko moduuli Odoo Enterprisen kanssa?
Moduuli riippuu vain Community-moduuleista (sale_management, account) ja kahdesta OCA-moduulista. Käytämme sitä Odoo 19 Communityssä. Enterprisessä emme ole testanneet sitä.
Millä lisenssillä moduuli julkaistaan?
AGPL-3:lla. Saatte käyttää, mukauttaa ja jakaa sitä, kunhan muutokset pysyvät saman lisenssin alla. Virheet ja parannusehdotukset ovat tervetulleita issueina GitHub-arkistossa .
Biricon IT Servicesin tuki
Huollamme Odoo-asennuksia pienille ja keskisuurille yrityksille: käyttöönotosta mukautuksiin ja migraatioihin sekä jatkuvaan käyttöön. Jos haluatte mukauttaa tuotesettejä, tarjouspohjia tai raportteja Odoo 19:ssä toimintatapoihinne tai jos haluatte tarkistaa tämän kaltaisen moduulin ja viedä sen turvallisesti tuotantoinstanssiinne, autamme mielellämme. Lisää omia kehityshankkeitamme löydätte blogistamme, esimerkiksi Luonnoslaskujen yhdistäminen ja Pankkimaksujen kohdistaminen automaattisesti laskuille.
Tilanne: 2. marraskuuta 2026. Moduulin versio 19.0.1.0.0. Kuvien esimerkkitiedot on valittu vapaasti, summat ovat nettosummia.