From 2da3aebdf11aa8138ffa3e7aed0e8a452c5d6f79 Mon Sep 17 00:00:00 2001
From: "S.Listl" <S.Listl@SLISTL.aditosoftware.local>
Date: Tue, 26 Mar 2019 13:21:03 +0100
Subject: [PATCH] Indexes added, attribute usage inheritance

---
 aliasDefinition/Data_alias/Data_alias.aod     |  72 +++++++++
 .../indexsearchgroups/contract/affectedIds.js |   4 +
 .../indexsearchgroups/offer/affectedIds.js    |   4 +
 .../indexsearchgroups/offer/query.js          |  22 +++
 .../indexsearchgroups/product/affectedIds.js  |   4 +
 .../salesorder/affectedIds.js                 |   4 +
 .../indexsearchgroups/salesorder/query.js     |  22 +++
 .../salesproject/affectedIds.js               |   4 +
 .../AttributeRelation_entity.aod              |   1 +
 .../recordcontainers/db/conditionProcess.js   |   9 +-
 .../AttributeUsage_entity.aod                 |   8 +
 .../ab_attribute_id/valueProcess.js           |   4 +-
 .../entityfields/object_type/valueProcess.js  |   5 +
 .../recordcontainers/db/onDBDelete.js         |   7 +
 .../recordcontainers/db/onDBInsert.js         |   8 +
 .../recordcontainers/db/onDBUpdate.js         |   9 ++
 entity/Attribute_entity/Attribute_entity.aod  |   5 +
 .../attribute_type/stateProcess.js            |   6 +-
 .../attrparentid_param/valueProcess.js        |   5 +-
 .../attrparenttype_param/valueProcess.js      |   8 +-
 .../entityfields/tsest/onActionProcess.js     |   3 +
 .../entityfields/usagelist/valueProcess.js    |  16 +-
 .../entityfields/printoffer/stateProcess.js   |   2 +-
 .../SalesprojectCompetition_entity.aod        |   4 +-
 .../children/objecttype_param/valueProcess.js |   6 +-
 process/Attribute_lib/process.js              | 144 ++++++++++++++++++
 process/Offer_lib/process.js                  |   2 +
 27 files changed, 364 insertions(+), 24 deletions(-)
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/contract/affectedIds.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/offer/affectedIds.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/offer/query.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/product/affectedIds.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/salesorder/affectedIds.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/salesorder/query.js
 create mode 100644 aliasDefinition/Data_alias/indexsearchgroups/salesproject/affectedIds.js
 create mode 100644 entity/AttributeUsage_entity/entityfields/object_type/valueProcess.js
 create mode 100644 entity/AttributeUsage_entity/recordcontainers/db/onDBDelete.js
 create mode 100644 entity/AttributeUsage_entity/recordcontainers/db/onDBInsert.js
 create mode 100644 entity/AttributeUsage_entity/recordcontainers/db/onDBUpdate.js
 create mode 100644 entity/Attribute_entity/entityfields/tsest/onActionProcess.js

diff --git a/aliasDefinition/Data_alias/Data_alias.aod b/aliasDefinition/Data_alias/Data_alias.aod
index 6bc32d7d9da..7e09926aa31 100644
--- a/aliasDefinition/Data_alias/Data_alias.aod
+++ b/aliasDefinition/Data_alias/Data_alias.aod
@@ -5130,5 +5130,77 @@
       </affectedTables>
       <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/person/affectedIds.js</affectedIds>
     </indexSearchGroup>
