Vai al contenuto principale
Sviluppo · Odoo 19, 18 e 17

Mantenere lo stesso modulo su Odoo 19, 18 e 17: cosa cambia, cosa si rompe in silenzio e come si testa davvero

Un ramo per versione, una build reale per versione e un inventario delle differenze. Cosa morde tra 17, 18 e 19, verificato nel codice sorgente di Odoo, e come lo testiamo con 105 moduli pubblicati.

Schermata delle App di Odoo con i moduli installati su un backend reale

Mantenere un modulo Odoo su 19, 18 e 17 contemporaneamente non significa copiare la cartella in tre rami e cambiare il numero nel manifest. Significa un ramo git per versione, una build di installazione reale per versione e un inventario delle differenze tra versioni che si rivede a ogni release. Noi manteniamo 105 moduli pubblicati sulle tre versioni; questo è l'elenco di ciò che si rompe, compresi i nostri errori.

Una scheda, tre rami (19.0, 18.0 e 17.0 con lo stesso nome tecnico) è spiegato in pubblicare un modulo nell'App Store di Odoo. Qui trovi cosa succede dentro ogni ramo.

Cosa cambia tra 17, 18 e 19 e non compare nelle guide alle novità

Le novità di Odoo 19 parlano di funzionalità; questa tabella, di ciò che fallisce all'installazione o, peggio, non fallisce. È verificata sul codice sorgente delle tre versioni, non su un changelog.

CosaOdoo 17Odoo 18Odoo 19Come fallisce
Vincoli SQL_sql_constraints_sql_constraintsmodels.ConstraintIn silenzio: la 19 avvisa e prosegue; il vincolo non esiste
Cron senza limitenumbercall=-1 obbligatorioIl campo non esisteIl campo non esiste17: senza, gira una volta e si spegne. 18/19: con il campo, non si installa
Gruppi dell'utentegroups_idgroups_idgroup_idsCampo non valido in installazione o in esecuzione
Utenti del gruppousersusersuser_idsUguale; ci ha rotto un cron di avvisi in 17 e 18
Categoria del gruppocategory_idcategory_idprivilege_id → res.groups.privilegeInvalid field 'category_id' in 'res.groups'
Vista elenco<tree><list><list>Tipo di vista non valido; gli xpath //tree non corrispondono

Il più pericoloso: _sql_constraints

In Odoo 19 _sql_constraints viene ignorato: il caricatore dei modelli scrive «no longer supported» nel log e prosegue. Il modulo si installa, i test passano e il vincolo di unicità non è nel database; un connettore che si affida a unique(external_id) per non duplicare gli ordini perde quella garanzia senza che nulla lo segnali. La nuova forma, models.Constraint('unique(...)', 'mensaje'), esiste solo nella 19. Odoo include un convertitore, odoo/cli/upgrade_code.py --script 18.1-00-sql-constraint, con una trappola che abbiamo scoperto migrando i nostri: restituisce codice di uscita 1 quando ha modificato dei file, cioè 1 significa successo. L'unica cosa che dimostra che il vincolo è attivo è un test che tenti il duplicato.

Quello che ha fregato noi: numbercall

In Odoo 17, ir.cron.numbercall vale 1 per impostazione predefinita: un cron dichiarato senza quel campo viene eseguito una volta e si disattiva da solo, senza errori né righe di log. Odoo 18 ha eliminato il campo e da lì in poi dichiararlo impedisce l'installazione, quindi il ramo 17 ha bisogno di <field name="numbercall">-1</field> in ogni cron e gli altri due non possono averlo. A settembre 2026 abbiamo fatto un audit dei nostri port a 17 e abbiamo trovato 14 cron senza quel campo in quattro connettori, due con demo attiva: nessuno testa una sincronizzazione periodica aspettando la seconda esecuzione. La correzione sono due righe per cron; il costo è stato non averlo nell'inventario.

Ciò che Odoo 17 valida e 18 e 19 lasciano passare

