Skip to Content

Розробка Odoo: від оцінки до продакшену

18 вересня 2026 р. від
Розробка Odoo: від оцінки до продакшену

Оцінка розробки Odoo - це зазвичай одне число: години. Воно нічого не каже про те, що відбувається всередині цих годин, а саме там вирішується, чи працюватиме модуль Odoo після наступного оновлення версії.

Тож ось порядок роботи на моїх проєктах. Здебільшого це шрами, що лишилися від версій з 15-ї по 19-ту.

Перш ніж зʼявиться модуль Odoo, зʼявляються два документи

Перший - план: що система має робити інакше в день, коли роботу завершено, написане мовою бізнесу. «Комірник сканує код, і рядок закривається» - це план. «Додати поле в stock.move.line» - ні, це вже відповідь, і зазвичай неправильна. У девʼяти випадках із десяти для сканування потрібне правило номенклатури штрихкодів і перевизначення наявного переміщення, а не нове поле бодай десь. План має лишатися мовою комірника достатньо довго, щоб це ще було видно.

Другий - архітектура, і ось тут розробка Odoo перестає бути схожою на розробку взагалі. Модуль Odoo не живе сам по собі. Він сидить усередині чужого коду - один мажорний реліз на рік, патч майже щотижня, від компанії, яка не знає про ваше існування. Тому архітектура відповідає на вужчий набір питань:

Які наявні моделі задіяні і чи достатньо їх успадкувати. Що додається: поле, метод, подання, маршрут. І що лишається недоторканим. Яка поведінка ядра перевизначається і чи взагалі правильної форми це перевизначення - _inherit розширює модель на місці, а _inherits тихо додає зовнішній ключ і делегований запис, і саме так у системі зʼявляється зайвий рядок на кожен товар, а чому - вже ніхто не памʼятає.

На якій редакції працює система, бо план, збудований навколо моделі з Enterprise, на Community вже не план. Скільки компаній, сайтів і мов має обслуговувати один інстанс: сорок сайтів в одному інстансі Odoo - це інша архітектура, ніж один.

І питання, яке пропускають найнадійніше: звідки беруться дані і що робить інтеграція Odoo о третій ночі, коли інша сторона перестає відповідати. «Повторює двічі, потім відкладає замовлення і пише мені листа» - це архітектура. Тиша - теж архітектура, просто її ніхто не обирав.

Архітектуру читають до того, як її побудували

Щойно ця сторінка існує - ви її бачите. Рішення, а не код: ці три моделі зачіпаються, ця - ні, інтеграція Odoo йде через чергу завдань, тож невдалий виклик до постачальника стає записом, який можна повторити і на який можна подивитися, а все за розкладом працює як ir.cron, який передає довгу роботу в чергу, бо крон-воркера вбивають, щойно він упирається в ліміт часу.

Ревʼю забирає близько години, і це останній дешевий момент передумати. Передумаєте після реалізації - платите за тиждень. Передумаєте після запуску - платите ще й за два тижні, протягом яких склад вводив усе двічі.

Тут же я кажу те, що коштує мені грошей: інколи чесна відповідь - це Studio, або модуль з магазину додатків, який підтримує хтось інший, або взагалі нічого. Odoo вже це вміє - погано, але прийнятно, - а власний модуль додасть рік підтримки заради десяти зекономлених хвилин на місяць. Власний модуль Odoo - найдорожчий із цих варіантів, і він має перемагати по суті.

Реалізація: успадковуйте модель Odoo, а не форкайте її

Правило нудне: розширювати, ніколи не копіювати. Форкнутий файл ядра чудово працює до оновлення, а потім він ваш назавжди.

Є один чесний виняток. У деяких методів ядра немає шва - нічого, навколо чого викликати super(), немає за що зачепитися. Коли такий метод доводиться замінити цілком, копія несе коментар із назвою вихідного файлу, гілки та версії, з якої вона взята, - щоб наступне оновлення мало для звірки дифф, а не загадку.

На практиці: успадкування для моделей, xpath для подань, QWeb для звітів, перевизначення, які все одно викликають оригінал. Поля, додані до моделі ядра, несуть префікс модуля - щоб поле ядра з такою самою назвою, яке приїде через дві версії, не лягло поверх мого, і щоб із назви поля було видно, який модуль його поставив. Префікс x_ я не чіпаю - він належить Studio.

