Skip to content

Annual Odoo port

Audience

PLANA staff. The October-cadence checklist for bringing the platform onto the next upstream Odoo major.

Upstream Odoo releases a new major every October. PLANA Pulse supports this cadence with a structured, ~12-week effort that turns a new major release into a production-ready PLANA tier on plana.planapulse.app plus the customer tenants that opt in.

This runbook is the checklist for each port. It is updated during each cycle.

When this runs

  • Year-round trigger: a CronJob opens the porting epic on October 1.
  • First-week activity: branch bootstrap (W0).
  • Production readiness target: by end of Q4.

Effort

PhaseEffortRisk
Framework adjustments (only first port)~9.5 eng-daysLow
PLANA module port~25 eng-daysMedium — plana_bus_redis is the riskiest per cycle
Pilot tenant migration~5 eng-daysMedium
Bulk customer migration~5 eng-daysLow (per-tenant), Medium overall
Recurring annual total~30–40 eng-days

After the first cycle (2026), subsequent ports drop the framework cost, landing at ~25–30 eng-days/year.

Phase 0 — bootstrap (W0)

TaskOwnerCommand / file
Create N+1 branch on odoo-modules from NLeadgit checkout -b 20.0 origin/19.0 && git push -u origin 20.0
Bump ARG ODOO_VERSION in Dockerfile on N+1 branchLeadsed -i 's/ARG ODOO_VERSION=19.0/ARG ODOO_VERSION=20.0/' Dockerfile
Bump SUPPORTED_VERSIONS in .forgejo/workflows/oca-sync.yml (on the default branch)LeadSUPPORTED_VERSIONS="18.0 19.0 20.0"
Trigger first base image build via Forgejo Actions on the new branchLeadPush commit → pipeline.yml fires
Create new namespace plana-odoo-N+1 with quota, secrets, NetworkPolicies, redis-allow-consumers entryPlatformAdd to infra/k8s/plana-odoo-N+1/ + PR

Phase 1 — port PLANA modules (W1–W3)

Order matters — dependencies first.

OrderModuleRiskCommon breaks
1plana_logging_jsonLowAPI surface stable
2plana_session_redisLowSession API stable
3plana_proxy_fixLowProxy header API stable
4plana_k8s_healthzLow–mediumfrom odoo import registry removal in v19
5plana_bus_redisHighBus mechanism is rewritten between majors — pair-port
6plana_session_timeout_redisLowDepends on session_redis
7auth_session_timeout patchesLowBehaviour stable
8pulse_account_apiMediumController API + auth API can change
9plana_saasMediumField renames on project.project, etc.
10plana_cron_db_filterLowStable
11plana_authMedium_login signature changes between v17 and v18
12plana_user_rolesMedium_login signature same as plana_auth

For each module: port → run flake8 + Odoo's --test-tags if available → commit on the N+1 branch → cherry-pick back to earlier branches if the fix is cross-version safe.

Phase 2 — OCA fork patches (W4–W5)

PLANA does NOT back-port OCA modules. We follow upstream OCA branches and pick them up as they land. For modules where we have a private fork (in OCA-fork/<repo> with branches 17.0-planapulse, 18.0-planapulse, 19.0-planapulse, etc.):

  • Bootstrap N+1.0-planapulse from upstream N+1.0
  • Apply our PLANA-specific patches on top
  • File upstream PRs where the patches are generally useful

OCA modules that have not yet shipped on N+1 go on a watchlist. PLANA tenants that depend on those modules stay on the previous major until upstream catches up.

Phase 3 — Staging cluster (W6)

TaskOutput
TemplateSnapshot{templateCode=basic, odooVersion=N+1} appliedbasic-template-N+1.planapulse.app
TemplateSnapshot{templateCode=pro, odooVersion=N+1} appliedpro-template-N+1.planapulse.app
Synthetic PLANAClient{name: e2etest-N+1, odooVersion: "N+1"} appliede2etest-N+1.planapulse.app
Verify /web/health and /web/login200 on both

