Преминете към съдържание

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 пакети, например измервателен уред с аксесоари и монтаж, работно място с хардуер и настройка или пакет за поддръжка от няколко услуги, се нуждае в офертата от две неща едновременно: Съставът трябва да присъства в поръчката, за да знаят склад, техници и фактуриране какво се доставя. На разпечатката за клиента обаче не във всеки случай трябва да се появява всяка отделна позиция с всяка отделна цена.

OCA модулът sale_product_set позволява да се вмъкват продуктови комплекти (спецификации за продажба) в оферта с едно щракване. Той обаче не определя вида на разпечатката. За тази цел разработихме модула Sale Product Set Layout (sale_product_set_layout). Написан е за Odoo 19 и е достъпен безплатно в GitHub като свободен софтуер под лиценза AGPL-3:

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 × Стандартен измервателен комплект, и поставя редовете на комплекта под него. Разделите в рамките на комплекта стават подраздели.
  2. Режим на печат за всеки комплект. В комплекта се задава веднъж как да се отпечатва. При вмъкване режимът може да бъде променен в помощника само за този конкретен случай.
  3. Скриване на редове. Отделни редове могат да бъдат маркирани като Не се печата (в комплекта: Скрит). Те се включват в сумата, но нито се отпечатват, нито се показват в клиентския портал. Маркировката се прехвърля от офертата във фактурата.
  4. Фиксирана цена за ред от комплекта. Един ред от комплекта може да има фиксирана единична цена, която замества цената от ценоразписа, например за скрита корекция на цената.
Без модула (само sale_product_set)С sale_product_set_layout
Редове на комплекта като отделни позицииРед-раздел с името на комплекта, количеството в името, редовете отдолу
Разделите в комплекта остават раздели и разрушават структуратаРазделите в комплекта стават подраздели (line_subsection)
Разпечатката е винаги една и същаРежим на печат за всеки комплект, може да се променя при вмъкване
Корекцията на цената е видима в офертата и във фактуратаРедът може да се скрие, но се включва в сумата
Цена само от ценоразписаПо избор фиксирана цена за ред от комплекта
Оформлението свършва при офертатаОформлението и скриването важат за оферта, поръчка, фактура и клиентски портал

Трите режима на печат в детайли

Режимът на печат е поле за избор print_mode в продуктовия комплект. При вмъкване той се превежда в двата стандартни превключвателя на реда-раздел. За целта в модула има едно-единствено, лесно четимо съответствие:

# Druckmodus -> (collapse_prices, collapse_composition) der Abschnittszeile
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 € за комплект), който се скрива. В бекенда фактурата е проследима, а на разпечатката се вижда цената на пакета.

Корекцията на цената е видима и маркирана в бекенда, но не се вижда в разпечатката на офертата, в PDF, във фактурата и в портала, сумата е идентична

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

  • В продуктовия комплект: ред от комплекта с Скрит и по избор Фиксирана цена заедно с Единична цена.
  • В редовете на оферти и фактури: колона Не се печата (hide_in_report), също и за обикновени редове без комплект.
  • Пренасяне: При създаване на фактурата от поръчката маркировката се прехвърля към реда на фактурата. При копиране на оферта тя се запазва.

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

Модулът е умишлено малък: той разширява съществуващи модели и замества само местата, където Odoo решава кои редове да се отпечатат и как да се вмъкне комплект. Няма нови шаблони за справки, нови модели и намеса в SQL.

Поток на данните: продуктов комплект, помощник за добавяне на комплект, редове на оферта, редове на фактура, PDF и клиентски портал

Разширени модели и полета

МоделДопълнение
product.setПолета insert_as_section (булево, по подразбиране включено) и 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 × Стандартен измервателен комплект, със запетая като десетичен разделител), за да се вижда в разпечатката, че става дума за два комплекта, а не за един комплект с удвоено количество.
  • Фиксирана цена само за продуктови редове. prepare_sale_order_line_values() задава hide_in_report и price_unit само ако редът от комплекта не е ред-раздел или ред-бележка.
  • Стандартен Odoo вместо собствена разработка. Режимът на печат и скриването използват наличните куки и полета на 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. В отделна директория свържете със символна връзка само двата необходими модула и включете тази директория в 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) скрива редовете изцяло, клиентът вижда само раздела със сумата.

Включва ли се скритият ред в сумата на фактурата?

Да. Скритите редове остават в сумата, данъка и счетоводното записване. Маркировката засяга само представянето в разпечатката и в клиентския портал.

Мога ли да променя режима на печат при вмъкване?

Да. Режимът се взема като стойност по подразбиране от комплекта, а в помощника може да се промени за съответното вмъкване. Самият комплект остава непроменен.

Какво става, ако изключа Вмъкване като раздел?

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

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

Самият модул не. При 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. Примерните данни в изображенията са произволно избрани, сумите са без ДДС.