Odoo
МодульДокумент-орієнтована модель: sale.order → picking → invoice
Як один SO запускає ланцюг операцій: реальний приклад руху бізнес-документа крізь моделі Odoo.
Що це і навіщо
Одна з найкрасивіших ідей Odoo — документ-орієнтований ланцюг. Підтверджуєш sale.order — і Odoo сам створює stock.picking на відвантаження. Валідуєш picking — створюється account.move (інвойс). Реєструєш платіж — створюється account.payment і одразу автоматично реконсилюється з інвойсом. Один бізнес-документ запускає чотири пов’язані сутності, які знають одна про одну.
Бухгалтер отримує дві речі: повну простежуваність («звідки взявся цей інвойс? — з SO/123, ось він») і автоматичні проводки на кожному кроці без ручної бухгалтерії. Але тут і пастка: якщо хтось у ланцюгу зламається, посиплються всі downstream-документи.
Як це працює
Стандартний прямий потік для матеріальних товарів (без виробництва):
sale.order (Sales)
↓ action_confirm()
stock.picking (Outgoing, Inventory)
↓ button_validate()
account.move (Customer Invoice, Accounting)
↓ action_register_payment()
account.payment (Accounting)
↓ reconcile with invoice
[Done]
Кожна стрілка — це або кнопка користувача, або server action.
# Підтвердження SO
so = self.env['sale.order'].browse(so_id)
so.action_confirm()
# Тепер so.picking_ids = [picking1]
# Валідація picking
picking = so.picking_ids[0]
picking.move_ids.quantity = picking.move_ids.product_uom_qty # підтвердили кількості
picking.button_validate()
# Тепер stock.move у стані done, account.move посилання через stock_valuation_layer_ids
# Створення інвойсу
invoice = so._create_invoices()
invoice.action_post()
# Тепер so.invoice_ids = [invoice], invoice.state = 'posted'
# Реєстрація платежу
payment_wizard = self.env['account.payment.register'].with_context(
active_model='account.move', active_ids=invoice.ids).create({})
payment_wizard.action_create_payments()
# Тепер account.payment створено і реконсильовано з invoice
Зв’язки між моделями:
sale.order.picking_ids(One2many) →stock.pickingsale.order.invoice_ids(One2many) →account.movestock.picking.sale_id(Many2one) ← назад до SOaccount.move.invoice_origin(Char) — зберігає назву SOaccount.move.line.sale_line_ids(Many2many) → лінії SOaccount.payment.reconciled_invoice_ids→ інвойси, з якими реконсильовано
Шлях для дослідження ланцюга — Smart buttons на формі SO: «Deliveries», «Invoices», «Payments».
У контексті українського обліку
Український ланцюг трохи розкішніший:
sale.order
↓
stock.picking (Outgoing) + ТТН (друк)
↓
account.move (Customer Invoice) + Видаткова накладна (друк)
↓ автоматично
tax.invoice.ua (Податкова накладна) → ЄРПН
↓
account.payment
↓ Bank Statement reconciliation
[Done]
Між інвойсом і платежем вклинюється податкова накладна: у неї свій термін реєстрації (15 днів від дати першої події) і своє правове життя. Тут грають ще два чинники:
- Дата першої події. Якщо платіж прийшов раніше за відвантаження — це передоплата, і ПН складають на дату платежу. Тоді ланцюг розгортається у зворотному порядку: account.payment → tax.invoice → stock.picking → account.move.
- РК при поверненнях. Customer returns → stock.picking (incoming) → account.move (credit note) → tax.invoice.ua (РК зменшуюче).
Як ми робимо (defense-контекст)
ТОВ «УкрПром-Тех» розширила ланцюг defense-сценарієм:
defense.contract (А-1234)
↓ generates
sale.order (з prozorro_lot_id, defense_contract_id)
↓ підтвердження + перевірка ЄДРПОУ замовника проти ДСЕКУ
mrp.production (з обов'язковим серійним номером)
↓ закриття MO
stock.picking + Акт приймання-передачі
↓ підпис представника замовника
account.move + Видаткова + ПН (тип 09 — звільнено)
↓
account.payment (часто частковими — за етапами)
↓ контракт А-1234 закрито, коли усі лінії оплачені
Усі моделі знають про defense_contract_id. Це поле тягнеться ланцюгом — на SO, на MO, на picking, на invoice. Звітність за контрактом А-1234 = просто всі сутності з цим ID.
У defense є ще зворотний ланцюг для повернень і браку — окремий складний випадок: stock.return.picking → stock.picking (return) → reverse mrp.production (якщо брак виявлено в експлуатації) → account.move credit note → tax.invoice РК.
Типові граблі
- Розрив ланцюга вручну. Бухгалтер створив інвойс «не з SO» (через меню Accounting → Customer Invoice). У SO smart-button «Invoices» порожній. Звітність за контрактом не сходиться. Правило: завжди створюй downstream через кнопки upstream-документа.
- Picking валідовано до підтвердження SO. Технічно можливо, якщо вручну створити stock.picking. Тоді SO лишається у draft, а склад уже зменшується. Лікувати доводиться так: підв’язати picking через sale_id заднім числом (працює погано).
- Invoice без
invoice_origin. Інвойс ніби «сам по собі», SO за ним не простежити. У defense-контексті це критично: для аудиту за контрактом А-1234 треба бачити повний ланцюг. - Reconciliation не спрацював. Платіж є, інвойс є, але не зв’язані — система покаже інвойс як неоплачений. Часто причина — різні partner_id (платіж від «дочки», інвойс на «маму») або різні валюти без рахунку курсової різниці.
- Backorder ламає звітність. Замовив 100, відвантажив 80, решта 20 — backorder. Інвойс на 80 (так працює
Invoicing policy=delivered quantities), а SO ще показує 100 в очікуванні. Якщо ті 20 так і не відвантажать, backorder висітиме. Закрий його — або відвантаженням, або скасуванням. - MO між SO і інвойсом. Make-to-order: SO → MO → MO done → stock.picking → invoice. Якщо MO зависло — нема picking, нема інвойсу. Завжди стеж за статусом MO у smart-button.
- Скасування не йде ланцюгом назад. Скасував SO — picking лишився у стані assigned, але без джерела. Запас і далі зарезервований. Cron «Stuck pickings» це покаже, але краще до такого не доводити.
Дотичне
- Як думає Odoo — як такі ланцюги взагалі моделюються
- Sales — глибше про SO
- Inventory — глибше про picking
- Accounting — глибше про invoice і payment
- ПН/РК — вклинення ПН у ланцюг
Де цей модуль зустрічається7
- OdooЯк думає OdooАрхітектурна основа: ORM, models, fields, views, actions, security. Без цього кастомізація — шаманство.
- OdooAccountingЯк Odoo веде подвійний запис: account.move як універсальний документ-проводка, журнали як шафи, account.move.l…
- OdooAnalytic AccountЧому аналітика окрема від плану рахунків і як нею вести проєктний облік оборонних контрактів, рахувати маржу з…
- OdooInventoryФізичні і логічні рухи запасів: stock.move як атом обліку, stock.quant як зріз наявності, anglo-saxon як основ…
- OdooMRPВиробниче замовлення (MO) як центр виробничого обліку: списання матеріалів, операції, оприбуткування готової п…
- OdooPurchaseЗамовлення постачальнику — джерело надходжень, рахунків від постачальника і вхідного ПДВ.
- OdooSalesЗамовлення покупця як точка входу в Odoo — звідси розгортається ланцюг постачання, виробництва і платежу.