Kas Odoo parduoda paketus, pavyzdžiui, matavimo prietaisą su priedais ir montavimu, darbo vietą su įranga ir paruošimu ar techninės priežiūros paketą iš kelių paslaugų, pasiūlyme vienu metu reikia dviejų dalykų: sudėtis turi būti nurodyta užsakyme, kad sandėlis, technikai ir sąskaitų rengimas žinotų, kas bus pristatoma. Tačiau kliento spausdinamame dokumente ne visada turi būti rodoma kiekviena pozicija su kiekviena kaina.
OCA modulis sale_product_set leidžia vienu paspaudimu įterpti į pasiūlymą produktų rinkinius (pardavimo komplektacijas). Dėl spausdinimo vaizdo jis nenusprendžia. Todėl sukūrėme modulį Sale Product Set Layout (sale_product_set_layout). Jis parašytas Odoo 19 ir kaip laisvoji programinė įranga pagal AGPL-3 licenciją nemokamai prieinamas GitHub:
github.com/Alex0176/odoo-sale-product-set-layout
Straipsnio turinys
- Problema: produktų rinkiniai be įtakos spausdinimo vaizdui
- Ką modulis daro
- Trys spausdinimo režimai išsamiai
- Paslėptos eilutės ir fiksuota kaina
- Techninis įgyvendinimas
- Diegimas be nepageidaujamo šalutinio poveikio
- Naudojimas žingsnis po žingsnio
- Testai
- Apribojimai ir teisinė pastaba
- Dažniausiai užduodami klausimai
- Biricon IT Services palaikymas
Problema: produktų rinkiniai be įtakos spausdinimo vaizdui
Odoo produktų rinkinys yra paruoštas produktų sąrašas su kiekiais. Užuot ieškojus dešimties prekių po vieną, pasiūlyme pasirenkama Pridėti rinkinį ir iš karto gaunamos visos eilutės. Tai taupo laiką ir apsaugo nuo pamirštų pozicijų. Modulis sale_product_set iš saugyklos OCA/sale-workflow daro būtent tai; patys rinkiniai yra iš product_set saugyklos OCA/product-attribute.
Praktikoje lieka trys spragos:
- Nėra matomo grupavimo. Rinkinio eilutės pasiūlyme atsiduria kaip pavienės pozicijos. Po įterpimo nebematyti, kurios eilutės priklauso viena kitai, o bendros rinkinio sumos niekur nėra.
- Jokios įtakos spausdinamam dokumentui. Kartais klientas turi matyti kiekvieną poziciją su kaina, kartais – tik paketo kainą, kartais – tik vieną eilutę. Tai sprendimas pagal rinkinį ar klientą, o ne kiekvieną kartą rankomis keičiamas kiekviename pasiūlyme.
- Kainų korekcijos atrodo nemaloniai. Jei paketas turi fiksuotą kainą, besiskiriančią nuo kainoraščio kainų sumos, reikia korekcijos eilutės. Ji priklauso skaičiavimui, bet nebūtinai kliento popieriui.
Odoo 19 turi sudedamųjų dalių, leidžiančių tai išspręsti tvarkingai: pasiūlymo eilutėse yra skyriai ir poskyriai su parinktimis Slėpti kainas ir Slėpti sudėtį (laukai collapse_prices ir collapse_composition). Modulis susieja produktų rinkinį su šiomis standartinėmis parinktimis, užuot kūręs savo spausdinimo formatą.
Ką modulis daro
- Rinkinio įterpimas kaip skyriaus. Vedlys Pridėti rinkinį sukuria skyriaus eilutę su rinkinio pavadinimu, pavyzdžiui, 2 × Messkit Standard, ir po ja įterpia rinkinio eilutes. Rinkinio viduje esantys skyriai tampa poskyriais.
- Spausdinimo režimas kiekvienam rinkiniui. Rinkinyje vieną kartą nustatoma, kaip jis spausdinamas. Įterpiant režimą galima pakeisti vedlyje tik tam vienam atvejui.
- Eilučių slėpimas. Atskiras eilutes galima pažymėti kaip Nespausdinti (rinkinyje: Paslėpta). Jos įskaičiuojamos į sumą, bet nei spausdinamos, nei rodomos klientų portale. Žymė iš pasiūlymo perkeliama į sąskaitą faktūrą.
- Fiksuota kaina kiekvienai rinkinio eilutei. Rinkinio eilutė gali turėti fiksuotą vieneto kainą, kuri pakeičia kainoraščio kainą, pavyzdžiui, paslėptai kainos korekcijai.
Be modulio (tik sale_product_set) | Su sale_product_set_layout |
|---|---|
| Rinkinio eilutės kaip pavienės pozicijos | Skyriaus eilutė su rinkinio pavadinimu, kiekis pavadinime, eilutės po ja |
| Rinkinio skyriai lieka skyriais ir suardo struktūrą | Rinkinio skyriai tampa poskyriais (line_subsection) |
| Spausdinimo vaizdas visada tas pats | Spausdinimo režimas kiekvienam rinkiniui, įterpiant keičiamas |
| Kainos korekcija matoma pasiūlyme ir sąskaitoje | Eilutę galima paslėpti, bet ji įskaičiuojama į sumą |
| Kaina tik iš kainoraščio | Pasirinktinai fiksuota kaina kiekvienai rinkinio eilutei |
| Maketas baigiasi pasiūlyme | Maketas ir slėpimas galioja pasiūlymui, užsakymui, sąskaitai faktūrai ir klientų portalui |
Trys spausdinimo režimai išsamiai
Spausdinimo režimas yra pasirinkimo laukas print_mode prie produktų rinkinio. Įterpiant jis paverčiamas dviem standartiniais skyriaus eilutės jungikliais. Tam modulyje yra vienas lengvai skaitomas priskyrimas:
# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
PRINT_MODE_FLAGS = {
"detailed": (False, False),
"total_only": (True, False),
"summary": (False, True),
}
Režimas (print_mode) | Rodinys rinkinyje | Standartinė parinktis Odoo | Spausdinimo vaizdas |
|---|---|---|---|
detailed | Kiekviena eilutė su kaina | nėra | Skyrius, visos eilutės su sumomis |
total_only | Eilutės be kainų, tik rinkinio suma | collapse_prices (Slėpti kainas) | Skyrius su suma, eilutės tik su pavadinimu ir kiekiu |
summary | Tik viena eilutė su rinkinio suma | collapse_composition (Slėpti sudėtį) | Tik skyriaus eilutė su suma |
Kadangi modulis naudoja standartines Odoo 19 parinktis, o ne savo spausdinimo formatą, pasiūlymas, užsakymo patvirtinimas, sąskaita faktūra ir klientų portalas veikia be pritaikytų ataskaitų šablonų. Kas turi savo ataskaitų šablonus, atsižvelgiančius į skyrių parinktis, tą patį elgesį gauna taip pat.
Paslėptos eilutės ir fiksuota kaina
Dažnas atvejis: paketo kaina yra mažesnė už atskirų kainų sumą. Užuot iškraipius atskiras kainas, naudojama korekcijos eilutė su fiksuota kaina (pavyzdžiui, −50,00 € už rinkinį) ir ji paslepiama. Vidinėje sistemoje sąskaita lengvai atsekama, o spausdintame dokumente matoma paketo kaina.
Svarbu suprasti: žymė hide_in_report nieko nekeičia skaičiavime. Kiekis, kaina, nuolaida, mokestis, sumos ir buhalterijos įrašai lieka tokie, kokius sukuria standartinė Odoo. Eilutės trūksta tik spausdintame dokumente ir portale.
- Produktų rinkinyje: rinkinio eilutė su Paslėpta ir pasirinktinai Fiksuota kaina kartu su Vieneto kaina.
- Pasiūlymo ir sąskaitos eilutėse: stulpelis Nespausdinti (
hide_in_report), taip pat paprastoms eilutėms be rinkinio. - Perdavimas: kuriant sąskaitą iš užsakymo, žymė perkeliama į sąskaitos eilutę. Kopijuojant pasiūlymą, ji išlieka.
Techninis įgyvendinimas
Modulis sąmoningai mažas: jis išplečia esamus modelius ir perrašo tik tas vietas, kuriose Odoo sprendžia, kurios eilutės spausdinamos ir kaip įterpiamas rinkinys. Nėra naujų ataskaitų šablonų, naujų modelių ir SQL intervencijų.
Išplėsti modeliai ir laukai
| Modelis | Papildymas |
|---|---|
product.set | Laukai insert_as_section (Boolean, pagal numatytuosius nustatymus įjungtas) ir print_mode (pasirinkimas, privalomas laukas, numatytasis detailed) |
product.set.line | Laukai hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() perduoda juos pasiūlymo eilutei |
sale.product.set.wizard | Laukai insert_as_section, section_name, print_mode; _prepare_order_lines() sukuria skyriaus eilutę |
sale.order / sale.order.line | Laukas hide_in_report (su copy=True); _get_order_lines_to_report() praleidžia paslėptas eilutes; _prepare_invoice_line() perduoda žymę toliau |
account.move / account.move.line | Laukas hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() ir _get_subsection_lines() filtruoja paslėptas eilutes; _get_aml_vals() perkelia žymę į ataskaitos duomenis |
Slėpimas pasiūlyme
Odoo 19 spausdintinas pasiūlymo eilutes nustato _get_order_lines_to_report(). Modulis ten prideda filtrą. Ingerencija į ataskaitos šabloną nereikalinga: ir pasiūlymo ataskaitos šablonas (sale/report/ir_actions_report_templates.xml), ir portalo šablonas (sale/views/sale_portal_templates.xml) šį metodą iškviečia:
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
)
Perdavimas į sąskaitą
Kuriant sąskaitos eilutę iš pasiūlymo eilutės (_prepare_invoice_line()), žymė nukopijuojama. Sąskaitos pusėje Odoo 19 yra keli keliai, kuriais eilutės patenka į ataskaitą: pagrindinis sąrašas, skyriaus eilutės ir poskyrio eilutės. Todėl modulis filtruoja keliose vietose (_get_move_lines_to_report() prie dokumento, _get_detailed_lines() ir _get_subsection_lines() prie eilutės), kitaip paslėpta eilutė galėtų vėl atsirasti vienu iš kitų kelių:
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")]
Vedlys: skyrius vietoj pavienių eilučių
Vedlys sale.product.set.wizard iš sale_product_set pasiūlymo eilutes sukuria metode _prepare_order_lines(). Modulis šį metodą perrašo. Jei Įterpti kaip skyrių išjungta, jis tiesiog iškviečia pradinį metodą ir elgesys lieka toks pat kaip OCA modulyje. Priešingu atveju pirmiausia sukuriama skyriaus eilutė su dviem standartiniais jungikliais, po to eina rinkinio eilutės. Rinkinio viduje esantys skyriai tampa poskyriais, kad nesuardytų pagrindinės struktūros:
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
Sprendimai, kurių nematyti
- Atskiri skaičiavimo metodai vedlyje. Skyriaus pavadinimas, spausdinimo režimas ir Įterpti kaip skyrių turi po atskirą
computemetodą (store=True,readonly=False). Jei visi trys naudotų tą patį metodą, aiškiai perduota vieno lauko reikšmė atstatytų kitus. Tai ypač pastebima, kai vedlys kuriamas iš kodo ar testų su reikšmėmis. - Skyriaus pavadinimas su kiekiu. Kai kiekis nelygus 1, jis nurodomas pavadinime (2 × Messkit Standard, kableliu kaip dešimtainiu skyrikliu), kad spausdintame dokumente būtų aišku, jog tai du rinkiniai, o ne vienas rinkinys dvigubo kiekio.
- Fiksuota kaina tik produktų eilutėms.
prepare_sale_order_line_values()nustatohide_in_reportirprice_unittik tada, kai rinkinio eilutė nėra skyriaus ar pastabos eilutė. - Standartinė Odoo vietoj savadarbio sprendimo. Spausdinimo režimas ir slėpimas naudoja esamus Odoo 19 kabliukus ir laukus. Dėl to atnaujinimai valdomi: jei Odoo pakeičia ataskaitos šabloną, modulis lieka galiojantis, kol metodai egzistuoja.
Diegimas be nepageidaujamo šalutinio poveikio
Reikalavimai: Odoo 19.0, sale_product_set (OCA/sale-workflow, šaka 19.0) ir product_set (OCA/product-attribute, šaka 19.0). Pats modulis priklauso nuo sale_management, sale_product_set ir account.
auto_install spąstai OCA moduliuose. Daugelyje OCA saugyklų yra modulių su auto_install. Kai tik atsiranda jų priklausomybės, Odoo juos be klausimo įdiegia kitą kartą diegiant ar atnaujinant. Todėl, kas visą OCA saugyklą deda į produkcinės sistemos addons_path, gali gauti modulių, kurių niekas nenorėjo. Saugykloje OCA/product-attribute tai, pavyzdžiui, liečia purchase_product_template_tags. Mūsų veiksmai:
- Abi OCA saugyklas klonuoti paviršutiniškai (
git clone --depth 1 --branch 19.0). - Atskirame kataloge simboline nuoroda susieti tik du reikalingus modulius ir šį katalogą įtraukti į
addons_path, o ne visas saugyklas. - Įdiegti bandomojoje duomenų bazėje ir patikrinti, kuriems moduliams tuo metu suteikiama būsena įdiegta.
- Tik tada diegti produkcinėje sistemoje su
--workers=0, kad komanda nekonkuruotų su veikiančia tarnyba dėl HTTP prievado.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
Nauji laukai ir rodiniai po įdiegimo yra duomenų bazėje. Jei tarnyba veikia daugiaprocesiu režimu, darbiniai procesai pakeistą registrą įkelia per duomenų bazės signalą. Modulio Python failų pakeitimai įsigalioja tik paleidus tarnybą iš naujo.
Naudojimas žingsnis po žingsnio
- Rinkinio sukūrimas: Pardavimai → Produktai → Produktų rinkiniai. Pavadinti rinkinį, pasirinkti Įterpti kaip skyrių ir Spausdinimo režimą, įvesti produktus su kiekiais. Korekcijos eilutėms nustatyti Paslėpta ir Fiksuota kaina.
- Pasiūlyme: spustelėti Pridėti rinkinį, pasirinkti rinkinį ir kiekį. Skyriaus pavadinimą ir spausdinimo režimą prieš įterpiant galima pakeisti.
- Atskirų eilučių slėpimas: pasiūlyme ar sąskaitoje įjungti stulpelį Nespausdinti.
- Patikrinimas: atidaryti peržiūrą arba PDF. Tą patį vaizdą klientai mato portale.
Testai
Modulis turi šešis automatizuotus testus, kuriuos vykdome bandomojoje duomenų bazėje prieš pradedant naudoti produkcinėje sistemoje:
- Įterpimas kaip skyriaus, įskaitant spausdinimo režimą ir poskyrį
- Paslėptos eilutės nėra tarp spausdintinų eilučių
- Režimas Viena eilutė (
summary) - Įterpimas be skyriaus, pradinis elgesys
- Sąskaita perima žymę
hide_in_report, eilutės nėra_get_move_lines_to_report(), skyriaus suma ją vis tiek apima - Sąskaita režimu Tik bendra kaina neparodo paslėptos eilutės skyriaus eilutėse, skyriaus suma lieka išsami
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
--test-enable --test-tags=/sale_product_set_layout --stop-after-init
Vertimas į vokiečių kalbą yra faile i18n/de.po.
Apribojimai ir teisinė pastaba
- Tik maketas. Modulis nekeičia, kaip Odoo skaičiuoja kainas, nuolaidas ar mokesčius. Jis keičia tik tai, kas spausdinama ir rodoma portale.
- Paslėptos eilutės vidinėje sistemoje matomos. Žymė liečia spausdintą dokumentą ir portalą, o ne darbuotojų ekrano vaizdą.
- Patikrinkite sąskaitų duomenis. Paslėptos eilutės įskaičiuojamos į sumą, bet dokumente jų nėra. Ar tai suderinama su jūsų šalies reikalavimais sąskaitoms, pavyzdžiui, su privalomu pristatytų daiktų ar suteiktų paslaugų aprašymu, turėtumėte išsiaiškinti su savo mokesčių konsultantu prieš naudodami sąskaitose. Pasiūlymuose slėpimas yra mažiau problemiškas. Šis straipsnis nėra teisinė konsultacija.
- Išbandyta su Odoo 19. Modulį sukūrėme ir išbandėme su Odoo 19.0 Community. Jis naudoja skyrių parinktis, kurios tokia forma yra Odoo 19. Kitų versijų netikrinome.
Dažniausiai užduodami klausimai
Kam man reikalingas modulis, jei Odoo 19 jau moka skyrius?
Skyrių parinktys yra pačioje Odoo. Trūko ryšio su produktų rinkiniu: rinkinys turėjo būti automatiškai įterpiamas kaip skyrius su rinkinyje nustatytu spausdinimo režimu. Be modulio po kiekvieno įterpimo skyriaus eilutę reikia sukurti ir sukonfigūruoti rankomis.
Kuo skiriasi „Slėpti kainas“ ir „Slėpti sudėtį“?
„Slėpti kainas“ (collapse_prices) palieka eilutes, bet nerodo sumų, tik skyriaus sumą. „Slėpti sudėtį“ (collapse_composition) eilutes paslepia visiškai, klientas mato tik skyrių su suma.
Ar paslėpta eilutė įskaičiuojama į sąskaitos sumą?
Taip. Paslėptos eilutės lieka sumoje, mokestyje ir buhalterinėje apskaitoje. Žymė liečia tik vaizdavimą spausdintame dokumente ir klientų portale.
Ar įterpiant dar galiu pakeisti spausdinimo režimą?
Taip. Režimas paimamas kaip numatytasis iš rinkinio, o vedlyje jį galima pakeisti konkrečiam įterpimui. Pats rinkinys tuo metu nesikeičia.
Kas atsitinka, jei išjungiu Įterpti kaip skyrių?
Tada vedlys elgiasi kaip OCA modulyje sale_product_set: rinkinio eilutės įterpiamos be skyriaus.
Ar modulis netikėtai neįdiegia kitų modulių?
Pats modulis – ne. Tačiau OCA saugyklose gali pasitaikyti auto_install modulių, jei į addons_path dedamos ištisos saugyklos. Todėl susiekite tik du reikalingus modulius (žr. skyrių Diegimas).
Ar modulis veikia su Odoo Enterprise?
Modulis priklauso tik nuo Community modulių (sale_management, account) ir dviejų OCA modulių. Mes jį naudojame su Odoo 19 Community. Su Enterprise neišbandėme.
Pagal kokią licenciją platinamas modulis?
Pagal AGPL-3. Galite jį naudoti, pritaikyti ir platinti, kol pakeitimai lieka pagal tą pačią licenciją. Apie klaidas ir tobulinimo pasiūlymus laukiame pranešimų užduočių (issue) pavidalu GitHub saugykloje .
Biricon IT Services palaikymas
Prižiūrime Odoo diegimus mažoms ir vidutinėms įmonėms: nuo įdiegimo, pritaikymų ir migracijų iki nuolatinio veikimo. Jei norite pritaikyti produktų rinkinius, pasiūlymų šablonus ar ataskaitas Odoo 19 savo procesams arba norite peržiūrėti tokį modulį ir saugiai perkelti jį į savo produkcinę sistemą, mielai padėsime. Kitų nuosavų kūrinių rasite mūsų tinklaraštyje, pavyzdžiui, Sąskaitų projektų sujungimas ir Banko mokėjimų automatinis priskyrimas sąskaitoms.
Būklė: 2026 m. lapkričio 2 d. Modulio versija 19.0.1.0.0. Paveikslų pavyzdiniai duomenys parinkti laisvai, sumos be PVM.