Ако продавате пакети в 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
Съдържание на статията
- Проблемът: продуктови комплекти без контрол върху изгледа при печат
- Какво прави модулът
- Трите режима на печат в подробности
- Скрити редове и фиксирана цена
- Техническа реализация
- Инсталиране без нежелани странични ефекти
- Работа стъпка по стъпка
- Тестове
- Ограничения и правна бележка
- Често задавани въпроси
- Съдействие от Biricon IT Services
Проблемът: продуктови комплекти без контрол върху изгледа при печат
В Odoo продуктовият комплект е предварително подготвен списък с продукти и количества. Вместо да търсите десет артикула поотделно, избирате Добавяне на комплект в офертата и получавате всички редове наведнъж. Това спестява време и предотвратява пропускането на позиции. Модулът sale_product_set от хранилището OCA/sale-workflow прави точно това; самите комплекти идват от product_set в хранилището OCA/product-attribute.
На практика остават три нерешени проблема:
- Липсва ясно групиране. Редовете на комплекта се добавят като отделни позиции в офертата. След добавянето вече не се вижда кои редове принадлежат към един комплект, а общата му сума не е посочена никъде.
- Няма контрол върху разпечатката. Понякога клиентът трябва да вижда всяка позиция с цена, понякога — само пакетната цена, а понякога — един-единствен ред. Това трябва да се задава за всеки комплект или клиент, а не да се настройва ръчно във всяка нова оферта.
- Ценовите корекции не изглеждат добре. Ако пакетът трябва да има фиксирана цена, различна от сбора на каталожните цени, е необходим коригиращ ред. Той трябва да участва в изчислението, но не е задължително да присъства в документа за клиента.
Odoo 19 разполага с необходимото за подредено решение: раздели и подраздели в редовете на офертата с опциите Скриване на цените и Скриване на състава (полетата collapse_prices и collapse_composition). Модулът свързва продуктовия комплект с тези стандартни опции, вместо да въвежда собствен формат за печат.
Какво прави модулът
- Добавяне на комплект като раздел. Помощникът Добавяне на комплект създава ред за раздел с името на комплекта, например 2 × Messkit Standard, и поставя редовете на комплекта под него. Разделите в самия комплект се превръщат в подраздели.
- Режим на печат за всеки комплект. Начинът на отпечатване се задава веднъж в комплекта. При добавяне режимът може да се промени в помощника само за конкретния случай.
- Скриване на редове. Отделни редове могат да бъдат маркирани като Да не се печата (в комплекта: Скрит). Те участват в общата сума, но не се отпечатват и не се показват в клиентския портал. Този признак се пренася от офертата във фактурата.
- Фиксирана цена за всеки ред от комплекта. Ред от комплекта може да има фиксирана единична цена, която заменя цената от ценовата листа — например за скрита ценова корекция.
Без модула (само 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 € за комплект) и го скривате. В административния интерфейс изчислението остава проследимо, а в разпечатката се вижда пакетната цена.
Важно уточнение: отметката hide_in_report не променя изчисленията. Количеството, цената, отстъпката, данъкът, сумите и счетоводните записвания се определят по стандартния начин в Odoo. Редът се скрива само в разпечатката и в портала.
- В продуктовия комплект: ред от комплекта с Скрит и по избор Фиксирана цена с Единична цена.
- В редовете на офертите и фактурите: колона Да не се отпечатва (
hide_in_report), включително за обикновени редове извън комплект. - Прехвърляне: при създаване на фактура от поръчката отметката се прехвърля в реда на фактурата. При копиране на оферта тя се запазва.
Техническа реализация
Модулът е умишлено малък: разширява съществуващи модели и предефинира само местата, в които Odoo определя кои редове да се отпечатват и как да се вмъква комплект. Няма нови шаблони за отчети, нови модели или намеси чрез SQL.
Разширени модели и полета
| Модел | Допълнение |
|---|---|
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 × Messkit Standard, със запетая като десетичен разделител), за да е ясно в разпечатката, че става дума за два комплекта, а не за един комплект с двойно количество.
- Фиксирана цена само за продуктови редове.
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. Нашият подход:
- Клонирайте двете хранилища на OCA с ограничена дълбочина (
git clone --depth 1 --branch 19.0). - В отделна директория създайте символни връзки само към двата необходими модула и добавете тази директория в
addons_path, а не целите хранилища. - Инсталирайте в тестова база данни и проверете кои модули получават статус Инсталиран.
- Едва след това инсталирайте в производствената среда с
--workers=0, за да не се конкурира командата с работещата услуга за HTTP порта.
odoo-bin -c /etc/odoo/odoo.conf -d <datenbank> -i sale_product_set_layout --stop-after-init
След инсталирането новите полета и изгледи са в базата данни. Ако услугата работи с няколко процеса, работните процеси зареждат променения регистър чрез сигнал от базата данни. Промените в Python файловете на модула влизат в сила едва след рестартиране на услугата.
Работа стъпка по стъпка
- Създаване на комплект: Продажби → Продукти → Продуктови комплекти. Задайте име на комплекта, изберете Вмъкване като раздел и Режим на печат, въведете продуктите и количествата. За коригиращите редове включете Скрит и Фиксирана цена.
- В офертата: щракнете върху Добавяне на комплект и изберете комплект и количество. Името на раздела и режимът на печат могат да се променят преди вмъкването.
- Скриване на отделни редове: в офертата или фактурата поставете отметка в колоната Да не се отпечатва.
- Проверка: отворете предварителния преглед или 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. Примерните данни в изображенията са произволно избрани, сумите са без ДДС.