+    <indexSearchGroup>
+      <name>OFFER</name>
+      <title>Offer</title>
+      <icon>VAADIN:CART</icon>
+      <active v="false" />
+      <idColumn>OFFERID</idColumn>
+      <titleColumn>TITLECOLUMN</titleColumn>
+      <descriptionColumn>DESCCOLUMN</descriptionColumn>
+      <query>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/offer/query.js</query>
+      <resultContextNeon>Offer</resultContextNeon>
+      <affectedTables>
+        <element>OFFER</element>
+      </affectedTables>
+      <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/offer/affectedIds.js</affectedIds>
+    </indexSearchGroup>
+    <indexSearchGroup>
+      <name>SALESORDER</name>
+      <title>Order</title>
+      <icon>VAADIN:DOLLAR</icon>
+      <active v="false" />
+      <idColumn>SALESORDERID</idColumn>
+      <titleColumn>TITLECOLUMN</titleColumn>
+      <descriptionColumn>DESCCOLUMN</descriptionColumn>
+      <query>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/salesorder/query.js</query>
+      <resultContextNeon>Order</resultContextNeon>
+      <affectedTables>
+        <element>SALESORDER</element>
+      </affectedTables>
+      <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/salesorder/affectedIds.js</affectedIds>
+    </indexSearchGroup>
+    <indexSearchGroup>
+      <name>CONTRACT</name>
+      <title>Contract</title>
+      <icon>VAADIN:FILE_TEXT</icon>
+      <active v="false" />
+      <idColumn>CONTRACT</idColumn>
+      <titleColumn>TITLECOLUMN</titleColumn>
+      <descriptionColumn>DESCCOLUMN</descriptionColumn>
+      <resultContextNeon>Contract</resultContextNeon>
+      <affectedTables>
+        <element>CONTRACT</element>
+      </affectedTables>
+      <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/contract/affectedIds.js</affectedIds>
+    </indexSearchGroup>
+    <indexSearchGroup>
+      <name>PRODUCT</name>
+      <title>Product</title>
+      <icon>VAADIN:HAMMER</icon>
+      <active v="false" />
+      <idColumn>PRODUCT</idColumn>
+      <titleColumn>TITLECOLUMN</titleColumn>
+      <descriptionColumn>DESCCOLUMN</descriptionColumn>
+      <resultContextNeon>Product</resultContextNeon>
+      <affectedTables>
+        <element>PRODUCT</element>
+      </affectedTables>
+      <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/product/affectedIds.js</affectedIds>
+    </indexSearchGroup>
+    <indexSearchGroup>
+      <name>SALESPROJECT</name>
+      <title>Salesproject</title>
+      <icon>VAADIN:BOOK_DOLLAR</icon>
+      <active v="false" />
+      <idColumn>SALESPROJECT</idColumn>
+      <titleColumn>TITLECOLUMN</titleColumn>
+      <descriptionColumn>DESCCOLUMN</descriptionColumn>
+      <resultContextNeon>Salesproject</resultContextNeon>
+      <affectedTables>
+        <element>SALESPROJECT</element>
+      </affectedTables>
+      <affectedIds>%aditoprj%/aliasDefinition/Data_alias/indexsearchgroups/salesproject/affectedIds.js</affectedIds>
+    </indexSearchGroup>
   </indexSearchGroups>
 </aliasDefinition>
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/contract/affectedIds.js b/aliasDefinition/Data_alias/indexsearchgroups/contract/affectedIds.js
new file mode 100644
index 00000000000..548f327a66b
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/contract/affectedIds.js
@@ -0,0 +1,4 @@
+import("system.vars");
+import("system.result");
+
+result.object([vars.getString("$local.idvalue")]);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/offer/affectedIds.js b/aliasDefinition/Data_alias/indexsearchgroups/offer/affectedIds.js
new file mode 100644
index 00000000000..548f327a66b
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/offer/affectedIds.js
@@ -0,0 +1,4 @@
+import("system.vars");
+import("system.result");
+
+result.object([vars.getString("$local.idvalue")]);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/offer/query.js b/aliasDefinition/Data_alias/indexsearchgroups/offer/query.js
new file mode 100644
index 00000000000..d8bff6962ee
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/offer/query.js
@@ -0,0 +1,22 @@
+import("system.result");
+import("system.vars");
+import("system.calendars");
+import("system.db");
+import("Sql_lib");
+
+var sqlQuery, sqlHelper, queryCondition, affectedIds;
+if (vars.exists("$local.idvalue")) {
+    affectedIds = vars.get("$local.idvalue");
+    queryCondition = "where OFFERID in ('" + affectedIds.map(function (v){return db.quote(v);}).join("', '") + "')";
+    //TODO: refactor this for incremental indexer (injections?)
+}
+sqlHelper = new SqlMaskingUtils();
+sqlQuery = "select OFFERID, " 
+    + "OFFERCODE as TITLECOLUMN, " 
+    + sqlHelper.concat(["ORGNAME", "'| Kd-Nr.: '", "CUSTOMERCODE"]) 
+    + " as DESCCOLUMN, OFFERCODE, ORGNAME, CUSTOMERCODE " 
+    + " from OFFER "
+    + " join CONTACT on OFFER.CONTACT_ID = CONTACTID "
+    + " join ORGANISATION on ORGANISATIONID = CONTACT.ORGANISATION_ID "
+    + queryCondition + " order by OFFERCODE ";
+result.string(sqlQuery);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/product/affectedIds.js b/aliasDefinition/Data_alias/indexsearchgroups/product/affectedIds.js
new file mode 100644
index 00000000000..548f327a66b
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/product/affectedIds.js
@@ -0,0 +1,4 @@
+import("system.vars");
+import("system.result");
+
+result.object([vars.getString("$local.idvalue")]);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/salesorder/affectedIds.js b/aliasDefinition/Data_alias/indexsearchgroups/salesorder/affectedIds.js
new file mode 100644
index 00000000000..548f327a66b
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/salesorder/affectedIds.js
@@ -0,0 +1,4 @@
+import("system.vars");
+import("system.result");
+
+result.object([vars.getString("$local.idvalue")]);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/salesorder/query.js b/aliasDefinition/Data_alias/indexsearchgroups/salesorder/query.js
new file mode 100644
index 00000000000..39a1d15028d
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/salesorder/query.js
@@ -0,0 +1,22 @@
+import("system.result");
+import("system.vars");
+import("system.calendars");
+import("system.db");
+import("Sql_lib");
+
+var sqlQuery, sqlHelper, queryCondition, affectedIds;
+if (vars.exists("$local.idvalue")) {
+    affectedIds = vars.get("$local.idvalue");
+    queryCondition = "where OFFERID in ('" + affectedIds.map(function (v){return db.quote(v);}).join("', '") + "')";
+    //TODO: refactor this for incremental indexer (injections?)
+}
+sqlHelper = new SqlMaskingUtils();
+sqlQuery = "select SALESORDERID, " 
+    + " ORDERCODE as TITLECOLUMN, " 
+    + sqlHelper.concat(["ORGNAME", "'| Kd-Nr.: '", "CUSTOMERCODE"]) 
+    + " as DESCCOLUMN, ORDERCODE, ORGNAME, CUSTOMERCODE "
+    + " from SALESORDER "
+    + " join RELATION on SALESORDER.RELATION_ID = RELATIONID "
+    + " join ORG on ORGID = RELATION.ORG_ID "
+    + queryCondition + " order by ORDERCODE ";
+result.string(sqlQuery);
\ No newline at end of file
diff --git a/aliasDefinition/Data_alias/indexsearchgroups/salesproject/affectedIds.js b/aliasDefinition/Data_alias/indexsearchgroups/salesproject/affectedIds.js
new file mode 100644
index 00000000000..548f327a66b
--- /dev/null
+++ b/aliasDefinition/Data_alias/indexsearchgroups/salesproject/affectedIds.js
@@ -0,0 +1,4 @@
+import("system.vars");
+import("system.result");
+
+result.object([vars.getString("$local.idvalue")]);
\ No newline at end of file
diff --git a/entity/AttributeRelation_entity/AttributeRelation_entity.aod b/entity/AttributeRelation_entity/AttributeRelation_entity.aod
index cabd4f0e481..e257ce32b6f 100644
--- a/entity/AttributeRelation_entity/AttributeRelation_entity.aod
+++ b/entity/AttributeRelation_entity/AttributeRelation_entity.aod
@@ -99,6 +99,7 @@
     <entityParameter>
       <name>ObjectRowId_param</name>
       <expose v="true" />
