From 53bfb85a633f8ffd4762c13dca796dfd53dbc776 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Maximilian=20Schr=C3=B6ger?= <m.schroeger@adito.de>
Date: Tue, 20 Nov 2018 15:24:09 +0100
Subject: [PATCH] =?UTF-8?q?Angebotsposten=20/=20St=C3=BCckliste:=20-=20Fer?=
 =?UTF-8?q?tigstellung=20St=C3=BCckliste=20-=20Einf=C3=BCgen=20eines=20neu?=
 =?UTF-8?q?en=20Posten=20mitsamt=20der=20St=C3=BCckliste=20des=20ausgew?=
 =?UTF-8?q?=C3=A4hlten=20Produktes=20-=20L=C3=B6schen=20eines=20Posten=20m?=
 =?UTF-8?q?itsamt=20seinen=20untergeordneten=20Posten=20-=20Funktionsbibli?=
 =?UTF-8?q?othek=20Offer=5Flib=20zu=20OfferOrder=5Flib=20ge=C3=A4ndert=20u?=
 =?UTF-8?q?nd=20Funktionen=20refactored?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../entityfields/offercode/onValidation.js    |   2 +-
 .../entityfields/offercode/valueProcess.js    |   2 +-
 entity/Offer_entity/grantDeleteProcess.js     |   2 +-
 entity/Offer_entity/grantUpdateProcess.js     |   2 +-
 entity/Offeritem_entity/Offeritem_entity.aod  |  18 +-
 .../Offeritem_entity/afterOperatingState.js   |  14 +
 .../entityfields/assignedto/valueProcess.js   |   0
 .../entityfields/itemposition/onValidation.js |   0
 .../entityfields/itemposition/valueProcess.js |   0
 .../entityfields/product_id/onValidation.js   |   0
 .../entityfields/product_id/onValueChange.js  |  15 +-
 .../entityfields/quantity/onValueChange.js    |   6 +-
 .../entityfields/totalprice/onValidation.js   |   0
 .../entityfields/totalprice/valueProcess.js   |   4 +-
 entity/Offeritem_entity/grantCreateProcess.js |   2 +-
 entity/Offeritem_entity/grantDeleteProcess.js |   2 +-
 entity/Offeritem_entity/grantUpdateProcess.js |   2 +-
 entity/Offeritem_entity/onDBDelete.js         |  10 +-
 entity/Offeritem_entity/onDBInsert.js         |  84 +--
 entity/Offeritem_entity/onDBUpdate.js         |   2 +-
 entity/Offeritem_entity/orderClauseProcess.js |   3 +
 entity/Prod2prod_entity/Prod2prod_entity.aod  |  16 +-
 entity/Prod2prod_entity/contentProcess.js     |   6 +-
 .../entityfields/prod2prodid/valueProcess.js  |   7 +
 .../entityfields/source_id/onValidation.js    |  29 -
 .../source_id/possibleItemsProcess.js         |  12 +-
 .../OfferOrder_lib.aod}                       |   4 +-
 process/OfferOrder_lib/process.js             | 619 ++++++++++++++++++
 process/Offer_lib/process.js                  |  96 ---
 process/Product_lib/process.js                | 444 ++++++++-----
 process/Util_lib/process.js                   |  22 +-
 31 files changed, 1012 insertions(+), 413 deletions(-)
 create mode 100644 entity/Offeritem_entity/afterOperatingState.js
 create mode 100644 entity/Offeritem_entity/entityfields/assignedto/valueProcess.js
 create mode 100644 entity/Offeritem_entity/entityfields/itemposition/onValidation.js
 create mode 100644 entity/Offeritem_entity/entityfields/itemposition/valueProcess.js
 create mode 100644 entity/Offeritem_entity/entityfields/product_id/onValidation.js
 create mode 100644 entity/Offeritem_entity/entityfields/totalprice/onValidation.js
 create mode 100644 entity/Offeritem_entity/orderClauseProcess.js
 create mode 100644 entity/Prod2prod_entity/entityfields/prod2prodid/valueProcess.js
 rename process/{Offer_lib/Offer_lib.aod => OfferOrder_lib/OfferOrder_lib.aod} (76%)
 create mode 100644 process/OfferOrder_lib/process.js
 delete mode 100644 process/Offer_lib/process.js

diff --git a/entity/Offer_entity/entityfields/offercode/onValidation.js b/entity/Offer_entity/entityfields/offercode/onValidation.js
index d74d0c362ec..bd2b194002b 100644
--- a/entity/Offer_entity/entityfields/offercode/onValidation.js
+++ b/entity/Offer_entity/entityfields/offercode/onValidation.js
@@ -1,7 +1,7 @@
 import("system.vars");
 import("system.result");
 import("system.neon");
-import("Offer_lib");
+import("OfferOrder_lib");
 import("Util_lib");
 
 var OfferUtils = new OfferUtils();
diff --git a/entity/Offer_entity/entityfields/offercode/valueProcess.js b/entity/Offer_entity/entityfields/offercode/valueProcess.js
index 05581ec804c..a962e8d4fb7 100644
--- a/entity/Offer_entity/entityfields/offercode/valueProcess.js
+++ b/entity/Offer_entity/entityfields/offercode/valueProcess.js
@@ -1,7 +1,7 @@
 import("system.vars");
 import("system.result");
 import("system.neon");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 if(vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_NEW)
 {
diff --git a/entity/Offer_entity/grantDeleteProcess.js b/entity/Offer_entity/grantDeleteProcess.js
index 1d03536547d..302a4d7ba36 100644
--- a/entity/Offer_entity/grantDeleteProcess.js
+++ b/entity/Offer_entity/grantDeleteProcess.js
@@ -1,6 +1,6 @@
 import("system.vars");
 import("system.result");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oUtils = new OfferUtils();
 result.string(oUtils.isEditable(vars.get("$field.STATUS")));
\ No newline at end of file
diff --git a/entity/Offer_entity/grantUpdateProcess.js b/entity/Offer_entity/grantUpdateProcess.js
index 1d03536547d..302a4d7ba36 100644
--- a/entity/Offer_entity/grantUpdateProcess.js
+++ b/entity/Offer_entity/grantUpdateProcess.js
@@ -1,6 +1,6 @@
 import("system.vars");
 import("system.result");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oUtils = new OfferUtils();
 result.string(oUtils.isEditable(vars.get("$field.STATUS")));
\ No newline at end of file
diff --git a/entity/Offeritem_entity/Offeritem_entity.aod b/entity/Offeritem_entity/Offeritem_entity.aod
index c28e322e3bd..3adf52ce78a 100644
--- a/entity/Offeritem_entity/Offeritem_entity.aod
+++ b/entity/Offeritem_entity/Offeritem_entity.aod
@@ -3,16 +3,18 @@
   <name>Offeritem_entity</name>
   <title>Offeritem</title>
   <majorModelMode>DISTRIBUTED</majorModelMode>
-  <alias>Data_alias</alias>
-  <conditionProcess>%aditoprj%/entity/Offeritem_entity/conditionProcess.js</conditionProcess>
-  <onDBInsert>%aditoprj%/entity/Offeritem_entity/onDBInsert.js</onDBInsert>
-  <onDBUpdate>%aditoprj%/entity/Offeritem_entity/onDBUpdate.js</onDBUpdate>
-  <onDBDelete>%aditoprj%/entity/Offeritem_entity/onDBDelete.js</onDBDelete>
   <grantCreateProcess>%aditoprj%/entity/Offeritem_entity/grantCreateProcess.js</grantCreateProcess>
   <grantUpdateProcess>%aditoprj%/entity/Offeritem_entity/grantUpdateProcess.js</grantUpdateProcess>
   <grantDeleteProcess>%aditoprj%/entity/Offeritem_entity/grantDeleteProcess.js</grantDeleteProcess>
   <recordContainerType>DB</recordContainerType>
   <caption>Offeritem</caption>
+  <afterOperatingState>%aditoprj%/entity/Offeritem_entity/afterOperatingState.js</afterOperatingState>
+  <alias>Data_alias</alias>
+  <conditionProcess>%aditoprj%/entity/Offeritem_entity/conditionProcess.js</conditionProcess>
+  <orderClauseProcess>%aditoprj%/entity/Offeritem_entity/orderClauseProcess.js</orderClauseProcess>
+  <onDBInsert>%aditoprj%/entity/Offeritem_entity/onDBInsert.js</onDBInsert>
+  <onDBUpdate>%aditoprj%/entity/Offeritem_entity/onDBUpdate.js</onDBUpdate>
+  <onDBDelete>%aditoprj%/entity/Offeritem_entity/onDBDelete.js</onDBDelete>
   <entityFields>
     <entityIncomingField>
       <name>#INCOMING</name>
@@ -29,6 +31,7 @@
       <name>ASSIGNEDTO</name>
       <tableName>OFFERITEM</tableName>
       <columnName>ASSIGNEDTO</columnName>
+      <valueProcess>%aditoprj%/entity/Offeritem_entity/entityfields/assignedto/valueProcess.js</valueProcess>
     </entityField>
     <entityField>
       <name>DATE_EDIT</name>
@@ -78,6 +81,8 @@
       <columnName>ITEMPOSITION</columnName>
       <caption>Position</caption>
       <state>READONLY</state>
+      <valueProcess>%aditoprj%/entity/Offeritem_entity/entityfields/itemposition/valueProcess.js</valueProcess>
+      <onValidation>%aditoprj%/entity/Offeritem_entity/entityfields/itemposition/onValidation.js</onValidation>
     </entityField>
     <entityField>
       <name>ITEMSORT</name>
@@ -153,7 +158,7 @@
       <tableName>OFFERITEM</tableName>
       <columnName>VAT</columnName>
       <caption>VAT</caption>
-      <state>READONLY</state>
+      <state>AUTO</state>
     </entityField>
     <entityParameter>
       <name>OfferId_param</name>
@@ -181,6 +186,7 @@
       <outputFormat>#,##0.00</outputFormat>
       <state>READONLY</state>
       <valueProcess>%aditoprj%/entity/Offeritem_entity/entityfields/totalprice/valueProcess.js</valueProcess>
+      <onValidation>%aditoprj%/entity/Offeritem_entity/entityfields/totalprice/onValidation.js</onValidation>
     </entityField>
     <entityField>
       <name>IMAGE</name>
diff --git a/entity/Offeritem_entity/afterOperatingState.js b/entity/Offeritem_entity/afterOperatingState.js
new file mode 100644
index 00000000000..bd495944d60
--- /dev/null
+++ b/entity/Offeritem_entity/afterOperatingState.js
@@ -0,0 +1,14 @@
+import("system.neon");
+import("system.vars");
+import("OfferOrder_lib");
+
+var opState = vars.get("$sys.operatingstate");
+
+if(opState == neon.OPERATINGSTATE_NEW)
+{
+    var offerId = vars.get("$field.OFFER_ID");
+    var oiUtils = new OfferItemUtils(offerId);
+    
+    vars.set("$field.ITEMSORT", oiUtils.getNextItemSort());
+    vars.set("$field.ITEMPOSITION", oiUtils.getNextItemPosition(vars.get("$field.ASSIGNEDTO")));
+}
\ No newline at end of file
diff --git a/entity/Offeritem_entity/entityfields/assignedto/valueProcess.js b/entity/Offeritem_entity/entityfields/assignedto/valueProcess.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/entity/Offeritem_entity/entityfields/itemposition/onValidation.js b/entity/Offeritem_entity/entityfields/itemposition/onValidation.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/entity/Offeritem_entity/entityfields/itemposition/valueProcess.js b/entity/Offeritem_entity/entityfields/itemposition/valueProcess.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/entity/Offeritem_entity/entityfields/product_id/onValidation.js b/entity/Offeritem_entity/entityfields/product_id/onValidation.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/entity/Offeritem_entity/entityfields/product_id/onValueChange.js b/entity/Offeritem_entity/entityfields/product_id/onValueChange.js
index 0c8a79b2abb..196b4ab169e 100644
--- a/entity/Offeritem_entity/entityfields/product_id/onValueChange.js
+++ b/entity/Offeritem_entity/entityfields/product_id/onValueChange.js
@@ -14,16 +14,17 @@ if(pid != "")
     var PriceListFilter = { currency: curr, quantity: vars.get("$field.QUANTITY"), relationId: relid };
     
     var ProductDetails = pUtils.getProductDetails(pid, PriceListFilter);
-    if(ProductDetails[pid] != undefined)
+    
+    if(ProductDetails.productId != undefined)
     {
-        vars.set("$field.GROUPCODEID", ProductDetails[pid].groupCode);
-        vars.set("$field.UNIT", ProductDetails[pid].unit);
-        vars.set("$field.ITEMNAME", ProductDetails[pid].productName);
+        vars.set("$field.GROUPCODEID", ProductDetails.groupCode);
+        vars.set("$field.UNIT", ProductDetails.unit);
+        vars.set("$field.ITEMNAME", ProductDetails.productName);
 
-        if(ProductDetails[pid].PriceListToUse != null)
+        if(ProductDetails.PriceListToUse != null)
         {
-            vars.set("$field.PRICE", ProductDetails[pid].PriceListToUse.price);
-            vars.set("$field.VAT", ProductDetails[pid].PriceListToUse.vat);
+            vars.set("$field.PRICE", ProductDetails.PriceListToUse.price);
+            vars.set("$field.VAT", ProductDetails.PriceListToUse.vat);
         }
     }
 }
