This is an old revision of the document!
This map defines a set of rules that must be evaluated to make a decision.
The general idea comes from the Decision Model and Notation (DMN) structure used in BPM but very adapted to the specific coreBOS environment and infrastructure.
You can find a few useful links about DMN here:
The accepted format for this map is basically a set of rules which can be of three types:
Expressions contain any workflow expression that can be evaluated in the context of the Decision Map. These are exactly like Condition Expression maps but written directly inside this business map.
Since an expression can return any type of value, in order to know if an expression has failed it must return the reserved string “__DoesNotPass__“
This type is simply the name or CRMID of any existing Condition Expression or Condition Query business map, they will be loaded and evaluated in the context of the Decision Map
Since an expression can return any type of value, in order to know if an expression has failed it must return the reserved string ”__DoesNotPass__“
This type expects to have the values to search on inside a coreBOS module.
We will be able to define the conditions to filter the records in the module and then a set of search conditions to look for records.
<decision> <hitPolicy></hitPolicy> <!-- U F C A R G --> <aggregate></aggregate> <!-- only available if hitPolicy=G: sum,min,max,count --> <rules> <rule> <sequence></sequence> <expression></expression> <output></output> <!-- ExpressionResult, FieldValue, crmObject, Row --> </rule> <rule> <sequence></sequence> <mapid></mapid> <output></output> <!-- ExpressionResult, FieldValue, crmObject, Row --> </rule> <rule> <sequence></sequence> <decisionTable> <module></module> <conditions> <!-- QueryGenerator conditions --> <condition> <input></input> <!-- context variable name --> <operation></operation> <!-- QueryGenerator operators --> <field></field> <!-- fieldname of module --> </condition> </conditions> <orderby></orderby> <!-- column to order the records by --> <searches> <search> <condition> <input></input> <!-- context variable name --> <operation></operation> <!-- QueryGenerator operators --> <field></field> <!-- fieldname of module --> </condition> </search> </searches> <output></output> <!-- fieldname or fieldnames separated by commas or star (*) for all fields --> </decisionTable> <output></output> <!-- ExpressionResult, FieldValue, crmObject, Row --> </rule> </rules> </decision>
To execute a decision map and get the result we use the coreBOS Rules service. coreBOS Rule will see that the given map is actually a decision table and will evaluate the map with the given context.
$result = coreBOS_Rule::evaluate(put your decision map ID or name here, $context);
<decision> <hitPolicy>U</hitPolicy> <rules> <rule> <sequence>1</sequence> <expression><![CDATA[if AND('$[season]'=='Fall', $[guestcount]<=8) then 'Spareribs' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> <rule> <sequence>2</sequence> <expression><![CDATA[if AND('$[season]'=='Winter', $[guestcount]<=8) then 'Roastbeef' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> <rule> <sequence>3</sequence> <expression><![CDATA[if AND('$[season]'=='Spring', $[guestcount]<=4) then 'Dry Aged Gourmet Steak' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> <rule> <sequence>4</sequence> <expression><![CDATA[if AND('$[season]'=='Spring', AND($[guestcount]>=5, $[guestCount]<=8)) then 'Steak' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> <rule> <sequence>5</sequence> <expression><![CDATA[if AND(OR('$[season]'=='Fall', OR('$[season]'=='Winter', '$[season]'=='Spring')), $[guestcount]>8) then 'Stew' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> <rule> <sequence>6</sequence> <expression><![CDATA[if '$[season]'=='Summer' then 'Light Salad and a nice Steak' else '__DoesNotPass__' end]]></expression> <output>ExpressionResult</output> </rule> </rules> </decision>
Let's suppose we have a module called DecisionConditions with these fields:
and these records
sequence | season | guestcountmin | guestcountmax | desireddish |
---|---|---|---|---|
1 | Fall | 0 | 8 | Spareribs |
2 | Winter | 0 | 8 | Roastbeef |
3 | Spring | 0 | 4 | Dry Aged Gourmet Steak |
4 | Spring | 5 | 8 | Steak |
5 | Fall | 9 | 10000 | Stew |
6 | Winter | 9 | 10000 | Stew |
7 | Spring | 9 | 10000 | Stew |
8 | Summer | 0 | 10000 | Light Salad and a nice Steak |
<decision> <hitPolicy>U</hitPolicy> <rules> <rule> <sequence>1</sequence> <decisionTable> <module>DecisionConditions</module> <orderby>sequence</orderby> <!-- column to order the records by --> <searches> <search> <condition> <input>season</input> <!-- context variable name --> <operation>e</operation> <!-- QueryGenerator operators --> <field>season</field> <!-- fieldname of module --> </condition> <condition> <input>guestcount</input> <!-- context variable name --> <operation>ge</operation> <!-- QueryGenerator operators --> <field>guestcountmin</field> <!-- fieldname of module --> </condition> <condition> <input>guestcount</input> <!-- context variable name --> <operation>le</operation> <!-- QueryGenerator operators --> <field>guestcountmax</field> <!-- fieldname of module --> </condition> </search> </searches> <output>desireddish</output> <!-- fieldname --> </decisionTable> <output>FieldValue</output> </rule> </rules> </decision>
In the example above I decided to add two columns for the Guest Count, in order to convert the range [5..8] into two records. In this mindset, I also use the value 10000 as an “infinite” value.
But the truth is that as the implementor of both the decision table map and module I have full control of how I want my users to define the conditions. Let's suppose that I want the users of the module to be able to define the condition with only one column for guest count like is reflected in the image. In this case, I would have implemented a module with these fields:
these records
sequence | season | guestcount | desireddish |
---|---|---|---|
1 | Fall | 8 | Spareribs |
2 | Fall | 10000 | Stew |
3 | Spring | 4 | Dry Aged Gourmet Steak |
4 | Spring | 8 | Steak |
5 | Spring | 10000 | Stew |
5 | Winter | 8 | Roastbeef |
6 | Winter | 10000 | Stew |
7 | Summer | 10000 | Light Salad and a nice Steak |
and this map
<decision> <hitPolicy>F</hitPolicy> <rules> <rule> <sequence>1</sequence> <decisionTable> <module>DecisionConditions</module> <orderby>sequence</orderby> <!-- column to order the records by --> <searches> <search> <condition> <input>season</input> <!-- context variable name --> <operation>e</operation> <!-- QueryGenerator operators --> <field>season</field> <!-- fieldname of module --> </condition> <condition> <input>guestcount</input> <!-- context variable name --> <operation>le</operation> <!-- QueryGenerator operators --> <field>guestcount</field> <!-- fieldname of module --> </condition> </search> </searches> <output>desireddish</output> <!-- fieldname --> </decisionTable> <output>FieldValue</output> </rule> </rules> </decision>
In this case, I am playing with the Hit Policy which has changed to First, so now my users must understand that the ranges are defined from the previous value to the one defined in each record and that sequence is VERY important.
Since, in the end, the supported operations are those of the Query Generator, which even supports ranges:
QueryGenerator->addConditions(column, operator, values) where operator can be '[]', '[[', ']]', '][' among many others
the possibilities that the implementor has are very big.
A decision map that would return the value of a global variable would look something like the map below. The context would have to send in all the role and group of the current user and I'm not totally sure if the “module list” would work as it is below, but it will be VERY close and enough for you to get an idea of how this map works.
<decision> <hitPolicy>F</hitPolicy> <rules> <rule> <sequence>1</sequence> <decisionTable> <module>GlobalVariable</module> <searches> <search> <!-- Mandatory GV --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>checkboxtrue</input> <!-- checkboxtrue == 1 in context --> <operation>e</operation> <field>mandatory</field> </condition> </search> <search> <!-- In module for user --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>userid</input> <operation>e</operation> <field>assigned_user_id</field> </condition> <condition> <input>checkboxtrue</input> <!-- checkboxtrue == 1 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> <condition> <input>gvmodule</input> <operation>c</operation> <field>gvmodule</field> </condition> </search> <search> <!-- In module for group --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>groupid</input> <operation>e</operation> <field>assigned_user_id</field> </condition> <condition> <input>checkboxtrue</input> <!-- checkboxtrue == 1 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> <condition> <input>gvmodule</input> <operation>c</operation> <field>gvmodule</field> </condition> </search> <search> <!-- In module for role --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>userrole</input> <operation>c</operation> <field>gvrole</field> </condition> <condition> <input>checkboxtrue</input> <!-- checkboxtrue == 1 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> <condition> <input>gvmodule</input> <operation>c</operation> <field>gvmodule</field> </condition> </search> <search> <!-- without module for user --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>userid</input> <operation>e</operation> <field>assigned_user_id</field> </condition> <condition> <input>checkboxfalse</input> <!-- checkboxfalse == 0 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> </search> <search> <!-- without module for group --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>groupid</input> <operation>e</operation> <field>assigned_user_id</field> </condition> <condition> <input>checkboxfalse</input> <!-- checkboxfalse == 1 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> </search> <search> <!-- without module for role --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>userrole</input> <operation>c</operation> <field>gvrole</field> </condition> <condition> <input>checkboxfalse</input> <!-- checkboxfalse == 1 in context --> <operation>e</operation> <field>inmodulelist</field> </condition> </search> <search> <!-- Default GV --> <condition> <input>gvname</input> <operation>e</operation> <field>gvname</field> </condition> <condition> <input>checkboxtrue</input> <!-- checkboxtrue == 1 in context --> <operation>e</operation> <field>default</field> </condition> </search> </searches> <output>value</output> <!-- fieldname --> </decisionTable> <output>FieldValue</output> </rule> </rules> </decision>