Upgrade Guide¶
HEMMS upgrades come in three sizes:
- Pull new code, re-run
install-private— every roots_hemms_* module bump. The Makefile target uses-i, which means "install or upgrade", so this single command handles both cases. - Upgrade one specific module — when you want to pick up a fix in
roots_hemms_workflowwithout touching anything else. - Odoo major version upgrade (18 → 19) — a planned, multi-week migration project covered conceptually at the end of this page.
Every one of these starts with the same step: take a backup. See Backup & Restore.
Before you start — the safety checklist¶
Skip these at your peril
-
make backup-private(and the filestore tarball — see Backup & Restore) completed within the last hour - Backup files verified — file size > 0, no errors in the output
- CI is green on
main(the upgrade pulls from there) — check GitHub Actions - Maintenance window communicated to the hospital — Odoo will be down for 1–10 min depending on data volume
- No long-running operations in flight (mass imports, PDF generation queues, etc.)
Reading the version field¶
Every HEMMS module has a version in its __manifest__.py. The current convention is 5-segment semver-ish, anchored to Odoo's major version:
"version": "18.0.1.0.0"
# │ │ │ │ │
# │ │ │ │ └─ Patch — bug fix, no schema change
# │ │ │ └─── Minor — backward-compat feature
# │ │ └───── Major — breaking change for downstream
# │ └─────── Module-local feature bump
# └─────────── Odoo major version (18.x)
The first segment always matches the Odoo major version. When we move to Odoo 19, every manifest gets re-versioned to 19.0.x.y.z.
Odoo's module loader checks this version against the value stored in ir.module.module. If the manifest is newer, Odoo runs the upgrade hooks for that module. If you bump the version in source but forget to -u <module> or -i <leaf>, the running instance keeps the old behaviour until the next restart with --update.
Pattern 1 — Pull and re-install (the common case)¶
# 1. Make sure nothing is in flight, then back up
make backup-private
# 2. Pull the new code
git pull origin main
# 3. Rebuild the image if the Dockerfile or requirements changed
# (most upgrades don't need this)
docker compose -f docker-compose.private.yml build
# 4. Install/upgrade all four leaf modules — this transitively
# upgrades every roots_hemms_* module that bumped its version
make install-private
# 5. Restart Odoo so the new code is loaded by the running process
make down-private && make up-private
make install-private runs odoo -i <leaf modules> --stop-after-init. Odoo's -i flag installs missing modules and upgrades modules whose manifest version is newer than the value stored in ir.module.module. That single command handles both fresh installs and incremental upgrades, which is why the same target appears in Installation.
Pattern 2 — Upgrade one specific module¶
When you want to pick up a fix in just one module — typically during development or when applying a hotfix:
docker compose -f docker-compose.private.yml exec odoo_private \
odoo -c /etc/odoo/odoo.conf \
-d hemms_private \
-u roots_hemms_workflow \
--stop-after-init
-u (uppercase semantics: upgrade) re-runs the module's data files (security/, views/, data/) and any migration scripts (see next section). It does not install missing dependencies — if you bumped a module that gained a new depends, use -i instead.
After the command exits, restart the long-running Odoo:
Migration files¶
When a module's upgrade needs to do more than re-load XML — e.g. rename a field, backfill a value, or merge two records — it adds a Python migration script under migrations/<version>/. Odoo runs them in order between the manifest version bump and the new XML load.
The current state of HEMMS migrations:
find addons/roots -path '*/migrations/*'
# (no results — none of the roots_hemms_* modules has needed a migration
# script yet because they were all added at 18.0.1.0.0)
When the first migration is needed (likely Q3 → Q3.x with a coverage-line schema change), the structure will be:
addons/roots/roots_hemms_service_contracts/
└── migrations/
└── 18.0.1.1.0/
├── pre-migrate.py # runs before module load
└── post-migrate.py # runs after module load
The OCA OCA/server-tools repo has a migration playbook and helper utilities — adopt those rather than hand-rolling SQL when the first real migration lands.
CI catches the obvious breakage¶
Before every PR merges to main, the Tests workflow installs every leaf module on a fresh Postgres 15 + Odoo 18 stack and runs all 177 HEMMS unit tests. If main is green, the install / upgrade path is proven on a fresh DB.
What CI does not catch:
- Migrations from old data shapes that don't exist in the test fixtures
- Performance regressions on large databases
- Browser-side regressions (no JS tests yet)
For a high-stakes upgrade, install the new code into a staging DB restored from a recent production backup, click through the critical flows, and only then upgrade production. This is the disaster-recovery drill from Backup & Restore put to dual use.
Pattern 3 — Odoo major upgrade (18 → 19)¶
This is a project, not a command. The full sequence:
- Wait for OCA 19.0 readiness. Track the OCA
maintenancerepo's 19.0 branch — HEMMS depends onmaintenance_plan,maintenance_stock,maintenance_partner, andsign_oca. None of the four can be ahead of their 19.0 release. - Audit deprecated APIs. Odoo's 19.0 release notes list every removed API. Grep
addons/roots/for each one. The Q7 CI run will catch most on a 19.0 base image, but some breakage only surfaces at runtime. - Rebase the OCA vendor layer to 19.0 branches.
- Bump every
roots_hemms_*manifest from18.0.x.y.zto19.0.x.y.z. - Write migration scripts for any field-rename or model-rename.
- Run the OCA migration playbook on a staging DB: https://github.com/OCA/openupgrade is the community tool for this.
- Stage, dry-run, document, then upgrade production during a planned downtime window.
Plan a multi-week project. The OCA openupgrade README is the best single resource.
Post-upgrade verification¶
After every upgrade — module-level or major — verify:
- Boot logs are clean.
make logs-privateshould end atModules loaded.with no tracebacks above it. - Module list updated. Settings → Apps shows the new versions in Installed.
- Smoke test the critical flows:
- Create and close a maintenance request
- Open last year's HA report — PDF renders
- Open a signed service contract — attachment downloads
- Run a mass-import dry-run and a commit on a small sample
- Audit log is intact. Spot-check
hemms.stage.transition.logrows from before and after the upgrade.
If anything fails, restore from the backup taken at step 1 of the safety checklist. See Backup & Restore.
Rollback¶
If something goes wrong mid-upgrade:
# 1. Stop Odoo
docker stop hemms_odoo_private
# 2. Roll the code back
git checkout <previous-good-sha>
docker compose -f docker-compose.private.yml build
# 3. Restore the pre-upgrade DB + filestore
# (full procedure in Backup & Restore)
# 4. Restart
make up-private
Do not try to "fix forward" on a production DB. Roll back, reproduce the failure on staging, fix it there, ship via PR through CI, then try again.
Related pages¶
- Backup & Restore — the prerequisite for every upgrade
- CI/CD — what CI proves before you upgrade
- Module Development — how to bump a module's version when you change it