Monday 15 July 2013

Adding a column to the InBasket grid in 9.3

I'm not sure how many times people have asked if it is possible to modify the InBasket to show different columns.

It must be enough since Aras are putting a configurable InBasket into release 9.4 at some time.

I had a look and found one way of adding your own additional columns to the InBasket.

In the InBasket.html file (found under the Innovator install directory Innovator/Client/Scripts/Inbasket) there is a function:

function populateActivityGrid()

This is the key function as it retrieves the current Activities for the logged on user and then formats them into the grid for display.
The method "top.aras.getAssignedTasks(...)" retrieves the tasks for the individual as formatted XML appropriate for creating a grid.

I decided it would be feasible to intercept the return from this method and insert additional information into the grid before displaying it to the user.
In this case I have added the "effective_date" field as the "Go Live Date"

function populateActivityGrid()
    {
      updateCurrentViewMode();
      emptygrid();
      var showWorkflow = 0;
      var showProject = 0;
      var showAction = 0;
      var saveArray = new Array();
      if (document.getElementById("wfl_act").checked) showWorkflow = 1;
      if (document.getElementById("project_act").checked)
        showProject = 1;
      else if (!ProjectSolutionLoadedInDB())
        showProject = -1;
      if (document.getElementById("action_items").checked) showAction = 1;
      saveArray.push(showWorkflow, showProject, showAction, currViewMode);
      top.aras.setPreferenceItemProperties("Core_GlobalLayout", null, { core_in_basket_history: saveArray.join("|") });

      if (currViewMode != "" && (showWorkflow == 1 || showProject == 1 || showAction == 1))

      {
          var tasks = top.aras.getAssignedTasks(currViewMode, showWorkflow, showProject, showAction);
          

          //******************************************************************************************

          // Brian Pye - Modified to add additional column for Go Live Date (effective_date) for changes.
          //******************************************************************************************
          // tasks is formatted xml.
          var taskXML = top.aras.createXMLDocument();
          taskXML.loadXML(tasks);

          //var thead = taskXML.selectSingleNode("//thead");

          var newHead = taskXML.createElement("th");
          newHead.appendChild(taskXML.createCDATASection("Go Live Date"));

          taskXML.childNodes[0].selectSingleNode("//thead").insertBefore(newHead, taskXML.selectSingleNode("//thead").childNodes[6]);

          var newCol = taskXML.createElement("column");
          newCol.setAttribute("width", "80");
          newCol.setAttribute("edit", "NOEDIT");
          newCol.setAttribute("align", "middle");
          newCol.setAttribute("sort", "DATE");
          newCol.setAttribute("inputformat", "d/MM/yyyy");
          newCol.setAttribute("locale", "en-AU");
          newCol.setAttribute("BGINVERT", "false");
          newCol.setAttribute("TEXTCOLORINVERT", "true");
          
          taskXML.childNodes[0].selectSingleNode("//columns").insertBefore(newCol,taskXML.selectSingleNode("//columns").childNodes[6]);


          var rows = taskXML.selectNodes("//tr");

          var inn = new top.Innovator();


          for (var i = 0; i < rows.length; i++) {

              var attID = taskXML.selectNodes("//tr")[i].selectSingleNode("./userdata[@key='attachedId']").getAttribute("value");
              var attTypeId = taskXML.selectNodes("//tr")[i].selectSingleNode("./userdata[@key='attachedTypeId']").getAttribute("value");

              var itm = inn.newItem();

              itm.setAttribute("typeId", attTypeId);
              itm.setID(attID);
              itm.setAttribute("select", "effectivity_date");
              itm.setAction("get");
              itm = itm.apply();

              var dteNode = "";

              if (itm.isError()) {
                  dteNode = taskXML.createElement("td");
              } else {
                  var effDate = itm.getProperty("effectivity_date", "");
                  dteNode = taskXML.createElement("td");
                  dteNode.appendChild(taskXML.createCDATASection(effDate));
              }

              taskXML.childNodes[0].selectNodes("//tr")[i].insertBefore(dteNode, taskXML.selectNodes("//tr")[i].childNodes[6]);

              

          }



          grid.initXML(taskXML.xml);

          //******************************************************************************************
          // end inserted code - Brian Pye
          //******************************************************************************************


        //grid.initXML(tasks);   // comment this out - BP

        grid.setSelectedRow(selRowId, false);
        updateControlsState(getSelectedRowID());
      }
      else
      {
        emptygrid();
        updateControlsState(getSelectedRowID());
        return;
      }
    }


I have used the IDs returned from the getAssignedTasks to then retrieve additional information from the server.
Since this is a change to an Aras html file you will have to keep this in mind when you move to a new version or go through an upgrade process.

