Ko u Odoo-u fakturiše na osnovu narudžbina, helpdesk tiketa i evidencije vremena, poznaje rezultat: kupac na kraju meseca ne dobija jedan račun, već pet. Svaki potiče iz druge narudžbine ili tiketa, svaki je stručno ispravan – a ipak kupac pita da li to ne bi moglo da stane u jedan dokument.
Upravo za to u Odoo 19 Community ne postoji čisto rešenje. Poznati OCA modul account_invoice_merge još nije objavljen za Odoo 19, a problem rešava na način koji za pružaoce usluga predstavlja ozbiljan rizik: iste stavke se objedinjuju, čime se gubi veza nazad ka narudžbini, zadatku i evidentiranom vremenu.
Za našu sopstvenu instalaciju Odoo 19 Community zato smo razvili modul Merge Draft Invoices. Pošto ovaj problem ne pogađa samo nas, besplatno ga stavljamo na raspolaganje na GitHub-u pod slobodnom AGPL-3 licencom.
Na projekat na GitHub-u:
Zašto sopstveni modul umesto OCA rešenja?
OCA već dugi niz godina održava account_invoice_merge u repozitorijumu 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, da ova dva modula rade različite stvari:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Rezultat | spaja se u jedan od izabranih računa | uvek nov račun |
| Stavke | iste stavke se objedinjuju, količine se sabiraju | svaka stavka se preuzima jedan prema jedan |
| Poreklo | invoice_origin / payment_reference spojeni u zaglavlju | polja porekla po stavci kopiraju se preko Odoo-ovog sopstvenog hooka _copy_data_extend_business_fields |
| Evidencija vremena | ne uzima se u obzir | timesheet_invoice_id se prebacuje na novi račun |
| Čitljivo za kupca | ništa | naslov iznad stavki svakog izvora sa narudžbinom i tiketom |
| Izvorni računi | se storniraju | stornirati (podrazumevano), obrisati ili zadržati – sa jasno imenovanim posledicama |
Objedinjavanje istih stavki je presudna razlika. Objedinjena stavka može imati samo jedno poreklo. Čim dve linije iz dve različite narudžbine izgledaju isto, veza od računa nazad ka narudžbini, zadatku i knjiženju vremena nestaje. U uslužnom preduzeću u kome se fakturisanje odvija preko tiket → evidencija vremena → račun, to znači: isti sati se naplaćuju dvostruko ili nestaju.
Naš modul zato u principu ništa ne objedinjuje. Spojeni račun ima tačno onoliko stavki koliko ih imaju svi njegovi izvori zajedno.
Nikakav kod iz OCA modula nije preuzet. Oba modula su pod AGPL-3.
Šta modul radi
U prikazu liste računa označiti dva ili više nacrta, u meniju akcija izabrati Spajanje nacrta računa – čarobnjak prikazuje izbor, provere i opcije za izvorne račune. Nakon potvrde direktno se dospeva na novi račun.
Pre spajanja modul proverava, i to sve tačke odjednom, da se posle svake ispravke ne bi videla sledeća greška:
- izabrana su najmanje dva računa
- svi su u statusu Nacrt
- nijedan nikada nije proknjižen (dokument vraćen na nacrt zadržava svoju istoriju knjiženja i odbija se)
- nijedan nije već spajan
- svi su račun, račun dobavljača, knjižno odobrenje ili priznanica – nikako čisti knjižni nalozi
- isti partner, tip dokumenta, preduzeće, valuta i žurnal
Razlike u uslovima plaćanja, poreskom mapiranju, adresi isporuke, bankovnom računu ili prodavcu ne blokiraju. Čarobnjak upozorava i preuzima vrednost najstarijeg izabranog računa.
Šta ostaje sačuvano
Suština modula je da svaka veza koju stavka računa ima prema svom poreklu postoji i na novom računu:
| Veza | Postupanje |
|---|---|
| Stavke prodajne narudžbine (sale_line_ids) | po stavci se kopira preko Odoo-ovog _copy_data_extend_business_fields |
| Stavke nabavne narudžbine (purchase_line_id) | isto tako |
| Raspodela po mestima troškova (analytic_distribution) | kopira se po stavci |
| druga polja porekla po stavci | automatski, ako modul koji ih isporučuje implementira dokumentovani Odoo hook |
| Poreklo i referenca u zaglavlju računa | spojeno, bez duplikata, može se menjati u čarobnjaku |
| Prodajna narudžbina: invoice_ids, invoice_status, qty_invoiced | izračunavaju se iz stavki – narudžbina sama prepoznaje novi račun |
| Evidencija vremena (timesheet_invoice_id) | prebacuje se na novi račun pre nego što izvori postanu nevažeći |
| Helpdesk tiket | vezan je za evidenciju vremena i time automatski ukazuje na novi račun |
| Chatter | poruka na novom računu sa svim izvorima, poruka na svakom izvoru sa linkom ka novom |
| merged_into_move_id / merged_from_move_ids | sačuvana veza u oba smera |
Poreske linije, linije uslova plaćanja, linije zaokruživanja i skonta ne kopiraju se, već ih Odoo iznova izračunava iz sadržaja. Spojeni račun tako ima jednu poresku liniju po poreskoj stopi, a ne jednu po izvoru. Kreira se bez broja kao nacrt; samo spajanje ne troši broj dokumenta.
Naslovi: kupac vidi odakle potiče svaka stavka
To je funkcija koja u svakodnevnom radu pravi razliku. Iznad stavki svakog izvornog računa modul postavlja liniju odeljka – isti element koji Odoo sam koristi za odeljak „Avansne uplate“:
BE10181 <- naslov
22,500 x Sat tehničara 3.577,50
Međuzbir 3.577,50
BE10182 · Tiket: HT00219 <- naslov
3,000 x Sat tehničara 477,00
Međuzbir 477,00
NETO IZNOS 4.054,50
Naslov se štampa na dokumentu koji kupac dobija, a Odoo-ov standardni izveštaj o računu sam dodaje međuzbir po odeljku. Kupac tako vidi i koliko je koštao svaki deo.
Oznaka se sastavlja iz onoga što izvor zaista ima:
- narudžbine, direktno iz veza stavki – imena su tako tačna; ako nedostaju veze sa narudžbinama, koristi se slobodan tekst iz invoice_origin
- tiketi, utvrđeni preko evidencije vremena – prikazani kao Tiket: HT00219 ili Tiketi: …, ograničeno na pet plus „i još N“
- ako ničeg od toga nema: broj izvornog računa, zatim njegova referenca
- ako nema baš ničeg: neutralno „Dodatne stavke“, kako linije jednog izvora nikada ne bi tiho nestale u bloku iznad
Ono što nedostaje izostavlja se, ne izmišlja. Tiket se štampa sa sopstvenim brojem – broj iz helpdesk_mgmt, Enterprise ticket_ref ili prikazano ime modela – nikada sa ID-jem iz baze podataka.
Naslovi se pišu na jeziku kupca, kao što Odoo to radi pri kreiranju računa iz narudžbina. Chatter ostaje na jeziku korisnika.
Naslov ne nosi ni proizvod, ni količinu, ni cenu, ni porez, ni konto, ni raspodelu po mestima troškova. Ne može da utiče na zbirove, poresku osnovicu i obračun troškova. Ko isključi naslove u čarobnjaku, dobija tačno onaj račun koji bi nastao bez ove funkcije – test poredi oba rezultata do centa.
Šta se dešava sa izvornim računima
Čarobnjak nudi tri opcije:
- Stornirati (podrazumevano, preporučeno). Storniranje nacrta je bez posledica: nema broja dokumenta, nema knjiženja. Računi ostaju u bazi podataka kao stornirani dokumenti sa Chatterom i linkom ka novom računu. qty_invoiced na prodajnoj narudžbini preskače stornirane dokumente, ništa se ne računa dvaput. To je opcija koja je razumljiva i za šest meseci.
- Obrisati. Nepovratno. Chatter i povratna veza nestaju sa zapisom; ostaje samo poruka na novom računu. Smisleno za nacrte za bacanje koje niko više ne treba da vidi.
- Ostaviti kao nacrt. Izvori ostaju otvoreni. Njihove prodajne narudžbine tada računaju izvor i novi račun kao fakturisane – rad izgleda dvostruko naplaćen dok neko ručno ne očisti nacrte. Namenjeno retkom slučaju kada se pre odluke želi uporediti. Test beleži upravo to dvostruko računanje, da nikoga ne iznenadi.
Granice – šta modul namerno ne radi
Molimo pročitajte pre produktivne upotrebe:
- Samo nacrti. Proknjiženi računi se ne spajaju. To je zadatak knjižnog odobrenja, a ne postupak kopiranja.
- Jednom proknjiženi, vraćeni nacrti se odbijaju. Oni imaju broj i istoriju knjiženja; skrivanje toga u drugom dokumentu nije nešto što zbirna akcija treba tiho da obavi.
- Nema spajanja preko različitih partnera, valuta, preduzeća ili žurnala. Za to ne postoji smislena pojedinačna vrednost, pa modul odbija umesto da pogađa.
- payment_reference se ne preuzima. Strukturirane reference plaćanja identifikuju jedan dokument; dve spojene ne odgovaraju ničemu u banci. Novi račun pri knjiženju dobija sopstvenu.
- Stavke avansa kopiraju se kao i svaka druga stavka. Ako izvori sadrže linije avansa iz modula sale, rezultat treba proveriti pre knjiženja.
- Automatski se prebacuje samo timesheet_invoice_id. Svaki drugi model sa Many2one poljem na account.move (sopstveni obračunski ciklusi, veze sa pretplatama, EDI slanja) i dalje pokazuje na izvor. Za to postoji hook _merge_draft_relink.
- Nema objedinjavanja istih stavki – namerno. Ko želi kratak, uredan račun, ovde nije na pravom mestu i platio bi to vezama porekla.
- Naslovi nastaju jednokratno pri spajanju. Posle toga su to obične linije odeljka: mogu se menjati, brisati i ne ažuriraju se ako se narudžbina ili tiket naknadno preimenuju.
- Naslov navodi tikete celog izvornog računa, a ne pojedinačne stavke.
Instalacija u Odoo 19 Community
Instalacija se vrši kao kod uobičajenog dodatnog Odoo modula:
- Kopirati direktorijum modula u konfigurisanu putanju addons.
- Ažurirati listu aplikacija u Odoo-u.
- Instalirati modul account_invoice_merge_draft.
Modul zavisi samo od account. Sve što zna o sale, purchase, project, hr_timesheet, sale_timesheet ili helpdesk modulu utvrđuje se tokom rada – instalira se i funkcioniše i na čistoj bazi za fakturisanje.
Zatim: Knjigovodstvo → Kupci → Računi (ili Dobavljači → Računi), označiti dva ili više nacrta, meni sa zupčanikom → Spajanje nacrta računa. Akcija je dostupna svim korisnicima sa grupom Obračun / Fakturisanje.
Ko ažurira sa ranije verzije modula, treba jednokratno da izvrši ažuriranje sa -u account_invoice_merge_draft --i18n-overwrite, kako bi izmenjeni nazivi polja stigli u nemački prevod. Nova instalacija time nije pogođena.
Za programere: API i hookovi
Spajanje se može pozvati i iz sopstvenog 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; podrazumevano True
)
merge_draft_moves baca UserError sa svim razlozima odbijanja ako izbor nije moguće spojiti. _merge_draft_get_errors() vraća iste razloge kao listu bez izuzetka.
Ko ima sopstvene povratne reference na account.move, prepisuje _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}
)
Ko ima povratnu referencu na stavci računa, ovaj modul uopšte ne treba: Odoo-ov sopstveni hook account.move.line._copy_data_extend_business_fields je dovoljan, a polje se preuzima podjednako pri spajanju, storniranju i dupliranju. Za druge helpdesk module ili drugačiju formulaciju naslova postoje _merge_draft_get_source_tickets() i _merge_draft_section_name().
Testirano pre nego što radi na produktivnim podacima
48 testova pokriva pomešane statuse, različite partnere, valute i tipove dokumenata, stavke sa i bez prodajne ili nabavne narudžbine, poreze, odeljke i beleške, raspodele po mestima troškova, prebacivanje evidencije vremena, sve tri opcije za izvorne račune, čarobnjak i naslove – da su podrazumevano aktivni, da njihovo isključivanje reprodukuje 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 sa ID-jem iz baze podataka.
Testni podaci se grade na postojećem kontnom planu aktivne firme umesto na AccountTestInvoicingCommon – taj helper na migriranim produktivnim bazama često ne startuje, a upravo na takvima modul treba da radi.
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 oblasti knjigovodstva, preporučujemo da se instalacija prvo proveri u test okruženju sa kopijom sopstvene baze podataka.
Besplatno i open source za Odoo Community
Mi u Biricon IT Services GmbH rešenja prvo razvijamo za konkretne zahteve iz sopstvenog poslovanja. Ako iz toga nastane opšte upotrebljiv modul, ne želimo da ga sakrivamo u jednoj instalaciji.
Merge Draft Invoices zato je besplatno dostupan kao open source projekat. Izvorni kod se može pregledati, proveriti, prilagoditi i dalje razvijati u okviru AGPL-3 licence.
Naš cilj nije bio najkraći mogući račun, već račun kod koga i za godinu dana svaka stavka još uvek vodi nazad do svoje narudžbine, svog tiketa i svog knjiženja vremena.
Projekat, izvorni kod i saradnja
Kompletan izvorni kod, uputstva za instalaciju i tehnička dokumentacija dostupni su na GitHub-u:
Otvoriti Odoo 19 Merge Draft Invoices na GitHub-u
Radujemo se iskustvima iz prakse, izveštajima o greškama, predlozima za poboljšanje i doprinosima iz Odoo zajednice.
Besplatno. Razumljivo. Open source. Za Odoo 19 Community.