\ No newline at end of file
diff --git a/entity/Offeritem_entity/entityfields/quantity/onValueChange.js b/entity/Offeritem_entity/entityfields/quantity/onValueChange.js
index ac528c67dd2..d605b64c6f8 100644
--- a/entity/Offeritem_entity/entityfields/quantity/onValueChange.js
+++ b/entity/Offeritem_entity/entityfields/quantity/onValueChange.js
@@ -16,9 +16,9 @@ if(pid != "" && newQuantity != "")
     
     var ProductDetails = pUtils.getProductDetails(pid, PriceListFilter);
     
-    if(ProductDetails[pid] != undefined && ProductDetails[pid].PriceListToUse != null)
+    if(ProductDetails.productId != undefined && ProductDetails.PriceListToUse != null)
     {
-        vars.set("$field.PRICE", ProductDetails[pid].PriceListToUse.price);
-        vars.set("$field.VAT", ProductDetails[pid].PriceListToUse.vat);
+        vars.set("$field.PRICE", ProductDetails.PriceListToUse.price);
+        vars.set("$field.VAT", ProductDetails.PriceListToUse.vat);
     }
 }
\ No newline at end of file
diff --git a/entity/Offeritem_entity/entityfields/totalprice/onValidation.js b/entity/Offeritem_entity/entityfields/totalprice/onValidation.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/entity/Offeritem_entity/entityfields/totalprice/valueProcess.js b/entity/Offeritem_entity/entityfields/totalprice/valueProcess.js
index 7fff82a426d..cbd08ae1d7b 100644
--- a/entity/Offeritem_entity/entityfields/totalprice/valueProcess.js
+++ b/entity/Offeritem_entity/entityfields/totalprice/valueProcess.js
@@ -1,8 +1,8 @@
 import("system.result");
 import("system.vars");
-import("Offer_lib");
+import("OfferOrder_lib");
 
-var oiUtils = new OfferItemUtils();
+var oiUtils = new OfferItemUtils(vars.get("$field.OFFER_ID"));
 
 result.string(oiUtils.roundPrice(oiUtils.getItemSum(vars.get("$field.QUANTITY"), vars.get("$field.PRICE")
                                 , vars.get("$field.DISCOUNT"), vars.get("$field.OPTIONAL"))));
\ No newline at end of file
diff --git a/entity/Offeritem_entity/grantCreateProcess.js b/entity/Offeritem_entity/grantCreateProcess.js
index 5d45e991a5d..6651ef933bf 100644
--- a/entity/Offeritem_entity/grantCreateProcess.js
+++ b/entity/Offeritem_entity/grantCreateProcess.js
@@ -1,6 +1,6 @@
 import("system.vars");
 import("system.result");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oUtils = new OfferUtils();
 result.string(oUtils.isEditable(vars.get("$param.OfferStatus_param")));
\ No newline at end of file
diff --git a/entity/Offeritem_entity/grantDeleteProcess.js b/entity/Offeritem_entity/grantDeleteProcess.js
index 5d45e991a5d..6651ef933bf 100644
--- a/entity/Offeritem_entity/grantDeleteProcess.js
+++ b/entity/Offeritem_entity/grantDeleteProcess.js
@@ -1,6 +1,6 @@
 import("system.vars");
 import("system.result");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oUtils = new OfferUtils();
 result.string(oUtils.isEditable(vars.get("$param.OfferStatus_param")));
\ No newline at end of file
diff --git a/entity/Offeritem_entity/grantUpdateProcess.js b/entity/Offeritem_entity/grantUpdateProcess.js
index 5d45e991a5d..6651ef933bf 100644
--- a/entity/Offeritem_entity/grantUpdateProcess.js
+++ b/entity/Offeritem_entity/grantUpdateProcess.js
@@ -1,6 +1,6 @@
 import("system.vars");
 import("system.result");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oUtils = new OfferUtils();
 result.string(oUtils.isEditable(vars.get("$param.OfferStatus_param")));