Дві пастки варто назвати, бо кожна коштує одного деплою. Успадкування подань не приймає довільний атрибут як селектор: xpath по aria-label відхиляється одразу. А hasclass(...) не знаходить нічого, коли батьківський шаблон будує цей клас через t-attf-class, бо атрибута class у вихіднику просто немає. Обидві помилки зʼявляються лише під час встановлення модуля - тобто вже після того, як статичні перевірки були зеленими. Я читаю сирий XML шаблону, який розширюю, а не відрендерений HTML, де класи вже склеєні докупи й зламаний селектор має цілком правильний вигляд.

Ще одне, про що клієнти цілком розумно думають навпаки: видалення модуля видаляє колонки, які він додав, разом із даними в них. Тож видалення тестується на копії, а якщо в полі лежить щось, що бізнесу ще знадобиться, це вирішується до того, як поле створено, а не того дня, коли хтось поставить галочку.

Більшість того, завдяки чому модуль Odoo переживає міграцію Odoo на наступну версію, вирішується саме тут - у виборі, якого ззовні ніхто не бачить і який усі відчувають через два роки.

Тести і що тут насправді робить ШІ

Кожна нетривіальна зміна отримує тести: TransactionCase для логіки ORM, тур HttpCase, коли змінюється екран, обидва з тегом post_install, щоб вони запускалися разом із тими модулями, які реально стоятимуть поруч із моїм.

Я пишу їх з ШІ, і цю фразу розтягнули настільки, що її варто визначити. ШІ швидкий у механічній половині: зібрати фікстури, покрити третю й четверту гілку умови, перетворити «перевірити, що скасоване замовлення звільняє резерв» на код, який запускається. Тести раніше були першим, що викреслювали, коли оцінка тиснула. Тепер - ні, і в цьому вся практична різниця: не в тому, що тести стали кращими, а в тому, що їх тепер пишуть.

Чого ШІ не може - це вирішити, що саме тестувати. Він не знає, що ця конкретна інтеграція падає, коли постачальник надсилає кому як десятковий роздільник, бо цей факт живе не в коді - він живе в останніх двох роках експортів того постачальника. Вибір випадків, які заслуговують на тест, досі моя робота, і передати її я поки не навчився.

Два правила навколо цього. Згенерований тест має спершу впасти, а вже потім пройти: якщо я не можу зламати код і побачити червоне, тест не перевіряє нічого, а згенеровані тести дуже переконливо вміють не перевіряти нічого. І фікстури - це вигадані записи, а не експорт ваших клієнтів.

Стейджинг на реальних даних, потім продакшен

Зміна спершу їде на стейджинг, на копію реальної бази. Демодані ввічливі. У них немає клієнтів із двома пробілами в назві, немає товарів, імпортованих багато років тому за правилами, яких ніхто не записав, немає рахунків у валюті, якої вже не існує. І обсягу в них теж немає: пошук, який на демоданих відповідає миттєво, на ста десяти тисячах товарів працював вісім секунд.

Копія вашої бази - це все ще ваша база, з вашими клієнтами всередині, тож перед роботою її нейтралізують: вихідна пошта вимкнена, завдання за розкладом вимкнені, платіжні провайдери на тестових доступах. Інакше перший же перезапуск процесу на стейджингу надішле листи трьомстам реальним людям із сервера, якого не має існувати.

На стейджингу роботу перевіряє той, хто її замовляв, на власних записах. Потім продакшен, у письмовому порядку: який модуль оновлюється, який міграційний скрипт іде до оновлення, а який після, і що стається з даними, які вже лежать у базі. Опублікований контент - звична несподіванка: запис, завантажений з noupdate, не переімпортується, хоч би що ви робили, тож текст, який уже живий, змінюється записом у сам запис усередині міграції - або не змінюється взагалі. І відкат, який я хоча б раз реально виконував. Припущений відкат - це не відкат.

Дефекти, які виникають лише в продакшені

Ось та частина розробки Odoo, якої оцінки ніколи не згадують. Деяким дефектам, щоб узагалі існувати, потрібен справжній кеш і справжній відвідувач, а в тестової бази немає ні того, ні того. Я окремо писав про те, що тримається і що ламається в живій системі Odoo; ці три - з одного тижня на цьому самому сайті.

Банер згоди записував відповідь відвідувача в неправильному форматі. «Лише необхідні» зберігала коректний JSON; «Прийняти» зберігало рядок true, бо повна згода перезапускає взаємодії сторінки, і обробник закриття модалки спрацював уже на свіжому екземплярі, який усе ще тримав значення за умовчанням. На сервері перевірка, що читає цю куку, очікує словник і видаляє все інше - гілка сумісності робить рівно те, для чого її написали. Тож кожен, хто натиснув «прийняти», отримував запит знову на наступній сторінці, а аналітика за ним лишалася на нулі, маючи при цьому цілком справний вигляд.

