Den, der fakturerer i Odoo ud fra ordrer, helpdesk-tickets og tidsregistrering, kender resultatet: En kunde får ved månedens slutning ikke én faktura, men fem. Hver stammer fra en anden ordre eller et andet ticket, hver er faglig korrekt – og alligevel spørger kunden, om det ikke kunne være på ét dokument.
Netop til det findes der i Odoo 19 Community ingen ren løsning. Det kendte OCA-modul account_invoice_merge er endnu ikke frigivet til Odoo 19, og det løser problemet på en måde, der for tjenesteudbydere udgør en alvorlig risiko: Ens linjer lægges sammen, og dermed går forbindelsen tilbage til ordren, opgaven og den registrerede tid tabt.
Til vores egen Odoo 19 Community-installation har vi derfor udviklet modulet Merge Draft Invoices. Fordi dette problem ikke kun angår os, stiller vi det gratis til rådighed på GitHub under den frie AGPL-3-licens.
Til projektet på GitHub:
Hvorfor et eget modul i stedet for OCA-løsningen?
OCA har i mange år vedligeholdt account_invoice_merge i repositoryet OCA/account-invoicing. På tidspunktet for dette indlæg er migreringen til 19.0 dér stadig en åben pull request, ikke et udgivet modul.
Vigtigere er dog, at de to moduler gør forskellige ting:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Resultat | flettes ind i en af de valgte fakturaer | altid en ny faktura |
| Linjer | ens linjer lægges sammen, mængder adderes | hver linje overtages én til én |
| Oprindelse | invoice_origin / payment_reference hægtes sammen i hovedet | oprindelsesfelter pr. linje kopieres via Odoos egen _copy_data_extend_business_fields-hook |
| Tidsregistrering | tages ikke i betragtning | timesheet_invoice_id flyttes over på den nye faktura |
| Læsbart for kunden | intet | en overskrift over linjerne fra hver kilde med ordre og ticket |
| Kildefakturaer | annulleres | annullér (standard), slet eller behold – med tydeligt benævnte konsekvenser |
At lægge ens linjer sammen er den afgørende forskel. En sammenlagt linje kan kun have én oprindelse. Så snart to linjer fra to forskellige ordrer ser ens ud, er forbindelsen fra fakturaen tilbage til ordren, opgaven og tidsbogføringen væk. I en servicevirksomhed, hvor faktureringen foregår via ticket → tidsregistrering → faktura, betyder det: De samme timer faktureres dobbelt eller forsvinder.
Vores modul lægger derfor grundlæggende intet sammen. Den sammenlagte faktura har nøjagtig lige så mange linjer som dens kilder tilsammen.
Der er ikke overtaget nogen kode fra OCA-modulet. Begge moduler står under AGPL-3.
Hvad modulet gør
I fakturalisten markeres to eller flere kladder, i handlingsmenuen vælges Sammenlæg fakturakladder – en guide viser valget, kontrollerne og mulighederne for kildefakturaerne. Efter bekræftelsen lander man direkte på den nye faktura.
Før sammenlægningen kontrollerer modulet, og det alle punkter på én gang, så man ikke efter hver rettelse ser den næste fejl:
- mindst to fakturaer valgt
- alle i status Kladde
- ingen er nogensinde blevet bogført (et bilag, der er sat tilbage til kladde, beholder sin bogføringshistorik og afvises)
- ingen er allerede blevet sammenlagt
- alle er faktura, leverandørfaktura, kreditnota eller bilag – ingen rene bogføringsposter
- samme partner, bilagstype, virksomhed, valuta og journal
Forskelle i betalingsbetingelse, skattetilknytning, leveringsadresse, bankkonto eller sælger blokerer ikke. Guiden advarer og overtager værdien fra den ældste valgte faktura.
Hvad der bevares
Modulets kerne er, at enhver forbindelse, som en fakturalinje har til sin oprindelse, også findes på den nye faktura:
| Forbindelse | Behandling |
|---|---|
| Salgsordrelinjer (sale_line_ids) | kopieres pr. linje via Odoos _copy_data_extend_business_fields |
| Indkøbsordrelinjer (purchase_line_id) | ligeledes |
| Omkostningsstedsfordeling (analytic_distribution) | kopieres pr. linje |
| andre oprindelsesfelter pr. linje | automatisk, hvis det leverende modul implementerer den dokumenterede Odoo-hook |
| Oprindelse og reference i fakturahovedet | sammenlagt, deduplikeret, kan redigeres i guiden |
| Salgsordre: invoice_ids, invoice_status, qty_invoiced | beregnes ud fra linjerne – ordren registrerer selv den nye faktura |
| Tidsregistrering (timesheet_invoice_id) | flyttes over på den nye faktura, før kilderne bliver ugyldige |
| Helpdesk-ticket | hænger på tidsregistreringen og peger dermed automatisk på den nye faktura |
| Chatter | Besked på den nye faktura med alle kilder, besked på hver kilde med link til den nye |
| merged_into_move_id / merged_from_move_ids | gemt forbindelse i begge retninger |
Skattelinjer, betalingsbetingelseslinjer, afrundings- og kontantrabatlinjer kopieres ikke, men genberegnes af Odoo ud fra indholdet. Den sammenlagte faktura har altså én skattelinje pr. skattesats, ikke én pr. kilde. Den oprettes uden nummer som kladde; selve sammenlægningen bruger ikke noget bilagsnummer.
Overskrifter: Kunden ser, hvor hver linje stammer fra
Det er den funktion, der gør forskellen i hverdagen. Over linjerne fra hver kildefaktura sætter modulet en afsnitslinje – det samme element, som Odoo selv anvender til afsnittet »Forudbetalinger«:
BE10181 <- Overskrift
22,500 x Teknikertime 3.577,50
Subtotal 3.577,50
BE10182 · Ticket: HT00219 <- Overskrift
3,000 x Teknikertime 477,00
Subtotal 477,00
NETTOBELØB 4.054,50
Overskriften udskrives på det dokument, kunden får, og Odoos standardfakturarapport tilføjer af sig selv en subtotal pr. afsnit. Kunden ser dermed også, hvad hver del har kostet.
Betegnelsen sammensættes af det, kilden faktisk har:
- ordrerne, direkte fra linjeforbindelserne – navnene er dermed nøjagtige; mangler ordreforbindelser, anvendes friteksten fra invoice_origin
- tickets, fundet via tidsregistreringen – udskrevet som Ticket: HT00219 eller Tickets: …, begrænset til fem plus »og N flere«
- findes ingen af delene: kildefakturaens nummer, derefter dens reference
- findes der slet intet: et neutralt »Yderligere linjer«, så en kildes linjer aldrig i stilhed forsvinder i blokken ovenfor
Det, der mangler, udelades, ikke opfindes. Ticket udskrives med sit eget nummer – helpdesk_mgmt-nummer, Enterprise-ticket_ref eller modellens visningsnavn – aldrig med database-id'et.
Overskrifterne skrives på kundens sprog, ligesom Odoo gør det, når der oprettes fakturaer ud fra ordrer. Chatteren forbliver på brugerens sprog.
En overskrift bærer intet produkt, ingen mængde, ingen pris, ingen skat, ingen konto og ingen omkostningsstedsfordeling. Den kan ikke påvirke summer, skattegrundlag og omkostningsregnskab. Den, der slår overskrifterne fra i guiden, får nøjagtig den faktura, der ville være opstået uden denne funktion – en test sammenligner begge resultater til sidste øre.
Hvad der sker med kildefakturaerne
Guiden tilbyder tre muligheder:
- Annullér (standard, anbefalet). At annullere en kladde er uden konsekvenser: intet bilagsnummer, ingen bogføring. Fakturaerne forbliver i databasen som annullerede bilag med chatter og link til den nye faktura. qty_invoiced på salgsordren springer annullerede bilag over, intet tælles dobbelt. Det er den mulighed, der også om seks måneder stadig kan efterspores.
- Slet. Uigenkaldeligt. Chatter og tilbagehenvisning dør med posten; kun beskeden på den nye faktura består. Fornuftigt til engangskladder, som ingen skal se mere.
- Behold som kladde. Kilderne forbliver åbne. Deres salgsordrer tæller så kilden og den nye faktura som faktureret – arbejdet ser ud til at være faktureret dobbelt, indtil nogen rydder kladderne op i hånden. Tænkt til det sjældne tilfælde, at man vil sammenligne, før man beslutter sig. En test fastholder netop denne dobbelttælling, så den ikke overrasker nogen.
Grænser – hvad modulet bevidst ikke gør
Læs venligst før produktiv brug:
- Kun kladder. Bogførte fakturaer sammenlægges ikke. Det er en kreditnota-opgave, ikke en kopiering.
- Én gang bogførte, tilbagestillede kladder afvises. De har et nummer og en bogføringshistorik; at skjule det i et andet dokument er ikke noget, en samlet handling bør gøre i stilhed.
- Ingen sammenlægning på tværs af partnere, valutaer, virksomheder eller journaler. Der findes ingen fornuftig enkeltværdi hertil, så modulet afviser i stedet for at gætte.
- payment_reference overtages ikke. Strukturerede betalingsreferencer identificerer et bilag; to hægtet sammen passer til intet hos banken. Den nye faktura får ved bogføringen sin egen.
- Forudbetalingslinjer kopieres som enhver anden linje. Indeholder kilderne forudbetalingslinjer fra sale, skal resultatet kontrolleres før bogføring.
- Kun timesheet_invoice_id flyttes automatisk over. Enhver anden model med et Many2one til account.move (egne faktureringskørsler, abonnementsforbindelser, EDI-overførsler) peger fortsat på kilden. Til det findes hooket _merge_draft_relink.
- Ingen sammenlægning af ens linjer – med vilje. Den, der vil have en kort, ryddelig faktura, er forkert på den her og ville betale for det med oprindelsesforbindelserne.
- Overskrifter opstår én gang ved sammenlægningen. Derefter er de almindelige afsnitslinjer: kan redigeres, kan slettes og opdateres ikke, hvis ordre eller ticket senere omdøbes.
- En overskrift nævner tickets for hele kildefakturaen, ikke for den enkelte linje.
Installation i Odoo 19 Community
Installationen sker som ved et almindeligt Odoo-tilføjelsesmodul:
- Kopiér modulmappen til en konfigureret addons-sti.
- Opdatér app-listen i Odoo.
- Installér modulet account_invoice_merge_draft.
Modulet afhænger kun af account. Alt, hvad det ved om sale, purchase, project, hr_timesheet, sale_timesheet eller et helpdesk-modul, afklares ved kørsel – det installeres og fungerer også på en ren faktureringsdatabase.
Derefter: Regnskab → Kunder → Fakturaer (eller Leverandører → Fakturaer), markér to eller flere kladder, tandhjulsmenu → Sammenlæg fakturakladder. Handlingen står til rådighed for alle brugere med gruppen Fakturering.
Den, der opdaterer fra en tidligere version af modulet, bør én gang opdatere med -u account_invoice_merge_draft --i18n-overwrite, så ændrede feltbetegnelser kommer med i den tyske oversættelse. En nyinstallation berøres ikke heraf.
Til udviklere: API og hooks
Sammenlægningen kan også kaldes fra egen kode:
python
merged = draft_moves.merge_draft_moves(
extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
source_handling="cancel", # "delete" eller "keep"
add_source_sections=True, # Overskrifter; standard True
)
merge_draft_moves kaster en UserError med alle afvisningsgrunde, hvis udvalget ikke kan sammenlægges. _merge_draft_get_errors() returnerer de samme grunde som liste uden exception.
Den, der har egne tilbagehenvisninger til account.move, overskriver _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}
)
Den, der har en tilbagehenvisning på fakturalinjen, har slet ikke brug for dette modul: Odoos eget hook account.move.line._copy_data_extend_business_fields er tilstrækkeligt, og feltet overtages lige så vel ved sammenlægning, annullering og duplikering. Til andre helpdesk-moduler eller en anden formulering af overskrifterne findes _merge_draft_get_source_tickets() og _merge_draft_section_name().
Testet, før det kører på produktive data
48 tests dækker blandede statusser, forskellige partnere, valutaer og bilagstyper, linjer med og uden salgs- eller indkøbsordre, skatter, afsnit og noter, omkostningsstedsfordelinger, flytningen af tidsregistreringen, alle tre muligheder for kildefakturaerne, guiden og overskrifterne – at de som standard er aktive, at deres frakobling reproducerer det gamle resultat til sidste øre, at en overskrift ikke bærer beløb eller konti, at en kildes linjer forbliver sammen, at ticket vises med sit nummer og aldrig med database-id'et.
Testdataene bygger på den eksisterende kontoplan for den aktive virksomhed i stedet for på AccountTestInvoicingCommon – denne helper starter ofte ikke på migrerede produktive databaser, og netop på sådanne skal modulet køre.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Som ved enhver udvidelse inden for regnskabsområdet anbefaler vi først at afprøve installationen i et testmiljø med en kopi af den egen database.
Gratis og open source til Odoo Community
Hos Biricon IT Services GmbH udvikler vi først og fremmest løsninger til konkrete krav fra vores egen drift. Hvis der heraf opstår et generelt anvendeligt modul, vil vi ikke gemme det væk i en enkelt installation.
Merge Draft Invoices er derfor gratis tilgængelig som open source-projekt. Kildekoden kan gennemses, kontrolleres, tilpasses og videreudvikles inden for rammerne af AGPL-3-licensen.
Vores mål var ikke den kortest mulige faktura, men en faktura, hvor hver linje også om et år stadig fører tilbage til sin ordre, sit ticket og sin tidsbogføring.
Projekt, kildekode og medvirken
Den komplette kildekode, installationsvejledningen og den tekniske dokumentation er tilgængelige på GitHub:
Åbn Odoo 19 Merge Draft Invoices på GitHub
Vi glæder os over praktiske erfaringer, fejlrapporter, forbedringsforslag og bidrag fra Odoo Community.
Gratis. Sporbart. Open source. Til Odoo 19 Community.