Building a Workflow Map Diagram that Shows Progress through Workflow

Hints, Tips and Tricks

Technical Hints Tips and Tricks that cover customization and development using Sage CRM. API usage and coding are covered.

Building a Workflow Map Diagram that Shows Progress through Workflow

  • Comments 6
  • Likes

Workflows in Sage CRM can become long and a user may wish they had a version of the diagram that is available when the workflow is constructed.  You can see from this image below that an extra button has been added to the Opportunity screen.

 

In this example application, when the 'Workflow Map' button is clicked, the current opportunity is checked to see if the record belongs to an workflow.  It the opportunity is attached to a workflow it determines which workflow has been used and produces a diagram.  The current workflow state is then highlighted (in red) in the created diagram.  The user is also given a primt button which will allow the page to be printed and a continue button that will return the page to the opportunity summary page.

An entity like opportunity may have multiple workflows available to it.  But an individual opportunity record may be attached to only one workflow.  The example custom page used here will detect which workflow diagram should be displayed.

Recreating this Feature

Note: Due to authorization requirements this feature needs to be restricted to either Info Managers with Workflow Rights or Full System Administration rights.  This is controlled within the area

Administration -> Users -> Users

Building the Button 

The button on the Opportunity Summary screen has been built by creating a new Button Group for the Opportunity Summary system action.

Administration -> Advanced Customization -> Button Groups

To ensure that the button only displays when

  1. The Opportunity is attached to a workflow and
  2. The User has the correct rights (either Info Admin with Workflow or Full Administrator)

The following Tab SQL Clause has been used:


oppo_workflowid is not null; user_per_infoadmin like '%user_per_workflow%' or user_per_admin=3
 

The Tab option calls the ASP page "showworkflow.asp".

The ASP Page

The code for the ASP page is show here:

 


<!-- #include file ="sagecrm.js"-->
<%
//this example will only work if the user has full admin rights or is an info manager with workflow rights.
var DominentKeyId = Request.Querystring("Key0");
//trick used to ensure value is string and not object
DominentKeyId = DominentKeyId+"";
var strEntity
switch (DominentKeyId)
{
case "1": strEntity="company";
break;
case "2": strEntity="person";
break;
case "6": strEntity="communication";
break;
case "7": strEntity="opportunity";
break;
case "8": strEntity="cases";
break;
case "68": strEntity="solutions";
break;
default: strEntity="";
}
//get workflow information
var TableRecord = CRM.FindRecord("custom_tables","bord_name='"+strEntity+"'");
var intWorkflowInstId = CRM.GetContextInfo(strEntity,TableRecord.bord_workflowidfield);
var WorkflowRecord = CRM.FindRecord("workflowinstance","wkin_instanceid="+intWorkflowInstId);
var intWorkflowId = WorkflowRecord.wkin_workflowid;
var WorkflowStateRecord = CRM.FindRecord("workflowstate","wkst_stateid ="+WorkflowRecord.wkin_currentstateid);
var strWorkflowState = WorkflowStateRecord.wkst_name;
//build path to diagram
//workflow diagram action is 1308
var strPath = new String(Request.ServerVariables("HTTP_REFERER"));
var arrayContext = strPath.split("?");
var strContextInfo = arrayContext[0];
var strSID = new String(Request.QueryString("SID"));
var strFullPath= strContextInfo+"?SID="+strSID+"&Act=1308&Mode=1&CLk=&Key0=47&Key47="+intWorkflowId+"&ex=&Err=";
//build 'wakeup' path
//this is needed to set the system ready to return the diagram
var strWakeUpPath= strContextInfo+"?SID="+strSID+"&Act=428&Mode=1&CLk=&Key0=47&Key47="+intWorkflowId+"&ex=&Err=";
//issue 'wakeup' call to CRM
var xml, text
//Instantiate the Object to carry out the XML request.
xml = Server.CreateObject("MSXML2.ServerXMLHTTP.3.0")
xml.open("GET", strWakeUpPath, 0);
//Response.Write(strWakeUpPath);
xml.send("");
//fetch the workflow diagram
//Instantiate the Object to carry out the XML request.
//xml = Server.CreateObject("MSXML2.ServerXMLHTTP.3.0")
xml.open("GET", strFullPath, 0);
//Response.Write(strFullPath);
xml.send("");
var text = String(xml.responseText);
xml = "";
//format the returned HTML to strip out unwanted buttons etc
var arrayTextByBody = text.split("<BODY");
if (arrayTextByBody[1])
{
var intPosBodyClose = arrayTextByBody[1].indexOf(">");
var strTextStrippedofBody = arrayTextByBody[1].substr(intPosBodyClose+1);
var arrayTextByEndBody = strTextStrippedofBody.split("</FORM>");
var strDiagram = arrayTextByEndBody[0];
//add in highlight for current state
var searchExpression = new RegExp(strWorkflowState, "gi");
var strHighlighting = "<style>.currentstate{color:red; font-style: italic;font-variant: small-caps}</style><span class=currentstate>"+strWorkflowState+"</span>";
var strDiagram = strDiagram.replace(searchExpression, strHighlighting);
}
else
{
var strDiagram = "Time Out Error";
}
var contentBlock = CRM.GetBlock("content");
contentBlock.contents = strDiagram;
var myBlockContainer = CRM.GetBlock("Container");
var strPrintButton = CRM.Button("print", "print.gif", "javascript:window.print()");
var strContinueButton = CRM.Button("continue", "continue.gif", Request.ServerVariables("HTTP_REFERER"));
with (myBlockContainer)
{
AddBlock(contentBlock);
DisplayButton(Button_Default) = false;
AddButton(strContinueButton);
AddButton(strPrintButton);
}
CRM.AddContent(myBlockContainer.Execute());
Response.Write(CRM.GetPage());
%>


