Ves al contingut

Odoo 19 Community: fusió de factures en esborrany amb el nostre mòdul de codi obert Merge Draft Invoices

18 de setembre de 2026 per
Odoo 19 Community: fusió de factures en esborrany amb el nostre mòdul de codi obert Merge Draft Invoices
Alexander Biringer | Biricon IT Services GmbH

Qui factura a Odoo a partir de comandes, tiquets d'assistència i registres d'hores ja sap què passa: a final de mes, el client rep cinc factures en lloc d'una. Cadascuna prové d'una comanda o un tiquet diferent i totes són correctes, però el client acaba preguntant si no es podria agrupar tot en un sol document.

Precisament per a això, Odoo 19 Community no ofereix cap solució ben resolta. El conegut mòdul de l'OCA account_invoice_merge encara no està disponible per a Odoo 19 i resol el problema d'una manera que comporta un risc important per a les empreses de serveis: agrupa les línies idèntiques i, en fer-ho, es perd el vincle amb la comanda, la tasca i les hores registrades.

Per això hem desenvolupat el mòdul Merge Draft Invoices per a la nostra pròpia instal·lació d'Odoo 19 Community. Com que aquest problema no ens afecta només a nosaltres, l'oferim gratuïtament a GitHub sota la llicència lliure AGPL-3.

El projecte a GitHub:

Odoo 19 Merge Draft Invoices

Per què un mòdul propi en lloc de la solució de l'OCA?

L'OCA manté account_invoice_merge al repositori OCA/account-invoicing des de fa molts anys. En el moment de publicar aquest article, la migració a la versió 19.0 encara és una sol·licitud d'incorporació oberta, no pas un mòdul publicat.

Però el més important és que els dos mòduls fan coses diferents:


OCA account_invoice_mergeMerge Draft Invoices
Resultates fusiona tot en una de les factures seleccionadessempre una factura nova
Líniess'agrupen les línies idèntiques i se sumen les quantitatscada línia es copia tal qual
Origeninvoice_origin / payment_reference concatenats a la capçaleraes copien els camps d'origen de cada línia mitjançant el punt d'extensió _copy_data_extend_business_fields del mateix Odoo
Registre d'horesno es té en comptetimesheet_invoice_id passa a apuntar a la nova factura
Claredat per al clientcap elementun encapçalament amb la comanda i el tiquet damunt de les línies de cada factura d'origen
Factures d'origenes cancel·lencancel·lar-les (opció predeterminada), suprimir-les o conservar-les, amb les conseqüències clarament explicades

La diferència decisiva és l'agrupació de línies idèntiques. Una línia agrupada només pot tenir un origen. Quan dues línies de dues comandes diferents tenen el mateix contingut, es perd el vincle de la factura amb la comanda, la tasca i el registre d'hores. En una empresa de serveis on la facturació segueix el circuit tiquet → registre d'hores → factura, això vol dir que les mateixes hores es facturen dues vegades o desapareixen.

Per això el nostre mòdul no agrupa mai les línies. La factura resultant té exactament tantes línies com totes les factures d'origen juntes.

No s'ha utilitzat cap codi del mòdul de l'OCA. Tots dos mòduls es distribueixen sota la llicència AGPL-3.

Què fa el mòdul

A la vista de llista de factures, seleccioneu dos esborranys o més i trieu Fusiona les factures en esborrany al menú d'accions. Un assistent mostra la selecció, les comprovacions i les opcions per a les factures d'origen. Un cop confirmeu l'operació, s'obre directament la nova factura.

Abans de fer la fusió, el mòdul comprova tots els punts següents alhora, perquè no aparegui un error nou després de cada correcció:

  • que s'hagin seleccionat almenys dues factures
  • que totes estiguin en estat Esborrany
  • que no se n'hagi comptabilitzat mai cap (un document retornat a l'estat d'esborrany conserva l'historial de comptabilització i es rebutja)
  • que no se n'hagi fusionat cap anteriorment
  • que totes siguin factures de client, factures de proveïdor, factures rectificatives o rebuts, i no simples assentaments comptables
  • que tinguin el mateix contacte, tipus de document, empresa, moneda i diari

