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.