Skip to content

T3276 relational mapping ambiguity - #2128

Open
danpa32 wants to merge 4 commits into
18.0from
T3276-relational-mapping-ambiguity
Open

T3276 relational mapping ambiguity#2128
danpa32 wants to merge 4 commits into
18.0from
T3276-relational-mapping-ambiguity

Conversation

@danpa32

@danpa32 danpa32 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

T3276 — Improve odoo message center json to odoo conversion

Problem

When GMC Connect sends a JSON message, textual values that map to a
Many2one field are resolved by a generic search on the target model. If more than one record
matches, the code silently takes the first one found — there was no way to
narrow the search or to be warned when the match is ambiguous.

What changed

  • models/field_to_json.py: two new fields on compassion.field.to.json:
    • relational_domain_restrict (Char, domain expression) — AND-ed into
      the search whenever a relational lookup is performed, regardless of
      field type.
    • many2one_multiple_match_policy (Selection first_match/raise,
      default first_match) — only meaningful for Many2one fields; when set
      to raise, _search_for_relational_values now raises a UserError
      if the search returns more than one record instead of silently taking
      records[:1].
    • New action_check_duplicate_matches(): a self-service diagnostic that
      scans the real current data of the target model/field for existing
      duplicate values (_read_group + having __count > 1), so anyone can
      check whether a given lookup is actually ambiguous today without DB
      access.
    • relational_comodel_name/relational_ttype: technical related
      fields (not compute+store) exposing the target model/type for the
      view and the diagnostic — deliberately not stored compute fields,
      see "Bugs found" below.
  • views/compassion_mapping_view.xml: exposes the two new fields (Many2one
    policy only shown for Many2one fields) and a "Check for duplicates"
    button on the field-to-json form; adds a dedicated flattened list +
    menu, Message Center → Configuration → Many2one Fields to Review,
    showing all Many2one relational lookups across every mapping in one
    screen (previously only reachable by opening each mapping individually).

Default policy is first_match for all existing rows — no behavior
change
for any existing mapping until someone explicitly reviews and
changes a specific field.

Unrelated fix bundled in (separate commit)

[T3284] Remove stale recurring.invoicer references blocking module upgradessponsorship_compassion/security/ir.model.access.csv and
views/sponsorship_contract_view.xml. Discovered that origin/18.0
itself currently crashes on any full module load: recurring.invoicer was
removed from compassion-accounting, but its cleanup companion (ticket
T3284) never got merged — it's sitting on its own unmerged branch,
T3284-Remove-recurring-invoicer-object. This commit mirrors that
already-authored fix exactly, scoped to unblock this branch.
T3284's own PR still needs to be merged so this isn't needed again on
the next fresh branch off 18.0.

Impact verification — deferred, not done in this PR

The ticket's own acceptance criteria calls for reviewing all existing
Many2one relational mappings and deciding, field by field, whether they
need a domain restriction and/or the raise policy, decisions are being made separately.

Once decisions are made, the actual mapping change needs to go into the
relevant module's static/mappings/*.json file (the real source of
truth — compassion.mapping.load_from_json() deletes and recreates all
of a mapping's compassion.field.to.json rows from that file on every
reload), then reloaded via -u <module> or the "Import GMC Mapping
Wizard" (Message Center → Configuration). Editing the field directly in
the UI is useful for testing but does not persist across a reload.

