使用项目计划 API 对计划实体执行操作
适用于:基于资源/非库存场景的 Project Operations,精简部署 - 估价开票交易。
计划实体
项目计划 API 提供对计划实体执行创建、更新和删除操作的能力。 这些实体通过 Web 项目中的计划引擎进行管理。 早期 Dynamics 365 Project Operations 版本中限制对计划实体执行创建、更新和删除操作。
下表提供了项目计划实体的完整列表。
实体名称 | 实体逻辑名称 |
---|---|
Project | msdyn_project |
项目任务 | msdyn_projecttask |
项目任务依赖关系 | msdyn_projecttaskdependency |
资源分派 | msdyn_resourceassignment |
项目 Bucket | msdyn_projectbucket |
项目团队成员 | msdyn_projectteam |
项目清单 | msdyn_projectchecklist |
项目标签 | msdyn_projectlabel |
要标记的项目任务 | msdyn_projecttasktolabel |
项目冲刺(sprint) | msdyn_projectsprint |
OperationSet
OperationSet 是一种工作单位模式,当必须在交易内处理多个计划影响请求时,可以使用该模式。
项目计划 API
下面是当前项目计划 API 的列表。
API | 说明 |
---|---|
msdyn_CreateProjectV1 | 此 API 用于创建项目。 项目和默认项目桶会立即创建。 项目创建也可以通过使用标准 Dataverse API 向项目表添加行来完成。 此过程不会为项目创建默认 Bucket,但可能会具有更好的性能。 |
msdyn_CreateTeamMemberV1 | 此 API 用于创建项目团队成员。 系统会立即创建团队成员记录。 团队成员创建也可以通过使用标准 Dataverse API 向项目团队成员表添加行来完成。 |
msdyn_CreateOperationSetV1 | 此 API 用于计划必须在交易中执行的多个请求。 |
msdyn_PssCreateV1 | 此 API 用于创建实体。 该实体可以是支持创建操作的任何项目计划实体。 |
msdyn_PssCreateV2 | 此 API 用于创建实体。 它的工作方式类似于 msdyn_PssCreateV1,但可以在一个操作中创建多个实体。 |
msdyn_PssUpdateV1 | 此 API 用于更新实体。 该实体可以是支持更新操作的任何项目计划实体。 |
msdyn_PssUpdateV2 | 此 API 用于更新实体。 它的工作方式类似于 msdyn_PssUpdateV1,但可以在一个操作中更新多个实体。 |
msdyn_PssDeleteV1 | 此 API 用于删除实体。 该实体可以是支持删除操作的任何项目计划实体。 |
msdyn_PssDeleteV2 | 此 API 用于删除实体。 它的工作方式类似于 msdyn_PssDeleteV1,但可以在一个操作中删除多个实体。 |
msdyn_ExecuteOperationSetV1 | 此 API 用于执行给定操作集内的所有操作。 |
msdyn_PssUpdateResourceAssignmentV1 | 此 API 用于更新资源分配计划的工作分布。 |
将项目计划 API 与 OperationSet 一起使用
由于会同时为 CreateProjectV1 和 CreateTeamMemberV1 立即创建记录,这些 API 不能直接在 OperationSet 中使用。 但是,您可以使用它们创建所需的记录,创建 OperationSet,然后使用 OperationSet 中 预先创建的记录。
不支持的操作
计划实体 | 创建 | Update | Delete | 重要考虑因素 |
---|---|---|---|---|
项目任务 | 是 | 是 | 是 | Progress、EffortCompleted和 EffortRemaining 字段可以在 Project for the Web 中进行编辑,但不能在 Project Operations 中编辑。 |
项目任务依赖关系 | 是 | No | 是 | 不会更新项目任务依赖关系记录。 而是可以删除旧记录,并可以创建新记录。 |
资源分派 | 是 | 是* | 是 | 不支持对以下字段执行操作:BookableResourceID、Effort、EffortCompleted、EffortRemaining 和 PlannedWork。 |
项目 Bucket | 是 | 是 | 是 | 默认桶使用 CreateProjectV1 API 创建。 更新版 16 中添加了对创建和删除项目桶的支持。 |
项目团队成员 | 是 | 是 | 是 | 对于创建操作,请使用 CreateTeamMemberV1 API。 |
集成 | 是 | 是 | 否 | 不支持以下对字段执行操作:StateCode、BulkGenerationStatus、GlobalRevisionToken、CalendarID、Effort、EffortCompleted、EffortRemaining、Progress、Finish、TaskEarliestStart 和 Duration。 |
项目清单 | 是 | 是 | 是 | |
项目标签 | No | 是 | No | 标签名称可以更改。 此功能只能用于 Project for the Web。 标签将在您首次打开项目时创建。 |
要标记的项目任务 | 是 | No | 是 | 此功能只能用于 Project for the Web。 |
项目冲刺(sprint) | 是 | 是 | 是 | 开始字段的日期必须早于完成字段。 同一项目的冲刺不能相互重叠。 此功能只能用于 Project for the Web。 |
项目目标 | 是 | 是 | 是 | 不支持具有以下字段的操作:DescriptionPlainText、TaskDisplayOrder |
项目任务与目标关系 | 是 | 否 | 是 | 不支持具有以下字段的操作:TaskDisplayOrder |
* 资源分配记录不会更新。 而是可以删除旧记录,并可以创建新记录。 提供了一个单独的 API 来更新资源分配分布。
ID 属性是可选的。 如果提供了 ID 属性,则系统会尝试使用它,如果无法使用,则会引发异常。 如果未提供此属性,系统将生成它。
限制和已知问题
以下列表显示了限制和已知问题:
项目计划 API 仅供具有 Microsoft Project 许可证的用户使用。 以下用户不能使用它们:
- 应用程序用户
- 系统用户
- 集成用户
- 没有所需许可证的其他用户
每个 OperationSets 最多只能有 200 项操作。
每个用户最多只能有 10 个公开的 OperationSets。
每个更新资源分配分布操作计为一次操作。
每个更新分布的列表最多可以包含 100 个时间片段。
OperationSet 故障状态和故障日志当前不可用。
每个项目最多有 400 个冲刺。
错误处理
- 要查看操作集生成的错误,转到设置>计划集成>操作集。
- 若要查看项目计划服务生成的错误,请转到设置>计划集成>PSS 错误日志。
编辑资源分配分布
与更新实体的所有其他项目计划 API 不同,资源分配分布 API 仅负责单个实体 msydn_resourceassignment 上的单个字段 msdyn_plannedwork 的更新。
给定计划模式为:
- 固定单位。
- 项目日历为星期一、星期二、星期四和星期五下午 9:00 到 5:00(太平洋时间)。 (星期三没有工作。)
- 资源日历为星期一到星期五上午 9:00 到下午 1:00(太平洋时间)。
此工作分配为期一周,每天 4 小时,因为资源日历是从上午 9:00 到下午 1:00(太平洋时间),即每天 4 小时。
任务 | 开始日期 | 结束日期 | 数量 | 2022 年 6 月 13 日 | 2022 年 6 月 14 日 | 2022 年 6 月 15 日 | 2022 年 6 月 16 日 | 2022 年 6 月 17 日 | |
---|---|---|---|---|---|---|---|---|---|
9-1 工作人员 | T1 | 2022 年 6 月 13 日 | 2022 年 6 月 17 日 | 20 | 4 | 4 | 4 | 4 | 4 |
例如,如果您希望工作人员本周每天只工作三个小时,为其他任务留出一小时。
UpdatedContours 示例有效负载
[{
"minutes":900.0,
"start":"2022-06-13T00:00:00-07:00",
"end":"2022-06-18T00:00:00-07:00"
}]
这是更新分布计划 API 运行后的分配。
任务 | 开始日期 | 结束日期 | 数量 | 2022 年 6 月 13 日 | 2022 年 6 月 14 日 | 2022 年 6 月 15 日 | 2022 年 6 月 16 日 | 2022 年 6 月 17 日 | |
---|---|---|---|---|---|---|---|---|---|
9-1 工作人员 | T1 | 2022 年 6 月 13 日 | 2022 年 6 月 17 日 | 15 | 3 | 3 | 3 | 3 | 3 |
示例方案
在此方案中,您将创建一个项目、一个团队成员、四个任务和两个资源分派。 接下来,您将更新一个任务、更新项目、更新资源分配信息、删除一个任务、删除一个资源分派以及创建任务依赖项。
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);
List<UpdatedContour> updatedContours = new List<UpdatedContour>();
UpdatedContour updatedContour = new UpdatedContour();
updatedContour.Start = DateTime.UtcNow.Date;
updatedContour.End = DateTime.UtcNow.Date.AddDays(1);
updatedContour.Minutes = 120;
updatedContours.Add(updatedContour);
String serializedUpdate = JsonConvert.SerializeObject(updatedContours);
var updateContoursResponse = CallPssUpdateContourAction(assignment1.Id, serializedUpdate, 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....");
其他示例
#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
/// </summary>
/// <param name="entity">Scheduling entity</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
/// </summary>
/// <param name="entity">Scheduling entity</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
/// </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 update a Resource Assignment contour
/// </summary>
/// <param name="resourceAssignmentId">Id of the resource assignment to be updated</param>
/// <param name="serializedUpdates">JSON formatted contour updates</param>
/// <param name="operationSetId">operationSet id</param>
/// <returns>OperationSetResponse</returns>
private OperationSetResponse CallPssUpdateContourAction(string resourceAssignmentId, string serializedUpdates string operationSetId)
{
OrganizationRequest operationSetRequest = new OrganizationRequest("msdyn_PssUpdateResourceAssignmentContourV1");
operationSetRequest["ResourceAssignmentId"] = resourceAssignmentId;
operationSetRequest["UpdatedContours"] = serializedUpdates;
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
#region UpdatedContour DataContract --- Sample code ----
[DataContract]
public class UpdatedContour
{
[DataMember(Name = "start")]
public DateTime Start { get; set; }
[DataMember(Name = "end")]
public DateTime End { get; set; }
[DataMember(Name = "minutes")]
public decimal Minutes { get; set; }
}
#endregion