From bef7d4723041ce188fec7ba74b241013dd431294 Mon Sep 17 00:00:00 2001
From: Sebastian Listl <s.listl@adito.de>
Date: Wed, 30 Sep 2020 10:37:25 +0200
Subject: [PATCH] #1065733 Duplicate contact merge refactoring

---
 process/DuplicateScanner_lib/process.js | 238 ++++++++++--------------
 1 file changed, 99 insertions(+), 139 deletions(-)

diff --git a/process/DuplicateScanner_lib/process.js b/process/DuplicateScanner_lib/process.js
index 23be5bb109..9c340dd052 100644
--- a/process/DuplicateScanner_lib/process.js
+++ b/process/DuplicateScanner_lib/process.js
@@ -613,14 +613,10 @@ DuplicateScannerUtils.TranslateEntityToIndexFields = function(pEntityName, pEnti
  *
  *  @param {String} pSourceContactId The contact to be integrated into another
  *  @param {String} pTargetContactId The contact in which the source gets integrated
- *  @returns {String}
+ *  @returns {Boolean} if the merge was sucessful
  */
 DuplicateScannerUtils.MergePerson = function(pSourceContactId, pTargetContactId)
 {
-    var updateStatementsCurrentAlias = [];
-    var updateStatementsSystemAlias = [];
-    var deleteStatements = [];
-
     var sourcePersonId = newSelect("PERSON_ID")
         .from("CONTACT")
         .where("CONTACT.CONTACTID", pSourceContactId)
@@ -629,41 +625,29 @@ DuplicateScannerUtils.MergePerson = function(pSourceContactId, pTargetContactId)
         .from("CONTACT")
         .where("CONTACT.CONTACTID", pTargetContactId)
         .cell();
-        
-    var tableInfosCurrentAlias = _DuplicateScannerUtils._getMergeUpdateTableInfosCurrentAlias();
-    var tableInfosSystemAlias = _DuplicateScannerUtils._getMergeUpdateTableInfosSystemAlias();
-
-    updateStatementsCurrentAlias.push(_DuplicateScannerUtils._buildUpdateResetStandardCommunications(pSourceContactId));
-    updateStatementsCurrentAlias = updateStatementsCurrentAlias.concat(_DuplicateScannerUtils._buildUpdateContactIdStatements(tableInfosCurrentAlias, pSourceContactId, pTargetContactId));
-    updateStatementsCurrentAlias = updateStatementsCurrentAlias.concat(_DuplicateScannerUtils._buildUpdateAttachParticipantsToNewContactQuery("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", pSourceContactId, pTargetContactId));
-
-    updateStatementsSystemAlias = updateStatementsSystemAlias.concat(_DuplicateScannerUtils._buildUpdateContactIdStatements(tableInfosSystemAlias, pSourceContactId, pTargetContactId, SqlUtils.getSystemAlias()));
-
-    deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteRemoveObsoleteParticipantsRecordsQuery("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", pSourceContactId, pTargetContactId));
     
+    var isLinkedDataUpdated = _DuplicateScannerUtils._migrateLinkedContactData(pSourceContactId, pTargetContactId);
+    var isParticipantsUpdated = _DuplicateScannerUtils._migrateParticipantsToNewContact("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", 
+        pSourceContactId, pTargetContactId);
+    
+    var deleteStatements = [];
     if (sourcePersonId != targetPersonId)
-        deleteStatements.push(["PERSON", newWhere("PERSON.PERSONID", sourcePersonId).build()]);
+        deleteStatements.push(newWhere("PERSON.PERSONID", sourcePersonId).buildDeleteStatement());
     
-    deleteStatements.push(["CONTACT", newWhere("CONTACT.CONTACTID", pSourceContactId).build()]);
+    deleteStatements.push(newWhere("CONTACT.CONTACTID", pSourceContactId).buildDeleteStatement());
     deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteCachedUnrelatedDuplicateQuery(pSourceContactId));
     
