プロジェクト スケジュール API を使用して、スケジュール エンティティで操作を実行する
適用対象 : リソース/非在庫ベースのシナリオに使用する Project Operations、見積請求に対応する小規模のデプロイ。
スケジューリング エンティティ
プロジェクト スケジュール API では、スケジュール エンティティを使用した作成、更新、削除操作を実行する機能を提供します。 これらのエンティティは、Project for the Web のスケジューリング エンジンを介して管理されます。 スケジューリング エンティティを使用した操作の作成、更新、および削除は、過去の Dynamics 365 Project Operations リリースに限定されていました。
次の表に、プロジェクト スケジュール エンティティの全リストを示します。
エンティティ名 | エンティティの論理名 |
---|---|
プロジェクト | msdyn_project |
プロジェクト タスク | msdyn_projecttask |
プロジェクト タスクの依存関係 | msdyn_projecttaskdependency |
リソース割り当て | msdyn_resourceassignment |
プロジェクト バケット | msdyn_projectbucket |
プロジェクト チーム メンバー | msdyn_projectteam |
プロジェクト チェックリスト | msdyn_projectchecklist |
プロジェクト ラベル | msdyn_projectlabel |
プロジェクト タスクとラベルの関連付け | +msdyn_projecttasktolabel |
プロジェクト スプリント | msdyn_projectsprint |
OperationSet
OperationSet は、スケジュールに影響を与える複数の要求をトランザクション内で処理する必要がある場合に使用できる作業単位パターンです。
プロジェクト スケジュール API
以下は、現在のプロジェクト スケジュール API のリストです。
API | 説明設定 |
---|---|
msdyn_CreateProjectV1 | この API は、プロジェクトを作成するために使用されます。 プロジェクトと規定のプロジェクト バケットがすぐに作成されます。 プロジェクトの作成は、標準の Dataverse API を使用してプロジェクト テーブルに行を追加することによっても実行できます。 このプロセスでは、プロジェクトのデフォルトバケットは作成されませんが、パフォーマンスが向上する可能性があります。 |
msdyn_CreateTeamMemberV1 | この API はプロジェクト チーム メンバーを作成するために使用されます。 チーム メンバーのレコードはすぐに作成されます。 チームメンバーの作成は、標準の Dataverse API を使用してプロジェクト チーム メンバー テーブルに行を追加することによっても実行できます。 |
msdyn_CreateOperationSetV1 | この API を使用して、トランザクション内で実行する必要のあるいくつかの要求をスケジュールできます。 |
msdyn_PssCreateV1 | この API は、エンティティを作成するために使用されます。 エンティティは、作成操作をサポートするプロジェクト スケジュール エンティティのいずれかとなります。 |
msdyn_PssCreateV2 | この API は、エンティティを作成するために使用されます。 msdyn_PssCreateV1 と同様に機能しますが、複数のエンティティを 1 つのアクションで作成することができます。 |
msdyn_PssUpdateV1 | この API は、エンティティを更新するために使用されます。 エンティティは、更新操作をサポートするプロジェクト スケジュール エンティティのいずれかとなります。 |
msdyn_PssUpdateV2 | この API は、更新されたエンティティに使用されます。 msdyn_PssCreateV1 と同様に機能しますが、複数のエンティティを 1 つのアクションで更新することができます。 |
msdyn_PssDeleteV1 | この API は、エンティティを削除するために使用されます。 エンティティには、削除操作をサポートするプロジェクト スケジュール エンティティのいずれかを指定できます。 |
msdyn_PssDeleteV2 | この API は、エンティティを削除するために使用されます。 msdyn_PssDeleteV1 と同様に機能しますが、複数のエンティティを 1 つのアクションで削除することができます。 |
msdyn_ExecuteOperationSetV1 | この API を使用して、指定された操作セット内ですべての操作を実行します。 |
msdyn_PssUpdateResourceAssignmentV1 | この API は、リソース割り当ての計画された作業輪郭を更新するために使用されます。 |
OperationSet でプロジェクト スケジュール API を使用する
レコードは CreateProjectV1 と CreateTeamMemberV1 の両方に対してすぐに作成されるため、これらの API は OperationSet で直接使用することはできません。 ただし、これらを使用して必要なレコードを作成し、OperationSet を作成し、これらの事前に作成されたレコードを OperationSet で使用できます。
サポートされている操作
スケジューリング エンティティ | 作成 | 更新する | 削除 | 重要な考慮事項 |
---|---|---|---|---|
プロジェクト タスク | はい | はい | はい | Progress、EffortCompleted、および EffortRemaining のフィールドは、Project for the Web で編集できますが、Project Operations では編集できません。 |
プロジェクト タスクの依存関係 | はい | いいえ | はい | プロジェクト タスクの依存関係レコードは更新されません。 代わりに、古いレコードを削除して、新しいレコードを作成できます。 |
リソース割り当て | はい | はい* | はい | 次のフィールドを使用した操作はサポートされていません: BookableResourceID、Effort、EffortCompleted、EffortRemaining、および PlannedWork。 |
プロジェクト バケット | はい | はい | はい | 規定のバケットは、CreateProjectV1 APIを使用して作成されます。 プロジェクト バケットの作成と削除のサポートは、アップデート リリース 16 で追加されました。 |
プロジェクト チーム メンバー | はい | はい | はい | 作成操作には、CreateTeamMemberV1 API を使用します。 |
Project | 可 | 可 | いいえ | 次のフィールドを使用した操作はサポートされていません: StateCode、BulkGenerationStatus、GlobalRevisionToken、 CalendarID、Effort、EffortCompleted、EffortRemaining、Progress、Finish、TaskEarliestStart、および Duration。 |
プロジェクト チェックリスト | はい | はい | はい | |
プロジェクト ラベル | いいえ | はい | いいえ | ラベル名は変更できます。 この機能は、Project for the Web でのみ利用可能です。 ラベルは、最初にプロジェクトを開いたときに作成されます。 |
プロジェクト タスクとラベルの関連付け | はい | いいえ | はい | この機能は、Project for the Web でのみ利用可能です。 |
プロジェクト スプリント | はい | はい | はい | 開始フィールドは、終了フィールドより前の日付である必要があります。 同じプロジェクトのスプリントが重複することはできません。 この機能は、Project for the Web でのみ利用可能です。 |
プロジェクト目標 | はい | はい | はい | 次のフィールドを使用した操作はサポートされていません: DescriptionPlainText、TaskDisplayOrder |
プロジェクト タスクと目標の関連付け | はい | いいえ | はい | 次のフィールドを使用した操作はサポートされていません: TaskDisplayOrder |
* リソース割り当てレコードは更新されません。 代わりに、古いレコードを削除して、新しいレコードを作成できます。 リソース割り当ての配分型を更新するために、別の API が提供されています。
ID プロパティは省略可能です。 ID プロパティが指定された場合、システムはそのプロパティを使用しようとし、使用できない場合は例外をスローします。 提供されていない場合は、システムが生成します。
制限事項と既知の問題
以下は、制限事項と既知の問題のリストです。
プロジェクト スケジュール API は、Microsoft Project のライセンスを持つユーザー のみが使用できます。 次のユーザーは使用できません:
- アプリケーション ユーザー
- システム ユーザー
- 統合ユーザー
- 必要なライセンスを持っていない他のユーザー
各 OperationSet に割り当てられるのは最大 200 件の操作までです。
各ユーザーが持つことができるのは、最大 10 件のオープン OperationSet です。
リソース割り当て配分型の各更新オペレーションは、1 つのオペレーションとしてカウントされます。
更新された配分型の各リストには、最大 100 のタイム スライスを含めることができます。
OperationSet 障害ステータスと障害ログは現在利用できません。
プロジェクトごとに最大 400 のスプリントがあります。
エラー処理
- 操作セットから生成されたエラーを確認するには、設定>スケジュールの統合>操作セットにアクセスしてください。
- プロジェクト スケジュール サービスから生成されたエラーを確認するには、設定>スケジュールの統合>PSS エラーログにアクセスしてください。
リソース割り当て配分型の編集
エンティティを更新する他のすべてのプロジェクト スケジューリング API とは異なり、リソース割り当て配分型 API は、1 つのエンティティ、msydn_resourceassignment の 1 つのフィールド msdyn_plannedwork の更新のみを担当します。
指定されたスケジュール モードは次のとおりです。
- 固定単位。
- プロジェクト カレンダーは、月曜日、火曜日、木曜日、金曜日の午前 9 時から午後 5 時 (太平洋標準時) までです。 (水曜日は仕事がありません。)
- リソース カレンダーは、月曜日から金曜日の午前 9 時から午後 1 時 (太平洋標準時) までです。
リソース カレンダーが午前 9:00 から午後 1:00 (太平洋標準時) まで、つまり 1 日あたり 4 時間であるため、この割り当ては 1 週間、1 日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 |
たとえば、ワーカーが今週 1 日 3 時間だけ働くようにし、1 時間は他のタスクに使用できるようにする場合です。
UpdatedContours のサンプル ペイロード
[{
"minutes":900.0,
"start":"2022-06-13T00:00:00-07:00",
"end":"2022-06-18T00:00:00-07:00"
}]
これは、Update Contour Schedule 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 |
シナリオ例
このシナリオでは、プロジェクト、チーム メンバー、4 つのタスク、および 2 つのリソース割り当てを作成します。 次に、1 つのタスクの更新、プロジェクトの更新、リソース割り当てコントーの更新、1 つのタスクの削除、1 つのリソース割り当ての削除、タスクの依存関係の作成を行います。
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