Skip to Content

Odoo 19 Community: сетови производи во понудата како дел – нашиот модул со отворен код Sale Product Set Layout

2 ноември 2026 | Odoo 19 Community | Сопствен развој
2 ноември 2026 од
Odoo 19 Community: сетови производи во понудата како дел – нашиот модул со отворен код Sale Product Set Layout

Модулот Sale Product Set Layout за Odoo 19: сетови производи како дел во понудата со режим на печатење, скриени редови и фиксна цена

Кој во Odoo продава пакети, на пример мерен уред со додатоци и монтажа, работно место со хардвер и поставување или пакет за одржување од повеќе услуги, му требаат во понудата две работи истовремено: составот мора да стои во нарачката за да знаат складот, техниката и фактурирањето што се испорачува. На испечатениот примерок за клиентот пак не треба во секој случај да се појавува секоја поединечна ставка со секоја поединечна цена.

Модулот sale_product_set на OCA овозможува сетови производи (списоци на материјали за продажба) да се вметнат во понуда со еден клик. За изгледот на печатењето тој не одлучува. За тоа го развивме модулот Sale Product Set Layout (sale_product_set_layout). Напишан е за Odoo 19 и е достапен како слободен софтвер под лиценцата AGPL-3 бесплатно на GitHub:

github.com/Alex0176/odoo-sale-product-set-layout

Проблемот: сетови производи без влијание врз изгледот на печатењето

Сетот производи во Odoo е однапред подготвен список на производи со количини. Наместо да бара десет артикли поединечно, во понудата корисникот избира Додај сет и ги добива сите редови одеднаш. Тоа заштедува време и спречува заборавени ставки. Модулот sale_product_set од репозиториумот OCA/sale-workflow го прави токму тоа; самите сетови потекнуваат од product_set во репозиториумот OCA/product-attribute.

Во пракса остануваат три празнини:

  • Нема препознатлива групација. Редовите на сетот завршуваат како разделни ставки во понудата. По вметнувањето повеќе не се гледа кои редови се заедно, а вкупниот збир на сетот не се гледа никаде.
  • Нема влијание врз испечатеното. Еднаш клиентот треба да ја види секоја ставка со цена, еднаш само цената на пакетот, еднаш само еден единствен ред. Тоа е одлука по сет или по клиент, а не нешто што се одредува одново рачно за секоја понуда.
  • Корекциите на цената се непријатни. Ако пакетот треба да има фиксна цена што отстапува од збирот на листите со цени, потребен е ред за корекција. Тој припаѓа во пресметката, но не мора да биде на хартијата на клиентот.

Odoo 19 ги носи градбените блокови за ова да се реши чисто: делови и подделови во редовите на понудата со опциите Сокриј цени и Сокриј состав (полиња collapse_prices и collapse_composition). Модулот го поврзува сетот производи со овие стандардни опции, наместо да измислува сопствен формат на печатење.

Што прави модулот

  1. Вметнување на сетот како дел. Асистентот Додај сет создава ред за дел со името на сетот, на пример 2 × Messkit Standard, и ги поставува редовите на сетот под него. Деловите во рамките на сетот стануваат поддел.
  2. Режим на печатење по сет. Во сетот се определува еднаш како се печати. При вметнувањето режимот може во асистентот да се промени за тој единечен случај.
  3. Сокривање редови. Одделни редови може да се означат како Не печати (кај сетот: Скриено). Тие се вбројуваат во збирот, но не се печатат ниту се прикажуваат во клиентскиот портал. Ознаката од понудата се префрла на фактурата.
  4. Фиксна цена по ред на сетот. Редот на сетот може да носи фиксна единечна цена што ја заменува цената од ценовникот, на пример за скриена корекција на цената.
Без модулот (само sale_product_set)Со sale_product_set_layout
Редови на сетот како разделни ставкиРед за дел со име на сетот, количина во името, редови под него
Деловите во сетот остануваат делови и ја кршат структуратаДеловите во сетот стануваат подделови (line_subsection)
Изгледот на печатењето е секогаш истРежим на печатење по сет, променлив при вметнувањето
Корекцијата на цената е видлива на понудата и фактуратаРедот може да се скрие, но се вбројува во збирот
Цена само од ценовникотОпционално фиксна цена по ред на сетот
Изгледот завршува кај понудатаИзгледот и сокривањето важат за понуда, нарачка, фактура и клиентски портал