This is not the most efficient way of doing things but provided your user doesn't have hundreds of tasks it doesn't have much impact on performance and allows you to add another column.

Sunday 26 May 2013

Programmatically vote for a workflow

While workflow voting in Innovator is pretty simple there are times when you might need to vote via a method to get your desired level of automation.

I did this recently for two different clients with two very different needs.

One had some Item that we needed to "park" for a while with no-one being really responsible for them and then via a scheduled server method vote them back into active management and the other  wanted a very clean way of recording receipt of some returned goods and at the same time voting for the advancement of the workflow.

What follows is a sample server method (c#) for achieving the vote:

// Programmatic voting for an activity to facilitate some// additional automation
// called as a generic method.
// Limitations: This method expects that there is only one
// assignee for the vote.
// Passed in is the id of the Item that we need to vote for:
// expecting: <itemId>id</itemId>
// <itemType>type</itemType>
// <votingPath>path</votingPath>

Innovator inn = this.getInnovator();
string itemID = this.getProperty("itemId","");
string itemType = this.getProperty("itemType","");
string votePath = this.getProperty("votingPath","");
if ( itemID == "" ){
    return inn.newError("No Item ID supplied");
}
if ( itemType == ""){
    return inn.newError("No Item type supplied"); 
}
if ( votePath == "" ){
   return inn.newError("No voting path supplied"); 
}
// retrieve the "Active" workflow activity:
Item wfcItem = inn.newItem(itemType,"get"); // work flow controlled Item wfcItem
wfcItem.setID(itemID);
Item workflow = inn.newItem("Workflow","get");
Item workflowProcess = inn.newItem("Workflow Process","get");
Item wfpa = inn.newItem("Workflow Process Activity","get");
Item activity = inn.newItem("Activity","get");
activity.setProperty("state","Active");
Item activityAssign = inn.newItem("Activity Assignment","get");
Item wpp = inn.newItem("Workflow Process Path","get");
wpp.setProperty("name",votePath);
activity.addRelationship(activityAssign);
activity.addRelationship(wpp);
wfpa.setRelatedItem(activity);
workflowProcess.addRelationship(wfpa);
workflow.setRelatedItem(workflowProcess);
wfcItem.addRelationship(workflow);
wfcItem = wfcItem.apply();

if ( wfcItem.isError() ){
    return inn.newError("Error retrieving Workflow Process Item: " + wfcItem.getErrorString()); 
}
Item wfPaths = wfcItem.getItemsByXPath("//Item[@type='Workflow Process Path']");
if ( wfPaths.getItemCount() != 1 ){
    return inn.newError("Unable to get voting path: " + votePath);
}
string submitPathId = wfPaths.getItemByIndex(0).getID();

Item act = wfcItem.getItemsByXPath("//Item[@type='Workflow Process Activity']/related_id/Item[@type='Activity']");
if ( act.getItemCount() != 1 ){
    return inn.newError("Unable to get activity"); 
}
string actId = act.getID();
string vote = votePath;
string comment = "";
string assignId = "";
Item actAss = wfcItem.getItemsByXPath("//Item[@type='Activity Assignment']");
if ( actAss.getItemCount() != 1){
    return inn.newError("Unable to get activity assignment"); 
}
assignId = actAss.getID();
// Build the voting request
StringBuilder voteXml = new StringBuilder("");
voteXml.Append("<Item type=\"Activity\" action=\"EvaluateActivity\">");
voteXml.Append(" <Activity>{0}</Activity>");
voteXml.Append(" <ActivityAssignment>{1}</ActivityAssignment>");
voteXml.Append(" <Paths>");
voteXml.Append(" <Path id=\"{2}\">{3}</Path>");
voteXml.Append(" </Paths>");
voteXml.Append(" <DelegateTo>0</DelegateTo>");
voteXml.Append(" <Tasks />");
voteXml.Append(" <Variables />");
voteXml.Append(" <Authentication mode=\"\" />");
voteXml.Append(" <Comments>{4}</Comments>");
voteXml.Append(" <Complete>1</Complete>");
voteXml.Append("</Item>");

// Submit the vote
Item res = inn.newItem();
res.loadAML(String.Format(voteXml.ToString(),actId,assignId,submitPathId,vote,comment));
res = res.apply();

return res;


This method is called from a client method:

// vote for the RMA to move down the "Receive" path
var body = "<itemId>" + myItem.getID() + "</itemId><itemType>MyItemType</itemType>votingPath>MyPath</votingPath>";
var voteRes = inn.applyMethod("FT Programmatic Vote", body);
if (voteRes.isError()) {
    alert("Error voting for activity: " + myItem.getProperty("item_number") + ": " + voteRes.getErrorString());
}