\ No newline at end of file
diff --git a/entity/Offeritem_entity/onDBDelete.js b/entity/Offeritem_entity/onDBDelete.js
index e7ce64e1531..ffa39a020f6 100644
--- a/entity/Offeritem_entity/onDBDelete.js
+++ b/entity/Offeritem_entity/onDBDelete.js
@@ -1,15 +1,21 @@
 import("system.neon");
 import("system.vars");
 import("system.db");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oid = vars.get("$field.OFFER_ID");
 if(oid != "")
 {
+    var oiid = vars.get("$field.OFFERITEMID");
+    var oiUtils = new OfferItemUtils(oid);
+    var deletedIds = oiUtils.deletePartsList(oiid);
+    oiUtils.reOrgItems();
+    
+    deletedIds.push(oiid);
     var cols = ["NET", "VAT"];
     var colTypes = db.getColumnTypes("OFFER", cols);
     var oUtils = new OfferUtils();
-    var vals = oUtils.getOfferNetAndVAT(oid, vars.get("$field.OFFERITEMID"));
+    var vals = oUtils.getOfferNetAndVAT(oid, deletedIds);
 
     db.updateData("OFFER", cols, colTypes, vals, "OFFERID = '" + oid + "'");
     
diff --git a/entity/Offeritem_entity/onDBInsert.js b/entity/Offeritem_entity/onDBInsert.js
index 786f099d48e..1f39458fda4 100644
--- a/entity/Offeritem_entity/onDBInsert.js
+++ b/entity/Offeritem_entity/onDBInsert.js
@@ -3,44 +3,18 @@ import("system.neon");
 import("system.vars");
 import("system.db");
 import("system.util");
-import("Offer_lib");
+import("OfferOrder_lib");
 import("Product_lib");
 
 var oid = vars.get("$field.OFFER_ID");
 if(oid != "")
 {
-    //insert parts list
-    var rootProdId = vars.get("$field.PRODUCT_ID");
-    if(rootProdId != "")
-    {
-        var p2pUtils = new Prod2prodUtils();
-        var pUtils = new ProductUtils();
-        var partsList = p2pUtils.getSubordinatedObject(rootProdId);
-        var statements = [];
-        var cols =  ["OFFERITEMID"
-                            , "OFFER_ID"
-                            , "PRODUCT_ID"
-                            , "GROUPCODEID"
-                            , "ASSIGNEDTO"
-                            , "ITEMNAME"
-                            , "UNIT"
-                            , "PRICE"
-                            , "VAT"
-                            , "QUANTITY"
-                            , "OPTIONAL"
-                            , "ITEMPOSITION"
-                            , "ITEMSORT"
-                            , "DATE_NEW"
-                            , "USER_NEW"];
-
-        var colTypes = db.getColumnTypes("OFFERITEM", cols);
-
-        __offeritemInsertStatement(partsList.root, vars.get("$field.OFFERITEMID"), vars.get("$field.ITEMPOSITION"));
-
-        if(statements.length > 0)
-            db.inserts(statements);
-    }
+    var curr = vars.exists("$param.Currency_param") ? vars.get("$param.Currency_param") : "";
+    var relid = vars.exists("$param.RelationId_param") ? vars.get("$param.RelationId_param") : "";
     
+    var oiUtils = new OfferItemUtils(vars.get("$field.OFFER_ID"));
+    oiUtils.insertPartsList(vars.get("$field.PRODUCT_ID"), vars.get("$field.OFFERITEMID"), curr, relid);
+    oiUtils.reOrgItems();
     
     //update offer price
     var cols = ["NET", "VAT"];
@@ -51,50 +25,4 @@ if(oid != "")
     db.updateData("OFFER", cols, colTypes, vals, "OFFERID = '" + oid + "'");
     
     neon.refresh("Offer_entity");
-}
-
-function __offeritemInsertStatement(pPartsListObj, pAssignedTo, pPos)
-{
-    for(var i = 0; i < pPartsListObj.ids.length; i++)
-    {
-        var p2pid = pPartsListObj.ids[i];
-        var newid = util.getNewUUID();
-
-        var Prod2prodObj = partsList[p2pid];
-        var prodid = partsList[p2pid].prodid;
-
-        var curr = vars.exists("$param.Currency_param") ? vars.get("$param.Currency_param") : "";
-        var relid = vars.exists("$param.RelationId_param") ? vars.get("$param.RelationId_param") : "";
-
-        var ProdDetails = pUtils.getProductDetails(prodid, { currency: curr, quantity: Prod2prodObj.quantity, relationId: relid } )
-
-        var price = "";
-        var vat = "";
-        if(Prod2prodObj.takeprice == "Y" && ProdDetails.PriceListToUse != null)
-        {
-            price = ProdDetails.PriceListToUse.price;
-            vat = ProdDetails.PriceListToUse.vat;
-        }
-
-        var vals =  [newid
-                    , oid
-                    , prodid
-                    , ProdDetails.groupCode
-                    , pAssignedTo
-                    , ProdDetails.productName
-                    , ProdDetails.unit
-                    , price
-                    , vat
-                    , Prod2prodObj.quantity
-                    , Prod2prodObj.optional
-                    , Prod2prodObj.pos //TODO: Pos
-                    , "" //TODO: Itemsort
-                    , datetime.date()
-                    , vars.get("$sys.user")
-                     ];
-
-        statements.push(["OFFERITEM", cols, colTypes, vals]);
-
-        __offeritemInsertStatement(partsList[p2pid], newid);
-    }
 }
\ No newline at end of file
diff --git a/entity/Offeritem_entity/onDBUpdate.js b/entity/Offeritem_entity/onDBUpdate.js
index 15e362e1097..2634bf71937 100644
--- a/entity/Offeritem_entity/onDBUpdate.js
+++ b/entity/Offeritem_entity/onDBUpdate.js
@@ -2,7 +2,7 @@ import("system.logging");
 import("system.vars");
 import("system.db");
 import("system.neon");
-import("Offer_lib");
+import("OfferOrder_lib");
 
 var oid = vars.get("$field.OFFER_ID");
 if(oid != "")
diff --git a/entity/Offeritem_entity/orderClauseProcess.js b/entity/Offeritem_entity/orderClauseProcess.js
new file mode 100644
index 00000000000..8eebb296c78
--- /dev/null
+++ b/entity/Offeritem_entity/orderClauseProcess.js
@@ -0,0 +1,3 @@
+import("system.result");
+
+result.object( {"OFFERITEM.ITEMSORT": "up"} );
\ No newline at end of file
diff --git a/entity/Prod2prod_entity/Prod2prod_entity.aod b/entity/Prod2prod_entity/Prod2prod_entity.aod
index 837bb6c123f..2299428d6bb 100644
--- a/entity/Prod2prod_entity/Prod2prod_entity.aod
+++ b/entity/Prod2prod_entity/Prod2prod_entity.aod
@@ -3,17 +3,18 @@
   <name>Prod2prod_entity</name>
   <majorModelMode>DISTRIBUTED</majorModelMode>
   <externalOpenAction>%aditoprj%/entity/Prod2prod_entity/externalOpenAction.js</externalOpenAction>
+  <recordContainerType>JDITO</recordContainerType>
   <alias>Data_alias</alias>
   <jDitoRecordAlias>Data_alias</jDitoRecordAlias>
   <fields>
     <element>UID</element>
+    <element>PARENTID</element>
+    <element>PROD2PRODID</element>
     <element>DEST_ID</element>
     <element>SOURCE_ID</element>
     <element>QUANTITY</element>
     <element>OPTIONAL</element>
     <element>TAKEPRICE</element>
-    <element>POS</element>
-    <element>PARENTID</element>
   </fields>
   <contentProcess>%aditoprj%/entity/Prod2prod_entity/contentProcess.js</contentProcess>
   <isPageable v="false" />
@@ -21,7 +22,6 @@
   <onInsert>%aditoprj%/entity/Prod2prod_entity/onInsert.js</onInsert>
   <onUpdate>%aditoprj%/entity/Prod2prod_entity/onUpdate.js</onUpdate>
   <onDelete>%aditoprj%/entity/Prod2prod_entity/onDelete.js</onDelete>
-  <recordContainerType>JDITO</recordContainerType>
   <entityFields>
     <entityIncomingField>
       <name>#INCOMING</name>
@@ -116,16 +116,16 @@
       <triggerRecalculation v="true" />
       <description>PARAMETER</description>
     </entityParameter>
-    <entityField>
-      <name>POS</name>
-      <fieldName>POS</fieldName>
-      <caption>Pos</caption>
-    </entityField>
     <entityField>
       <name>PARENTID</name>
       <fieldName>PARENTID</fieldName>
       <caption>Parent</caption>
     </entityField>
+    <entityField>
+      <name>PROD2PRODID</name>
+      <fieldName>PROD2PRODID</fieldName>
+      <valueProcess>%aditoprj%/entity/Prod2prod_entity/entityfields/prod2prodid/valueProcess.js</valueProcess>
+    </entityField>
   </entityFields>
   <linkInformation>
     <linkInformation>
diff --git a/entity/Prod2prod_entity/contentProcess.js b/entity/Prod2prod_entity/contentProcess.js
index 922c7492f54..912ab1f560d 100644
--- a/entity/Prod2prod_entity/contentProcess.js
+++ b/entity/Prod2prod_entity/contentProcess.js
@@ -1,6 +1,7 @@
 import("system.result");
 import("system.vars");
 import("system.db");
+import("system.util");
 import("Product_lib");
 
 var prodid = vars.exists("$param.ProductId_param") 
@@ -8,7 +9,6 @@ var prodid = vars.exists("$param.ProductId_param")
 
 if(prodid != "")
 {
-    var p2pUtils = new Prod2prodUtils();
-    
-    result.object(p2pUtils.getSubordinatedData2DArray(prodid));
+    var p2pUtils = new Prod2ProdUtils(prodid);
+    result.object(p2pUtils.getPartsListForRecordContainer());
 }
\ No newline at end of file
diff --git a/entity/Prod2prod_entity/entityfields/prod2prodid/valueProcess.js b/entity/Prod2prod_entity/entityfields/prod2prodid/valueProcess.js
new file mode 100644
index 00000000000..5be18c39832
--- /dev/null
+++ b/entity/Prod2prod_entity/entityfields/prod2prodid/valueProcess.js
@@ -0,0 +1,7 @@
+import("system.util");
+import("system.vars");
+import("system.result");
+import("system.neon");
+
+if(vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_NEW)
+    result.string(util.getNewUUID());
\ No newline at end of file
diff --git a/entity/Prod2prod_entity/entityfields/source_id/onValidation.js b/entity/Prod2prod_entity/entityfields/source_id/onValidation.js
index 09a093be99f..e69de29bb2d 100644
--- a/entity/Prod2prod_entity/entityfields/source_id/onValidation.js
+++ b/entity/Prod2prod_entity/entityfields/source_id/onValidation.js
@@ -1,29 +0,0 @@
-//import("system.vars");
-//import("system.result");
-//import("system.db");
-//import("system.neon");
-//import("Keyword_lib");
-//import("Product_lib");
-//
-//var kwUtils = new KeywordUtils();
-//
-//var condition = "";
-//if(vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_EDIT || vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_NEW)
-//{
-//    var prodid = vars.get("$field.DEST_ID");
-//    var excludeableProds = [prodid];
-//    
-//    var p2pUtils = new Prod2prodUtils();
-//    excludeableProds = excludeableProds.concat(p2pUtils.getSubordinatedProdIds(prodid));
-//    
-//    condition += " where PRODUCTID not in ('" + excludeableProds.join("','") + "')";
-//}
-//
-//var prods = db.table("select PRODUCTID, GROUPCODEID, PRODUCTNAME, PRODUCTCODE from PRODUCT " + condition);
-//var res = [];
-//for(var i = 0; i < prods.length; i++)
-//{
-//    res.push([prods[i][0], kwUtils.getViewValue("GROUPCODE", prods[i][1]) + " / " + prods[i][2] + " / " + prods[i][3]]);
-//}
-//
-//result.object(res);
\ No newline at end of file
diff --git a/entity/Prod2prod_entity/entityfields/source_id/possibleItemsProcess.js b/entity/Prod2prod_entity/entityfields/source_id/possibleItemsProcess.js
index bee5aec9a2b..5f303b27235 100644
--- a/entity/Prod2prod_entity/entityfields/source_id/possibleItemsProcess.js
+++ b/entity/Prod2prod_entity/entityfields/source_id/possibleItemsProcess.js
@@ -2,6 +2,7 @@ import("system.vars");
 import("system.result");
 import("system.db");
 import("system.neon");
+import("system.logging");
 import("Keyword_lib");
 import("Product_lib");
 
@@ -13,9 +14,14 @@ if(vars.get("$sys.operatingstate") == neon.OPERATINGSTATE_EDIT || vars.get("$sys
     var prodid = vars.get("$field.DEST_ID");
     var excludeableProds = [prodid];
     
-    var p2pUtils = new Prod2prodUtils();
-    excludeableProds = excludeableProds.concat(p2pUtils.getSubordinatedProdIds(prodid));
-    excludeableProds = excludeableProds.concat(p2pUtils.getSupervisedProdIds(prodid));
+    var p2pUtils = new Prod2ProdUtils(prodid);
+    
+    logging.log("partslist:" + p2pUtils.getPartsListProdIds());
+    logging.log("parentlist:" + p2pUtils.getParentProdIds());
+    
+    
+    excludeableProds = excludeableProds.concat(p2pUtils.getPartsListProdIds());
+    excludeableProds = excludeableProds.concat(p2pUtils.getParentProdIds());
     
     condition += " where PRODUCTID not in ('" + excludeableProds.join("','") + "')";
 }
diff --git a/process/Offer_lib/Offer_lib.aod b/process/OfferOrder_lib/OfferOrder_lib.aod
similarity index 76%
rename from process/Offer_lib/Offer_lib.aod
rename to process/OfferOrder_lib/OfferOrder_lib.aod
index ae2ae4230f7..54e028a23f6 100644
--- a/process/Offer_lib/Offer_lib.aod
+++ b/process/OfferOrder_lib/OfferOrder_lib.aod
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <process xmlns="http://www.adito.de/2018/ao/Model" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VERSION="1.1.7" xsi:schemaLocation="http://www.adito.de/2018/ao/Model adito://models/xsd/process/1.1.7">
-  <name>Offer_lib</name>
+  <name>OfferOrder_lib</name>
   <majorModelMode>DISTRIBUTED</majorModelMode>
-  <process>%aditoprj%/process/Offer_lib/process.js</process>
+  <process>%aditoprj%/process/OfferOrder_lib/process.js</process>
 </process>
diff --git a/process/OfferOrder_lib/process.js b/process/OfferOrder_lib/process.js
new file mode 100644
index 00000000000..67d8e8a9dfa
--- /dev/null
+++ b/process/OfferOrder_lib/process.js
@@ -0,0 +1,619 @@
+import("system.translate");
+import("system.db");
+import("system.eMath");
+import("system.tools");
+import("Util_lib");
+import("Product_lib");
+
+function OfferUtils()
+{
+    var that = this;
+    /**
+     * Delivers the next valid offer number (has to be unique)
+     * 
+     * @return {String} next valid offer number
+     */
+    this.getNextOfferNumber = function()
+    {
+        var JdUtils = new JDitoUtils();
+        return JdUtils.getNextUniqueNumber("OFFERCODE", "OFFER");
+    }
+    
+    /**
+     * Checks if the passed offer number is valid (has to be unique)
+     * 
+     * @param {String} pOfferNumber offer number to check
+     * 
+     * @return {boolean} passed number is valid
+     */
+    this.validateOfferNumber = function(pOfferNumber)
+    {
+        var JdUtils = new JDitoUtils();
+        return JdUtils.validateUniqueNumber(pOfferNumber, "OFFERCODE", "OFFER");
+    }
+    
+    this.getOfferNumberValidationFailString = function()
+    {
+        return translate.text("The offer number already exists!");
+    }
+    
+    this.getOfferNetAndVAT = function(pOfferId, pOfferitemIdsToDel)
+    {
+        //TODO: Bessere Möglichkeit als per SQL die zugehörigen Posten aus der DB zu holen?
+        var sum = 0;
+        var vat = 0;
+        var oiUtils = new OfferItemUtils(pOfferId);
+        
+        var condition = "where OFFER_ID = '" + pOfferId + "'";
+        if(pOfferitemIdsToDel != undefined)
+            condition += " and OFFERITEMID not in ('" + pOfferitemIdsToDel.join("','") + "')";
+        
+        var offerItems = db.table("select QUANTITY, PRICE, DISCOUNT, VAT, OPTIONAL "
+                                 + "from OFFERITEM " + condition);
+        
+        for(var i = 0; i < offerItems.length; i++)
+        {
+            sum += oiUtils.getItemSum(offerItems[i][0], offerItems[i][1], offerItems[i][2], offerItems[i][4]);
+            vat += oiUtils.getItemVAT(offerItems[i][0], offerItems[i][1], offerItems[i][2], offerItems[i][3], offerItems[i][4]);
+        }
+        
+        return [sum, vat];
+    }
+    
+    this.isEditable = function(pStatus)
+    {
+        
+        //TODO: Administrator darf immer ändern, warten auf neue Berechtigungslogik?
+        
+        //Offer should be editable if offer state not equals "Sent", "Won" or "Lost"
+        return pStatus != "2" && pStatus != "3" && pStatus != "4";
+    }
+}
+
+/**OfferItemUtils
+ *
+ * Instance class that provides methods for dealing with offer / order items.
+ * Inherits method from abstract class ItemUtils.
+ * For documentation, see class ItemUtils.
+ *
+ ******************************************************************************/
+function OfferItemUtils(pOfferId) //constructor
+{
+    ItemUtils.apply(this, [pOfferId]);
+    OfferItemUtils.prototype = Object.create(ItemUtils.prototype);
+    OfferItemUtils.prototype.constructor = OfferItemUtils;
+}
+
+OfferItemUtils.prototype.initItemTree = function()
+{
+    ItemUtils.prototype.initItemTree.apply(this, ["OFFERITEM", "OFFER_ID"]);
+}
+
+OfferItemUtils.prototype.getItemSum = function(pQuantity, pPrice, pDiscount, pOptional)
+{    
+    return ItemUtils.prototype.getItemSum.apply(this, [pQuantity, pPrice, pDiscount, pOptional]);
+}
+
+OfferItemUtils.prototype.getItemVAT = function(pQuantity, pPrice, pDiscount, pVAT, pOptional)
+{   
+    return ItemUtils.prototype.getItemVAT.apply(this, [pQuantity, pPrice, pDiscount, pVAT, pOptional]);
+}
+
+OfferItemUtils.prototype.roundPrice = function(pPrice)
+{
+    return ItemUtils.prototype.roundPrice.apply(this, [pPrice]);
+}
+
+OfferItemUtils.prototype.insertPartsList = function(pProductId, pAssignedTo, pCurrency, pRelationId)
+{
+    this.initItemTree();
+    
+    var cols =  ["OFFERITEMID"
+                , "OFFER_ID"
+                , "PRODUCT_ID"
+                , "GROUPCODEID"
+                , "ASSIGNEDTO"
+                , "ITEMNAME"
+                , "UNIT"
+                , "PRICE"
+                , "VAT"
+                , "QUANTITY"
+                , "OPTIONAL"
+                , "ITEMPOSITION"
+                , "ITEMSORT"
+                , "DATE_NEW"
+                , "USER_NEW"];
+
+    var table = "OFFERITEM";
+
+    return ItemUtils.prototype.insertPartsList.apply(this, [cols, table, pProductId, pAssignedTo, pCurrency, pRelationId]);
+}
+
+OfferItemUtils.prototype.deletePartsList = function(pItemId)
+{
+    this.initItemTree();
+    
+    return ItemUtils.prototype.deletePartsList.apply(this, [pItemId, "OFFERITEM"]);
+}
+
+OfferItemUtils.prototype.getNextItemSort = function(pIds)
+{
+    this.initItemTree();
+
+    return ItemUtils.prototype.getNextItemSort.apply(this, [pIds]);
+}
+
+OfferItemUtils.prototype.getNextItemPosition = function(pAssignedTo, pTree, pIds)
+{  
+    this.initItemTree();
+
+    return ItemUtils.prototype.getNextItemPosition.apply(this, [pAssignedTo, pTree, pIds]);
+}
+
+OfferItemUtils.prototype.reOrgItems = function()
+{
+    this.initItemTree();
+    
+    ItemUtils.prototype.reOrgItems.apply(this, ["OFFERITEM"]);
+}
+
+/*
+ * OfferItemUtils **************************************************************
+ */
+
+/**ItemUtils
+ *
+ * @abstract
+ * 
+ * Abstract class that provides methods for dealing with offer / order items.
+ * Do not instantiate!
+ * Create a subclass like OfferItemUtils above to use methods of this class.
+ * 
+ *******************************************************************************
+ */
+function ItemUtils(pOfferOrderId)
+{
+    if(this.constructor === ItemUtils)
+        throw new Error("Can't instantiate abstract class ItemUtils!");
+    
+    
+    this.offerOrderId = pOfferOrderId;
+    this.ItemTree = undefined;//object for dealing with hierarchical structure of Items, item position and item order
+    this.ItemIds = undefined;//contains all ItemIDs to save the order
+    
+    this._getItemTreeNodeObject = function(pAssignedTo, pPos, pItemSort)
+    {
+        return {
+            ids:[]
+            , assignedto: pAssignedTo
+            , pos: pPos
+            , itemsort: pItemSort
+        };
+    }
+    
+    /**
+     *Adds a node to an ItemTree Object. <br>
+     *If no ItemTree Object is passed, the class variable this.ItemTree will be used.
+     *
+     *@param {String} pItemId req UID of item
+     *@param {String} pAssignedTo opt Parent UID of item
+     *@param {Object} pTree opt ItemTree Object to append
+     *@param {[]} pIds opt pIds Array for all ItemIDs to save order
+     *
+     */
+    this._appendNode = function(pItemId, pAssignedTo, pTree, pIds)
+    {
+        if(pTree == undefined)
+            pTree = this.ItemTree;
+        if(pIds == undefined)
+            pIds = this.ItemIds;
+
+        pTree[pItemId] = this._getItemTreeNodeObject(pAssignedTo
+                                                    , this.getNextItemPosition(pAssignedTo, pTree, pIds)
+                                                    , this.getNextItemSort(pIds));
+        pIds.push(pItemId);
+
+        if(pAssignedTo != "")
+        {
+            pTree[pAssignedTo].ids.push(pItemId);
+        }
+    }
+    
+    /**
+     *Deletes a node from an ItemTree Object within its subnodes. <br>
+     *If no ItemTree Object is passed, the class variable this.ItemTree will be used.
+     *
+     *@param {String} pItemId req UID of item
+     *@param {Object} pTree opt ItemTree Object to delete from
+     *@param {[]} pIds opt pIds Array for all ItemIDs to save order
+     *
+     */
+    this._deleteNodes = function(pItemId, pTree, pIds)
+    {
+        if(pTree == undefined)
+            pTree = this.ItemTree;
+        if(pIds == undefined)
+            pIds = this.ItemIds;
+
+        __delete(pTree[pItemId]);
+
+        _deleteNode(pItemId);
+
+        function __delete(pNode)
+        {
+            for(var i = 0; i < pNode.ids.length; i++)
+            {
+                __delete(pTree[pNode.ids[i]]);
+                _deleteNode(pNode.ids[i]);
+            }
+        }
+        
+        function _deleteNode(pItemId)
+        {
+            //delete Object property
+            delete pTree[pItemId];
+
+            //delete id in global Array "ItemIds"
+            var idx = pIds.indexOf(pItemId);
+            if(idx > -1)
+                pIds.splice(idx, 1);
+
+            //delete id in Array from property "ids" of Tree
+            for(var oiid in pTree)
+            {
+                idx = pTree[oiid].ids.indexOf(pItemId);
+                if(idx > -1)
+                    pTree[oiid].ids.splice(idx, 1);
+            }
+        }
+    }
+    
+    /**
+     *Compares Positions & Sort of the passed ItemTree Object with the class variable this.ItemTree <br>
+     *and updates differences in database.
+     *
+     *
+     *@param {String} pTable req DB-Table of Items
+     *@param {Object} pCompTree req ItemTree Object to compare with this.ItemTree
+     *
+     */
+    this._updateReOrgItemChangesInDB = function(pTable, pCompTree)
+    {
+        var statements = [];
+        var cols = ["ITEMSORT", "ITEMPOSITION"];
+        var colTypes = db.getColumnTypes(pTable, cols);
+
+        var oiTree = this.ItemTree;
+        for(var oiid in pCompTree)
+        {
+            if(oiTree[oiid] != undefined)
+            {
+                //check if itemsort/pos has been changed
+                if(oiTree[oiid].itemsort != pCompTree[oiid].itemsort || oiTree[oiid].pos != pCompTree[oiid].pos)
+                {
+                    var vals = [pCompTree[oiid].itemsort, pCompTree[oiid].pos];
+                    statements.push([pTable, cols, colTypes, vals, pTable + "ID = '" + oiid + "'"]);
+                }
+            }
+        }
+
+        if(statements.length > 0)
+            db.updates(statements);
+    }
+}
+
+/**
+ *Initializes ItemTree Object for class variable this.ItemTree <br>
+ *with items already stored in database.
+ *
+ *@abstract
+ *
+ *@param {String} pTable req DB-Table of Items
+ *@param {String} pForeignKeyColumn req DB-Column that contains foreign key Items belong to
+ *
+ */
+ItemUtils.prototype.initItemTree = function(pTable, pForeignKeyColumn)
+{
+    if(this.ItemTree == undefined)
+    {
+        this.ItemTree = {};
+        this.ItemIds = [];
+
+        var data = db.table("select " + pTable + "ID, ASSIGNEDTO, ITEMPOSITION, ITEMSORT from " + pTable 
+                            + " where " + pForeignKeyColumn + " = '" + this.offerOrderId + "' order by ITEMSORT");
+
+        for(var i = 0; i < data.length; i++)
+        {
+            this.ItemTree[data[i][0]] = this._getItemTreeNodeObject(data[i][1], data[i][2], data[i][3]);
+            this.ItemIds.push(data[i][0]);
+        }
+
+        for(var oiid in this.ItemTree)
+        {
+            if(this.ItemTree[oiid].assignedto != "")
+                this.ItemTree[ this.ItemTree[oiid].assignedto ].ids.push(oiid);
+        }
+    }
+}
+
+ItemUtils.prototype.getItemSum = function(pQuantity, pPrice, pDiscount, pOptional)
+{    
+    pQuantity = pQuantity || 0;
+    pPrice = pPrice || 0;
+    pDiscount = pDiscount || 0;
+
+    return Number(pOptional) * parseFloat(pQuantity) * parseFloat(pPrice) * ((100 - parseFloat(pDiscount)) / 100);
+}
+
+ItemUtils.prototype.getItemVAT = function(pQuantity, pPrice, pDiscount, pVAT, pOptional)
+{   
+    pQuantity = pQuantity || 0;
+    pPrice = pPrice || 0;
+    pDiscount = pDiscount || 0;
+    pVAT = pVAT || 0;
+
+    return Number(pOptional) * parseFloat(pQuantity) * parseFloat(pPrice) * ((100 - parseFloat(pDiscount)) / 100) * (parseFloat(pVAT) / 100);
+}
+
+ItemUtils.prototype.roundPrice = function(pPrice)
+{
+    return eMath.roundDec(pPrice, 2, eMath.ROUND_HALF_UP);
+}
+
+/**
+* Inserts parts list of the passed product into database.
+* 
+* @abstract
+* 
+* @param {[]} pColumns req Array of Item DB Columns 
+* @param {String} pTable req DB Table of Items
+* @param {String} pProductId req UID of root product (selected product)
+* @param {String} pAssignedTo opt UID of parent item
+* @param {String} pCurrency opt currency for price list to use
+* @param {String} pRelationId opt relationid for price list to use (custom price list)
+* 
+* @return {[]} Array of inserted ItemIDs
+*/
+ItemUtils.prototype.insertPartsList = function(pColumns, pTable, pProductId, pAssignedTo, pCurrency, pRelationId)
+{
+    var insertedItemIds = [];
+    
+    //save address for this here to call methods in recursive sub function __itemInsertStatement
+    var self = this;
+    
+    if(pAssignedTo == undefined)
+        pAssignedTo = "";
+    if(pCurrency == undefined)
+        pCurrency = "";
+    if(pRelationId == undefined)
+        pRelationId = "";
+    
+    var rootProdId = pProductId;
+    if(rootProdId != "")
+    {
+        var pUtils = new ProductUtils();
+        
+        var p2pUtils = new Prod2ProdUtils(rootProdId);
+        var partsList = p2pUtils.getPartsListObject();
+        
+        var statements = [];
+        var colTypes = db.getColumnTypes(pTable, pColumns);
+
+        __itemInsertStatement(partsList.root, pAssignedTo, pCurrency, pRelationId);
+
+        if(statements.length > 0)
+            db.inserts(statements);
+    }
+    
+    return insertedItemIds;
+    
+    //recursive function for building item insert statements 
+    function __itemInsertStatement(pPartsListObj, pAssignedTo, pCurrency, pRelationId)
+    {
+        for(var i = 0; i < pPartsListObj.ids.length; i++)
+        {
+            var newid = util.getNewUUID();
+            self._appendNode(newid, pAssignedTo);
+            var pos = self.ItemTree[newid].pos;
+            var itemsort = self.ItemTree[newid].itemsort;
+            
+            var p2pid = pPartsListObj.ids[i];
+            var P2pObject = partsList[p2pid];
+            var prodid = partsList[p2pid].sourceid;
+            var ProductDetails = pUtils.getProductDetails(prodid, { currency: pCurrency, quantity: P2pObject.quantity, relationId: pRelationId } )
+
+            var price = "";
+            var vat = "";
+            if(P2pObject.takeprice == "Y" && ProductDetails.productId && ProductDetails.PriceListToUse)
+            {
+                price = ProductDetails.PriceListToUse.price;
+                vat = ProductDetails.PriceListToUse.vat;
+            }
+            
+            var vals =  [newid
+                        , self.offerOrderId
+                        , prodid
+                        , ProductDetails.groupCode
+                        , pAssignedTo
+                        , ProductDetails.productName
+                        , ProductDetails.unit
+                        , price
+                        , vat
+                        , P2pObject.quantity
+                        , P2pObject.optional
+                        , pos
+                        , itemsort
+                        , datetime.date()
+                        , vars.get("$sys.user")];
+
+            statements.push([pTable, pColumns, colTypes, vals]);
+            insertedItemIds.push(newid);
+
+            __itemInsertStatement(partsList[p2pid], newid);
+        }
+    }
+}
+
+/**
+* Deletes parts list of the passed item from database.
+* 
+* @abstract
+* 
+* @param {String} pItemId req UID of item that will be deleted
+* @param {String} pTable req DB Table of Items
+* 
+* @return {[]} Array of deleted ItemIDs
+*/
+ItemUtils.prototype.deletePartsList = function(pItemId, pTable)
+{
+    var deletedItemIds = [];
+        
+    //save address for this here to get class variables in recursive sub function __itemDeleteStatement
+    var self = this;
+    
+    var statements = [];
+
+    __itemDeleteStatement(pItemId);
+
+    if(statements.length > 0)
+        db.deletes(statements);
+
+    self._deleteNodes(pItemId);
+
+    return deletedItemIds;
+    
+    //recursive function for building item delete statements 
+    function __itemDeleteStatement(pItemId)
+    {
+        var itemsToDelete = self.ItemTree[pItemId].ids;
+
+        for(var i = 0; i < itemsToDelete.length; i++)
+        {
+            //unshift due to foreign key constraints (Delete hierarchically starting at the bottom)
+            statements.unshift([pTable, pTable + "ID = '" + itemsToDelete[i] + "'"]);
+            deletedItemIds.push(itemsToDelete[i]);
+            __itemDeleteStatement(itemsToDelete[i]);
+        }
+    }
+}
+
+/**
+* Delivers next item sort value by passed ItemID-Array. <br>
+* If no parameter is passed the class variable this.ItemIds will be used.
+* 
+* @abstract
+* 
+* @param {[]} pIds opt Array for all ItemIDs to save order
+* 
+* @return {String} Next item sort value
+*/
+ItemUtils.prototype.getNextItemSort = function(pIds)
+{
+    if(pIds == undefined)
+        pIds = this.ItemIds;
+
+    return (pIds.length + 1).toString();
+}
+
+/**
+* Delivers next item position value by passed ItemTree Object. <br>
+* If no parameter is passed the class variable this.ItemTree will be used. <br>
+* <br>
+* Item position is generated according to the following pattern: <br>
+* 1 (Parent ItemID (AssignedTo) is empty) <br>
+*  1.1 (Parent ItemID (AssignedTo) = ItemID of Pos 1) <br>
+*  1.2 <br>
+*  1.2.1 (Parent ItemID (AssignedTo) = ItemID of Pos 1.2) <br>
+*  ... <br>
+* 2 <br>
+*  2.1 <br>
+*  2.1.1 <br>
+*  ... <br>
+* 3 <br>
+*  ... <br>
+*  
+* @abstract
+* 
+* @param {String} pAssignedTo Parent ItemID
+* @param {Object} pTree opt ItemTree Object to get next position from
+* @param {[]} pIds opt Array for all ItemIDs to save order
+* 
+* @return {String} Next item position value
+*/
+ItemUtils.prototype.getNextItemPosition = function(pAssignedTo, pTree, pIds)
+{  
+    if(pAssignedTo == undefined)
+        pAssignedTo = "";
+    if(pTree == undefined)
+        pTree = this.ItemTree;
+    if(pIds == undefined)
+        pIds = this.ItemIds;
+
+    var maxPos = ["0"];
+
+    if(pAssignedTo != "")
+    {
+        if(pTree[pAssignedTo].ids.length < 1) //first item in this level
+        {
+            maxPos = pTree[pAssignedTo].pos.split(".");
+            maxPos = maxPos.concat(["0"]); //next level pos
+        }
+        else
+        {
+            var childIds = pTree[pAssignedTo].ids;
+            maxPos = pTree[childIds[childIds.length-1]].pos.split(".");
+        }
+    }
+    else
+    {
+        if(pIds.length > 0)
+        {
+            maxPos[0] = this.ItemTree[pIds[pIds.length-1]].pos;
+        }    
+    }
+
+    maxPos[maxPos.length-1] = (Number(maxPos[maxPos.length-1]) + 1).toString();
+
+    return maxPos.join(".");
+}
+
+/**
+* Reorganizes hierarchical structure of Items, item position and item order. <br>
+* Changes will be updated in database. <br>
+* This function has to be executed after items have been added or deleted. <br>
+* 
+* @abstract
+* 
+* @param {String} pTable req DB Table of Items
+* 
+*/
+ItemUtils.prototype.reOrgItems = function(pTable)
+{
+    //save address for this here to get methods in recursive sub function __reorg
+    var self = this;
+    
+    var newTree = {};
+    var newIds = [];
+
+    __reorg(self.ItemIds, "");
+
+    self._updateReOrgItemChangesInDB(pTable, newTree);
+    
+    //recursive function to build new ItemTree Structure
+    function __reorg(pIds, pAssignedTo)
+    {
+        for(var i = 0; i < pIds.length; i++)
+        {
+            if(newTree[pIds[i]] == undefined)
+            {
+                //build new tree
+                self._appendNode(pIds[i], pAssignedTo, newTree, newIds);
+
+                __reorg(self.ItemTree[pIds[i]].ids, pIds[i]);
+            }
+        }
+    }
+}
+/*
+ * ItemUtils *******************************************************************
+ */
\ No newline at end of file
diff --git a/process/Offer_lib/process.js b/process/Offer_lib/process.js
deleted file mode 100644
index e83df959577..00000000000
--- a/process/Offer_lib/process.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import("system.translate");
-import("system.db");
-import("system.eMath");
-import("system.tools");
-import("Util_lib");
-
-function OfferUtils(){
-    var that = this;
-    /**
-     * Delivers the next valid offer number (has to be unique)
-     * 
-     * @result {String} next valid offer number
-     */
-    this.getNextOfferNumber = function(){
-        var JdUtils = new JDitoUtils();
-        return JdUtils.getNextUniqueNumber("OFFERCODE", "OFFER");
-    }
-    
-    /**
-     * Checks if the passed offer number is valid (has to be unique)
-     * 
-     * @param {String} pOfferNumber offer number to check
-     * 
-     * @result {boolean} passed number is valid
-     */
-    this.validateOfferNumber = function(pOfferNumber){
-        var JdUtils = new JDitoUtils();
-        return JdUtils.validateUniqueNumber(pOfferNumber, "OFFERCODE", "OFFER");
-    }
-    
-    this.getOfferNumberValidationFailString = function(){
-        return translate.text("The offer number already exists!");
-    }
-    
-    this.getOfferNetAndVAT = function(pOfferId, pOfferitemIdToDel){
-        //TODO: Bessere Möglichkeit als per SQL die zugehörigen Posten aus der DB zu holen?
-        var sum = 0;
-        var vat = 0;
-        var oiUtils = new OfferItemUtils();
-        
-        var condition = "where OFFER_ID = '" + pOfferId + "'";
-        if(pOfferitemIdToDel != undefined)
-            condition += " and OFFERITEMID <> '" + pOfferitemIdToDel + "'";
-        
-        var offerItems = db.table("select QUANTITY, PRICE, DISCOUNT, VAT, OPTIONAL "
-                                 + "from OFFERITEM " + condition);
-        
-        for(var i = 0; i < offerItems.length; i++){
-            sum += oiUtils.getItemSum(offerItems[i][0], offerItems[i][1], offerItems[i][2], offerItems[i][4]);
-            vat += oiUtils.getItemVAT(offerItems[i][0], offerItems[i][1], offerItems[i][2], offerItems[i][3], offerItems[i][4]);
-        }
-        
-        return [sum, vat];
-    }
-    
-    this.isEditable = function(pStatus){
-        
-        //TODO: Administrator darf immer ändern, warten auf neue Berechtigungslogik?
-        
-        //Offer should be editable if offer state not equals "Sent", "Won" or "Lost"
-        return pStatus != "2" && pStatus != "3" && pStatus != "4";
-    }
-}
-
-function OfferItemUtils(){
-    
-    this.getItemSum = function(pQuantity, pPrice, pDiscount, pOptional){
-        
-        pQuantity = pQuantity || 0;
-        pPrice = pPrice || 0;
-        pDiscount = pDiscount || 0;
-        
-        return Number(pOptional) * parseFloat(pQuantity) * parseFloat(pPrice) * ((100 - parseFloat(pDiscount)) / 100);
-    }
-    
-    this.getItemVAT = function(pQuantity, pPrice, pDiscount, pVAT, pOptional){
-        
-        pQuantity = pQuantity || 0;
-        pPrice = pPrice || 0;
-        pDiscount = pDiscount || 0;
-        pVAT = pVAT || 0;
-        
-        return Number(pOptional) * parseFloat(pQuantity) * parseFloat(pPrice) * ((100 - parseFloat(pDiscount)) / 100) * (parseFloat(pVAT) / 100);
-    }
-    
-    this.roundPrice = function(pPrice){
-        return eMath.roundDec(pPrice, 2, eMath.ROUND_HALF_UP);
-    }
-    
-    this.insertPartsList = function()
-    {
-        
-        
-        
-    }
-}
\ No newline at end of file
diff --git a/process/Product_lib/process.js b/process/Product_lib/process.js
index 11c08a74625..161c665d32d 100644
--- a/process/Product_lib/process.js
+++ b/process/Product_lib/process.js
@@ -5,6 +5,12 @@ import("system.db");
 import("system.vars");
 import("Util_lib");
 
+/**
+ * Class containing utility functions for products
+ * 
+ * @class
+ *
+ */
 function ProductUtils()
 {
     var that = this;
@@ -68,13 +74,63 @@ function ProductUtils()
     }
     
     /**
-     * Delivers the stock
+     * Delivers metadata and price lists of the passed product. 
+     * If parameter "pPriceListFilter" is passed valid price lists and the 
+     * current price list to use for offer/order are delivered.
      * 
-     * @param pPid {String} ProductID
+     * @param pPid {String} req ProductID
+     * @param pPriceListFilter {Object} opt { currency: "currencyValue", quantity: "quantityValue", relationId: "relationIdValue (for custom price lists)" }
      * 
-     * @example productUtils.getStockCount(vars.get("$field.PRODUCTID"))
+     * @example //Product_entity, Field: PRODUCT_ID, Process: onValueChange
+     *          var pid = ProcessHandlingUtil.getOnValidationValue(vars.get("$field.PRODUCT_ID"));
+     *          var curr = vars.exists("$param.Currency_param") ? vars.get("$param.Currency_param") : "";
+     *          var relid = vars.exists("$param.RelationId_param") ? vars.get("$param.RelationId_param") : "";
+     *          var pUtils = new ProductUtils();
+     *          var PriceListFilter = { currency: curr, quantity: vars.get("$field.QUANTITY"), relationId: relid };
+     *          var ProductDetails = pUtils.getProductDetails(pid, PriceListFilter);
      * 
-     * @result {String} stock count
+     * @return {Object} { <br>
+     *                   productId: "productid" <br>
+     *                   , productName: "product name" <br>
+     *                   , groupCode: "keyvalue of keyword 'GROUPCODE'" <br>
+     *                   , unit: "keyvalue of keyword 'UNIT'" <br>
+     *                   , PriceLists: {$pricelistid$ { <br>
+     *                          priceListId: "pricelistid" <br>
+     *                          , relationId: "relationid" when filled -> custom price list <br>
+     *                          , priceList: "keyvalue of keyword 'PRICELIST'" <br>
+     *                          , price: "price" <br>
+     *                          , vat: "vat" <br>
+     *                          , validFrom: TIMESTAMP <br>
+     *                          , validTo: TIMESTAMP <br>
+     *                          , buySell: "SP" / "PP" <br>
+     *                          , fromQuantity: "fromquantity" <br>
+     *                          , currency: "keyvalue of keyword 'CURRENCY'" <br>
+     *                      } } <br>
+     *                   , CurrentValidPriceLists: {$pricelistid$ { <br>
+     *                          priceListId: "pricelistid" <br>
+     *                          , relationId: "relationid" when filled -> custom price list <br>
+     *                          , priceList: "keyvalue of keyword 'PRICELIST'" <br>
+     *                          , price: "price" <br>
+     *                          , vat: "vat" <br>
+     *                          , validFrom: TIMESTAMP <br>
+     *                          , validTo: TIMESTAMP <br>
+     *                          , buySell: "SP" / "PP" <br>
+     *                          , fromQuantity: "fromquantity" <br>
+     *                          , currency: "keyvalue of keyword 'CURRENCY'" <br>
+     *                      } } <br>
+     *                   , PriceListToUse: {$pricelistid$ { <br>
+     *                          priceListId: "pricelistid" <br>
+     *                          , relationId: "relationid" when filled -> custom price list <br>
+     *                          , priceList: "keyvalue of keyword 'PRICELIST'" <br>
+     *                          , price: "price" <br>
+     *                          , vat: "vat" <br>
+     *                          , validFrom: TIMESTAMP <br>
+     *                          , validTo: TIMESTAMP <br>
+     *                          , buySell: "SP" / "PP" <br>
+     *                          , fromQuantity: "fromquantity" <br>
+     *                          , currency: "keyvalue of keyword 'CURRENCY'" <br>
+     *                      } } <br>
+     *               }
      */
     this.getProductDetails = function( pPid, pPriceListFilter )
     {
@@ -103,7 +159,9 @@ function ProductUtils()
             && pPriceListFilter.quantity != undefined && pPriceListFilter.quantity != "")
         {
             validPriceLists = true;
-            var colsPricelistValid = ["validPP.PRODUCTPRICEID", "validPP.RELATION_ID", "validPP.PRICELIST", "validPP.PRICE", "validPP.VAT"];
+            var colsPricelistValid = ["validPP.PRODUCTPRICEID", "validPP.RELATION_ID", "validPP.PRICELIST", "validPP.PRICE", "validPP.VAT"
+                            , "validPP.VALID_FROM", "validPP.VALID_TO", "validPP.BUYSELL", "validPP.FROMQUANTITY", "validPP.CURRENCY"];
+                        
             cols = cols.concat(colsPricelistValid);
             joins.push(" left join PRODUCTPRICE validPP on validPP.PRODUCT_ID = PRODUCTID "
                         + " and validPP.CURRENCY = " + pPriceListFilter.currency 
@@ -144,18 +202,7 @@ function ProductUtils()
             var colIdx = colsProduct.length;
             if(ProductData[i][colIdx] != "" && ProductDetails.PriceLists[ProductData[i][colIdx]] == undefined) //Pricelist found
             {
-                ProductDetails.PriceLists[ProductData[i][colIdx]] = {
-                    priceListId: ProductData[i][colIdx++]
-                    , relationId: ProductData[i][colIdx++]
-                    , priceList: ProductData[i][colIdx++]
-                    , price: ProductData[i][colIdx++]
-                    , vat: ProductData[i][colIdx++]
-                    , validFrom: ProductData[i][colIdx++]
-                    , validTo: ProductData[i][colIdx++]
-                    , buySell: ProductData[i][colIdx++]
-                    , fromQuantity: ProductData[i][colIdx++]
-                    , currency: ProductData[i][colIdx++]
-                }
+                ProductDetails.PriceLists[ProductData[i][colIdx]] = _getPriceListObject();
             }
            
             //Pricelist (currently valid)
@@ -164,13 +211,7 @@ function ProductUtils()
             {
                 if(ProductData[i][colIdx] != "" && ProductDetails.CurrentValidPriceLists[ProductData[i][colIdx]] == undefined) //Pricelist found
                 {
-                    ProductDetails.CurrentValidPriceLists[ProductData[i][colIdx]] = {
-                        priceListId: ProductData[i][colIdx++]
-                        , relationId: ProductData[i][colIdx++]
-                        , priceList: ProductData[i][colIdx++]
-                        , price: ProductData[i][colIdx++]
-                        , vat: ProductData[i][colIdx++]
-                    }
+                    ProductDetails.CurrentValidPriceLists[ProductData[i][colIdx]] = _getPriceListObject();
                 }
             }
         }
@@ -180,12 +221,28 @@ function ProductUtils()
             
         return ProductDetails;
         
+        function _getPriceListObject()
+        {
+            return {
+                    priceListId: ProductData[i][colIdx++]
+                    , relationId: ProductData[i][colIdx++]
+                    , priceList: ProductData[i][colIdx++]
+                    , price: ProductData[i][colIdx++]
+                    , vat: ProductData[i][colIdx++]
+                    , validFrom: ProductData[i][colIdx++]
+                    , validTo: ProductData[i][colIdx++]
+                    , buySell: ProductData[i][colIdx++]
+                    , fromQuantity: ProductData[i][colIdx++]
+                    , currency: ProductData[i][colIdx++]
+            };
+        }
         
+        //price list to use for offer/order
         function _getPriceListToUse(pPriceLists, pPriceListFilter)
         {
             for(var list in pPriceLists)
             {
-                //customer specific price (defined in Org -> Conditions)
+                //custom price (defined in Org -> Conditions)
                 if(pPriceListFilter.relationId != "" && pPriceListFilter.relationId == pPriceLists[list].relationId)
                 {
                     return pPriceLists[list];
@@ -207,21 +264,59 @@ function ProductUtils()
         }
     }
     
-    //pPriceList Object beschreiben
+    /**
+     * Checks if there is already an existing price list identical to the passed price list 
+     * 
+     * @param pPid {String} req ProductID
+     * @param pPriceList {Object} req { <br>
+     *                                  priceList: "keyvalue of keyword 'PRICELIST'" <br>
+     *                                  , validFrom: TIMESTAMP <br>
+     *                                  , validTo: TIMESTAMP <br>
+     *                                  , buySell: "SP" / "PP" <br>
+     *                                  , fromQuantity: "fromquantity" <br>
+     *                                  , currency: "keyvalue of keyword 'CURRENCY'" <br>
+     *                             }
+     * 
+     * @example //Productprice_entity, Field: PRICELIST, Process: onValidation
+     *          var pUtils = new ProductUtils();
+     *          var priceList = {
+     *                          priceList: ProcessHandlingUtil.getOnValidationValue(vars.get("$field.PRICELIST"))
+     *                          , fromQuantity: vars.get("$field.FROMQUANTITY")
+     *                          , buySell: vars.get("$field.BUYSELL")
+     *                          , currency: vars.get("$field.CURRENCY")
+     *                          , validFrom: vars.get("$field.VALID_FROM")
+     *                          , validTo: vars.get("$field.VALID_TO")
+     *                      };
+     *
+     *          var identicalPriceList = pUtils.checkForIndenticalPriceLists(vars.get("$field.PRODUCT_ID"), priceList);
+     *          if(identicalPriceList != null)
+     *          {
+     *              result.string(translate.text("Identical price list found!"));
+     *          }
+     * 
+     * @return {Object | null} null if no identical price list was found, otherwise the found price list
+     */
     this.checkForIndenticalPriceLists = function(pPid, pPriceList)
     {
         var PriceLists = that.getProductDetails(pPid).PriceLists;
         
         for(var pricelist in PriceLists)
         {
-            
+            //equal price list
+            //equal fromquantity
+            //equal currency
+            //equal pp/sp
             if( pPriceList.priceList == PriceLists[pricelist].priceList 
                 && pPriceList.fromQuantity == PriceLists[pricelist].fromQuantity 
                 && pPriceList.buySell == PriceLists[pricelist].buySell
                 && pPriceList.currency == PriceLists[pricelist].currency )
             
             {
-                
+                //identical validFrom & validTo
+                // OR NOT [ validFrom_new <= validFrom & validTo_new <= validTo
+                //        OR validFrom_new >= validFrom & validTo_new >= validTo
+                //        OR validFrom_new < validFrom & validTo_new > validTo
+                // ]
                 if( pPriceList.validFrom == PriceLists[pricelist].validFrom && pPriceList.validTo == PriceLists[pricelist].validTo
                     || ! ( pPriceList.validFrom <= PriceLists[pricelist].validFrom && pPriceList.validTo <= PriceLists[pricelist].validTo
                            || pPriceList.validFrom >= PriceLists[pricelist].validFrom && pPriceList.validTo >= PriceLists[pricelist].validTo
@@ -234,193 +329,224 @@ function ProductUtils()
             }
         }
         
-        return null;
-        
-        //equal price list
-        
-            //identical fromquantity
-            //identical currency
-            //identical pp/sp
-            
-                //identical validFrom & validTo
-                 // OR NOT [ validFrom_new <= validFrom & validTo_new <= validTo
-                 //        OR validFrom_new >= validFrom & validTo_new >= validTo
-                 //        OR validFrom_new < validFrom & validTo_new > validTo
-                 // ]
-                 
+        //no identical price list found
+        return null;        
     }
-    
 }
 
-function Prod2prodUtils()
+/**
+ * Class containing utility functions for Prod2Prod (Parts list)
+ * 
+ * @param pProductId req ProductID
+ * 
+ * @class
+ *
+ */
+function Prod2ProdUtils(pProductId)
 {
-    var data = getProd2prodData();
+    var data;
     
-    function getProd2prodData()
+    /**
+     * Delivers an Object containing parts list structure for passed product "pProductId" (Constructor parameter)
+     * 
+     * @return {Object} { $prod2prodid$ { <br>
+     *                       ids: [ Array containing child Prod2ProdIds for passed product "pProductId" (Constructor parameter) ] <br>
+     *                       , rowdata: [ "PROD2PRODID", "DEST_ID", "SOURCE_ID", "QUANTITY", "OPTIONAL", "TAKEPRICE" ] from DB-Table PROD2PROD <br>
+     *                       , destid: "Parent ProductID" <br>
+     *                       , sourceid: "Child ProductID" <br>
+     *                       , quantity: "Quantity" <br>
+     *                       , optional: "1" = not optional, "0" = optional (for easier calculation) <br>
+     *                       , takeprice: "Y" = price, "N" = no price <br>
+     *                  } }
+     */
+    this.getPartsListObject = function()
     {
-        var sqlStr = "select PROD2PRODID, DEST_ID, SOURCE_ID, QUANTITY, OPTIONAL, TAKEPRICE "
-                            + "from PROD2PROD join PRODUCT on PROD2PROD.SOURCE_ID = PRODUCTID "
-                            + "order by PRODUCTCODE ";
-
-        return db.table(sqlStr);
+        return _relateChilds();
     }
     
-    function _buildTree(pPid, pSupervised)
+    /**
+     * Delivers a 2D-Array for RecordContainer of Entity "Prod2prod_entity" 
+     * containing parts list for passed product "pProductId" (Constructor parameter).
+     * 
+     * It is necessary to generate a specifically UID for the RecordContainer because 
+     * the same data record can be listed several times. Therefore the primary key "PROD2PRODID"
+     * can not be used for UID because this must be unique.
+     * 
+     * @return {[ [] ]} [ ["UID"
+     *                    , "PARENTID" (equals "DEST_ID")
+     *                    , "PROD2PRODID"
+     *                    , "DEST_ID"
+     *                    , "SOURCE_ID"
+     *                    , "QUANTITY"
+     *                    , "OPTIONAL"
+     *                    , "TAKEPRICE"] ]
+     */
+    this.getPartsListForRecordContainer = function()
     {
-        /* object tree to relate products by DEST_ID / SOURCE_ID.
-         * Parts list shows subordinated products.
-         **/
-        var tree = { root: {ids: [], sourceid: pPid } };
+        var ret = [];
+        var childs = _relateChilds();
         
-        if(pSupervised)
-            tree = { root: {ids: [], destid: pPid } };
-
-        for (var i = 0; i < data.length; i++)
+        __push(childs.root);
+        
+        function __push(pObj)
         {
-            var prod2prodid = data[i][0];
-            if ( tree[prod2prodid] == undefined )                                         
-            tree[prod2prodid] = {
-                ids: [] 
-                , prodid: ""
-                , rowdata: data[i].slice(0)//copy to get NativeArray for concatenation
-                , destid: data[i][1]
-                , sourceid: data[i][2] 
-                , quantity: data[i][3]
-                , optional: data[i][4]
-                , takeprice: data[i][5]
-                , pos: 0
-                , parentid: ""
-            };  
+            for(var i = 0; i < pObj.ids.length; i++)
+            {
+                var rowdata = childs[pObj.ids[i]].rowdata;
+                var UID = util.getNewUUID();
+                var PARENTID = childs[pObj.ids[i]].destid;
+
+                rowdata = [UID, PARENTID].concat(rowdata);
+                ret.push(rowdata);
+                __push( childs[pObj.ids[i]] );
+            }
         }
         
-        return tree;
+        return ret;
     }
     
-    function _getSubordinated(pPid)
+    /**
+    * Delivers an Array containing productids of the parts list 
+    * for passed product "pProductId" (Constructor parameter).
+    * 
+    * 
+    * @return {[]} [ "SOURCE_ID" ]
+    */
+    this.getPartsListProdIds = function()
     {
-        var tree = _buildTree(pPid, false);
-
-        __relate("root", ["0"]);
+        var ret = [];
+        var childs = _relateChilds();
         
-        return tree;
+        __push(childs.root);
         
+        return ret;
         
-        function __relate(pID, pPos)
+        function __push(pObj)
         {
-            for ( var id in tree )
+            for(var i = 0; i < pObj.ids.length; i++)
             {
-                if ( tree[id].destid == tree[pID].sourceid && tree[pID].ids.indexOf(id) == -1 )
-                {   
-                    tree[pID].ids.push(id);
-
-                    var rowdata = tree[id].rowdata;
-                    pPos[pPos.length-1] = (Number(pPos[pPos.length-1]) + 1).toString();
-                                                 //POS, PARENTID
-                    rowdata = rowdata.concat([pPos.join("."), pID]);
-
-                    tree[id].rowdata = rowdata;
-                    tree[id].prodid = tree[id].sourceid;
-                    tree[id].pos = rowdata[6];
-                    tree[id].parentid = rowdata[7];
-
-                    __relate(id, pPos.concat([0]));
-                }    
+                ret.push(childs[pObj.ids[i]].sourceid);
+                __push( childs[pObj.ids[i]] );
             }
         }
     }
     
-    this.getSubordinatedObject = function(pPid)
-    {
-        return _getSubordinated(pPid);
-    }
-    
-    this.getSubordinatedData2DArray = function(pPid)
+    /**
+    * Delivers an Array containing productids of the parent list
+    * for passed product "pProductId" (Constructor parameter).
+    * 
+    * 
+    * @return {[]} [ "DEST_ID" ]
+    */
+    this.getParentProdIds = function()
     {
         var ret = [];
-        var so = _getSubordinated(pPid);
+        var parents = _relateParents();
         
-        __push(so.root);
+        __push(parents.root);
+
+        return ret;
         
         function __push(pObj)
         {
             for(var i = 0; i < pObj.ids.length; i++)
             {
-                ret.push(so[pObj.ids[i]].rowdata);
-                __push( so[pObj.ids[i]] );
+                ret.push(parents[pObj.ids[i]].destid);
+                __push( parents[pObj.ids[i]] );
             }
         }
-        
-        var dUtils = new DataUtils();
-        dUtils.array_mDimSort(ret, 6, true);
-        
-        return ret;
     }
     
-    this.getSubordinatedProdIds = function(pPid)
+    /** 
+    * Function to initalize class variable "data" containing complete Prod2Prod-Data.<br>
+    * It guarantees a unique load of data per instance.
+    *
+    */
+    function _initProd2ProdData()
     {
-        var ret = [];
-        var so = _getSubordinated(pPid);
-        for(var p2pid in so)
-            ret.push(so[p2pid].prodid);
-        
-        return ret;
+        if(data == undefined)
+        {
+            var sqlStr  = "select PROD2PRODID, DEST_ID, SOURCE_ID, QUANTITY, OPTIONAL, TAKEPRICE "
+                        + "from PROD2PROD join PRODUCT on PROD2PROD.SOURCE_ID = PRODUCTID "
+                        + "order by PRODUCTCODE ";
+
+            data = db.table(sqlStr);
+        }
     }
     
-    function _getSupervised(pPid)
+    /* object tree to relate products by DEST_ID / SOURCE_ID.
+     * 
+     **/
+    function _buildTree(pSupervised)
     {
-        var SuperVised = {};
+        _initProd2ProdData();
+
+        var tree = { root: {ids: [], sourceid: pProductId } };
         
-        var tree = _buildTree(pPid, true);
+        if(pSupervised)
+            tree = { root: {ids: [], destid: pProductId } };
 
-        __relate("root", ["0"]);
+        for (var i = 0; i < data.length; i++)
+        {
+            var prod2prodid = data[i][0];
+            if ( tree[prod2prodid] == undefined )   
+            {
+                tree[prod2prodid] = {
+                    ids: [] 
+                    , rowdata: data[i].slice(0)//copy to get NativeArray for concatenation
+                    , destid: data[i][1]
+                    , sourceid: data[i][2] 
+                    , quantity: data[i][3]
+                    , optional: data[i][4]
+                    , takeprice: data[i][5]
+                };
+            }
+        }
         
-        return SuperVised;
+        return tree;
+    }
+    
+    function _relateChilds()
+    {
+        var tree = _buildTree(false);
+
+        __relate("root");
+        
+        return tree;
         
         
-        function __relate(pID, pPos)
+        function __relate(pID)
         {
             for ( var id in tree )
             {
-                if ( tree[id].sourceid == tree[pID].destid && tree[pID].ids.indexOf(id) == -1 )
+                if ( tree[id].destid == tree[pID].sourceid && tree[pID].ids.indexOf(id) == -1 )
                 {   
-                   var rowdata = tree[id].data;
-                   pPos[pPos.length-1] = (Number(pPos[pPos.length-1]) + 1).toString();
-                                                //POS, PARENTID
-                   rowdata = rowdata.concat([pPos.join("."), pID]);
-                   
-                   SuperVised[id] = { prodid: tree[id].destid, data: rowdata };
-                   
-                   __relate(id, pPos.concat([0]));
+                    tree[pID].ids.push(id);
+                    __relate(id);
                 }    
             }
         }
     }
     
-    this.getSupervisedData = function(pPid)
+    function _relateParents()
     {
-        return _getSupervised(pPid);
-    }
-    
-    this.getSupervisedData2DArray = function(pPid)
-    {
-        var ret = [];
-        var so = _getSupervised(pPid);
-        for(var p2pid in so)
-            ret.push(so[p2pid].data);
-        
-        var dUtils = new DataUtils();
-        dUtils.array_mDimSort(ret, 6, true);
-        
-        return ret;
-    }
-    
-    this.getSupervisedProdIds = function(pPid)
-    {
-        var ret = [];
-        var so = _getSupervised(pPid);
-        for(var p2pid in so)
-            ret.push(so[p2pid].prodid);
+        var tree = _buildTree(true);
+
+        __relate("root");
         
-        return ret;
+        return tree;
+
+
+        function __relate(pID)
+        {
+            for ( var id in tree )
+            {
+                if ( tree[id].sourceid == tree[pID].destid && tree[pID].ids.indexOf(id) == -1 )
+                {   
+                    tree[pID].ids.push(id);
+                    __relate(id);
+                }    
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/process/Util_lib/process.js b/process/Util_lib/process.js
index 9a02ca2d3e8..e88d7781f55 100644
--- a/process/Util_lib/process.js
+++ b/process/Util_lib/process.js
@@ -11,7 +11,7 @@ import("system.swing");
 import("system.question");
 import("system.eMath");
 import("system.datetime");
-import("Offer_lib");
+import("OfferOrder_lib");
 import("Date_lib");
 
 /**
@@ -910,11 +910,12 @@ function JDitoUtils()
      * @param {String} pColumn req database column that contains unique numbers
      * @param {String} pTable req database table
      * @param {Number} pStartNumber opt number to start numeration
+     * @param {String} pCondition opt SQL Where Conditon
      * 
      * @result {String} next valid number
      */
-    this.getNextUniqueNumber = function(pColumn, pTable, pStartNumber){
-        var maxNum = that.getMaxUniqueNumber(pColumn, pTable);
+    this.getNextUniqueNumber = function(pColumn, pTable, pStartNumber, pCondition){
+        var maxNum = that.getMaxUniqueNumber(pColumn, pTable, pCondition);
         
         if(maxNum == "0")    
         {
@@ -931,11 +932,12 @@ function JDitoUtils()
      * @param {String} pNumber number to check
      * @param {String} pColumn req database column that contains unique numbers
      * @param {String} pTable req database table
+     * @param {String} pCondition opt SQL Where Conditon
      * 
      * @result {boolean} passed number is valid
      */
-    this.validateUniqueNumber = function(pNumber, pColumn, pTable){
-        var maxNum = that.getMaxUniqueNumber(pColumn, pTable);
+    this.validateUniqueNumber = function(pNumber, pColumn, pTable, pCondition){
+        var maxNum = that.getMaxUniqueNumber(pColumn, pTable, pCondition);
         
         return Number(pNumber) > Number(maxNum);
     }
@@ -945,11 +947,17 @@ function JDitoUtils()
      * 
      * @param {String} pColumn req database column that contains unique numbers
      * @param {String} pTable req database table
+     * @param {String} pCondition opt SQL Where Conditon
      * 
      * @result {String} hightest number
      */
-    this.getMaxUniqueNumber = function(pColumn, pTable){
-        var maxNum = db.cell("select max(" + pColumn + ") from " + pTable);
+    this.getMaxUniqueNumber = function(pColumn, pTable, pCondition){
+        
+        var condition = "";
+        if(pCondition != undefined)
+            condition += " where " + pCondition;
+        
+        var maxNum = db.cell("select max(" + pColumn + ") from " + pTable + condition);
         
         return maxNum == "" ? "0" : maxNum;
     }
-- 
GitLab