Другий дефект ховається в екрані налаштувань. Чи можна завантажити трекер, вирішує метод, який починається з «якщо банер cookie вимкнений, згода надана» - логіка тут така, що власник, який вимкнув банер, зібрав згоду якось інакше. Зніміть цю галочку - і кожен відвідувач мовчки вважається таким, що погодився, годину поспіль, бо відрендерена сторінка закешована. Ніщо в тому екрані налаштувань не має небезпечного вигляду.

Третій: подію конверсії почепили на момент життєвого циклу сторінки, який на час завантаження скрипта вже минув, бо скрипт їде у відкладеному бандлі, що стартує після завершення завантаження сторінки. Форма працювала, лід приходив, число лишалося нулем.

Усі три знайшлися однаково - відкрити живий сайт, натиснути обидві кнопки, прочитати куку і вкладку мережі замість коду. Це пʼятнадцять хвилин, і це єдине місце, де ці баги існують. Я виставляю їх у рахунок як частину роботи.

Скільки коштує наступна версія

Стаття була б нечесною, якби радила питати про оновлення, а потім ухилилася від відповіді. Оновлювати базу - робота Odoo, і роблять вони це добре. Оновлювати ваш власний код - моя, і ціна залежить від одного: скільки модуля стоїть поверх поведінки ядра, яка зрушила.

Модуль, який додає власну модель, кілька полів і кілька подань, зазвичай переживає мажорну версію за півдня правок. Модуль, який перевизначає create, патчить звіт і успадковує чотири шаблони ядра, - це тиждень. Варто знати до того, як він стане вашим: create має декоратор @api.model_create_multi і отримує список записів, а не один, тож перевизначення, написане під один набір значень, не падає голосно - воно тихо обробляє перший рядок пакетного імпорту й не помічає решти.

І оновлення йдуть версія за версією. Odoo нічого не пропускає, тож власний код переносять на кожному кроці, а не один раз у кінці.

Права - частина функціоналу

Кожна модель, яку я додаю, приїжджає з правилами доступу і з правилом запису, коли дані не для всіх, - написаними одночасно з моделлю. Саме так, із правами, прикрученими до готового модуля заднім числом, продавець і починає читати маржу чужого регіону.

Найпильніше в будь-якому коді, включно зі своїм, я шукаю sudo(). Він вимикає всі правила одразу, це найшвидший спосіб змусити баг із правами зникнути, а в публічному контролері це витік даних із привітною адресою.

Доведення після запуску

Модуль готовий приблизно через два тижні після запуску, коли повертається список дрібних незручностей: у цього поля має бути значення за умовчанням, цьому екрану потрібно на один клік менше, цей звіт читають щоранку о восьмій, тож він має бути пунктом меню, а не фільтром, який хтось перевиставляє руками і в понеділок помиляється.

Цей прохід короткий. І саме він відрізняє модуль, який проходить свої тести, від модуля, який люди перестають обходити стороною. Пропустіть його - і матимете технічно правильну функцію, а поруч із нею таблицю, яка робить справжню роботу.

Одне питання, яке варто поставити

Нічого з цього ви не пригадаєте, сидячи навпроти розробника. Тож ось питання, яке я поставив би на вашому місці:

Що станеться з цим модулем, коли Odoo випустить наступну версію?

Якщо у відповіді названо конкретний ризик - це перевизначення сидить на методі, який Odoo часто чіпає, тож його перевіряють на кожному оновленні, - людина вже робила міграцію Odoo. «Усе буде добре» теж може бути правдивою відповіддю - для модуля, який додає два поля і списковий вигляд. Слухати тут треба одне: чи може людина сказати вам, який із двох саме ваш.

Це питання працює з будь-ким, кого ви розглядаєте, і відповідь скаже вам більше, ніж значок, - у чому й полягає більша частина різниці між партнером Odoo і незалежним розробником.

Якщо у вас є система, яка працює, і зміна, до якої не знаєте, як підступитися, напишіть мені. Перша розмова - про те, що має стати інакше, коли роботу зроблено, а не про години.

Партнер Odoo чи незалежний розробник: у чому різниця насправді
З чого складаються рівні Ready, Silver і Gold та кому платять, коли ви купуєте більше місць.