Overslaan naar inhoud

Odoo 19 Community: conceptfacturen samenvoegen – onze open-sourcemodule Merge Draft Invoices

18 september 2026 in
Odoo 19 Community: conceptfacturen samenvoegen – onze open-sourcemodule Merge Draft Invoices
Alexander Biringer | Biricon IT Services GmbH

Wie in Odoo factureert op basis van orders, helpdesktickets en urenregistratie, kent het resultaat: een klant krijgt aan het einde van de maand niet één factuur, maar vijf. Elke factuur komt uit een andere order of een ander ticket, elke is inhoudelijk correct – en toch vraagt de klant of dat niet op één document zou kunnen.

Juist daarvoor is er in Odoo 19 Community geen nette oplossing. De bekende OCA-module account_invoice_merge is voor Odoo 19 nog niet vrijgegeven, en lost het probleem op een manier op die voor dienstverleners een serieus risico vormt: gelijke regels worden samengevoegd, en daarmee gaat de koppeling terug naar de order, de taak en de geregistreerde tijd verloren.

Voor onze eigen Odoo 19 Community-installatie hebben we daarom de module Merge Draft Invoices ontwikkeld. Omdat dit probleem niet alleen ons treft, stellen we haar gratis beschikbaar op GitHub onder de vrije AGPL-3-licentie.

Naar het project op GitHub:

Odoo 19 Merge Draft Invoices

Waarom een eigen module in plaats van de OCA-oplossing?

De OCA onderhoudt al vele jaren account_invoice_merge in de repository OCA/account-invoicing. Ten tijde van dit bericht is de migratie naar 19.0 daar nog een openstaande pull request, geen gepubliceerde module.

Belangrijker is echter dat beide modules verschillende dingen doen:


OCA account_invoice_mergeMerge Draft Invoices
Resultaatwordt samengevoegd in een van de geselecteerde facturenaltijd een nieuwe factuur
Regelsgelijke regels worden samengevoegd, hoeveelheden opgeteldelke regel wordt één op één overgenomen
Herkomstinvoice_origin / payment_reference in de kop aan elkaar geplaktherkomstvelden per regel gekopieerd via Odoo's eigen _copy_data_extend_business_fields-hook
Urenregistratieniet meegenomentimesheet_invoice_id wordt omgehangen naar de nieuwe factuur
Leesbaar voor de klantnietseen kop boven de regels van elke bron met order en ticket
Bronfacturenworden geannuleerdannuleren (standaard), verwijderen of behouden – met duidelijk benoemde gevolgen

Het samenvoegen van gelijke regels is het doorslaggevende verschil. Een samengevoegde regel kan maar één herkomst dragen. Zodra twee regels uit twee verschillende orders er hetzelfde uitzien, is de koppeling van de factuur terug naar de order, de taak en de tijdboeking weg. In een dienstverlenend bedrijf waar de facturering verloopt via ticket → urenregistratie → factuur, betekent dat: dezelfde uren worden dubbel gefactureerd of verdwijnen.

Onze module voegt daarom in principe niets samen. De samengevoegde factuur heeft precies evenveel regels als haar bronnen samen.

Er is geen code uit de OCA-module overgenomen. Beide modules vallen onder AGPL-3.

Wat de module doet

In de factuurlijstweergave twee of meer concepten markeren, in het actiemenu Conceptfacturen samenvoegen kiezen – een assistent toont de selectie, de controles en de opties voor de bronfacturen. Na bevestiging komt men direct op de nieuwe factuur terecht.

Vóór het samenvoegen controleert de module, en wel alle punten in één keer, zodat men niet na elke correctie de volgende fout te zien krijgt:

  • minstens twee facturen geselecteerd
  • alle in de status Concept
  • geen enkele is ooit geboekt (een naar concept teruggezet document behoudt zijn boekingsgeschiedenis en wordt geweigerd)
  • geen enkele is al samengevoegd
  • alle zijn factuur, inkoopfactuur, creditnota of bon – geen zuivere boekingen
  • dezelfde partner, documenttype, bedrijf, valuta en journaal

Verschillen in betalingsvoorwaarde, belastingtoewijzing, afleveradres, bankrekening of verkoper blokkeren niet. De assistent waarschuwt en neemt de waarde van de oudste geselecteerde factuur over.

Wat behouden blijft

De kern van de module is dat elke verbinding die een factuurregel met haar herkomst heeft, ook op de nieuwe factuur bestaat:

KoppelingBehandeling
Verkooporderregels (sale_line_ids)per regel gekopieerd via Odoo's _copy_data_extend_business_fields
Inkooporderregels (purchase_line_id)eveneens
Kostenplaatsverdeling (analytic_distribution)per regel gekopieerd
andere herkomstvelden per regelautomatisch, als de leverende module de gedocumenteerde Odoo-hook implementeert
Herkomst en referentie in de factuurkopsamengevoegd, gededupliceerd, bewerkbaar in de assistent
Verkooporder: invoice_ids, invoice_status, qty_invoicedworden berekend uit de regels – de order herkent de nieuwe factuur vanzelf
Urenregistratie (timesheet_invoice_id)wordt omgehangen naar de nieuwe factuur voordat de bronnen ongeldig worden
Helpdesktickethangt aan de urenregistratie en wijst daarmee automatisch naar de nieuwe factuur
Chatterbericht op de nieuwe factuur met alle bronnen, bericht op elke bron met link naar de nieuwe
merged_into_move_id / merged_from_move_idsopgeslagen koppeling in beide richtingen

Belastingregels, betalingsvoorwaarderegels, afrondings- en kortingsregels (korting bij contante betaling) worden niet gekopieerd, maar door Odoo opnieuw berekend uit de inhoud. De samengevoegde factuur heeft dus één belastingregel per belastingtarief, niet één per bron. Ze wordt zonder nummer als concept aangemaakt; het samenvoegen zelf verbruikt geen documentnummer.

Koppen: de klant ziet waar elke regel vandaan komt

Dit is de functie die in het dagelijks gebruik het verschil maakt. Boven de regels van elke bronfactuur plaatst de module een sectieregel – hetzelfde element dat Odoo zelf gebruikt voor de sectie “Aanbetalingen”:

BE10181                                     <- kop
  22,500 x Technicusuur           3.577,50
                    Subtotaal     3.577,50
BE10182 · Ticket: HT00219                   <- kop
   3,000 x Technicusuur             477,00
                    Subtotaal       477,00
                      NETTOBEDRAG 4.054,50

De kop wordt afgedrukt op het document dat de klant ontvangt, en Odoo's standaardfactuurrapport voegt uit zichzelf een subtotaal per sectie toe. De klant ziet daarmee ook wat elk deel heeft gekost.

Het opschrift wordt samengesteld uit wat de bron daadwerkelijk heeft:

  1. de orders, rechtstreeks uit de regelkoppelingen – de namen zijn daarmee exact; ontbreken orderkoppelingen, dan wordt de vrije tekst uit invoice_origin gebruikt
  2. de tickets, bepaald via de urenregistratie – weergegeven als Ticket: HT00219 of Tickets: …, beperkt tot vijf plus “en N andere”
  3. is er geen van beide: het nummer van de bronfactuur, daarna haar referentie
  4. is er helemaal niets: een neutrale “Overige regels”, zodat de regels van een bron nooit stilzwijgend in het blok erboven verdwijnen

Wat ontbreekt, wordt weggelaten, niet verzonnen. Het ticket wordt afgedrukt met zijn eigen nummer – helpdesk_mgmt-nummer, Enterprise-ticket_ref of de weergavenaam van het model – nooit met de database-ID.

