共用方式為


使用專案排程 API 以對排程實體執行作業

適用於:與 ERP 整合的 Project Operations、Project Operations Core。

排程實體

專案排程 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 Description
msdyn_CreateProjectV1 此 API 是用來建立專案的。 專案和預設專案貯體會立即建立。 還可以使用標準 Dataverse API,將一個資料列新增至專案資料表來完成專案建立。 此過程不會為專案建立預設儲存桶,但可能具有更好的效能。
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 是用來更新資源指派計畫作業分佈。

與 OperationSet 搭配使用專案排程 API

因為 CreateProjectV1CreateTeamMemberV1 都會立即建立記錄,無法直接將這些 API 用於 OperationSet。 但是,您可以使用它們來建立所需的記錄,建立一個 OperationSet,然後使用 OperationSet 中預先建立的記錄。

支援的作業

排程實體 建立​​ 更新 删除 重要考量
專案工作 EffortCompletedEffortRemaining 欄位可以在 Project 網頁版中編輯,但無法在 Project Operations 中編輯。
專案工作相依性 不會更新專案工作相依性記錄。 相反地,可以刪除舊記錄,也可以建立新記錄。
資源指派 是* 不支援對下列欄位的作業:BookableResourceID投入量EffortCompletedEffortRemainingPlannedWork
專案貯體 預設貯體是使用 CreateProjectV1 API 所建立。 更新版本 16 中已新增對建立和刪除專案貯體的支援。
專案團隊成員 如果是建立作業,請使用 CreateTeamMemberV1 API。
專案 不支援對下列欄位的作業:StateCodeBulkGenerationStatusGlobalRevisionTokenCalendarID投入量EffortCompletedEffortRemaining進度完成TaskEarliestStart期間
專案檢查清單
專案標籤 標籤名稱可以變更。 此功能僅適用於 Project for the Web。 第一次開啟專案時,會建立標籤。
專案工作與標籤 此功能僅適用於 Project for the Web。
專案短期衝刺 起始欄位的日期必須早於結束欄位。 同一個專案的短期衝刺不能相互重疊。 此功能僅適用於 Project for the Web。
專案目標 不支援對下列欄位的作業:DescriptionPlainText、TaskDisplayOrder
專案工作與目標 不支援對下列欄位的作業:TaskDisplayOrder

* 不會更新資源指派記錄。 相反地,可以刪除舊記錄,也可以建立新記錄。 提供單獨的 API 來更新資源分配輪廓。

識別碼屬性可選用。 如果提供了 ID 屬性,系統會嘗試使用它,如果無法使用則拋出異常。 如果未提供,則系統會產生此屬性。

限制和已知問題

以下清單顯示了限制和已知問題:

  • 只有擁有 Microsoft Project 授權的使用者才能使用專案排程 API。 無法供下列使用者使用:

    • 應用程式使用者
    • 系統使用者
    • 整合使用者
    • 其他沒有必要授權的使用者
  • 每個 OperationSet 最多只能有 200 項作業。

  • 每個使用者最多只能有 10 個已開啟的 OperationSets

  • 每個更新資源指派分佈作業都計為單一作業。

  • 每個更新的分佈清單最多可以包含 100 個時間配量。

  • OperationSet 失敗狀態和失敗記錄目前未提供。

  • 每個專案最多有 400 個短期衝刺。

  • 專案和工作的限制與界限

錯誤處理

  • 若要檢閱作業集所產生的錯誤,請移至設定>排程整合>作業集
  • 若要檢閱專案排程服務所產生的錯誤,請移至設定>排程整合>PSS 錯誤記錄

編輯資源指派分佈

與更新實體的所有其他專案排程 API 不同,資源指派分佈 API 只負責更新單一實體 msydn_resourceassignment 上的單一欄位 msdyn_plannedwork。

指定的排程模式為:

  • 固定單位
  • 專案行事曆是星期一、星期二、星期四和星期五下午 9:00 到 5:00 (太平洋時間)。 (星期三執行沒有工作。)
  • 資源行事曆是星期一至星期五上午 9:00 到下午 1:00 (太平洋時間)。

此分配為期一周,每天四小時,因為資源行事曆是從上午 9:00 到下午 1:00 (太平洋時間),即每天四個小時。

  Task 開始日期 結束日期 數量 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
9-1 工作人員 T1 6/13/2022 6/17/2022 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 執行後的指派。

  Task 開始日期 結束日期 數量 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
9-1 工作人員 T1 6/13/2022 6/17/2022 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