From 62ae27b720f5ebfd63ac611e946b7c6a26834fbf Mon Sep 17 00:00:00 2001 From: "d.buechler" <d.buechler@adito.de> Date: Thu, 10 Oct 2019 14:51:26 +0200 Subject: [PATCH] Several changes to the ScanForDuplicates algorithm have been made. It's now possible to have a scanner configuration without any prefilters. No threshold logic is applied, only the configured fields are used to search. On the other hand is it now possible to have a scanner with only a prefilter. --- .../recordcontainer/contentProcess.js | 2 +- .../PersonDuplicatesFilter_view.aod | 1 - process/DuplicateScanner_lib/process.js | 121 ++++++++++++------ 3 files changed, 80 insertions(+), 44 deletions(-) diff --git a/entity/Duplicates_entity/recordcontainers/recordcontainer/contentProcess.js b/entity/Duplicates_entity/recordcontainers/recordcontainer/contentProcess.js index f97c59fc29..2e4953a02c 100644 --- a/entity/Duplicates_entity/recordcontainers/recordcontainer/contentProcess.js +++ b/entity/Duplicates_entity/recordcontainers/recordcontainer/contentProcess.js @@ -17,7 +17,7 @@ let selectedClusterId = vars.get("$param.ClusterId_param"); let duplicateInfosQuery = ""; let selectedId = vars.get("$local.idvalues"); -logging.log("selectedId -> " + selectedId); + if(selectedId) { /* diff --git a/neonView/PersonDuplicatesFilter_view/PersonDuplicatesFilter_view.aod b/neonView/PersonDuplicatesFilter_view/PersonDuplicatesFilter_view.aod index 5a4edbce1a..bc5391cf0d 100644 --- a/neonView/PersonDuplicatesFilter_view/PersonDuplicatesFilter_view.aod +++ b/neonView/PersonDuplicatesFilter_view/PersonDuplicatesFilter_view.aod @@ -11,7 +11,6 @@ <tableViewTemplate> <name>PersonDuplicatesTable</name> <favoriteActionGroup1>PersonOpenClusterDetailActionGroup</favoriteActionGroup1> - <favoriteActionGroup2>DuplicateClusterActionGroup</favoriteActionGroup2> <hideContentSearch v="true" /> <entityField>#ENTITY</entityField> <isCreatable v="false" /> diff --git a/process/DuplicateScanner_lib/process.js b/process/DuplicateScanner_lib/process.js index 582f8a7658..5b76a06e6d 100644 --- a/process/DuplicateScanner_lib/process.js +++ b/process/DuplicateScanner_lib/process.js @@ -1,3 +1,5 @@ +import("system.translate"); +import("ActivityTask_lib"); import("Contact_lib"); import("system.datetime"); import("JditoFilter_lib"); @@ -32,7 +34,7 @@ DuplicateScannerUtils.loadFilters = function(pFilterName, pTargetEntity) let query = "select FILTER_CONDITION, COUNT_CHARACTERS_TO_USE, MAX_RESULTS_THRESHOLD from DUPLICATESCANNERPREFILTERCONFIG" + " join DUPLICATESCANNER on DUPLICATESCANNER.ID = DUPLICATESCANNERPREFILTERCONFIG.DUPLICATESCANNER_ID" + " where FILTER_NAME = '" + pFilterName + "'" - + " and ENTITY_TO_SCAN_NAME = '" + pTargetEntity + "'"; + + " and ENTITY_TO_SCAN_NAME = '" + pTargetEntity + "'"; return db.table(query); } @@ -148,7 +150,10 @@ DuplicateScannerUtils.CreateUnrelatedDuplicateRelation = function(pSourceContact let newUid = util.getNewUUID(); let columns = ["ID", "SOURCEDUPLICATEID", "UNRELATEDDUPLICATEID", "CLUSTERID"]; let values = [newUid, pSourceContactId, pUnrelatedContactId, pClusterId]; - + logging.log("in -> CreateUnrelatedDuplicateRelation"); + logging.log("columns -> " + columns); + logging.log("values -> " + values); + return db.insertData("UNRELATEDDUPLICATES", columns, null, values); } @@ -705,10 +710,17 @@ DuplicateScannerUtils.TranslateEntityToIndexFields = function(pEntityName, pEnti } /* + * 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} - * @param {String[]} + * @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) @@ -746,6 +758,20 @@ DuplicateScannerUtils.MergePerson = function(pSourceContactId, pTargetContactId) return (affectedRowsCurrentAlias > 0 && deletedRows >= 2); } +DuplicateScannerUtils.CreateMergeSuccessActivity = function(pSourceContactId, pTargetContactId, pCurrentContactId, pContext) +{ + var activityDataForInsert = { + subject: translate.withArguments("A %0 record has been merged", [pContext]),//"Es wurde ein Personendatensatz in diesen integriert", + content: translate.withArguments("%0 with ID \"%1\" has been integrated into the %0 with the ID \"%2\"", [pContext, pSourceContactId, pTargetContactId]), + //categoryKeywordId: $KeywordRegistry.ac + directionKeywordId: "x", + responsibleContactId: pCurrentContactId + }; + var activityLinks = [[pContext, pTargetContactId]]; + + return ActivityUtils.insertNewActivity(activityDataForInsert, activityLinks, null, db.getCurrentAlias()); +} + DuplicateScannerUtils.MergeOrganisation = function(pSourceContactId, pTargetContactId) { let updateStatements = []; @@ -874,20 +900,37 @@ pResultFields, pRecordIdFieldToIgnore, pRecordIdValueToIgnore, pFormatValuesCons let ignoredRecordFilter = _DuplicateScannerUtils._getIgnoreRecordFilter(pRecordIdFieldToIgnore, pRecordIdValueToIgnore, pTargetEntity); let configuredFilters = _DuplicateScannerUtils._loadFilters(pFilterName, pTargetEntity); - //To ensure the record which the current search is based on isnt found as result, the other configured filters get appended to - //the filter of said records to ignore - configuredFilters = [ignoredRecordFilter].concat(configuredFilters); //logging.log("Found filters -> " + configuredFilters); + let preFilter = null; + + //Only run the prefilter if filters have been configured. If not, run the indexsearch based on the field configuration + if(configuredFilters != null && configuredFilters.length > 0) + { + //To ensure the record which the current search is based on isnt found as result, the other configured filters get appended to + //the filter of said records to ignore + configuredFilters = [ignoredRecordFilter].concat(configuredFilters); - let preFilter = _DuplicateScannerUtils._applyPreFilter(pTargetEntity, configuredFilters, pFilterFieldValueRays); - - //logging.log("preFilter welcher Elemente im erlaubten bereich ausgibt -> " + preFilter); + preFilter =_DuplicateScannerUtils._applyPreFilter(pTargetEntity, configuredFilters, pFilterFieldValueRays); - if(preFilter == null) + //logging.log("preFilter welcher Elemente im erlaubten bereich ausgibt -> " + preFilter); + + //The scan can be executed even without any prefilters. If a prefilter has been configured but doesn't match the + //threshold criteria no search shall be run. + if(preFilter == null) + return null; + } + + //No prefilter and no filterfields => No indexsearch + if(preFilter == null && pFilterFieldValueRays.length < 1) return null; - + + //If at this point the prefilter is null but a search has to be executed, add the ignorefilter manually that the search doesn't find the base record as duplicate to itself. + //This is the case if no prefilter but indexfields are configured. + if(preFilter == null) + preFilter = ignoredRecordFilter; + possibleDuplicates = _DuplicateScannerUtils._callIndexSearch(pTargetEntity, preFilter, pFilterFieldValueRays, pResultFields, 100); //logging.log("possibleDuplicates -> " + JSON.stringify(possibleDuplicates)); @@ -1051,40 +1094,34 @@ _DuplicateScannerUtils._applyPreFilter = function(pTargetEntity, pFilterCountCha */ _DuplicateScannerUtils._callIndexSearch = function(pTargetEntity, pPreFilterJson, pEntityFieldValueRays, pResultFields, pResultSetRows) { - let parsedFilterAsPatternTerm = indexsearch.buildQueryFromSearchCondition(pPreFilterJson); - ////logging.log("pTargetEntity -> " + pTargetEntity); - //logging.log("pResultFields -> " + pResultFields); - ////logging.log("pResultSetRows -> " + pResultSetRows); + let indexPattern = null; + let filterPattern = null; + + //The pPreFilterJson is never null because it always contains at least the default ignore record filter + indexPattern = indexsearch.buildQueryFromSearchCondition(pPreFilterJson); + + let filterPatternConfig = _DuplicateScannerUtils._buildFilterPatternConfig(pEntityFieldValueRays, pTargetEntity); + + if(filterPatternConfig != null) + filterPattern = indexsearch.buildPatternString(filterPatternConfig); + + //The indexPattern can't be null because it is required to run the search. + if(indexPattern == null) + return null; + let indexQuery = indexsearch.createIndexQuery() - .setPattern(parsedFilterAsPatternTerm) + .setPattern(indexPattern) .setEntities([pTargetEntity]) //.addSearchFields("Person_entity.FIRSTNAME", "Person_entity.LASTNAME", "Person_entity.CONTACTID") //.setRows(pResultSetRows); - indexQuery = _DuplicateScannerUtils._setResultFields(indexQuery, pResultFields); - //indexQuery = indexQuery.addResultFields(["Person_entity.FIRSTNAME", "Person_entity.LASTNAME"]); - //indexQuery = indexQuery.addResultFields(["FIRSTNAME", "LASTNAME"]); - - let filterPatternConfig = _DuplicateScannerUtils._buildFilterPatternConfig(pEntityFieldValueRays, pTargetEntity); - if(filterPatternConfig != null) - { - let filterPatternString = indexsearch.buildPatternString(filterPatternConfig); - indexQuery = indexQuery.addFilter(filterPatternString); - //logging.log("real filter PatternString -> " + filterPatternString); - } - //logging.log("parsedFilterAsPatternTerm -> " + parsedFilterAsPatternTerm); - - if(filterPatternConfig == null && pEntityFieldValueRays.length > 0) - { - //logging.log("FilterPattern ist null aber es gibt pEntityFieldValueRays -> Die Felder sollten genutzt werden, beinhalten aber keine Werte"); - return null; - } - else - { - //logging.log("Starte Indexsuche -> "); - return indexsearch.searchIndex(indexQuery); - } + if(filterPattern != null) + indexQuery = indexQuery.addFilter(filterPattern); + logging.log("indexQuery.getPattern -> " + indexQuery.getPattern()); + logging.log("indexQuery.getFilters -> " + indexQuery.getFilters()); + //logging.log("Starte Indexsuche -> "); + return indexsearch.searchIndex(indexQuery); } /* @@ -1136,7 +1173,7 @@ _DuplicateScannerUtils._setResultFields = function(pIndexQuery, pResultFields) * * @param {String[[]]} pEntityFieldValueRays Array of Arrays containing the name of a used field and its value. * @param {String} pTargetEntity Entity which has been configured - * @returns {PatternConfig} PatternConfig created with "indexsearch.createPatternConfig()" + * @returns {PatternConfig} PatternConfig created with "indexsearch.createPatternConfig()", null if the creation wasn't successful */ _DuplicateScannerUtils._buildFilterPatternConfig = function(pEntityFieldValueRays, pTargetEntity) { -- GitLab