Трите режими на печатење детално

Режимот на печатење е поле за избор print_mode кај сетот производи. При вметнувањето се претвора во двата стандардни прекинувачи на редот за дел. За тоа во модулот постои една единствена, лесно читлива доделба:

# Режим на печатење -> (collapse_prices, collapse_composition) на редот за дел
PRINT_MODE_FLAGS = {
    "detailed":   (False, False),
    "total_only": (True,  False),
    "summary":    (False, True),
}

Три изгледи на печатење на истиот сет производи: секој ред со цена, само вкупна цена, еден единствен ред

Режим (print_mode)Приказ во сетотСтандардна опција во OdooИзглед на печатењето
detailedСекој ред со ценанемаДел, сите редови со износ
total_onlyРедови без цени, само збир на сетотcollapse_prices (Сокриј цени)Дел со збир, редови само со назив и количина
summaryСамо еден ред со збирот на сетотcollapse_composition (Сокриј состав)Само редот за дел со збирот

Бидејќи модулот ги користи стандардните опции на Odoo 19, а не сопствен формат на печатење, понудата, потврдата на нарачката, фактурата и клиентскиот портал функционираат без прилагодени шаблони за извештаи. Кој има сопствени шаблони за извештаи што ги земаат предвид опциите за делови, го добива истото однесување.

Скриени редови и фиксна цена

Чест случај: пакетот има цена на пакетот што е под збирот на поединечните цени. Наместо да се кривуваат поединечните цени, се зема ред за корекција со фиксна цена (на пример −50,00 € по сет) и се скрива. Во позадинскиот дел (backend) фактурата е проследива, а на испечатеното стои цената на пакетот.

Корекцијата на цената е видлива и означена во позадинскиот дел, а невидлива во испечатеното од понудата, PDF-от, фактурата и порталот, збирот е ист

Важно за разбирањето: ознаката hide_in_report не менува ништо во пресметката. Количината, цената, попустот, данокот, збировите и книжењата настануваат непроменето преку стандардниот Odoo. Редот само недостасува во испечатеното и во порталот.

  • Кај сетот производи: ред на сетот со Скриено и по желба Фиксна цена заедно со Единечна цена.
  • Кај редовите на понудата и фактурата: колона Не печати (hide_in_report), и за обични редови без сет.
  • Пренос: при создавањето на фактурата од нарачката ознаката се пренесува на редот на фактурата. При копирање на понуда таа се зачувува.

Техничка реализација

Модулот е намерно мал: ги проширува постојните модели и ги препишува само местата каде што Odoo одлучува кои редови се печатат и како се вметнува сетот. Нема нови шаблони за извештаи, нови модели ниту зафати со SQL.

Проток на податоци: сет производи, асистент Додај сет, редови на понуда, редови на фактура, PDF и клиентски портал

Проширени модели и полиња

МоделДополнување
product.setПолиња insert_as_section (Boolean, стандардно вклучено) и print_mode (избор, задолжително поле, стандардно detailed)
product.set.lineПолиња hide_in_report, fixed_price, price_unit; prepare_sale_order_line_values() ги проследува до редот на понудата
sale.product.set.wizardПолиња insert_as_section, section_name, print_mode; _prepare_order_lines() го гради редот за дел
sale.order / sale.order.lineПоле hide_in_report (со copy=True); _get_order_lines_to_report() ги прескокнува скриените редови; _prepare_invoice_line() ја проследува ознаката
account.move / account.move.lineПоле hide_in_report; _get_move_lines_to_report(), _get_detailed_lines() и _get_subsection_lines() ги филтрираат скриените редови; _get_aml_vals() ја внесува ознаката во податоците за извештајот

Сокривање во понудата

Odoo 19 ги утврдува редовите на понудата за печатење во _get_order_lines_to_report(). Модулот таму додава филтер. Зафат во шаблонот за извештајот не е потребен: и шаблонот за извештајот на понудата (sale/report/ir_actions_report_templates.xml) и шаблонот на порталот (sale/views/sale_portal_templates.xml) го повикуваат овој метод:

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
        )

Пренос на фактурата

