Używanie interfejsów API harmonogramu projektu do wykonywania operacji na encji planowania

Dotyczy: Operacje projektu zintegrowane z ERP, Project Operations Core.

Encje planowania

Interfejsy API harmonogramu projektu umożliwiają tworzenie, aktualizowanie i usuwanie jednostek planowania. Aparat planowania w programie Project dla sieci Web zarządza tymi jednostkami. Wcześniejsze wersje Dynamics 365 Project Operations ograniczały operacje tworzenia, aktualizowania i usuwania dla jednostek planowania.

W poniższej tabeli przedstawiono pełną listę encji harmonogramu projektu.

Nazwa podmiotu Logiczna nazwa encji
Project msdyn_project
Zadanie projektu msdyn_projecttask
Zależność zadania projektu msdyn_projecttaskdependency
Przypisanie zasobu msdyn_resourceassignment
Zasobnik projektu msdyn_projectbucket
Członek zespołu projektu msdyn_projectteam
Listy kontrolne projektu msdyn_lista kontrolna projektu
Etykieta projektu msdyn_projectlabel
Zadanie projektu do etykietowania msdyn_projecttasktolabel
Przebieg projektu msdyn_projectsprint

OperationSet

Używaj OperationSet jako jednostki pracy, gdy musisz przetworzyć kilka zgłoszeń wpływających na harmonogram w ramach transakcji.

Interfejsy API harmonogramu projektu

Na poniższej liście przedstawiono aktualne API harmonogramu projektu.

API Opis
msdyn_CreateProjectV1 Użyj tego interfejsu API, aby utworzyć projekt. Natychmiast tworzy projekt i domyślny zasobnik projektu. Projekt można również utworzyć, dodając wiersz do tabeli projektu przy użyciu standardowych interfejsów API usługi Dataverse. Ten proces nie tworzy domyślnego zasobnika dla projektu, ale może mieć lepszą wydajność.
msdyn_CreateTeamMemberV1 Użyj tego interfejsu API, aby utworzyć członka zespołu projektu. Natychmiast tworzy rekord członka zespołu. Możesz również utworzyć członka zespołu, dodając wiersz do tabeli Członek zespołu projektu przy użyciu standardowych interfejsów API usługi Dataverse.
msdyn_CreateOperationSetV1 Użyj tego interfejsu API, aby zaplanować kilka żądań, które muszą być wykonywane w ramach transakcji.
msdyn_PssCreateV1 Użyj tego interfejsu API, aby utworzyć jednostkę. Encją może być dowolna z encji planowania projektów, które obsługują operację tworzenia.
msdyn_PssCreateV2 Użyj tego interfejsu API, aby utworzyć jednostkę. Działa jak msdyn_PssCreateV1, ale można utworzyć wiele jednostek w jednej akcji.
msdyn_PssUpdateV1 Użyj tego interfejsu API, aby zaktualizować jednostkę. Encją może być dowolna z encji planowania projektów, które obsługują operację aktualizowania.
msdyn_PssUpdateV2 Użyj tego interfejsu API, aby zaktualizować jednostki. Działa jak msdyn_PssUpdateV1, ale można zaktualizować wiele jednostek w jednej akcji.
msdyn_PssDeleteV1 Użyj tego interfejsu API, aby usunąć jednostkę. Encją może być dowolna z encji planowania projektów, które obsługują operację usuwania.
msdyn_PssDeleteV2 Użyj tego interfejsu API, aby usunąć jednostki. Działa jak msdyn_PssDeleteV1, ale można usunąć wiele jednostek w jednej akcji.
msdyn_ExecuteOperationSetV1 Użyj tego interfejsu API, aby wykonać wszystkie operacje w danym zestawie operacji.
msdyn_PssUpdateResourceAssignmentV1 Użyj tego interfejsu API służy do aktualizacji przebiegu planowanych prac przypisanych do zasobów.

Używanie interfejsów API harmonogramu projektu z zestawem OperationSet

Ponieważ zarówno CreateProjectV1 , jak i CreateTeamMemberV1 natychmiast tworzą rekordy, nie można używać tych interfejsów API bezpośrednio w elemecie OperationSet. Można jednak użyć tych interfejsów API do utworzenia wymaganych rekordów, utworzenia elementu OperationSet, a następnie użycia wstępnie utworzonych rekordów w elemecie OperationSet.

Obsługiwane operacje

