Skip to content

[20.0][MIG] mis_builder: Migration to 20.0 - #8

Open
dnplkndll wants to merge 5 commits into
20.0from
20.0-mig-mis_builder
Open

dnplkndll wants to merge 5 commits into
20.0from
20.0-mig-mis_builder

Conversation

@dnplkndll

Copy link
Copy Markdown

Port of mis_builder (and the mis_builder_budget / mis_builder_demo ACL conversions) from 19.0 to 20.0. Depends on report_xlsx (OCA/reporting-engine#1203) and date_range (ledoent/server-ux#7), both pinned in test-requirements.txt — that pin is what makes Detect unreleased dependencies red.

100 tests pass for mis_builder, 18 for mis_builder_budget.

OCA's 20.0 branch here is 19.0 plus a commit flipping installable to False, so the module history is already present and nothing needed replaying — unlike the orphan skeletons in other repos. FROM_VER is origin/19.0, which is what production pins, not the diverged ledoent/19.0-mig-mis_builder.

Non-mechanical adaptations worth flagging

  • ir.model.access + ir.rule → one ir.access model. 23 ACL rows plus the global multi-company rule become a 24-row security/ir.access.csv; audited against the original, nothing dropped. Same conversion for the budget and demo modules' XML-declared ACLs and their four global rules.

  • odoo/tools/safe_eval.py is a package in 20.0, and compile_codeobj is missing from safe_eval/evaluation.py's __all__ — so the package's star re-export does not carry it and importing it from odoo.tools.safe_eval raises ImportError. Imported from the submodule instead.

  • The legacy <> domain operator is gone; 20.0 accepts only !=. aep.py built ("debit", "<>", 0.0) domains, which now raise ValueError at search time.

  • ir.actions.report.report_file no longer exists, so the xlsx report record failed to load the module.

  • Char fields named name now copy with a "(copy)" suffix unless copy is declared (orm/fields_textual.py:529-533). mis.report.kpi, .subkpi and .query all constrain name to a valid python identifier, so duplicating a report raised ValidationError. Those three now declare copy=True explicitly.

  • pytz → zoneinfo. pytz is rejected by .pylintrc-mandatory and is no longer in Odoo's own requirements.txt. The subtlety: localize(..., is_dst=False) resolved the ambiguous DST fall-back hour to standard time, which zoneinfo spells fold=1 — not a plain replace(tzinfo=...).

  • Owl 3. useState → proxy, useSubEnv from @web/owl2/utils, static props → props = useProps({t.*}). SearchModel's constructor takes a wider service bag now and calls usePlugin() internally, so field/name/tree_processor are supplied too — core's WithSearch is the only in-core reference for building one. The field widget also had to declare props at all, since Owl 3 only materialises declared ones; it is bound to the id field, so props.record.resId replaces the props.value read, which had been dead since standardFieldProps stopped carrying value.

  • t-esc → t-out, and 17 fa-* icons → Material Symbols (20.0 ships no FontAwesome in any form).

  • .ruff.toml pins select to ruff's classic default: the skeleton bumps ruff v0.13.0 → v0.16.8, whose much wider default silently switched on whole families the repo never chose, since the file only ever said extend-select.

AI-assisted (Claude Code); every change reviewed, tested, and owned by the author.

The 20.0 skeleton bumps ruff v0.13.0 -> v0.16.8, whose default rule set is
much wider. .ruff.toml only ever said extend-select, so that drift silently
switched on whole families the repo never chose. Pinning select to ruff's
classic default reproduces exactly what v0.13.0 enforced on 19.0.

Assisted-by: Claude Opus 5
OCA's 20.0 branch is 19.0 plus a commit flipping installable to False, so the
module history is already present and nothing needed replaying -- unlike the
orphan skeletons in other repos. FROM_VER is origin/19.0, which is what
production pins (erp/odoo-19/prod/repos.yaml), not the diverged
ledoent/19.0-mig-mis_builder.

* 20.0 merged ir.model.access and ir.rule into one ir.access model. The 23 ACL
  rows and the global multi-company rule become security/ir.access.csv (24
  rows, none dropped) and mis_builder_security.xml goes away. Same for the
  budget and demo modules' XML-declared ACLs and their 4 global rules.
* odoo/tools/safe_eval.py is a package in 20.0, and compile_codeobj is missing
  from safe_eval/evaluation.py's __all__ -- so the package's star re-export
  does not carry it and importing it from odoo.tools.safe_eval raises
  ImportError. Imported from the submodule.
* The legacy "<>" domain operator is gone; 20.0 accepts only "!=". aep.py built
  ("debit", "<>", 0.0) domains, which now raise ValueError at search time.
* ir.actions.report.report_file no longer exists, so the xlsx report record
  failed to load.
* Char fields *named* `name` now copy with a "(copy)" suffix unless copy is
  declared (orm/fields_textual.py:529-533). mis.report.kpi, .subkpi and .query
  all constrain name to a valid python identifier, so duplicating a report
  raised ValidationError. Those three declare copy=True explicitly.
* pytz is rejected by .pylintrc-mandatory and is no longer in Odoo's own
  requirements. _utc_midnight moves to zoneinfo; pytz's
  localize(..., is_dst=False) resolved the ambiguous DST fall-back hour to
  standard time, which zoneinfo spells fold=1.
* Owl 3: useState -> proxy, useSubEnv from @web/owl2/utils, and `static props`
  -> props = useProps({t.*}). SearchModel's constructor takes a wider service
  bag now and calls usePlugin() internally, so field/name/tree_processor are
  supplied as well -- core's WithSearch is the reference. The field widget also
  had to declare props at all, since Owl 3 only materialises declared ones; it
  is bound to the `id` field, so props.record.resId replaces the props.value
  read, which had been dead since standardFieldProps stopped carrying value.
* env.clear() is deprecated in 20.0 in favour of transaction.clear, and OCA's
  checklog gate turns that DeprecationWarning into a job failure even though
  every test passes.
* t-esc -> t-out, and 17 fa-* icons -> Material Symbols (20.0 ships no
  FontAwesome).
* Dropped the dead "qweb" manifest key and made the summary one line.

100 tests pass.

Assisted-by: Claude Opus 5
@dnplkndll
dnplkndll force-pushed the 20.0-mig-mis_builder branch 2 times, most recently from 45fcd6e to 3680945 Compare October 1, 2026 15:08
Previewing any MIS report died client-side with

    TypeError: Cannot read properties of undefined (reading 'mis_report_data')
        at MisReportWidget.template_mis_builder_MisReportWidget

Owl 3 renders a template with `{this}` as the ENTIRE context. A bare
identifier is not a component member any more, it is undefined -- so
`state.mis_report_data` read a property off nothing and took the widget down
before it drew a single row. 23 sites across the widget and the annotation
dialog: `state`, `props`, and every handler (`refresh`, `printPdf`,
`exportXls`, `drilldown`, `annotate`, `remove_annotation`, ...).

`this` is also the *whole* context, so GLOBALS are not in scope either. With
the members fixed the widget rendered and then threw
`Cannot read properties of undefined (reading 'stringify')` on
`JSON.stringify(cell.drilldown_arg)`. Core 20.0 uses JSON.stringify in zero
templates and keeps it in JS, so this adds a `drilldownArg(cell)` helper and
calls that instead.

Left bare deliberately: `notes`, `cell`, `row` and friends are bound by t-set
and t-foreach in these same templates, and prefixing them would break the
loops.

Why the MIG missed it: upgrade_code's owl3-migration `upgrade_this` sub-task
only matches components declaring `static template = "..."`. This widget does
`MisReportWidget.template = "mis_builder.MisReportWidget"` -- an assignment --
so the sub-task matched nothing and prefixed none of it. The 100 python tests
stayed green throughout; the module ships no JS tests, and only rendering the
widget can see this.

Found by walking the migrated production copy in a browser, which is also how
the same bug was found in dms.

Assisted-by: Claude Opus 5
@dnplkndll
dnplkndll force-pushed the 20.0-mig-mis_builder branch from 8ed21cd to cbf08f6 Compare October 3, 2026 14:41
…filter

Follow-up to cbf08f6, which converted this template to `this.` but missed
the DateTimeInput node. Owl 3's render context is `{this}` only, so a bare
identifier is undefined:

    value="state.pivot_date"            -> TypeError reading 'pivot_date'
    onChange.bind="onDateTimeChanged"   -> TypeError reading 'bind'

Both are inside `t-if="this.showPivotDate"`, and `widget_show_pivot_date`
defaults to False -- so the node had never rendered on 20.0 and the earlier
pass had nothing to fail against. Enabling the picker on any instance makes
Preview die client-side with an Oops dialog.

Verified in a browser against a copy of production on the 20.0 image: the
report renders, and setting the base date to 12/31/2025 re-anchors both
columns (NET INCOME 15,848.49 YTD-2026 -> -3,075.49 FY2025 vs 28,783.42
FY2024) with a clean console.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant