Zum Inhalt springen

Odoo 19 Community: Entwurfsrechnungen zusammenführen – unser Open-Source-Modul Merge Draft Invoices

18. September 2026 durch
Odoo 19 Community: Entwurfsrechnungen zusammenführen – unser Open-Source-Modul Merge Draft Invoices
Alexander Biringer | Biricon IT Services GmbH

Wer in Odoo aus Aufträgen, Helpdesk-Tickets und Zeiterfassung abrechnet, kennt das Ergebnis: Ein Kunde bekommt am Monatsende nicht eine Rechnung, sondern fünf. Jede stammt aus einem anderen Auftrag oder Ticket, jede ist fachlich korrekt – und trotzdem fragt der Kunde, ob das nicht auf ein Dokument passen würde.

Genau dafür gibt es in Odoo 19 Community keine saubere Lösung. Das bekannte OCA-Modul account_invoice_merge ist für Odoo 19 noch nicht freigegeben, und es löst das Problem auf eine Weise, die für Dienstleister ein ernstes Risiko darstellt: Gleiche Positionen werden zusammengefasst, und damit geht die Verbindung zurück zum Auftrag, zur Aufgabe und zur erfassten Zeit verloren.

Für unsere eigene Odoo-19-Community-Installation haben wir deshalb das Modul Merge Draft Invoices entwickelt. Weil dieses Problem nicht nur uns betrifft, stellen wir es unter der freien AGPL-3-Lizenz kostenlos auf GitHub zur Verfügung.

Zum Projekt auf GitHub:

Odoo 19 Merge Draft Invoices

Warum ein eigenes Modul statt der OCA-Lösung?

Die OCA pflegt seit vielen Jahren account_invoice_merge im Repository OCA/account-invoicing. Zum Zeitpunkt dieses Beitrags ist die Migration auf 19.0 dort noch ein offener Pull Request, kein veröffentlichtes Modul.

Wichtiger ist aber, dass die beiden Module unterschiedliche Dinge tun:


OCA account_invoice_mergeMerge Draft Invoices
Ergebniswird in eine der ausgewählten Rechnungen zusammengeführtimmer eine neue Rechnung
Positionengleiche Positionen werden zusammengefasst, Mengen addiertjede Position wird eins zu eins übernommen
Herkunftinvoice_origin / payment_reference im Kopf zusammengehängtHerkunftsfelder je Position über Odoos eigenen _copy_data_extend_business_fields-Hook kopiert
Zeiterfassungnicht berücksichtigttimesheet_invoice_id wird auf die neue Rechnung umgehängt
Lesbar für den Kundennichtseine Überschrift über den Positionen jeder Quelle mit Auftrag und Ticket
Quellrechnungenwerden storniertstornieren (Standard), löschen oder behalten – mit klar benannten Folgen

Das Zusammenfassen gleicher Positionen ist der entscheidende Unterschied. Eine zusammengefasste Position kann nur eine Herkunft tragen. Sobald zwei Zeilen aus zwei verschiedenen Aufträgen gleich aussehen, ist die Verbindung von der Rechnung zurück zum Auftrag, zur Aufgabe und zur Zeitbuchung weg. In einem Dienstleistungsbetrieb, in dem die Abrechnung über Ticket → Zeiterfassung → Rechnung läuft, bedeutet das: Dieselben Stunden werden doppelt verrechnet oder verschwinden.

Unser Modul fasst deshalb grundsätzlich nichts zusammen. Die zusammengeführte Rechnung hat exakt so viele Positionen wie ihre Quellen zusammen.

Es wurde kein Code aus dem OCA-Modul übernommen. Beide Module stehen unter AGPL-3.

Was das Modul macht

Im Rechnungslisten-View zwei oder mehr Entwürfe markieren, im Aktionsmenü Entwurfsrechnungen zusammenführen wählen – ein Assistent zeigt die Auswahl, die Prüfungen und die Optionen für die Quellrechnungen. Nach dem Bestätigen landet man direkt auf der neuen Rechnung.

