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.