Kdo v Odoo fakturuje ze zakázek, tiketů helpdesku a evidence času, zná výsledek: zákazník na konci měsíce nedostane jednu fakturu, ale pět. Každá pochází z jiné zakázky nebo tiketu, každá je věcně správná – a přesto se zákazník ptá, zda by se to nevešlo do jednoho dokladu.
Právě pro to v Odoo 19 Community neexistuje čisté řešení. Známý modul OCA account_invoice_merge zatím není pro Odoo 19 uvolněn a řeší problém způsobem, který pro poskytovatele služeb představuje vážné riziko: stejné položky se sloučí, a tím se ztratí vazba zpět na zakázku, úkol a zaznamenaný čas.
Pro naši vlastní instalaci Odoo 19 Community jsme proto vyvinuli modul Merge Draft Invoices. Protože se tento problém netýká jen nás, poskytujeme jej zdarma na GitHubu pod svobodnou licencí AGPL-3.
K projektu na GitHubu:
Proč vlastní modul místo řešení OCA?
OCA udržuje account_invoice_merge v repozitáři OCA/account-invoicing již mnoho let. V době vzniku tohoto příspěvku je migrace na verzi 19.0 stále otevřeným pull requestem, nikoli zveřejněným modulem.
Důležitější však je, že oba moduly dělají různé věci:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Výsledek | sloučí se do jedné z vybraných faktur | vždy nová faktura |
| Položky | stejné položky se sloučí, množství se sečtou | každá položka se převezme jedna k jedné |
| Původ | invoice_origin / payment_reference v hlavičce spojeny dohromady | pole původu u každé položky zkopírována pomocí vlastního hooku Odoo _copy_data_extend_business_fields |
| Evidence času | nezohledňuje se | timesheet_invoice_id se přepojí na novou fakturu |
| Čitelné pro zákazníka | nic | nadpis nad položkami každého zdroje se zakázkou a tiketem |
| Zdrojové faktury | jsou stornovány | stornovat (výchozí), smazat nebo ponechat – s jasně pojmenovanými důsledky |
Slučování stejných položek je rozhodující rozdíl. Sloučená položka může mít jen jeden původ. Jakmile dva řádky ze dvou různých zakázek vypadají stejně, je vazba z faktury zpět na zakázku, úkol a časový záznam pryč. V poskytovatelském provozu, kde fakturace probíhá přes tiket → evidence času → faktura, to znamená: tytéž hodiny se buď fakturují dvakrát, nebo zmizí.
Náš modul proto zásadně nic neslučuje. Sloučená faktura má přesně tolik položek, kolik mají dohromady její zdroje.
Z modulu OCA nebyl převzat žádný kód. Oba moduly jsou pod licencí AGPL-3.
Co modul dělá
V seznamu faktur označit dva nebo více konceptů, v nabídce akcí zvolit Sloučit koncepty faktur – asistent zobrazí výběr, kontroly a možnosti pro zdrojové faktury. Po potvrzení se uživatel ocitne přímo na nové faktuře.
Před sloučením modul provede kontrolu, a to všech bodů najednou, aby uživatel neviděl po každé opravě další chybu:
- vybrány alespoň dvě faktury
- všechny ve stavu Koncept
- žádná nebyla nikdy zaúčtována (doklad vrácený do konceptu si zachovává historii účtování a je odmítnut)
- žádná nebyla již sloučena
- všechny jsou faktura, faktura dodavatele, dobropis nebo doklad – žádné čisté účetní zápisy
- stejný partner, typ dokladu, společnost, měna a deník
Rozdíly v platebních podmínkách, daňovém zařazení, dodací adrese, bankovním účtu nebo obchodníkovi neblokují. Asistent upozorní a převezme hodnotu nejstarší vybrané faktury.
Co zůstane zachováno
Jádrem modulu je, že každá vazba, kterou má fakturační položka na svůj původ, existuje i na nové faktuře:
| Propojení | Zpracování |
|---|---|
| Položky prodejní objednávky (sale_line_ids) | u každé položky zkopírováno pomocí _copy_data_extend_business_fields z Odoo |
| Položky nákupní objednávky (purchase_line_id) | stejně |
| Rozdělení nákladových středisek (analytic_distribution) | zkopírováno u každé položky |
| další pole původu u každé položky | automaticky, pokud dodávající modul implementuje zdokumentovaný hook Odoo |
| Původ a reference v hlavičce faktury | sloučeno, deduplikováno, upravitelné v asistentovi |
| Prodejní objednávka: invoice_ids, invoice_status, qty_invoiced | počítají se z položek – objednávka novou fakturu pozná sama |
| Evidence času (timesheet_invoice_id) | se přepojí na novou fakturu dříve, než zdroje ztratí platnost |
| Tiket helpdesku | je navázán na evidenci času, a ukazuje tak automaticky na novou fakturu |
| Chatter | zpráva na nové faktuře se všemi zdroji, zpráva na každém zdroji s odkazem na novou |
| merged_into_move_id / merged_from_move_ids | uložené propojení v obou směrech |
Daňové řádky, řádky platebních podmínek, zaokrouhlovací řádky a řádky skonta se nekopírují, ale Odoo je znovu vypočítá z obsahu. Sloučená faktura má tedy jeden daňový řádek na každou daňovou sazbu, nikoli jeden na každý zdroj. Vytvoří se bez čísla jako koncept; samotné sloučení nespotřebuje žádné číslo dokladu.
Nadpisy: zákazník vidí, odkud každá položka pochází
Toto je funkce, která v každodenním provozu dělá rozdíl. Nad položkami každé zdrojové faktury modul vloží řádek sekce – tentýž prvek, který Odoo samo používá pro sekci „Zálohy“:
BE10181 <- Nadpis
22,500 x Hodina technika 3.577,50
Mezisoučet 3.577,50
BE10182 · Ticket: HT00219 <- Nadpis
3,000 x Hodina technika 477,00
Mezisoučet 477,00
ČISTÁ ČÁSTKA 4.054,50
Nadpis se vytiskne na dokladu, který zákazník dostane, a standardní report faktury Odoo sám doplní mezisoučet za každou sekci. Zákazník tak vidí i to, kolik stála každá část.
Popisek se sestavuje z toho, co zdroj skutečně má:
- zakázky, přímo z vazeb na položky – názvy jsou tak přesné; pokud vazby na zakázky chybějí, použije se volný text z invoice_origin
- tikety, zjištěné přes evidenci času – zobrazí se jako Ticket: HT00219 nebo Tickets: …, omezeno na pět plus „a N dalších“
- pokud není ani jedno: číslo zdrojové faktury, poté její reference
- pokud není vůbec nic: neutrální „Další položky“, aby řádky jednoho zdroje nikdy mlčky nezmizely v bloku nad nimi
Co chybí, se vynechá, nevymýšlí se. Tiket se tiskne s vlastním číslem – číslo helpdesk_mgmt, ticket_ref z Enterprise nebo zobrazovaný název modelu – nikdy s ID databáze.
Nadpisy se píší v jazyce zákazníka, stejně jako to dělá Odoo při vytváření faktur ze zakázek. Chatter zůstává v jazyce uživatele.
Nadpis nenese žádný produkt, množství, cenu, daň, účet ani rozdělení nákladových středisek. Nemůže ovlivnit součty, základ daně ani nákladové účetnictví. Kdo nadpisy v asistentovi vypne, dostane přesně tu fakturu, která by vznikla bez této funkce – test porovnává oba výsledky na halíř.
Co se stane se zdrojovými fakturami
Asistent nabízí tři možnosti:
- Stornovat (výchozí, doporučeno). Storno konceptu je bez následků: žádné číslo dokladu, žádné zaúčtování. Faktury zůstávají v databázi jako stornované doklady s chatterem a odkazem na novou fakturu. qty_invoiced na prodejní objednávce stornované doklady přeskakuje, nic se nepočítá dvakrát. Je to možnost, která zůstane dohledatelná i za půl roku.
- Smazat. Nenávratně. Chatter a zpětný odkaz zanikají se záznamem; zůstává jen zpráva na nové faktuře. Vhodné pro zahazovací koncepty, které už nikdo nemá vidět.
- Ponechat jako koncept. Zdroje zůstanou otevřené. Jejich prodejní objednávky pak počítají zdroj i novou fakturu jako vyfakturované – práce vypadá jako dvakrát vyúčtovaná, dokud někdo koncepty ručně neuklidí. Určeno pro vzácný případ, kdy chce uživatel před rozhodnutím porovnávat. Test zachycuje právě toto dvojí započtení, aby nikoho nepřekvapilo.
Hranice – co modul záměrně nedělá
Přečtěte si prosím před produkčním nasazením:
- Pouze koncepty. Zaúčtované faktury se neslučují. To je úloha pro dobropisy, nikoli kopírování.
- Jednou zaúčtované, vrácené koncepty se odmítají. Mají číslo a historii účtování; skrývat to v jiném dokladu není nic, co by měla hromadná akce vykonávat tiše.
- Žádné sloučení napříč partnery, měnami, společnostmi nebo deníky. Pro to neexistuje smysluplná jednotná hodnota, proto modul odmítne, místo aby hádal.
- payment_reference se nepřebírá. Strukturované platební reference identifikují jeden doklad; dvě spojené do sebe nesedí na nic v bance. Nová faktura dostane při zaúčtování vlastní.
- Položky záloh se kopírují jako každá jiná položka. Pokud zdroje obsahují řádky záloh ze sale, je třeba výsledek před zaúčtováním zkontrolovat.
- Automaticky se přepojuje pouze timesheet_invoice_id. Každý další model s Many2one na account.move (vlastní fakturační běhy, vazby na předplatná, předávání EDI) ukazuje nadále na zdroj. Pro to existuje hook _merge_draft_relink.
- Žádné slučování stejných položek – záměrně. Kdo chce krátkou, přehlednou fakturu, je tu na špatném místě a zaplatil by za to vazbami na původ.
- Nadpisy vznikají jednorázově při sloučení. Poté jsou to běžné řádky sekcí: lze je upravovat, mazat a neaktualizují se, pokud se zakázka nebo tiket později přejmenují.
- Nadpis uvádí tikety celé zdrojové faktury, nikoli jednotlivé položky.
Instalace v Odoo 19 Community
Instalace probíhá jako u běžného doplňkového modulu Odoo:
- Zkopírovat adresář modulu do nakonfigurované cesty addons.
- Aktualizovat seznam aplikací v Odoo.
- Nainstalovat modul account_invoice_merge_draft.
Modul závisí pouze na account. Vše, co ví o sale, purchase, project, hr_timesheet, sale_timesheet nebo modulu helpdesku, se zjišťuje za běhu – instaluje se a funguje i na čisté fakturační databázi.
Poté: Účetnictví → Zákazníci → Faktury (nebo Dodavatelé → Faktury), označit dva nebo více konceptů, menu s ozubeným kolem → Sloučit koncepty faktur. Akce je k dispozici všem uživatelům se skupinou Fakturace.
Kdo aktualizuje z dřívější verze modulu, měl by jednorázově provést update s -u account_invoice_merge_draft --i18n-overwrite, aby se změněné názvy polí dostaly do německého překladu. Nová instalace se tím netýká.
Pro vývojáře: API a hooky
Sloučení lze volat i 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" nebo "keep"
add_source_sections=True, # nadpisy; výchozí True
)
merge_draft_moves vyvolá UserError se všemi důvody odmítnutí, pokud výběr nelze sloučit. _merge_draft_get_errors() vrací tytéž důvody jako seznam bez výjimky.
Kdo má vlastní zpětné odkazy na account.move, přepíš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}
)
Kdo má zpětný odkaz na fakturační položce, tento modul vůbec nepotřebuje: stačí vlastní hook Odoo account.move.line._copy_data_extend_business_fields a pole se přebírá stejně při sloučení, stornování i duplikaci. Pro jiné moduly helpdesku nebo jinou formulaci nadpisů existují _merge_draft_get_source_tickets() a _merge_draft_section_name().
Otestováno dříve, než poběží na produkčních datech
48 testů pokrývá smíšené stavy, různé partnery, měny a typy dokladů, položky s prodejní nebo nákupní objednávkou i bez ní, daně, sekce a poznámky, rozdělení nákladových středisek, přepojení evidence času, všechny tři možnosti pro zdrojové faktury, asistenta a nadpisy – že jsou standardně zapnuté, že jejich vypnutí reprodukuje starý výsledek na halíř, že nadpis nenese žádné částky ani účty, že položky jednoho zdroje zůstávají pohromadě, že se tiket zobrazuje s vlastním číslem a nikdy s ID databáze.
Testovací data vycházejí z existující účtové osnovy aktivní společnosti, nikoli z AccountTestInvoicingCommon – tento helper na migrovaných produkčních databázích často nenastartuje, a právě na takových má modul běžet.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Jako u každého rozšíření v oblasti účetnictví doporučujeme instalaci nejprve vyzkoušet v testovacím prostředí s kopií vlastní databáze.
Zdarma a open source pro komunitu Odoo
My ve společnosti Biricon IT Services GmbH vyvíjíme řešení nejprve pro konkrétní požadavky z našeho vlastního provozu. Pokud z toho vznikne obecně použitelný modul, nechceme jej ukrývat v jediné instalaci.
Merge Draft Invoices je proto k dispozici zdarma jako open-source projekt. Zdrojový kód lze prohlížet, kontrolovat, upravovat a v rámci licence AGPL-3 dále rozvíjet.
Naším cílem nebyla nejkratší možná faktura, ale faktura, u které i za rok každá položka vede zpět ke své zakázce, svému tiketu a svému časovému záznamu.
Projekt, zdrojový kód a spolupráce
Kompletní zdrojový kód, pokyny k instalaci a technická dokumentace jsou k dispozici na GitHubu:
Otevřít Odoo 19 Merge Draft Invoices na GitHubu
Těšíme se na zkušenosti z praxe, hlášení chyb, návrhy na vylepšení a příspěvky od komunity Odoo.
Zdarma. Dohledatelné. Open source. Pro Odoo 19 Community.