-    for (var i = 0; i < updateStatementsCurrentAlias.length; i++) {  
-            if(updateStatementsCurrentAlias[i][0] == "RemoveThisItem") //see _DuplicateScannerUtils._buildUpdateContactIdStatements for further information
-                updateStatementsCurrentAlias.splice(i,1);
-        }
-        
     //update binary 
     var metaData = db.getBinaryMetadata("CONTACT", "DOCUMENT", pSourceContactId, true, SqlUtils.getBinariesAlias());
-    for (let i = 0; i < metaData.length;  i++) {
-        var updateBinary = db.updateBinaryAssignment(metaData[i]["id"], "CONTACT", "DOCUMENT", pTargetContactId, SqlUtils.getBinariesAlias());
-    }
-    var affectedRowsCurrentAlias = db.updates(updateStatementsCurrentAlias);
-    var affectedRowsSystemAlias = db.updates(updateStatementsSystemAlias, SqlUtils.getSystemAlias());
+    metaData.forEach(function (binaryMetaData) 
+    {
+        db.updateBinaryAssignment(binaryMetaData.id, "CONTACT", "DOCUMENT", pTargetContactId, SqlUtils.getBinariesAlias());
+    });
     var deletedRows = db.deletes(deleteStatements)
 
     DuplicateScannerUtils.DeleteCachedDuplicate(pSourceContactId);
 
-    return (affectedRowsCurrentAlias > 0 && deletedRows > 0);
+    return ((isLinkedDataUpdated || isParticipantsUpdated) && deletedRows > 0);
 }
 
 DuplicateScannerUtils.CreateMergeSuccessActivity = function(pSourceContactId, pTargetContactId, pCurrentContactId, pContext)
@@ -687,30 +671,30 @@ DuplicateScannerUtils.MergeOrganisation = function(pSourceContactId, pTargetCont
     let deleteStatements = [];
 
     var sourceOrganisationId = newSelect("ORGANISATION_ID")
-                                    .from("CONTACT")
-                                    .where("CONTACT.CONTACTID", pSourceContactId)
-                                    .cell();
-
-    var tableInfosCurrentAlias = _DuplicateScannerUtils._getMergeUpdateTableInfosCurrentAlias();
-    var tableInfosSystemAlias = _DuplicateScannerUtils._getMergeUpdateTableInfosSystemAlias();
-
-	updateStatementsCurrentAlias = updateStatementsCurrentAlias.concat(_DuplicateScannerUtils._buildUpdateResetStandardCommunications(pSourceContactId));
-    updateStatementsCurrentAlias = updateStatementsCurrentAlias.concat(_DuplicateScannerUtils._buildUpdateContactIdStatements(tableInfosCurrentAlias, pSourceContactId, pTargetContactId));
-    updateStatementsCurrentAlias = updateStatementsCurrentAlias.concat(_DuplicateScannerUtils._buildUpdateAttachParticipantsToNewContactQuery("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", pSourceContactId, pTargetContactId));
-
-    updateStatementsSystemAlias = updateStatementsSystemAlias.concat(_DuplicateScannerUtils._buildUpdateContactIdStatements(tableInfosSystemAlias, pSourceContactId, pTargetContactId));
-
-    deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteRemoveObsoleteParticipantsRecordsQuery("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", pSourceContactId, pTargetContactId));
+        .from("CONTACT")
+        .where("CONTACT.CONTACTID", pSourceContactId)
+        .cell();
+        
+    var isLinkedDataUpdated = _DuplicateScannerUtils._migrateLinkedContactData(pSourceContactId, pTargetContactId);
+    var isParticipantsUpdated = _DuplicateScannerUtils._migrateParticipantsToNewContact("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", 
+        pSourceContactId, pTargetContactId);
+    
+    var deleteStatements = [];
+    deleteStatements.push(newWhere("CONTACT.CONTACTID", pSourceContactId).buildDeleteStatement());
     deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteOrganisationAndContactQuery(sourceOrganisationId, pSourceContactId));
     deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteCachedUnrelatedDuplicateQuery(pSourceContactId));
