From 3dcfa6506348e9612055c2f487f5a29eeab0a376 Mon Sep 17 00:00:00 2001
From: "S.Listl" <S.Listl@SLISTL.aditosoftware.local>
Date: Thu, 25 Jun 2020 10:17:35 +0200
Subject: [PATCH] 1058079 Show duplicates in QuickEntry

---
 .../AnyContact_entity/AnyContact_entity.aod   | 22 +++++
 .../contactsbyids/documentation.adoc          |  3 +
 .../recordcontainers/db/conditionProcess.js   |  5 +-
 .../onlyshowcontactids_param/valueProcess.js  | 27 +++---
 .../onlyshowcontactids_param/valueProcess.js  | 24 +++--
 .../QuickEntry_entity/QuickEntry_entity.aod   | 18 ++++
 .../children/contactids_param/valueProcess.js | 87 +++++++++++++++++++
 .../withprivatepersons_param/valueProcess.js  |  3 +
 neonContext/AnyContact/AnyContact.aod         |  4 +
 .../AnyContactDuplicates_view.aod             | 45 ++++++++++
 .../DuplicateScannerFilter_view.aod           |  2 +-
 .../QuickEntryEdit_view.aod                   |  5 ++
 process/DuplicateScanner_lib/process.js       | 26 ++++++
 13 files changed, 249 insertions(+), 22 deletions(-)
 create mode 100644 entity/AnyContact_entity/entityfields/contactsbyids/documentation.adoc
 create mode 100644 entity/QuickEntry_entity/entityfields/organdpersduplicates/children/contactids_param/valueProcess.js
 create mode 100644 entity/QuickEntry_entity/entityfields/organdpersduplicates/children/withprivatepersons_param/valueProcess.js
 create mode 100644 neonView/AnyContactDuplicates_view/AnyContactDuplicates_view.aod

diff --git a/entity/AnyContact_entity/AnyContact_entity.aod b/entity/AnyContact_entity/AnyContact_entity.aod
index 4b02be4aeaa..174d18bdf2e 100644
--- a/entity/AnyContact_entity/AnyContact_entity.aod
+++ b/entity/AnyContact_entity/AnyContact_entity.aod
@@ -180,6 +180,28 @@ See ContactUtils.getRelationTypeByPersOrg for possible values</description>
       <name>AvatarText_param</name>
       <valueProcess>%aditoprj%/entity/AnyContact_entity/entityfields/avatartext_param/valueProcess.js</valueProcess>
     </entityParameter>
+    <entityProvider>
+      <name>ContactsByIds</name>
+      <documentation>%aditoprj%/entity/AnyContact_entity/entityfields/contactsbyids/documentation.adoc</documentation>
+      <dependencies>
+        <entityDependency>
+          <name>0206f7a8-fd58-47e8-8b7a-5ff4531e56fb</name>
+          <entityName>QuickEntry_entity</entityName>
+          <fieldName>OrgAndPersDuplicates</fieldName>
+          <isConsumer v="false" />
+        </entityDependency>
+      </dependencies>
+      <children>
+        <entityParameter>
+          <name>ContactId_param</name>
+          <expose v="false" />
+        </entityParameter>
+      </children>
+    </entityProvider>
+    <entityParameter>
+      <name>ContactIds_param</name>
+      <expose v="true" />
+    </entityParameter>
   </entityFields>
   <recordContainers>
     <dbRecordContainer>
diff --git a/entity/AnyContact_entity/entityfields/contactsbyids/documentation.adoc b/entity/AnyContact_entity/entityfields/contactsbyids/documentation.adoc
new file mode 100644
index 00000000000..bcf8477de13
--- /dev/null
+++ b/entity/AnyContact_entity/entityfields/contactsbyids/documentation.adoc
@@ -0,0 +1,3 @@
+= AnyContact_entity - ContactsByIds
+
+The list of contacts can be filtered by providing the contact ids in the parameter "ContactIds_param"
\ No newline at end of file
diff --git a/entity/AnyContact_entity/recordcontainers/db/conditionProcess.js b/entity/AnyContact_entity/recordcontainers/db/conditionProcess.js
index b6d99a470cf..a793d04a1b1 100644
--- a/entity/AnyContact_entity/recordcontainers/db/conditionProcess.js
+++ b/entity/AnyContact_entity/recordcontainers/db/conditionProcess.js
@@ -24,10 +24,13 @@ if (vars.getString("$param.WithPrivatePersons_param") == "true")
                                         .and("CONTACT.PERSON_ID is not null"));
 }
     