Encja planowania Create Update Delete Ważne uwagi
Operacja w projekcie Yes Yes Yes Pola EffortCompleted i EffortRemaining można edytować w programie Project for the Web, ale nie można edytować tych pól w operacjach programu Project.
Zależność zadania projektu Yes No Yes Rekordy zależności zadań projektu nie są aktualizowane. Zamiast tego możesz usunąć stary rekord i utworzyć nowy rekord.
Przydział zasobów Yes Tak* Yes Operacje z następującymi polami nie są obsługiwane: BookableResourceID, Effort, EffortCompleted, EffortRemaining i PlannedWork.
Zasobnik projektu Yes Yes Yes Domyślny zasobnik jest tworzony przy użyciu interfejsu API CreateProjectV1. W wersji 16. aktualizacji dodano obsługę tworzenia i usuwania zasobników projektów.
Członek zespołu projektu Yes Yes Yes Aby utworzyć operację tworzenia, użyj interfejsu API CreateTeamMemberV1.
Project Yes Yes No Operacje z następującymi polami nie są obsługiwane: StateCode, BulkGenerationStatus, GlobalRevisionToken, CalendarID, Effort, EffortCompleted, EffortRemaining, Progress, Finish, TaskEarliestStart i Duration.
Listy kontrolne projektu Yes Yes Yes
Etykieta projektu No Yes No Możesz zmienić nazwy etykiet. Ta funkcja jest dostępna tylko w programie Project for the Web. Etykiety są tworzone po raz pierwszy podczas otwierania projektu.
Zadanie projektu do etykietowania Yes No Yes Ta funkcja jest dostępna tylko w programie Project for the Web.
Przebieg projektu Yes Yes Yes Pole Początek musi mieć datę wcześniejszą niż pole Zakończenie. Sprinty dotyczące tego samego projektu nie mogą się na siebie nakładać. Ta funkcja jest dostępna tylko w programie Project for the Web.
Cel projektu Yes Yes Yes Operacje z następującymi polami nie są obsługiwane: DescriptionPlainText, TaskDisplayOrder
Zadanie projektu do ustanawiania celu Yes No Yes Operacje z następującymi polami nie są obsługiwane: TaskDisplayOrder

* Rekordy przypisania zasobów nie są aktualizowane. Zamiast tego możesz usunąć stary rekord i utworzyć nowy rekord. Dostępny jest oddzielny interfejs API służący do aktualizowania rozkładów przypisania zasobów.

Właściwość numeru identyfikacyjnego jest opcjonalna. Jeśli podasz właściwość ID, system spróbuje go użyć i zgłosi wyjątek, jeśli nie można go użyć. Jeśli nie podasz właściwości ID, system go wygeneruje.

Ograniczenia i znane problemy

Poniższa lista zawiera ograniczenia i znane problemy:

  • Tylko użytkownicy z licencją programu Microsoft Project mogą używać interfejsów API harmonogramu projektu. Następujący użytkownicy nie mogą używać tych interfejsów API:

    • Użytkownicy aplikacji
    • Użytkownicy systemowi
    • Użytkownicy integracyjni
    • Inni użytkownicy, którzy nie mają wymaganej licencji
  • Każdy element OperationSet może mieć maksymalnie 200 operacji.

  • Każdy użytkownik może mieć maksymalnie 10 otwartych zestawów OperationSet.

  • Każda operacja „Update Resource Assignment Contour” jest liczona jako jedna operacja.

  • Każda lista zaktualizowanych konturów może zawierać maksymalnie 100 wycinków czasu.

  • Obecnie nie są dostępne dzienniki błędów i stanu niepowodzenia OperationSet.

  • Każdy projekt może mieć do 400 sprintów maksymalnie.

  • Limity i ograniczenia projektów i zadań.

Obsługa błędów

  • Aby przejrzeć błędy wygenerowane z zestawów operacji, przejdź do strony Ustawienia>Harmonogram integracji>Zestawy operacji.
  • Aby przejrzeć błędy wygenerowane przez usługę harmonogramu projektów, przejdź do pozycji Ustawienia>Integracja harmonogramu>Dzienniki błędów PSS.

Edytowanie konturów przypisania zasobów

W przeciwieństwie do wszystkich innych interfejsów API planowania projektów, które aktualizują jednostkę, interfejs API konturu przypisania zasobów aktualizuje wyłącznie jedno pole, msdyn_plannedwork, w jednej jednostce msydn_resourceassignment.

Podany tryb harmonogramu to:

  • stałe jednostki.
  • Kalendarz projektu obowiązuje od 9:00 godz. do 17:00 godz. (Czas Pacyficzny) w poniedziałki, wtorki, czwartki i piątki. (W środy nie ma pracy).
  • Kalendarz zasobów jest dostępny od 9:00 do 13:00 (czasu pacyficznego) od poniedziałku do piątku.

To zadanie trwa jeden tydzień, cztery godziny dziennie, ponieważ kalendarz zasobów obejmuje godziny od 9:00 do 13:00 (czasu pacyficznego), czyli cztery godziny dziennie.

  Zadanie Data rozpoczęcia Data zakończenia Ilość 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
9-1 pracownik T1 6/13/2022 6/17/2022 20 4 4 4 4 4

Na przykład, jeśli chcesz, aby w tym tygodniu pracownik pracował tylko trzy godziny dziennie i miał jedną godzinę na inne zadania.

Przykładowa zawartość UpdatedContours

[{

"minutes":900.0,

"start":"2022-06-13T00:00:00-07:00",

"end":"2022-06-18T00:00:00-07:00"

}]

To przypisanie jest po uruchomieniu interfejsu API harmonogramu przebiegu aktualizacji.

  Zadanie Data rozpoczęcia Data zakończenia Ilość 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
9-1 pracownik T1 6/13/2022 6/17/2022 15 3 3 3 3 3

Przykładowy scenariusz

W tym scenariuszu zostanie utworzyć projekt, członka zespołu, cztery zadania i dwa przypisania zasobów. Następnie należy zaktualizować jedno zadanie, zaktualizować projekt, zaktualizować przydział zasobów, usunąć jedno zadanie, usunąć jedno przypisanie zasobów i utworzyć zależność zadania.

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....");

Dodatkowe przykłady

#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