-
-    let affectedRowsCurrentAlias = db.updates(updateStatementsCurrentAlias);
-    let affectedRowsSystemAlias = db.updates(updateStatementsSystemAlias, SqlUtils.getSystemAlias());
-    let deletedRows = db.deletes(deleteStatements)
+    
+    //update binary 
+    var metaData = db.getBinaryMetadata("CONTACT", "DOCUMENT", pSourceContactId, true, SqlUtils.getBinariesAlias());
+    metaData.forEach(function (binaryMetaData) 
+    {
+        db.updateBinaryAssignment(binaryMetaData.id, "CONTACT", "DOCUMENT", pTargetContactId, SqlUtils.getBinariesAlias());
+    });
+    var deletedRows = db.deletes(deleteStatements)
 
     DuplicateScannerUtils.DeleteCachedDuplicate(pSourceContactId);
 
-    return (affectedRowsCurrentAlias > 0 && deletedRows >= 2);
+    return ((isLinkedDataUpdated || isParticipantsUpdated) && deletedRows >= 2);
 }
 
 /*
@@ -1092,7 +1076,7 @@ _DuplicateScannerUtils._createInsertDuplicatesClusterQuery = function (pDuplicat
     let duplicatesToInsertQueries = [];
     let cols = ["ID", "CLUSTERID", "DUPLICATEID", "TARGET_ENTITY"];
 
-    if(pClusterId == undefined || pClusterId == null || pClusterId == "")
+    if (!pClusterId)
         pClusterId = util.getNewUUID();
             
     for (let i = 0; i < pDuplicatesRay.length; i++)
@@ -1120,36 +1104,24 @@ _DuplicateScannerUtils._deleteDuplicateClusters = function ()
  * This is because otherwise there would now be in total two "participants" in the same "group" as opposed to one before.
  * Also if they already are in the same "group" those records shouldn't be updated because it would lead to the same outcome.
  *
- * Mandatory: All records ignored for the time being have to be deleted aswell! See #_DuplicateScannerUtils._buildRemoveObsoleteParticipantsRecordsDeleteQuery
- * @returns {String[]} Query to update records
+ * @returns {Boolean} If records have been updated
  */
-_DuplicateScannerUtils._buildUpdateAttachParticipantsToNewContactQuery = function (pTableName, pContactIdColumn, pAssignableIdColumn, pSourceContactId, pTargetContactId, updateStatements)
-{
-    var selectAssignableIdsOfTargetContactQuery = newSelect(pAssignableIdColumn)
-                                                        .from(pTableName)
-                                                        .where([pTableName, pContactIdColumn], pTargetContactId);
-                                                        
-    var subSelectTable = newSelect(["subselect." + pAssignableIdColumn]).from(selectAssignableIdsOfTargetContactQuery,"subselect");
-
-    let updateCondition = newWhere([pTableName, pAssignableIdColumn], subSelectTable, SqlBuilder.NOT_IN())
-                                .and([pTableName, pContactIdColumn], pSourceContactId)
-
-    return [[pTableName, [pContactIdColumn], null, [pTargetContactId], updateCondition.build()]];
-}
-
-
-_DuplicateScannerUtils._buildDeleteRemoveObsoleteParticipantsRecordsQuery = function (pTableName, pContactIdColumn, pAssignableIdColumn, pSourceContactId, pTargetContactId, updateStatements)
+_DuplicateScannerUtils._migrateParticipantsToNewContact = function (pTableName, pContactIdColumn, pAssignableIdColumn, pSourceContactId, pTargetContactId)
 {
-    var selectAssignableIdsOfTargetContactQuery = newSelect(pAssignableIdColumn)
-                                                        .from(pTableName)
-                                                        .where([pTableName, pContactIdColumn], pTargetContactId);
-                                                        
-    var subSelectTable = newSelect(["subselect." + pAssignableIdColumn]).from(selectAssignableIdsOfTargetContactQuery,"subselect");
-
-    let deleteCondition = newWhere([pTableName, pAssignableIdColumn], subSelectTable, SqlBuilder.IN())
-                                .and([pTableName, pAssignableIdColumn], pSourceContactId)
+    var excludedIds = newSelect(pAssignableIdColumn)
+        .from(pTableName)
+        .where([pTableName, pContactIdColumn], pTargetContactId);
 
-    return [[pTableName, deleteCondition.build()]];
+    var updateCount = newWhere([pTableName, pAssignableIdColumn], excludedIds, SqlBuilder.NOT_IN())
+        .and([pTableName, pContactIdColumn], pSourceContactId)
+        .updateFields(new Map().set(pContactIdColumn, pTargetContactId), pTableName);
+        
+    var deleteCount = newWhere([pTableName, pAssignableIdColumn], excludedIds, SqlBuilder.IN())
+        .and([pTableName, pContactIdColumn], pSourceContactId)
+        .tableName(pTableName)
+        .deleteData();
+    
+    return updateCount > 0 || deleteCount > 0;
 }
 
 _DuplicateScannerUtils._buildDeleteOrganisationAndContactQuery = function(pSourceOrganisationId, pSourceContactId)