-
 //exclude private organisation
 var cond = newWhereIfSet(conditionPrivateOrganisation)
                .andIfSet("CONTACT.ORGANISATION_ID", orgContactId);
 
+var contactIds = vars.exists("$param.ContactIds_param") && vars.get("$param.ContactIds_param");
+if (contactIds) 
+    cond.andIfSet("CONTACT.CONTACTID", JSON.parse(contactIds), SqlBuilder.IN());
+
 //TODO: use a preparedCondition (.build instead of .toString) when available #1030812 #1034026
 result.string(cond.toString());
\ No newline at end of file
diff --git a/entity/Organisation_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js b/entity/Organisation_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
index dc76aa3c55e..27d043515ab 100644
--- a/entity/Organisation_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
+++ b/entity/Organisation_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
@@ -7,11 +7,11 @@ import("system.result");
 var scannerName = "OrganisationDuplicates";
 var targetEntity = "Organisation_entity";
 var valuesToCheck = {};
-var entityFieldsToLoad = DuplicateScannerUtils.GetEntityFieldsFromConfig(scannerName, targetEntity);
+var entityFieldsToLoad = DuplicateScannerUtils.getEntityFieldObjectFromConfig(scannerName, targetEntity);
 
 var idsForEmptyResult = JSON.stringify(["nodata"]);
 
