Something went wrong on our end
-
Johannes Goderbauer authored
1048420, 1052262, 1048690 See merge request xrm/basic!143 (cherry picked from commit cad0c57e) 2f8afb14 [Projekt: Entwicklung - Neon][TicketNr.: 1048690][Reiter Beziehungen in der Firma] 67c72553 zwischencommit b4ef5e07 [Projekt: Entwicklung - Neon][TicketNr.: 1052262][Firmen und Personen können nicht gelöscht werden] 53deb0a0 [Projekt: Entwicklung - Neon][TicketNr.: 1048420][Dublette/ Kontakt:... 64f98d05 [Projekt: Entwicklung - Neon][TicketNr.: 1048420][Dublette/ Kontakt:...
Johannes Goderbauer authored1048420, 1052262, 1048690 See merge request xrm/basic!143 (cherry picked from commit cad0c57e) 2f8afb14 [Projekt: Entwicklung - Neon][TicketNr.: 1048690][Reiter Beziehungen in der Firma] 67c72553 zwischencommit b4ef5e07 [Projekt: Entwicklung - Neon][TicketNr.: 1052262][Firmen und Personen können nicht gelöscht werden] 53deb0a0 [Projekt: Entwicklung - Neon][TicketNr.: 1048420][Dublette/ Kontakt:... 64f98d05 [Projekt: Entwicklung - Neon][TicketNr.: 1048420][Dublette/ Kontakt:...
process.js 54.93 KiB
import("system.project");
import("ActivityTask_lib");
import("KeywordRegistry_basic");
import("system.translate");
import("system.datetime");
import("JditoFilter_lib");
import("system.process");
import("system.util");
import("system.vars");
import("system.net");
import("system.logging");
import("system.db");
import("system.entities");
import("Sql_lib");
import("system.indexsearch");
/**
* Methods for duplicate scanning.
* Do not create an instance of this!
*
* @class
*/
function DuplicateScannerUtils() {}
/*
* Loads all prefilters for a scanner in the form of arrays in an array.
* Single filter record: [FILTER_CONDITION, COUNT_CHARACTERS_TO_USE, MAX_RESULTS_THRESHOLD]
*
* @param {String} pFilterName Name of the filter
* @param {String} pTargetEntity Entity which has been configured
* @returns {String[[]]} Array of arrays containing the configured values
*/
DuplicateScannerUtils.loadFilters = function(pFilterName, pTargetEntity)
{
let query = newSelect("FILTER_CONDITION, COUNT_CHARACTERS_TO_USE, MAX_RESULTS_THRESHOLD")
.from("DUPLICATESCANNERPREFILTERCONFIG")
.join("DUPLICATESCANNER", "DUPLICATESCANNER.ID = DUPLICATESCANNERPREFILTERCONFIG.DUPLICATESCANNER_ID")
.where("DUPLICATESCANNER.FILTER_NAME", pFilterName)
.and("DUPLICATESCANNER.ENTITY_TO_SCAN_NAME", pTargetEntity);
return query.table();
}
/*
* Deletes the cached duplicate for the given id.
* If there would only remain one item in the cluster after deletion, the whole cluster including the duplicate gets deleted.
* In this case, all records marked as unrelated duplicate will be deleted aswell.
*
* @param {String} pDuplicateId Id of the duplicate to delete
*/
DuplicateScannerUtils.DeleteCachedDuplicate = function(pDuplicateId)
{
var query = newSelect("count(ID), CLUSTERID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.CLUSTERID", newSelect("CLUSTERID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId).build(),
SqlBuilder.IN())
.and("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId, SqlBuilder.NOT_EQUAL())
.groupBy("CLUSTERID");
var countAndClusterId = query.arrayRow();
let countDuplicatesInClusterWithoutParameterId = countAndClusterId[0];
let clusterId = countAndClusterId[1];
//If only one duplicate would be remaining,
//the whole cluster has to be deleted because there are no more duplicates.
//Otherwise delete just the single duplicate. The parameterized duplicate has been excluded via sql
//therefore check for smaller/equals 1
if(countDuplicatesInClusterWithoutParameterId <= 1)
{
let deleteStatements = [];
deleteStatements.push(["DUPLICATECLUSTERS", newWhere("DUPLICATECLUSTERS.CLUSTERID", clusterId).build()]);
deleteStatements.push(["UNRELATEDDUPLICATES", newWhere("UNRELATEDDUPLICATES.CLUSTERID", clusterId).build()]);
db.deletes(deleteStatements);
}
else
{
newWhereIfSet("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId)
.deleteData(true, "DUPLICATECLUSTERS");
//Delete all records where this duplicateId is mentioned
DuplicateScannerUtils.DeleteAllUnrelatedDuplicateRelations(pDuplicateId);
}
}
/*
* Deletes all Clusters for the given target Entity.
* No records markes as unrelated duplicate are being deleted.
*
* @param {String} pTargetEntity Entity which has been configured
* @return Count of deleted rows
*/
DuplicateScannerUtils.DeleteDuplicateClustersByTargetEntity = function(pTargetEntity)
{
return newWhereIfSet("DUPLICATECLUSTERS.TARGET_ENTITY", pTargetEntity)
.deleteData(true, "DUPLICATECLUSTERS");
}
/*
* Updates the duplicate relations markes as unrelated for the given target entity.
* All ClusterIds get updated with the new values if the same combination of duplicateIds
* still exists in the DUPLICATECLUSTERS table.
*
* Afterwards, all records which contain a nonexistend clusterId are being deleted
*
* @param {String} pTargetEntity Name of Entity whose duplicates should be updated
*/
DuplicateScannerUtils.RefreshUnrelatedDuplicateRelations = function(pTargetEntity)
{
/*
* Update all records with the current valid clusterId where the same duplicateId combination exists
*/
let INDEX_NEW_CLUSTERID = 0;
let INDEX_OLD_CLUSTERID = 1;
let query = newSelect("dc1.CLUSTERID, ud.CLUSTERID")
.from("UNRELATEDDUPLICATES", "ud")
.join("DUPLICATECLUSTERS", "dc1.DUPLICATEID = ud.SOURCEDUPLICATEID", "dc1")
.join("DUPLICATECLUSTERS", "dc2.DUPLICATEID = ud.UNRELATEDDUPLICATEID", "dc2")
.where(["DUPLICATECLUSTERS", "TARGET_ENTITY", "dc1"], pTargetEntity);
let newIdOldIdRay = query.table();
let updateStatements = [];
//Build update statements to set new clusterIds
for (let i = 0; i < newIdOldIdRay.length; i++)
{
let newClusterId = newIdOldIdRay[i][INDEX_NEW_CLUSTERID];
let oldClusterId = newIdOldIdRay[i][INDEX_OLD_CLUSTERID];
let updateColumns = ["CLUSTERID"];
let updateStatement = ["UNRELATEDDUPLICATES", updateColumns, null, [newClusterId], newWhere("UNRELATEDDUPLICATES.CLUSTERID", oldClusterId).build()];
updateStatements.push(updateStatement);
}
db.updates(updateStatements);
/*
* All unrelated duplicate ids that still exist in a cluster, have been updated with the new cluster id.
* All records with a nonexistend clusterid can now be deleted because they haven't been detected as a duplicate any more.
*/
newWhere("UNRELATEDDUPLICATES.CLUSTERID", newSelect("dc1.CLUSTERID").from("DUPLICATECLUSTERS", "dc1"), SqlBuilder.NOT_IN())
.deleteData(true, "UNRELATEDDUPLICATES");
}
/*
* Creates a relation between two duplicates which means they are unrelated.
* They will not appear in each others duplicate tab any more.
* To remove this relation use DuplicateScannerUtils.DeleteUnrelatedDuplicateRelation
*
* @param {String} pSourceContactId Id of first duplicate
* @param {String} pUnrelatedContactId Id of second duplicate
* @param {String} pClusterId Id of the cluster in which the duplicates are aggregated
* @returns {String} Number of Records inserted
*/
DuplicateScannerUtils.CreateUnrelatedDuplicateRelation = function(pSourceContactId, pUnrelatedContactId, pClusterId)
{
let newUid = util.getNewUUID();
let columns = ["ID", "SOURCEDUPLICATEID", "UNRELATEDDUPLICATEID", "CLUSTERID"];
let values = [newUid, pSourceContactId, pUnrelatedContactId, pClusterId];
return db.insertData("UNRELATEDDUPLICATES", columns, null, values);
}
/*
* Gets the cluster id in which the given duplicate id exists
*
* @param {String} pDuplicateId whose cluster id should be searched
* @returns {String} Cluster id
*/
DuplicateScannerUtils.GetClusterId = function(pDuplicateId)
{
return newSelect("CLUSTERID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId)
.cell();
}
/*
* Deletes the "unrelated" relation between two duplicates
*
* @param {String} pSourceDuplicateId Id of the source duplicate
* @param {String} pUnrelatedDuplicateId Id of the source duplicate
* @returns {String} Number of records deleted
*/
DuplicateScannerUtils.DeleteUnrelatedDuplicateRelation = function(pSourceDuplicateId, pUnrelatedDuplicateId)
{
return newWhere("UNRELATEDDUPLICATES.SOURCEDUPLICATEID", pSourceDuplicateId)
.and("UNRELATEDDUPLICATES.UNRELATEDDUPLICATEID", pUnrelatedDuplicateId)
.deleteData(true, "UNRELATEDDUPLICATES");
}
/*
* Deletes all relations to a duplicate id wether the id is the source or the unrelated duplicate in the relation
*
* @param {String} pDuplicateId Duplicate id whose "unrelated" relations are to delete
* @returns {String} Number of records deleted
*/
DuplicateScannerUtils.DeleteAllUnrelatedDuplicateRelations = function(pDuplicateId)
{
DuplicateScannerUtils.DeleteUnrelatedDuplicateRelation(pDuplicateId, pDuplicateId);
}
/*
* Loads all other duplicates from the cluster in which the parameterized duplicate is located
*
* @param {String} pDuplicateId
* @returns {String[]} Array of duplicate ids
*/
DuplicateScannerUtils.GetCachedDuplicatesForDuplicateId = function(pDuplicateId)
{
return newSelect("DUPLICATEID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.CLUSTERID", newSelect("CLUSTERID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId),
SqlBuilder.IN())
.and("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateId, SqlBuilder.NOT_EQUAL())
.and("DUPLICATECLUSTERS.DUPLICATEID", newSelect("UNRELATEDDUPLICATEID")
.from("UNRELATEDDUPLICATES")
.where("UNRELATEDDUPLICATES.SOURCEDUPLICATEID", pDuplicateId),
SqlBuilder.NOT_IN())
.and("DUPLICATECLUSTERS.DUPLICATEID", newSelect("SOURCEDUPLICATEID")
.from("UNRELATEDDUPLICATES")
.where("UNRELATEDDUPLICATES.UNRELATEDDUPLICATEID", pDuplicateId),
SqlBuilder.NOT_IN())
.arrayColumn();
}
/*
* Returns all duplicate ids which haven't been marked as unrelated for the given cluster id.
*
* @param {String} pClusterId The clusters id
* @return {String[]} Array of duplicate ids excluding those marked as unrelated
*/
DuplicateScannerUtils.GetCachedDuplicatesForClusterId = function(pClusterId)
{
return newSelect("DUPLICATEID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.DUPLICATEID", newSelect("UNRELATEDDUPLICATEID").from("UNRELATEDDUPLICATES"),
SqlBuilder.NOT_IN())
.and("DUPLICATECLUSTERS.CLUSTERID", pClusterId)
.arrayColumn();
}
/*
* Recreates the cached duplicate clusters based on the configured pattern. <br />
* The old clusters have to be deleted manually beforehand or by using "DeleteDuplicateClustersByTargetEntity".<br />
* If there have already been ignored relations between duplicate records, it's advised to call "RefreshUnrelatedDuplicateRelations" after the recreation of the duplicates cache.<br />
* Please check the documentation of the params on how to get the infos required.<br />
* <br />
* If the usage of an external webservice has been activated, the search will be executed beforehand and the results will then be given to the pFormatValuesConsumeWebserviceCallback via parameter.<br />
* To access the values it is advised to run thru the parameter like an array and access its value by key which is the index field name. The entity<br />
* field names can be converted using DuplicateScannerUtils.TranslateEntityToIndexFields. For further infos see the example section below.<br />
* <br />
* Attention!<br />
* If it is configured to use the external webservice callback the values have to be in the same format as they are in the parameter of the callback.<br />
* <br />
* @param {String} pFilterName Name of the filter to use
* @param {String} pTargetEntity The target entity which has been assigned to the filters configuration
* @param {String} pRecordsBlockSize The values which are checked get loaded in blocks.
* @param {String} pFormatValuesConsumeWebserviceCallback Null if no external service is used otherwise a function with one parameter.
* @return {Int} Count of duplicate clusters created
*
* @example
* var filterName = "PersonDuplicates";
* var targetEntity = "Person_entity";
* var recordBlockSize = DuplicateScannerUtils.GetBlockSize();
*
* let formatToJsonAndCallWsCallback = function(pPossibleDuplicatesRay)
* {
* let indexResultFields = DuplicateScannerUtils.TranslateEntityToIndexFields(targetEntity, resultFields)
*
* //Run thru every duplicate result an read out the resultfields
* for (let i = 0; i < pPossibleDuplicatesRay.length; i++)
* {
* for (let b = 0; b < resultFields.length; b++)
* {
* let entityFieldName = resultFields[b];
* let indexFieldName = indexResultFields[entityFieldName];
* //format values
* }
* }
* //call webservice
* //reformat results to same structure as before
* return pPossibleDuplicatesRay;
* };
*
* DuplicateScannerUtils.DeleteDuplicateClustersByTargetEntity(targetEntity);
*
* DuplicateScannerUtils.RebuildDuplicatesCache(filterName, targetEntity, pRecordsBlockSize, formatToJsonAndCallWsCallback);
*
* DuplicateScannerUtils.RefreshUnrelatedDuplicateRelations(targetEntity);
*/
DuplicateScannerUtils.RebuildDuplicatesCache = function(pFilterName, pTargetEntity,
pRecordsBlockSize, pFormatValuesConsumeWebserviceCallback)
{
let useExternalWebservice = _DuplicateScannerUtils._isUseExternalWebservice(pFilterName, pTargetEntity);
let resultFields = DuplicateScannerUtils.LoadResultFields(filterName, targetEntity);
let indexPattern = _DuplicateScannerUtils._loadIndexPattern(pFilterName, pTargetEntity);
let entityFieldConfigs = _DuplicateScannerUtils._loadEntityFieldConfigsFromPattern(indexPattern);
let entityIdField = _DuplicateScannerUtils._loadEntityIdField(pFilterName, pTargetEntity);
let alreadyIdentifiedIds = [];
let entityFields = _DuplicateScannerUtils._loadEntityFieldsFromFieldConfigs(entityFieldConfigs);
entityFields.push(entityIdField);
let targetRecords = DuplicateScannerUtils.LoadEntityRecords(pTargetEntity, entityFields, 0, pRecordsBlockSize);
let currentRecordIndex = pRecordsBlockSize;
while(targetRecords.length > 0)
{
foundDuplicateIds = DuplicateScannerUtils.ScanRecords(pTargetEntity, targetRecords,
entityFieldConfigs, resultFields, useExternalWebservice,
pFormatValuesConsumeWebserviceCallback, alreadyIdentifiedIds, indexPattern, entityIdField);
if (foundDuplicateIds)
alreadyIdentifiedIds = alreadyIdentifiedIds.concat(foundDuplicateIds);
if(targetRecords.length < pRecordsBlockSize)
{
break;
}
targetRecords = DuplicateScannerUtils.LoadEntityRecords(pTargetEntity, entityFields,
currentRecordIndex, pRecordsBlockSize);
currentRecordIndex += pRecordsBlockSize;
}
}
DuplicateScannerUtils.ScanRecords = function(pTargetEntity, pTargetRecordsData,
pEntityFieldConfigs, pResultFields, pUseExternalWebservice, pFormatValuesConsumeWebserviceCallback, pAlreadyIdentifiedIds, pIndexPattern, pEntityIdField)
{
let foundDuplicateIds = [];
//If the contact id loading query has no results, stop.
//No ids should be deleted if an error has been made in this query.
if(pTargetRecordsData.length <= 0)
return null;
//First it gets checked if the current id has already been identified. If that's the case it'll continue with the next.
//Otherwise an object gets build in the form of ["FilterFieldName" = "FilterFieldValueFromQuery"] with which a scan for possible duplicates get's started
var duplicatesToInsertQueries = [];
for (b = 0; b < pTargetRecordsData.length; b++)
{
let entityFieldValuesRay = DuplicateScannerUtils.BuildEntityFieldConfigValueRays(pEntityFieldConfigs, pTargetRecordsData[b]);
//The first field in this Array must always be the configured id field. This is ensured using onValidation-logic
let idValue = pTargetRecordsData[b][pEntityIdField];
//If the current Id has already been identified, continue
if(pAlreadyIdentifiedIds.indexOf(pTargetRecordsData[b][pEntityIdField]) > -1)
{
continue;
}
let foundDuplicates = _DuplicateScannerUtils._scanForDuplicates(pTargetEntity,
entityFieldValuesRay, pResultFields, idValue, pFormatValuesConsumeWebserviceCallback, pUseExternalWebservice, pIndexPattern)
if(foundDuplicates == null || foundDuplicates.length == 0)
{
continue;
}
//Insert all found duplicate ids into an cache array because those ids don't have to be checked again lateron.
for (let i = 0; i < foundDuplicates.length; i++)
{
let localId = foundDuplicates[i][indexsearch.FIELD_ID];
foundDuplicateIds.push(localId);
}
pAlreadyIdentifiedIds = pAlreadyIdentifiedIds.concat(foundDuplicateIds);
//The duplicates list contains only the found duplicates to the original id, therefore it get's added manually
foundDuplicateIds.push(pTargetRecordsData[b][pEntityIdField]);
let insertQueriesRay = _DuplicateScannerUtils._createInsertDuplicatesClusterQuery(foundDuplicateIds, pTargetEntity)
duplicatesToInsertQueries = duplicatesToInsertQueries.concat(insertQueriesRay);
foundDuplicateIds = [];
}
db.inserts(duplicatesToInsertQueries, db.getCurrentAlias(), 10 * datetime.ONE_MINUTE);
return foundDuplicateIds;
}
/*
* Searches for a cluster which contains the duplicates specified by the parameterized array. <br />
* The contents of the cluster have to be identical, if no fitting cluster could be found an empty string is returned.
*
* @param {String} pNewRecordId The id of the record which was used to scan for duplicates
* @param {String[]} pDuplicateIds Duplicate ids used to search for a cluster containing them
* @param {String} pTargetEntity Entity which has been configured
* @returns {String} A clusterid if a matching cluster has been found, otherwise ""
*/
DuplicateScannerUtils.CacheNewScanResults = function(pNewRecordId, pDuplicateIds, pTargetEntity)
{
let duplicateIds = [];
//Run thru every duplicate result and read out the id.
//Do it now to have a simple array on all usages lateron.
for (let i = 0; i < pDuplicateIds.length; i++)
{
let duplicateContactId = pDuplicateIds[i][indexsearch.FIELD_ID];
duplicateIds.push(duplicateContactId);
}
let clusterId = DuplicateScannerUtils.GetClusterWithIdenticalDuplicates(duplicateIds);
//If no cluster has beend found, create a new one with all found duplicateIds,
//otherwise add the id to the existing cluster
let idRayToInsert = [];
if(clusterId == undefined || clusterId == null || clusterId == "")
{
idRayToInsert = duplicateIds;
idRayToInsert.push(pNewRecordId);
}
else
idRayToInsert.push(pNewRecordId);
insertQueriesRay = _DuplicateScannerUtils._createInsertDuplicatesClusterQuery(idRayToInsert, pTargetEntity, clusterId)
return db.inserts(insertQueriesRay);
}
/*
* Searches for a cluster which contains the duplicates specified by the parameterized array. <br />
* The contents of the cluster have to be identical, if no fitting cluster could be found an empty string is returned.
*
* @param {String[]} pDuplicateIds Duplicate ids which should be in the same cluster
* @returns {String} Id of the cluster which contains all given duplicate ids or ""
*/
DuplicateScannerUtils.GetClusterWithIdenticalDuplicates = function(pDuplicateIds)
{
let RESULT_NO_CLUSTER_FOUND = "";
if(pDuplicateIds.length < 1)
return RESULT_NO_CLUSTER_FOUND;
let clusterIdSelect = newSelect("distinct CLUSTERID")
.from("DUPLICATECLUSTERS")
.where();
for (let i = 0; i < pDuplicateIds.length; i++)
{
clusterIdSelect.and("DUPLICATECLUSTERS.CLUSTERID", newSelect("CLUSTERID").from("DUPLICATECLUSTERS").where("DUPLICATECLUSTERS.DUPLICATEID", pDuplicateIds[i]),
SqlBuilder.IN());
}
let foundClusterId = clusterIdSelect.cell();
if(foundClusterId == null || foundClusterId == "")
return RESULT_NO_CLUSTER_FOUND;
let duplicatesInCluster = newSelect("DUPLICATEID")
.from("DUPLICATECLUSTERS")
.where("DUPLICATECLUSTERS.CLUSTERID", foundClusterId)
.arrayColumn();
/*
* A cluster has been searched which contains all duplicate ids as specified via parameter.
* There's the possibility that this cluster contains even more duplicates than specified via the parameter.
* In this case, the cluster and the parameterized duplicateids are not identical
* which means a new cluster has to be created.
*/
if(pDuplicateIds.length != duplicatesInCluster.length)
return RESULT_NO_CLUSTER_FOUND;
else
return foundClusterId;
}
DuplicateScannerUtils.LoadEntityRecords = function(pTargetEntity, pEntityFields, pStartRow, pCountRecordsToLoad)
{
let getRowsConfig = entities.createConfigForLoadingRows()
.entity(pTargetEntity)
.fields(pEntityFields)
.count(pCountRecordsToLoad)
.startrow(pStartRow);
return entities.getRows(getRowsConfig);
}
/*
* Loads the configured resultfields as array
*
* @param {String} pFilterName Name of the filter
* @param {String} pTargetEntity Entity which has been configured
* @returns {String[]} Resultfields as array
*/
DuplicateScannerUtils.LoadResultFields = function(pFilterName, pTargetEntity)
{
return newSelect("dsrfc.ENTITY_FIELD_NAME")
.from("DUPLICATESCANNERRESULTFIELDCONFIG dsrfc")
.join("DUPLICATESCANNER", "DUPLICATESCANNER.ID = dsrfc.DUPLICATESCANNER_ID")
.where("DUPLICATESCANNER.FILTER_NAME", pFilterName)
.and("DUPLICATESCANNER.ENTITY_TO_SCAN_NAME", pTargetEntity)
.arrayColumn();
}
/*
* Scans for duplicates based on the configured pattern and the selected id field<br />
* All values to the used placeholders have to be present in "pValuesToCheck"<br />
* First all placeholders in the pattern will be replaced with their respective values.
* Then, the pattern is extended wo exclude the record on which the search is based on.
*<br />
* If the usage of an external webservice has been activated, the result will then be given to the pFormatValuesConsumeWebserviceCallback via parameter.<br />
* To access the values it is advised to run thru the parameter like an array and access its value by key which is the index field name. The entity
* field names can be converted using DuplicateScannerUtils.TranslateEntityToIndexFields
* <br />
* <br />
* Attention!<br />
* If it's a single ScanForDuplicates call it doesn't matter what the callback returns because after the callback, no more modifications follow before
* returning the data.<br />
* If it's inside the RebuildCache the values have to be in the same format as the parameter
*
* @param {String} pFilterName Name of the filter
* @param {String} pTargetEntity Respective target entity
* @param {{"key", "value"}} pValuesToCheck An object with key value pairs which hold the name of the entity field as key and it's value as value. See the example "valuesToCheck"
* @param {function} pFormatValuesConsumeWebserviceCallback Null if no external service is used otherwise a function with one parameter.
* If the function is called is based on the configuration of the current scanner
* @returns {[["key", "value"]]} Array of Key-Value-Pairs based on the configured resultfields, if an external webservices was used
* the structure is defined by the parameterized function "pFormatValuesConsumeWebserviceCallback"
*
* @example
* var filterName = "PersonDuplicates";
* let targetEntity = "Person_entity";
* let valuesToCheck = {};
* var entityModel = project.getEntityStructure(targetEntity);
*
* //Read the values of all available entity fields and write the fieldname/value combination
* //as key/value pairs into an object. This is used to trigger the scan for duplicates
* let fieldValue = "";
* let entityFields = [];
* for (fieldname in entityModel.fields)
* {
* field = entityModel.fields[fieldname];
* if(field.fieldType == project.ENTITYFIELDTYPE_FIELD)
* {
* fieldValue = vars.get("$field." + field.name);
*
* if(fieldValue != null && fieldValue != "")
* {
* valuesToCheck[field.name] = fieldValue;
* }
* }
* }
*
* let formatToJsonAndCallWsCallback = function(pPossibleDuplicatesRay)
* {
* let indexResultFields = DuplicateScannerUtils.TranslateEntityToIndexFields(targetEntity, resultFields)
*
* //Run thru every duplicate result an read out the resultfields
* for (let i = 0; i < pPossibleDuplicatesRay.length; i++)
* {
* for (let b = 0; b < resultFields.length; b++)
* {
* let entityFieldName = resultFields[b];
* let indexFieldName = indexResultFields[entityFieldName];
* //format values
* }
* }
* //call webservice
* //reformat results to same structure as before
* return pPossibleDuplicatesRay;
* };
*
* //The result values can be accessed as seen above in "formatToJsonAndCallWsCallback"
* DuplicateScannerUtils.ScanForDuplicates(filterName, targetEntity, valuesToCheck,
* formatToJsonAndCallWsCallback);
*/
DuplicateScannerUtils.ScanForDuplicates = function(pFilterName, pTargetEntity, pValuesToCheck,
pFormatValuesConsumeWebserviceCallback)
{
let useExternalWebservice = _DuplicateScannerUtils._isUseExternalWebservice(pFilterName, pTargetEntity);
let resultFields = DuplicateScannerUtils.LoadResultFields(pFilterName, pTargetEntity);
let indexPattern = _DuplicateScannerUtils._loadIndexPattern(pFilterName, pTargetEntity);
let entityFieldConfigs = _DuplicateScannerUtils._loadEntityFieldConfigsFromPattern(indexPattern);
let entityIdField = _DuplicateScannerUtils._loadEntityIdField(pFilterName, pTargetEntity);
let entityFieldConfigValuesRay = DuplicateScannerUtils.BuildEntityFieldConfigValueRays(entityFieldConfigs, pValuesToCheck);
//The first field in this Array must always be the configured id field.
let idValue = pValuesToCheck[entityIdField];
return _DuplicateScannerUtils._scanForDuplicates(pTargetEntity,
entityFieldConfigValuesRay, resultFields, idValue,
pFormatValuesConsumeWebserviceCallback, useExternalWebservice, indexPattern)
}
/*
* Executes a indexsearch.lookupIndexField for eacht entity field in the parameterized array
* and returns it as Map.
*
* @param {String} pEntityName ...Name of the entity
* @param {String[]} pEntityFields Array of the entities Fields to translate to index fields
* @returns Map-like object where (key = entity field) and (value = index field)
*
* @example
* let entityResultFields = ["LASTNAME"];
* let entityIndexFields = DuplicateScannerUtils.TranslateEntityToIndexFields("Person_entity", entityResultFields);
*
*/
DuplicateScannerUtils.TranslateEntityToIndexFields = function(pEntityName, pEntityFields)
{
let entityIndexFields = {};
for (let i = 0; i < pEntityFields.length; i++)
{
let entityFieldName = pEntityFields[i];
//todo remove if api has been fixed
if(entityFieldName.startsWith(pEntityName))
entityFieldName = entityFieldName.replace(pEntityName + ".", "");
let indexField = indexsearch.lookupIndexField(pEntityName, entityFieldName);
entityIndexFields[pEntityFields[i]] = indexField;
}
return entityIndexFields;
}
/*
*
* Merges the source person into the target person.
* This
* - replaces the source's with the target's contactid in a predefined set of tables.
* - resets the standard communications of the source contact and keeps the ones of the target.
* - updates participants of campaigns and removes obsolet ones(which would be duplicates)
* - deletes the source person and contact
* - deletes the duplicate record, if one exists
* - deletes all unrelated-duplicate-relations containing the source contact id
*
* @param {String} pSourceContactId The contact to be integrated into another
* @param {String} pTargetContactId The contact in which the source gets integrated
* @returns {String}
*/
DuplicateScannerUtils.MergePerson = function(pSourceContactId, pTargetContactId)
{
var updateStatementsCurrentAlias = [];
var updateStatementsSystemAlias = [];
var deleteStatements = [];
var sourcePersonId = newSelect("PERSON_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, SqlUtils.getSystemAlias()));
deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteRemoveObsoleteParticipantsRecordsQuery("CAMPAIGNPARTICIPANT", "CONTACT_ID", "CAMPAIGN_ID", pSourceContactId, pTargetContactId));
deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeletePersonAndContactQuery(sourcePersonId, pSourceContactId));
deleteStatements = deleteStatements.concat(_DuplicateScannerUtils._buildDeleteCachedUnrelatedDuplicateQuery(pSourceContactId));
var affectedRowsCurrentAlias = db.updates(updateStatementsCurrentAlias);
var affectedRowsSystemAlias = db.updates(updateStatementsSystemAlias, SqlUtils.getSystemAlias());
var deletedRows = db.deletes(deleteStatements)
DuplicateScannerUtils.DeleteCachedDuplicate(pSourceContactId);
return (affectedRowsCurrentAlias > 0 && deletedRows >= 2);
}
DuplicateScannerUtils.CreateMergeSuccessActivity = function(pSourceContactId, pTargetContactId, pCurrentContactId, pContext)
{
var activityDataForInsert = {
subject: translate.withArguments("A %0 record has been merged", [pContext]),
content: translate.withArguments("%0 with ID \"%1\" has been integrated into the %0 with the ID \"%2\"", [pContext, pSourceContactId, pTargetContactId]),
//categoryKeywordId: $KeywordRegistry.ac
directionKeywordId: $KeywordRegistry.activityDirection$internal(),
responsibleContactId: pCurrentContactId
};
var activityLinks = [[pContext, pTargetContactId]];
return ActivityUtils.insertNewActivity(activityDataForInsert, activityLinks, null, db.getCurrentAlias());
}
DuplicateScannerUtils.MergeOrganisation = function(pSourceContactId, pTargetContactId)
{
let updateStatementsCurrentAlias = [];
let updateStatementsSystemAlias = [];
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));
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)
DuplicateScannerUtils.DeleteCachedDuplicate(pSourceContactId);
return (affectedRowsCurrentAlias > 0 && deletedRows >= 2);
}
/*
* Creates an array of arrays containing the entity field config paired with it's value.
*
* @param {[]} pDuplicateFieldsConfig An Array with the configured fields in the form of [ENTITY_FIELD, IS_ID, USE_FOR_SEARCH]. @see LoadDuplicateIndexFieldsConfiguration()
* @param {{"key", "value"}} pTargetRecordData One record containing the values for the configured fields. It's in the format of {"key(=EntityFieldName", "Value"}
* @return {[[]]} An array of arrays containing the entity field config an its value. ["{entityfield: FIRSTNAME}", "PETER"]]
* @example
* pDuplicateFieldsConfig
* ["CONTACTID", true, false]
* ["FIRSTNAME", false, true]
*
* pTargetRecordData
* ["d786045c-8b21-4f22-b6d9-72be9f61c04d", "PETER"]
*
* => ["{entityfield: FIRSTNAME}", "PETER"]]
*/
DuplicateScannerUtils.BuildEntityFieldConfigValueRays = function(pDuplicateFieldsConfig, pTargetRecordData)
{
let INDEX_CONFIG_ENTITY_FIELD = 0;
let entityFieldConfigValuesRay = [];
/*
* Based on the parameterized filter field names and the values loaded via the query,
* an array which contains records in the style of ["FilterFieldName", "FilterFieldValueFromQuery"] gets created.
* This is mandatory to run the scan for this record.
*/
for (a = 0; a < pDuplicateFieldsConfig.length; a++)
{
let fieldConfig = JSON.parse("{" + pDuplicateFieldsConfig[a] + "}");
let entityField = fieldConfig.entityfield;
let entityFieldValue = pTargetRecordData[entityField];
if(entityFieldValue == null)
entityFieldValue = "";
else
entityFieldValue = entityFieldValue.toLowerCase().replace(/[():\.\/!]/gi, "");
let exclude = fieldConfig.exclude;
if ( exclude )
{
for(let i = 0; i < exclude.length; i++)
{
entityFieldValue = " " + entityFieldValue + " ";
entityFieldValue = entityFieldValue.replace(new RegExp( " " + exclude[i] + " ", "gi"), "");
}
}
let valuelength = fieldConfig.length;
if ( valuelength )
entityFieldValue = entityFieldValue.substr(0, parseInt(valuelength)) + "*";
let emptyall = fieldConfig.emptyall;
if ( fieldConfig.emptyall && entityFieldValue == "")
entityFieldValue = "*";
entityFieldValue = entityFieldValue.trim();
entityFieldConfigValuesRay.push([pDuplicateFieldsConfig[a], entityFieldValue]);
}
return entityFieldConfigValuesRay.length > 0 ? entityFieldConfigValuesRay : [["", ""]];
}
DuplicateScannerUtils.GetBlockSize = function()
{
return project.getPreferenceValue("custom.duplicates.dataBlockSize", "5000");
}
DuplicateScannerUtils.GetEntityFieldsFromConfig = function(pFilterName, pTargetEntity)
{
let indexPattern = _DuplicateScannerUtils._loadIndexPattern(pFilterName, pTargetEntity);
if(indexPattern == null || indexPattern == "")
return [];
let fieldConfigs = _DuplicateScannerUtils._loadEntityFieldConfigsFromPattern(indexPattern);
if(fieldConfigs == null || fieldConfigs.length < 1)
return [];
let entityFields = _DuplicateScannerUtils._loadEntityFieldsFromFieldConfigs(fieldConfigs);
let entityIdField = _DuplicateScannerUtils._loadEntityIdField(pFilterName, pTargetEntity);
entityFields.push(entityIdField);
return entityFields;
}
DuplicateScannerUtils.GetUnrelatedRelationsForDuplicate = function(pDuplicateId)
{
let unrelatedIds = [];
let duplicateIds = newSelect("SOURCEDUPLICATEID, UNRELATEDDUPLICATEID")
.from("UNRELATEDDUPLICATES")
.where("UNRELATEDDUPLICATES.SOURCEDUPLICATEID", pDuplicateId)
.or("UNRELATEDDUPLICATES.UNRELATEDDUPLICATEID", pDuplicateId)
.table();
let sourceDuplicateId = "";
let unrelatedDuplicateId = "";
for (let i = 0; i < duplicateIds.length; i++)
{
sourceDuplicateId = duplicateIds[i][0];
unrelatedDuplicateId = duplicateIds[i][1];
if(sourceDuplicateId != null && sourceDuplicateId != "" && sourceDuplicateId != pDuplicateId)
{
unrelatedIds.push(sourceDuplicateId);
}
else if(unrelatedDuplicateId != null && unrelatedDuplicateId != "" && unrelatedDuplicateId != pDuplicateId)
{
unrelatedIds.push(unrelatedDuplicateId);
}
}
return unrelatedIds;
}
function _DuplicateScannerUtils() {}
var INDEX_FILTER_CONDITION = 0;
var INDEX_COUNT_CHARS_TO_USE = 1;
var INDEX_MAX_RESULTS_THRESHOLD = 2;
var INDEX_TABLE_NAME = 0;
var INDEX_COLUMN_NAME = 1;
var INDEX_CONDITION = 2;
_DuplicateScannerUtils._loadEntityFieldsFromFieldConfigs = function(pEntityFieldConfigs)
{
let fieldNames = [];
for (i = 0; i < pEntityFieldConfigs.length; i++)
{
let fieldConfig = JSON.parse("{" + pEntityFieldConfigs[i] + "}");
let entityField = fieldConfig.entityfield;
fieldNames.push(entityField);
}
return fieldNames;
}
_DuplicateScannerUtils._buildUpdateResetStandardCommunications = function(pSourceContactId)
{
return [["COMMUNICATION", ["ISSTANDARD"], null, ["0"], newWhere("COMMUNICATION.CONTACT_ID", pSourceContactId).build()]];
}
/*
* Gets the Pattern for the scanner
* A pattern usually contains placeholders in the style of "{entityFieldName]"
*
* @param {String} pScannerName Name of the filter to use
* @param {String} pTargetEntity The target entity which has been assigned to the filters configuration
* @returns {String} Scan pattern as string
*/
_DuplicateScannerUtils._loadIndexPattern = function(pScannerName, pTargetEntity)
{
let scanPattern = newSelect("SCAN_PATTERN")
.from("DUPLICATESCANNER")
.where("DUPLICATESCANNER.FILTER_NAME", pScannerName)
.and("DUPLICATESCANNER.ENTITY_TO_SCAN_NAME", pTargetEntity)
.cell();
scanPattern = scanPattern.trim();
return scanPattern;
}
_DuplicateScannerUtils._loadEntityFieldConfigsFromPattern = function(indexPattern)
{
return indexPattern.match(/[^{}]+(?=\})/g);
}
_DuplicateScannerUtils._replacePlaceholderForValuesInPattern = function(pIndexPattern, pEntityFieldValueRays)
{
let INDEX_ENTITY_FIELD_NAME = 0;
let INDEX_ENTITY_FIELD_VALUE = 1;
let placeholder = "";
let fieldValue = "";
for (let i = 0; i < pEntityFieldValueRays.length; i++)
{
placeholder = "{" + pEntityFieldValueRays[i][INDEX_ENTITY_FIELD_NAME] + "}";
fieldValue = pEntityFieldValueRays[i][INDEX_ENTITY_FIELD_VALUE];
pIndexPattern = pIndexPattern.replace(placeholder, fieldValue);
}
return pIndexPattern;
}
_DuplicateScannerUtils._loadEntityIdField = function(pFilterName, pTargetEntity)
{
return newSelect("ID_FIELD_NAME")
.from("DUPLICATESCANNER")
.where("DUPLICATESCANNER.FILTER_NAME", pFilterName)
.and("DUPLICATESCANNER.ENTITY_TO_SCAN_NAME", pTargetEntity)
.cell();
}
/*
* @see DuplicateScannerUtils.ScanForDuplicates for the documentation
*/
_DuplicateScannerUtils._scanForDuplicates = function(pTargetEntity, pEntityFieldConfigValuesRay,
pResultFields, pRecordIdValueToIgnore, pFormatValuesConsumeWebserviceCallback, pUseExternalWebservice, pIndexPattern)
{
//No filterfields/indexpattern => No indexsearch
if(pEntityFieldConfigValuesRay.length < 1 || pIndexPattern == null || pIndexPattern == "")
return null;
let possibleDuplicates = [];
let ignoreSourceRecordPattern = _DuplicateScannerUtils._getIgnoreSourceRecordPattern(pRecordIdValueToIgnore);
let indexPatternWithValues = _DuplicateScannerUtils._replacePlaceholderForValuesInPattern(pIndexPattern, pEntityFieldConfigValuesRay);
indexPatternWithValues = ignoreSourceRecordPattern + indexPatternWithValues + ")";
possibleDuplicates = _DuplicateScannerUtils._callIndexSearch(pTargetEntity, indexPatternWithValues, pResultFields, 100);
if(possibleDuplicates == null)
return null;
possibleDuplicates = possibleDuplicates[indexsearch.HITS];
if(pUseExternalWebservice && pFormatValuesConsumeWebserviceCallback != null)
possibleDuplicates = pFormatValuesConsumeWebserviceCallback.apply(this, [possibleDuplicates]);
return possibleDuplicates;
}
/*
* Returns a bool which say wether or not an external service should be used
*
* @param {String} pFilterName Name of the filter
* @param {String} pTargetEntity Entity which has been configured
* @returns {Bool} True = use, False = no use
*/
_DuplicateScannerUtils._isUseExternalWebservice = function(pFilterName, pTargetEntity)
{
let isUseWebservice = newSelect("EXTERNAL_SERVICE_USAGE_ALLOWED")
.from("DUPLICATESCANNER")
.where("DUPLICATESCANNER.FILTER_NAME", pFilterName)
.and("DUPLICATESCANNER.ENTITY_TO_SCAN_NAME", pTargetEntity)
.cell();
return (isUseWebservice == 0) ? false : true;
}
/*
* Runs the indexsearch based on the given parameters.
* If the "pEntityFieldValueRays" is empty, no search will be executed.
*
* @param {String} pTargetEntity Entity which has been configured
* @param {String} pIndexPatternWithValues The pattern used to search. Has to contain the values already.
* @param {String} pResultFields The result field config. Use "DuplicateScannerUtils.LoadResultFields"
* @param {String} pResultSetRows todo
* @returns {[["key", "value"]] || null} Array of Key-Value-Pairs based on the configured pResultFields, if no pattern exists null
*/
_DuplicateScannerUtils._callIndexSearch = function(pTargetEntity, pIndexPatternWithValues, pResultFields, pResultSetRows)
{
//The indexPattern can't be null because it is required to run the search.
if(pIndexPatternWithValues == null || pIndexPatternWithValues == "")
return null;
let indexQuery = indexsearch.createIndexQuery()
.setPattern(pIndexPatternWithValues)
.setEntities([pTargetEntity])
// .setDefaultOperator(indexsearch.OPERATOR_AND)
//.addSearchFields("Person_entity.FIRSTNAME", "Person_entity.LASTNAME", "Person_entity.CONTACTID")
//.setRows(pResultSetRows);
indexQuery = _DuplicateScannerUtils._setResultFields(indexQuery, pResultFields);
return indexsearch.searchIndex(indexQuery);
}
/*
* Sets each field of the given array as resultfield on the given query.
* Supports indexsearch internal fields aswell
* (indexsearch.FIELD_ID, indexsearch.FIELD_TITLE, indexsearch.FIELD_TYPE, indexsearch.FIELD_DESCRIPTION)
*
* @param {IndexQuery} pIndexQuery An indexquery created with "indexsearch.createIndexQuery()"
* @param {String[]} pResultFields The result field config. Use "DuplicateScannerUtils.LoadResultFields"
* @returns {IndexQuery} IndexQuery with the resultfields set
*/
_DuplicateScannerUtils._setResultFields = function(pIndexQuery, pResultFields)
{
let resultIndexFields = [];
let resultFields = [];
for (let i = 0; i < pResultFields.length; i++)
{
if(pResultFields[i] == indexsearch.FIELD_ID
|| pResultFields[i] == indexsearch.FIELD_TITLE
|| pResultFields[i] == indexsearch.FIELD_TYPE
|| pResultFields[i] == indexsearch.FIELD_DESCRIPTION)
{
resultIndexFields.push(pResultFields[i]);
}
else
resultFields.push(pResultFields[i]);
}
if(resultIndexFields.length > 0)
pIndexQuery = pIndexQuery.addResultIndexFields(resultIndexFields);
if(resultFields.length > 0)
{
pIndexQuery = pIndexQuery.addResultFields(resultFields);
}
return pIndexQuery;
}
/*
* Creates the queries to insert new duplicates into a new cluster based on the pDuplicatesRay
*
* @param {String[]} pDuplicatesRay Array of duplicate ids
* @param {String} pTargetEntity Entity which has been configured
* @returns {String[]} Array of insert queries
*/
_DuplicateScannerUtils._createInsertDuplicatesClusterQuery = function (pDuplicatesRay, pTargetEntity)
{
return _DuplicateScannerUtils._createInsertDuplicatesClusterQuery(pDuplicatesRay, pTargetEntity, util.getNewUUID());
}
/*
* Creates the queries to insert new duplicates into a new cluster based on the pDuplicatesRay
*
* @param {String[]} pDuplicatesRay Array of duplicate ids
* @param {String} pTargetEntity Entity which has been configured
* @param {String} pClusterId Clusters id to which the duplicates are in relation
* @returns {String[]} Array of insert queries
*/
_DuplicateScannerUtils._createInsertDuplicatesClusterQuery = function (pDuplicatesRay, pTargetEntity, pClusterId)
{
let duplicatesToInsertQueries = [];
let cols = ["ID", "CLUSTERID", "DUPLICATEID", "TARGET_ENTITY"];
if(pClusterId == undefined || pClusterId == null || pClusterId == "")
pClusterId = util.getNewUUID();
for (i = 0; i < pDuplicatesRay.length; i++)
{
let newId = util.getNewUUID();
let valuesToInsert = [newId, pClusterId, pDuplicatesRay[i], pTargetEntity];
duplicatesToInsertQueries.push(["DUPLICATECLUSTERS", cols, null, valuesToInsert]);
}
return duplicatesToInsertQueries;
}
/*
* Deletes all clusters
*
* @returns {String} Count of records deleted
*/
_DuplicateScannerUtils._deleteDuplicateClusters = function ()
{
return db.deleteData("DUPLICATECLUSTERS");
}
/*
* All records with contactId = sourceContactId get updated, which are not assigned to the same "group" as the targetContactId.
* 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
*/
_DuplicateScannerUtils._buildUpdateAttachParticipantsToNewContactQuery = function (pTableName, pContactIdColumn, pAssignableIdColumn, pSourceContactId, pTargetContactId, updateStatements)
{
var selectAssignableIdsOfTargetContactQuery = newSelect(pAssignableIdColumn)
.from(pTableName)
.where([pTableName, pContactIdColumn], pTargetContactId);
let updateCondition = newWhere([pTableName, pAssignableIdColumn], selectAssignableIdsOfTargetContactQuery, SqlBuilder.NOT_IN())
.and([pTableName, pContactIdColumn], pSourceContactId)
return [[pTableName, [pContactIdColumn], null, [pTargetContactId], updateCondition.build()]];
}
_DuplicateScannerUtils._buildDeleteRemoveObsoleteParticipantsRecordsQuery = function (pTableName, pContactIdColumn, pAssignableIdColumn, pSourceContactId, pTargetContactId, updateStatements)
{
var selectAssignableIdsOfTargetContactQuery = newSelect(pAssignableIdColumn)
.from(pTableName)
.where([pTableName, pContactIdColumn], pTargetContactId);
let deleteCondition = newWhere([pTableName, pAssignableIdColumn], selectAssignableIdsOfTargetContactQuery, SqlBuilder.IN())
.and([pTableName, pAssignableIdColumn], pSourceContactId)
let recordsToDelete = [];
recordsToDelete.push([pTableName, deleteCondition.build()]);
return recordsToDelete;
}
/*
* Creates Queries to delete from the PERSON and CONTACT table.
*
* @returns {String[]} Queries to delete
*/
_DuplicateScannerUtils._buildDeletePersonAndContactQuery = function(pSourcePersonId, pSourceContactId)
{
let recordsToDelete = []
recordsToDelete.push(["PERSON", newWhere("PERSON.PERSONID", pSourcePersonId).build()]);
recordsToDelete.push(["CONTACT", newWhere("CONTACT.CONTACTID", pSourceContactId).build()]);
return recordsToDelete;
}
_DuplicateScannerUtils._buildDeleteOrganisationAndContactQuery = function(pSourceOrganisationId, pSourceContactId)
{
let recordsToDelete = []
recordsToDelete.push(["ORGANISATION", newWhere("ORGANISATION.ORGANISATIONID", pSourceOrganisationId).build()]);
recordsToDelete.push(["CONTACT", newWhere("CONTACT.CONTACTID", pSourceContactId).build()]);
return recordsToDelete;
}
_DuplicateScannerUtils._buildDeleteCachedUnrelatedDuplicateQuery = function(pSourceContactId)
{
let recordsToDelete = []
recordsToDelete.push(["UNRELATEDDUPLICATES", newWhere("UNRELATEDDUPLICATES.SOURCEDUPLICATEID", pSourceContactId).build()]);
recordsToDelete.push(["UNRELATEDDUPLICATES", newWhere("UNRELATEDDUPLICATES.UNRELATEDDUPLICATEID", pSourceContactId).build()]);
return recordsToDelete;
}
/*
* Creates a pattern which excludes the field and it's value
*
* @param {String} pRecordIdValueToIgnore The fields value
* @returns {String} Pattern which excludes the gived field in combination with the value
*/
_DuplicateScannerUtils._getIgnoreSourceRecordPattern = function(pRecordIdValueToIgnore)
{
return "( +( -" + indexsearch.FIELD_ID + ":(" + pRecordIdValueToIgnore + ") ) ";
}
_DuplicateScannerUtils._buildUpdateContactIdStatements = function(pTableInfos, pSourceContactId, pTargetContactId, pAlias)
{
let statements = [];
for (let i = 0; i < pTableInfos.length; i++)
{
let tableInfo = pTableInfos[i];
let updateStatement = _DuplicateScannerUtils._buildStatement(tableInfo, pSourceContactId, pTargetContactId, pAlias);
statements.push(updateStatement);
}
return statements;
}
_DuplicateScannerUtils._buildStatement = function(pTableinfos, pSourceContactId, pTargetContactId, pAlias)
{
let tableName = pTableinfos[INDEX_TABLE_NAME];
let columnName = pTableinfos[INDEX_COLUMN_NAME];
let additionalCondition = pTableinfos[INDEX_CONDITION];
let condition = newWhere([tableName, columnName], pSourceContactId, undefined, undefined, pAlias);
if(additionalCondition != "")
condition.and(additionalCondition);
return [tableName, [columnName], null, [pTargetContactId], condition.build()];
}
/*
* 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]
*/
_DuplicateScannerUtils._getMergeUpdateTableInfosCurrentAlias = function()
{
var tableInfos = new Array();
tableInfos.push(["AB_APPOINTMENTLINK", "OBJECT_ROWID", ""]);
tableInfos.push(["AB_CTILOG", "CONTACT_ID", ""]);
tableInfos.push(["AB_OBJECTRELATION", "AB_OBJECTRELATIONID", ""]);
tableInfos.push(["AB_OBJECTRELATION", "OBJECT1_ROWID", ""]);
tableInfos.push(["AB_OBJECTRELATION", "OBJECT2_ROWID", ""]);
tableInfos.push(["AB_LOGHISTORY", "TABLENAMEID", ""]);
tableInfos.push(["ADDRESS", "CONTACT_ID", ""]);
tableInfos.push(["BULKMAILRECIPIENT", "CONTACT_ID", ""]);
tableInfos.push(["BULKMAIL", "TESTING_CONTACT_ID", ""]);
tableInfos.push(["CAMPAIGN", "EMPLOYEE_CONTACT_ID", ""]);
tableInfos.push(["CAMPAIGNSTEP", "EMPLOYEE_CONTACT_ID", ""]);
tableInfos.push(["COMMRESTRICTION", "CONTACT_ID", ""]);
tableInfos.push(["COMMRESTRICTION", "EMPLOYEE_INVOLVED", ""]);
tableInfos.push(["COMMUNICATION", "CONTACT_ID", ""]);
tableInfos.push(["COMPETITION", "CONTACT_ID", ""]);
tableInfos.push(["CONTRACT", "CONTACT_ID", ""]);
tableInfos.push(["LETTERRECIPIENT", "CONTACT_ID", ""]);
tableInfos.push(["OBJECTMEMBER", "CONTACT_ID", ""]);
tableInfos.push(["OFFER", "CONTACT_ID", ""]);
tableInfos.push(["PRODUCT", "CONTACT_ID", ""]);
tableInfos.push(["PRODUCTPRICE", "CONTACT_ID", ""]);
tableInfos.push(["SALESORDER", "CONTACT_ID", ""]);
tableInfos.push(["SALESPROJECT", "CONTACT_ID", ""]);
tableInfos.push(["TASK", "REQUESTOR_CONTACT_ID", ""]);
tableInfos.push(["TASK", "EDITOR_CONTACT_ID", ""]);
tableInfos.push(["TASKLINK", "OBJECT_ROWID", ""]);
tableInfos.push(["ACTIVITY", "RESPONSIBLE", ""]);
tableInfos.push(["DSGVO", "CONTACT_ID", ""]);
tableInfos.push(["DSGVOINFO", "CONTACT_ID", ""]);
tableInfos.push(["TIMETRACKING", "CONTACT_ID", ""]);
tableInfos.push(["ACTIVITYLINK", "OBJECT_ROWID", ""]);
tableInfos.push(["AB_ATTRIBUTERELATION", "OBJECT_ROWID", ""]);
return tableInfos;
}
/*
* Contains all Tables and their fields which may contain the contact id to be replaced for the system alias
*
* @returns {String[[]]} Array in the format [TableName, ContactIdColumnName, AdditionalCondition]
*/
_DuplicateScannerUtils._getMergeUpdateTableInfosSystemAlias = function()
{
var tableInfos = new Array();
tableInfos.push(["ASYS_CALENDARLINK", "DBID", ""]);
tableInfos.push(["ASYS_BINARIES", "ROW_ID", "TABLENAME = 'CONTACT'"]);
return tableInfos;
}
/*
* Returns wether or not a value should be substring'd
*
* @return true if pCountCharsOfValueToUse is a number, greater than 0 and smaller than the values length
*/
_DuplicateScannerUtils._isValueLongerThanCharsToUse = function(pValueLength, pCountCharsOfValueToUse)
{
return !isNaN(pCountCharsOfValueToUse)
&& pCountCharsOfValueToUse > 0
&& pValueLength > pCountCharsOfValueToUse;
}
/*
* Returns wether or not the parameter isnt null and a number or not
*
* @param {String} pCountCharsOfValueToUse Hopefully a number
* @returns {String} True if parameter isnt null and a number, False if it's null or no number
*/
_DuplicateScannerUtils._isNotNullAndANumber = function(pCountCharsOfValueToUse)
{
return pCountCharsOfValueToUse != null && !isNaN(pCountCharsOfValueToUse);
}