Partager via


Statusing.SubmitStatusForResource Method

Submits one or more modified and saved assignments for the specified resource to the project manager for approval. Does not require impersonation of the resource.

Namespace:  [Statusing Web service]
Service reference: http://ServerName:32843/[Project Service Application GUID]/PSI/Statusing.svc
Web service reference: http://ServerName/ProjectServerName/_vti_bin/PSI/Statusing.asmx?wsdl

Syntax

'Declaration
<SoapDocumentMethodAttribute("https://schemas.microsoft.com/office/project/server/webservices/Statusing/SubmitStatusForResource", RequestNamespace := "https://schemas.microsoft.com/office/project/server/webservices/Statusing/",  _
    ResponseNamespace := "https://schemas.microsoft.com/office/project/server/webservices/Statusing/",  _
    Use := SoapBindingUse.Literal, ParameterStyle := SoapParameterStyle.Wrapped)> _
Public Sub SubmitStatusForResource ( _
    resid As Guid, _
    updateGuids As Guid(), _
    comment As String _
)
'Usage
Dim instance As Statusing
Dim resid As Guid
Dim updateGuids As Guid()
Dim comment As String

instance.SubmitStatusForResource(resid, _
    updateGuids, comment)
[SoapDocumentMethodAttribute("https://schemas.microsoft.com/office/project/server/webservices/Statusing/SubmitStatusForResource", RequestNamespace = "https://schemas.microsoft.com/office/project/server/webservices/Statusing/", 
    ResponseNamespace = "https://schemas.microsoft.com/office/project/server/webservices/Statusing/", 
    Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
public void SubmitStatusForResource(
    Guid resid,
    Guid[] updateGuids,
    string comment
)

Parameters

  • updateGuids
    Type: []
    Array of assignment GUIDs. A null value submits all changed assignments.

Remarks

Project Server Permissions

Permission

Description

StatusBrokerPermission

Allows a user to read and update status on behalf of a resource. Global permission.

Examples

Example for WCF and manually scheduled tasks:   The UpdateStatus_ManualTasks example is a test application that does the following, for a manually scheduled task or an auto-scheduled task:

  • Parses the project name, task name, number of hours worked, the optionalassigned resource, and an optional status comment.

  • If no resource is specified, assumes the application user is assigned to the task.

  • Gets the GUID for the user assigned to the specified task.

  • Gets and validates the assignment data. Assumes only one resource is assigned.

  • Creates the changeXml string for the UpdateStatus method. For a simple test, converts reported hours to percent complete (maximum 100). If the application user specifies a different resource, adds the ResID attribute to the Assn element in the changeXml string.

  • Calls UpdateStatus, and then calls SubmitStatusForResource, for the single assignment.

  • Calls ReadStatusForResource to get an updated StatusingDataSet.

    The application also writes the datasets and the changeXml value to XML files, for use in testing.

To use the UpdateStatus test application, do the following:

  1. Create a test project, add two manually scheduled tasks, set the task duration and start date, and then add yourself and one other user as resources. Assign one task to you and the other task to the other user.

    For example, name the project Test project, name the tasks T1 and T2, and then set the duration of each task to three days. Assume your user name is User 1, and the other user is User 2. Assign T1 to User 1 and assign T2 to User 2.

  2. For configuration of the WCF endpoints, create an app.config file. For information about using the code sample in a Microsoft Visual Studio 2010 project and creating an app.config file, see Prerequisites for WCF-Based Code Samples.

  3. Run tests, using various parameters. See the Usage method in the following code for parameter information. For example, run the following tests in a Command Prompt window:

    • UpdateStatus -p "Test project" -t "T1" -hours 6 -c "This is a comment"

      The output shows:

      Updating status for User 1 on task 'T1': 6 hours
              Manually scheduled task
      
    • UpdateStatus -p "Test project" -t "T1" -hours 6 -r "User 2" -c "This is a comment"

      User 2 is not assigned to task T1, so the application shows The assignment on task 'T1' is for User 1, not for User 2.

    • UpdateStatus -p "Test project" -t "T2" -hours 6 -r "User 2" -c "This is a comment"

      The output shows:

      Updating status for User 2 on task 'T2': 6 hours
              Manually scheduled task
      
  4. After each test, check the Approval Center in Project Web App for status updates.

Note

When you add a manually scheduled task that does not have a start date or duration, the default is eight hours of work. You can still assign a resource to the task and update the status. For example, if a task T3 has no start date or duration, and you use the UpdateStatus test application to set six hours of work for the assigned resource, Project Server sets the start date for T3 to the project start date, and then adds six hours of actual work. After accepting the status update, you can add the Actual Work column and the Remaining Work column to the Gantt Chart view in Project Professional 2010. The work for T3 is eight hours, actual work is six hours, and remaining work is two hours. If you add the Actual Work row to the Details pane in the Resource Usage view or the Task Usage view, you can also see the six hours of actual work.

The complete Visual Studio solution is in the Project 2010 SDK download.

using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Security.Principal;
using System.Xml;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace Microsoft.SDK.Project.Samples.UpdateStatus
{
    class Program
    {
        private const string ENDPOINT_PROJECT = "basicHttp_Project";
        private const string ENDPOINT_RESOURCE = "basicHttp_Resource";
        private const string ENDPOINT_STATUSING = "basicHttp_Statusing";

        // Change the output directory for your computer.
        private const string OUTPUT_FILES = @"C:\Project\Samples\Output\";
        private const string XML_FILE1 = "UpdateStatus_jc_BasicProjectInfo.xml";
        private const string XML_FILE2 = "UpdateStatus_jc_FullProjectInfo.xml";
        private const string XML_FILE3 = "UpdateStatus_jc_ChangeXml.xml";
        private const string XML_FILE4 = "UpdateStatus_jc_NewStatus.xml";

        private static SvcProject.ProjectClient projectClient;
        private static SvcResource.ResourceClient resourceClient;
        private static SvcStatusing.StatusingClient statusingClient;

        private static string projectName = string.Empty;      // Project name.
        private static string taskName = string.Empty;         // Task name.
        private static string resName = string.Empty;          // Assigned resource name.
        private static string requestedResName = string.Empty; // Requested resource name.
        private static string appUserName = string.Empty;      // Application user name.
        private static DateTime minDate = DateTime.Today;      // Minimum date for statusing data.
        private static DateTime maxDate = DateTime.Today;      // Maximum date for statusing data.
        private static decimal hoursWorked = 0.0M;             // Number of hours worked on the assignment.
        private static string comment = string.Empty;          // Comment for status submission.

        private static bool showUsage = false;
        private static StreamWriter writer;
 
        static void Main(string[] args)
        {
            string outFilePath1 = OUTPUT_FILES + XML_FILE1;
            string outFilePath2 = OUTPUT_FILES + XML_FILE2;
            string outFilePath3 = OUTPUT_FILES + XML_FILE3;
            string outFilePath4 = OUTPUT_FILES + XML_FILE4;

            writer = new StreamWriter(outFilePath3);

            Guid projUid = Guid.Empty;          // GUID of the project.
            Guid taskUid = Guid.Empty;          // GUID of the task.
            Guid assnUid = Guid.Empty;          // GUID of the assignment.
            Guid resUid = Guid.Empty;           // GUID of the assigned resource.
            Guid requestedResUid = Guid.Empty;  // GUID of the requested resource, for submitting status.
            Guid myUid = Guid.Empty;            // GUID of the application user.
            double regularWork = 0;             // Assignment working time at the regular rate, 
                                                // in thousandths of minutes.
            bool isTaskManual = false;          // Specifies whether the task is manually scheduled.

            if (!ParseCommandLine(args)) 
            {
                Usage();
                ExitApp();
            }

            try
            {
                ConfigClientEndpoints(ENDPOINT_PROJECT);
                ConfigClientEndpoints(ENDPOINT_RESOURCE);
                ConfigClientEndpoints(ENDPOINT_STATUSING);

                WindowsIdentity winId = WindowsIdentity.GetCurrent();
                appUserName = winId.Name;

                myUid = resourceClient.GetCurrentUserUid();
                Console.WriteLine("User: {0}; PWA GUID: {1}", appUserName, myUid.ToString());

                // Get the basic information for the project.
                SvcProject.ProjectDataSet projectDs = projectClient.ReadProjectStatus(
                    Guid.Empty, SvcProject.DataStoreEnum.PublishedStore, projectName,
                    (int)PSLibrary.Project.ProjectType.Project);

                Console.WriteLine("XML output for ReadProjectStatus:\n\t{0}",
                    outFilePath1);
                projectDs.WriteXml(outFilePath1);

                projUid = projectDs.Project[0].PROJ_UID;

                if (projUid == Guid.Empty)
                    throw (new ArgumentException(
                        string.Format("\nThe project '{0}' does not exist.", projectName)));

                // Get the full information for the project.
                projectDs = projectClient.ReadProject(projUid,
                    SvcProject.DataStoreEnum.PublishedStore);
                Console.WriteLine("XML output for ReadProject:\n\t{0}", outFilePath2);
                projectDs.WriteXml(outFilePath2);

                bool isCurrentUser;  // Specifies whether the assignment is for the current user.

                // Get requested resource GUID. Exit if the requested resource is not a project resource.
                if (string.IsNullOrEmpty(requestedResName))
                {
                    // The current user is the requested resource.
                    isCurrentUser = true;

                    // Use a Linq query over the typed ProjectDataSet to check whether 
                    // the app user is a resource on the project.
                    var query = from r in projectDs.ProjectResource
                                where r.RES_UID == myUid
                                select new { r.RES_NAME };

                    foreach (var resRow in query)
                    {
                        requestedResName = resRow.RES_NAME;
                        requestedResUid = myUid;
                    }
                }
                else
                {
                    isCurrentUser = false;

                    var query = from r in projectDs.ProjectResource
                                where r.RES_NAME == requestedResName
                                select new { r.RES_UID };

                    foreach (var resRow in query)
                    {
                        requestedResUid = resRow.RES_UID;
                    }
                }

                if (requestedResUid == Guid.Empty)
                {
                    if (string.IsNullOrEmpty(requestedResName)) 
                        requestedResName = appUserName;

                    throw (new ArgumentException(
                        string.Format("\nThe resource '{0}' is not on the project '{1}'",
                                      requestedResName, projectName)));
                }

                // Get the assignment data for the task.
                for (int i = 0; i < projectDs.Assignment.Count; i++)
                {
                    if (projectDs.Assignment[i].TASK_NAME == taskName)
                    {
                        taskUid = projectDs.Assignment[i].TASK_UID;
                        assnUid = projectDs.Assignment[i].ASSN_UID;
                        resUid = projectDs.Assignment[i].RES_UID;
                        regularWork = projectDs.Assignment[i].ASSN_WORK;
                        minDate = projectDs.Assignment[i].ASSN_START_DATE;
                        maxDate = projectDs.Assignment[i].ASSN_FINISH_DATE;

                        // Get the assigned resource name.
                        for (int j = 0; j < projectDs.ProjectResource.Count; j++)
                        {
                            if (projectDs.ProjectResource[j].RES_UID == resUid)
                            {
                                resName = projectDs.ProjectResource[j].RES_NAME;
                                break;
                            }
                        }

                        // Is the task manually scheduled?
                        for (int t = 0; t < projectDs.Task.Count; t++)
                        {
                            if (projectDs.Task[t].TASK_UID == taskUid)
                            {
                                isTaskManual = projectDs.Task[t].TASK_IS_MANUAL;
                                break;
                            }
                        }
                        break;
                    }
                }
                ValidateAssignment(isCurrentUser, resUid, myUid, assnUid, requestedResUid);

                string changeXml = CreateChangeXml(projUid, assnUid, resUid, 
                                                   hoursWorked, regularWork, isCurrentUser);

                Console.WriteLine("XML output for ChangeXml:\n\t{0}", outFilePath3);
                writer.WriteLine(changeXml);

                Console.ForegroundColor = ConsoleColor.Yellow;
                string yellowString = "\nUpdating status for {0} on task '{1}': {2} hours\n\t";
                yellowString += (isTaskManual) ? "Manually scheduled task" : "Auto-scheduled task";
                Console.WriteLine(yellowString, resName, taskName, hoursWorked);
                Console.ResetColor();

                statusingClient.UpdateStatus(changeXml);

                Guid[] assignments = { assnUid };
                statusingClient.SubmitStatusForResource(resUid, assignments, comment); 

                SvcStatusing.StatusingDataSet statusingDs =
                    statusingClient.ReadStatusForResource(resUid, assnUid, minDate, maxDate);

                Console.WriteLine("\nXML output for ReadStatusForResource:\n\t{0}", outFilePath4);
                statusingDs.WriteXml(outFilePath4);
            }
            catch (FaultException fault)
            {
                // Use the WCF FaultException, because the ASMX SoapException does not 
                // exist in a WCF-based application.
                Console.ForegroundColor = ConsoleColor.Red;
                WriteFaultOutput(fault);
                Console.WriteLine();
                showUsage = true;
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
                Console.WriteLine();
                showUsage = true;
            }
            finally
            {
                writer.Close();
                Console.ResetColor();
                if (showUsage) Usage();
                ExitApp();
            }
        }

        // Validate the assignment and the resource.
        private static void ValidateAssignment(bool isCurrentUser, Guid resUid, Guid myUid,
                                               Guid assnUid, Guid requestedResUid)
        {
            if (!isCurrentUser && resUid == myUid)
                throw (new ArgumentException(
                    string.Format("\nThe assignment on task '{0}' is for {1}, not for {2}",
                                  taskName, resName, requestedResName)));

            if (assnUid == Guid.Empty)
                throw (new ArgumentException(
                    string.Format("\nNo assignment for {0} on task '{0}'", resName, taskName)));

            if (requestedResUid != resUid)
                throw (new ArgumentException(
                    string.Format("\nResource '{0}' does not match assignment on task '{1}'",
                                  requestedResName, taskName)));
        }

        /// <summary>Create a ChangeXml string for the UpdateStatus method.</summary>
        /// <param name="projUid">Project GUID</param>
        /// <param name="assnUid">Assignment GUID</param>
        /// <param name="resUid">Resource GUID</param>
        /// <param name="hoursWorked">Number of hours worked.</param>
        /// <param name="totalRegHours">
        /// Total assigned hours at the regular rate, in thousandths of minutes.</param>
        /// <param name="isCurrentUser">True if the assignment is for the current user.</param>
        /// <returns></returns>
        private static string CreateChangeXml(Guid projUid, Guid assnUid, Guid resUid,  
            decimal hoursWorked, double totalRegHours, bool isCurrentUser)
        {
            StringBuilder cXmlBuilder = new StringBuilder("<Changes>");
            cXmlBuilder.AppendFormat("<Proj ID=\"{0}\">", projUid.ToString());
            cXmlBuilder.AppendFormat("<Assn ID=\"{0}\"", assnUid.ToString());

            // If the assignment is not for the current user, add the ResID attribute, 
            // and then close the Assn element.
            if (isCurrentUser)
                cXmlBuilder.Append(">");
            else
                cXmlBuilder.AppendFormat(" ResID=\"{0}\">", resUid.ToString());

            // Specify the process ID (PID) for percent work complete.
            // The "Supported Project Fields and Field Information for Statusing ChangeXML"  
            // SDK article shows the PID value is 251658274.
            string pidPctWorkComplete = PSLibrary.AssnConstID.s_apid_pct_wrk_complete.ToString(
                CultureInfo.InvariantCulture);

            // Calculate the percent work complete.
            int pctComplete = Convert.ToInt32(100 * hoursWorked / 
                              Convert.ToDecimal(totalRegHours / 1000 / 60));

            if (pctComplete > 100) pctComplete = 100;

            cXmlBuilder.AppendFormat("<Change PID=\"{0}\">{1}</Change>", pidPctWorkComplete,
                pctComplete.ToString());
            cXmlBuilder.Append("</Assn></Proj></Changes>");

            return cXmlBuilder.ToString();
        }

        // Extract a PSClientError object from the WCF FaultException object, and
        // then display the exception details and each error in the PSClientError stack.
        private static void WriteFaultOutput(FaultException fault)
        {
            string errAttributeName;
            string errAttribute;
            string errOut;
            string errMess = "".PadRight(30, '=') + "\r\n"
                + "Error details: " + "\r\n";

            PSLibrary.PSClientError error = Helpers.GetPSClientError(fault, out errOut);
            errMess += errOut;

            PSLibrary.PSErrorInfo[] errors = error.GetAllErrors();
            PSLibrary.PSErrorInfo thisError;

            for (int i = 0; i < errors.Length; i++)
            {
                thisError = errors[i];
                errMess += "\r\n".PadRight(30, '=') + "\r\nPSClientError output:\r\n";
                errMess += thisError.ErrId.ToString() + "\n";

                for (int j = 0; j < thisError.ErrorAttributes.Length; j++)
                {
                    errAttributeName = thisError.ErrorAttributeNames()[j];
                    errAttribute = thisError.ErrorAttributes[j];
                    errMess += "\r\n\t" + errAttributeName
                        + ": " + errAttribute;
                }
            }
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(errMess);
            Console.ResetColor();
        }

        // Use the endpoints defined in app.config to configure the client.
        public static void ConfigClientEndpoints(string endpt)
        {
            if (endpt == ENDPOINT_PROJECT)
                projectClient = new SvcProject.ProjectClient(endpt);
            else if (endpt == ENDPOINT_RESOURCE)
                resourceClient = new SvcResource.ResourceClient(endpt);
            else if (endpt == ENDPOINT_STATUSING)
                statusingClient = new SvcStatusing.StatusingClient(endpt);
        }

        // Parse the command line. Return true if there are no errors.
        private static bool ParseCommandLine(string[] args)
        {
            int i;
            bool error = false;
            int argsLength = args.Length;

            for (i = 0; i < argsLength; i++)
            {
                if (error) break;
                if (args[i].StartsWith("-") || args[i].StartsWith("/"))
                    args[i] = "*" + args[i].Substring(1).ToLower();

                switch (args[i])
                {
                    case "*projectname":
                    case "*p":
                        if (++i >= argsLength) return false;
                        projectName = args[i];
                        break;
                    case "*taskname":
                    case "*t":
                        if (++i >= argsLength) return false;
                        taskName = args[i];
                        break;
                    case "*resourcename":
                    case "*r":
                        if (++i >= argsLength) return false;
                        requestedResName = args[i];
                        break;
                    case "*hours":
                    case "*h":
                        if (++i >= argsLength) return false;
                        hoursWorked = Convert.ToDecimal(args[i]);
                        break;
                    case "*comment":
                    case "*c":
                        if (++i >= argsLength) return false;
                        comment = args[i];
                        break;
                    case "*?":
                    default:
                        error = true;
                        break;
                }
            }
            return !error;
        }

        private static void Usage()
        {
            string example = "Usage: UpdateStatus -p \"Project Name\" -t \"Task name\"  -h 3"
                + "\r\n\t\t[-r \"Resource Name\"] [-d 6/29/2011] [-c \"This is a comment\"]";
            Console.WriteLine(example);
            Console.WriteLine("  -projectName | -p:  Name of the project.");
            Console.WriteLine("  -taskName | -t:  Name of the assigned task.");
            Console.WriteLine("  -hours | -h:  Number of hours worked.");
            Console.WriteLine("  -resourceName | -r:  Resource name. Default is the current user.");
            Console.WriteLine("  -date | -d:  Date of the work. Default is non-timephased.");
            Console.WriteLine("  -comment | -c:  Comment for submitted status.");
        }

        private static void ExitApp()
        {
            Console.Write("\nPress any key to exit... ");
            Console.ReadKey(true);
            Environment.Exit(0);
        }
    }

    // Helper method: GetPSClientError.
    class Helpers
    {
        /// <summary>
        /// Extract a PSClientError object from the ServiceModel.FaultException,
        /// for use in output of the GetPSClientError stack of errors.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="errOut">Shows that FaultException has more information 
        /// about the errors than PSClientError has. FaultException can also contain 
        /// other types of errors, such as failure to connect to the server.</param>
        /// <returns>PSClientError object, for enumerating errors.</returns>
        public static PSLibrary.PSClientError GetPSClientError(FaultException e, 
                                                               out string errOut)
        {
            const string PREFIX = "GetPSClientError() returns null: ";
            errOut = string.Empty;
            PSLibrary.PSClientError psClientError = null;

            if (e == null)
            {
                errOut = PREFIX + "Null parameter (FaultException e) passed in.";
                psClientError = null;
            }
            else
            {
                // Get a ServiceModel.MessageFault object.
                var messageFault = e.CreateMessageFault();

                if (messageFault.HasDetail)
                {
                    using (var xmlReader = messageFault.GetReaderAtDetailContents())
                    {
                        var xml = new XmlDocument();
                        xml.Load(xmlReader);

                        var serverExecutionFault = xml["ServerExecutionFault"];
                        if (serverExecutionFault != null)
                        {
                            var exceptionDetails = serverExecutionFault["ExceptionDetails"];
                            if (exceptionDetails != null)
                            {
                                try
                                {
                                    errOut = exceptionDetails.InnerXml + "\r\n";
                                    psClientError = 
                                        new PSLibrary.PSClientError(exceptionDetails.InnerXml);
                                }
                                catch (InvalidOperationException ex)
                                {
                                    errOut = PREFIX + "Unable to convert fault exception info ";
                                    errOut += "a valid Project Server error message. Message: \n\t";
                                    errOut += ex.Message;
                                    psClientError = null;
                                }
                            }
                            else
                            {
                                errOut = PREFIX 
                                    + "The FaultException e is a ServerExecutionFault, "
                                    + "but does not have ExceptionDetails.";
                            }
                        }
                        else
                        {
                            errOut = PREFIX 
                                + "The FaultException e is not a ServerExecutionFault.";
                        }
                    }
                }
                else // No detail in the MessageFault.
                {
                    errOut = PREFIX + "The FaultException e does not have any detail.";
                }
            }
            errOut += "\r\n" + e.ToString() + "\r\n";
            return psClientError;
        }
    }
}

See Also

Reference

Statusing Class

Statusing Members

Statusing Web Service

ReadStatusForResource

UpdateStatus