How to test manually

  1. Message Center → Configuration → Many2one Fields to Review.
  2. Pick a row (e.g. PrimaryOwner on "Hold Creation") → Check for
    duplicates
    . With no duplicates, get a green "No ambiguity found"
    notification. With duplicates (tested live against CompassNeedKey and
    `PrimaryHoldOwner), get a list of the actual
    matching records.
  3. Open a Many2one field row's form: Domain Restrict and Match Policy
    only show for Many2one fields with search_relational_record enabled;
    non-relational rows (e.g. HoldID) show neither.

danpa32 added 2 commits August 5, 2026 15:33
…grades

The recurring.invoicer model was removed from compassion-accounting, but
its cleanup companion (T3284) never landed on 18.0, leaving a stale ACL
row and menu item in sponsorship_compassion that crash any full module
load. Mirrors the already-authored fix sitting unmerged on
T3284-Remove-recurring-invoicer-object.
…mappings

Generic JSON-to-Odoo mapping picks the first matching record for a
Many2one relational field, with no way to narrow the search or react
when several records match (e.g. two users named the same).

Adds two fields on compassion.field.to.json:
- relational_domain_restrict: optional domain to narrow the search for
  an existing relational record.
- many2one_multiple_match_policy: take the first match (default, current
  behavior) or raise an error when several records match.

Also adds a "Check for duplicates" action that scans the real target
model/field for existing duplicate values, and a dedicated "Many2one
Fields to Review" list (Message Center > Configuration) flattening every
Many2one relational lookup across all mappings for review.

Impact verification of the ~97 existing Many2one lookups and any
resulting mapping changes are tracked separately.
@danpa32
danpa32 requested a review from ecino August 5, 2026 13:40
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

Not safe to merge until the duplicate checker selects the same final target model as the runtime relational lookup.

A focused executable probe reproduced a failure in the administrator duplicate-check flow for dotted mappings, while direct matching, restriction, and duplicate-error paths passed in that probe.

Files Needing Attention: message_center_compassion/models/field_to_json.py

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proof for a posted P1 finding and linked it to the corresponding review comment.
  • Validation could not run end-to-end because the Odoo runtime, Odoo configuration, and PostgreSQL service were unavailable, and GitHub CLI credentials were not authenticated for review-thread inspection.
  • T-Rex produced proof for another posted P1 finding.
  • T-Rex ran the relational GMC mapping probe against the current changes, compared it to the pre-fix baseline, and parsed the mapping view XML to verify the path.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Duplicate checker uses the first relation instead of the relation actually searched by dotted mappings

    • Bug
      • action_check_duplicate_matches() selects relational_comodel_name, which is related to relational_field_id and therefore represents only the first segment of odoo_field. Runtime lookup instead uses field_id.relation, representing the final mapped field. For a dotted mapping where these differ, the duplicate checker queries the wrong model. The executed reproduction with a final res.users.login lookup and first-hop child.compassion model raises KeyError('login'); where both models expose the key it would produce a misleading result.
    • Cause
      • relational_comodel_name is declared from relational_field_id.relation at line 78 and consumed as the diagnostic target at line 441, while runtime derives the target from field.relation at line 330.
    • Fix
      • Derive the diagnostic target from the same final field metadata as runtime, e.g. target_model = self.field_id.relation or self.field_id.model; validate that search_field exists in that model before grouping. Use this final relation as the domain widget target as well if the restriction is intended for the searched record.

    T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/18...." | Re-trigger Greptile

Comment thread message_center_compassion/models/field_to_json.py Outdated
…ntics

action_check_duplicate_matches() grouped exact values only and ignored
relational_domain_restrict, so it disagreed with the actual lookup done
by _search_for_relational_values(): case-only collisions (e.g.
"ABC"/"abc") were missed since runtime falls back to a case-insensitive
=ilike match, and values already disambiguated by a configured domain
restrict were still flagged as ambiguous.

Now applies relational_domain_restrict to the diagnostic's own domain,
and merges same-value groups case-insensitively for char/text/selection
search fields before judging ambiguity.

Addresses greptile-apps P1 review finding on PR #2128.
[domain, safe_eval(self.relational_domain_restrict)]
)
records = relational_model.search(domain)
else:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Duplicate diagnostic misses falsy Many2one lookups

A mapping with a configured search_key can receive a dictionary that omits that key, or supplies 0. In both cases search_val is falsy, so this branch assigns the entire target model to records; with the new raise policy, any target model with multiple rows rejects the incoming message. However, action_check_duplicate_matches() only groups non-false values of the configured search field and can report “No ambiguity found” when all populated values are unique. This gives mapping administrators a successful diagnostic immediately before the same input fails at conversion time. Reject or skip a missing/falsy configured search key explicitly, or make the diagnostic warn whenever this all-record fallback would be ambiguous.

Artifacts

Executable faithful harness for missing-key and numeric-zero Many2one lookups

  • The executed Python source reproduces the exact runtime and diagnostic branches with a minimal Odoo-like recordset, showing their differing handling of falsy search values.

Baseline one-row falsy lookup output

  • The one-target-row control run returned relation ID 101 and reported no ambiguity, establishing the non-failing baseline.

Two-row falsy lookup mismatch output

  • The two-target-row run shows both missing-key and numeric-zero inputs raise at runtime while the diagnostic reports no ambiguity, verifying the mismatch.

View artifacts

T-Rex Ran code and verified through T-Rex

"with 'Search Relational Record' enabled."
)
)
target_model = self.relational_comodel_name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Duplicate diagnostic targets the wrong model for dotted mappings

action_check_duplicate_matches() uses relational_comodel_name, derived from the first segment of odoo_field through relational_field_id.relation. Runtime lookup instead searches the final mapped field's relation through field_id.relation. A mapping such as child_id.user_id.login therefore searches res.users.login at runtime but makes this diagnostic query child.compassion. If login is absent on the first-hop model, the button raises KeyError; if both models have that field, it can report a misleading clean result. Derive the diagnostic target—and the domain-widget target—from the final field metadata used by runtime lookup, and validate that search_key exists there.

Artifacts

Executable relational GMC mapping probe

  • Python source for the narrow Odoo-stub probe that invokes the changed mapping methods and reproduces the dotted-relation failure, with the takeaway that the diagnostic and runtime select different target models.

Baseline relational GMC mapping probe output

  • Captured command output from the probe loaded against commit 3ac72ad before the duplicate-diagnostic follow-up, showing the comparison baseline and its exit status.

Changed relational GMC mapping probe output

  • Captured command output from the probe loaded against the current changed file, showing passing direct matching checks and the reproduced KeyError for a dotted mapping.

Changed mapping view XML parse output

  • Captured XML parser output for the changed mapping view, showing that the view file is well-formed XML but not substituting for unavailable Odoo registry validation.

View artifacts

T-Rex Ran code and verified through T-Rex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants