Wednesday, April 10, 2013

Project Server - Restart workflow from code

When you want to restart workflow in Project Server, you can do that manually in Project Server in two ways:

1. When you open one of your project, in SharePoint Ribbon you will see "Options" button and its submenu "Restart Workflow". By clicking this option, you can restart your workflow, but you can not select the stage to which you want to restart workflow. This option will always restart workflow to the first stage, to the beginning. 




2. Second option better in sense that you can select particular stage to which you want to restart your workflow. You can also select multiple projects and some other options.

This option is located in: Server settings --> Change or Restart Workflows (in section Workflow and Project Detail Pages).




 Downside of this two options is that it has to be done manually, and that is not very convenient when you have to do this on large number of projects. So the best way to do this is from code behind.



 RESTART FROM CODE:

Workflow can be restarted to any stage using SubmitStage function from FluentPS library (which I described in one of my previous posts).

Here is the example of usage of that function:




public void RestartStage(Guid projectUID, string stageName)
{
      var logService = new LogService();

      var sessionService = new PSSessionService()
      {
           HostName = "serverName", // your PWA host name
           SiteName = "pwa" // your PWA site name
      };

      PsiContextService psiContextService = new PsiContextService();
      PSISvcsFactory psiSvcsFactory = new PSISvcsFactory(sessionService, psiContextService);

      Project svcProject = psiSvcsFactory.CreateSvcClient<Project>();
 
      Workflow svcWf = psiSvcsFactory.CreateSvcClient<Workflow>();

      SPSPagesService svcPage = new SPSPagesService(sessionService);
      SPSharepointService svcSP = new SPSharepointService(sessionService, svcPage, logService);
      PSWorkflowService svcPSWf= new PSWorkflowService(logService, svcWf, svcSP);

      WorkflowDataSet workflowDS = svcWf.ReadAvailableEnterpriseProjectTypes();
      List<PSWorkflowStatus> wfStatus = svcPSWf.GetProjectWorkflowStatus(projectUID);

      //Enteprise project template UID
      Guid _eptUID = new Guid();//

      //Stage UID
      Guid _stageGuid = new Guid();

      //Getting the stage GUID
      string _phaseName = "";

      for (int i = 0; i < wfStatus.Count; i++)
      {
           if (wfStatus[i].StageName == stageName)
           {
                _stageGuid = wfStatus[i].StageUid;
                _phaseName = wfStatus[i].PhaseName;
                break;
           }
      }
     
      //getting the enterprise project type GUID
      for (int i = 0; i < workflowDS.EnterpriseProjectType.Count; i++)
      {
          if (workflowDS.EnterpriseProjectType[i].ENTERPRISE_PROJECT_TYPE_NAME == _phaseName)
          {
                _eptUID = workflowDS.EnterpriseProjectType[i].ENTERPRISE_PROJECT_TYPE_UID;
                break;
          }
      }

           //RESTART to the stage
      Guid _restartGuid = svcPSWf.SubmitStage(projectUID, _eptUID, false, _stageGuid);
     
}

Sunday, April 7, 2013

Project Server - Read custom field with value from lookup

In one of my previous posts, I've described how to update value of a Enterprise custom field that contains value from a Lookup table. 

Reading of that field is similar, only shorter and easier. Here, I'm using PSI functions from FluentPS library which is free and easy to use but the same PSI functions can be used when you set your own call of Project Server web services.

Let's say we have custom field called "Product" and it contains values from Lookup table called "Products".

NOTE: If Custom field on your project contains no value, then you won't be able to see that field on that project. If field is empty, Project Server acts as if that field doesn't exist.  


This is the code to do it:

        public bool ReadProduct(string projectUid)
        {        
            Guid projectGuid = new Guid(projectUid);

            if (projectGuid.Equals(Guid.Empty)) return false;
            var logService = new LogService();
            var sessionService = new PSSessionService()
            {
                HostName = "server_name",
                SiteName ="PWA"
            };

             FluentPS.Services.Impl.PsiContextService psiContextService = new PsiContextService();
             FluentPS.Services.Impl.PSISvcsFactory psiSvcsFactory = new PSISvcsFactory(sessionService, psiContextService);

             FluentPS.WebSvcLookupTable.LookupTable svcLookupTable = psiSvcsFactory.CreateSvcClient<FluentPS.WebSvcLookupTable.LookupTable>();
             FluentPS.WebSvcCustomFields.CustomFields svcCustomFields = psiSvcsFactory.CreateSvcClient<FluentPS.WebSvcCustomFields.CustomFields>();

             FluentPS.WebSvcProject.Project svcProject = psiSvcsFactory.CreateSvcClient<FluentPS.WebSvcProject.Project>();


             FluentPS.WebSvcProject.ProjectDataSet _project = svcProject.ReadProject(projectGuid, FluentPS.WebSvcProject.DataStoreEnum.WorkingStore);

             try
             {
                 //Guid of lookup table (Product) in which we look for value
                 Guid _lookupTableUid = Guid.Empty;
                 //MD Guid of lookup custom field
                 Guid _lookupCustomFieldGuid = Guid.Empty;


                 FluentPS.WebSvcCustomFields.CustomFieldDataSet customFieldsDs = svcCustomFields.ReadCustomFields("", false);

                 //First, we need to find Lookup table guid for that Custom field on our project
                 FluentPS.WebSvcCustomFields.CustomFieldDataSet.CustomFieldsDataTable cfDataTable = customFieldsDs.CustomFields;
                 for (int i = 0; i < cfDataTable.Count; i++)
                 {
                     if (cfDataTable[i].MD_PROP_NAME == "Product")
                     {
                         _lookupCustomFieldGuid = cfDataTable[i].MD_PROP_UID;
                         _lookupTableUid = cfDataTable[i].MD_LOOKUP_TABLE_UID;

                         break;
                     }

                 }

                 //Then, we need to find the value of guid of Lookup table value stored in that Custom field
                 Guid _lookupTableValueGuid = Guid.Empty;
                 foreach (FluentPS.WebSvcProject.ProjectDataSet.ProjectCustomFieldsRow cfRow in _project.ProjectCustomFields)
                 {
                     if (cfRow.MD_PROP_UID == _lookupCustomFieldGuid)
                     {
                         _lookupTableValueGuid = cfRow.CODE_VALUE;
                     }

                 }

                 string _value = "";

                 using (FluentPS.WebSvcLookupTable.LookupTableDataSet lookupTableDs = svcLookupTable.ReadLookupTables(string.Empty, false, 1033))
                 {
                  //now, we search through Lookup table for the text value which corresponds to guid found in previous loop
                  for (int i = 0; i < lookupTableDs.LookupTableTrees.Count; i++)
                  {
                       try
                       {
                             if (lookupTableDs.LookupTableTrees[i].LT_STRUCT_UID == _lookupTableValueGuid)
                             {
                                   //and here we read the value we were looking for
                                   _value = lookupTableDs.LookupTableTrees[i].LT_VALUE_TEXT;
                                   break;
                             }
                        }
                        catch (Exception)
                        {
                                //this is only for possible null values
                         }

                  }
              }

              return _value;

          }
          catch (Exception)
          {
                return null;
          }
           
 }