Tko u Odoo-u obračunava iz narudžbi, helpdesk tiketa i evidencije vremena, poznaje rezultat: kupac na kraju mjeseca ne dobiva jedan račun, nego pet. Svaki potječe iz druge narudžbe ili tiketa, svaki je stručno ispravan – a ipak kupac pita ne bi li sve stalo u jedan dokument.
Upravo za to u Odoo 19 Community ne postoji čisto rješenje. Poznati OCA modul account_invoice_merge još nije objavljen za Odoo 19, a problem rješava na način koji za pružatelje usluga predstavlja ozbiljan rizik: iste se stavke objedinjuju, čime se gubi veza natrag s narudžbom, zadatkom i evidentiranim vremenom.
Za vlastitu instalaciju Odoo 19 Community stoga smo razvili modul Merge Draft Invoices. Budući da taj problem ne pogađa samo nas, besplatno ga stavljamo na raspolaganje na GitHubu pod slobodnom licencom AGPL-3.
Na projekt na GitHubu:
Zašto vlastiti modul umjesto OCA rješenja?
OCA već dugi niz godina održava account_invoice_merge u repozitoriju OCA/account-invoicing. U trenutku objave ovog članka migracija na 19.0 tamo je još otvoren pull request, a ne objavljen modul.
Važnije je, međutim, to što ta dva modula rade različite stvari:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Rezultat | spaja se u jedan od odabranih računa | uvijek novi račun |
| Stavke | iste se stavke objedinjuju, količine se zbrajaju | svaka se stavka preuzima jedan prema jedan |
| Podrijetlo | invoice_origin / payment_reference u zaglavlju spojeni jedno za drugim | polja podrijetla po stavci kopiraju se putem Odoo-ova vlastitog _copy_data_extend_business_fields hooka |
| Evidencija vremena | nije uzeta u obzir | timesheet_invoice_id prebacuje se na novi račun |
| Čitljivo za kupca | ništa | naslov iznad stavki svakog izvora s narudžbom i tiketom |
| Izvorni računi | se storniraju | stornirati (standardno), izbrisati ili zadržati – s jasno imenovanim posljedicama |
Objedinjavanje istih stavki ključna je razlika. Objedinjena stavka može imati samo jedno podrijetlo. Čim dva retka iz dviju različitih narudžbi izgledaju jednako, veza od računa natrag do narudžbe, zadatka i knjiženja vremena nestaje. U uslužnoj tvrtki u kojoj se obračun odvija putem tiket → evidencija vremena → račun to znači: isti se sati naplaćuju dvaput ili nestaju.
Naš modul stoga načelno ništa ne objedinjuje. Spojeni račun ima točno onoliko stavki koliko ih imaju njegovi izvori zajedno.
Nikakav kôd nije preuzet iz OCA modula. Oba modula su pod licencom AGPL-3.
Što modul radi
U prikazu popisa računa označiti dva ili više nacrta, u izborniku radnji odabrati Spoji nacrte računa – čarobnjak prikazuje odabir, provjere i opcije za izvorne račune. Nakon potvrde odmah se dolazi na novi račun.
Prije spajanja modul provjerava, i to sve točke odjednom, kako se ne bi nakon svakog ispravka vidjela sljedeća pogreška:
- odabrana najmanje dva računa
- svi u statusu Nacrt
- nijedan nikada nije bio knjižen (dokument vraćen u nacrt zadržava povijest knjiženja i odbija se)
- nijedan nije već bio spojen
- svi su račun, ulazni račun, odobrenje ili dokument – ne čisti nalozi za knjiženje
- isti partner, vrsta dokumenta, tvrtka, valuta i dnevnik
Razlike u uvjetima plaćanja, dodjeli poreza, adresi isporuke, bankovnom računu ili prodavaču ne blokiraju. Čarobnjak upozorava i preuzima vrijednost najstarijeg odabranog računa.
Što se zadržava
Srž modula je u tome da svaka veza koju stavka računa ima prema svom podrijetlu postoji i na novom računu:
| Povezivanje | Postupanje |
|---|---|
| Stavke prodajne narudžbe (sale_line_ids) | po stavci se kopira putem Odoo-ova _copy_data_extend_business_fields |
| Stavke narudžbenice (purchase_line_id) | jednako |
| Raspodjela po mjestima troška (analytic_distribution) | kopira se po stavci |
| ostala polja podrijetla po stavci | automatski, ako modul koji ih isporučuje implementira dokumentirani Odoo hook |
| Podrijetlo i referenca u zaglavlju računa | spojeno, deduplicirano, može se uređivati u čarobnjaku |
| Prodajna narudžba: invoice_ids, invoice_status, qty_invoiced | izračunavaju se iz stavki – narudžba sama prepoznaje novi račun |
| Evidencija vremena (timesheet_invoice_id) | prebacuje se na novi račun prije nego što izvori postanu nevažeći |
| Helpdesk tiket | vezan je uz evidenciju vremena i tako automatski upućuje na novi račun |
| Chatter | poruka na novom računu sa svim izvorima, poruka na svakom izvoru s poveznicom na novi |
| merged_into_move_id / merged_from_move_ids | pohranjena veza u oba smjera |
Porezni retci, retci uvjeta plaćanja, retci zaokruživanja i skonta ne kopiraju se, nego ih Odoo iznova izračunava iz sadržaja. Spojeni račun stoga ima jedan porezni redak po poreznoj stopi, a ne jedan po izvoru. Izrađuje se kao nacrt bez broja; samo spajanje ne troši broj dokumenta.
Naslovi: kupac vidi odakle potječe svaka stavka
To je funkcija koja u svakodnevici čini razliku. Iznad stavki svakog izvornog računa modul postavlja redak odjeljka – isti element koji Odoo sam koristi za odjeljak „Predujmovi“:
BE10181 <- naslov
22,500 x Sat tehničara 3.577,50
Međuzbroj 3.577,50
BE10182 · Tiket: HT00219 <- naslov
3,000 x Sat tehničara 477,00
Međuzbroj 477,00
NETO IZNOS 4.054,50
Naslov se ispisuje na dokumentu koji kupac dobiva, a Odoo-ovo standardno izvješće o računu samo od sebe dodaje međuzbroj po odjeljku. Kupac tako vidi i koliko je koštao svaki dio.
Oznaka se sastavlja iz onoga što izvor stvarno ima:
- narudžbe, izravno iz poveznica stavki – nazivi su stoga točni; ako nedostaju poveznice s narudžbama, koristi se slobodan tekst iz invoice_origin
- tiketi, utvrđeni putem evidencije vremena – ispisani kao Tiket: HT00219 ili Tiketi: …, ograničeno na pet plus „i još N“
- ako nema ni jednog ni drugog: broj izvornog računa, zatim njegova referenca
- ako nema ničega: neutralno „Ostale stavke“, kako reci jednog izvora nikada ne bi neprimjetno nestali u bloku iznad
Što nedostaje, izostavlja se, ne izmišlja se. Tiket se ispisuje s vlastitim brojem – helpdesk_mgmt broj, Enterprise ticket_ref ili prikazni naziv modela – nikada s ID-jem baze podataka.
Naslovi se pišu na jeziku kupca, kao što to Odoo čini pri izradi računa iz narudžbi. Chatter ostaje na jeziku korisnika.
Naslov ne nosi proizvod, količinu, cijenu, porez, konto ni raspodjelu po mjestima troška. Ne može utjecati na zbrojeve, poreznu osnovicu i obračun troškova. Tko u čarobnjaku isključi naslove, dobiva točno onaj račun koji bi nastao bez ove funkcije – test uspoređuje oba rezultata do centa.
Što se događa s izvornim računima
Čarobnjak nudi tri opcije:
- Stornirati (standardno, preporučeno). Storniranje nacrta nema posljedica: nema broja dokumenta, nema knjiženja. Računi ostaju u bazi podataka kao stornirani dokumenti s chatterom i poveznicom na novi račun. qty_invoiced na prodajnoj narudžbi preskače stornirane dokumente, ništa se ne broji dvaput. To je opcija koja je razumljiva i za šest mjeseci.
- Izbrisati. Neopozivo. Chatter i povratna referenca nestaju sa zapisom; ostaje samo poruka na novom računu. Smisleno za privremene nacrte koje nitko više ne treba vidjeti.
- Ostaviti kao nacrt. Izvori ostaju otvoreni. Njihove prodajne narudžbe tada računaju izvor i novi račun kao fakturirane – rad izgleda dvaput obračunat dok netko ručno ne počisti nacrte. Namijenjeno rijetkom slučaju kada se prije odluke želi usporediti. Test bilježi upravo to dvostruko brojanje kako nikoga ne bi iznenadilo.
Granice – što modul namjerno ne radi
Molimo pročitajte prije produktivne uporabe:
- Samo nacrti. Knjiženi računi ne spajaju se. To je zadatak za odobrenja, a ne postupak kopiranja.
- Jednom knjiženi, vraćeni nacrti se odbijaju. Oni imaju broj i povijest knjiženja; skrivati to u drugom dokumentu nije nešto što bi skupna radnja trebala tiho obaviti.
- Nema spajanja preko različitih partnera, valuta, tvrtki ili dnevnika. Za to ne postoji smislena pojedinačna vrijednost, pa modul odbija umjesto da pogađa.
- payment_reference se ne preuzima. Strukturirane reference plaćanja identificiraju dokument; dvije spojene jedna za drugom ne odgovaraju ničemu u banci. Novi račun pri knjiženju dobiva vlastitu.
- Stavke predujma kopiraju se kao i svaka druga stavka. Ako izvori sadrže retke predujma iz modula sale, rezultat treba provjeriti prije knjiženja.
- Automatski se prebacuje samo timesheet_invoice_id. Svaki drugi model s poljem Many2one na account.move (vlastiti obračunski ciklusi, poveznice s pretplatama, EDI dostave) i dalje upućuje na izvor. Za to postoji hook _merge_draft_relink.
- Nema objedinjavanja istih stavki – namjerno. Tko želi kratak, uredan račun, ovdje je na pogrešnom mjestu i za to bi platio vezama podrijetla.
- Naslovi nastaju jednokratno pri spajanju. Nakon toga su to normalni retci odjeljaka: mogu se uređivati, brisati i ne ažuriraju se ako se narudžba ili tiket kasnije preimenuju.
- Naslov navodi tikete cijelog izvornog računa, a ne pojedine stavke.
Instalacija u Odoo 19 Community
Instalacija se provodi kao kod uobičajenog dodatnog modula za Odoo:
- Kopirati direktorij modula u konfiguriranu putanju addonsa.
- Ažurirati popis aplikacija u Odoo-u.
- Instalirati modul account_invoice_merge_draft.
Modul ovisi samo o modulu account. Sve što zna o modulima sale, purchase, project, hr_timesheet, sale_timesheet ili helpdesk modulu utvrđuje se za vrijeme izvođenja – instalira se i radi i na čistoj obračunskoj bazi podataka.
Zatim: Računovodstvo → Kupci → Računi (ili Dobavljači → Računi), označiti dva ili više nacrta, izbornik zupčanika → Spoji nacrte računa. Radnja je dostupna svim korisnicima s grupom Obračun / Fakturiranje.
Tko ažurira s ranije verzije modula, trebao bi jednokratno ažurirati s -u account_invoice_merge_draft --i18n-overwrite kako bi izmijenjeni nazivi polja stigli u njemački prijevod. Nova instalacija time nije pogođena.
Za programere: API i hookovi
Spajanje se može pozvati i iz vlastitog kôda:
python
merged = draft_moves.merge_draft_moves(
extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
source_handling="cancel", # "delete" ili "keep"
add_source_sections=True, # naslovi; zadano True
)
merge_draft_moves baca UserError sa svim razlozima odbijanja ako se odabir ne može spojiti. _merge_draft_get_errors() vraća iste razloge kao popis bez iznimke.
Tko ima vlastite povratne reference na account.move, nadjačava _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}
)
Tko ima povratnu referencu na stavci računa, ovaj modul uopće ne treba: Odoo-ov vlastiti hook account.move.line._copy_data_extend_business_fields je dovoljan, a polje se jednako preuzima pri spajanju, storniranju i dupliciranju. Za druge helpdesk module ili drukčiju formulaciju naslova postoje _merge_draft_get_source_tickets() i _merge_draft_section_name().
Testirano prije nego što se pokrene na produktivnim podacima
48 testova pokriva miješane statuse, različite partnere, valute i vrste dokumenata, stavke s prodajnom ili nabavnom narudžbom i bez nje, poreze, odjeljke i bilješke, raspodjele po mjestima troška, prebacivanje evidencije vremena, sve tri opcije za izvorne račune, čarobnjak i naslove – da su standardno aktivni, da njihovo isključivanje reproducira stari rezultat do centa, da naslov ne nosi iznose ni konta, da stavke jednog izvora ostaju zajedno, da se tiket pojavljuje sa svojim brojem, a nikada s ID-jem baze podataka.
Testni podaci temelje se na postojećem kontnom planu aktivne tvrtke, a ne na AccountTestInvoicingCommon – taj helper na migriranim produktivnim bazama podataka često ne pokreće, a modul treba raditi upravo na takvima.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Kao i kod svakog proširenja u računovodstvenom području, preporučujemo da se instalacija najprije provjeri u testnom okruženju s kopijom vlastite baze podataka.
Besplatno i open source za Odoo Community
Mi u Biricon IT Services GmbH rješenja najprije razvijamo za konkretne zahtjeve iz vlastitog poslovanja. Ako iz toga nastane općenito upotrebljiv modul, ne želimo ga skrivati u jednoj instalaciji.
Merge Draft Invoices stoga je besplatno dostupan kao open source projekt. Izvorni se kôd može pregledati, provjeriti, prilagoditi i dalje razvijati u okviru licence AGPL-3.
Naš cilj nije bio što kraći račun, nego račun kod kojeg se i za godinu dana svaka stavka može pratiti natrag do svoje narudžbe, svog tiketa i svog knjiženja vremena.
Projekt, izvorni kôd i suradnja
Cjelokupni izvorni kôd, upute za instalaciju i tehnička dokumentacija dostupni su na GitHubu:
Otvoriti Odoo 19 Merge Draft Invoices na GitHubu
Veseli nas iskustva iz prakse, izvješća o pogreškama, prijedlozi poboljšanja i doprinosi iz Odoo zajednice.
Besplatno. Razumljivo. Open source. Za Odoo 19 Community.