Vor dem Zusammenführen prüft das Modul, und zwar alle Punkte auf einmal, damit man nicht nach jeder Korrektur den nächsten Fehler sieht:

  • mindestens zwei Rechnungen ausgewählt
  • alle im Status Entwurf
  • keine wurde jemals gebucht (ein auf Entwurf zurückgesetzter Beleg behält seine Buchungshistorie und wird abgelehnt)
  • keine wurde bereits zusammengeführt
  • alle sind Rechnung, Lieferantenrechnung, Gutschrift oder Beleg – keine reinen Buchungssätze
  • gleicher Partner, Belegtyp, Unternehmen, Währung und Journal

Unterschiede bei Zahlungsbedingung, Steuerzuordnung, Lieferadresse, Bankkonto oder Verkäufer blockieren nicht. Der Assistent warnt und übernimmt den Wert der ältesten ausgewählten Rechnung.

Was erhalten bleibt

Der Kern des Moduls ist, dass jede Verbindung, die eine Rechnungsposition zu ihrer Herkunft hat, auch auf der neuen Rechnung existiert:

VerknüpfungBehandlung
Verkaufsauftragspositionen (sale_line_ids)je Position über Odoos _copy_data_extend_business_fields kopiert
Einkaufsauftragspositionen (purchase_line_id)ebenso
Kostenstellenverteilung (analytic_distribution)je Position kopiert
andere Herkunftsfelder pro Positionautomatisch, wenn das liefernde Modul den dokumentierten Odoo-Hook implementiert
Herkunft und Referenz im Rechnungskopfzusammengeführt, dedupliziert, im Assistenten editierbar
Verkaufsauftrag: invoice_ids, invoice_status, qty_invoicedwerden aus den Positionen berechnet – der Auftrag erkennt die neue Rechnung von selbst
Zeiterfassung (timesheet_invoice_id)wird auf die neue Rechnung umgehängt, bevor die Quellen ungültig werden
Helpdesk-Tickethängt an der Zeiterfassung und zeigt damit automatisch auf die neue Rechnung
ChatterNachricht auf der neuen Rechnung mit allen Quellen, Nachricht auf jeder Quelle mit Link zur neuen
merged_into_move_id / merged_from_move_idsgespeicherte Verknüpfung in beide Richtungen

Steuerzeilen, Zahlungsbedingungszeilen, Rundungs- und Skontozeilen werden nicht kopiert, sondern von Odoo aus dem Inhalt neu berechnet. Die zusammengeführte Rechnung hat also eine Steuerzeile pro Steuersatz, nicht eine pro Quelle. Sie wird unnummeriert als Entwurf angelegt; das Zusammenführen selbst verbraucht keine Belegnummer.

Überschriften: Der Kunde sieht, woher jede Position stammt

Das ist die Funktion, die den Unterschied im Alltag macht. Über den Positionen jeder Quellrechnung setzt das Modul eine Abschnittszeile – dasselbe Element, das Odoo selbst für den Abschnitt „Anzahlungen" verwendet:

BE10181                                     <- Überschrift
  22,500 x Technikerstunde        3.577,50
                    Zwischensumme 3.577,50
BE10182 · Ticket: HT00219                   <- Überschrift
   3,000 x Technikerstunde          477,00
                    Zwischensumme   477,00
                      NETTOBETRAG 4.054,50

Die Überschrift wird auf dem Dokument gedruckt, das der Kunde bekommt, und Odoos Standard-Rechnungsbericht ergänzt von sich aus eine Zwischensumme pro Abschnitt. Der Kunde sieht damit auch, was jeder Teil gekostet hat.

Die Beschriftung wird aus dem zusammengesetzt, was die Quelle tatsächlich hat:

  1. die Aufträge, direkt aus den Positionsverknüpfungen – die Namen sind damit exakt; fehlen Auftragsverknüpfungen, wird der Freitext aus invoice_origin verwendet
  2. die Tickets, ermittelt über die Zeiterfassung – ausgegeben als Ticket: HT00219 oder Tickets: …, begrenzt auf fünf plus „und N weitere"
  3. gibt es beides nicht: die Nummer der Quellrechnung, dann ihre Referenz
  4. gibt es gar nichts: ein neutrales „Weitere Positionen", damit die Zeilen einer Quelle nie stillschweigend im Block darüber verschwinden

Was fehlt, wird weggelassen, nicht erfunden. Das Ticket wird mit seiner eigenen Nummer gedruckt – helpdesk_mgmt-Nummer, Enterprise-ticket_ref oder der Anzeigename des Modells – niemals mit der Datenbank-ID.