Les diferències en les condicions de pagament, la posició fiscal, l'adreça de lliurament, el compte bancari o el comercial no bloquegen l'operació. L'assistent n'avisa i pren el valor de la factura més antiga de les seleccionades.

Què es conserva

La base del mòdul és que tots els vincles entre una línia de factura i el seu origen es conservin també a la nova factura:

VincleTractament
Línies de comanda de venda (sale_line_ids)es copien per a cada línia mitjançant _copy_data_extend_business_fields d'Odoo
Línies de comanda de compra (purchase_line_id)igualment
Distribució per centres de cost (analytic_distribution)es copia per a cada línia
altres camps d'origen de cada líniaautomàticament, si el mòdul que els proporciona implementa el punt d'extensió documentat d'Odoo
Origen i referència a la capçalera de la facturaes combinen, se n'eliminen els duplicats i es poden editar a l'assistent
Comanda de venda: invoice_ids, invoice_status, qty_invoicedes calculen a partir de les línies: la comanda reconeix automàticament la nova factura
Registre d'hores (timesheet_invoice_id)es vincula a la nova factura abans que les factures d'origen deixin de ser vàlides
Tiquet d'assistènciaestà vinculat al registre d'hores i, per tant, passa a apuntar automàticament a la nova factura
Chatterun missatge a la nova factura amb totes les factures d'origen i un missatge a cadascuna d'aquestes amb un enllaç a la nova
merged_into_move_id / merged_from_move_idsvincle desat en tots dos sentits

Les línies d'impostos, de condicions de pagament, d'arrodoniment i de descompte per pagament immediat no es copien: Odoo les torna a calcular a partir del contingut. Per tant, la factura resultant té una línia d'impost per tipus impositiu, no una per factura d'origen. Es crea com a esborrany sense numerar; la fusió en si no consumeix cap número de document.

Encapçalaments: el client veu d'on prové cada línia

Aquesta és la funció que marca la diferència en el dia a dia. Damunt de les línies de cada factura d'origen, el mòdul afegeix una línia de secció, el mateix element que fa servir Odoo per a la secció «Bestretes»:

BE10181                                     <- Encapçalament
  22,500 x Hora de tècnic         3.577,50
                         Subtotal 3.577,50
BE10182 · Tiquet: HT00219                   <- Encapçalament
   3,000 x Hora de tècnic           477,00
                         Subtotal   477,00
                       IMPORT NET 4.054,50

L'encapçalament s'imprimeix al document que rep el client, i l'informe estàndard de factures d'Odoo hi afegeix automàticament un subtotal per secció. Així, el client també veu quant ha costat cada part.

El text de l'encapçalament es compon a partir de les dades que realment té la factura d'origen:

  1. les comandes, obtingudes directament dels vincles de les línies, de manera que els noms són exactes; si no hi ha vincles amb comandes, es fa servir el text lliure d'invoice_origin
  2. els tiquets, identificats a través del registre d'hores; es mostren com a Tiquet: HT00219 o Tiquets: …, amb un màxim de cinc i el text «i N més»
  3. si no hi ha cap de les dues coses: el número de la factura d'origen i, si no en té, la seva referència
  4. si no hi ha cap dada: el text neutre «Altres línies», perquè les línies d'una factura d'origen no quedin mai integrades al bloc anterior sense cap indicació

Les dades que falten s'ometen, no s'inventen. El tiquet s'imprimeix amb el seu propi número —el número de helpdesk_mgmt, el camp ticket_ref d'Enterprise o el nom de visualització del model—, mai amb l'identificador de la base de dades.

Els encapçalaments es redacten en la llengua del client, tal com fa Odoo quan genera factures a partir de comandes. El Chatter es manté en la llengua de l'usuari.