Notes

 

  1. The Page is designed to be reused in any of the main system entities that use Workflow.  At the beginning of the code there is a switch case statement that is used to detect which context the page is being used in. 
  2. The page uses a serverside HTTP request to fetch the diagram from within CRM. The diagram is part of the workflow administration screens.
  3. Two web requests have to be made to produce the diagram, this is because the diagram is not usually expected to be called except within the system administration screens. 
  4. The HTML returned has to be processed to removed the edit buttons.
  5. The current workflow state is then highlighted by adding additional style information into the HTML.
  6. The diagram HTML is added to a CRM content block and in turn is added to a container.
  7. The print and the continue buttons are added to the contain and the page is returned.
Comments
  • Hi Jeff,

    I have tried to implement this but I can only get it to work when the User connects to CRM on the actual web server (localhost).

    When they connect normally (even using the Admin account) I get the "Time Out Error" message.

    In the System Log there is an entry for:

    IP Address mismatch.  User Id:admin. Session RemoteAddress:<USERS LOCAL IP ADDRESS> <-> Request RemoteAddr:<CRM SERVER IP ADDRESS>

    I haven't modified your code at all.

    Any help?

  • Hi Jeff,

    this is a good work when it would run. But at the moment there is still the issue Hawkins mentioned.

    Could you have a look into?

    Christian

  • I've tidied up the code a little.  BUT I am now getting the time out message when testing from another machine.  I would have to go back to the beginning.  I don't have a fast answer for this.

  • Is it possible to simply print the workflow map from the administrator view in Sage CRM 7.3? I want to have a printed copy to share and discuss with managers about making workflow edits, so I don't need to show the current state within the workflow, I simply want to show the workflow itself.

  • i tried this example on a 2018 instance and get timeout error, what is the reason for this timeout ?

  • Not sure if this is still relevant, seeing as no-one has a working solution yet.

    I built this guy a little while ago, we only work-flowed the opportunity entity, it's easy to jack up and make dynamic.

    Add a popup button to your button block that calls the asp via custom content:

    HERE'S THE ASP PAGE:

    <!-- #include file ="../sagecrm.js"-->

    ///////////////////////////////////////////////////////////

    try

    {

    //Declare variables

    var SID

    var OppoId

    var OppoRec

    var WinID

    var WfRec

    var WfID

    var StateId

    var StateName

    var StateRec

    var StateDesc

    var contentBlock = CRM.GetBlock("content");

    //Get context and records

    //Session ID

    SID = Request.QueryString("SID");

    //Opportunity ID and associated workflow record - you can elaborate and define other entities too

    OppoId = CRM.GetContextInfo("Opportnity","oppo_opportunityID");

    if (!OppoId){

    OppoId = Request.QueryString("Key7");}

    OppoRec = CRM.FindRecord("Opportunity", "Oppo_opportunityid ="+OppoId);

    if (!OppoRec.eof){

    WinID = OppoRec.oppo_workflowID;}

    //Get workflow Records: Workflow, state, description etc.

    WfRec = CRM.FindRecord("WorkflowInstance", "wkin_instanceid ="+WinID);

    if (!WfRec.eof){

    WfID = WfRec.wkin_workflowid;

    StateId = WfRec.wkin_currentstateID;}

    var StateRec = CRM.FindRecord("WorkflowState","wkst_stateID="+StateId);

    if (!StateRec.eof){

    Statename = StateRec.WkSt_name;

    StateDesc = StateRec.Wkst_description

    if(StateDesc == undefined){

    //If state description is undefined pass a generic message

    StateDesc = "Sorry I can't help any more right now. Kindly inform your admin to update the workflow description"}}

    //Start buiding HTML to inject into page

    //overlay prevents unecessary hyperlinking/clicks etc.

    var strDiagram ='<div id="target-div">Loading Please Wait. . .</div>';

    //create Onload Function to inject code

    strDiagram +="";

    //populate ontent block and call page

    contentBlock.contents = strDiagram;

    CRM.AddContent(contentBlock.Execute());

    Response.Write(CRM.GetPage());

    }

    catch(exception)

    {

    //Your Error handling code code goes here

    Response.Write(CRM.GetTabs());

    Response.Write('<table class="content"><tbody><tr><td colspan="2" class="gridhead"><b>There has been an error</b></td></tr></tbody></table>');

    Response.Write("<table><tbody><tr><td class="row1"><b>Error Name:</b> </td><td class="row1">"+exception.name+"</td></tr></tbody></table>")

    Response.Write("<table><tbody><tr><td class="row1"><b>Error Number:</b> </td><td class="row1">"+exception.number+"</td></tr></tbody></table>")

    Response.Write("<table><tbody><tr><td class="row1"><b>Error Number:</b> </td><td class="row1">"+(exception.number & 0xFFFF)+"</td></tr></tbody></table>")

    Response.Write("<table><tbody><tr><td class="row2"><b>Error Description:</b></td><td class="row2">"+exception.description+"</td></tr></tbody></table>");

    }

    finally

    {

    //End Section

    Response.Write(EndBody);

    Response.End();

    }

    %>