Use Project schedule APIs to perform operations with Scheduling entities
Applies To: Project Operations for resource/non-stocked based scenarios, Lite deployment - deal to proforma invoicing
Scheduling entities
Project schedule APIs provide the ability to perform create, update, and delete operations with Scheduling entities. These entities are managed through the Scheduling engine in Project for the web. Create, update, and delete operations with Scheduling entities were restricted in earlier Dynamics 365 Project Operations releases.
The following table provides a full list of the Project schedule entities.
| Entity name | Entity logical name |
|---|---|
| Project | msdyn_project |
| Project Task | msdyn_projecttask |
| Project Task Dependency | msdyn_projecttaskdependency |
| Resource Assignment | msdyn_resourceassignment |
| Project Bucket | msdyn_projectbucket |
| Project Team Member | msdyn_projectteam |
OperationSet
OperationSet is a unit-of-work pattern that can be used when several schedule impacting requests must be processed within a transaction.
Project schedule APIs
The following is a list of current Project schedule APIs.
- msdyn_CreateProjectV1: This API can be used to create a project. The project and default project bucket are created immediately.
- msdyn_CreateTeamMemberV1: This API can be used to create a project team member. The team member record is created immediately.
- msdyn_CreateOperationSetV1: This API can be used to schedule several requests that must be performed within a transaction.
- msdyn_PssCreateV1: This API can be used to create an entity. The entity can be any of the Project scheduling entities that support the create operation.
- msdyn_PssUpdateV1: This API can be used to update an entity. The entity can be any of the Project scheduling entities that support the update operation.
- msdyn_PssDeleteV1: This API can be used to delete an entity. The entity can be any of the Project scheduling entities that support the delete operation.
- msdyn_ExecuteOperationSetV1: This API is used to execute all of the operations within the given operation set.
Using Project schedule APIs with OperationSet
Because records with both CreateProjectV1 and CreateTeamMemberV1 are created immediately, these APIs can't be used in the OperationSet directly. However, you can use the API to create needed records, create an OperationSet, and then use these pre-created records in the OperationSet.
Supported operations
| Scheduling entity | Create | Update | Delete | Important considerations |
|---|---|---|---|---|
| Project task | Yes | Yes | Yes | The Progress, EffortCompleted, and EffortRemaining fields can be edited in Project for the Web, but they can't be edited in Project Operations. |
| Project task dependency | Yes | Yes | Project task dependency records aren't updated. Instead, an old record can be deleted, and a new record can be created. | |
| Resource assignment | Yes | Yes | Operations with the following fields aren't supported: BookableResourceID, Effort, EffortCompleted, EffortRemaining, and PlannedWork. Resource assignment records aren't updated. Instead, the old record can be deleted, and a new record can be created. | |
| Project bucket | Yes | Yes | Yes | The default bucket is created by using the CreateProjectV1 API. Support for creating and deleting project buckets was added in Update Release 16. |
| Project team member | Yes | Yes | Yes | For the create operation, use the CreateTeamMemberV1 API. |
| Project | Yes | Yes | Operations with the following fields aren't supported: StateCode, BulkGenerationStatus, GlobalRevisionToken, CalendarID, Effort, EffortCompleted, EffortRemaining, Progress, Finish, TaskEarliestStart, and Duration. |
These APIs can be called with entity objects that include custom fields.
The ID property is optional. If it's provided, the system attempts to use it and throws an exception if it can't be used. If it isn't provided, the system will generate it.
Restricted fields
The following tables define the fields that are restricted from Create and Edit.
Project task
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_actualcost | No | No |
| msdyn_actualcost_base | No | No |
| msdyn_actualend | No | No |
| msdyn_actualsales | No | No |
| msdyn_actualsales_base | No | No |
| msdyn_actualstart | No | No |
| msdyn_costatcompleteestimate | No | No |
| msdyn_costatcompleteestimate_base | No | No |
| msdyn_costconsumptionpercentage | No | No |
| msdyn_effortcompleted | No (yes for Project) | No (yes for Project) |
| msdyn_effortremaining | No (yes for Project) | No (yes for Project) |
| msdyn_effortestimateatcomplete | No | No |
| msdyn_iscritical | No | No |
| msdyn_iscriticalname | No | No |
| msdyn_ismanual | No | No |
| msdyn_ismanualname | No | No |
| msdyn_ismilestone | No | No |
| msdyn_ismilestonename | No | No |
| msdyn_LinkStatus | No | No |
| msdyn_linkstatusname | No | No |
| msdyn_msprojectclientid | No | No |
| msdyn_plannedcost | No | No |
| msdyn_plannedcost_base | No | No |
| msdyn_plannedsales | No | No |
| msdyn_plannedsales_base | No | No |
| msdyn_pluginprocessingdata | No | No |
| msdyn_progress | No (yes for Project) | No (yes for Project) |
| msdyn_remainingcost | No | No |
| msdyn_remainingcost_base | No | No |
| msdyn_remainingsales | No | No |
| msdyn_remainingsales_base | No | No |
| msdyn_requestedhours | No | No |
| msdyn_resourcecategory | No | No |
| msdyn_resourcecategoryname | No | No |
| msdyn_resourceorganizationalunitid | No | No |
| msdyn_resourceorganizationalunitidname | No | No |
| msdyn_salesconsumptionpercentage | No | No |
| msdyn_salesestimateatcomplete | No | No |
| msdyn_salesestimateatcomplete_base | No | No |
| msdyn_salesvariance | No | No |
| msdyn_salesvariance_base | No | No |
| msdyn_scheduleddurationminutes | No | No |
| msdyn_scheduledend | No | No |
| msdyn_scheduledstart | No | No |
| msdyn_schedulevariance | No | No |
| msdyn_skipupdateestimateline | No | No |
| msdyn_skipupdateestimatelinename | No | No |
| msdyn_summary | No | No |
| msdyn_varianceofcost | No | No |
| msdyn_varianceofcost_base | No | No |
Project task dependency
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_linktype | No | No |
| msdyn_linktypename | No | No |
| msdyn_predecessortask | Yes | No |
| msdyn_predecessortaskname | Yes | No |
| msdyn_project | Yes | No |
| msdyn_projectname | Yes | No |
| msdyn_projecttaskdependencyid | Yes | No |
| msdyn_successortask | Yes | No |
| msdyn_successortaskname | Yes | No |
Resource assignment
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_bookableresourceid | Yes | No |
| msdyn_bookableresourceidname | Yes | No |
| msdyn_bookingstatusid | No | No |
| msdyn_bookingstatusidname | No | No |
| msdyn_committype | No | No |
| msdyn_committypename | No | No |
| msdyn_effort | No | No |
| msdyn_effortcompleted | No | No |
| msdyn_effortremaining | No | No |
| msdyn_finish | No | No |
| msdyn_plannedcost | No | No |
| msdyn_plannedcost_base | No | No |
| msdyn_plannedcostcontour | No | No |
| msdyn_plannedsales | No | No |
| msdyn_plannedsales_base | No | No |
| msdyn_plannedsalescontour | No | No |
| msdyn_plannedwork | No | No |
| msdyn_projectid | Yes | No |
| msdyn_projectidname | No | No |
| msdyn_projectteamid | No | No |
| msdyn_projectteamidname | No | No |
| msdyn_start | No | No |
| msdyn_taskid | No | No |
| msdyn_taskidname | No | No |
| msdyn_userresourceid | No | No |
Project team member
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_calendarid | No | No |
| msdyn_creategenericteammemberwithrequirementname | No | No |
| msdyn_deletestatus | No | No |
| msdyn_deletestatusname | No | No |
| msdyn_effort | No | No |
| msdyn_effortcompleted | No | No |
| msdyn_effortremaining | No | No |
| msdyn_finish | No | No |
| msdyn_hardbookedhours | No | No |
| msdyn_hours | No | No |
| msdyn_markedfordeletiontimer | No | No |
| msdyn_markedfordeletiontimestamp | No | No |
| msdyn_msprojectclientid | No | No |
| msdyn_percentage | No | No |
| msdyn_requiredhours | No | No |
| msdyn_softbookedhours | No | No |
| msdyn_start | No | No |
Project
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_actualexpensecost | No | No |
| msdyn_actualexpensecost_base | No | No |
| msdyn_actuallaborcost | No | No |
| msdyn_actuallaborcost_base | No | No |
| msdyn_actualsales | No | No |
| msdyn_actualsales_base | No | No |
| msdyn_contractlineproject | Yes | No |
| msdyn_contractorganizationalunitid | Yes | No |
| msdyn_contractorganizationalunitidname | Yes | No |
| msdyn_costconsumption | No | No |
| msdyn_costestimateatcomplete | No | No |
| msdyn_costestimateatcomplete_base | No | No |
| msdyn_costvariance | No | No |
| msdyn_costvariance_base | No | No |
| msdyn_duration | No | No |
| msdyn_effort | No | No |
| msdyn_effortcompleted | No | No |
| msdyn_effortestimateatcompleteeac | No | No |
| msdyn_effortremaining | No | No |
| msdyn_finish | Yes | Yes |
| msdyn_globalrevisiontoken | No | No |
| msdyn_islinkedtomsprojectclient | No | No |
| msdyn_islinkedtomsprojectclientname | No | No |
| msdyn_linkeddocumenturl | No | No |
| msdyn_msprojectdocument | No | No |
| msdyn_msprojectdocumentname | No | No |
| msdyn_plannedexpensecost | No | No |
| msdyn_plannedexpensecost_base | No | No |
| msdyn_plannedlaborcost | No | No |
| msdyn_plannedlaborcost_base | No | No |
| msdyn_plannedsales | No | No |
| msdyn_plannedsales_base | No | No |
| msdyn_progress | No | No |
| msdyn_remainingcost | No | No |
| msdyn_remainingcost_base | No | No |
| msdyn_remainingsales | No | No |
| msdyn_remainingsales_base | No | No |
| msdyn_replaylogheader | No | No |
| msdyn_salesconsumption | No | No |
| msdyn_salesestimateatcompleteeac | No | No |
| msdyn_salesestimateatcompleteeac_base | No | No |
| msdyn_salesvariance | No | No |
| msdyn_salesvariance_base | No | No |
| msdyn_scheduleperformance | No | No |
| msdyn_scheduleperformancename | No | No |
| msdyn_schedulevariance | No | No |
| msdyn_taskearlieststart | No | No |
| msdyn_teamsize | No | No |
| msdyn_teamsize_date | No | No |
| msdyn_teamsize_state | No | No |
| msdyn_totalactualcost | No | No |
| msdyn_totalactualcost_base | No | No |
| msdyn_totalplannedcost | No | No |
| msdyn_totalplannedcost_base | No | No |
Project bucket
| Logical name | Can create | Can edit |
|---|---|---|
| msdyn_displayorder | Yes | No |
| msdyn_name | Yes | Yes |
| msdyn_project | Yes | No |
| msdyn_projectbucketid | Yes | No |
Limitations and known issues
The following is a list of limitations and known issues:
Project Schedule APIs can only be used by Users with Microsoft Project License. They can't be used by:
- Application users
- System users
- Integration users
- Other users that don't have the required license
Each OperationSet can only have a maximum of 100 operations.
Each user can only have a maximum of 10 open OperationSets.
Project Operations currently supports a maximum of 500 total tasks on a project.
OperationSet failure status and failure logs aren't currently available.
Error handling
- To review errors generated from the Operation Sets, go to Settings > Schedule Integration > Operations Sets.
- To review errors generated from the Project schedule Service, go to Settings > Schedule Integration > PSS Error Logs.
Sample scenario
In this scenario, you will create a project, a team member, four tasks, and two resource assignments. Next, you will update one task, update the project, delete one task, delete one resource assignment, and create a task dependency.
Entity project = CreateProject();
project.Id = CallCreateProjectAction(project);
var projectReference = project.ToEntityReference();
var teamMember = new Entity("msdyn_projectteam", Guid.NewGuid());
teamMember["msdyn_name"] = $"TM {DateTime.Now.ToShortTimeString()}";
teamMember["msdyn_project"] = projectReference;
var createTeamMemberResponse = CallCreateTeamMemberAction(teamMember);
var description = $"My demo {DateTime.Now.ToShortTimeString()}";
var operationSetId = CallCreateOperationSetAction(project.Id, description);
var task1 = GetTask("1WW", projectReference);
var task2 = GetTask("2XX", projectReference, task1.ToEntityReference());
var task3 = GetTask("3YY", projectReference);
var task4 = GetTask("4ZZ", projectReference);
var assignment1 = GetResourceAssignment("R1", teamMember, task2, project);
var assignment2 = GetResourceAssignment("R2", teamMember, task3, project);
var task1Response = CallPssCreateAction(task1, operationSetId);
var task2Response = CallPssCreateAction(task2, operationSetId);
var task3Response = CallPssCreateAction(task3, operationSetId);
var task4Response = CallPssCreateAction(task4, operationSetId);
var assignment1Response = CallPssCreateAction(assignment1, operationSetId);
var assignment2Response = CallPssCreateAction(assignment2, operationSetId);
task2["msdyn_subject"] = "Updated Task";
var task2UpdateResponse = CallPssUpdateAction(task2, operationSetId);
project["msdyn_subject"] = $"Proj update {DateTime.Now.ToShortTimeString()}";
var projectUpdateResponse = CallPssUpdateAction(project, operationSetId);
var task4DeleteResponse = CallPssDeleteAction(task4.Id.ToString(), task4.LogicalName, operationSetId);
var assignment2DeleteResponse = CallPssDeleteAction(assignment2.Id.ToString(), assignment2.LogicalName, operationSetId);
var dependency1 = GetTaskDependency(project, task2, task3);
var dependency1Response = CallPssCreateAction(dependency1, operationSetId);
CallExecuteOperationSetAction(operationSetId);
Console.WriteLine("Done....");
Additional samples
#region Call actions --- Sample code ----
/// <summary>
/// Calls the action to create an operationSet
/// </summary>
/// <param name="projectId">project id for the operations to be included in this operationSet</param>
/// <param name="description">description of this operationSet</param>
/// <returns>operationSet id</returns>
private string CallCreateOperationSetAction(Guid projectId, string description)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_CreateOperationSetV1");
operationSetRequest["ProjectId"] = projectId.ToString();
operationSetRequest["Description"] = description;
OrganizationResponse response = organizationService.Execute(operationSetRequest);
return response["OperationSetId"].ToString();
}
/// <summary>
/// Calls the action to create an entity, only Task and Resource Assignment for now
/// </summary>
/// <param name="entity">Task or Resource Assignment</param>
/// <param name="operationSetId">operationSet id</param>
/// <returns>OperationSetResponse</returns>
private OperationSetResponse CallPssCreateAction(Entity entity, string operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_PssCreateV1");
operationSetRequest["Entity"] = entity;
operationSetRequest["OperationSetId"] = operationSetId;
return GetOperationSetResponseFromOrgResponse(organizationService.Execute(operationSetRequest));
}
/// <summary>
/// Calls the action to update an entity, only Task for now
/// </summary>
/// <param name="entity">Task or Resource Assignment</param>
/// <param name="operationSetId">operationSet Id</param>
/// <returns>OperationSetResponse</returns>
private OperationSetResponse CallPssUpdateAction(Entity entity, string operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_PssUpdateV1");
operationSetRequest["Entity"] = entity;
operationSetRequest["OperationSetId"] = operationSetId;
return GetOperationSetResponseFromOrgResponse(organizationService.Execute(operationSetRequest));
}
/// <summary>
/// Calls the action to update an entity, only Task and Resource Assignment for now
/// </summary>
/// <param name="recordId">Id of the record to be deleted</param>
/// <param name="entityLogicalName">Entity logical name of the record</param>
/// <param name="operationSetId">OperationSet Id</param>
/// <returns>OperationSetResponse</returns>
private OperationSetResponse CallPssDeleteAction(string recordId, string entityLogicalName, string operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_PssDeleteV1");
operationSetRequest["RecordId"] = recordId;
operationSetRequest["EntityLogicalName"] = entityLogicalName;
operationSetRequest["OperationSetId"] = operationSetId;
return GetOperationSetResponseFromOrgResponse(organizationService.Execute(operationSetRequest));
}
/// <summary>
/// Calls the action to execute requests in an operationSet
/// </summary>
/// <param name="operationSetId">operationSet id</param>
/// <returns>OperationSetResponse</returns>
private OperationSetResponse CallExecuteOperationSetAction(string operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_ExecuteOperationSetV1");
operationSetRequest["OperationSetId"] = operationSetId;
return GetOperationSetResponseFromOrgResponse(organizationService.Execute(operationSetRequest));
}
/// <summary>
/// This can be used to abandon an operationSet that is no longer needed
/// </summary>
/// <param name="operationSetId">operationSet id</param>
/// <returns>OperationSetResponse</returns>
protected OperationSetResponse CallAbandonOperationSetAction(Guid operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_AbandonOperationSetV1");
operationSetRequest["OperationSetId"] = operationSetId.ToString();
return GetOperationSetResponseFromOrgResponse(organizationService.Execute(operationSetRequest));
}
/// <summary>
/// Calls the action to create a new project
/// </summary>
/// <param name="project">Project</param>
/// <returns>project Id</returns>
private Guid CallCreateProjectAction(Entity project)
{
OrganizationRequest createProjectRequest = new OrganizationRequest("msdyn_CreateProjectV1");
createProjectRequest["Project"] = project;
OrganizationResponse response = organizationService.Execute(createProjectRequest);
var projectId = Guid.Parse((string)response["ProjectId"]);
return projectId;
}
/// <summary>
/// Calls the action to create a new project team member
/// </summary>
/// <param name="teamMember">Project team member</param>
/// <returns>project team member Id</returns>
private string CallCreateTeamMemberAction(Entity teamMember)
{
OrganizationRequest request = new OrganizationRequest("msdyn_CreateTeamMemberV1");
request["TeamMember"] = teamMember;
OrganizationResponse response = organizationService.Execute(request);
return (string)response["TeamMemberId"];
}
private OperationSetResponse GetOperationSetResponseFromOrgResponse(OrganizationResponse orgResponse)
{
return JsonConvert.DeserializeObject<OperationSetResponse>((string)orgResponse.Results["OperationSetResponse"]);
}
private EntityCollection GetDefaultBucket(EntityReference projectReference)
{
var columnsToFetch = new ColumnSet("msdyn_project", "msdyn_name");
var getDefaultBucket = new QueryExpression("msdyn_projectbucket")
{
ColumnSet = columnsToFetch,
Criteria =
{
Conditions =
{
new ConditionExpression("msdyn_project", ConditionOperator.Equal, projectReference.Id),
new ConditionExpression("msdyn_name", ConditionOperator.Equal, "Bucket 1")
}
}
};
return organizationService.RetrieveMultiple(getDefaultBucket);
}
private Entity GetBucket(EntityReference projectReference)
{
var bucketCollection = GetDefaultBucket(projectReference);
if (bucketCollection.Entities.Count > 0)
{
return bucketCollection[0].ToEntity<Entity>();
}
throw new Exception($"Please open project with id {projectReference.Id} in the Dynamics UI and navigate to the Tasks tab");
}
private Entity CreateProject()
{
var project = new Entity("msdyn_project", Guid.NewGuid());
project["msdyn_subject"] = $"Proj {DateTime.Now.ToShortTimeString()}";
return project;
}
private Entity GetTask(string name, EntityReference projectReference, EntityReference parentReference = null)
{
var task = new Entity("msdyn_projecttask", Guid.NewGuid());
task["msdyn_project"] = projectReference;
task["msdyn_subject"] = name;
task["msdyn_effort"] = 4d;
task["msdyn_scheduledstart"] = DateTime.Today;
task["msdyn_scheduledend"] = DateTime.Today.AddDays(5);
task["msdyn_start"] = DateTime.Now.AddDays(1);
task["msdyn_projectbucket"] = GetBucket(projectReference).ToEntityReference();
task["msdyn_LinkStatus"] = new OptionSetValue(192350000);
//Custom field handling
/*
task["new_custom1"] = "Just my test";
task["new_age"] = 98;
task["new_amount"] = 591.34m;
task["new_isready"] = new OptionSetValue(100000000);
*/
if (parentReference == null)
{
task["msdyn_outlinelevel"] = 1;
}
else
{
task["msdyn_parenttask"] = parentReference;
}
return task;
}
private Entity GetResourceAssignment(string name, Entity teamMember, Entity task, Entity project)
{
var assignment = new Entity("msdyn_resourceassignment", Guid.NewGuid());
assignment["msdyn_projectteamid"] = teamMember.ToEntityReference();
assignment["msdyn_taskid"] = task.ToEntityReference();
assignment["msdyn_projectid"] = project.ToEntityReference();
assignment["msdyn_name"] = name;
return assignment;
}
protected Entity GetTaskDependency(Entity project, Entity predecessor, Entity successor)
{
var taskDependency = new Entity("msdyn_projecttaskdependency", Guid.NewGuid());
taskDependency["msdyn_project"] = project.ToEntityReference();
taskDependency["msdyn_predecessortask"] = predecessor.ToEntityReference();
taskDependency["msdyn_successortask"] = successor.ToEntityReference();
taskDependency["msdyn_linktype"] = new OptionSetValue(192350000);
return taskDependency;
}
#endregion
#region OperationSetResponse DataContract --- Sample code ----
[DataContract]
public class OperationSetResponse
{
[DataMember(Name = "operationSetId")]
public Guid OperationSetId { get; set; }
[DataMember(Name = "operationSetDetailId")]
public Guid OperationSetDetailId { get; set; }
[DataMember(Name = "operationType")]
public string OperationType { get; set; }
[DataMember(Name = "recordId")]
public string RecordId { get; set; }
[DataMember(Name = "correlationId")]
public string CorrelationId { get; set; }
}
#endregion
Feedback
Submit and view feedback for