Un encapçalament no té producte, quantitat, preu, impost, compte ni distribució per centres de cost. No pot afectar els totals, la base imposable ni la comptabilitat de costos. Si desactiveu els encapçalaments a l'assistent, obtindreu exactament la factura que es generaria sense aquesta funció: una prova compara tots dos resultats fins a l'últim cèntim.

Què passa amb les factures d'origen

L'assistent ofereix tres opcions:

  • Cancel·lar (opció predeterminada i recomanada). Cancel·lar un esborrany no té conseqüències: no hi ha número de document ni assentament comptable. Les factures es conserven a la base de dades com a documents cancel·lats, amb el Chatter i un enllaç a la nova factura. El camp qty_invoiced de la comanda de venda ignora els documents cancel·lats, de manera que no es compta res dues vegades. Aquesta opció permet seguir el rastre de l'operació fins i tot al cap de sis mesos.
  • Suprimeix. De manera irreversible. El Chatter i l'enllaç de retorn desapareixen amb el registre; només queda el missatge a la factura nova. Útil per a esborranys d'un sol ús que ja no ha de veure ningú.
  • Deixar com a esborrany. Les fonts continuen obertes. Les seves comandes de venda compten llavors la font i la factura nova com a facturades; la feina sembla facturada dues vegades fins que algú netegi els esborranys a mà. Pensat per al cas poc freqüent en què es vol comparar abans de decidir. Una prova recull precisament aquest doble recompte perquè no sorprengui ningú.

Límits: què no fa el mòdul expressament

Llegiu-ho abans de fer-lo servir en producció:

  • Només esborranys. Les factures comptabilitzades no es fusionen. Això requereix factures rectificatives, no una operació de còpia.
  • Es rebutgen els esborranys que ja s'havien comptabilitzat i s'han restablert. Tenen un número i un historial comptable; amagar-los en un altre document no és una cosa que una acció massiva hagi de fer sense avisar.
  • No es poden fusionar factures de contactes, monedes, empreses o diaris diferents. No hi ha cap valor únic coherent per a aquests casos, de manera que el mòdul rebutja l'operació en lloc de fer suposicions.
  • payment_reference no es copia. Les referències de pagament estructurades identifiquen un document; concatenar-ne dues no correspon a res al banc. La factura nova rep la seva pròpia referència quan es comptabilitza.
  • Les línies de bestreta es copien com qualsevol altra línia. Si les factures d'origen contenen línies de bestreta de sale, cal revisar el resultat abans de comptabilitzar-lo.
  • Només es reassigna automàticament timesheet_invoice_id. Qualsevol altre model amb un Many2one a account.move (processos de facturació propis, enllaços amb subscripcions, transmissions EDI) continua apuntant a la factura d'origen. Per a aquests casos hi ha el hook _merge_draft_relink.
  • Les línies iguals no s'agrupen, expressament. Aquesta eina no és adequada per a qui vulgui una factura breu i endreçada, perquè aconseguir-ho implicaria perdre els enllaços amb l'origen.
  • Els encapçalaments es creen una sola vegada, en fusionar. Després són línies de secció normals: es poden editar i suprimir, i no s'actualitzen si més endavant es canvia el nom de la comanda o del tiquet.
  • Cada encapçalament indica els tiquets de tota la factura d'origen, no els de cada línia.

Instal·lació a Odoo 19 Community

S'instal·la com qualsevol mòdul addicional d'Odoo:

  1. Copieu el directori del mòdul en una de les rutes de complements configurades.
  2. Actualitzeu la llista d'aplicacions a Odoo.
  3. Instal·leu el mòdul account_invoice_merge_draft.

El mòdul només depèn d'account. Tot el que necessita saber sobre sale, purchase, project, hr_timesheet, sale_timesheet o qualsevol mòdul de Helpdesk es determina en temps d'execució: també s'instal·la i funciona en una base de dades dedicada exclusivament a la facturació.