При создавањето на редот на фактурата од редот на понудата (_prepare_invoice_line()) ознаката се копира. На страната на фактурата во Odoo 19 има повеќе патишта по кои редовите доаѓаат во извештајот: главната листа, редовите на еден дел и редовите на еден поддел. Затоа модулот филтрира на повеќе места (_get_move_lines_to_report() кај документот, _get_detailed_lines() и _get_subsection_lines() кај редот), инаку скриен ред би можел да се појави повторно по еден од другите патишта:

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")]

Асистентот: дел наместо разделни редови

Асистентот sale.product.set.wizard од sale_product_set ги создава редовите на понудата во _prepare_order_lines(). Модулот го препишува овој метод. Ако Вметни како дел е исклучено, едноставно го повикува оригиналниот метод и однесувањето останува како во модулот на OCA. Во спротивно прво настанува редот за дел со двата стандардни прекинувачи, а потоа следуваат редовите на сетот. Деловите во рамките на сетот стануваат подделови за да не ја кршат главната структура:

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

Одлуки што не се гледаат

  • Одделни методи за пресметка во асистентот. Името на делот, режимот на печатење и Вметни како дел имаат по еден сопствен метод compute (store=True, readonly=False). Ако сите три би го имале истиот метод, експлицитно предадена вредност за едно поле би ги ресетирала другите. Тоа се забележува пред сè кога асистентот се создава од код или од тестови со вредности.
  • Име на делот со количина. При количина различна од 1 таа стои во името (2 × Messkit Standard, со запирка како децимален разделник) за во испечатеното да се препознае дека се два сета, а не еден сет со двојна количина.
  • Фиксна цена само кај редовите на производи. prepare_sale_order_line_values() ги поставува hide_in_report и price_unit само ако редот на сетот не е ред за дел или за белешка.
  • Стандарден Odoo наместо сопствена изработка. Режимот на печатење и сокривањето користат постојни куки (hooks) и полиња на Odoo 19. Тоа ги прави ажурирањата управливи: ако Odoo го промени шаблонот на извештајот, модулот останува важечки сè додека методите постојат.

Инсталација без несакани нуспојави

Предуслови: Odoo 19.0, sale_product_set (OCA/sale-workflow, гранка 19.0) и product_set (OCA/product-attribute, гранка 19.0). Самиот модул зависи од sale_management, sale_product_set и account.

Стапицата auto_install кај модулите на OCA. Многу репозиториуми на OCA содржат модули со auto_install. Штом ќе бидат присутни нивните зависности, Odoo ги инсталира заедно со другите без прашање при следното инсталирање или ажурирање. Кој затоа ќе го стави целиот репозиториум на OCA во addons_path на производствена инстанца, можеби ќе добие модули што никој не ги сакал. Кај OCA/product-attribute тоа на пример го засега purchase_product_template_tags. Нашиот пристап:

  1. Двата репозиториума на OCA се клонираат плитко (git clone --depth 1 --branch 19.0).
  2. Во посебен директориум се поврзуваат со симболичка врска (symlink) само двата потребни модула и овој директориум се става во addons_path, а не целите репозиториуми.
  3. Се инсталира на тест-база на податоци и се проверува кои модули притоа го добиваат статусот инсталиран.
  4. Дури потоа се инсталира производствено, со --workers=0, за повикот да не се натпреварува со активната услуга за HTTP-портот.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init

Новите полиња и прикази по инсталирањето се во базата на податоци. Ако услугата работи во повеќепроцесен режим, воркерите го вчитуваат променетиот регистар преку сигналот на базата на податоци. Измените во Python-датотеките на модулот стапуваат на сила дури по рестартирање на услугата.

Работа чекор по чекор

  1. Создавање сет: Продажба → Производи → Сетови производи. Именувајте го сетот, изберете Вметни како дел и Режим на печатење, внесете ги производите со количини. Редовите за корекција обележете ги со Скриено и Фиксна цена.
  2. Во понудата: Кликнете на Додај сет, изберете сет и количина. Името на делот и режимот на печатење може да се променат пред вметнувањето.
  3. Сокривање одделни редови: Во понудата или фактурата активирајте ја колоната Не печати.
  4. Проверка: Отворете преглед или PDF. Истиот приказ го гледаат клиентите во порталот.

Тестови