+      <triggerRecalculation v="true" />
       <description>PARAMETER</description>
     </entityParameter>
     <entityField>
diff --git a/entity/AttributeRelation_entity/recordcontainers/db/conditionProcess.js b/entity/AttributeRelation_entity/recordcontainers/db/conditionProcess.js
index aee41089141..623fac4157f 100644
--- a/entity/AttributeRelation_entity/recordcontainers/db/conditionProcess.js
+++ b/entity/AttributeRelation_entity/recordcontainers/db/conditionProcess.js
@@ -1,14 +1,17 @@
+import("system.logging");
 import("system.vars");
 import("system.db");
 import("system.result");
 import("Sql_lib");
 
-
-
 var cond = SqlCondition.begin()
                    .andPrepareVars("AB_ATTRIBUTERELATION.OBJECT_ROWID", "$param.ObjectRowId_param");
 
-if (vars.exists("$param.FilteredAttributeIds_param") && vars.get("$param.FilteredAttributeIds_param"))
+if (vars.exists("$param.ObjectRowId_param"))
+    logging.log(vars.get("$param.ObjectRowId_param"))
+
+if (vars.exists("$param.ObjectRowId_param") && vars.get("$param.ObjectRowId_param")
+    && vars.exists("$param.FilteredAttributeIds_param") && vars.get("$param.FilteredAttributeIds_param"))
 {
     var filteredIds = JSON.parse(vars.get("$param.FilteredAttributeIds_param"));
     var filteredIdsCondition = new SqlCondition();
diff --git a/entity/AttributeUsage_entity/AttributeUsage_entity.aod b/entity/AttributeUsage_entity/AttributeUsage_entity.aod
index 18af22e8347..db802200adc 100644
--- a/entity/AttributeUsage_entity/AttributeUsage_entity.aod
+++ b/entity/AttributeUsage_entity/AttributeUsage_entity.aod
@@ -13,7 +13,12 @@
       <name>OBJECT_TYPE</name>
       <title>Module</title>
       <consumer>Context</consumer>
+      <valueProcess>%aditoprj%/entity/AttributeUsage_entity/entityfields/object_type/valueProcess.js</valueProcess>
       <displayValueProcess>%aditoprj%/entity/AttributeUsage_entity/entityfields/object_type/displayValueProcess.js</displayValueProcess>
+      <onValueChangeTypes>
+        <element>MASK</element>
+        <element>PROCESS</element>
+      </onValueChangeTypes>
     </entityField>
     <entityField>
       <name>AB_ATTRIBUTEUSAGEID</name>
@@ -71,6 +76,9 @@
       <name>db</name>
       <alias>Data_alias</alias>
       <conditionProcess>%aditoprj%/entity/AttributeUsage_entity/recordcontainers/db/conditionProcess.js</conditionProcess>
+      <onDBInsert>%aditoprj%/entity/AttributeUsage_entity/recordcontainers/db/onDBInsert.js</onDBInsert>
+      <onDBUpdate>%aditoprj%/entity/AttributeUsage_entity/recordcontainers/db/onDBUpdate.js</onDBUpdate>
+      <onDBDelete>%aditoprj%/entity/AttributeUsage_entity/recordcontainers/db/onDBDelete.js</onDBDelete>
       <linkInformation>
         <linkInformation>
           <name>c30f5670-580e-4621-95dd-0fec4a99190f</name>
diff --git a/entity/AttributeUsage_entity/entityfields/ab_attribute_id/valueProcess.js b/entity/AttributeUsage_entity/entityfields/ab_attribute_id/valueProcess.js
index c3a2248b66e..9efed36fc26 100644
--- a/entity/AttributeUsage_entity/entityfields/ab_attribute_id/valueProcess.js
+++ b/entity/AttributeUsage_entity/entityfields/ab_attribute_id/valueProcess.js
@@ -3,4 +3,6 @@ import("system.result");
 import("system.neon");
 
 if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW && vars.exists("$param.AttributeId_param") && vars.get("$param.AttributeId_param") != null)
-    result.string(vars.get("$param.AttributeId_param"));
\ No newline at end of file
+    result.string(vars.get("$param.AttributeId_param"));
+else if (vars.get("$field.AB_ATTRIBUTE_ID"))
+    result.string(vars.get("$field.AB_ATTRIBUTE_ID"));
\ No newline at end of file
diff --git a/entity/AttributeUsage_entity/entityfields/object_type/valueProcess.js b/entity/AttributeUsage_entity/entityfields/object_type/valueProcess.js
new file mode 100644
index 00000000000..40b7c76fc55
--- /dev/null
+++ b/entity/AttributeUsage_entity/entityfields/object_type/valueProcess.js
@@ -0,0 +1,5 @@
+import("system.vars");
+
+//this is a workaround to get the old value in the onDBUpdate process
+//@TODO replace this when it's possible to get the old value in onDBUpdate
+vars.set("$context.originalObjectType", vars.get("$field.OBJECT_TYPE"));
\ No newline at end of file
diff --git a/entity/AttributeUsage_entity/recordcontainers/db/onDBDelete.js b/entity/AttributeUsage_entity/recordcontainers/db/onDBDelete.js
new file mode 100644
index 00000000000..b41e99cd82a
--- /dev/null
+++ b/entity/AttributeUsage_entity/recordcontainers/db/onDBDelete.js
@@ -0,0 +1,7 @@
+import("system.vars");
+import("Attribute_lib");
+
+var attributeId = vars.get("$field.AB_ATTRIBUTE_ID");
+var objectType = vars.get("$field.OBJECT_TYPE");
+
+AttributeUsageUtil.deleteChildrenUsages(attributeId, objectType);
\ No newline at end of file
diff --git a/entity/AttributeUsage_entity/recordcontainers/db/onDBInsert.js b/entity/AttributeUsage_entity/recordcontainers/db/onDBInsert.js
new file mode 100644
index 00000000000..73c28f29169
--- /dev/null
+++ b/entity/AttributeUsage_entity/recordcontainers/db/onDBInsert.js
@@ -0,0 +1,8 @@
+import("system.vars");
+import("Attribute_lib");
+
+var attributeId = vars.get("$field.AB_ATTRIBUTE_ID");
+var objectType = vars.get("$field.OBJECT_TYPE");
+
+AttributeUsageUtil.insertChildrenUsages(attributeId, objectType);
+AttributeUsageUtil.removeDuplicates(attributeId);
\ No newline at end of file
diff --git a/entity/AttributeUsage_entity/recordcontainers/db/onDBUpdate.js b/entity/AttributeUsage_entity/recordcontainers/db/onDBUpdate.js
new file mode 100644
index 00000000000..02890f1b92c
--- /dev/null
+++ b/entity/AttributeUsage_entity/recordcontainers/db/onDBUpdate.js
@@ -0,0 +1,9 @@
+import("system.vars");
+import("Attribute_lib");
+
+var attributeId = vars.get("$field.AB_ATTRIBUTE_ID");
+var oldObjectType = vars.get("$context.originalObjectType");
+var newObjectType = vars.get("$field.OBJECT_TYPE");
+
+AttributeUsageUtil.updateChildrenUsages(attributeId, oldObjectType, newObjectType);
+AttributeUsageUtil.removeDuplicates(attributeId);
\ No newline at end of file
diff --git a/entity/Attribute_entity/Attribute_entity.aod b/entity/Attribute_entity/Attribute_entity.aod
index 405955e8bde..a5c910a2b97 100644
--- a/entity/Attribute_entity/Attribute_entity.aod
+++ b/entity/Attribute_entity/Attribute_entity.aod
@@ -240,6 +240,11 @@
       <title>Name</title>
       <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/name_with_type/valueProcess.js</valueProcess>
     </entityField>
+    <entityActionField>
+      <name>tsest</name>
+      <fieldType>ACTION</fieldType>
+      <onActionProcess>%aditoprj%/entity/Attribute_entity/entityfields/tsest/onActionProcess.js</onActionProcess>
+    </entityActionField>
   </entityFields>
   <recordContainers>
     <dbRecordContainer>
diff --git a/entity/Attribute_entity/entityfields/attribute_type/stateProcess.js b/entity/Attribute_entity/entityfields/attribute_type/stateProcess.js
index b768fb2fba7..82e1e3f89fd 100644
--- a/entity/Attribute_entity/entityfields/attribute_type/stateProcess.js
+++ b/entity/Attribute_entity/entityfields/attribute_type/stateProcess.js
@@ -8,5 +8,7 @@ if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW && vars.get("$field.
 {
     var type = AttributeHandler.begin(vars.get("$field.ATTRIBUTE_PARENT_ID")).getAttributeType();
     if (type == $AttributeTypes.COMBO)
-        result.string(neon.COMPONENTSTATE_INVISIBLE);
-}
\ No newline at end of file
+        result.string(neon.COMPONENTSTATE_READONLY);
+}
+else if (vars.get("$sys.recordstate") != neon.OPERATINGSTATE_NEW)
+    result.string(neon.COMPONENTSTATE_READONLY);
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/attributechildren/children/attrparentid_param/valueProcess.js b/entity/Attribute_entity/entityfields/attributechildren/children/attrparentid_param/valueProcess.js
index f2318723e88..b68489b6e02 100644
--- a/entity/Attribute_entity/entityfields/attributechildren/children/attrparentid_param/valueProcess.js
+++ b/entity/Attribute_entity/entityfields/attributechildren/children/attrparentid_param/valueProcess.js
@@ -1,4 +1,7 @@
 import("system.vars");
 import("system.result");
+import("Attribute_lib");
 
-result.string(vars.getString("$field.AB_ATTRIBUTEID"));
+var type = vars.get("$field.ATTRIBUTE_TYPE").trim();
+if (type == $AttributeTypes.GROUP || type == $AttributeTypes.COMBO)
+    result.string(vars.getString("$field.AB_ATTRIBUTEID"));
diff --git a/entity/Attribute_entity/entityfields/attributechildren/children/attrparenttype_param/valueProcess.js b/entity/Attribute_entity/entityfields/attributechildren/children/attrparenttype_param/valueProcess.js
index 372decd8620..fe18eca06c7 100644
--- a/entity/Attribute_entity/entityfields/attributechildren/children/attrparenttype_param/valueProcess.js
+++ b/entity/Attribute_entity/entityfields/attributechildren/children/attrparenttype_param/valueProcess.js
@@ -1,10 +1,4 @@
-import("system.neon");
 import("system.vars");
 import("system.result");
 
-var type;
-if (vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_NEW)
-    type = "GETGROUPS";
-else
-    type = vars.getString("$field.ATTRIBUTE_TYPE").trim()
-result.string(type);
+result.string(vars.getString("$field.ATTRIBUTE_TYPE").trim());
diff --git a/entity/Attribute_entity/entityfields/tsest/onActionProcess.js b/entity/Attribute_entity/entityfields/tsest/onActionProcess.js
new file mode 100644
index 00000000000..dd3c5349ea7
--- /dev/null
+++ b/entity/Attribute_entity/entityfields/tsest/onActionProcess.js
@@ -0,0 +1,3 @@
+import("Attribute_lib");
+
+AttributeUsageUtil.removeDuplicates();
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/usagelist/valueProcess.js b/entity/Attribute_entity/entityfields/usagelist/valueProcess.js
index 0192da48ac5..675da9521dc 100644
--- a/entity/Attribute_entity/entityfields/usagelist/valueProcess.js
+++ b/entity/Attribute_entity/entityfields/usagelist/valueProcess.js
@@ -3,10 +3,16 @@ import("system.result");
 import("system.db");
 import("system.vars");
 import("Sql_lib");
+import("Attribute_lib");
 
-var usages = db.array(db.COLUMN, SqlCondition.begin()
-    .andPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", vars.get("$field.AB_ATTRIBUTEID"))
-    .buildSql("select OBJECT_TYPE from AB_ATTRIBUTEUSAGE"));
-var usageStr = translate.text("Usage");
+var retStr = "\u00A0"; // \u00A0 -> space character that doesn't get trimmed 
+if (vars.get("$field.ATTRIBUTE_TYPE").trim() != $AttributeTypes.COMBOVALUE)
+{
+    var usages = db.array(db.COLUMN, SqlCondition.begin()
+        .andPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", vars.get("$field.AB_ATTRIBUTEID"))
+        .buildSql("select OBJECT_TYPE from AB_ATTRIBUTEUSAGE"));
+    if (usages.length)
+        retStr = translate.text("Usage") +  ": " + usages.join(", ");
+}
 
-result.string(usages.length ? usageStr + ": " + usages.join(", ") : "\u00A0"); // \u00A0 -> space character that doesn't get trimmed 
\ No newline at end of file
+result.string(retStr);
\ No newline at end of file
diff --git a/entity/Offer_entity/entityfields/printoffer/stateProcess.js b/entity/Offer_entity/entityfields/printoffer/stateProcess.js
index b6ef75cff77..bb8a76fc637 100644
--- a/entity/Offer_entity/entityfields/printoffer/stateProcess.js
+++ b/entity/Offer_entity/entityfields/printoffer/stateProcess.js
@@ -10,4 +10,4 @@ var itemcount = db.cell(SqlCondition.begin()
 if(itemcount == "0")
     result.string(neon.COMPONENTSTATE_DISABLED);
 else
-    result.string(neon.COMPONENTSTATE_AUTO);
\ No newline at end of file
+    result.string(neon.COMPONENTSTATE_EDITABLE);
\ No newline at end of file
diff --git a/entity/SalesprojectCompetition_entity/SalesprojectCompetition_entity.aod b/entity/SalesprojectCompetition_entity/SalesprojectCompetition_entity.aod
index 33fcc987940..0df2e74d7c2 100644
--- a/entity/SalesprojectCompetition_entity/SalesprojectCompetition_entity.aod
+++ b/entity/SalesprojectCompetition_entity/SalesprojectCompetition_entity.aod
@@ -229,17 +229,15 @@
         <entityParameter>
           <name>FilteredAttributeIds_param</name>
           <valueProcess>%aditoprj%/entity/SalesprojectCompetition_entity/entityfields/attributes/children/filteredattributeids_param/valueProcess.js</valueProcess>
-          <expose v="false" />
         </entityParameter>
         <entityParameter>
           <name>ObjectRowId_param</name>
           <valueProcess>%aditoprj%/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objectrowid_param/valueProcess.js</valueProcess>
-          <expose v="false" />
+          <triggerRecalculation v="true" />
         </entityParameter>
         <entityParameter>
           <name>ObjectType_param</name>
           <valueProcess>%aditoprj%/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objecttype_param/valueProcess.js</valueProcess>
-          <expose v="false" />
         </entityParameter>
         <entityParameter>
           <name>DisplaySimpleName_param</name>
diff --git a/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objecttype_param/valueProcess.js b/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objecttype_param/valueProcess.js
index 41202c0153c..9acfa14adab 100644
--- a/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objecttype_param/valueProcess.js
+++ b/entity/SalesprojectCompetition_entity/entityfields/attributes/children/objecttype_param/valueProcess.js
@@ -1,3 +1,7 @@
+import("system.vars");
 import("system.result");
 
-result.string("Organisation");
+if (vars.get("$field.CONTACT_ID"))
+    result.string("Organisation");
+else
+    result.string("true");
\ No newline at end of file
diff --git a/process/Attribute_lib/process.js b/process/Attribute_lib/process.js
index 6b234d3c235..ff994422d9e 100644
--- a/process/Attribute_lib/process.js
+++ b/process/Attribute_lib/process.js
@@ -1,3 +1,5 @@
+import("system.logging");
+import("system.util");
 import("system.datetime");
 import("system.translate");
 import("system.neon");
@@ -486,3 +488,145 @@ AttributeTypeUtil.getTypeColumnIndex = function (pAttributeType)
         AttributeTypeUtil._initTypeColumnData();
     return this._typeColumnMap[pAttributeType.trim()];
 }
