T3276 relational mapping ambiguity - #2128
Conversation
…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.
Confidence Score: 4/5Not 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
What T-Rex did
|
…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: |
There was a problem hiding this comment.
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.
| "with 'Search Relational Record' enabled." | ||
| ) | ||
| ) | ||
| target_model = self.relational_comodel_name |
There was a problem hiding this comment.
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.
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 oncompassion.field.to.json:relational_domain_restrict(Char, domain expression) — AND-ed intothe search whenever a relational lookup is performed, regardless of
field type.
many2one_multiple_match_policy(Selectionfirst_match/raise,default
first_match) — only meaningful for Many2one fields; when setto
raise,_search_for_relational_valuesnow raises aUserErrorif the search returns more than one record instead of silently taking
records[:1].action_check_duplicate_matches(): a self-service diagnostic thatscans the real current data of the target model/field for existing
duplicate values (
_read_group+having __count > 1), so anyone cancheck whether a given lookup is actually ambiguous today without DB
access.
relational_comodel_name/relational_ttype: technicalrelatedfields (not
compute+store) exposing the target model/type for theview and the diagnostic — deliberately not stored
computefields,see "Bugs found" below.
views/compassion_mapping_view.xml: exposes the two new fields (Many2onepolicy 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_matchfor all existing rows — no behaviorchange 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 upgrades—sponsorship_compassion/security/ir.model.access.csvandviews/sponsorship_contract_view.xml. Discovered thatorigin/18.0itself currently crashes on any full module load:
recurring.invoicerwasremoved from
compassion-accounting, but its cleanup companion (ticketT3284) never got merged — it's sitting on its own unmerged branch,
T3284-Remove-recurring-invoicer-object. This commit mirrors thatalready-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
raisepolicy, decisions are being made separately.Once decisions are made, the actual mapping change needs to go into the
relevant module's
static/mappings/*.jsonfile (the real source oftruth —
compassion.mapping.load_from_json()deletes and recreates allof a mapping's
compassion.field.to.jsonrows from that file on everyreload), then reloaded via
-u <module>or the "Import GMC MappingWizard" (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
PrimaryOwneron "Hold Creation") → Check forduplicates. With no duplicates, get a green "No ambiguity found"
notification. With duplicates (tested live against
CompassNeedKeyand`PrimaryHoldOwner), get a list of the actual
matching records.
Domain RestrictandMatch Policyonly show for Many2one fields with
search_relational_recordenabled;non-relational rows (e.g.
HoldID) show neither.