Модулот носи шест автоматизирани теста што ги извршуваме врз тест-база на податоци пред нешто да влезе во производство:

  • Вметнување како дел, вклучувајќи режим на печатење и поддел
  • Скриениот ред недостасува во редовите за печатење
  • Режим Еден ред (summary)
  • Вметнување без дел, оригиналното однесување
  • Фактурата ја презема ознаката hide_in_report, редот недостасува во _get_move_lines_to_report(), збирот на делот го содржи и натаму
  • Фактурата во режим Само вкупна цена не го покажува скриениот ред во редовите на делот, збирот на делот останува комплетен
odoo-bin -d <testdatenbank> -i sale_product_set_layout \
         --test-enable --test-tags=/sale_product_set_layout --stop-after-init

Германскиот превод се наоѓа во i18n/de.po.

Ограничувања и правна напомена

  • Само изглед. Модулот не менува како Odoo ги пресметува цените, попустите или даноците. Менува само што се печати и прикажува во порталот.
  • Скриените редови се видливи во позадинскиот дел. Ознаката се однесува на печатењето и порталот, а не на приказот на екранот на вработените.
  • Проверете ги податоците на фактурата. Скриените редови се вбројуваат во збирот, но не стојат на документот. Дали тоа е во согласност со барањата за фактури во вашата земја, на пример со пропишаниот опис на испорачаните предмети или извршените услуги, треба да го разјасните пред употребата на фактури со вашиот даночен советник. На понудите сокривањето е понепроблематично. Написот не е правен совет.
  • Тестирано за Odoo 19. Модулот го развивме и тестиравме за Odoo 19.0 Community. Користи опции за делови што во оваа форма постојат во Odoo 19. За други верзии не сме го проверувале.

Често поставувани прашања

Зошто ми треба модулот ако Odoo 19 веќе знае делови?

Опциите за делови постојат во самиот Odoo. Недостасуваше врската со сетот производи: сетот треба автоматски да се вметне како дел, со режимот на печатење зададен во сетот. Без модулот по секое вметнување редот за дел мора рачно да се создаде и конфигурира.

Како се разликуваат „Сокриј цени“ и „Сокриј состав“?

„Сокриј цени“ (collapse_prices) ги оставува редовите, но не покажува износи, само збирот кај делот. „Сокриј состав“ (collapse_composition) ги сокрива редовите сосема, клиентот го гледа само делот со збирот.

Дали скриениот ред се вбројува во збирот на фактурата?

Да. Скриените редови остануваат во збирот, данокот и книжењето. Ознаката се однесува само на приказот на испечатеното и во клиентскиот портал.

Можам ли режимот на печатење да го променам при вметнувањето?

Да. Режимот доаѓа како зададена вредност од сетот, а во асистентот може да се промени за конкретното вметнување. Самиот сет притоа останува непроменет.

Што се случува ако го исклучам Вметни како дел?

Тогаш асистентот се однесува како во модулот sale_product_set на OCA: редовите на сетот се вметнуваат без дел.

Дали модулот неочекувано инсталира други модули?

Самиот модул не. Кај репозиториумите на OCA сепак може да дојде до модули со auto_install, ако се ставаат цели репозиториуми во addons_path. Затоа поврзете ги само двата потребни модула (видете го делот Инсталација).

Дали модулот работи со Odoo Enterprise?

Модулот зависи само од модули на Community (sale_management, account) и од двата модула на OCA. Го користиме на Odoo 19 Community. На Enterprise не сме го тестирале.

Под која лиценца е модулот?

Под AGPL-3. Смеете да го користите, прилагодувате и пренесувате, сè додека измените остануваат под истата лиценца. Грешките и предлозите за подобрување се добредојдени како issue во GitHub-репозиториумот .

Поддршка од Biricon IT Services

Се грижиме за инсталации на Odoo за мали и средни претпријатија: од воведувањето преку прилагодувања и миграции до тековното работење. Ако сакате сетовите производи, шаблоните за понуди или извештаите во Odoo 19 да ги прилагодите на вашите процеси или ако сакате да проверите модул како овој и безбедно да го внесете во вашата производствена инстанца, со задоволство ќе ве поддржиме. Други сопствени развивања ќе најдете во нашиот блог, на пример Спојување на нацрт-фактури и Автоматско доделување на банкарски плаќања на фактури.

Контактирајте не сега

Состојба: 2 ноември 2026 година. Верзија на модулот 19.0.1.0.0. Примерните податоци на сликите се слободно избрани, износите се нето.