Actions are used to run stored procedures after some event occurs, like editing cell, row insert or manual action selection. To add an action in workspaces you need to complete the following steps:
de_action_sps
de_actions
.data_element_actions
if the action should run after the standard update actionde_action_sps
In this control table we configure the database actions to be able to use them in workspace actions, inline actions, insert actions or update actions.
id
(int) - required: The id of the action you are adding, should be within your selected id rangename
(nvarchar) - required: The name you give your action, must be uniquecaption
(nvarchar) - required: The name that is shown in the system. You can either hard code the caption or enter a translation key that references the translations in the systemdescription
(nvarchar) - not required: shows up in the system when you use workspace actions. Always good to populate this with a clear description for documentation purposes.sp_schema
(nvarchar) - required: The database schema that the store procedure belongs to.sp_name
(nvarchar) - required: The name of the stored procedure in the database.de_actions
The de_actions
table contains the Actions that show up within the AGR View settings in the system and can be added into a view.
id
] (int): the id of the action, must be uniquename
]: the name you give the actioncaption
] (nvarchar): The name that is shown in the system. You can either hard code the caption or enter a translation key that references the translations in the systemdescription
] (nvarchar) - not required: appears when user clicks the question mark in View Settings in the systemtype
] (nvarchar) - not required: probably not used in the code, is always populated with nullgroup_id
] (int): (required): The id of the group you want to put your action. The group_id references the table data_element_groups. The group you select must be connected to the workspace type so that the action will appear in the systemaction_json
] (nvarchar) - required: Action settings are defined in this column in JSON format. Information about available properties are shown below and it can also be useful to look at the example actions provided in the database.available
] (int): 1 if the action should be available in the system; 0 otherwiseselected_by_default
]): 1 if the action should be selected into the grid settings by default, 0 otherwisedbo.de_actions
Action settings is defined in dbo.de_actions.action_json
:
clientData
(object): …
gridSelection
(string) ‘all’: … [The default value is ‘all’ but this parameter can’t be omitted 🤔]actionArgs
: (object): Arguments to pass to stored procedure.
includeAffected
(boolean): …longRunningAction
(boolean): Start the action without waiting for it to complete (the API will not wait).refreshGridAfterAction
(boolean): Sync grid data when this action finishes. Note: There is a known issue in the app that causes the drill-down to load incorrectly if refreshGridAfterAction and transferView is used at the same time. See transferView.serieOverrideFromArg
(string): Matches name in modal operation selected options field (operations.fields.name)serieOverride
???operations
(array of objects): …
name
(string): …
type
(“modal” | “lockAffectedCells” | “sp” | “refreshAffectedCells” | “unlockAffectedCells” | “refreshView”):
fields
(array of objects): (Only if the type = "modal"
) Form fields in modal. Contains an array for all of the fields that should be presented to the user in a modal when the action is executed. Each object describes one field and will result in the user selected value to be sent to the server for the action of type “sp”.
name
(string): Used to create form object in code. Must be unique in this form.caption
(string): Field title.description
(string): Optional. Will add a (?) icon after caption that user can click and see description.type
(“select” | “text” | “int” | “string” | “date”): Type of input element in form. E.g. use “select” to get dropdown.required
(boolean): Required fields must be set before modal is submitted.defaultValue
(string): Add default value to input.optionValues
(array of objects): (Only used if type="select"
). Options to show in dropdown.
name
(string): The value set when option is selected.
caption
(string): The option title displayed in dropdown.optionsFrom
(“viewDataSeries” | “planDataSeries”): (Only used if type="select"
). The options in the select should consists of:
multiple
(boolean): (Only used if type="select"
). Whether the select should have multiple selections possible. When false
(default) only one option can be selected. When true
more than one option can be selected. In this case, when the action is executed, the value sent to the server will be an array of selected name values (instead of just the selected name value).optionsQuery
(object with three values): (Only used if type="select"
). Specifies that the option values should be acquired by a query executed when the modal box is presented to the user. The values that are required are the following:
nameColumn
(string): The name of the column in the query result that provides the name value of optionscaptionColumn
(string): The name of the column in the query result that provides the caption value of optionsquery
(DeQuery): The query to be executed to get the option values for the select.Example:
Note: When type
is set to "date"
the date value is delivered to the backend as json. To get the value into a TSQL date value the following is needed
DECLARE @date_val date, @date_raw nvarchar(max)
SET @date_raw = dbo.fn_de_act_get_arg(@query, 'dateField')
SELECT @date_val = DATEFROMPARTS(year_val, month_val, day_val)
FROM OPENJSON(JSON_QUERY(@date_raw))
WITH ( year_val int '$.year', month_val int '$.month', day_val int '$.day' )
{
"clientData": {
"gridSelection": "all",
"actionArgs": {
"refreshGridAfterAction": true,
"includeAffected": true,
"serieOverride": {
"readFromArg": "viewSerie"
}
}
},
"operations": [
{
"type": "modal",
"fields": [
{
"name": "viewSerie",
"caption": "View serie",
"type": "select",
"optionValues": "viewDataSeries",
"description": "Selection of any of the data series visible in the view"
},
{
"name": "planSerie",
"caption": "Plan serie",
"type": "select",
"optionValues": "planDataSeries",
"description": "Selection of any of the data series in the plan",
"required": true
},
{
"name": "customOptions",
"caption": "A custom list of options",
"type": "select",
"optionValues": [
{
"name": "val1",
"caption": "Value 1"
},
{
"name": "val2",
"caption": "Value 2"
},
{
"name": "val3",
"caption": "Value 3"
}
],
"defaultValue": "val2",
"description": "Another long description of this field"
},
{
"name": "intField",
"caption": "Running time (sec)",
"type": "int",
"description": "This is an integer value"
},
{
"name": "stringField",
"caption": "String Value",
"type": "string",
"description": "This is a string value"
}
]
},
{
"type": "sp",
"name": "test"
}
]
}
data_element_actions
This table is configured when we want a custom procedure to run after the standard update procedure. From version 6.2 this table is only used for column update, series update is configured through the data_element_series table
action_name
(nvarchar) - required: The name of the action this execution will follow, usually “update_action”data_element_type
(nvarchar) - required: series or column, depending on weather this should run after a column update or series updatedata_element_name
(nvarchar) - required: The name of the data_element or the data_element_ref_column that is being updatedde_action_sp_name
(nvarchar) - not required: The name of the action sps, from de_action_spsord
(int) - required: If you configure many actions to run after the update, here you define the order they should run inThe procedure that is run as an action needs to have the required input parameters and it needs to return some output. The @query
is sent to the procedure by the system and contains the JSON XXXX needed to know the scope the action should be run for. The procedure needs to start by parsing the JSON into variable that can be used in the database. THis is always done in the same way so use one of the example procedurse below when creating your action.
In this chapter you can find a few examples of stored procedures that can be used in different scenarios in the system.
The arguments specified in actionArgs
will be available in the @query
parameter in the stored procedure. For example, to pass a specific integer value from action to the store procedure:
"actionArgs": { "intValue": 42 }
Now the stored procedure one can access the value with:
DECLARE @int_value INT
SET @int_value = CONVERT(INT, dbo.fn_de_act_get_arg(@query, 'intValue'))
The backend store procedure executed by the action can provide a refreshed row values. To do this it must return a "data"
property with the value ["refreshedRow"]
and provide a second result set containing the refreshed values. An example of this would be:
SELECT '{"result": "OK", "caption": "Everything executed fine!", "data": ["refreshedRow"]}' AS result
SELECT 'Some new value' AS item_group_lvl_1, 'Also new' AS item_group_lvl_2
It is also possible to instruct the client to delete the row in the grid, typically used for delete actions. This is an example of this:
SELECT '{"result": "OK", "caption": "deleteRow test", "deleteRow": true}' as result, 1 as deleteRow
It is also possible to instruct the client to transfer to another view when the action is executed. Note: If transferView
is used, the refreshGridAfterAction
property in de_actions.action_json.clientData.actionArgs
should be false
. Otherwise the drill-down can load incorrectly.
Example:
SELECT '{"result": "OK", "caption": "deleteRow test", "transferView": "name-of-view-to-transfer-to"}' as result, 1 as deleteRow
In addition to transferring to another view, the drilldowns on that view can also be set by including them in the result. This is an example of how to do that:
SELECT '{"result": "OK", "caption": "deleteRow test", "transferView": "name-of-view-to-transfer-to", "drilldowns":{"location": 2, "some_other_drilldown": 666}}' as result, 1 as deleteRow
When the user triggers this action from the User Interface the procedure should run only for the items (scope) that the user has filtered on in the workspace. For example if a user has filtered on 3 item numbers running the action should only affect these 3 item numbers
dbo.de_action_sps
and then add it to dbo.de_actionsCREATE PROCEDURE [dbo].[de_act_example_a]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
@query NVARCHAR(MAX),
@user_id INT,
@debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
-- which you find by running your action in the system and catching the action from watching live data in Extended Events
--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_baseline","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[-6,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false}},{"column":{"name":"location_id"},"operator":"=","values":10}],"columns":[{"name":"item_no"},{"name":"primary_vendor"}],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
--@user_id INT = 1,
--@debug BIT = 0
-- declaring some variables and then parsing the query string
DECLARE @series de_qb_data_series,
@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX), @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)
-- only for debugging
DECLARE @msg NVARCHAR(500)
IF @debug = 1 PRINT 'VALIDATING JSON:'
IF ISJSON(@query) = 0
BEGIN
SET @msg = 'Invalid JSON request'
SELECT dbo.json_error(@msg) as result
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
RETURN
END
-- I recommend that you comment out the select statements below each parsing part to see what is stored in the @filters/@seris/@columns
-- variables then you can see that all the filters/drilldowns/series/columns from your view in the workspace are stored in the variables.
-- parsing filters
INSERT INTO @filters
SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @filters
-- parsing series
INSERT INTO @series
SELECT * FROM [dbo].[fn_de_qp_series](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @series
--parsing columns
INSERT INTO @columns
SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @columns
--now let's get all the joins and filters on string format. You can comment out the select statements (@table_joins/@where_filter)
-- to see what is in the string variables
EXEC [de_qb_sql_table_joins] @columns,NULL,@filters,@user_id, @table_joins OUT
--SELECT @table_joins
EXEC [dbo].[de_qb_sql_where_filter_clause] @filters, 1, @where_filter OUT
--SELECT @where_filter
-- YOU SHOULD ONLY NEED TO ADJUST/CHANGE THE CODE BELOW THIS COMMENT
-- Finally the parsed data must be combined and I recommend using temp table which stores all the data in the view/grid
-- and then you can do whatever you want to do/update regarding the data.
--creating temp table that in my case I call update_table. Adjust the name and columns to your need based on what is in your view/grid
IF OBJECT_ID('tempdb..#update_table') IS NOT NULL
DROP TABLE #update_table
CREATE TABLE #update_table([item_id] INT,
[item_no] NVARCHAR(MAX),
[location] NVARCHAR(100),
[primary_vendor] NVARCHAR(100),
[first_date_id_of_month] INT)
-- Now I use the sql string to insert into the temp table. Here you have to change the columns you are inserting into based on the columns
-- in the table you created above as well as selecting the right columns from right tables.
-- The table names always start with 'DET' and then the id of the table in data_element_ref_tables. For example the items table has id=1 in
-- dbo.data_element_ref_tables which means that all the columns you want and need from the items table come from DET1 e.g. DET1.id is
-- the item_id
SELECT @sql = 'INSERT INTO #update_table ([item_id], [item_no], [location], [primary_vendor], [first_date_id_of_month])
SELECT DISTINCT DET1.id, DET2.item_no, DET4.name, DET5.name, DET0.first_date_id_of_month' -- change this row and the row above
+ CHAR(10) + @table_joins + CHAR(10) + CASE WHEN LEN(@where_filter) = 0 THEN '' ELSE @where_filter END -- should not need to change this row
-- comment out the statement below to see your complete string and try running it if you want
--PRINT @sql
EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the update_table
-- comment out the statement below to see that you have all the required data from your view/grid now stored in a temp table
--SELECT * FROM #update_table
-- do whatever you need to do (update values, insert new rows, run some procedures) below by
-- joining with/using the data in the temp table you created above.
----------------------------------------------------------------------
----------------------------------------------------------------------
SELECT dbo.json_ok('Everything executed fine!') as result -- the procedure must return a result, like this. You can change the result message if you want.
END TRY
BEGIN CATCH
DECLARE @sp_name NVARCHAR(500)
SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE OBJECT_ID = @@PROCID
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
SELECT dbo.json_error('Action could not be executed') AS result
END CATCH;
END
GO
When the user triggers this action from the client the procedure should run only for the items (scope) that the user has filtered on in the workspace. For example if a user has filtered on 3 item numbers running the action should only affect these 3 item numbers. In addition, in this scenario, we want the user to be able to select one of three options (A,B or C) before the action is run.
dbo.de_action_sps
and then add it to dbo.de_actions
For this scenario the following config can be inserted into the de_actions table {"clientData":{
"gridSelection":"all",
"actionArgs":{
"includeAffected":true,
"serieOverride":{
"readFromArg":"viewSerie"
}
}
},
"operations":[
{
"type":"modal",
"fields":[
{
"name":"customOptions",
"caption":"A custom list of options",
"type":"select",
"optionValues":[
{
"name":"optionA",
"caption":"A"
},
{
"name":"optionB",
"caption":"B"
},
{
"name":"optionC",
"caption":"C"
}
],
"defaultValue":"optionA",
"description":"Another long description of this field"
}
]
},
{
"type":"sp",
"name":"update_sales_plan_by_locations"
}
]
}
CREATE PROCEDURE [dbo].[de_act_example_b]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
@query NVARCHAR(MAX),
@user_id INT,
@debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
-- which you find by running your action in the system and catching the action from watching live data in Extended Events
--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"viewSerie"},"customOptions":"optionA","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[1,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false,"class":"Oe"}},{"column":{"name":"location_id"},"operator":"=","values":[10]}],"columns":[],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
--@user_id INT = 1,
--@debug BIT = 0
-- declaring some variables and then parsing the query string
DECLARE @series de_qb_data_series,
@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX), @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)
-- only for debugging
DECLARE @msg NVARCHAR(500)
IF @debug = 1 PRINT 'VALIDATING JSON:'
IF ISJSON(@query) = 0
BEGIN
SET @msg = 'Invalid JSON request'
SELECT dbo.json_error(@msg) as result
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
RETURN
END
-- I recommend that you comment out the select statements below each parsing part to see what is stored in the @filters/@seris/@columns
-- variables then you can see that all the filters/drilldowns/series/columns from your view in the workspace are stored in the variables.
-- parsing filters
INSERT INTO @filters
SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @filters
-- parsing series
INSERT INTO @series
SELECT * FROM [dbo].[fn_de_qp_series](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @series
--parsing columns
INSERT INTO @columns
SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
--SELECT * FROM @columns
--now let's get all the joins and filters on string format. You can comment out the select statements (@table_joins/@where_filter)
-- to see what is in the string variables
EXEC [de_qb_sql_table_joins] @columns,NULL,@filters,@user_id, @table_joins OUT
--SELECT @table_joins
EXEC [dbo].[de_qb_sql_where_filter_clause] @filters, 1, @where_filter OUT
--SELECT @where_filter
-- YOU SHOULD ONLY NEED TO ADJUST/CHANGE THE CODE BELOW THIS COMMENT
----------------------------------------------------------------------------------------------
-- HERE YOU HAVE TO DO DO CHANGES RELATED TO THE PARAMETERS IN YOUR ACTION
-- Declaring variable that will store the option chosen in the action (A,B,C in my case)
-- If your action has int/string variables and more than one parameter you can declare your variables here for that
DECLARE @option NVARCHAR(MAX)
-- Here I'm looking at the arguments from the query and storing the option chosen into the @option variable
-- Example: if the user has chosen option A when running the action, the @option variable would be 'A'
-- If you have other types of parameters I recommend that you run the following query: SELECT * FROM @q_args
-- There you should see your parameters and in that way figure out how to catch them
SET @option = JSON_VALUE(@query, '$.args.customOptions')
--------------------------------------------------------------------------------------------------------
-- Finally the parsed data must be combined and I recommend using temp table which stores all the data in the view/grid
-- and then you can do whatever you want to do/update regarding the data.
--creating temp table that in my case I call item_list. Adjust the name and columns to your need based on what is in your view/grid
IF OBJECT_ID('tempdb..#item_list') IS NOT NULL
DROP TABLE #item_list
CREATE TABLE #item_list([item_id] INT)
-- Now I use the procedure [dbo].[de_qb_sql_specific_columns_select] to give me the column item_id (dbo.data_element_ref_columns) using the @query and insert into the ids into the temp table. Here you have to change the columns you are inserting into based on the columns
-- in the table you created.
EXEC dbo.de_qb_sql_specific_columns_select @query=@query, @user_id= @user_id, @debug = @debug, @columns_comma_list = 'item_id', @distinct = 1, @sql = @sql OUT
SET @sql = 'INSERT INTO #item_list (item_id) ' + @sql + ';'
-- comment out the statement below to see your complete string and try running it if you want
PRINT @sql
EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the item_list
-- comment out the statement below to see that you have all the required data from your view/grid now stored in a temp table
--SELECT * FROM #item_list
-- do whatever you need to do (update values, insert new rows, run some procedures) below by
--joining with/using the data in the temp table you created above.
----------------------------------------------------------------------
-- Uncomment below if you are using the parameters from the action
--IF @option = 'A'
--BEGIN
-- -- do something
--END
--IF @option = 'B'
--BEGIN
-- -- do something
--END
--IF @option = 'C'
--BEGIN
-- -- do something
--END
-- the procedure must return a result message. Lets have the result message display what option was selected and the number of item_ids behind the grid data.
DECLARE @result_msg NVARCHAR(MAX)
SELECT @result_msg = 'item_id count: '+ CAST(COUNT(il.item_id) AS NVARCHAR)+CHAR(10)+'option selected: '+@option
FROM #item_list il
----------------------------------------------------------------------
--SELECT dbo.json_ok('Everything executed fine!') as result
SELECT dbo.json_ok(@result_msg) as result
END TRY
BEGIN CATCH
DECLARE @sp_name NVARCHAR(500)
SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE OBJECT_ID = @@PROCID
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
SELECT dbo.json_error('Action could not be executed') AS result
END CATCH;
END
GO
When a user edits a value in a data series in the standard setup it is updated through the procedure ‘dbo.de_update_batch_series’ distributing the values either
• Flat if nothing has been entered in or stored in the data series before
• Proportionally based on what was entered in that data series before if anything has been entered.
In this example we want to distribute the value entered on total level in a particular data series proportionally based on another data series. Therefore we will create another action and config the series to run your new action in stead of the standard one.
Let‘s say we want to distribute promotions based on the forecast. Currently if you enter in 1000 in promotions on total level for all items it will spread equally (333 for each item) but we want it to distribute based on the forecast proportions like the screenshot below
dbo.de_action_sps
INSERT INTO dbo.de_action_sps
select 'update_value_example_c','Distribute Promo based on Forecast','','dbo','de_act_example_c'
dbo.data_element_series
table or in the view settings of a workspace in the AGR client).
Change the config on your data series and make sure that when the data series is edited your action will run instead of the
standard update_value action. The action in the config is the name in the dbo.de_action_sps table{
"edit": {
"enabled": true,
"action": "update_value_example_c",
}
}
CREATE PROCEDURE [dbo].[de_act_example_c]
-- the following parameters must be included when creating all 'planner' actions that are used in dbo.de_action_sps
(
@query NVARCHAR(MAX),
@user_id INT,
@debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
-- which you find by running your action in the system and catching the action from watching live data in Extended Events
--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_baseline","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[-6,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false}},{"column":{"name":"location_id"},"operator":"=","values":10}],"columns":[{"name":"item_no"},{"name":"primary_vendor"}],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
--@user_id INT = 1,
--@debug BIT = 0
-- declaring some variables and then parsing the query string
DECLARE @series de_qb_data_series,
@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX), @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)
-- only for debugging
DECLARE @msg NVARCHAR(500)
IF @debug = 1 PRINT 'VALIDATING JSON:'
IF ISJSON(@query) = 0
BEGIN
SET @msg = 'Invalid JSON request'
SELECT dbo.json_error(@msg) as result
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
RETURN
END
-- the easiest way to use proportion from another data series is to do the following:
-- 1. copy from the data series you want to use the proportion from into your data series by using copy values
-- 2. then use the standard update action to spread proportionally.
-- creating two new variables which we use when we call the copy values action
DECLARE @new_query NVARCHAR(MAX), @q_arg_new NVARCHAR(MAX)
-- change the copyFrom and copyTo data series if you are not using forecast and promotions
SET @q_arg_new = '{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_promotions","displaySuccessfulNotification":true}'
-- extract only the query part (not the arguments since we are using the arguments in @q_arg_new
-- uncomment the 'select @new_query' below and compare it with 'select @query' to see the difference
SELECT @new_query = JSON_QUERY(@query, '$.query')
-- select @new_query
-- combine the arguments and query parts
SET @new_query = @q_arg_new + ',"query":' + @new_query + '}'
-- run the copy values action with the new query
exec de_execute_action @de_action_name=N'copy_serie',@query = @new_query, @user_id = @user_id
-- finally run the batch update with the original query.
exec de_execute_action @de_action_name=N'batch_update',@query=@query,@user_id=@user_id
----------------------------------------------------------------------
SELECT dbo.json_ok('Everything executed fine!') as result -- the procedure must return a result, like this. You can change the result message if you want.
END TRY
BEGIN CATCH
DECLARE @sp_name NVARCHAR(500)
SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE OBJECT_ID = @@PROCID
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
SELECT dbo.json_error('Action could not be executed') AS result
END CATCH;
END
GO
When a user edits a value in a column in a workspace in the standard setup it is updated through the procedure ‘dbo.de_update_column’.
This action will make the forecast invalid when a user will edit the ABC column in the system. That is done by running action after the standard update action has updated the ABC column. Therefore we will create another action and config the column to run your new action after the standard one.
Steps
dbo.de_actions_sps
INSERT INTO data_element_actions (action_name, data_element_type,data_element_name,de_action_sp_name,ord) VALUES (‘update_value’,‘column’,‘abc’,‘de_act_example_d’,1)
CREATE PROCEDURE [dbo].[de_act_example_d]
-- the following parameters must be included when creating all 'planner' actions that are used in dbo.de_action_sps
(
@query NVARCHAR(MAX),
@user_id INT,
@debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
-- which you find by running your action in the system and catching the action from watching live data in Extended Events
-- DECLARE @query NVARCHAR(MAX) = N'{"args":{"newValue":"A","oldValue":"C","targetColumn":"abc","clientColumnChanged":"abc"},"query":{"filters":[{"column":{"name":"location_id"},"operator":"=","values":10},{"column":{"name":"master_item_id"},"operator":"=","values":86},{"column":{"name":"master_item_id"},"operator":"=","values":86}],"columns":[{"name":"item_no"},{"name":"name"},{"name":"abc"}],"series":[]}}',
-- @user_id INT = 1,
-- @debug BIT = 0
-- declaring some variables and then parsing the query string
DECLARE @series de_qb_data_series,
@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX), @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX),@q_createdObj pjsonData
-- only for debugging
DECLARE @msg NVARCHAR(500)
IF @debug = 1 PRINT 'VALIDATING JSON:'
IF ISJSON(@query) = 0
BEGIN
SET @msg = 'Invalid JSON request'
SELECT dbo.json_error(@msg) as result
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
RETURN
END
-- I recommend that you comment out the select queries below each parsing to see what is stored in the variables
-- parsing filters
-- INSERT INTO @filters
-- SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
-- SELECT * FROM @filters
--parsing columns
-- INSERT INTO @columns
-- SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
-- SELECT * FROM @columns
--creating temp table that in my case I call item_list. Adjust the name and columns to your need based on what is in your view/grid
IF OBJECT_ID('tempdb..#item_list') IS NOT NULL
DROP TABLE #item_list
CREATE TABLE #item_list([item_id] INT)
-- Now I use the procedure [dbo].[de_qb_sql_specific_columns_select] to give me the column item_id (dbo.data_element_ref_columns) using the @query and insert into the ids into the temp table. Here you have to change the columns you are inserting into based on the columns
-- in the table you created.
EXEC dbo.de_qb_sql_specific_columns_select @query=@query, @user_id= @user_id, @debug = @debug, @columns_comma_list = 'item_id', @distinct = 1, @sql = @sql OUT
SET @sql = 'INSERT INTO #item_list (item_id) ' + @sql + ';'
EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the item_list
-- Finally we update the forecast table, making sure that the valid field is set to 0 for only the item that was updated.
UPDATE f
SET f.valid = 0
FROM dbo.forecasts f
INNER JOIN #item_list il ON f.item_id = il.item_id
----------------------------------------------------------------------
-- refreshing and returning the results
SELECT '{"result": "OK", "caption": "ABC was updated and Forecast made Invalid"}' AS result
END TRY
BEGIN CATCH
DECLARE @sp_name NVARCHAR(500)
SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE OBJECT_ID = @@PROCID
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
SELECT dbo.json_error('Action could not be executed') AS result
END CATCH;
END
In this example we want for some reason to be able to add new locations through the system. This can be done with a insert action which will add the new location into the location table.
dbo.de_actions_sps
table{
"insert": {
"preInsertQuery": {
"columns": [
{
"name": "location"
}
]
},
"enabled": true,
"requiredColumns": [
"location"
],
"action": "add_location"
},
"row": {
"autoRefresh": true,
"refreshWholeGrid": true
},
"name": "new-locations"
}
Create the template procedure in the database (see below) and add the action into dbo.de_actions
Adjust the action procedure for your needs and finally try adding in new locations from the system
CREATE PROCEDURE [dbo].[de_act_example_e]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
@query NVARCHAR(MAX),
@user_id INT,
@debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
-- which you find by running your action in the system and catching the action from watching live data in Extended Events
-- DECLARE @query NVARCHAR(MAX) = N'{"args":{"createdObj":{"location":"new location","location_id":10,"rowNodeId":23,"inserted":true,"submitting":true,"isFirstLine":true}},"query":{"filters":[],"columns":[{"name":"location"}],"series":[]}}',
-- @user_id INT = 1,
-- @debug BIT = 0
-- declaring some variables and then parsing the query string
DECLARE @series de_qb_data_series,
@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX), @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)
-- only for debugging
DECLARE @msg NVARCHAR(500)
IF @debug = 1 PRINT 'VALIDATING JSON:'
IF ISJSON(@query) = 0
BEGIN
SET @msg = 'Invalid JSON request'
SELECT dbo.json_error(@msg) as result
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
RETURN
END
--parsing query
-- I recommend uncommenting out the select query below this comment. There you will see what you need to catch from the system.
-- SELECT * FROM @q_createdObj
-- Declaring variables to store the new location name and the highest location id in the location table
-- so that we make sure that we add higher location id into the table.
DECLARE @max_location_id INT,@new_location NVARCHAR(255), @new_location_id INT
-- storing the new location name that was entered in the system into the @new_location variable.
SET @new_location = JSON_VALUE(@query, '$.args.createdObj.location')
-- finding highest location id in the location table
SET @max_location_id = (SELECT MAX(id) FROM dbo.locations)
-- The new location id will get one higher integer number than the highest location id
SET @new_location_id = @max_location_id + 1
-- Adding the new location into the locations table
INSERT INTO dbo.locations
VALUES(@new_location_id,'N0' + CAST(@new_location_id AS NVARCHAR(100)), @new_location,'store',NULL,GETDATE(),GETDATE())
----------------------------------------------------------------------
-- refreshing and returning the results
SELECT '{"result": "OK", "caption": "Location Added", "data": ["refreshedRow"]}' AS result
SELECT @new_location_id AS location_id, @new_location AS location
END TRY
BEGIN CATCH
DECLARE @sp_name NVARCHAR(500)
SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
WHERE OBJECT_ID = @@PROCID
EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
SELECT dbo.json_error('Action could not be executed') AS result
END CATCH;
END
GO