add: bulk merge of partners duplicated on the same VAT - #2832
Merged
Conversation
Odoo's own merge wizard walks the foreign keys group by group: for every pair it goes through all ~158 columns that reference res_partner. Fine for a handful of duplicates; on a client database with 6.055 duplicate groups it means over 750.000 statements and does not finish in any usable window. Deleting a single partner has the same problem from the other side -- the database checks all 158 constraints, and any column without an index turns that into a full table scan. This module wraps the SQL procedure from scripts/partner_merge/, which inverts the loops: one statement per column for the whole batch. Foreign keys stay enabled throughout, so integrity is guaranteed by PostgreSQL rather than by the procedure being right, and no superuser is needed. Measured on a production copy of 538.000 partners: 5.350 records merged in about four and a half minutes. The queries live in models/sql_queries.py, unchanged from the scripts except for the psql meta-commands, which become parameters and Python. The working tables keep their names, so a batch prepared from the UI can be inspected from psql. Four buttons -- Analyze, Simulate, Apply, Verify -- with a status bar that does not let you skip the simulation. The simulation runs inside a savepoint and rolls it back, so it cannot write even if this code is wrong; deliberately NOT a second cursor, because the working tables are created in the current transaction and a second cursor deadlocks waiting for the lock CREATE TABLE holds until commit. Applying refuses to finish if any reference is left pointing at an absorbed record. Groups whose unreconciled balance is spread over several records are classified apart -- merging those moves money between ledgers. Guarded out entirely: groups holding the company's own partner record, groups with portal users on more than one record, and groups whose names differ completely on the same VAT number. The kept record is chosen by document volume, which is not the same as name quality, so records whose surviving name looks mangled at import are flagged, with the absorbed names captured before the merge for correction. Depends on base only: the sub-queries touching account_move, sale_order, purchase_order and stock_picking are guarded with to_regclass. Verified on a production copy: the classification reproduces the scripts' figures exactly (925/931 on A, 3.875/4.419 on B, 117/184 on C, 843/2.010 on D, 284 groups guarded out), and after a simulation all 21 records of the test batch still exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Adds
deltatech_partner_merge: merging partners duplicated on the same VAT number, from theinterface, in bulk.
De ce
Mecanismul standard nu e utilizabil, și nu doar la volum. Măsurat pe o copie a bazei unui client
(543.036 parteneri, 1.163.295 note contabile), cu indexurile deja create:
Cauza: standardul parcurge toate cele ~158 de coloane care referă
res_partnerpentru fiecarepereche. Modulul inversează buclele — o instrucțiune per coloană, pentru tot lotul.
Separat, pe aceeași bază lipseau 67 de indexuri pe coloanele FK către parteneri. Ștergerea unei
fișe: 499 ms fără ele, 27 ms cu ele (crearea tuturor a durat o secundă). Modulul semnalează
situația și oferă butonul de creare.
Cum
Reia interogările din
scripts/partner_merge/*.sql, strânse înmodels/sql_queries.py. Singuradiferență față de scripturi e stratul de meta-comenzi psql (
\gexec,\if, variabilele-v), caredevine parametri și Python. Tabelele de lucru păstrează numele, deci un lot pregătit din interfață
poate fi inspectat din psql.
Cheile străine rămân active pe tot parcursul: integritatea e garantată de PostgreSQL, nu de
corectitudinea procedurii, și nu e nevoie de superuser — deci merge și pe odoo.sh.
Patru pași: Analizează → Simulează → Aplică → Verifică, cu bară de stare care nu permite
aplicarea înainte de simulare.
Simularea rulează într-un savepoint și îl anulează, deci nu poate scrie nici dacă acest cod ar
greși. Deliberat nu pe un al doilea cursor: tabelele de lucru se creează în tranzacția curentă, iar
CREATE TABLEține lockACCESS EXCLUSIVEpână la commit — un al doilea cursor se blocheazăașteptând un commit care vine abia după ce simularea se întoarce. Am pierdut zece minute pe deadlock-ul
ăsta la prima variantă; explicația e în cod, ca să nu fie „reparat" înapoi.
Aplicarea refuză să se încheie dacă rămâne vreo referință către o fișă absorbită.
Gărzi și clasificare
Excluse automat din lot: grupurile care conțin fișa companiei proprii, cele cu utilizatori de portal
pe mai multe fișe, și cele cu denumiri complet diferite pe același CUI.
Fișa păstrată se alege după volumul de documente — ceea ce nu e același lucru cu calitatea denumirii.
De aceea masterii cu nume care pare lipit la import sunt marcați, cu denumirile absorbite alături,
capturate înainte de merge (după, ele nu mai există).
Verificat
Pe copia de producție, din interfață:
Anterior, procedura SQL echivalentă a fost rulată pe staging-ul clientului: 575 de fișe în trei
loturi, zero abateri.
Note
dependsdoar pebase: sub-interogările peaccount_move,sale_order,purchase_order,stock_pickingsunt protejate cuto_regclass.CREATE INDEX CONCURRENTLYnu poate rula în tranzacția Odoo, deci butonul creează indexurilenormal (lock de secunde pe tabelele mari); formularul trimite explicit la
01_fk_indexes.sqlpentruinstanțele unde nici atât nu e acceptabil.