Die Überschriften werden in der Sprache des Kunden geschrieben, so wie Odoo es beim Erzeugen von Rechnungen aus Aufträgen tut. Der Chatter bleibt in der Sprache des Anwenders.

Eine Überschrift trägt kein Produkt, keine Menge, keinen Preis, keine Steuer, kein Konto und keine Kostenstellenverteilung. Sie kann Summen, Steuerbasis und Kostenrechnung nicht beeinflussen. Wer die Überschriften im Assistenten abschaltet, bekommt exakt die Rechnung, die ohne diese Funktion entstehen würde – ein Test vergleicht beide Ergebnisse bis auf den Cent.

Was mit den Quellrechnungen passiert

Der Assistent bietet drei Optionen:

  • Stornieren (Standard, empfohlen). Das Stornieren eines Entwurfs ist folgenlos: keine Belegnummer, keine Buchung. Die Rechnungen bleiben als stornierte Belege mit Chatter und Link zur neuen Rechnung in der Datenbank. qty_invoiced am Verkaufsauftrag überspringt stornierte Belege, nichts wird doppelt gezählt. Das ist die Option, die auch in sechs Monaten noch nachvollziehbar ist.
  • Löschen. Unwiderruflich. Chatter und Rückverweis sterben mit dem Datensatz; nur die Nachricht auf der neuen Rechnung bleibt. Sinnvoll für Wegwerf-Entwürfe, die niemand mehr sehen soll.
  • Als Entwurf belassen. Die Quellen bleiben offen. Ihre Verkaufsaufträge zählen dann Quelle und neue Rechnung als fakturiert – die Arbeit sieht doppelt abgerechnet aus, bis jemand die Entwürfe von Hand bereinigt. Gedacht für den seltenen Fall, dass man vor der Entscheidung vergleichen möchte. Ein Test hält genau diese Doppelzählung fest, damit sie niemanden überrascht.

Grenzen – was das Modul bewusst nicht tut

Bitte vor dem Produktiveinsatz lesen:

  • Nur Entwürfe. Gebuchte Rechnungen werden nicht zusammengeführt. Das ist eine Gutschriften-Aufgabe, kein Kopiervorgang.
  • Einmal gebuchte, zurückgesetzte Entwürfe werden abgelehnt. Sie besitzen eine Nummer und eine Buchungshistorie; das in einem anderen Dokument zu verstecken ist nichts, was eine Sammelaktion still erledigen sollte.
  • Kein Zusammenführen über Partner, Währungen, Unternehmen oder Journale hinweg. Es gibt dafür keinen sinnvollen Einzelwert, also lehnt das Modul ab, statt zu raten.
  • payment_reference wird nicht übernommen. Strukturierte Zahlungsreferenzen identifizieren einen Beleg; zwei zusammengehängt passen auf nichts bei der Bank. Die neue Rechnung bekommt beim Buchen ihre eigene.
  • Anzahlungspositionen werden wie jede andere Position kopiert. Enthalten die Quellen Anzahlungszeilen aus sale, ist das Ergebnis vor dem Buchen zu prüfen.
  • Nur timesheet_invoice_id wird automatisch umgehängt. Jedes andere Modell mit einem Many2one auf account.move (eigene Abrechnungsläufe, Abo-Verknüpfungen, EDI-Übermittlungen) zeigt weiterhin auf die Quelle. Dafür gibt es den Hook _merge_draft_relink.
  • Keine Zusammenfassung gleicher Positionen – absichtlich. Wer eine kurze, aufgeräumte Rechnung will, ist hier falsch und würde dafür mit den Herkunftsverknüpfungen bezahlen.
  • Überschriften entstehen einmalig beim Zusammenführen. Danach sind sie normale Abschnittszeilen: editierbar, löschbar und nicht aktualisiert, wenn Auftrag oder Ticket später umbenannt werden.
  • Eine Überschrift nennt die Tickets der gesamten Quellrechnung, nicht der einzelnen Position.

Installation in Odoo 19 Community

Die Installation erfolgt wie bei einem üblichen Odoo-Zusatzmodul:

  1. Das Modulverzeichnis in einen konfigurierten Addons-Pfad kopieren.
  2. Die App-Liste in Odoo aktualisieren.
  3. Das Modul account_invoice_merge_draft installieren.

