Kto v Odoo fakturuje zo zákaziek, tiketov helpdesku a evidencie času, pozná výsledok: Zákazník na konci mesiaca nedostane jednu faktúru, ale päť. Každá pochádza z inej zákazky alebo tiketu, každá je vecne správna – a napriek tomu sa zákazník pýta, či by sa to nedalo zmestiť do jedného dokladu.
Práve na to neexistuje v Odoo 19 Community čisté riešenie. Známy modul OCA account_invoice_merge zatiaľ nie je pre Odoo 19 uvoľnený a rieši problém spôsobom, ktorý pre poskytovateľov služieb predstavuje vážne riziko: Rovnaké položky sa zlúčia a tým sa stratí väzba späť na zákazku, úlohu a zaevidovaný čas.
Pre našu vlastnú inštaláciu Odoo 19 Community sme preto vyvinuli modul Merge Draft Invoices. Keďže tento problém netrápi len nás, poskytujeme ho bezplatne na GitHube pod slobodnou licenciou AGPL-3.
Prejsť na projekt na GitHube:
Prečo vlastný modul namiesto riešenia OCA?
OCA už mnoho rokov udržiava account_invoice_merge v repozitári OCA/account-invoicing. V čase vzniku tohto príspevku je migrácia na 19.0 tam stále otvoreným pull requestom, nie zverejneným modulom.
Dôležitejšie však je, že oba moduly robia odlišné veci:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Výsledok | zlúči sa do jednej z vybraných faktúr | vždy nová faktúra |
| Položky | rovnaké položky sa zlúčia, množstvá sa sčítajú | každá položka sa prevezme jedna k jednej |
| Pôvod | invoice_origin / payment_reference sa v hlavičke spoja | polia pôvodu sa pre každú položku skopírujú cez vlastný hook Odoo _copy_data_extend_business_fields |
| Evidencia času | nezohľadňuje sa | timesheet_invoice_id sa prepojí na novú faktúru |
| Čitateľné pre zákazníka | nič | nadpis nad položkami každého zdroja so zákazkou a tiketom |
| Zdrojové faktúry | sa stornujú | stornovať (štandard), zmazať alebo ponechať – s jasne pomenovanými dôsledkami |
Zlučovanie rovnakých položiek je rozhodujúci rozdiel. Zlúčená položka môže mať len jeden pôvod. Hneď ako dva riadky z dvoch rôznych zákaziek vyzerajú rovnako, je väzba od faktúry späť na zákazku, úlohu a časový záznam preč. V prevádzke poskytovateľa služieb, kde fakturácia prebieha cez tiket → evidencia času → faktúra, to znamená: Tie isté hodiny sa buď fakturujú dvakrát, alebo zmiznú.
Náš modul preto v zásade nič nezlučuje. Zlúčená faktúra má presne toľko položiek, koľko majú jej zdroje spolu.
Z modulu OCA sa neprevzal žiadny kód. Oba moduly sú pod AGPL-3.
Čo modul robí
V zobrazení zoznamu faktúr označiť dva alebo viac konceptov, v menu akcií zvoliť Zlúčiť koncepty faktúr – sprievodca zobrazí výber, kontroly a možnosti pre zdrojové faktúry. Po potvrdení sa používateľ ocitne priamo na novej faktúre.
Pred zlúčením modul kontroluje, a to všetky body naraz, aby človek po každej oprave nevidel ďalšiu chybu:
- vybrané aspoň dve faktúry
- všetky v stave Koncept
- žiadna nebola nikdy zaúčtovaná (doklad vrátený do konceptu si ponecháva históriu zaúčtovania a je zamietnutý)
- žiadna nebola už zlúčená
- všetky sú faktúra, dodávateľská faktúra, dobropis alebo doklad – nie čisto účtovné zápisy
- rovnaký partner, typ dokladu, spoločnosť, mena a denník
Rozdiely v platobných podmienkach, daňovom zaradení, dodacej adrese, bankovom účte alebo predajcovi neblokujú. Sprievodca upozorní a prevezme hodnotu z najstaršej vybranej faktúry.
Čo sa zachová
Jadrom modulu je, že každá väzba, ktorú má položka faktúry na svoj pôvod, existuje aj na novej faktúre:
| Prepojenie | Spracovanie |
|---|---|
| Položky predajnej objednávky (sale_line_ids) | pre každú položku skopírované cez _copy_data_extend_business_fields z Odoo |
| Položky nákupnej objednávky (purchase_line_id) | rovnako |
| Rozdelenie nákladových stredísk (analytic_distribution) | skopírované pre každú položku |
| ďalšie polia pôvodu pri jednotlivých položkách | automaticky, ak dodávajúci modul implementuje zdokumentovaný hook Odoo |
| Pôvod a referencia v hlavičke faktúry | zlúčené, deduplikované, upraviteľné v sprievodcovi |
| Predajná objednávka: invoice_ids, invoice_status, qty_invoiced | vypočítajú sa z položiek – objednávka novú faktúru rozpozná sama |
| Evidencia času (timesheet_invoice_id) | prepojí sa na novú faktúru skôr, než zdroje prestanú platiť |
| Tiket helpdesku | je naviazaný na evidenciu času a tým automaticky odkazuje na novú faktúru |
| Chatter | správa na novej faktúre so všetkými zdrojmi, správa na každom zdroji s odkazom na novú |
| merged_into_move_id / merged_from_move_ids | uložené prepojenie v oboch smeroch |
Daňové riadky, riadky platobných podmienok, riadky zaokrúhlenia a zliav za skoré zaplatenie sa nekopírujú, ale Odoo ich prepočíta nanovo z obsahu. Zlúčená faktúra má teda jeden daňový riadok na sadzbu dane, nie jeden na zdroj. Vytvorí sa bez čísla ako koncept; samotné zlúčenie nespotrebuje žiadne číslo dokladu.
Nadpisy: Zákazník vidí, odkiaľ každá položka pochádza
Toto je funkcia, ktorá robí v každodennej praxi rozdiel. Nad položkami každej zdrojovej faktúry vloží modul riadok sekcie – ten istý prvok, ktorý Odoo samo používa pre sekciu „Zálohy“:
BE10181 <- nadpis
22,500 x Hodina technika 3.577,50
Medzisúčet 3.577,50
BE10182 · Tiket: HT00219 <- nadpis
3,000 x Hodina technika 477,00
Medzisúčet 477,00
ČISTÁ SUMA 4.054,50
Nadpis sa vytlačí na doklade, ktorý zákazník dostane, a štandardná správa faktúry Odoo sama doplní medzisúčet za každú sekciu. Zákazník tak vidí aj to, koľko stála každá časť.
Popis sa zostavuje z toho, čo zdroj skutočne má:
- zákazky, priamo z prepojení položiek – názvy sú tak presné; ak chýbajú prepojenia na zákazky, použije sa voľný text z invoice_origin
- tikety, zistené cez evidenciu času – uvedené ako Tiket: HT00219 alebo Tikety: …, obmedzené na päť plus „a N ďalších“
- ak neexistuje ani jedno: číslo zdrojovej faktúry, potom jej referencia
- ak neexistuje vôbec nič: neutrálne „Ďalšie položky“, aby riadky zdroja nikdy potichu nezmizli v bloku nad nimi
Čo chýba, sa vynechá, nevymyslí sa. Tiket sa vytlačí s vlastným číslom – číslo helpdesk_mgmt, ticket_ref z Enterprise alebo zobrazovaný názov modelu – nikdy nie s ID z databázy.
Nadpisy sa píšu v jazyku zákazníka, tak ako to robí Odoo pri vytváraní faktúr zo zákaziek. Chatter zostáva v jazyku používateľa.
Nadpis nenesie žiadny produkt, množstvo, cenu, daň, účet ani rozdelenie nákladových stredísk. Nemôže ovplyvniť súčty, základ dane ani nákladové účtovníctvo. Kto nadpisy v sprievodcovi vypne, dostane presne takú faktúru, aká by vznikla bez tejto funkcie – test porovnáva oba výsledky s presnosťou na cent.
Čo sa stane so zdrojovými faktúrami
Sprievodca ponúka tri možnosti:
- Stornovať (štandard, odporúčané). Stornovanie konceptu je bez následkov: žiadne číslo dokladu, žiadne zaúčtovanie. Faktúry zostanú v databáze ako stornované doklady s chatterom a odkazom na novú faktúru. qty_invoiced pri predajnej objednávke stornované doklady preskakuje, nič sa nezapočíta dvakrát. Je to možnosť, ktorá je dohľadateľná aj o šesť mesiacov.
- Zmazať. Nenávratne. Chatter a spätný odkaz zaniknú spolu so záznamom; zostane len správa na novej faktúre. Zmysluplné pre jednorazové koncepty, ktoré už nikto nemá vidieť.
- Ponechať ako koncept. Zdroje zostanú otvorené. Ich predajné objednávky potom počítajú zdroj aj novú faktúru ako vyfakturované – práca vyzerá ako dvakrát vyúčtovaná, kým niekto koncepty ručne nevyčistí. Určené pre zriedkavý prípad, keď chce človek pred rozhodnutím porovnávať. Test zachytáva práve toto dvojité započítanie, aby nikoho neprekvapilo.
Hranice – čo modul zámerne nerobí
Prosím, prečítajte si pred produkčným nasadením:
- Len koncepty. Zaúčtované faktúry sa nezlučujú. To je úloha pre dobropisy, nie kopírovací proces.
- Raz zaúčtované, vrátené koncepty sa zamietajú. Majú číslo a históriu zaúčtovania; schovať to v inom doklade nie je vec, ktorú by mala potichu vybaviť hromadná akcia.
- Žiadne zlučovanie cez partnerov, meny, spoločnosti alebo denníky. Na to neexistuje zmysluplná jednotlivá hodnota, preto modul odmietne namiesto toho, aby hádal.
- payment_reference sa neprevezme. Štruktúrované platobné referencie identifikujú doklad; dve spojené nezodpovedajú v banke ničomu. Nová faktúra dostane pri zaúčtovaní vlastnú.
- Položky záloh sa kopírujú ako každá iná položka. Ak zdroje obsahujú riadky záloh zo sale, treba výsledok pred zaúčtovaním skontrolovať.
- Automaticky sa prepája iba timesheet_invoice_id. Každý ďalší model s Many2one na account.move (vlastné fakturačné behy, prepojenia predplatného, odoslania EDI) naďalej ukazuje na zdroj. Na to slúži hook _merge_draft_relink.
- Žiadne zlučovanie rovnakých položiek – zámerne. Kto chce krátku, upratanú faktúru, je tu na zlej adrese a zaplatil by za to väzbami na pôvod.
- Nadpisy vznikajú jednorazovo pri zlúčení. Potom sú to bežné riadky sekcií: upraviteľné, zmazateľné a neaktualizujú sa, ak sa zákazka alebo tiket neskôr premenuje.
- Nadpis uvádza tikety celej zdrojovej faktúry, nie jednotlivej položky.
Inštalácia v Odoo 19 Community
Inštalácia prebieha ako pri bežnom doplnkovom module Odoo:
- Skopírovať adresár modulu do nakonfigurovanej cesty addonov.
- Aktualizovať zoznam aplikácií v Odoo.
- Nainštalovať modul account_invoice_merge_draft.
Modul závisí len od account. Všetko, čo vie o sale, purchase, project, hr_timesheet, sale_timesheet alebo module helpdesku, sa zisťuje za behu – inštaluje sa a funguje aj na čisto fakturačnej databáze.
Potom: Účtovníctvo → Zákazníci → Faktúry (alebo Dodávatelia → Faktúry), označiť dva alebo viac konceptov, menu ozubeného kolesa → Zlúčiť koncepty faktúr. Akcia je k dispozícii všetkým používateľom so skupinou Vyúčtovanie / Fakturácia.
Kto aktualizuje z predchádzajúcej verzie modulu, mal by jednorazovo aktualizovať pomocou -u account_invoice_merge_draft --i18n-overwrite, aby sa zmenené označenia polí dostali do nemeckého prekladu. Nová inštalácia sa to netýka.
Pre vývojárov: API a hooky
Zlúčenie možno vyvolať aj z vlastného kódu:
python
merged = draft_moves.merge_draft_moves(
extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
source_handling="cancel", # "delete" alebo "keep"
add_source_sections=True, # nadpisy; štandardne True
)
merge_draft_moves vyhodí UserError so všetkými dôvodmi zamietnutia, ak sa výber nedá zlúčiť. _merge_draft_get_errors() vráti rovnaké dôvody ako zoznam bez výnimky.
Kto má vlastné spätné odkazy na account.move, prepíše _merge_draft_relink:
python
class AccountMove(models.Model):
_inherit = "account.move"
def _merge_draft_relink(self, new_move):
super()._merge_draft_relink(new_move)
if "my_move_id" not in self.env["my.model"]._fields:
return
self.env["my.model"].search([("my_move_id", "in", self.ids)]).write(
{"my_move_id": new_move.id}
)
Kto má spätný odkaz na položke faktúry, tento modul vôbec nepotrebuje: stačí vlastný hook Odoo account.move.line._copy_data_extend_business_fields a pole sa prevezme rovnako pri zlúčení, pri storne aj pri duplikovaní. Pre iné moduly helpdesku alebo inú formuláciu nadpisov existujú _merge_draft_get_source_tickets() a _merge_draft_section_name().
Otestované ešte predtým, než beží na produkčných údajoch
48 testov pokrýva zmiešané stavy, rôznych partnerov, meny a typy dokladov, položky s predajnou či nákupnou objednávkou aj bez nej, dane, sekcie a poznámky, rozdelenia nákladových stredísk, prepojenie evidencie času, všetky tri možnosti pre zdrojové faktúry, sprievodcu a nadpisy – že sú predvolene aktívne, že ich vypnutie reprodukuje starý výsledok s presnosťou na cent, že nadpis nenesie sumy ani účty, že položky jedného zdroja zostanú spolu, že sa tiket zobrazuje so svojím číslom a nikdy s ID z databázy.
Testovacie údaje stavajú na existujúcej účtovej osnove aktívnej firmy namiesto na AccountTestInvoicingCommon – tento helper sa na migrovaných produkčných databázach často nespustí, a práve na takých má modul bežať.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Ako pri každom rozšírení v oblasti účtovníctva odporúčame inštaláciu najprv otestovať v testovacom prostredí s kópiou vlastnej databázy.
Bezplatne a open source pre komunitu Odoo
My v Biricon IT Services GmbH vyvíjame riešenia najprv pre konkrétne požiadavky z našej vlastnej prevádzky. Ak z toho vznikne všeobecne použiteľný modul, nechceme ho skrývať v jednej inštalácii.
Merge Draft Invoices je preto k dispozícii bezplatne ako open source projekt. Zdrojový kód možno prezerať, kontrolovať, upravovať a v rámci licencie AGPL-3 ďalej rozvíjať.
Naším cieľom nebola čo najkratšia faktúra, ale faktúra, pri ktorej aj o rok každá položka vedie späť k svojej zákazke, svojmu tiketu a svojmu časovému záznamu.
Projekt, zdrojový kód a spolupráca
Kompletný zdrojový kód, pokyny na inštaláciu a technická dokumentácia sú dostupné na GitHube:
Otvoriť Odoo 19 Merge Draft Invoices na GitHube
Tešíme sa na skúsenosti z praxe, hlásenia chýb, návrhy na zlepšenie a príspevky z komunity Odoo.
Bezplatne. Dohľadateľne. Open source. Pre Odoo 19 Community.