Svako ko u sistemu Odoo izdaje račune na osnovu narudžbi, zahtjeva za podršku i evidencije radnog vremena zna kako to izgleda: na kraju mjeseca kupac dobije pet računa umjesto jednog. Svaki potiče iz druge narudžbe ili zahtjeva za podršku, svaki je ispravan – a kupac ipak pita može li se sve objediniti u jednom dokumentu.
Upravo za to Odoo 19 Community nema odgovarajuće rješenje. Poznati OCA modul account_invoice_merge još nije objavljen za Odoo 19, a problem rješava na način koji za pružaoce usluga predstavlja ozbiljan rizik: iste stavke se objedinjuju, čime se gubi veza s narudžbom, zadatkom i evidentiranim radnim vremenom.
Zato smo za vlastitu instalaciju sistema Odoo 19 Community razvili modul Merge Draft Invoices. Budući da se s ovim problemom ne susrećemo samo mi, besplatno ga stavljamo na raspolaganje na GitHubu pod slobodnom licencom AGPL-3.
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 vrijeme objavljivanja ovog članka migracija na verziju 19.0 još je otvoren zahtjev za spajanje izmjena, a ne objavljen modul.
Još je važnije to što ova dva modula rade različite stvari:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Rezultat | objedinjuje se u jedan od odabranih računa | uvijek novi račun |
| Stavke | iste stavke se objedinjuju, a količine sabiraju | svaka stavka se prenosi jedan prema jedan |
| Porijeklo | invoice_origin / payment_reference spajaju se u zaglavlju | polja porijekla kopiraju se za svaku stavku putem Odooove ugrađene metode _copy_data_extend_business_fields |
| Evidencija radnog vremena | ne uzima se u obzir | timesheet_invoice_id preusmjerava se na novu fakturu |
| Pregledno za kupca | ništa | naslov iznad stavki svake izvorne fakture, s narudžbom i tiketom |
| Izvorne fakture | storniraju se | stornirati (zadano), izbrisati ili zadržati – uz jasno navedene posljedice |
Objedinjavanje istih stavki ključna je razlika. Objedinjena stavka može imati samo jedno porijeklo. Čim dva reda iz dvije različite narudžbe izgledaju isto, gubi se veza od fakture do narudžbe, zadatka i unosa radnog vremena. U uslužnoj firmi u kojoj fakturisanje ide putem tiket → evidencija radnog vremena → faktura, to znači da se isti sati naplaćuju dvaput ili nestaju.
Zato naš modul u pravilu ne objedinjuje stavke. Spojena faktura ima tačno onoliko stavki koliko sve njene izvorne fakture zajedno.
Nije preuzet nikakav kod iz OCA modula. Oba modula dostupna su pod licencom AGPL-3.
Šta modul radi
U prikazu liste faktura označite dva ili više nacrta i u meniju radnji odaberite Spoji nacrte faktura – čarobnjak prikazuje odabrane fakture, provjere i opcije za izvorne fakture. Nakon potvrde direktno se otvara nova faktura.
Prije spajanja modul provjerava sve sljedeće uslove odjednom, kako se nakon svake ispravke ne bi pojavljivala nova greška:
- odabrane su najmanje dvije fakture
- sve su u statusu Nacrt
- nijedna nikada nije bila proknjižena (dokument vraćen u status nacrta zadržava historiju knjiženja i bit će odbijen)
- nijedna već nije spojena
- sve su izlazne fakture, ulazne fakture, knjižna odobrenja ili potvrde – nisu obični nalozi za knjiženje
- isti partner, vrsta dokumenta, preduzeće, valuta i dnevnik
Razlike u uslovima plaćanja, fiskalnoj poziciji, adresi isporuke, bankovnom računu ili prodavaču ne sprečavaju spajanje. Čarobnjak prikazuje upozorenje i preuzima vrijednost s najstarije odabrane fakture.
Šta se zadržava
Suština modula je u tome da svaka veza stavke fakture s njenim izvorom ostaje sačuvana i na novoj fakturi:
| Veza | Postupanje |
|---|---|
| Stavke prodajne narudžbe (sale_line_ids) | kopiraju se za svaku stavku putem Odooove metode _copy_data_extend_business_fields |
| Stavke nabavne narudžbe (purchase_line_id) | isto |
| Raspodjela po mjestima troška (analytic_distribution) | kopira se za svaku stavku |
| ostala polja porijekla po stavci | automatski, ako modul koji ih dodaje implementira dokumentovanu Odooovu metodu za proširenje |
| Porijeklo i referenca u zaglavlju fakture | spajaju se uz uklanjanje duplikata i mogu se uređivati u čarobnjaku |
| Prodajna narudžba: invoice_ids, invoice_status, qty_invoiced | izračunavaju se na osnovu stavki – narudžba sama prepoznaje novu fakturu |
| Evidencija radnog vremena (timesheet_invoice_id) | preusmjerava se na novu fakturu prije nego što izvorne fakture postanu nevažeće |
| Tiket korisničke podrške | povezan je s evidencijom radnog vremena i tako automatski upućuje na novu fakturu |
| Chatter | poruka na novoj fakturi sa svim izvornim fakturama, poruka na svakoj izvornoj fakturi s poveznicom na novu |
| merged_into_move_id / merged_from_move_ids | sačuvana veza u oba smjera |
Redovi poreza, uslova plaćanja, zaokruživanja i skonta ne kopiraju se, već ih Odoo ponovo izračunava na osnovu sadržaja. Spojena faktura zato ima jedan red poreza po poreskoj stopi, a ne po izvornoj fakturi. Kreira se kao nacrt bez broja; samo spajanje ne troši broj dokumenta.
Naslovi: kupac vidi odakle potiče svaka stavka
Ova funkcija pravi razliku u svakodnevnom radu. Iznad stavki svake izvorne fakture modul umeće red odjeljka – isti element koji i sam Odoo koristi za odjeljak „Avansi":
BE10181 <- Naslov
22,500 x Sat rada tehničara 3.577,50
Međuzbir 3.577,50
BE10182 · Tiket: HT00219 <- Naslov
3,000 x Sat rada tehničara 477,00
Međuzbir 477,00
NETO IZNOS 4.054,50
Naslov se ispisuje na dokumentu koji kupac dobija, a Odooov standardni izvještaj fakture automatski dodaje međuzbir za svaki odjeljak. Kupac tako vidi i koliko je svaki dio koštao.
Naslov se sastavlja od podataka koji zaista postoje na izvornoj fakturi:
- narudžbe, direktno iz veza na stavkama – nazivi su zato tačni; ako nema veza s narudžbama, koristi se slobodni tekst iz polja invoice_origin
- tiketi, utvrđeni putem evidencije radnog vremena – prikazuju se kao Tiket: HT00219 ili Tiketi: …, najviše pet, uz dodatak „i još N"
- ako nema ni jednog ni drugog: broj izvorne fakture, a zatim njena referenca
- ako nema ničega: neutralni naslov „Dodatne stavke", kako se redovi jedne izvorne fakture nikada ne bi neprimjetno stopili s prethodnim blokom
Ono što nedostaje izostavlja se, ne izmišlja se. Tiket se ispisuje s vlastitim brojem – brojem iz modula helpdesk_mgmt, poljem ticket_ref iz izdanja Enterprise ili prikaznim nazivom modela – nikada s ID-jem iz baze podataka.
Naslovi se pišu na jeziku kupca, kao što Odoo radi pri kreiranju faktura iz narudžbi. Chatter ostaje na jeziku korisnika.
Naslov ne sadrži proizvod, količinu, cijenu, porez, konto ni raspodjelu po mjestima troška. Ne može utjecati na ukupne iznose, poresku osnovicu ni obračun troškova. Ako u čarobnjaku isključite naslove, dobit ćete potpuno istu fakturu kakva bi nastala bez te funkcije – test poredi oba rezultata do posljednjeg centa.
Šta se dešava s izvornim fakturama
Čarobnjak nudi tri opcije:
- Storniraj (zadano, preporučeno). Storniranje nacrta nema posljedica: nema broja dokumenta ni knjiženja. Fakture ostaju u bazi podataka kao stornirani dokumenti s Chatterom i poveznicom na novu fakturu. Polje qty_invoiced na prodajnoj narudžbi preskače stornirane dokumente, pa se ništa ne računa dvaput. Ova opcija omogućava da se i za šest mjeseci jasno vidi šta je urađeno.
- Izbriši. Nepovratno. Chatter i povratna veza nestaju zajedno sa zapisom; ostaje samo poruka na novoj fakturi. Prikladno za privremene nacrte koje više niko ne treba vidjeti.
- Ostavi kao nacrt. Izvorne fakture ostaju otvorene. Njihove prodajne narudžbe tada računaju izvornu i novu fakturu kao fakturisane – izgleda kao da je rad obračunat dvaput, sve dok neko ručno ne sredi nacrte. Namijenjeno rijetkim slučajevima kada prije odluke želite uporediti fakture. Jedan test izričito potvrđuje ovo dvostruko računanje kako nikoga ne bi iznenadilo.
Ograničenja – šta modul namjerno ne radi
Pročitajte prije upotrebe u produkciji:
- Samo nacrti. Proknjižene fakture se ne spajaju. To se rješava knjižnim odobrenjima, a ne kopiranjem.
- Nacrti koji su ranije bili proknjiženi pa vraćeni u status nacrta odbijaju se. Oni imaju broj i historiju knjiženja; njihovo prikrivanje u drugom dokumentu nije nešto što bi grupna radnja trebala obaviti neprimjetno.
- Nema spajanja faktura s različitim partnerima, valutama, preduzećima ili dnevnicima. Za njih ne postoji smislena zajednička vrijednost, pa modul odbija spajanje umjesto da nagađa.
- payment_reference se ne prenosi. Strukturirane reference plaćanja identificiraju jedan dokument; dvije spojene reference ne odgovaraju ničemu u banci. Nova faktura pri knjiženju dobija vlastitu referencu.
- Avansne stavke kopiraju se kao i sve ostale stavke. Ako izvorne fakture sadrže avansne redove iz modula sale, rezultat treba provjeriti prije knjiženja.
- Samo timesheet_invoice_id automatski se preusmjerava. Svaki drugi model s poljem Many2one koje upućuje na account.move (vlastiti postupci fakturisanja, veze s pretplatama, EDI prijenosi) i dalje upućuje na izvornu fakturu. Za to postoji metoda za proširenje _merge_draft_relink.
- Nema objedinjavanja istih stavki – namjerno. Ako želite kratku, sažetu fakturu, ovaj modul nije za vas, jer biste to platili gubitkom veza s izvorima.
- Naslovi se kreiraju samo jednom, pri spajanju. Nakon toga su obični redovi odjeljaka: mogu se uređivati i brisati, a ne ažuriraju se ako se narudžba ili tiket kasnije preimenuju.
- Naslov navodi tikete cijele izvorne fakture, a ne pojedinačne stavke.
Instalacija u Odoo 19 Community
Instalacija se obavlja kao i za svaki uobičajeni dodatni Odoo modul:
- Kopirajte direktorij modula na jednu od konfigurisanih putanja za dodatke.
- Ažurirajte listu aplikacija u Odoou.
- Instalirajte modul account_invoice_merge_draft.
Modul zavisi samo od modula account. Sve što zna o modulima sale, purchase, project, hr_timesheet, sale_timesheet ili nekom modulu za korisničku podršku utvrđuje se tokom izvršavanja – instalira se i radi i na bazi podataka koja služi isključivo za fakturisanje.
Zatim: Računovodstvo → Kupci → Fakture (ili Dobavljači → Fakture), označite dva ili više nacrta, meni sa zupčanikom → Spoji nacrte faktura. Radnja je dostupna svim korisnicima iz grupe Obračun / Fakturisanje.
Ako nadograđujete raniju verziju modula, jednom pokrenite ažuriranje s opcijama -u account_invoice_merge_draft --i18n-overwrite kako bi izmijenjeni nazivi polja bili uključeni u njemački prijevod. Ovo se ne odnosi na novu instalaciju.
Za programere: API i metode za proširenje
Spajanje se može pozvati i iz vlastitog koda:
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 podiže izuzetak UserError sa svim razlozima odbijanja ako se odabrane fakture ne mogu spojiti. _merge_draft_get_errors() vraća iste razloge kao listu, bez izuzetka.
Ako imate vlastite povratne veze na account.move, redefinišite _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}
)
Ako imate povratnu vezu na stavci fakture, ovaj modul vam uopće nije potreban: dovoljna je Odooova ugrađena metoda account.move.line._copy_data_extend_business_fields i polje će se prenositi pri spajanju, storniranju i dupliciranju. Za druge module korisničke podrške ili drugačije formulacije naslova postoje _merge_draft_get_source_tickets() i _merge_draft_section_name().
Testirano prije rada s produkcijskim podacima
Ukupno 48 testova obuhvata različite statuse, partnere, valute i vrste dokumenata, stavke sa i bez prodajne ili nabavne narudžbe, poreze, odjeljke i napomene, raspodjele po mjestima troška, preusmjeravanje evidencije radnog vremena, sve tri opcije za izvorne fakture, čarobnjak i naslove – da su zadano uključeni, da njihovo isključivanje daje prethodni rezultat do posljednjeg centa, da naslov ne sadrži iznose ni konta, da stavke iste izvorne fakture ostaju zajedno i da se tiket prikazuje s vlastitim brojem, a nikada s ID-jem iz baze podataka.
Testni podaci zasnivaju se na postojećem kontnom planu aktivnog preduzeća, umjesto na klasi AccountTestInvoicingCommon – taj pomoćni alat često se ne može pokrenuti na migriranim produkcijskim bazama podataka, a modul treba raditi upravo na takvim bazama.
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 za računovodstvo, preporučujemo da instalaciju prvo provjerite u testnom okruženju s kopijom vlastite baze podataka.
Besplatno i otvorenog koda za Odoo Community
U kompaniji Biricon IT Services GmbH rješenja prvo razvijamo za konkretne potrebe vlastitog poslovanja. Kada iz toga nastane modul koji može koristiti i drugima, ne želimo ga zadržati samo u jednoj instalaciji.
Zato je Merge Draft Invoices besplatno dostupan kao projekt otvorenog koda. Izvorni kod može se pregledati, provjeriti, prilagoditi i dalje razvijati u okviru licence AGPL-3.
Naš cilj nije bila najkraća moguća faktura, već faktura na kojoj se i za godinu dana svaka stavka može povezati s pripadajućom narudžbom, tiketom i unosom radnog vremena.
Projekt, izvorni kod i doprinosi
Kompletan izvorni kod, upute za instalaciju i tehnička dokumentacija dostupni su na GitHubu:
Otvori Odoo 19 Merge Draft Invoices na GitHubu
Radujemo se iskustvima iz prakse, prijavama grešaka, prijedlozima za poboljšanje i doprinosima iz zajednice Odoo Community.
Besplatno. Sljedivo. Otvorenog koda. Za Odoo 19 Community.