Il traffico non va solo verso l'alto. Odoo 17 richiede che un campo usato in un modificatore di vista (invisible, readonly) sia presente nella stessa vista; 18 e 19 non lo controllano. Un modulo scritto in 19 e portato all'indietro fallisce in 17 con Field 'state' used in modifier 'invisible' (state == 'draft') must be present in view but is missing. Per questo un port non si considera buono finché non si installa nella versione più vecchia. Altri casi in un modulo di Odoo 19 compila ma non si installa e errori di migrazione a Odoo 19.

Strategia dei rami: uno per versione e nessun condizionale

Il ramo principale è quello della versione più recente, oggi 19.0. Ogni modifica si fa e si testa lì e poi si porta all'indietro con cherry-pick su 18.0 e 17.0, adattando ciò che la tabella obbliga ad adattare. Quello che non facciamo mai è un unico codice con if version >= 18 dentro: ne esce un modulo con le tre API insieme, di cui nessuno sa quale dei tre rami stia testando. Per i connettori nuovi generiamo i rami 18 e 17 dal sorgente della 19 con uno script che codifica la tabella qui sopra; non sostituisce il test, sostituisce il copia e incolla.

Due eccezioni, ed entrambe chiedono una capacità, non un numero di versione:

  • Rilevamento di campo o modello a runtime. Se il modulo scrive in un campo che non esiste in tutte le versioni, si interroga il registro: 'mobile' in partner._fields o 'hr.expense.sheet' in self.env. Guarda ciò che c'è, non ciò che dovrebbe esserci.
  • Test che vengono saltati quando la capacità non c'è, con un skipTest condizionato alla presenza del campo. Quello che non vale è passare con 0 su 0: un nostro modulo aveva zero test nei tre rami; scrivendogliene 25 sono emersi tre errori reali, uno era il cron di avvisi che usava user_ids in 17 e 18.

La prova che conta: installare davvero su tutte e tre

Né il linter, né import, né una suite verde sulla tua macchina dimostrano che un modulo si installa: un XML con numbercall è XML valido e un xpath su //tree è un xpath valido. L'errore compare solo quando Odoo carica il modulo su un database di quella versione. La nostra regola: una modifica non esiste finché non è stata installata, pulita, su un Odoo reale di ogni versione e, se il modulo è già in uso presso qualcuno, anche come aggiornamento dalla versione precedente.

L'upgrade conta per un motivo concreto: i gruppi di base hanno noupdate="1", quindi un modulo che aggiunge il proprio gruppo di responsabile a base.group_system con implied_ids ci riesce in una nuova installazione e Odoo lo ignora in silenzio durante l'aggiornamento. Lo abbiamo visto in una produzione su Odoo.sh: il gruppo è rimasto senza membri e nemmeno l'amministratore poteva leggere il campo delle credenziali che ne dipendeva. Serve una migrazione in migrations/ oltre all'XML, e un test che fallisca se il gruppo resta vuoto.

Usiamo due tipi di banco. Uno di prova, un Odoo per versione (in locale o in container) che installa ogni modulo ed esegue la sua suite sulle tre: lì sono passate le 37 app della nostra prima campagna, lì il connettore di Mirakl supera 163 test in ogni versione e quello di Amazon 786 in 19 e 685 in 18 e in 17. E Odoo.sh, una build per ramo, per l'installazione reale. Un giro di gate dura una decina di minuti e li vale tutti: i due moduli che ci sono costati di più quest'anno avevano tutto verde in locale e non si installavano (test verdi e il connettore fallisce in produzione).

Errore nostro: la prima campagna da 37 app l'abbiamo data per conclusa con installazione e test in verde. Quattro giorni dopo, un secondo passaggio su 47 app in Odoo 17 e 18 Community, facendo lavorare ogni vista con dei dati, ha lasciato 61 esecuzioni pulite su 94: 28 errori di installazione e 4 di esercizio, circa undici errori reali. Xpath su //list in 17, ancoraggi su campi che non esistono in 17. Codice portato all'indietro senza installarlo nella versione di destinazione.

