Salta al contingut principal
Desenvolupament · Odoo 19, 18 i 17

Mantenir el mateix mòdul a Odoo 19, 18 i 17: què canvia, què es trenca en silenci i com es prova de veritat

Una branca per versió, un build real per versió i un inventari de diferències. El que mossega entre la 17, la 18 i la 19, verificat al codi font d'Odoo, i com ho provem amb 105 mòduls publicats.

Pantalla d'Apps d'Odoo amb mòduls instal·lats en un backend real

Mantenir un mòdul d'Odoo a la 19, la 18 i la 17 alhora no és copiar la carpeta a tres branques i canviar el número del manifest. És una branca de git per versió, un build d'instal·lació real per versió i un inventari de diferències entre versions que es revisa a cada release. Nosaltres mantenim 105 mòduls publicats a les tres versions; aquesta és la llista del que es trenca, incloent-hi els nostres propis errors.

Una fitxa, tres branques (19.0, 18.0 i 17.0 amb el mateix nom tècnic) està explicat a publicar un mòdul a l'App Store d'Odoo. Aquí va el que passa dins de cada branca.

El que canvia entre la 17, la 18 i la 19 i no surt a les guies de novetats

Les novetats d'Odoo 19 parlen de funcionalitat; aquesta taula, del que falla en instal·lar o, pitjor, no falla. Està comprovada contra el codi font de les tres versions, no contra un changelog.

QuèOdoo 17Odoo 18Odoo 19Com falla
Restriccions SQL_sql_constraints_sql_constraintsmodels.ConstraintEn silenci: la 19 avisa i continua; la restricció no existeix
Cron sense límitnumbercall=-1 obligatoriNo existeix el campNo existeix el camp17: sense ell s'executa una vegada i s'apaga. 18/19: amb ell no s'instal·la
Grups de l'usuarigroups_idgroups_idgroup_idsCamp invàlid en instal·lar o executar
Usuaris del grupusersusersuser_idsIgual; ens va rebentar un cron d'avisos a la 17 i la 18
Categoria del grupcategory_idcategory_idprivilege_id → res.groups.privilegeInvalid field 'category_id' in 'res.groups'
Vista de llista<tree><list><list>Tipus de vista invàlid; els xpath //tree no quadren

El més perillós: _sql_constraints

A Odoo 19 _sql_constraints s'ignora: el carregador de models escriu «no longer supported» al log i continua. El mòdul s'instal·la, els tests passen i la restricció d'unicitat no és a la base de dades; un connector que confia en unique(external_id) per no duplicar comandes perd aquesta garantia sense que res ho digui. La forma nova, models.Constraint('unique(...)', 'mensaje'), només existeix a la 19. Odoo porta un conversor, odoo/cli/upgrade_code.py --script 18.1-00-sql-constraint, amb una trampa que vam aprendre migrant les nostres: retorna codi de sortida 1 quan ha canviat fitxers, o sigui, 1 és èxit. L'única cosa que demostra que la restricció és viva és un test que intenti el duplicat.

El que ens va enxampar a nosaltres: numbercall

A Odoo 17, ir.cron.numbercall val 1 per defecte: un cron declarat sense aquest camp s'executa una vegada i es desactiva sol, sense error ni línia de log. Odoo 18 va eliminar el camp i des d'aleshores declarar-lo impedeix instal·lar, així que la branca 17 necessita <field name="numbercall">-1</field> a cada cron i les altres dues no el poden dur. Al setembre de 2026 vam auditar els nostres propis ports a la 17 i vam trobar 14 crons sense aquest camp en quatre connectors, dos amb demo en marxa: ningú prova una sincronització periòdica esperant la segona execució. La correcció són dues línies per cron; el cost va ser no tenir-ho a l'inventari.

El que Odoo 17 valida i la 18 i la 19 deixen passar

El trànsit no va només cap amunt. Odoo 17 exigeix que un camp usat en un modificador de vista (invisible, readonly) sigui a aquesta mateixa vista; la 18 i la 19 no ho comproven. Un mòdul escrit a la 19 i portat cap enrere falla a la 17 amb Field 'state' used in modifier 'invisible' (state == 'draft') must be present in view but is missing. Per això un port no es dona per bo fins que s'instal·la a la versió més antiga. Més casos a un mòdul d'Odoo 19 compila però no s'instal·la i errors de migració a Odoo 19.

Estratègia de branques: una per versió i cap condicional

La branca principal és la de la versió més nova, avui 19.0. Cada canvi es fa i es prova allà i després es porta cap enrere amb cherry-pick a 18.0 i 17.0, adaptant el que la taula obliga a adaptar. El que no fem mai és un sol codi amb if version >= 18 a dins: en surt un mòdul amb les tres API alhora, del qual ningú sap quina de les tres branques està provant. Per als connectors nous generem les branques 18 i 17 des del font de la 19 amb un script que codifica la taula de dalt; no substitueix la prova, substitueix el copiar i enganxar.

Dues excepcions, i totes dues pregunten per una capacitat, no per un número de versió:

  • Detecció de camp o model en temps d'execució. Si el mòdul escriu en un camp que no existeix a totes les versions, es pregunta al registre: 'mobile' in partner._fields o 'hr.expense.sheet' in self.env. Mira el que hi ha, no el que hi hauria de ser.
  • Tests que se salten quan la capacitat no hi és, amb un skipTest condicionat a la presència del camp. El que no val és passar amb 0 de 0: un mòdul nostre tenia zero tests a les tres branques; en escriure-li'n 25 van sortir tres errors reals, un el cron d'avisos que feia servir user_ids a la 17 i la 18.

La prova que val: instal·lar de veritat a les tres

