Skip to content

T3276 relational mapping ambiguity - #2128

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

T3276 relational mapping ambiguity#2128
danpa32 wants to merge 2 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

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a focused executable source-pinned check for the diagnostic and runtime lookup mismatch and captured its outputs.
  • T-Rex produced an additional proof for the posted P1 finding.
  • T-Rex executed and documented general contract validation across data scenarios ABC/abc and NORTH, and summarized the UI implications.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Duplicate-check diagnostic does not model the configured Many2one lookup

    • Bug
      • The UI diagnostic groups exact stored values without the mapping restriction. Consequently, it misses case-only collisions that runtime =ilike matches, and it flags exact duplicates even when relational_domain_restrict leaves only one runtime match.
    • Cause
      • action_check_duplicate_matches uses _read_group with only [(search_field, "!=", False)] and groupby=[search_field] at lines 446-451. The runtime mapping at lines 270-279 instead uses (search_field, "=", search_val) OR (search_field, "=ilike", str(search_val)), then ANDs safe_eval(self.relational_domain_restrict) when configured.
    • Fix
      • Make the diagnostic use the same effective match semantics as runtime: apply relational_domain_restrict before identifying duplicate candidates and normalize/group string search values compatibly with the case-insensitive =ilike lookup (or explicitly qualify the UI message so it does not claim runtime ambiguity).

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "[T3276] Add domain restriction and match..." | Re-trigger Greptile

Comment on lines +446 to +451
duplicate_groups = self.env[target_model]._read_group(
domain=[(search_field, "!=", False)],
groupby=[search_field],
aggregates=["__count"],
having=[("__count", ">", 1)],
)

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 check does not match runtime lookup

The duplicate-check action groups exact target values and only filters out falsy values. Runtime resolution also uses a case-insensitive =ilike fallback and applies relational_domain_restrict. As a result, values such as ABC and abc are not shown as duplicates even though one runtime lookup matches both records, while an exact duplicate that the configured restriction narrows to one record is still reported. Apply the same effective domain and case-insensitive matching semantics in the diagnostic so its review results reflect the records conversion can actually resolve.

Artifacts

Focused executable source-pinned check for the diagnostic and runtime lookup mismatch

  • The authored minimal Python check asserts the reviewed source contains both query shapes and executes case-only and domain-restriction reproductions, confirming the mismatch.

Output from the focused PR 2128 diagnostic and runtime check

  • Running the focused check exited 0 and shows the diagnostic disagrees with runtime for both case-only values and a domain-excluded duplicate, confirming the defect.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant