Repository navigation
T3642 FIX clean and merge the duplicated contacts - #2196
Merged
Merged
Conversation
Contacts attached with partner_contact_in_several_companies were migrated as children of type other and now appear as duplicates. - crm_request: migration moving every relation of these aliases to their main contact, archiving the empty ones; incoming requests from an alias address belong to the main contact while email_from keeps the alias - interaction_resume: the resume of a contact includes its aliases Assisted-by: claude-sonnet-5-5, medium, claude-code
- end-migration: find duplicated persons (same name and e-mail / zip, same name and birthdate, same address), merge the certain ones in jobs, turn the others into children of type other, flag the rest with an activity (Check Duplicates, partner_auto_match responsible) - post-migration: also schedule an activity for the aliases left untouched - rename the legacy cms_form_compassion.match_validation_responsible key - merge job: res.partner._merge_duplicates, shared helpers in tools Assisted-by: claude-sonnet-5-5, medium, claude-code
The aliases of a contact get the type email_alias instead of other, so that the real other addresses are not mistaken for aliases and not hidden. Both migrations set it on the contacts they clean. Assisted-by: claude-sonnet-5-5, medium, claude-code
ecino
marked this pull request as ready for review
November 2, 2026 12:22
ecino
commented
Nov 2, 2026
Member
Author
There was a problem hiding this comment.
This review is AI-generated (Claude Code) and has not been verified line by line.
Updated after the second review pass. The findings of the first pass were reported as fixed and are removed from this comment; only what remains or is new is listed.
To fix before merging
models/res_partner.py:6: the import chainres_partner.py→tools/partner_cleanup.py→openupgradelibruns on every registry load, but the manifest only declarespandas. Addopenupgradelibtoexternal_dependencies(or keep those helpers out of the model import path).migrations/18.0.1.0.1/end-migration.py:219: "same name + same zip, e-mail missing on one side" still returnsmergewithout looking at the street, and the birthdate guard only applies when both birthdates are filled. Two different people in the same village can be merged (the source partner is deleted). A differing street, or an e-mail on one side only, should downgrade tochild/review.tools/partner_cleanup.py:152: converted duplicates with their own address/phone become children but keep typecontact, so Odoo's_children_syncoverwrites their address on the next address edit of the main contact. Set them toother.
Smaller points
post-migration.py:48:set(aliases.ids)is rebuilt for every alias inside the comprehension (O(N²)); compute it once.end-migration.py:160:_add_usagereadsrecurring_contract.correspondent_idunguarded, while post-migration usescolumn_exists.partner_cleanup.py:107: the "no data of its own" test ignores name,ref,lang,comment, city and country, and the shared-token heuristic can classify "Marie Dupont" under "Jean Dupont" as an alias that is then archived. Compare names (equal, blank or a copy) before archiving.partner_cleanup.py:29:COPY_NAMEmisses the Italian(copia).partner_cleanup.py:216:move_relationsre-implements FK/reference re-pointing that the merge wizard already provides (_update_foreign_keys_generic,_update_reference_fields_generic); the fallback loops per alias with one savepoint each.partner_cleanup.py:180:_rename_like_maindoes one UPDATE per alias and is the only place stored computed fields (complete_name, ...) are refreshed, so re-parented but not renamed aliases keep stale values.models/request.py:288: an alias is resolved to its parent only for the field sync;partner_idand messageauthor_idkeep the alias, and the duplicate-alias check ignores archived aliases.
- only clean the children of type other that look like contacts attached in v14, leave the real other addresses untouched - never merge or convert contacts with different birthdates - drop rows only in link tables on a unique violation, and log them - guard the sponsorship tables, merge the visitors of a partner before a merge, normalize the e-mail of the alias created from a request - keep the data an alias carries (phone, birthdate), re-parent the children of a converted contact, use SQL instead of the ORM in the migrations - compute the aliases once per fetch of the interaction resume - turn an email_alias without parent into a contact Assisted-by: claude-sonnet-5-5, medium, claude-code
- no openupgradelib in the code imported by the models - contacts kept for their own address or phone get the type other, as Odoo synchronizes the address of the type contact with the main contact - move the relations with the foreign keys of the merge wizard, never delete a row, leave a conflicting row on the alias - recompute the stored fields of the moved contacts, rename in one query - a request on an alias goes to the main contact, archived aliases are not created again - (copia) is a copy, compute the set of aliases once Assisted-by: claude-sonnet-5-5, medium, claude-code
Member
Author
|
This reply is AI-generated (Claude Code). Thanks for the second pass. Fixed in 44181c4: points 1 and 3, and the smaller points on
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
T3642
Contacts attached with
partner_contact_in_several_companiesin v14 were migrated as children of typeotherand now show up as duplicates. Other duplicates exist too.New partner type
email_alias(defined incrm_request, it was inpartner_compassionin v14). The aliases of a contact get this type, so that real "Other address" children are neither treated as aliases nor hidden. Withoutcrm_requestthe type does not exist andinteraction_resumesimply finds no alias.Migration 18.0.1.0.1, post-migration (aliases of the v14 migration)
otherthat look like contacts attached in v14 are cleaned: blank, copied or same name as their parent (same words, any order), or sharing a word with their parent when it is a person. Any other child, like a contact person under a company, is left untouched.email_alias. The ones with only an address or a phone number get the typeother(Odoo synchronizes the address of the typecontactwith the main contact).other, with an activity on their main contact.email_aliaswithout parent becomes a normal contact.Migration 18.0.1.0.1, end-migration (other duplicates)
res.partner._merge_duplicates): same name and e-mail, same name and zip with an e-mail missing but not different, or a birthdate / address match with the same e-mail.otherif they only have an address or a phone number).partner_auto_match.match_validation_responsible. The legacy keycms_form_compassion.match_validation_responsibleis renamed to it first.Code
crm_request: an incoming request from an alias address belongs to the main contact,email_fromkeeps the alias so replies still go to it. The alias created from a request uses the normalized address and is attached to the main contact. A request set on an alias is put on its main contact, and an archived alias is not created again.interaction_resume: the resume of a contact includes its aliases (computed once per fetch).The "Main contacts" filter hiding the aliases by default is in CompassionCH/compassion-switzerland#1884.
Tested on a copy of swiss18 (upgrade 3 min 30, then 31 merge jobs, 4 min)
other: 4539 cleaned (938 archived, 3271email_aliaswith an e-mail, 330 kept asotherwith an address or a phone number), 51 left untouched (real children), 10 flagged (user or sponsorship).