Configuring the real-time calculation in a process grid
This document describes the configuration steps to enable the real-time calculation feature in beqom process grids. Real-time calculation enable users to obtain instant feedback and validation on the data they are inputting in the grid for a given payee.
The real-time calculation is a very powerful feature, but due to its very nature, it is a complex feature to configure. A large part of the configuration steps required to successfully set it up, such as object trigger creation, cannot be performed from the application interface. This operation can only be performed by API calls.
In addition, calculations performed using the real-time calculation feature are based on a stored procedure that needs to be updated manually within the application database.
Real-time calculation configuration roadmap
beqom recommends that you follow the below roadmap to perform the configuration of the RTC feature:
| Step # | Description | Comment |
|---|---|---|
| 1 | Synchronize the processes | This step is required only for old implementations, dating back before the introduction of the catalog database (9.1). For newer implementations, this step is automatically performed. |
| 2 | Create an object action | |
| 3 | Create an object action trigger | |
| 4 | Prepare the stored procedure |
Synchronizing Processes
The first step in configuring the real-time calculation feature is to synchronize process attached objects. This synchronization step ensures that information, specifically process object attributes, is shared between the environment database and the catalog database.
Synchronizing process attached objects
To synchronize the attached objects of a specific process, run the following API method:
| Service | Action | Endpoint | Parameters |
|---|---|---|---|
| Gateway | POST | /api/ProcessExplorer/SynchronizeProcessAttachedObject/{idProcess} |
|
To synchronize the attached objects of all processes in the database, run the following API method:
| Service | Action | Endpoint |
|---|---|---|
| Gateway | POST | /api/ProcessExplorer/SynchronizeProcessAttachedObjects |
Synchronizing process data source & attribute information
To synchronize the data source and attribute information of a single process, run the following API method:
| Service | Action | Endpoint | Parameters |
|---|---|---|---|
| Gateway | POST | /api/Admin/SynchronizePlanById/{idProcess} |
|
To synchronize the data source and attribute information of all processes, run the following API method:
| Service | Action | Endpoint |
|---|---|---|
| Gateway | POST | /api/Admin/SynchronizePlan |
Creating an object action
Now that you have retrieved the details of the process in which the RTC needs to be configured, you must create an action. Actions are containers for triggers. Once you have created an action, you can use the identifier of the action of to add triggers to that action.
| Service | Action | Endpoint | Parameters | Body |
|---|---|---|---|---|
| Object | POST | /api/object/{{uidObject}}/action |
|
Copy |
Creating an object action trigger
Once process objects, data sources and attributes have been synchronized, you need to create object action triggers. These triggers enable the real-time calculation feature to know which action in a process grid will send information to the RTC stored procedure and start the calculation. Object action triggers are based on the following items:
Process object: a container for object attributes
Object attribute: indicator field from the relevant process
Action: a container for triggers. Currently, the RTC feature supports one action (with triggers within this action)
Trigger: link with an object attribute
Retrieving process object details
The first step in configuring triggers is to obtain the details of the relevant process object. To obtain the details of a process object, run the following API method:
| Service | Action | Endpoint | Parameters | Output Sample |
|---|---|---|---|---|
| Object | GET | /api/objects/{uidObject} |
|
Copy |
Creating the trigger
Now that you have retrieved the details of the process in which the RTC needs to be configure, you can start creating triggers. Triggers are created for a specific action and are linked with the attributes of the object, retrieved using the aforementioned method.
To create a trigger, execute the following API method:
| Service | Action | Endpoint | Parameters |
Body Sample |
|---|---|---|---|---|
| Object | POST | /api/object/{{uidObject}}/action/{{uidAction}}/trigger |
|
Copy |
The UID of the action is returned by the POST method used to create the action. However, if you are adding an additional trigger to an existing action, you can still use the following method to obtain the UID of the action:
| Service | Action | Endpoint | Parameters | Output Sample |
|---|---|---|---|---|
| Object | GET | /api/object/{{uidObject}}/action | uidObject: process ID from the k_m_plans table |
Copy |
Configuring the stored procedure
The real-time calculation is associated to a stored procedure that is called every time a field on which a trigger is defined is modified in the process grid. The parameters of the procedure are collected from the application UI and sent to the stored procedure. That same SP is then used to calculate new values.
The following table details the parameters of the stored procedure:
| Name | Parameter | Output |
|---|---|---|
| Kernel_SP_Process_DataOnChange |
Copy |
|
Empty stored procedure available in the tenant database:
CREATEPROC [dbo].[Kernel_SP_Process_DataOnChange]
@uidObject UNIQUEIDENTIFIER,
@uidProfile UNIQUEIDENTIFIER,
@uidUser UNIQUEIDENTIFIER,
@idStepint,
@idTreeint,
@idTreeSecurityint,
@ValuesAS [dbo].[Kernel_Type_Process_Values] READONLY
AS
Declare@TriggerAlias nvarchar(max) ='',
@TriggerValue nvarchar(max) ='',
@PlanIdint=0,
@calculatedValueint=0;
CREATETABLE#tempTable
(
PrimaryKey INT NOTNULL ,
ObjectFieldAlias VARCHAR(100),
ObjectAttribute VARCHAR(40),
IsValid BIT,
InvalidReason NVARCHAR(400),
NewValue NVARCHAR(200),
);
SELECT*FROM#tempTable
Managing security
The real-time calculation feature requires a security configuration for the two following activities:
Trigger configuration
Trigger execution
Trigger configuration security
The security related to the configuration of triggers is linked to user permissions. To be able to configure triggers, the user must be the owner of the process object or the manage permission must have been granted to the user. The owner of a process is the user who executed the synchronization or the user who created the process. The manage permission can be granted to other users using APIs, as described in the following sections.
Obtaining the list of permissions associated with a process
To grant manage permission to users other than the owner of a process, you must first obtain the list of permissions associated to process, using the following API method:
| Service | Action | Endpoint | Parameters |
|---|---|---|---|
| Object | GET | /api/objects/{{uidObject}}/access |
|
Creating new permissions
Once you have retrieved the list of existing permissions for a process object, you can create new permissions for a given user or profile.
To add permissions for a specific user, execute the following API method:
| Service | Action | Endpoint | Parameters | Body Sample |
|---|---|---|---|---|
| Object | POST | /api/objects/{{uidObject}}/access |
|
Copy |
To add permissions for a specific profile, execute the following API method:
| Service | Action | Endpoint |
Parameters
|
Body Sample |
|---|---|---|---|---|
| Object | POST | /api/objects/{{uidObject}}/access |
|
Copy |
Trigger execution security
The security related to the execution of triggers is linked with the security token of the user, which manages application access and hierarchy. The token is automatically generated by the beqom application. The execution of the triggers is only possible from process grids.
Additional actions
Obtaining the list of triggers related to a process object
To view the list of triggers associated to a process object, execute the following API method:
| Service | Action | Endpoint | Parameters |
|---|---|---|---|
| Object | GET | /api/object/{{uidObject}}/trigger |
|
Deleting triggers from a process object
To delete triggers from a particular process object, execute the following API method:
| Service | Action | Endpoint | Parameters |
|---|---|---|---|
| Object | DELETE | /api/object/{{uidObject}}/action/{{uidAction}}/trigger/{{uidTrigger}} |
|
Known limitations
The following limitations apply to the real-time calculation feature:
The RTC works only with input fields (string, int, nvarchar, decimal).
The RTC does not work with date fields, date-time fields, boolean fields.
The RTC does not work if the field that is updated by data operations is in read-only.
The RTC cannot calculate or validate already saved values. Therefore, the same following logic has to be added to the "Pre Save Stored Procedure" (Kernel_SP_Process_PreSave).
Stored procedure example
The stored procedure example below uses the following logic:
Triggers on the Base salary, Percentage increase and Amount increase fields.
Validation depending on the age of the payee
CREATEPROC [dbo].[Kernel_SP_Process_DataOnChange]
@uidObject UNIQUEIDENTIFIER,
@uidProfile UNIQUEIDENTIFIER,
@uidUser UNIQUEIDENTIFIER,
@idStepint,
@idTreeint,
@idTreeSecurityint,
@ValuesAS [dbo].[Kernel_Type_Process_Values] READONLY
AS
Declare
@calculatedValuefloat=0,
@TriggerAlias nvarchar(max) ='',
@TriggerValue nvarchar(max) ='',
@PlanIdint=0,
@increaseAmount nvarchar(max) ='0',
@increaseAmountAlias nvarchar(max) ='12_54_57',
@baseSalary nvarchar(max) ='0',
@baseSalaryAlias nvarchar(max) ='12_54_56',
@percIncrease nvarchar(max) ='0',
@percIncreaseFloatfloat=0,
@percIncreaseAlias nvarchar(max) ='12_54_58',
@proposedSalary nvarchar(max) ='0',
@proposedSalaryAlias nvarchar(max) ='12_54_60',
@bonus nvarchar(max) ='0',
@bonusAlias nvarchar(max) ='12_54_59',
@totalProposedSalary nvarchar(max) ='0',
@totalProposedSalaryAlias nvarchar(max) ='12_54_61',
@intAgeint;
CREATETABLE#tempTable
(
PrimaryKey INT NOTNULL ,
ObjectFieldAlias VARCHAR(100),
ObjectAttribute VARCHAR(40),
IsValid BIT,
InvalidReason NVARCHAR(400),
AllowEdit BIT,
NewValue NVARCHAR(200),
);
--Custom Logic
SELECT@PlanId= id_plan
FROM k_m_plans WHERE uid_object =@uidObject;
SELECTTOP1@intAge= age FROM
k_m_plans_payees_steps t1
JOIN Kernel_View_Payee_List t2 ON t2.idPayee = t1.id_payee
WHERE t1.id_step =@idStepAND t1.id_plan =@PlanId;
selectTOP1@TriggerAlias=CONCAT(CAST(@PlanIdAS NVARCHAR(30)), '_', CAST(idIndicator AS NVARCHAR(30)), '_', CAST(idField AS NVARCHAR(30))), @TriggerValue= inputValue
FROM@Values
where isTrigger=1
IF@PlanId=12
BEGIN
SELECT@totalProposedSalary= inputValue FROM@ValuesWHERE idIndicator =54AND idField =61;
select@bonus= inputValue FROM@ValuesWHERE idIndicator =54AND idField =59;
select@baseSalary= inputValue FROM@ValuesWHERE idIndicator =54AND idField =56;
select@increaseAmount= inputValue FROM@ValuesWHERE idIndicator =54AND idField =57;
select@percIncrease= inputValue FROM@ValuesWHERE idIndicator =54AND idField =58;
select@proposedSalary= inputValue FROM@ValuesWHERE idIndicator =54AND idField =60;
IF@TriggerAlias=@bonusAliasAND (@bonusISNOTNULLAND@bonus<>'')
BEGIN
SET@percIncreaseFloat= CAST(@percIncreaseASFLOAT);
-- Proposed salary=BaseSalary+IncreaseAmount
SET@totalProposedSalary= CAST((CAST(@bonusASFLOAT) +CAST(@proposedSalaryASFLOAT)) AS NVARCHAR(MAX));
END
IF@TriggerAlias=@increaseAmountAliasAND (@increaseAmountISNOTNULLAND@increaseAmount<>'')
BEGIN
SET@percIncreaseFloat= (CAST(@TriggerValueASFLOAT)/CAST(@baseSalaryASFLOAT));
SET@percIncrease=CAST(@percIncreaseFloatAS NVARCHAR(MAX));
-- Proposed salary=BaseSalary+IncreaseAmount
SET@proposedSalary= CAST((CAST(@baseSalaryASFLOAT) +CAST(@increaseAmountASFLOAT)) AS NVARCHAR(MAX));
SET@totalProposedSalary= CAST((CAST(@bonusASFLOAT) +CAST(@proposedSalaryASFLOAT)) AS NVARCHAR(MAX));
END
IF@TriggerAlias=@percIncreaseAliasAND (@percIncreaseISNOTNULLAND@percIncrease<>'')
BEGIN
SET@percIncreaseFloat= CAST(@percIncreaseASFLOAT);
SET@increaseAmount=CAST(((CAST(@baseSalaryASFLOAT) *CAST(@TriggerValueASFLOAT))) AS NVARCHAR(MAX));
-- Proposed salary=BaseSalary+IncreaseAmount
SET@proposedSalary= CAST((CAST(@baseSalaryASFLOAT) +CAST(@increaseAmountASFLOAT)) AS NVARCHAR(MAX));
SET@totalProposedSalary= CAST((CAST(@bonusASFLOAT) +CAST(@proposedSalaryASFLOAT)) AS NVARCHAR(MAX));
END
IF ((@TriggerAlias=@increaseAmountAliasAND@increaseAmountISNOTNULLAND@increaseAmount<>'')
OR (@TriggerAlias=@percIncreaseAlias AND@percIncreaseISNOTNULLAND@percIncrease<>'')
OR (@TriggerAlias=@bonusAliasAND@bonusISNOTNULLAND@bonus<>'' ))
BEGIN
-- age < 18, 2%
-- 18<=age<=65, 2%<=%<=5%
-- else 2%
IF (@intAge<18)
BEGIN
If (@percIncreaseFloat=0.02)
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
ELSE
BEGIN-- AGE_BELOW_18_SHOULD_BE_2_PERCENTAGE
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 0, 'When age <18, percentage should be equal to 2 --> here should be translation label', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
--INSERT INTO #tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
END
IF (18<=@intAgeAND@intAge<=65)
BEGIN
If (@percIncreaseFloat>=0.02AND@percIncreaseFloat<=0.05)
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
ELSE
BEGIN-- AGE_18_65_SHOULD_BE_2_5_PERCENTAGE
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 0, 'When age between 50 and 65, percentage should be between 2 and 5 --> here should be translation label', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
--INSERT INTO #tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
END
IF (@intAge>65)
BEGIN
If (@percIncreaseFloat=0.02)
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
ELSE
BEGIN-- AGE_BELOW_65_SHOULD_BE_2_PERCENTAGE
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 0, 'When age >65, percentage should be equal to 2 --> here should be translation label', 1, @percIncrease);
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, @increaseAmount);
INSERTINTO#tempTable VALUES(@idStep, @proposedSalaryAlias, '', 1, '', 1, @proposedSalary);
INSERTINTO#tempTable VALUES(@idStep, @totalProposedSalaryAlias, '', 1, '', 1, @totalProposedSalary);
END
END
END
IF@TriggerAlias=@increaseAmountAliasAND (@increaseAmountISNULLOR@increaseAmount='')
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @increaseAmountAlias, '', 1, '', 1, '');
END
IF@TriggerAlias=@percIncreaseAliasAND (@percIncreaseISNULLOR@percIncrease='')
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @percIncreaseAlias, '', 1, '', 1, '');
END
IF@TriggerAlias=@bonusAliasAND (@bonusISNULLOR@bonus='')
BEGIN
INSERTINTO#tempTable VALUES(@idStep, @bonusAlias, '', 1, '', 1, '');
END
END
select*FROM#tempTable
The same logic is once again executed in the Kernel_SP_Process_PreSave stored procedure:
CREATEPROC [dbo].[Kernel_SP_Process_PreSave]
@idProfileint,
@idUserint,
@idTreeint,
@kMValuesDataAS [dbo].[Kernel_Type_k_m_values] READONLY,
@selectedIdPayeeint,
@idProcessint
AS
Declare@Resultbit=1;
Declare@Message nvarchar(max) =NULL;
Declare@argumentsForError nvarchar(max)=NULL
DECLARE@isValidint=1;
CREATETABLE#tempTableWithMessages
(
idStep INT,
age int,
msg NVARCHAR(540)
);
IF@idProcess=12
BEGIN
----select 0 AS Continue_Flag, 'AGE_IS_WRONG_WITH_PERCENTAGE' as Message, @argumentsForError as ArgumentsForError
-- age < 18, 2%
-- 25<=age<=65, 2%<=%<=5%
-- else 2%
INSERTINTO#tempTableWithMessages(idStep, age, msg)
SELECT t1.id_step AS idStep, t3.age,
CASE
WHEN ((18> t3.age) AND (t1.input_value_numeric <>2)) THEN'AGE_BELOW_18_SHOULD_BE_2_PERCENTAGE'
WHEN ((18<= t3.age AND t3.age <65) AND (t1.input_value_numeric <2AND t1.input_value_numeric >5)) THEN'AGE_18_65_SHOULD_BE_2_5_PERCENTAGE'
WHEN ((65< t3.age) AND (t1.input_value_numeric <>2)) THEN'AGE_BELOW_65_SHOULD_BE_2_PERCENTAGE'
ENDAS msg
FROM@kMValuesData t1
JOIN k_m_plans_payees_steps t2 ON t2.id_step = t1.id_step AND id_plan =@idProcess
JOIN Kernel_View_Payee_List t3 ON t3.idPayee = t2.id_payee
WHERE (id_ind =54AND id_field =58)
AND (
((18> t3.age) AND (t1.input_value_numeric <>2))
OR
((18<= t3.age AND t3.age <65) AND (t1.input_value_numeric <2OR t1.input_value_numeric >5))
OR
((65< t3.age) AND (t1.input_value_numeric <>2))
);
DECLARE@x NVARCHAR(MAX) = (SELECT*FROM@kMValuesDataFOR JSON AUTO);
DECLARE@y NVARCHAR(MAX) = (SELECT*FROM#tempTableWithMessages FOR JSON AUTO);
SELECTTOP1@isValid=0
FROM@kMValuesData t1
JOIN k_m_plans_payees_steps t2 ON t2.id_step = t1.id_step AND id_plan =@idProcess
JOIN Kernel_View_Payee_List t3 ON t3.idPayee = t2.id_payee
WHERE (id_ind =54AND id_field =58)
AND (
((18> t3.age) AND (t1.input_value_numeric <>2))
OR
((18<= t3.age AND t3.age <65) AND (t1.input_value_numeric <2OR t1.input_value_numeric >5))
OR
((65< t3.age) AND (t1.input_value_numeric <>2))
);
INSERTINTO _x_table_log_Kernel_SP_Process_PreSave VALUES(GETDATE(), @idUser, @idProcess, @x, @y, @isValid);
IF@isValid=0
BEGIN
select0AS Continue_Flag, 'You have ('+CAST((SELECTcount(*) FROM#tempTableWithMessages)AS NVARCHAR(MAX)) + ') invalid rows, saving is impossible. Please, fix them.' as Message, @argumentsForError as ArgumentsForError
END
ELSE
BEGIN
select@Resultas Continue_Flag , @Messageas Message, @argumentsForErroras ArgumentsForError
END
END
ELSE
BEGIN
select@Resultas Continue_Flag , @Messageas Message, @argumentsForErroras ArgumentsForError
END