Das Modul hängt nur von account ab. Alles, was es über sale, purchase, project, hr_timesheet, sale_timesheet oder ein Helpdesk-Modul weiß, wird zur Laufzeit ermittelt – es installiert und funktioniert auch auf einer reinen Abrechnungsdatenbank.

Danach: Buchhaltung → Kunden → Rechnungen (oder Lieferanten → Rechnungen), zwei oder mehr Entwürfe markieren, Zahnrad-Menü → Entwurfsrechnungen zusammenführen. Die Aktion steht allen Anwendern mit der Gruppe Abrechnung / Fakturierung zur Verfügung.

Wer von einer früheren Version des Moduls aktualisiert, sollte einmalig mit -u account_invoice_merge_draft --i18n-overwrite updaten, damit geänderte Feldbezeichnungen in der deutschen Übersetzung ankommen. Eine Neuinstallation ist davon nicht betroffen.

Für Entwickler: API und Hooks

Das Zusammenführen lässt sich auch aus eigenem Code aufrufen:

python

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

merge_draft_moves wirft einen UserError mit allen Ablehnungsgründen, wenn die Auswahl nicht zusammenführbar ist. _merge_draft_get_errors() liefert dieselben Gründe als Liste ohne Exception.

Wer eigene Rückverweise auf account.move hat, überschreibt _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}
        )

Wer einen Rückverweis auf der Rechnungsposition hat, braucht dieses Modul gar nicht: Odoos eigener Hook account.move.line._copy_data_extend_business_fields genügt, und das Feld wird beim Zusammenführen, beim Stornieren und beim Duplizieren gleichermaßen übernommen. Für andere Helpdesk-Module oder eine andere Formulierung der Überschriften gibt es _merge_draft_get_source_tickets() und _merge_draft_section_name().

Getestet, bevor es auf Produktivdaten läuft

48 Tests decken gemischte Status, unterschiedliche Partner, Währungen und Belegtypen ab, Positionen mit und ohne Verkaufs- oder Einkaufsauftrag, Steuern, Abschnitte und Notizen, Kostenstellenverteilungen, das Umhängen der Zeiterfassung, alle drei Optionen für die Quellrechnungen, den Assistenten und die Überschriften – dass sie standardmäßig aktiv sind, dass ihr Abschalten das alte Ergebnis auf den Cent reproduziert, dass eine Überschrift keine Beträge oder Konten trägt, dass die Positionen einer Quelle zusammenbleiben, dass das Ticket mit seiner Nummer und nie mit der Datenbank-ID erscheint.

Die Testdaten bauen auf dem vorhandenen Kontenplan der aktiven Firma auf statt auf AccountTestInvoicingCommon – dieser Helper startet auf migrierten Produktivdatenbanken oft nicht, und genau auf solchen soll das Modul laufen.

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

Wie bei jeder Erweiterung im Buchhaltungsbereich empfehlen wir, die Installation zuerst in einer Testumgebung mit einer Kopie der eigenen Datenbank zu prüfen.

Kostenlos und Open Source für die Odoo Community

Wir bei Biricon IT Services GmbH entwickeln Lösungen zunächst für konkrete Anforderungen aus unserem eigenen Betrieb. Wenn daraus ein allgemein nutzbares Modul entsteht, möchten wir es nicht in einer einzelnen Installation verstecken.

Merge Draft Invoices steht deshalb kostenlos als Open-Source-Projekt zur Verfügung. Der Quellcode kann eingesehen, geprüft, angepasst und im Rahmen der AGPL-3-Lizenz weiterentwickelt werden.

Unser Ziel war nicht die kürzestmögliche Rechnung, sondern eine Rechnung, bei der auch in einem Jahr noch jede Position zu ihrem Auftrag, ihrem Ticket und ihrer Zeitbuchung zurückführt.

Projekt, Quellcode und Mitarbeit

Der vollständige Quellcode, die Installationshinweise und die technische Dokumentation sind auf GitHub verfügbar:

Odoo 19 Merge Draft Invoices auf GitHub öffnen

Wir freuen uns über Praxiserfahrungen, Fehlerberichte, Verbesserungsvorschläge und Beiträge aus der Odoo Community.

Kostenlos. Nachvollziehbar. Open Source. Für Odoo 19 Community.