+
+/*********************************************************************************************************************/
+
+/**
+ * @class
+ */
+function AttributeUsageUtil () {}
+
+AttributeUsageUtil.insertChildrenUsages = function (pAttributeId, pObjectType)
+{
+    var table = "AB_ATTRIBUTEUSAGE";
+    var columns = ["AB_ATTRIBUTEUSAGEID", "AB_ATTRIBUTE_ID", "OBJECT_TYPE"];
+    var types = db.getColumnTypes(table, columns);
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, "
+            + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
+            + pObjectType + "') from AB_ATTRIBUTE";
+    
+    var inserts = [];
+    _addInserts(pAttributeId, pObjectType);
+    db.inserts(inserts);
+    
+    function _addInserts (pAttributeId, pObjectType)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1] != "true")
+            {
+                let values = [util.getNewUUID(), row[0], pObjectType];
+                inserts.push([table, columns, types, values]);
+            }
+            _addInserts(row[0], pObjectType);
+        });
+    }
+}
+
+AttributeUsageUtil.updateChildrenUsages = function (pAttributeId, pOldObjectType, pNewObjectType)
+{
+    if (!pNewObjectType)
+        return;
+    
+    var table = "AB_ATTRIBUTEUSAGE";
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID, "
+        + " exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID and OBJECT_TYPE = '" 
+        + pNewObjectType + "')"
+        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pOldObjectType + "'";
+    
+    var updateIds = [];
+    
+    //it is possible that the new objectType is already in a subordinate attribute 
+    //and an update could cause a duplicate entry so one has to be deleted
+    var deleteIds = []; 
+    
+    _addUpdateIds(pAttributeId, pOldObjectType);
+        
+    if (updateIds.length)
+        db.updateData(table, ["OBJECT_TYPE"], null, [pNewObjectType], SqlCondition.begin()
+            .and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID in ('" + updateIds.join("','") + "')")
+            .build("1=2")
+        );
+    if (deleteIds.length)
+        db.deleteData(table, SqlCondition.begin()
+            .and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID in ('" + deleteIds.join("','") + "')")
+            .build("1=2"));
+    
+    function _addUpdateIds (pAttributeId)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1] && row[2] == "true")
+                deleteIds.push(row[1]);
+            else if (row[1])
+                updateIds.push(row[1]);
+            _addUpdateIds(row[0]);
+        });
+    }
+}
+
+AttributeUsageUtil.deleteChildrenUsages = function (pAttributeId, pObjectType)
+{
+    var table = "AB_ATTRIBUTEUSAGE";
+    
+    var sqlSelect = "select AB_ATTRIBUTEID, AB_ATTRIBUTEUSAGEID "
+        + " from AB_ATTRIBUTE left join AB_ATTRIBUTEUSAGE on AB_ATTRIBUTEID = AB_ATTRIBUTE_ID and OBJECT_TYPE = '" + pObjectType + "'";
+    
+    var deleteIds = [];
+    _addDeleteIds(pAttributeId, pObjectType);
+    if (deleteIds.length)
+        db.deleteData(table, SqlCondition.begin()
+            .and("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID in ('" + deleteIds.join("','") + "')")
+            .build("1=2"));
+    
+    function _addDeleteIds (pAttributeId)
+    {
+        var condition = SqlCondition.begin()
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_TYPE", $AttributeTypes.COMBOVALUE, "# != ?")
+            .andPrepare("AB_ATTRIBUTE.ATTRIBUTE_PARENT_ID", pAttributeId);
+        var attributes = db.table(condition.buildSql(sqlSelect));
+        
+        attributes.forEach(function (row)
+        {
+            if (row[1])
+                deleteIds.push(row[1]);
+            _addDeleteIds(row[0]);
+        });
+    }
+}
+
+AttributeUsageUtil.removeDuplicates = function (pAttributeId)
+{
+    var condition = SqlCondition.begin()
+        .and("exists (select AB_ATTRIBUTEUSAGEID from AB_ATTRIBUTEUSAGE AU where AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AU.AB_ATTRIBUTE_ID "
+            + "and AB_ATTRIBUTEUSAGE.OBJECT_TYPE = AU.OBJECT_TYPE and AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID != AU.AB_ATTRIBUTEUSAGEID)");
+    if (pAttributeId)
+        condition.andPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID", pAttributeId);
+    
+    var duplicates = db.table(condition.buildSql("select AB_ATTRIBUTEUSAGEID, AB_ATTRIBUTE_ID, OBJECT_TYPE from AB_ATTRIBUTEUSAGE"));
+    var usageObj = {};
+    var deleteCond = SqlCondition.begin();
+    
+    duplicates.forEach(function (row)
+    {
+        if (!(row[1] in this))
+            this[row[1]] = {};
+        if (row[2] in this[row[1]])
+            deleteCond.orPrepare("AB_ATTRIBUTEUSAGE.AB_ATTRIBUTEUSAGEID", row[0]);
+        this[row[1]][row[2]] = true;
+    }, usageObj);
+    if (deleteCond.isSet())
+        db.deleteData("AB_ATTRIBUTEUSAGE", deleteCond.build("1=2"));
+}
\ No newline at end of file
diff --git a/process/Offer_lib/process.js b/process/Offer_lib/process.js
index aa218c4ff40..b4c8bea33f5 100644
--- a/process/Offer_lib/process.js
+++ b/process/Offer_lib/process.js
@@ -148,6 +148,8 @@ OfferUtils.openOfferReport = function (pOfferID)
         );
     var itemData = db.table(offerItemSql);
     
+    if (itemData.length == 0)
+        return;
     
     // TODO: AddrObject implementieren
     //var addrobj = new AddrObject(contactId);
-- 
GitLab