-if (entityFieldsToLoad == null || entityFieldsToLoad.length == 0)
+if (entityFieldsToLoad == null)
     result.string(idsForEmptyResult);
 else
 {
@@ -22,17 +22,22 @@ else
     vars.get("$field.STANDARD_CITY");
     vars.get("$field.STANDARD_ZIP");
     vars.get("$field.STANDARD_ADDRESS");
-
-    for (let fieldname in entityFieldsToLoad) 
-    { 
-        var field = entityFieldsToLoad[fieldname];
+    
+    var allFieldsToLoad = entityFieldsToLoad.entityFields.concat(entityFieldsToLoad.entityIdField);
+    allFieldsToLoad.forEach(function (field)
+    {
         var fieldValue = vars.get("$field." + field);
-
         if (fieldValue)
-           valuesToCheck[field] = fieldValue;
-    }
-
-    var scanResults = DuplicateScannerUtils.ScanForDuplicates(scannerName, targetEntity, valuesToCheck, null) || [];
+            valuesToCheck[field] = fieldValue;
+    });
+    
+    var scanResults = [];
+    
+    //don't search if only the id field has a value
+    var fieldsToCheck = Object.keys(valuesToCheck);
+    if (!(fieldsToCheck.length === 0 || (fieldsToCheck.length === 1 && entityFieldsToLoad.entityIdField in valuesToCheck)))
+        scanResults = DuplicateScannerUtils.ScanForDuplicates(scannerName, targetEntity, valuesToCheck, null) || [];
+    
     var duplicateIds = scanResults.map(function (scanResult)
     {
         return scanResult[indexsearch.FIELD_ID];
diff --git a/entity/Person_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js b/entity/Person_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
index 298a3115245..ce8253a306e 100644
--- a/entity/Person_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
+++ b/entity/Person_entity/entityfields/selfduplicatesuncached/children/onlyshowcontactids_param/valueProcess.js
@@ -8,11 +8,11 @@ import("system.result");
 var scannerName = "PersonDuplicates";
 var targetEntity = "Person_entity";
 var valuesToCheck = {};
-var entityFieldsToLoad = DuplicateScannerUtils.GetEntityFieldsFromConfig(scannerName, targetEntity);
+var entityFieldsToLoad = DuplicateScannerUtils.getEntityFieldObjectFromConfig(scannerName, targetEntity);
 
 var idsForEmptyResult = JSON.stringify(["nodata"]);
 
-if (entityFieldsToLoad == null || entityFieldsToLoad.length == 0)
+if (entityFieldsToLoad == null)
     result.string(idsForEmptyResult);
 else
 {
@@ -27,16 +27,22 @@ else
     vars.get("$field.PersAddresses.insertedRows")
     vars.get("$field.PersAddresses.changedRows")
     vars.get("$field.PersAddresses.deletedRows")
-
-    for (let fieldname in entityFieldsToLoad)
-    { 
-        var field = entityFieldsToLoad[fieldname];
+    
+    var allFieldsToLoad = entityFieldsToLoad.entityFields.concat(entityFieldsToLoad.entityIdField);
+    allFieldsToLoad.forEach(function (field)
+    {
         var fieldValue = vars.get("$field." + field);
         if (fieldValue)
             valuesToCheck[field] = fieldValue;
-    }
-
-    var scanResults = DuplicateScannerUtils.ScanForDuplicates(scannerName, targetEntity, valuesToCheck, null) || [];
+    });
+    
+    var scanResults = [];
+    
+    //don't search if only the id field has a value
+    var fieldsToCheck = Object.keys(valuesToCheck);
+    if (!(fieldsToCheck.length === 0 || (fieldsToCheck.length === 1 && entityFieldsToLoad.entityIdField in valuesToCheck)))
+        scanResults = DuplicateScannerUtils.ScanForDuplicates(scannerName, targetEntity, valuesToCheck, null) || [];
+    
     var duplicateIds = scanResults.map(function (duplicate)
     {
         return duplicate[indexsearch.FIELD_ID];
diff --git a/entity/QuickEntry_entity/QuickEntry_entity.aod b/entity/QuickEntry_entity/QuickEntry_entity.aod
index 953caf69508..ac7e1e7e1bc 100644
--- a/entity/QuickEntry_entity/QuickEntry_entity.aod
+++ b/entity/QuickEntry_entity/QuickEntry_entity.aod
@@ -228,6 +228,24 @@
       <stateProcess>%aditoprj%/entity/QuickEntry_entity/entityfields/leadquickacquisition/stateProcess.js</stateProcess>
       <onValueChange>%aditoprj%/entity/QuickEntry_entity/entityfields/leadquickacquisition/onValueChange.js</onValueChange>
     </entityField>
+    <entityConsumer>
+      <name>OrgAndPersDuplicates</name>
+      <dependency>
+        <name>dependency</name>
+        <entityName>AnyContact_entity</entityName>
+        <fieldName>ContactsByIds</fieldName>
+      </dependency>
+      <children>
+        <entityParameter>
+          <name>ContactIds_param</name>
+          <valueProcess>%aditoprj%/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/contactids_param/valueProcess.js</valueProcess>
+        </entityParameter>
+        <entityParameter>
+          <name>WithPrivatePersons_param</name>
+          <valueProcess>%aditoprj%/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/withprivatepersons_param/valueProcess.js</valueProcess>
+        </entityParameter>
+      </children>
+    </entityConsumer>
   </entityFields>
   <recordContainers>
     <jDitoRecordContainer>
diff --git a/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/contactids_param/valueProcess.js b/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/contactids_param/valueProcess.js
new file mode 100644
index 00000000000..206a4b979fd
--- /dev/null
+++ b/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/contactids_param/valueProcess.js
@@ -0,0 +1,87 @@
+import("system.indexsearch");
+import("system.vars");
+import("DuplicateScanner_lib");
+import("system.result");
+
+//trigger refresh
+vars.get("$field.FIRSTNAME");
+vars.get("$field.LASTNAME");
+
+var uid = vars.get("$field.UID");
+var idsForEmptyResult = JSON.stringify(["nodata"]);
+var duplicateScans = [];
+
+duplicateScans.push(["PersonDuplicates", "Person_entity", {"CONTACTID" : uid}]);
+
+vars.get("$field.Contacts.insertedRows").forEach(function (contact)
+{
+    duplicateScans.push(["PersonDuplicates", "Person_entity", contact]);
+});
+
+var organisationName = vars.get("$field.ORGANISATION_NAME");
+//although the standard address is not set at this point, it can be assumed that it will be the first one
+var firstOrganisationAddress = vars.get("$field.OrgAddresses.insertedRows")[0];
+if (organisationName || firstOrganisationAddress)
+{
+    var city = null;
+    var zipCode = null;
+    var address = null;
+    
+    if (firstOrganisationAddress)
+    {
+        city = firstOrganisationAddress["CITY"];
+        zipCode = firstOrganisationAddress["ZIP"];
+        address = firstOrganisationAddress["ADDRESS"];
+    }
+    
+    duplicateScans.push(["OrganisationDuplicates", "Organisation_entity", {
+        "CONTACTID" : uid,
+        "NAME" : organisationName,
+        "STANDARD_CITY" : city,
+        "STANDARD_ZIP" : zipCode,
+        "STANDARD_ADDRESS" : address
+    }]);
+}
+
+var duplicates = duplicateScans.reduce(function (duplicateArr, [scannerName, entity, fieldValues])
+{
+    return duplicateArr.concat(_getDuplicates(scannerName, entity, fieldValues));
+}, []);
+
+if (duplicates.length === 0)
+    result.string(idsForEmptyResult);
+else
+    result.string(JSON.stringify(duplicates));
+
+
+function _getDuplicates (pScannerName, pEntity, pEntityFieldValues)
+{
+    var fieldsToLoad = DuplicateScannerUtils.getEntityFieldObjectFromConfig(pScannerName, pEntity);
+    if (fieldsToLoad == null)
+        return [];
+    
+    var valuesToCheck = {};
+    
+    var allFieldsToLoad = fieldsToLoad.entityFields.concat(fieldsToLoad.entityIdField);
+    allFieldsToLoad.forEach(function (field)
+    {
+        var fieldValue = field in pEntityFieldValues
+            ? pEntityFieldValues[field]
+            : vars.get("$field." + field);
+        if (fieldValue)
+            valuesToCheck[field] = fieldValue;
+    });
+    
+    //don't search if only the id field has a value
+    var fieldsToCheck = Object.keys(valuesToCheck);
+    if (fieldsToCheck.length === 0 || (fieldsToCheck.length === 1 && fieldsToLoad.entityIdField in valuesToCheck))
+        return [];
+    
+    var scanResults = DuplicateScannerUtils.ScanForDuplicates(pScannerName, pEntity, valuesToCheck, null) || [];
+    var duplicateIds = scanResults.map(function (duplicate)
+    {
+        return duplicate[indexsearch.FIELD_ID];
+    });
+
+    return duplicateIds;
+}
diff --git a/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/withprivatepersons_param/valueProcess.js b/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/withprivatepersons_param/valueProcess.js
new file mode 100644
index 00000000000..40effa01784
--- /dev/null
+++ b/entity/QuickEntry_entity/entityfields/organdpersduplicates/children/withprivatepersons_param/valueProcess.js
@@ -0,0 +1,3 @@
+import("system.result");
+
+result.string(true);
\ No newline at end of file
diff --git a/neonContext/AnyContact/AnyContact.aod b/neonContext/AnyContact/AnyContact.aod
index b3ce579fd4d..60b312b5724 100644
--- a/neonContext/AnyContact/AnyContact.aod
+++ b/neonContext/AnyContact/AnyContact.aod
@@ -9,5 +9,9 @@
       <name>1ea0b1ed-c2b5-4b8c-b359-27ffdef6e5ea</name>
       <view>AnyContactLookup_view</view>
     </neonViewReference>
+    <neonViewReference>
+      <name>45efa66c-b525-447c-8e16-014942843299</name>
+      <view>AnyContactDuplicates_view</view>
+    </neonViewReference>
   </references>
 </neonContext>
diff --git a/neonView/AnyContactDuplicates_view/AnyContactDuplicates_view.aod b/neonView/AnyContactDuplicates_view/AnyContactDuplicates_view.aod
new file mode 100644
index 00000000000..ff719ed3711
--- /dev/null
+++ b/neonView/AnyContactDuplicates_view/AnyContactDuplicates_view.aod
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<neonView xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.1.6" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/neonView/1.1.6">
+  <name>AnyContactDuplicates_view</name>
+  <majorModelMode>DISTRIBUTED</majorModelMode>
+  <layout>
+    <noneLayout>
+      <name>layout</name>
+    </noneLayout>
+  </layout>
+  <children>
+    <tableViewTemplate>
+      <name>Table</name>
+      <hideActions v="true" />
+      <entityField>#ENTITY</entityField>
+      <hideHeader v="true" />
+      <title>Duplicates</title>
+      <columns>
+        <neonTableColumn>
+          <name>c37eef9d-3e62-4353-8499-fae685376761</name>
+          <entityField>#IMAGE</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>39645840-a9ca-4d72-a1a1-0355810243de</name>
+          <entityField>ORGANISATION_NAME</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>556ac9f2-c0ce-4401-83d1-f17ead6e14f4</name>
+          <entityField>PERSON_FULL_NAME</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>aa4ce6ed-7f7d-477c-accd-81a2f02a72e6</name>
+          <entityField>STANDARD_PHONE_COMMUNICATION</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>3f5d8420-f3f2-4845-b4a9-14d31905bd9f</name>
+          <entityField>STANDARD_EMAIL_COMMUNICATION</entityField>
+        </neonTableColumn>
+        <neonTableColumn>
+          <name>c90379c3-f74b-4344-bc56-8e7863bb8f65</name>
+          <entityField>ADDRESS_ID</entityField>
+        </neonTableColumn>
+      </columns>
+    </tableViewTemplate>
+  </children>
+</neonView>
diff --git a/neonView/DuplicateScannerFilter_view/DuplicateScannerFilter_view.aod b/neonView/DuplicateScannerFilter_view/DuplicateScannerFilter_view.aod
index aca74dc7364..1ffb18b8f78 100644
--- a/neonView/DuplicateScannerFilter_view/DuplicateScannerFilter_view.aod
+++ b/neonView/DuplicateScannerFilter_view/DuplicateScannerFilter_view.aod
@@ -13,7 +13,7 @@
       <favoriteActionGroup2></favoriteActionGroup2>
       <favoriteActionGroup3>RunActionGroup</favoriteActionGroup3>
       <entityField>#ENTITY</entityField>
-      <isCreatable v="false" />
+      <isCreatable v="true" />
       <isDeletable v="false" />
       <isEditable v="true" />
       <columns>
diff --git a/neonView/QuickEntryEdit_view/QuickEntryEdit_view.aod b/neonView/QuickEntryEdit_view/QuickEntryEdit_view.aod
index 125b8e68227..9598c3420b1 100644
--- a/neonView/QuickEntryEdit_view/QuickEntryEdit_view.aod
+++ b/neonView/QuickEntryEdit_view/QuickEntryEdit_view.aod
@@ -11,6 +11,11 @@
     </boxLayout>
   </layout>
   <children>
+    <neonViewReference>
+      <name>adf71384-2c12-401b-a3aa-89ed23e757c2</name>
+      <entityField>OrgAndPersDuplicates</entityField>
+      <view>AnyContactDuplicates_view</view>
+    </neonViewReference>
     <genericViewTemplate>
       <name>GeneralData</name>
       <editMode v="true" />
diff --git a/process/DuplicateScanner_lib/process.js b/process/DuplicateScanner_lib/process.js
index 9e6e4e7557b..ed3ef2cce6b 100644
--- a/process/DuplicateScanner_lib/process.js
+++ b/process/DuplicateScanner_lib/process.js
@@ -801,6 +801,32 @@ DuplicateScannerUtils.GetEntityFieldsFromConfig = function(pFilterName, pTargetE
     return entityFields;
 }
 
+/**
+ * Loads the configured entity fields required for the given duplicate scanner.
+ * 
+ *  @param {String} pFilterName the name of the scanner
+ *  @param {String} pTargetEntity the target entity
+ *  @return {Object} an object with two properties:
+ *      <ul>
+ *          <li>entityFields: array of entity fields</li>
+ *          <li>entityIdField: the id field name as string</li>
+ *      </ul>
+ */
+DuplicateScannerUtils.getEntityFieldObjectFromConfig = function (pFilterName, pTargetEntity)
+{
+    var indexPattern = _DuplicateScannerUtils._loadIndexPattern(pFilterName, pTargetEntity);
+    if (!indexPattern)
+        return null;
+    var fieldConfigs = _DuplicateScannerUtils._loadEntityFieldConfigsFromPattern(indexPattern);
+    if (fieldConfigs == null || fieldConfigs.length === 0)
+        return null;
+    
+    return {
+        entityFields : _DuplicateScannerUtils._loadEntityFieldsFromFieldConfigs(fieldConfigs),
+        entityIdField : _DuplicateScannerUtils._loadEntityIdField(pFilterName, pTargetEntity)
+    };
+}
+
 DuplicateScannerUtils.GetUnrelatedRelationsForDuplicate = function(pDuplicateId)
 {
     let unrelatedIds = [];
-- 
GitLab