@@ -1185,72 +1157,70 @@ _DuplicateScannerUtils._getIgnoreSourceRecordPattern = function(pRecordIdValueTo
     return null;
 }
 
-_DuplicateScannerUtils._buildUpdateContactIdStatements = function(pTableInfos, pSourceContactId, pTargetContactId, pAlias)
+_DuplicateScannerUtils._migrateLinkedContactData = function (pSourceContactId, pTargetContactId)
 {
-    return pTableInfos.map(function ([tableName, columnName, additionalCondition])
+    var updateStatements = new Map();
+    var resetStandardCommunications = _DuplicateScannerUtils._buildUpdateResetStandardCommunications(pSourceContactId);
+    var currentAlias = db.getCurrentAlias();
+    updateStatements.set(currentAlias, [resetStandardCommunications]);
+    _DuplicateScannerUtils._getLinkedTableInfos.forEach(function ([tableName, columnName, additionalCondition, alias])
     {
+        if (!alias)
+            alias = currentAlias;
+        
         var updateValues = {};
         updateValues[columnName] = pTargetContactId;
-        var exclude = false;
          
-        if(tableName == "COMMUNICATION")
+        var targetStandard = newSelect("CONTACT.ADDRESS_ID")
+                                        .from("CONTACT")
+                                        .where("CONTACT.CONTACTID", pTargetContactId)
+                                        .cell();
+
+        //set the standardaddress of the sourceContact as standard of the targetContact if it doesn't have one set yet
+        if(!targetStandard) 
         {
-            var sourceComm = newSelect("COMMUNICATION.ADDR")
-                                        .from("COMMUNICATION")
-                                        .where("COMMUNICATION.CONTACT_ID", pSourceContactId)
-                                        .arrayColumn();
-                                        
-            var targetComm = newSelect("COMMUNICATION.ADDR")
-                                        .from("COMMUNICATION")
-                                        .where("COMMUNICATION.CONTACT_ID", pTargetContactId)
-                                        .arrayColumn();
-            //set exclude to true if both of the datatasets have the exact same address (we don't want one person to have the same address twice)
-            if(sourceComm.length > 0 && targetComm.length > 0 )
-                {
-                    for (var i = 0; i < sourceComm.length; i++) {
-                        for (var x = 0; x < targetComm.length; x++) {
-                            if(sourceComm[i] == targetComm[x])
-                                exclude = true;
-                        }
-                    }
-                }
-                
-            var targetStandard = newSelect("CONTACT.ADDRESS_ID")
-                                            .from("CONTACT")
-                                            .where("CONTACT.CONTACTID", pTargetContactId)
-                                            .cell();
-                                            
-            //set the standardaddress of the sourceContact as standard of the targetContact if it doesn't have one set yet
-            if(!targetStandard) 
-            {
-                var sourceStandard = newSelect("CONTACT.ADDRESS_ID")
-                                            .from("CONTACT")
-                                            .where("CONTACT.CONTACTID", pSourceContactId)
-                                            .cell();
-                newWhere("CONTACT.CONTACTID", pTargetContactId).updateData(true, "CONTACT", ["ADDRESS_ID"], null, [sourceStandard]);
-            }
+            var sourceStandard = newSelect("CONTACT.ADDRESS_ID")
+                                        .from("CONTACT")
+                                        .where("CONTACT.CONTACTID", pSourceContactId)
+                                        .cell();
+            newWhere("CONTACT.CONTACTID", pTargetContactId).updateFields({"ADDRESS_ID": sourceStandard});
         }
         
-        if(exclude)
-        {
-            newWhere("COMMUNICATION.CONTACT_ID", pSourceContactId).deleteData(true, "COMMUNICATION"); //delete the address of the sourceContact
-            return ["RemoveThisItem"]
-        }
+        var updateCondition = newWhere([tableName, columnName], pSourceContactId).andIfSet(additionalCondition);
+        
+        //don't use communications that the target already has
+        if (tableName == "COMMUNICATION")
+            updateCondition.and(null, newSelect("targetComm.COMMUNICATIONID")
+                    .from("COMMUNICATION", "targetComm")
+                    .where(["COMMUNICATION", "CONTACT_ID", "targetComm"], pTargetContactId)
+                    .and("targetComm.ADDR = COMMUNICATION.ADDR"),
+                SqlBuilder.NOT_EXISTS());
+            
+        var updateStatement = updateCondition.buildUpdateStatement(updateValues, tableName);
+        if (updateStatements.has(alias))
+            updateStatements.get(alias).push(updateStatement);
         else
-        {
-            return newWhere([tableName, columnName], pSourceContactId, undefined, undefined, pAlias)
-                .andIfSet(additionalCondition)
-                .buildUpdateStatement(updateValues, tableName)
-        }
+            updateStatements.set(alias, [updateStatement]);
+    });
+    
+    var totalChanges = 0;
+    
+    updateStatements.forEach(function (statements, alias)
+    {
+        totalChanges += db.updates(statements, alias);
     });
+    
+    totalChanges += newWhere("COMMUNICATION.CONTACT_ID", pSourceContactId).deleteData(); //delete leftover communications from the source contact
+
+    return totalChanges > 0;
 }
 
 /*
  * Contains all Tables and their fields which may contain the contact id to be replaced for the data alias
  *
- * @returns {String[[]]} Array in the format [TableName, ContactIdColumnName, AdditionalCondition]
+ * @returns {String[[]]} Array in the format [TableName, ContactIdColumnName, AdditionalCondition, alias]
  */
-_DuplicateScannerUtils._getMergeUpdateTableInfosCurrentAlias = function()
+_DuplicateScannerUtils._getLinkedTableInfos = function()
 {
     return[
             ["AB_APPOINTMENTLINK", "OBJECT_ROWID", ""],
@@ -1284,22 +1254,12 @@ _DuplicateScannerUtils._getMergeUpdateTableInfosCurrentAlias = function()
             ["DSGVOINFO", "CONTACT_ID", ""],
             ["TIMETRACKING", "CONTACT_ID", ""],
             ["ACTIVITYLINK", "OBJECT_ROWID", ""],
-            ["AB_ATTRIBUTERELATION", "OBJECT_ROWID", ""]
+            ["AB_ATTRIBUTERELATION", "OBJECT_ROWID", ""],
+            
+            ["ASYS_CALENDARLINK", "DBID", "", SqlUtils.getSystemAlias()]
         ];
 }
 
-/*
- * Contains all Tables and their fields which may contain the contact id to be replaced for the system alias
- * in the past ASYS_BINARY had also been updated like this, but know we are using 
- * the function db.updateBinaryAssignment (see also DuplicateScannerUtils.MergePerson).
- *
- * @returns {String[[]]} Array in the format [TableName, ContactIdColumnName, AdditionalCondition]
- */
-_DuplicateScannerUtils._getMergeUpdateTableInfosSystemAlias = function()
-{
-    return [["ASYS_CALENDARLINK", "DBID", ""]];
-}
-
 /*
  * Returns wether or not a value should be substring'd
  *
-- 
GitLab