Ni el linter, ni el import, ni una suite verda a la teva màquina demostren que un mòdul s'instal·la: un XML amb numbercall és XML vàlid i un xpath sobre //tree és un xpath vàlid. L'error només apareix quan Odoo carrega el mòdul contra una base de dades d'aquella versió. La nostra regla: un canvi no existeix fins que s'ha instal·lat, net, en un Odoo real de cada versió i, si el mòdul ja és desplegat a algú, també com a actualització des de la versió anterior.

L'upgrade importa per una raó concreta: els grups de base porten noupdate="1", així que un mòdul que afegeix el seu grup de gestor a base.group_system amb implied_ids ho aconsegueix en una instal·lació nova i Odoo ho ignora en silenci en actualitzar. Ho vam veure en una producció d'Odoo.sh: el grup va quedar sense membres i ni l'administrador podia llegir el camp de credencials que en depenia. Cal una migració a migrations/ a més de l'XML, i un test que falli si el grup queda buit.

Fem servir dues classes de banc. Un de proves, un Odoo per versió (en local o en contenidors) que instal·la cada mòdul i executa la seva suite contra les tres: allà van córrer les 37 apps de la nostra primera campanya, allà el connector de Mirakl passa 163 tests a cada versió i el d'Amazon 786 a la 19 i 685 a la 18 i a la 17. I Odoo.sh, un build per branca, per a la instal·lació real. Una volta de gate són uns deu minuts i val cadascun: els dos mòduls que més car ens han costat aquest any tenien tot verd en local i no s'instal·laven (tests verds i el connector falla en producció).

Error propi: la primera campanya de 37 apps la vam donar per acabada amb instal·lació i tests en verd. Quatre dies després, una segona passada sobre 47 apps a Odoo 17 i 18 Community, exercint cada vista amb dades, va deixar 61 de 94 execucions netes: 28 errors d'instal·lació i 4 d'exercici, uns onze errors reals. Xpath sobre //list a la 17, ancoratges sobre camps que no existeixen a la 17. Codi portat cap enrere sense instal·lar-lo a la versió de destinació.

El cost real i quan s'abandona la 17

Tres branques no tripliquen el desenvolupament, perquè la lògica s'escriu una vegada. Tripliquen la prova i la publicació: tres instal·lacions per canvi, tres suites, tres fitxes per revisar, i un inventari que cal rrellegir sencer a cada versió nova, com la 20 (setembre de 2026), que afegeix files a la taula. Odoo treu una versió major l'any i en manté les tres últimes; el dia que publiqui la 20, la 17 surt d'aquesta finestra i es deixa de mantenir. Fins aleshores la branca 17 rep cada correcció: qui continua a la 17 està planificant una migració, i migrarà amb el teu mòdul o sense.

El que sí que redueix el cost és tenir l'inventari com a eina: un escàner de només lectura que assenyala quina línia trepitja quina diferència segons la versió del manifest. El nostre va arribar a marcar 67 mòduls per escriure category_id en un grup, i els 67 estaven bé: aquella línia vivia dins d'un <record> de res.groups.privilege, que és exactament l'arranjament correcte de la 19. La regla mirava la línia i no el registre que l'envolta. Un recompte sense obrir el codi no és una dada, i una regla que assenyala l'arranjament correcte t'ensenya a ignorar l'eina.

Checklist abans de cada push

  1. Restriccions: models.Constraint a la 19, _sql_constraints a la 18 i la 17, i un test que intenta el duplicat.
  2. Crons: numbercall=-1 a tots els de la branca 17; cap a la 18 i la 19.
  3. Grups: group_ids, user_ids i privilege_id (model res.groups.privilege) a la 19; groups_id, users i category_id a la 18 i la 17.
  4. Vistes: <list> a la 19 i la 18, <tree> a la 17; a la 17, tot camp usat en un modificador és a la vista.
  5. Instal·lació neta a les tres, i actualització des de la versió anterior si ja és desplegat.
  6. Suite completa en verd a les tres, i no buida: 0 de 0 no és verd.
  7. Res de __pycache__ al commit. Un git add -A després dels tests ens va posar 1.078 fitxers .pyc a 35 de 37 repositoris.

És el que fem quan un partner d'Odoo ens encarrega mantenir un mòdul seu en diverses versions, o quan una consultoria necessita que un desenvolupament que li funciona a la 17 arribi a la 19 sense perdre la 17. El dreturer és el que descobreix el primer comprador de la fitxa.

Preguntes freqüents

Puc mantenir un sol codi amb condicionals per versió?

L'App Store publica des de branques 17.0, 18.0 i 19.0, així que acabes amb tres branques igualment, només que amb les tres API barrejades a cadascuna. Una branca per versió és més codi repetit i molts menys errors. Les úniques condicions acceptables pregunten per una capacitat, mai pel número de versió.

Per què no n'hi ha prou que compili i que els tests passin en local?

Perquè els errors de versió apareixen en carregar el mòdul contra una base de dades d'aquella versió, no en importar el codi; i alguns, com _sql_constraints a la 19, ni tan sols fallen: cal provar que la restricció existeix.

Enllaços útils dins de FlexigoTech

Publicar un mòdul a l'App Store d'OdooUna fitxa, tres branques: repositori, manifest i re-escaneigUn mòdul d'Odoo 19 compila però no s'instal·laLes trampes d'API de la 19 que l'anàlisi estàtica no veuTests verds i el connector falla en produccióPer què la prova que val és la instal·lació realMigració a Odoo 19Servei de migració amb els mòduls propis inclosos

Tens un mòdul en una versió i el necessites a les tres?

El instal·lem a la 19, la 18 i la 17, et diem què es trenca a cadascuna i el deixem amb una branca per versió i el seu build en verd. Escriu a comercial@flexigobe.com o truca al +34 616 809 504.

Parlar amb un enginyer