Se, joka laskuttaa Odoossa tilauksista, Helpdesk-tiketeistä ja työajanseurannasta, tuntee lopputuloksen: asiakas saa kuukauden lopussa yhden laskun sijaan viisi. Jokainen on peräisin eri tilauksesta tai tiketistä, jokainen on asiallisesti oikein – ja silti asiakas kysyy, eikö tämä mahtuisi yhteen asiakirjaan.
Juuri siihen Odoo 19 Communityssa ei ole siistiä ratkaisua. Tunnettua OCA-moduulia account_invoice_merge ei ole vielä julkaistu Odoo 19:lle, ja se ratkaisee ongelman tavalla, joka on palveluntarjoajille vakava riski: samat rivit yhdistetään, ja samalla yhteys takaisin tilaukseen, tehtävään ja kirjattuun työaikaan katoaa.
Siksi kehitimme omaa Odoo 19 Community -asennustamme varten moduulin Merge Draft Invoices. Koska tämä ongelma ei koske vain meitä, tarjoamme sen ilmaiseksi GitHubissa vapaalla AGPL-3-lisenssillä.
Projektiin GitHubissa:
Miksi oma moduuli OCA:n ratkaisun sijaan?
OCA ylläpitää account_invoice_merge-moduulia repositoriossa OCA/account-invoicing jo useiden vuosien ajan. Tämän artikkelin kirjoitushetkellä siirto versioon 19.0 on siellä vielä avoin Pull Request, ei julkaistu moduuli.
Tärkeämpää kuitenkin on, että nämä kaksi moduulia tekevät eri asioita:
| OCA account_invoice_merge | Merge Draft Invoices | |
|---|---|---|
| Tulos | yhdistetään yhdeksi valituista laskuista | aina uusi lasku |
| Rivit | samat rivit yhdistetään, määrät lasketaan yhteen | jokainen rivi otetaan yksi yhteen mukaan |
| Alkuperä | invoice_origin / payment_reference yhdistetään otsakkeessa | alkuperäkentät riveittäin kopioidaan Odoon oman _copy_data_extend_business_fields-hookin kautta |
| Työajanseuranta | ei huomioida | timesheet_invoice_id siirretään uuteen laskuun |
| Luettavissa asiakkaalle | ei mitään | otsikko kunkin lähteen rivien yläpuolella, jossa on tilaus ja tiketti |
| Lähdelaskut | mitätöidään | mitätöi (oletus), poista tai säilytä – selkeästi nimetyin seurauksin |
Samojen rivien yhdistäminen on ratkaiseva ero. Yhdistetyllä rivillä voi olla vain yksi alkuperä. Heti kun kaksi riviä kahdesta eri tilauksesta näyttävät samoilta, yhteys laskusta takaisin tilaukseen, tehtävään ja työaikakirjaukseen katoaa. Palveluyrityksessä, jossa laskutus kulkee ketjussa tiketti → työajanseuranta → lasku, tämä tarkoittaa: samat tunnit laskutetaan kahdesti tai ne katoavat.
Moduulimme ei siksi periaatteessa yhdistä mitään. Yhdistetyllä laskulla on täsmälleen yhtä monta riviä kuin sen lähteillä yhteensä.
OCA-moduulista ei ole otettu mitään koodia. Molemmat moduulit ovat AGPL-3-lisenssin alaisia.
Mitä moduuli tekee
Merkitse laskuluettelonäkymässä kaksi tai useampia luonnoksia, valitse toimintovalikosta Yhdistä luonnoslaskut – avustaja näyttää valinnan, tarkistukset ja lähdelaskujen asetukset. Vahvistuksen jälkeen päädyt suoraan uuteen laskuun.
Ennen yhdistämistä moduuli tarkistaa, ja nimenomaan kaikki kohdat kerralla, jotta ei tarvitse nähdä seuraavaa virhettä jokaisen korjauksen jälkeen:
- vähintään kaksi laskua valittu
- kaikki tilassa Luonnos
- mitään ei ole koskaan kirjattu (luonnokseksi palautettu asiakirja säilyttää kirjaushistoriansa ja hylätään)
- mitään ei ole jo yhdistetty
- kaikki ovat laskuja, ostolaskuja, hyvityslaskuja tai kuitteja – ei pelkkiä kirjauksia
- sama kumppani, asiakirjatyyppi, yritys, valuutta ja päiväkirja
Erot maksuehdossa, veroasemassa, toimitusosoitteessa, pankkitilissä tai myyjässä eivät estä yhdistämistä. Avustaja varoittaa ja ottaa vanhimman valitun laskun arvon.
Mikä säilyy
Moduulin ydin on, että jokainen yhteys, joka laskun rivillä on sen alkuperään, on olemassa myös uudella laskulla:
| Yhteys | Käsittely |
|---|---|
| Myyntitilausrivit (sale_line_ids) | riveittäin kopioidaan Odoon _copy_data_extend_business_fields-hookin kautta |
| Ostotilausrivit (purchase_line_id) | samoin |
| Kustannuspaikkajakauma (analytic_distribution) | kopioidaan riveittäin |
| muut alkuperäkentät riviä kohden | automaattisesti, jos toimittava moduuli toteuttaa dokumentoidun Odoo-hookin |
| Alkuperä ja viite laskun otsakkeessa | yhdistetään, poistetaan päällekkäisyydet, muokattavissa avustajassa |
| Myyntitilaus: invoice_ids, invoice_status, qty_invoiced | lasketaan riveistä – tilaus tunnistaa uuden laskun itse |
| Työajanseuranta (timesheet_invoice_id) | siirretään uuteen laskuun ennen kuin lähteet mitätöityvät |
| Helpdesk-tiketti | on kiinni työajanseurannassa ja osoittaa siten automaattisesti uuteen laskuun |
| Chatter | Viesti uudella laskulla kaikkine lähteineen, viesti jokaisella lähteellä linkkinä uuteen |
| merged_into_move_id / merged_from_move_ids | tallennettu yhteys molempiin suuntiin |
Verorivejä, maksuehtorivejä, pyöristys- ja kassa-alennusrivejä ei kopioida vaan Odoo laskee ne uudelleen sisällöstä. Yhdistetyllä laskulla on siis yksi verorivi verokantaa kohden, ei yksi lähdettä kohden. Se luodaan numeroimattomana luonnoksena; yhdistäminen itsessään ei kuluta asiakirjanumeroa.
Otsikot: asiakas näkee, mistä kukin rivi on peräisin
Tämä on toiminto, joka tekee eron arjessa. Jokaisen lähdelaskun rivien yläpuolelle moduuli lisää osiorivin – saman elementin, jota Odoo itse käyttää osiossa ”Ennakkomaksut”:
BE10181 <- Otsikko
22,500 x Teknikkotunti 3.577,50
Välisumma 3.577,50
BE10182 · Tiketti: HT00219 <- Otsikko
3,000 x Teknikkotunti 477,00
Välisumma 477,00
NETTOMÄÄRÄ 4.054,50
Otsikko tulostetaan asiakkaalle annettavaan asiakirjaan, ja Odoon vakiolaskuraportti lisää omatoimisesti välisumman kullekin osiolle. Asiakas näkee siten myös, mitä kukin osa maksoi.
Otsikko muodostetaan siitä, mitä lähteellä todella on:
- tilaukset suoraan rivien yhteyksistä – nimet ovat siten täsmälliset; jos tilausyhteyksiä puuttuu, käytetään invoice_origin-kentän vapaata tekstiä
- tiketit työajanseurannan kautta selvitettyinä – tulostetaan muodossa Tiketti: HT00219 tai Tiketit: …, rajattuna viiteen plus ”ja N muuta”
- jos kumpaakaan ei ole: lähdelaskun numero, sitten sen viite
- jos ei ole mitään: neutraali ”Muut rivit”, jotta lähteen rivit eivät koskaan katoa hiljaa yllä olevaan lohkoon
Mikä puuttuu, jätetään pois, ei keksitä. Tiketti tulostetaan omalla numerollaan – helpdesk_mgmt-numero, Enterprisen ticket_ref tai mallin näyttönimi – ei koskaan tietokannan ID:llä.
Otsikot kirjoitetaan asiakkaan kielellä, aivan kuten Odoo tekee luodessaan laskuja tilauksista. Chatter pysyy käyttäjän kielellä.
Otsikolla ei ole tuotetta, määrää, hintaa, veroa, tiliä eikä kustannuspaikkajakaumaa. Se ei voi vaikuttaa summiin, veropohjaan eikä kustannuslaskentaan. Se, joka kytkee otsikot pois avustajassa, saa täsmälleen sen laskun, joka syntyisi ilman tätä toimintoa – testi vertaa molempia tuloksia sentin tarkkuudella.
Mitä lähdelaskuille tapahtuu
Avustaja tarjoaa kolme vaihtoehtoa:
- Mitätöi (oletus, suositeltu). Luonnoksen mitätöinti on seurauksetonta: ei asiakirjanumeroa, ei kirjausta. Laskut jäävät mitätöityinä asiakirjoina tietokantaan Chatterin ja linkin kanssa uuteen laskuun. Myyntitilauksen qty_invoiced ohittaa mitätöidyt asiakirjat, mitään ei lasketa kahdesti. Tämä on vaihtoehto, joka on jäljitettävissä vielä kuuden kuukauden kuluttuakin.
- Poista. Peruuttamaton. Chatter ja takaisinviittaus katoavat tietueen mukana; vain uuden laskun viesti säilyy. Järkevä kertakäyttöluonnoksille, joita kukaan ei enää halua nähdä.
- Jätä luonnokseksi. Lähteet jäävät avoimiksi. Niiden myyntitilaukset laskevat silloin lähteen ja uuden laskun laskutetuiksi – työ näyttää laskutetun kahdesti, kunnes joku siivoaa luonnokset käsin. Tarkoitettu harvinaiseen tilanteeseen, jossa halutaan verrata ennen päätöstä. Testi kirjaa juuri tämän kaksoislaskennan, jotta se ei yllätä ketään.
Rajat – mitä moduuli tietoisesti ei tee
Lue ennen tuotantokäyttöä:
- Vain luonnokset. Kirjattuja laskuja ei yhdistetä. Se on hyvityslaskun tehtävä, ei kopiointia.
- Kerran kirjatut, palautetut luonnokset hylätään. Niillä on numero ja kirjaushistoria; sen piilottaminen toiseen asiakirjaan ei ole asia, jonka joukkotoiminnon pitäisi hoitaa hiljaa.
- Ei yhdistämistä eri kumppaneiden, valuuttojen, yritysten tai päiväkirjojen välillä. Niille ei ole mielekästä yksittäistä arvoa, joten moduuli kieltäytyy arvaamisen sijaan.
- payment_reference-kenttää ei oteta mukaan. Jäsennellyt maksuviitteet tunnistavat yhden asiakirjan; kaksi yhteen liitettyä ei sovi pankissa mihinkään. Uusi lasku saa kirjattaessa oman viitteensä.
- Ennakkomaksurivit kopioidaan kuten mikä tahansa muu rivi. Jos lähteissä on sale-moduulin ennakkomaksurivejä, tulos on tarkistettava ennen kirjaamista.
- Vain timesheet_invoice_id siirretään automaattisesti. Jokainen muu malli, jolla on Many2one-kenttä account.move-malliin (omat laskutusajot, tilausyhteydet, EDI-lähetykset), osoittaa edelleen lähteeseen. Sitä varten on hook _merge_draft_relink.
- Samoja rivejä ei yhdistetä – tarkoituksella. Se, joka haluaa lyhyen, siistin laskun, on väärässä paikassa ja maksaisi siitä alkuperäyhteyksillä.
- Otsikot syntyvät kertaalleen yhdistettäessä. Sen jälkeen ne ovat tavallisia osiorivejä: muokattavissa, poistettavissa, eikä niitä päivitetä, jos tilaus tai tiketti nimetään myöhemmin uudelleen.
- Otsikko mainitsee koko lähdelaskun tiketit, ei yksittäisen rivin.
Asennus Odoo 19 Communityyn
Asennus tapahtuu kuten tavallisessa Odoon lisämoduulissa:
- Kopioi moduulihakemisto määritettyyn addons-polkuun.
- Päivitä Odoon sovellusluettelo.
- Asenna moduuli account_invoice_merge_draft.
Moduuli riippuu vain moduulista account. Kaikki, mitä se tietää moduuleista sale, purchase, project, hr_timesheet, sale_timesheet tai Helpdesk-moduulista, selvitetään ajon aikana – se asentuu ja toimii myös pelkässä laskutustietokannassa.
Sen jälkeen: Kirjanpito → Asiakkaat → Laskut (tai Toimittajat → Laskut), merkitse kaksi tai useampia luonnoksia, hammasratasvalikko → Yhdistä luonnoslaskut. Toiminto on käytettävissä kaikille käyttäjille, joilla on ryhmä Laskutus.
Moduulin aiemmasta versiosta päivittävän kannattaa kerran päivittää komennolla -u account_invoice_merge_draft --i18n-overwrite, jotta muuttuneet kenttien nimet tulevat saksankieliseen käännökseen. Uudelleenasennus ei vaikuta tähän.
Kehittäjille: API ja hookit
Yhdistämisen voi kutsua myös omasta koodista:
python
merged = draft_moves.merge_draft_moves(
extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
source_handling="cancel", # "delete" tai "keep"
add_source_sections=True, # otsikot; oletus True
)
merge_draft_moves heittää UserErrorin kaikkine hylkäämissyineen, jos valintaa ei voi yhdistää. _merge_draft_get_errors() palauttaa samat syyt luettelona ilman poikkeusta (Exception).
Jos sinulla on omia takaisinviittauksia malliin account.move, ylikirjoita _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}
)
Jos sinulla on takaisinviittaus laskun rivillä, et tarvitse tätä moduulia lainkaan: Odoon oma hook account.move.line._copy_data_extend_business_fields riittää, ja kenttä otetaan mukaan yhtä lailla yhdistettäessä, mitätöitäessä ja monistettaessa. Muita Helpdesk-moduuleja tai otsikoiden erilaista sanamuotoa varten on _merge_draft_get_source_tickets() ja _merge_draft_section_name().
Testattu ennen kuin se ajetaan tuotantotiedoilla
48 testiä kattavat sekalaiset tilat, eri kumppanit, valuutat ja asiakirjatyypit, rivit myynti- tai ostotilauksen kanssa ja ilman, verot, osiot ja muistiinpanot, kustannuspaikkajakaumat, työajanseurannan siirron, kaikki kolme vaihtoehtoa lähdelaskuille, avustajan ja otsikot – että ne ovat oletuksena käytössä, että niiden kytkeminen pois toistaa vanhan tuloksen sentin tarkkuudella, että otsikko ei sisällä summia tai tilejä, että yhden lähteen rivit pysyvät yhdessä, että tiketti näkyy omalla numerollaan eikä koskaan tietokannan ID:llä.
Testitiedot rakentuvat aktiivisen yrityksen olemassa olevalle tilikartalle eikä AccountTestInvoicingCommon-luokalle – tämä apuluokka ei usein käynnisty migroiduissa tuotantotietokannoissa, ja juuri sellaisissa moduulin on tarkoitus toimia.
odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
--test-tags /account_invoice_merge_draft --stop-after-init
Kuten kaikissa kirjanpidon alueen laajennuksissa, suosittelemme tarkistamaan asennuksen ensin testiympäristössä oman tietokannan kopiolla.
Ilmainen ja avoin lähdekoodi Odoo Communitylle
Me Biricon IT Services GmbH:ssa kehitämme ratkaisuja ensin oman toimintamme konkreettisiin tarpeisiin. Kun siitä syntyy yleisesti käytettävä moduuli, emme halua piilottaa sitä yhteen asennukseen.
Merge Draft Invoices on siksi saatavilla ilmaiseksi avoimen lähdekoodin projektina. Lähdekoodia voi tarkastella, tarkistaa, mukauttaa ja kehittää edelleen AGPL-3-lisenssin puitteissa.
Tavoitteemme ei ollut mahdollisimman lyhyt lasku, vaan lasku, jossa vielä vuoden kuluttuakin jokainen rivi johtaa takaisin tilaukseensa, tikettiinsä ja työaikakirjaukseensa.
Projekti, lähdekoodi ja osallistuminen
Koko lähdekoodi, asennusohjeet ja tekninen dokumentaatio ovat saatavilla GitHubissa:
Avaa Odoo 19 Merge Draft Invoices GitHubissa
Otamme mielellämme vastaan käytännön kokemuksia, virheraportteja, parannusehdotuksia ja Odoo Communityn kehitysapua.
Ilmainen. Jäljitettävä. Avoin lähdekoodi. Odoo 19 Communitylle.