A continuació: Comptabilitat → Clients → Factures (o Proveïdors → Factures), seleccioneu dos esborranys o més i aneu al menú de l'engranatge → Fusiona les factures en esborrany. L'acció està disponible per a tots els usuaris del grup Facturació / Facturació.

Si actualitzeu des d'una versió anterior del mòdul, executeu una vegada l'actualització amb -u account_invoice_merge_draft --i18n-overwrite perquè els canvis en les etiquetes dels camps s'apliquin a la traducció alemanya. Això no afecta les instal·lacions noves.

Per a desenvolupadors: API i hooks

La fusió també es pot executar des de codi propi:

python

merged = draft_moves.merge_draft_moves(
    extra_vals={"invoice_date": date(2026, 3, 31), "ref": "Q1"},
    source_handling="cancel",   # "delete" o "keep"
    add_source_sections=True,   # Encapçalaments; True per defecte
)

merge_draft_moves llança un UserError amb tots els motius de rebuig si la selecció no es pot fusionar. _merge_draft_get_errors() retorna els mateixos motius en una llista, sense llançar cap excepció.

Si teniu enllaços de retorn propis a account.move, sobreescriviu _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}
        )

Si teniu un enllaç de retorn a la línia de factura, ni tan sols necessiteu aquest mòdul: n'hi ha prou amb el hook propi d'Odoo account.move.line._copy_data_extend_business_fields, i el camp es copia tant en fusionar com en rectificar i duplicar. Per a altres mòduls de Helpdesk o per canviar el text dels encapçalaments, hi ha _merge_draft_get_source_tickets() i _merge_draft_section_name().

Provat abans de fer-lo servir amb dades de producció

48 proves cobreixen estats combinats, contactes, monedes i tipus de document diferents, línies amb comanda de venda o de compra i sense, impostos, seccions i notes, distribucions per centres de cost, la reassignació dels registres d'hores, les tres opcions per a les factures d'origen, l'assistent i els encapçalaments: que estiguin activats per defecte, que desactivar-los reprodueixi el resultat anterior fins a l'últim cèntim, que cap encapçalament contingui imports ni comptes, que les línies d'una mateixa factura d'origen es mantinguin juntes i que el tiquet aparegui amb el seu número i mai amb l'ID de la base de dades.

Les dades de prova es basen en el pla comptable existent de l'empresa activa en lloc d'AccountTestInvoicingCommon: aquest auxiliar sovint no s'inicia en bases de dades de producció migrades, i és precisament en aquestes on ha de funcionar el mòdul.

odoo-bin -d <db> -u account_invoice_merge_draft --test-enable \
         --test-tags /account_invoice_merge_draft --stop-after-init

Com amb qualsevol extensió de comptabilitat, recomanem comprovar primer la instal·lació en un entorn de proves amb una còpia de la vostra base de dades.

Gratuït i de codi obert per a la comunitat d'Odoo

A Biricon IT Services GmbH desenvolupem solucions, en primer lloc, per a necessitats concretes de la nostra pròpia activitat. Quan en surt un mòdul que pot ser útil per a tothom, no volem deixar-lo amagat en una única instal·lació.

Per això, Merge Draft Invoices està disponible gratuïtament com a projecte de codi obert. El codi font es pot consultar, revisar, adaptar i continuar desenvolupant d'acord amb la llicència AGPL-3.

El nostre objectiu era una factura que, fins i tot al cap d'un any, permetés resseguir cada línia fins a la comanda, el tiquet i el registre d'hores corresponents, més que no pas una factura tan curta com fos possible.

Projecte, codi font i col·laboració

El codi font complet, les instruccions d'instal·lació i la documentació tècnica estan disponibles a GitHub:

Obre Odoo 19 Merge Draft Invoices a GitHub

Agraïm les experiències d'ús, els informes d'errors, els suggeriments de millora i les contribucions de la comunitat d'Odoo.

Gratuït. Traçable. De codi obert. Per a Odoo 19 Community.