De koppen worden geschreven in de taal van de klant, zoals Odoo dat doet bij het aanmaken van facturen uit orders. De chatter blijft in de taal van de gebruiker.

Een kop draagt geen product, geen hoeveelheid, geen prijs, geen belasting, geen rekening en geen kostenplaatsverdeling. Ze kan totalen, belastinggrondslag en kostprijsberekening niet beïnvloeden. Wie de koppen in de assistent uitschakelt, krijgt exact de factuur die zonder deze functie zou zijn ontstaan – een test vergelijkt beide resultaten tot op de cent.

Wat er met de bronfacturen gebeurt

De assistent biedt drie opties:

  • Annuleren (standaard, aanbevolen). Het annuleren van een concept heeft geen gevolgen: geen documentnummer, geen boeking. De facturen blijven als geannuleerde documenten met chatter en link naar de nieuwe factuur in de database staan. qty_invoiced bij de verkooporder slaat geannuleerde documenten over, niets wordt dubbel geteld. Dit is de optie die ook over zes maanden nog navolgbaar is.
  • Verwijderen. Onherroepelijk. Chatter en terugverwijzing verdwijnen met het record; alleen het bericht op de nieuwe factuur blijft. Zinvol voor wegwerpconcepten die niemand meer hoeft te zien.
  • Als concept laten staan. De bronnen blijven open. Hun verkooporders tellen dan bron en nieuwe factuur als gefactureerd – het werk ziet er dubbel gefactureerd uit tot iemand de concepten met de hand opruimt. Bedoeld voor het zeldzame geval dat men vóór de beslissing wil vergelijken. Een test legt precies deze dubbeltelling vast, zodat ze niemand verrast.

Grenzen – wat de module bewust niet doet

Lees dit vóór het productieve gebruik:

  • Alleen concepten. Geboekte facturen worden niet samengevoegd. Dat is een creditnota-taak, geen kopieerhandeling.
  • Eenmaal geboekte, teruggezette concepten worden geweigerd. Ze hebben een nummer en een boekingsgeschiedenis; dat in een ander document verbergen is niets wat een bulkactie stilzwijgend zou moeten afhandelen.
  • Geen samenvoeging over partners, valuta, bedrijven of journalen heen. Daarvoor bestaat geen zinvolle afzonderlijke waarde, dus de module weigert in plaats van te raden.
  • payment_reference wordt niet overgenomen. Gestructureerde betalingsreferenties identificeren één document; twee aan elkaar geplakt passen bij de bank nergens op. De nieuwe factuur krijgt bij het boeken haar eigen referentie.
  • Voorschotregels worden als elke andere regel gekopieerd. Bevatten de bronnen voorschotregels uit sale, dan moet het resultaat vóór het boeken worden gecontroleerd.
  • Alleen timesheet_invoice_id wordt automatisch omgehangen. Elk ander model met een Many2one naar account.move (eigen factureringsruns, abonnementskoppelingen, EDI-verzendingen) wijst nog steeds naar de bron. Daarvoor is er de hook _merge_draft_relink.
  • Geen samenvoeging van gelijke regels – opzettelijk. Wie een korte, opgeruimde factuur wil, zit hier verkeerd en zou daarvoor met de herkomstkoppelingen betalen.
  • Koppen ontstaan eenmalig bij het samenvoegen. Daarna zijn het gewone sectieregels: bewerkbaar, verwijderbaar en niet bijgewerkt als order of ticket later wordt hernoemd.
  • Een kop noemt de tickets van de gehele bronfactuur, niet van de afzonderlijke regel.

Installatie in Odoo 19 Community

De installatie verloopt zoals bij een gebruikelijke Odoo-extramodule:

  1. De modulemap kopiëren naar een geconfigureerd addonspad.
  2. De app-lijst in Odoo bijwerken.
  3. De module account_invoice_merge_draft installeren.

