The table ``theme_ir_ui_view`` only exists when the ``theme`` (website)
module was installed in the source database. If it was never installed,
the cleanup block crashes with "relation does not exist". Add a
``to_regclass`` guard to skip silently instead.
``account_factoring`` (depends on ``account_payment_partner`` +
``account_payment_mode``) is replaced by ``account_factoring_oca``
(depends on ``account_payment_base_oca`` +
``account_payment_base_oca_sale``) in v18. Both modules share rigorously
identical stored fields on ``account_journal``, ``account_move`` and
``res_partner``, so a rename (not an uninstall) preserves the 4910
invoice factor statuses, 311 partner credit limits and journal factor
config; OpenUpgrade then treats ``account_factoring_oca`` as already
installed and adopts the existing columns via ORM ``_auto_init``.
Redirects dependencies pointing to the old name, drops the obsolete
``account_payment_partner``/``account_payment_mode`` dependencies and
inserts the new OCA equivalents. Must run AFTER the bank-payment
renaming block which creates ``account_payment_base_oca`` in the DB.
The orphaned ``base.module_account_factoring`` xmlid is renamed so
OpenUpgrade can rediscover the physical ``account_factoring`` addon dir
without conflicting on ``(base, module_account_factoring)``.
``contract_payment_mode`` v18 still depends on ``account_payment_partner``
(removed by the bank-payment renaming) and uses the obsolete
``account.payment.mode`` model; no v18 replacement provides contract
integration, and only 2 contracts carried a ``payment_mode_id``.
Must run BEFORE the bank-payment renaming and BEFORE OpenUpgrade's
``button_upgrade()`` which would otherwise crash on the missing
dependency.
The module is DELETED (module row + ``ir_model_data`` + dependencies)
rather than marked 'to remove', because Odoo's "Transient module states
were reset" in ``load_modules()`` converts 'to remove' -> 'installed',
making ``button_upgrade()`` re-parse the missing
``account_payment_partner`` dependency and abort the registry load. The
orphaned ``base.module_contract_payment_mode`` xmlid is renamed so
``update_list()`` rediscovers the physical addon as a fresh uninstalled
module that ``button_upgrade()`` never touches.
The migrated 16.0 database still holds ``theme_ir_ui_view`` rows whose
``inherit_id`` points to ``ir.ui.view`` ids removed by the 17.0 asset
bundle rework (``web._assets_utils``, ``website.assets_editor``,
``website._assets_frontend_helpers`` replaced by the ``ir.asset`` model).
When ``website._theme_load`` runs during the registry load,
``ThemeView._convert_to_base_model`` accesses ``inherit.website_id`` on
the ghost record and raises ``MissingError``, aborting the whole
registry build.
Delete the ``ir_model_data`` + ``theme_ir_ui_view`` rows whose
``inherit_id`` targets a missing ``ir.ui.view``. Generic (covers any
theme), idempotent, scoped to ``theme_ir_ui_view`` only; no
``ir.ui.view`` copies exist for the affected templates.
The core script ``l10n_fr_account/migrations/2.1/end-migrate_update_taxes.py``
calls ``account.chart.template.try_loading('fr', company)``. During
``_post_load_data``, Odoo 18 resolves the French template's default accounts via
``self.ref()`` WITHOUT ``raise_if_not_found=False`` (chart_template.py:711/714
and the ``_get_property_accounts`` loop at :746). If any expected
``account.<company>_<key>`` xml-id is missing, it raises
``ValueError: External ID not found`` and aborts the registry load.
This happens when a database customized its chart of accounts: the generic PCG
account was deleted (e.g. 607000) or recreated without its xml-id (e.g. 707000).
Add a generic ORM ``post-migration`` script that, for every company with a
``chart_template``, reads the expected account xml-ids from the template API
(``_get_property_accounts`` / ``_get_chart_template_data`` /
``_get_account_account``) and recreates any missing ``account.<company>_<key>``
``ir.model.data`` pointing to the best matching account: exact code, else the
company account sharing the longest common code prefix, else a non-blocking
warning. It runs at stage ``post`` (before the native ``end`` script) and only
creates missing xml-ids — no account is modified. Idempotent, generic across
all migrated FR databases.
Remove the previous hardcoded 607/707 SQL block from
``versions/18.0/pre_upgrade.sh`` now superseded by this script.
``project_list`` (OCA) is not ported to 17.0 and is merged into core: in 17.0
the core ``project`` module natively adds the kanban/tree views to the
"Projects" actions (``act_window_id`` 149 & 760), and 17.0 introduces the
constraint ``act_window_view_unique_mode_per_action = unique(act_window_id,
view_mode)``.
When core reloads ``project_project_views.xml`` it INSERTs its own
``(act_window_id, view_mode='kanban')`` rows, colliding with the rows still
owned by ``project_list`` and aborting the registry load with a
``UniqueViolation`` on ``act_window_view_unique_mode_per_action``.
Delete ``project_list``'s ``ir.actions.act_window.view`` records (and their
``ir_model_data``) in ``versions/17.0/pre_upgrade.sh`` so core can recreate its
canonical rows. Verified against the 17.0 OpenUpgrade scripts: none reference
``project_list`` xml-ids nor remap these records, so this is safe. Scoped to
``ir.actions.act_window.view`` only, idempotent.
Custom migration scripts for a core module used to require copying the
whole module into ``versions/18.0/addons/`` so its ``migrations/`` folder
would be picked up. Because the migration container mounts that copy with
priority (``--addons-path=mig,addons``) and ``get_module_path`` returns the
first match, the copy fully shadowed the core module — dragging along its
entire data/models just to carry one script.
Instead, inject the scripts through a dedicated OpenUpgrade ``upgrade_path``:
- New ``versions/18.0/scripts/l10n_fr_account/18.0.2.2/`` holding
``pre-migration-0k-fix-orphan-expressions.py`` (converted to the
``@openupgrade.migrate()`` / ``migrate(env, version)`` convention) and
``post-migration-0k-vat-box25.py`` + ``noupdate_changes.xml`` that reapply
the box 25 VAT-report customization via ``openupgrade.load_data``.
- ``config/compose.yml``: mount it as ``/opt/odoo/auto/upgrade`` for ou18.
- ``versions/18.0/upgrade.sh``: pass
``--upgrade-path=/opt/odoo/auto/upgrade,/opt/odoo/auto/addons/openupgrade_scripts/scripts``.
Odoo appends every path to ``odoo.upgrade.__path__`` and concatenates the
per-version scripts, so our scripts run IN ADDITION to the native ones;
the native path is listed explicitly because ``--upgrade-path`` overrides
``config['upgrade_path']`` which ``openupgrade_framework`` only sets when empty.
Files are named with a ``-0k-`` marker so they never collide with the native
``pre-migration.py`` / ``post-migration.py``. This removes the need for the
shadowing module copy entirely.
Also ignore ``__pycache__``/``*.pyc``.
Some target versions add a ``UNIQUE`` constraint on a field that allowed
duplicates in the source version (e.g. ``utm.source.name`` in 16.0). The
unique index creation then aborts the whole registry load with a
``psycopg2.errors.UniqueViolation``.
Declare such constraints per version in ``known_changes.yaml`` under the
``new_unique_constraints`` key (only ``model`` and ``fields`` needed).
During ``prepare_db.sh``, ``dedup_unique_constraints.py`` aggregates the
declarations of every traversed version and, on the source database,
renames duplicates by suffixing `` [<id>]`` while preserving foreign keys.
The actual PostgreSQL column type is used (not the ORM ``translate``
attribute) so both ``varchar`` and already-converted ``jsonb`` columns are
handled: ``jsonb`` duplicates are compared and renamed on every language
key (indexed ``en_US`` value). Renamed records are reported in a
timestamped JSON file and summarized at the end of ``migration.log``.
The 0k dev-pack's compose script doesn't handle absolute paths correctly.
It passes HOST_COMPOSE_YML_FILE to the container, which tries to open
it directly instead of using the mounted path.
Add run_compose() wrapper that changes to PROJECT_ROOT before calling
compose with a relative path, ensuring consistent behavior regardless
of the current working directory.