Phase 4 — Pilot tenants (W7–W8)

Pick 2–3 friendly tenants whose installed module set is known to be fully ported. Schedule a maintenance window. For each:

yaml
apiVersion: planapulse.com/v1alpha1
kind: TenantUpgrade
metadata: { name: pilot-<slug>-to-<N+1> }
spec:
  slug: <slug>
  fromVersion: "N"
  toVersion: "N+1"
  strategy: snapshot-then-upgrade

Soak each pilot for 5–7 days before declaring success.

Phase 5 — GA to new tenants (W9–W10)

TaskWhere
Bump XRD default: PLANAClient.spec.odooVersion = "N+1"infra/crossplane/xrd-planaclient.yaml
Bump XRD default: TemplateSnapshot.spec.odooVersion = "N+1"infra/crossplane/xrd-templatesnapshot.yaml
Bump XRD default: TenantUpgrade.spec.fromVersion = "N+1", toVersion = "N+1"infra/crossplane/xrd-tenantupgrade.yaml
plana_saas.tier.basic.supported_odoo_versions += "N+1"ERP data
plana_saas.tier.basic.default_odoo_version = "N+1"ERP data
Update marketing copy on planapulse.ai/pricingpulse-website repo

Phase 6 — Bulk migration (W11–W12)

Open the migration window. Per-tenant:

  1. Audit installed modules (SELECT name FROM ir_module_module WHERE state='installed')
  2. Confirm every module is available on N+1
  3. If yes, schedule TenantUpgrade{strategy: snapshot-then-upgrade} in an off-peak window
  4. If no, defer — note which module is blocking

Throughout: monitor the soak of recently-migrated tenants. Roll back individual tenants if a problem appears (insurance backups retained 7 days).

Phase 7 — Sunset the N-1 major

Once N-1 has no remaining production tenants:

TaskWhere
Mark N-1 branch protected and read-onlyForgejo settings
Remove N-1 from SUPPORTED_VERSIONS in oca-sync.ymlDefault branch
Stop building base-N-1 images in pipeline.ymlPer-version branch
Scale plana-odoo-N-1 Deployment to 0Wait 30 days, then archive namespace
Remove the N-1 row from this table on docs.planapulse.com/plana-business-cloud/version-differences

Drop the N-1 namespace's filestore PVC and image-pull secret after the 30-day soak.

What goes wrong each year

A short list of bugs that surface during every port and how to handle them.

Field renames

Odoo renames fields between majors. The Python compiler does not catch these; tests do, sometimes. Grep the codebase for the old field name in all PLANA modules.

Import path moves

Where you previously did from odoo import registry, now it's from odoo.modules.registry import Registry. Use try/except aliases so the same module file compiles on multiple majors:

python
try:
    from odoo.modules.registry import Registry
except ImportError:
    from odoo import registry as Registry  # pre-19 fallback

View tag renames

<tree><list> in v18+. A simple sed over all view XML works:

bash
git grep -lE '<(tree|/tree)' -- '*.xml' | xargs sed -i 's/<tree/<list/g; s|</tree>|</list>|g'

Same again with the <tree string=...> form. Confirm by opening a sample view in Studio.

UID change in the base image

v17 used UID 101; v18 and v19 use UID 100. If you tar-pipe a filestore across a major boundary with --same-owner, the target worker can't write because the UID owner does not match. Always pass --no-same-owner --no-same-permissions to the receiving tar, or chown after with a root-uid Job.

PEP 668 on Debian 12 base

Odoo 18+'s base is Debian 12. Pip refuses to install system-wide without --break-system-packages. Update any pip install in our Dockerfile or ad-hoc Job manifests.

Enterprise dependencies

A module that was Community in v17 might be moved to Enterprise in a later release. Drop it from the tier seed data; replace with an OCA equivalent if one exists.

Where to read more

© PLANA Digital Ltd.