Een Odoo-module tegelijk onderhouden op 19, 18 en 17 betekent niet de map naar drie branches kopiëren en het versienummer in de manifest aanpassen. Het is één git-branch per versie, één echte installatiebuild per versie en een overzicht van verschillen tussen versies dat bij elke release wordt herzien. Wij onderhouden 105 gepubliceerde modules op de drie versies; dit is de lijst van wat er breekt, inclusief onze eigen fouten.
Eén vermelding, drie branches (19.0, 18.0 en 17.0 met dezelfde technische naam) wordt uitgelegd in een module publiceren in de Odoo App Store. Hier gaat het over wat er binnen elke branch gebeurt.
Wat verandert tussen 17, 18 en 19 en niet in de overzichten van nieuwigheden staat
De nieuwigheden van Odoo 19 gaan over functionaliteit; deze tabel gaat over wat misgaat bij de installatie of, erger, niet misgaat. Ze is gecontroleerd aan de broncode van de drie versies, niet aan een changelog.
| Wat | Odoo 17 | Odoo 18 | Odoo 19 | Hoe het faalt |
|---|---|---|---|---|
| SQL-constraints | _sql_constraints | _sql_constraints | models.Constraint | Stilletjes: 19 waarschuwt en gaat door; de constraint bestaat niet |
| Cron zonder limiet | numbercall=-1 verplicht | Het veld bestaat niet | Het veld bestaat niet | 17: zonder dit veld draait hij één keer en schakelt zichzelf uit. 18/19: met dit veld lukt de installatie niet |
| Groepen van de gebruiker | groups_id | groups_id | group_ids | Ongeldig veld bij installatie of uitvoering |
| Gebruikers van de groep | users | users | user_ids | Hetzelfde; het liet een meldingscron van ons crashen op 17 en 18 |
| Categorie van de groep | category_id | category_id | privilege_id → res.groups.privilege | Invalid field 'category_id' in 'res.groups' |
| Lijstweergave | <tree> | <list> | <list> | Ongeldig weergavetype; de xpath's op //tree komen niet overeen |
De gevaarlijkste: _sql_constraints
In Odoo 19 wordt _sql_constraints genegeerd: de modellader schrijft "no longer supported" in de log en gaat door. De module installeert, de tests slagen en de uniciteitsconstraint staat niet in de database; een connector die op unique(external_id) vertrouwt om geen dubbele orders aan te maken, verliest die garantie zonder dat iets het meldt. De nieuwe vorm, models.Constraint('unique(...)', 'mensaje'), bestaat alleen in 19. Odoo levert een conversietool mee, odoo/cli/upgrade_code.py --script 18.1-00-sql-constraint, met een valkuil die we leerden bij het migreren van onze eigen modules: hij geeft exitcode 1 terug als hij bestanden heeft gewijzigd, dus 1 betekent geslaagd. Het enige dat bewijst dat de constraint werkt, is een test die de dubbele invoer probeert.
Degene die ons te pakken nam: numbercall
In Odoo 17 staat ir.cron.numbercall standaard op 1: een cron zonder dat veld draait één keer en schakelt zichzelf uit, zonder fout en zonder logregel. Odoo 18 heeft het veld verwijderd en sindsdien blokkeert het declareren ervan de installatie, dus de 17-branch heeft <field name="numbercall">-1</field> nodig in elke cron en de andere twee mogen het niet bevatten. In september 2026 hebben we onze eigen ports naar 17 gecontroleerd en vonden we 14 crons zonder dat veld in vier connectoren, twee met een draaiende demo: niemand test een periodieke synchronisatie door op de tweede uitvoering te wachten. De oplossing is twee regels per cron; de kosten zaten in het ontbreken ervan in het overzicht.
Wat Odoo 17 valideert en 18 en 19 laten passeren
Het verkeer gaat niet alleen omhoog. Odoo 17 eist dat een veld dat in een weergavemodifier wordt gebruikt (invisible, readonly) in diezelfde weergave staat; 18 en 19 controleren dat niet. Een module die in 19 is geschreven en naar achteren is geport, faalt in 17 met Field 'state' used in modifier 'invisible' (state == 'draft') must be present in view but is missing. Daarom geldt een port pas als goed wanneer hij op de oudste versie is geïnstalleerd. Meer gevallen in een Odoo 19-module compileert maar installeert niet en migratiefouten naar Odoo 19.
Branchestrategie: één per versie en geen voorwaarden
De hoofdbranch is die van de nieuwste versie, nu 19.0. Elke wijziging wordt daar gemaakt en getest en daarna met cherry-pick naar achteren gebracht naar 18.0 en 17.0, waarbij we aanpassen wat de tabel afdwingt. Wat we nooit doen, is één code met if version >= 18 erin: dan krijg je een module met alle drie de API's tegelijk, waarvan niemand weet welke van de drie branches wordt getest. Voor nieuwe connectoren genereren we de branches 18 en 17 uit de broncode van 19 met een script dat de tabel hierboven codeert; het vervangt de test niet, het vervangt het kopiëren en plakken.
Twee uitzonderingen, en beide vragen naar een mogelijkheid, niet naar een versienummer:
- Detectie van veld of model tijdens de uitvoering. Als de module naar een veld schrijft dat niet in alle versies bestaat, vraag je het aan het register:
'mobile' in partner._fieldsof'hr.expense.sheet' in self.env. Het kijkt naar wat er is, niet naar wat er zou moeten zijn. - Tests die worden overgeslagen als de mogelijkheid ontbreekt, met een
skipTestdie afhangt van de aanwezigheid van het veld. Wat niet door de beugel kan, is slagen met 0 van 0: een module van ons had nul tests in alle drie de branches; toen we er 25 voor schreven, kwamen er drie echte fouten uit, waaronder de meldingscron dieuser_idsgebruikte in 17 en 18.
De test die telt: echt installeren op alle drie
Noch de linter, noch import, noch een groene suite op je machine bewijst dat een module installeert: een XML met numbercall is geldige XML en een xpath op //tree is een geldige xpath. De fout komt pas aan het licht wanneer Odoo de module laadt in een database van die versie. Onze regel: een wijziging bestaat pas als ze schoon is geïnstalleerd op een echte Odoo van elke versie en, als de module al bij iemand draait, ook als update vanaf de vorige versie.
De upgrade is belangrijk om een concrete reden: de groepen van base hebben noupdate="1", dus een module die zijn beheerdersgroep toevoegt aan base.group_system met implied_ids krijgt dat bij een nieuwe installatie voor elkaar, terwijl Odoo het bij een update stilletjes negeert. We zagen het in een Odoo.sh-productieomgeving: de groep bleef zonder leden en zelfs de beheerder kon het inloggegevensveld dat ervan afhing niet lezen. Naast de XML is een migratie in migrations/ nodig, en een test die faalt als de groep leeg blijft.
We gebruiken twee soorten testomgeving. Een testbank, één Odoo per versie (lokaal of in containers) die elke module installeert en de suite ervan op alle drie draait: daar draaiden de 37 apps van onze eerste campagne, daar slaagt de Mirakl-connector voor 163 tests per versie en die van Amazon voor 786 in 19 en 685 in 18 en 17. En Odoo.sh, één build per branch, voor de echte installatie. Een gate-ronde duurt ongeveer tien minuten en elke minuut is het waard: de twee modules die ons dit jaar het duurst zijn komen te staan, waren lokaal helemaal groen en installeerden niet (groene tests en de connector faalt in productie).
Eigen fout: de eerste campagne van 37 apps beschouwden we als afgerond met groene installatie en tests. Vier dagen later liet een tweede ronde over 47 apps op Odoo 17 en 18 Community, waarbij elke weergave met gegevens werd uitgeprobeerd, 61 van 94 runs schoon: 28 installatiefouten en 4 uitvoeringsfouten, zo'n elf echte fouten. Xpath's op //list in 17, ankers op velden die in 17 niet bestaan. Code die naar achteren was geport zonder hem te installeren op de doelversie.
De echte kosten en wanneer je 17 loslaat
Drie branches verdrievoudigen de ontwikkeling niet, want de logica schrijf je één keer. Ze verdrievoudigen het testen en publiceren: drie installaties per wijziging, drie suites, drie vermeldingen om na te lopen, en een overzicht dat bij elke nieuwe versie volledig opnieuw gelezen moet worden, zoals 20 (september 2026), die rijen aan de tabel toevoegt. Odoo brengt elk jaar een hoofdversie uit en ondersteunt de laatste drie; de dag dat 20 verschijnt, valt 17 uit dat venster en stoppen we met het onderhoud. Tot dan krijgt de 17-branch elke correctie: wie nog op 17 zit, plant een migratie en migreert met of zonder jouw module.
Wat de kosten wel verlaagt, is het overzicht als hulpmiddel: een alleen-lezen scanner die aanwijst welke regel op welk verschil stuit, afhankelijk van de versie in de manifest. De onze markeerde ooit 67 modules omdat ze category_id op een groep schreven, en alle 67 waren in orde: die regel stond binnen een <record> van res.groups.privilege, en dat is precies de juiste oplossing voor 19. De regel keek naar de regel en niet naar het record eromheen. Een telling zonder de code te openen is geen gegeven, en een regel die de juiste oplossing aanwijst, leert je het hulpmiddel te negeren.
Checklist vóór elke push
- Constraints:
models.Constraintin 19,_sql_constraintsin 18 en 17, en een test die de dubbele invoer probeert. - Crons:
numbercall=-1in alle crons van de 17-branch; in 18 en 19 in geen enkele. - Groepen:
group_ids,user_idsenprivilege_id(modelres.groups.privilege) in 19;groups_id,usersencategory_idin 18 en 17. - Weergaven:
<list>in 19 en 18,<tree>in 17; in 17 staat elk veld dat in een modifier wordt gebruikt in de weergave. - Schone installatie op alle drie, en update vanaf de vorige versie als de module al in gebruik is.
- Volledige suite groen op alle drie, en niet leeg: 0 van 0 is niet groen.
- Geen
__pycache__in de commit. Eengit add -Ana de tests bracht 1.078.pyc-bestanden in 35 van de 37 repositories.
Dit is wat we doen wanneer een Odoo-partner ons vraagt een module van hem op meerdere versies te onderhouden, of wanneer een adviesbureau wil dat een ontwikkeling die voor hen op 17 werkt, 19 haalt zonder 17 te verliezen. De sluiproute is wat de eerste koper van de vermelding ontdekt.
Veelgestelde vragen
Kan ik één code onderhouden met voorwaarden per versie?
De App Store publiceert vanuit de branches 17.0, 18.0 en 19.0, dus je eindigt hoe dan ook met drie branches, alleen met de drie API's door elkaar in elk ervan. Eén branch per versie betekent meer herhaalde code en veel minder fouten. De enige aanvaardbare voorwaarden vragen naar een mogelijkheid, nooit naar het versienummer.
Waarom is het niet genoeg dat het compileert en de tests lokaal slagen?
Omdat versiefouten naar voren komen wanneer de module wordt geladen in een database van die versie, niet bij het importeren van de code; en sommige, zoals _sql_constraints in 19, falen zelfs niet: je moet bewijzen dat de constraint bestaat.
Nuttige links binnen FlexigoTech
Heb je een module op één versie en heb je hem op alle drie nodig?
We installeren hem op 19, 18 en 17, vertellen je wat er op elke versie breekt en leveren hem op met één branch per versie en een groene build. Mail naar comercial@flexigobe.com of bel +34 616 809 504.