Il costo reale e quando si abbandona la 17

Tre rami non triplicano lo sviluppo, perché la logica si scrive una volta. Triplicano il test e la pubblicazione: tre installazioni per modifica, tre suite, tre schede da controllare e un inventario da rileggere per intero a ogni nuova versione, come la 20 (settembre 2026), che aggiunge righe alla tabella. Odoo rilascia una versione maggiore all'anno e mantiene le ultime tre; il giorno in cui pubblica la 20, la 17 esce da quella finestra e si smette di mantenerla. Fino ad allora il ramo 17 riceve ogni correzione: chi è ancora in 17 sta pianificando una migrazione, e migrerà con il tuo modulo o senza.

Ciò che riduce davvero il costo è avere l'inventario come strumento: uno scanner in sola lettura che segnala quale riga calpesta quale differenza in base alla versione del manifest. Il nostro è arrivato a segnalare 67 moduli per aver scritto category_id in un gruppo, e tutti e 67 erano corretti: quella riga stava dentro un <record> di res.groups.privilege, che è esattamente la correzione giusta della 19. La regola guardava la riga e non il record che la racchiude. Un conteggio senza aprire il codice non è un dato, e una regola che segnala la correzione giusta insegna a ignorare lo strumento.

Checklist prima di ogni push

  1. Vincoli: models.Constraint in 19, _sql_constraints in 18 e 17, e un test che tenta il duplicato.
  2. Cron: numbercall=-1 in tutti quelli del ramo 17; nessuno in 18 e 19.
  3. Gruppi: group_ids, user_ids e privilege_id (modello res.groups.privilege) in 19; groups_id, users e category_id in 18 e 17.
  4. Viste: <list> in 19 e 18, <tree> in 17; in 17, ogni campo usato in un modificatore è nella vista.
  5. Installazione pulita su tutte e tre, e aggiornamento dalla versione precedente se è già in uso.
  6. Suite completa in verde su tutte e tre, e non vuota: 0 su 0 non è verde.
  7. Niente __pycache__ nel commit. Un git add -A dopo i test ci ha messo 1.078 file .pyc in 35 repository su 37.

È ciò che facciamo quando un partner di Odoo ci incarica di mantenere un suo modulo su più versioni, o quando una consulenza ha bisogno che uno sviluppo che le funziona in 17 arrivi alla 19 senza perdere la 17. La scorciatoia è ciò che scopre il primo acquirente della scheda.

Domande frequenti

Posso mantenere un unico codice con condizionali per versione?

L'App Store pubblica dai rami 17.0, 18.0 e 19.0, quindi finisci comunque con tre rami, solo con le tre API mescolate in ciascuno. Un ramo per versione significa più codice ripetuto e molti meno errori. Le uniche condizioni accettabili chiedono una capacità, mai il numero di versione.

Perché non basta che compili e che i test passino in locale?

Perché gli errori di versione compaiono quando il modulo viene caricato su un database di quella versione, non quando si importa il codice; e alcuni, come _sql_constraints in 19, non falliscono nemmeno: bisogna dimostrare che il vincolo esiste.

Link utili dentro FlexigoTech

Pubblicare un modulo nell'App Store di OdooUna scheda, tre rami: repository, manifest e nuova scansioneUn modulo di Odoo 19 compila ma non si installaLe trappole delle API della 19 che l'analisi statica non vedeTest verdi e il connettore fallisce in produzionePerché la prova che conta è l'installazione realeMigrazione a Odoo 19Servizio di migrazione con i moduli propri inclusi

Hai un modulo su una versione e lo vuoi su tutte e tre?

Lo installiamo su 19, 18 e 17, ti diciamo cosa si rompe in ciascuna e lo consegniamo con un ramo per versione e la sua build in verde. Scrivi a comercial@flexigobe.com o chiama il +34 616 809 504.

Parla con un ingegnere