From 75f9d4f9cb85d402f319c36dfa58b20fdac8f6b4 Mon Sep 17 00:00:00 2001
From: Sebastian Pongratz <s.pongratz@adito.de>
Date: Tue, 7 Dec 2021 14:23:15 +0100
Subject: [PATCH] [Projekt: xRM-ContactManagement][TicketNr.:
 2002650][360Degree_entity:...

---
 entity/360Degree_entity/documentation.adoc | 347 +++++++++++++++++++--
 1 file changed, 320 insertions(+), 27 deletions(-)

diff --git a/entity/360Degree_entity/documentation.adoc b/entity/360Degree_entity/documentation.adoc
index b07c00500c..ff20617c53 100644
--- a/entity/360Degree_entity/documentation.adoc
+++ b/entity/360Degree_entity/documentation.adoc
@@ -1,36 +1,329 @@
+:hardbreaks:
+:toc2: left
+:toclevels: 5
+:sectnums:
+:sectnumlevels: 5
+:toc-title: Index
+:figure-caption: Figure
+:icons: font
+:numbered:
+:source-autofit:
+:table-stripes: odd
 = 360Degree_entity
 
-The 360Degree_entity shows the Connection between data models.
-The entity has two providers for persons and organizations.
+////
+(required only for separate PDF generation)
+////
 
-The 360Degree_entity shows direct connections, don´t be confused with the ObjectRelation_entity.
+//include::_default_attributes_EN.adoc[]
 
-== Adding new modules to the 360° view
-You may want to display more modules within the 360degree view (for example after creating and implemeting your own module).
+<<<
 
-.An Example:
-You added a module called "T-Shirts" to your project. Every person-contact can have _n_ "T-Shirts" and whenever a person owns a T-Shirt you want to
-display it in the 360degree view. 
+== Introduction
 
-Heres is a list of what you need to do whenever you want to do this:
+The 360Degree_entity models the relations between specific Entities and enables the user to work with these dependencies via the 360DegreeFilter_view, which includes the ViewTemplates "Tree" and "Timeline".
 
-- At first you have to fill/add certain Fields/Processes in your new Context.
-    - Add (These have to be written exactly like that, if not already existing):
-        - DATE_NEW, this Field has to contain a Date as a Long Value. That will be used as the Date Value you can see in the Timeline View-Template.
-        - ACTIVE, in this Field you can specify which Datasets are active or not. You have to declare that in the Value Expression as a Case-When due to the Filter
-    - Fill (these can be filled in the specific Entity as a Process)
-        - #CONTENTTITLE, this will be the main display Text and the blue Link
-        - #CONTENTDESCRIPTION, this will be the smaller, mostly longer, Text beneath the #CONTENTTITLE 
-        
-- Add an element (with the name of the context you want to add) in the process of the `ObjectType_param` in the *corresponding provider* which is located in the `360Degree_entity`. _We would extend the JSON-Object which is returned in the `PersonObjects`-provider by the name of our t-shirt-context: "TShirt" in our example
+Currently, the 360Degree logic is restricted to relations of Contact_entity (i.e., of companies and persons). This means that
 
-- In that JSON-Object you can/have to add some Configuration Elements to change the results.
-    - connectionField, here you have to declare a Field that writes exactly like the one in your Entity (Standard is CONTACT_ID!!!). The Value of this Field will be used to compare it with the Object_Rowid. 
-    - setGroupBy: here you can declare a DB-Column that will be used as the new group by of that context. A good example is Order. We want to group by the Ordertype so we have to declare ORDERTYPE as our groupBy. 
-        - If your DB-Value is related to a Keyword you also have to use groupByKeyword and add the Container as Value
+* the 360Degree View can only be referenced in the MainViews of the Contexts "Organisation" and "Person" (appearing as tab "360 Degree");
+* the 360Degree View can only include datasets (records) of Contexts having a relation to Context "Contact" (directly or via a "junction Context") - such as the Contexts "Salesproject", "Offer", or "Order" do.
 
-- If you have a Connection of 1:N in your Database Schema (like Task and TaskLink) you have to add specific Properties in your Context Object. Lets take TaskLink as a Example.
-    - subContext: the Name of the "Link"-Context -> TaskLink
-    - childField: The Field where the LinkID to your Parent is stored -> TASK_ID
-    - parentField: The field in your Parent Context where your Connection to your Child is stored -> TASKID
-    - contactIdField: The Field in the Link Context where your Contactid is stored -> OBJECT_ROWID
\ No newline at end of file
+[NOTE]
+This documentation requires you to be familiar with the "ADITO xRM" project's data model, especially the data structure for managing datasets of persons and companies (i.e., the function of the database tables PERSON, ORGANISATION, and CONTACT - see chapter "Core tables of the xRM project" of the document "Customizing Manual").
+
+This documentation will
+
+* explain the basics of the 360Degree logic, using examples of the ADITO xRM project;
+* teach you how you can include further Contexts in the existing 360Degree View implementations, using a plain and hands-on example.
+
+This documentation does _not_ include a description of how to implement a 360Degree View in the MainView of Contexts _other_ than Organisation and Person. Nevertheless, in principle, you can customize this by yourself, according to you requirements, using the 360 Degree logic as pattern.
+
+== Basics 
+
+To understand how to modify the behavior of the 360DegreeFilter_view in existing implementations or how to add them to further Contexts, you need to be aware of some basics regarding specific mandatory fields as well as the configuration of specific Provider, Consumer, and Parameters.
+
+=== RecordContainer
+
+The core of the 360Degree_entity's logic is the contentProcess of its jDitoRecordContainer. There, a tree (or timeline) structure is created, with 
+
+* the Entities' titles as root nodes
+* (if configured, see below:) specific sub-nodes, used for grouping
+* the datasets of the Entities on 2nd or, if a grouping applies, on 3rd level
+
+=== Mandatory settings
+
+The logic of the jDitoRecordContainer's contentProcess requires the Contexts, which are to be included in the 360Degree View, to have specific EntityFields and properties well-configured. 
+
+==== EntityFields
+
+The following EntityFields must be present and configured in all Entities whose datasets are to be included in the 360Degree View: 
+
+* ACTIVE
++
+The value of this EntityField determines whether or not a dataset will be shown. Often, the value of this EntityField is being calculated in property "expression" of the corresponding RecordFieldMapping (e.g., via a CASE/WHEN SQL statement).
+* DATE_NEW
++
+The value of this EntityField will be used in the ViewTemplate "Timeline". Its calculation follows the standard logic for this EntityField.
+
+Both EntityFields must be present and named exactly as shown above. Otherwise the logic will fail.
+
+==== Properties
+
+The following propertys are not essential, but if they are missing, the appearance of the Context's datasets will be suboptimal:
+
+* Entity properties:
+** contentTitleProcess:
++
+The result of this process will, in this case, appear as "headlines" of the datasets shown in the 360Degree View, including a hyperlink to the datasets' MainView. If this property is not set, the datasets' primary keys will be displayed.
+** contentDescriptionProcess:
++
+The result of this process will, in this case, appear as "sub-headlines" of the datasets shown in the 360Degree View.
+* Context properties:
+** icon/iconProcess: Specifies the icon that will be shown to the left of the headline/sub-headline. Usually, this should be the same icon as the icon of the Entity (i.e., the icon shown in the sidebar, to the left of the client) - except when a grouping (see below) is configured (in this case, the iconProcess should be used, in order to show different icons for each group).
+
+=== Providers and Consumers
+
+The dependencies (relations) between the 360Degree Context and the Contexts referencing the 360Degree View in their MainViews is, as usual in ADITO, established via a Provider and a Consumer (one for each dependency).
+
+==== Providers
+
+The 360Degree Context must have a Provider to for each dependency, named according to the Pattern `<Context name>Objects`. Examples:
+
+* OrganisationObjects, for supplying the 360Degree tab in the OrganisationMain_view
+* PersonObjects, for supplying the 360Degree tab in the PersonMain_view
+
+The following property values need to be set (they are the same for each Provider):
+
+* targetContextField: TARGET_CONTEXT
+* targetidField: TARGET_ID
+* initFilterMergeMode: AND
+
+All other Provider properties should usually remain in default state.
+
+==== Consumers
+
+The above mentioned Providers need to be referenced by Consumers of the Contexts that show the 360Degree View in their MainView (i.e., currently, only the Contexts "Organisation" and "Person").
+
+These Consumers should always be named "360DegreeObjects" (spelling convention, no technical requirement) and have the following properties set:
+
+* entityName: 360Degree_entity
+* fieldName: name of specific provider - e.g., "OrganisationObjects", or "PersonObjects", respectively
+
+All other Consumer properties should usually remain in default state.
+
+==== Parameters
+
+The 360Degree_entity has the following Parameters, each having property "expose" set to true:
+
+* BaseContextId_param
+* ObjectRowId_param
+* ObjectStatus_param
+* ObjectType_param
+
+These Parameters have been predefined by the ADITO xRM developers. Never rename them and never change the configuration of their originals under node "Parameters", but only modify their instances appearing under the nodes of the respective 360Degree_entity's Providers or under the Consumers connected to these Providers, respectively.
+
+===== Provider Parameter settings 
+
+The Parameters "ObjectRowId_param" and "ObjectStatus_param" are configured exclusively on _Consumer_ side (see below). Therefore, leave their instances appearing under node "Providers" in default state (do NOT initialize them). This makes sure that their original property "expose" keeps its value "true", which makes them appear on Consumer side.
+
+The Parameters "BaseContextId_param" and "ObjectType_param" are configured exclusively on _Provider_ side. Therefore, their instances under the node "Provider" must be initialized and have their property "expose" set to "false". This overwrites the original setting ("true", see above) and thus makes sure that these Parameters do not appear on Consumer side.  
+
+The valueProcesses of the 2 Parameter instances need to be set as follows (all other properties should remain in default state): 
+
+====== BaseContextId_param
+
+BaseContextId_param's valueProcess must supply the name of the Context, whose MainView should show the 360Degree View. Example:
+
+.360Degree_entity.OrganisationObjects.BaseContextId_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("system.result");
+
+result.string("Organisation");
+----
+
+====== ObjectType_param
+
+ObjectType_param's valueProcess must supply a JSON String including information about the names of all Contexts to be shown in the 360Degree View.
+
+Example:
+[source%autofit, javascript]
+----
+result.string(JSON.stringify({
+    "Salesproject": {},
+    "Offer": {},
+    "Contract": {}));
+----
+
+(Scroll further down, in order to see the complete example code of a valueProcess. This might help you to understand the following explanations of optional settings better.)
+
+Optionally, the following features can be specified via JSON properties (included in the curly brackets behind the Context name), separately for each Context:
+
+* connectionField: The name of the Context's EntityField that works as foreign key pointing to the other side of the relation, i.e., to EntityField CONTACTID of Context "Contact". Example: `"connectionField":"TASK_REQUESTOR_CONTACT_ID"` (enabling to show datasets of Context "SupportTicket"). This property must only be set if its value is other than "CONTACT_ID".
+* Grouping: If configured, the datasets of the respective Context are grouped into sub-nodes.
+** setGroupBy: This mandatory property specifies the EntityField whose values determine the grouping. Example (belonging to Context "Order"): `"setGroupBy":"ORDERTYPE"` effects a grouping of the "Order" datasets into sub-nodes labelled with the respective value of Order_entity's EntityField ORDERTYPE.
+** groupByKeyword (optional): If (as in the above example) the values of the EntityField specified via `setGroupBy` are KeywordEntry keys, then you can optionally specify the corresponding KeywordCategory via property `groupByKeyword`. The effect will be that the grouping nodes will no longer show the KeywordEntries' keys, but their titles. Example: If you specify `"groupByKeyword":"OrderType"`, then "Invoice" (=TITLE) will be shown instead of "ORDTYPEINVO" (= KEYID).  
+* Junction table: You can specify a Context that works as junction between the Context that shows the 360Degree View and the Context that is to be shown in this View. Example: Context "BulkMailRecipient" works as "junction Context" between the Context "BulkMail" and Context "Contact", in order to enable the user to assign a specific contact (person) to a specific bulk mail.
++
+The configuration of a "junction Context" requires 4 properties to be set:
+
+** subContext: The name of the "junction Context". Example: `"subContext":"BulkMailRecipient"`
+** childField: The name of the EntityField of the subContext that includes the foreign key related to the primary key of the "parent" Context (i.e., of "BulkMail", in our example). Example: `"childField":"BULKMAIL_ID"`
+** parentField: The name of the EntityField of the "parent" Context that in cludes the primary key, to which the foreign key of the "child" context relates. Example: `"parentField":"BULKMAILID"`
+** contactIdField: The name of the subContext's EntityField that works as foreign key pointing to the other side of the relation, i.e., to EntityField CONTACTID of Context "Contact" (in our example, this is the CONTACTID of the recipient). Example: `"contactIdField":"CONTACT_ID"` 
+
+Example:
+
+.360Degree_entity.OrganisationObjects.ObjectType_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("system.vars");
+import("system.result");
+
+var res = {
+    "Salesproject": {}, 
+    "Offer": {}, 
+    "Order": {
+        "setGroupBy":"ORDERTYPE",
+        "groupByKeyword":"OrderType"
+    }, 
+    "Contract": {}, 
+    "SupportTicket": {
+        "connectionField":"TASK_REQUESTOR_CONTACT_ID",
+        "setGroupBy":"NameFor360"
+    },
+    "Advertising": {},
+    "BulkMail": {        
+        "subContext":"BulkMailRecipient",
+        "childField":"BULKMAIL_ID",
+        "parentField":"BULKMAILID",
+        "contactIdField":"CONTACT_ID"
+    },
+    "SerialLetter": {        
+        "subContext":"LetterRecipient",
+        "childField":"SERIALLETTER_ID",
+        "parentField":"SERIALLETTERID",
+        "contactIdField":"CONTACT_ID"
+    }
+}
+
+result.string(JSON.stringify(res))
+----
+
+===== Consumer Parameter settings 
+
+If set correctly on Provider side (see above), instances of the Parameters "ObjectRowId_param" and "ObjectStatus_param" will appear under each Consumer "360DegreeObjects" (see above). As mentioned before, currently, there are only 2 Contexts having this Consumer: "Organisation" and "Person".
+
+====== ObjectRowId_param
+
+This Parameter's valueProcess retrieves (in JSON format) the CONTACTIDs of all involved Contact datasets - i.e., all CONTACTIDs to be used for retrieving the related Contexts' datasets, according to their "connectionField" (= foreign key, which is usually named "CONTACT_ID", but can also have a different name - see description of property "connectionField" above).
+
+This means, in Context "Person", the result of this valueProcess is simply a JSON String with the CONTACTID connected to the Person dataset:
+
+.Person_entity.360DegreeObjects.ObjectRowId_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("system.vars");
+import("system.result");
+
+result.string(JSON.stringify([vars.getString("$field.CONTACTID")]));
+----
+
+In Context "Organisation", the result of this valueProcess is a JSON String including the CONTACTIDs of both the selected company and all persons belonging to this company:
+
+.Organisation_entity.360DegreeObjects.ObjectRowId_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("Sql_lib");
+import("system.vars");
+import("system.result");
+
+var contactids = newSelect( "CONTACTID" )
+        .from("CONTACT")
+        .where("CONTACT.ORGANISATION_ID", vars.getString("$field.ORGANISATIONID"))
+        .orderBy(new SqlMaskingUtils().isNull("PERSON_ID", "'0'"))
+        .arrayColumn();
+
+result.string(JSON.stringify(contactids));
+----
+
+[NOTE]
+As already mentioned at the beginning of this documentation, currently, the 360Degree logic is restricted to relations of Contact_entity (i.e., of companies and persons). Therefore, the CONTACTID is the central reference here, as it (exlusively) identifies datasets of companies or persons, respectively. If you want to implement a 360Degree View in the MainView of Contexts _other_ than Organisation and Person, you can use the 360 Degree logic as pattern, but you need to find another EntityField as central reference.
+
+
+====== ObjectStatus_param
+
+For both instances of Consumer "360Degree" (= in Contexts "Person" and "Organisation"), the result of this Parameter's valueProcess is simply the value of the EntityField holding the state of the selected person/company:
+
+.Person_entity.360DegreeObjects.ObjectStatus_param.valueProcess.js and Organisation_entity.360DegreeObjects.ObjectStatus_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("system.vars");
+import("system.result");
+
+result.string(vars.get("$field.STATUS"));
+----
+
+This Parameter's value is evaluated in the stateProcess of 360Degree_entity's ActionGroup "newModule", which controls whether or not the 360Degree View shows a button for creating new datasets for the referenced Contexts (Sales Project, Contract, Order, etc.): If the selected person or company is in state "Inactive" or "To Delete" (i.e., if its EntityField "STATUS" has the keyword-related value "CONTACTSTATINACTIVE" or "CONTACTSTATDELETE"), then this button is disabled.
+
+== Example for extending 360Degree View
+
+As a plain and hands-on teaching example, here are the instructions for the task "Include Activity datasets in the 360Degree View of the Contexts Person and Organisation". (By itself, this task is nonsense, of course, because the Activity datasets are already shown in the MainView's tab "Activities". Nevertheless, this task is well-suited to be used for learning purposes.)
+
+The task should also include a grouping of the Activity records according to Activit_entity's keyword-related EntityField "CATEGORY".
+
+=== Activity_entity: Mandatory settings
+
+* Property "contentTitleProcess" is already set: Its result is the value of EntityField "SUBJECT", which perfectly fits for our task. (Beside, you should never change it anyway, as it is also used in other parts of the client.)
+* Set property "contentDescriptionProcess" with a reasonable logic. EntityField "INFO" seems to be suitable to be shown below the contentTitle (see above), so the code to be set here is: 
++
+.Activity_entity.contentDescriptionProcess.js
+[source%autofit, javascript]
+----
+import("system.vars");
+import("system.result");
+
+result.string(vars.get("$field.INFO"));
+----
+* EntityField DATE_NEW is already present, so there is nothing to do.
+* Create the new EntityField "ACTIVE". For testing purposes, it is enough to configure the fix value "true", preferably via property "expression" of the respective RecordFieldMapping:
++
+.Activity_entity.db.ACTIVE.value.expression.js
+[source%autofit, javascript]
+----
+import("system.result");
+
+result.string('true');
+----
+* Set the Context's property "icon" to the same icon that appears in the side bar to the left of the client - i.e., in this case, select the icon named "VAADIN:HOURGLASS_END".
+* If a grouping according to Activity_entity's EntityField CATEGORY is applied (see below), you may use propertyIcon process for setting code that enables CATEGORY-specific icons to appear beside the grouped datasets. 
+
+=== 360Degree_entity: Extending ObjectType_param
+
+Extend the JSON configured in the valueProcess of Parameter ObjectType_param of Provider, both for Context "Organisation" and "Person":
+
+.360Degree_entity.OrganisationObjects.ObjectType_param.valueProcess.js and .360Degree_entity.PersonObjects.ObjectType_param.valueProcess.js
+[source%autofit, javascript]
+----
+import("system.vars");
+import("system.result");
+
+var res = {
+    "Activity": {
+        "setGroupBy:":"CATEGORY",
+        "groupByKeyword":"ActivityCategory",
+        "subContext":"ActivityLink",
+        "childField":"ACTIVITY_ID",
+        "parentField":"ACTIVITYID",
+        "contactIdField":"OBJECT_ROWID"
+    },
+    "Salesproject": {}, 
+    "Offer": {}, 
+
+    (etc...)
+}
+
+result.string(JSON.stringify(res))
+----
+
+Now, our task is completed. All other 360Degree-related settings explained in the chapters further above do not require any changes.
\ No newline at end of file
-- 
GitLab