Kdor v Odoo obračunava iz naročil, zahtevkov helpdeska in beleženja časa, pozna rezultat: stranka ob koncu meseca ne dobi enega računa, temveč pet. Vsak izvira iz drugega naročila ali zahtevka, vsak je strokovno pravilen – in vendar stranka vpraša, ali ne bi vse šlo na en dokument.
Za to v Odoo 19 Community ni čiste rešitve. Znani modul OCA account_invoice_merge za Odoo 19 še ni odobren in problem rešuje na način, ki za ponudnike storitev pomeni resno tveganje: enake postavke se združijo in s tem se izgubi povezava nazaj do naročila, naloge in zabeleženega časa.
Za lastno namestitev Odoo 19 Community smo zato razvili modul Merge Draft Invoices. Ker ta težava ne zadeva le nas, ga brezplačno ponujamo na GitHubu pod prosto licenco AGPL-3.
Na projekt na GitHubu:
Zakaj lasten modul namesto rešitve OCA?
OCA že vrsto let vzdržuje account_invoice_merge v repozitoriju OCA/account-invoicing. Ob času tega prispevka je migracija na 19.0 tam še odprt pull request, ne objavljen modul.
Pomembnejše pa je, da modula počneta različni stvari:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Rezultat | se združi v enega od izbranih računov | vedno nov račun |
| Postavke | enake postavke se združijo, količine se seštejo | vsaka postavka se prevzame ena proti ena |
| Izvor | invoice_origin / payment_reference v glavi se zlepita | polja izvora na postavko se kopirajo prek Odoojeve lastne kljuke _copy_data_extend_business_fields |
| Beleženje časa | ni upoštevano | timesheet_invoice_id se prenese na nov račun |
| Berljivo za stranko | nič | naslov nad postavkami vsakega vira z naročilom in zahtevkom |
| Izvorni računi | se stornirajo | stornirati (privzeto), izbrisati ali obdržati – z jasno poimenovanimi posledicami |
Združevanje enakih postavk je odločilna razlika. Združena postavka lahko nosi le en izvor. Takoj ko dve vrstici iz dveh različnih naročil izgledata enako, je povezava od računa nazaj do naročila, naloge in knjižbe časa izgubljena. V storitvenem podjetju, kjer obračun poteka prek zahtevek → beleženje časa → račun, to pomeni: iste ure se zaračunajo dvakrat ali izginejo.
Naš modul zato načeloma ničesar ne združuje. Združeni račun ima natanko toliko postavk kot njegovi viri skupaj.
Iz modula OCA ni bila prevzeta nobena koda. Oba modula sta pod AGPL-3.
Kaj modul počne
V seznamskem pogledu računov označite dva ali več osnutkov, v meniju dejanj izberite Združi osnutke računov – pomočnik prikaže izbor, preverjanja in možnosti za izvorne račune. Po potrditvi pristanete neposredno na novem računu.
Pred združitvijo modul preveri, in sicer vse točke naenkrat, da po vsakem popravku ne vidite naslednje napake:
- izbrana sta vsaj dva računa
- vsi so v stanju osnutek
- noben ni bil nikoli knjižen (dokument, vrnjen v osnutek, ohrani zgodovino knjiženja in se zavrne)
- noben ni bil že združen
- vsi so račun, račun dobavitelja, dobropis ali dokument – ne zgolj knjižbe
- isti partner, vrsta dokumenta, podjetje, valuta in dnevnik
Razlike pri plačilnem pogoju, davčni dodelitvi, naslovu dobave, bančnem računu ali prodajalcu ne blokirajo. Pomočnik opozori in prevzame vrednost najstarejšega izbranega računa.
Kaj ostane ohranjeno
Bistvo modula je, da vsaka povezava, ki jo postavka računa ima s svojim izvorom, obstaja tudi na novem računu:
| Povezava | Obravnava |
|---|---|
| Postavke prodajnega naloga (sale_line_ids) | se kopirajo na postavko prek Odoojevega _copy_data_extend_business_fields |
| Postavke nabavnega naročila (purchase_line_id) | enako |
| Razporeditev stroškovnih mest (analytic_distribution) | se kopira na postavko |
| druga polja izvora na postavko | samodejno, če modul, ki jih zagotavlja, implementira dokumentirano kljuko Odoo |
| Izvor in referenca v glavi računa | združena, odstranjeni dvojniki, urejljiva v pomočniku |
| Prodajni nalog: invoice_ids, invoice_status, qty_invoiced | se izračunajo iz postavk – naročilo novi račun prepozna samo |
| Beleženje časa (timesheet_invoice_id) | se prenese na nov račun, preden viri postanejo neveljavni |
| Zahtevek helpdeska | je vezan na beleženje časa in tako samodejno kaže na nov račun |
| Chatter | sporočilo na novem računu z vsemi viri, sporočilo na vsakem viru s povezavo na novega |
| merged_into_move_id / merged_from_move_ids | shranjena povezava v obe smeri |
Davčne vrstice, vrstice plačilnih pogojev, vrstice zaokroževanja in vrstice popusta za takojšnje plačilo se ne kopirajo, temveč jih Odoo na novo izračuna iz vsebine. Združeni račun ima torej eno davčno vrstico na davčno stopnjo, ne eno na vir. Ustvari se neoštevilčen kot osnutek; samo združevanje ne porabi številke dokumenta.
Naslovi: stranka vidi, od kod izvira vsaka postavka
To je funkcija, ki v vsakdanu naredi razliko. Nad postavkami vsakega izvornega računa modul vstavi vrstico razdelka – isti element, ki ga Odoo sam uporablja za razdelek „Predplačila“:
BE10181 <- naslov
22,500 x ura tehnika 3.577,50
Vmesna vsota 3.577,50
BE10182 · Zahtevek: HT00219 <- naslov
3,000 x ura tehnika 477,00
Vmesna vsota 477,00
NETO ZNESEK 4.054,50
Naslov se natisne na dokumentu, ki ga prejme stranka, standardno poročilo o računu Odoo pa samo doda vmesno vsoto za vsak razdelek. Stranka tako vidi tudi, koliko je stal vsak del.
Oznaka se sestavi iz tistega, kar vir dejansko ima:
- naročila, neposredno iz povezav postavk – imena so tako natančna; če povezav z naročili ni, se uporabi prosto besedilo iz invoice_origin
- zahtevki, ugotovljeni prek beleženja časa – izpisani kot Zahtevek: HT00219 ali Zahtevki: …, omejeno na pet plus „in še N drugih“
- če ni ničesar od tega: številka izvornega računa, nato njegova referenca
- če ni prav ničesar: nevtralno „Dodatne postavke“, da vrstice enega vira nikoli neopazno ne izginejo v bloku nad njimi
Kar manjka, se izpusti, ne izmisli. Zahtevek se natisne z lastno številko – številka helpdesk_mgmt, Enterprise ticket_ref ali prikazno ime modela – nikoli z ID-jem iz baze podatkov.
Naslovi se pišejo v jeziku stranke, tako kot to naredi Odoo pri ustvarjanju računov iz naročil. Chatter ostane v jeziku uporabnika.
Naslov ne nosi izdelka, količine, cene, davka, konta ali razporeditve stroškovnih mest. Ne more vplivati na vsote, davčno osnovo in stroškovno računovodstvo. Kdor naslove v pomočniku izklopi, dobi natanko račun, ki bi nastal brez te funkcije – test primerja oba rezultata do centa natančno.
Kaj se zgodi z izvornimi računi
Pomočnik ponuja tri možnosti:
- Stornirati (privzeto, priporočeno). Storno osnutka je brez posledic: brez številke dokumenta, brez knjižbe. Računi ostanejo v zbirki podatkov kot stornirani dokumenti s chatterjem in povezavo na nov račun. qty_invoiced pri prodajnem nalogu preskoči stornirane dokumente, nič se ne šteje dvakrat. To je možnost, ki je razumljiva tudi čez šest mesecev.
- Izbrisati. Nepreklicno. Chatter in povratna povezava izginejo skupaj z zapisom; ostane le sporočilo na novem računu. Smiselno za osnutke za enkratno rabo, ki jih nihče več ne želi videti.
- Pustiti kot osnutek. Viri ostanejo odprti. Njihova prodajna naročila potem štejejo vir in nov račun kot fakturirana – delo je videti dvakrat obračunano, dokler nekdo osnutkov ne počisti ročno. Namenjeno redkemu primeru, ko želite pred odločitvijo primerjati. Test zajame prav to dvojno štetje, da ne preseneti nikogar.
Meje – česar modul namenoma ne počne
Prosimo, preberite pred produkcijsko uporabo:
- Samo osnutki. Knjiženi računi se ne združujejo. To je naloga dobropisov, ne postopek kopiranja.
- Enkrat knjiženi, vrnjeni osnutki se zavrnejo. Imajo številko in zgodovino knjiženja; skrivanje tega v drugem dokumentu ni nekaj, kar bi smela skupinska akcija opraviti tiho.
- Brez združevanja prek več partnerjev, valut, podjetij ali dnevnikov. Za to ni smiselne enotne vrednosti, zato modul zavrne, namesto da bi ugibal.
- payment_reference se ne prevzame. Strukturirane reference plačil identificirajo dokument; dve zlepljeni se ne ujemata z ničemer pri banki. Nov račun ob knjiženju dobi svojo.
- Postavke predplačil se kopirajo kot vsaka druga postavka. Če viri vsebujejo vrstice predplačil iz modula sale, je treba rezultat pred knjiženjem preveriti.
- Samo timesheet_invoice_id se samodejno prenese. Vsak drug model z Many2one na account.move (lastni obračunski zagoni, povezave naročnin, pošiljanje EDI) še naprej kaže na vir. Za to obstaja kljuka _merge_draft_relink.
- Brez združevanja enakih postavk – namenoma. Kdor želi kratek, urejen račun, je tu na napačnem mestu in bi za to plačal s povezavami do izvora.
- Naslovi nastanejo enkrat ob združevanju. Potem so običajne vrstice razdelkov: urejljive, izbrisljive in se ne posodabljajo, če se naročilo ali zahtevek pozneje preimenuje.
- Naslov navaja zahtevke celotnega izvornega računa, ne posamezne postavke.
Namestitev v Odoo 19 Community
Namestitev poteka kot pri običajnem dodatnem modulu Odoo:
- Mapo modula kopirajte v nastavljeno pot dodatkov (addons).
- Posodobite seznam aplikacij v Odoo.
- Namestite modul account_invoice_merge_draft.
Modul je odvisen le od account. Vse, kar ve o sale, purchase, project, hr_timesheet, sale_timesheet ali modulu helpdeska, se ugotovi med izvajanjem – namesti se in deluje tudi na čisti obračunski zbirki podatkov.
Nato: Računovodstvo → Stranke → Računi (ali Dobavitelji → Računi), označite dva ali več osnutkov, meni z zobnikom → Združi osnutke računov. Dejanje je na voljo vsem uporabnikom s skupino Obračun / Fakturiranje.
Kdor posodablja s prejšnje različice modula, naj enkrat posodobi z -u account_invoice_merge_draft --i18n-overwrite, da spremenjene oznake polj pridejo v slovenski prevod. Nova namestitev tega ne zadeva.
Za razvijalce: API in kljuke
Združevanje je mogoče poklicati tudi iz lastne kode:
python
merged = draft_moves.merge_draft_moves(
extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
source_handling="cancel", # "delete" ali "keep"
add_source_sections=True, # naslovi; privzeto True
)
merge_draft_moves sproži UserError z vsemi razlogi za zavrnitev, če izbora ni mogoče združiti. _merge_draft_get_errors() vrne iste razloge kot seznam brez izjeme.
Kdor ima lastne povratne reference na account.move, prepiš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}
)
Kdor ima povratno referenco na postavki računa, tega modula sploh ne potrebuje: zadošča Odoojeva lastna kljuka account.move.line._copy_data_extend_business_fields in polje se prevzame enako pri združevanju, storniranju in podvajanju. Za druge module helpdeska ali drugačno formulacijo naslovov obstajata _merge_draft_get_source_tickets() in _merge_draft_section_name().
Preizkušeno, preden teče na produkcijskih podatkih
48 testov pokriva mešana stanja, različne partnerje, valute in vrste dokumentov, postavke z in brez prodajnega ali nabavnega naročila, davke, razdelke in opombe, razporeditve stroškovnih mest, prenos beleženja časa, vse tri možnosti za izvorne račune, pomočnika in naslove – da so privzeto vklopljeni, da njihov izklop do centa reproducira star rezultat, da naslov ne nosi zneskov ali kontov, da postavke enega vira ostanejo skupaj, da se zahtevek pojavi s svojo številko in nikoli z ID-jem iz baze podatkov.
Testni podatki temeljijo na obstoječem kontnem načrtu aktivnega podjetja namesto na AccountTestInvoicingCommon – ta pomočnik se na migriranih produkcijskih zbirkah podatkov pogosto ne zažene, modul pa mora teči prav na takih.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Kot pri vsaki razširitvi na računovodskem področju priporočamo, da namestitev najprej preizkusite v testnem okolju s kopijo lastne zbirke podatkov.
Brezplačno in odprtokodno za skupnost Odoo Community
V družbi Biricon IT Services GmbH rešitve najprej razvijamo za konkretne zahteve iz lastnega poslovanja. Če iz tega nastane splošno uporaben modul, ga ne želimo skrivati v eni sami namestitvi.
Merge Draft Invoices je zato brezplačno na voljo kot odprtokodni projekt. Izvorno kodo je mogoče pregledati, preveriti, prilagoditi in v okviru licence AGPL-3 razvijati naprej.
Naš cilj ni bil najkrajši možni račun, temveč račun, pri katerem tudi čez leto dni vsaka postavka vodi nazaj do svojega naročila, svojega zahtevka in svoje knjižbe časa.
Projekt, izvorna koda in sodelovanje
Celotna izvorna koda, napotki za namestitev in tehnična dokumentacija so na voljo na GitHubu:
Odpri Odoo 19 Merge Draft Invoices na GitHubu
Veselimo se izkušenj iz prakse, poročil o napakah, predlogov za izboljšave in prispevkov skupnosti Odoo Community.
Brezplačno. Razumljivo. Odprta koda. Za Odoo 19 Community.