De module is alleen afhankelijk van account. Alles wat ze weet over sale, purchase, project, hr_timesheet, sale_timesheet of een helpdeskmodule, wordt tijdens de uitvoering bepaald – ze installeert en werkt ook op een zuivere factureringsdatabase.

Daarna: Boekhouding → Klanten → Facturen (of Leveranciers → Facturen), twee of meer concepten markeren, tandwielmenu → Conceptfacturen samenvoegen. De actie is beschikbaar voor alle gebruikers met de groep Facturering.

Wie van een eerdere versie van de module bijwerkt, moet eenmalig updaten met -u account_invoice_merge_draft --i18n-overwrite, zodat gewijzigde veldnamen in de Duitse vertaling terechtkomen. Een nieuwe installatie is daarvan uitgezonderd.

Voor ontwikkelaars: API en hooks

Het samenvoegen kan ook vanuit eigen code worden aangeroepen:

python

merged = draft_moves.merge_draft_moves(
    extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
    source_handling="cancel",   # "delete" of "keep"
    add_source_sections=True,   # koppen; standaard True
)

merge_draft_moves werpt een UserError met alle weigeringsredenen als de selectie niet kan worden samengevoegd. _merge_draft_get_errors() levert dezelfde redenen als lijst zonder exception.

Wie eigen terugverwijzingen naar account.move heeft, overschrijft _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}
        )

Wie een terugverwijzing op de factuurregel heeft, heeft deze module helemaal niet nodig: Odoo's eigen hook account.move.line._copy_data_extend_business_fields volstaat, en het veld wordt bij het samenvoegen, bij het annuleren en bij het dupliceren op dezelfde manier overgenomen. Voor andere helpdeskmodules of een andere formulering van de koppen zijn er _merge_draft_get_source_tickets() en _merge_draft_section_name().

Getest voordat het op productiegegevens draait

48 tests dekken gemengde statussen, verschillende partners, valuta en documenttypen af, regels met en zonder verkoop- of inkooporder, belastingen, secties en notities, kostenplaatsverdelingen, het omhangen van de urenregistratie, alle drie de opties voor de bronfacturen, de assistent en de koppen – dat ze standaard actief zijn, dat uitschakelen ervan het oude resultaat tot op de cent reproduceert, dat een kop geen bedragen of rekeningen draagt, dat de regels van een bron bij elkaar blijven, dat het ticket met zijn nummer en nooit met de database-ID verschijnt.

De testgegevens bouwen voort op het bestaande rekeningschema van het actieve bedrijf in plaats van op AccountTestInvoicingCommon – die helper start op gemigreerde productiedatabases vaak niet, en juist daarop moet de module draaien.

odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
         --test-tags /account_invoice_merge_draft --stop-after-init

Zoals bij elke uitbreiding in het boekhoudgebied raden we aan de installatie eerst te controleren in een testomgeving met een kopie van de eigen database.

Gratis en open source voor de Odoo Community

Wij bij Biricon IT Services GmbH ontwikkelen oplossingen eerst voor concrete eisen uit ons eigen bedrijf. Als daaruit een algemeen bruikbare module ontstaat, willen we die niet in één enkele installatie verbergen.

Merge Draft Invoices is daarom gratis beschikbaar als open-sourceproject. De broncode kan worden ingezien, gecontroleerd, aangepast en binnen de kaders van de AGPL-3-licentie verder worden ontwikkeld.

Ons doel was niet de kortst mogelijke factuur, maar een factuur waarbij ook over een jaar nog elke regel terugleidt naar zijn order, zijn ticket en zijn tijdboeking.

Project, broncode en medewerking

De volledige broncode, de installatie-instructies en de technische documentatie zijn beschikbaar op GitHub:

Odoo 19 Merge Draft Invoices openen op GitHub

We stellen praktijkervaringen, foutrapporten, verbetervoorstellen en bijdragen uit de Odoo Community zeer op prijs.

Gratis. Navolgbaar. Open source. Voor Odoo 19 Community.