Compartilhar via


Use APIs de agendamento do Project para realizar operações com entidades de Agendamento

Aplica-se a: Project Operations para cenários baseados em recursos/não estocados, implantação Lite – transação para faturamento pro forma.

Entidades de agendamento

APIs de agendamento do Project fornecem a capacidade de executar operações de criação, atualização e exclusão com Entidades de agendamento. Essas entidades são gerenciadas por meio do mecanismo de agendamento no projeto para a Web. Criar, atualizar e excluir operações com Entidades de agendamento foram restritos em versões anteriores do Dynamics 365 Project Operations.

A tabela a seguir fornece uma lista completa das entidades de agendamento do Project.

Nome da entidade Nome lógico da entidade
Project msdyn_project
Tarefa do Projeto msdyn_projecttask
Dependência de Tarefa do Projeto msdyn_projecttaskdependency
Atribuição de Recurso msdyn_resourceassignment
Bucket do Projeto msdyn_projectbucket
Membro da Equipe do Projeto msdyn_projectteam
Listas de Verificação do Projeto msdyn_projectchecklist
Rótulo do Projeto msdyn_projectlabel
Tarefa do Projeto para o Rótulo msdyn_projecttasktolabel
Sprint do Projeto msdyn_projectsprint

OperationSet

OperationSet é um padrão de unidade de trabalho que pode ser usado quando várias solicitações de impacto de agenda devem ser processadas em uma transação.

APIs de agendamento do Project

A seguir está uma lista de APIs atuais de agendamento do Project.

API Descrição
msdyn_CreateProjectV1 Essa API é usada para criar um projeto. O projeto e o bucket de projeto padrão são criados imediatamente. A criação do projeto também pode ser feita adicionando-se uma linha à tabela do projeto usando APIs do Dataverse padrão. Esse processo não vai criar um bucket padrão para o projeto, mas pode apresentar um desempenho melhor.
msdyn_CreateTeamMemberV1 Essa API é usada para criar um membro da equipe do projeto. O registro do membro da equipe é criado imediatamente. A criação do membro da equipe também pode ser feita adicionando-se uma linha à tabela do membro da equipe do projeto usando APIs do Dataverse padrão.
msdyn_CreateOperationSetV1 Essa API é usada para agendar várias solicitações que devem ser executadas em uma transação.
msdyn_PssCreateV1 Essa API é usada para criar uma entidade. A entidade pode ser qualquer uma das entidades de agendamento do Project que oferecem suporte à operação de criação.
msdyn_PssCreateV2 Essa API é usada para criar uma entidade. Ela funciona como msdyn_PssCreateV1, mas várias entidades podem ser criadas em uma ação.
msdyn_PssUpdateV1 Essa API é usada para atualizar uma entidade. A entidade pode ser qualquer uma das entidades de agendamento do Project que oferecem suporte à operação de atualização.
msdyn_PssUpdateV2 Essa API é usada em entidades atualizadas. Ela funciona como msdyn_PssUpdateV1, mas várias entidades podem ser atualizadas em uma ação.
msdyn_PssDeleteV1 Essa API é usada para excluir uma entidade. A entidade pode ser qualquer uma das entidades de agendamento do Project que oferecem suporte à operação de exclusão.
msdyn_PssDeleteV2 Essa API é usada para excluir entidades. Ela funciona como msdyn_PssDeleteV1, mas várias entidades podem ser excluídas em uma ação.
msdyn_ExecuteOperationSetV1 Essa API é usada para executar todas as operações no conjunto de operações especificado.
msdyn_PssUpdateResourceAssignmentV1 Essa API é usada para atualizar um contorno de trabalho planejado de Atribuição de Recurso.

Usando APIs de agendamento do Project com OperationSet

Como registros são criados imediatamente para CreateProjectV1 e CreateTeamMemberV1, essas APIs não podem ser usadas diretamente no OperationSet. No entanto, você pode usá-las para criar os registros obrigatórios, criar um OperationSet e usar esses registros pré-criados no OperationSet.

Operações com suporte

Entidade de agendamento Criar Atualizar Delete Considerações importantes
Tarefa do projeto Sim Sim Sim É possível editar os campos Progress, EffortCompleted e EffortRemaining no Project for the Web, mas não no Project Operations.
Dependência de tarefa do projeto Sim No Sim Os registros de dependência da tarefa do projeto não são atualizados. Em vez disso, é possível excluir um registro antigo e criar um novo registro.
Atribuição de recurso Sim Sim* Sim Não há suporte a operações com os seguintes campos: BookableResourceID, Effort, EffortCompleted, EffortRemaining e PlannedWork.
Bucket do projeto Sim Sim Sim O bucket padrão é criado usando a API CreateProjectV1. O suporte para a criação e exclusão de buckets de projeto foi adicionado na Versão de atualização 16.
Membro da equipe do projeto Sim Sim Sim Para a operação de criação, use a API CreateTeamMemberV1.
Project Sim Sim No Não há suporte para operações com os seguintes campos: StateCode, BulkGenerationStatus, GlobalRevisionToken, CalendarID, Effort, EffortCompleted, EffortRemaining, Progress, Finish, TaskEarliestStart e Duration.
Listas de Verificação do Projeto Sim Sim Sim
Rótulo do Projeto No Sim No Os nomes dos rótulos podem ser alterados. Este recurso só está disponível para o Project for the Web. Os rótulos são criados na primeira vez em que você abre um projeto.
Tarefa do Projeto para o Rótulo Sim No Sim Este recurso só está disponível para o Project for the Web.
Sprint do Projeto Sim Sim Sim O campo Iniciar deve ter uma data anterior ao campo Concluir. As sprints do mesmo projeto não podem se sobrepor. Este recurso só está disponível para o Project for the Web.
Meta do Projeto Sim Sim Sim As operações com os seguintes campos não são compatíveis: DescriptionPlainText, TaskDisplayOrder
Tarefa do Projeto para Meta Sim Não Sim As operações com os seguintes campos não são compatíveis: TaskDisplayOrder

* Os registros de atribuição do recurso não são atualizados. Em vez disso, é possível excluir o registro antigo e criar um novo registro. Uma API separada foi fornecida para atualizar os contornos da Atribuição de Recurso.

A propriedade da ID é opcional. Se ela for fornecida, o sistema vai tentar usá-la e lançar uma exceção se ela não puder ser usada. Se ela não for fornecida, o sistema vai gerá-la.

Limitações e problemas conhecidos

Esta é uma lista de limitações e problemas conhecidos:

  • As APIs de agendamento de projetos podem ser usadas somente por Usuários com Licença do Microsoft Project. Elas não podem ser usadas por:

    • Usuários do aplicativo
    • Usuários do sistema
    • Usuários de Integração
    • Outros usuários sem a licença necessária
  • Cada OperationSet pode ter no máximo 200 operações.

  • Cada usuário pode ter no máximo 10 OperationSets abertos.

  • No momento, o Project Operations dá suporte no máximo a 1000 tarefas em um projeto.

  • Cada operação Atualizar o Contorno da Atribuição de Recurso conta como uma única operação.

  • Cada lista de contornos atualizados pode conter no máximo 100 frações de tempo.

  • O status de falha e logs de falha do OperationSet não estão disponíveis no momento.

  • Há, no máximo, 400 sprints por projeto.

  • Limites em projetos e tarefas.

  • No momento, os rótulos estão disponíveis somente para o Project for the Web.

  • Os rótulos são criados na primeira vez em que você abre um projeto.

  • Há, no máximo, dez metas por projeto.

  • Cada tarefa pode aparecer na Tarefa do Projeto para Meta uma vez.

Tratamento de erros

  • Para revisar erros gerados a partir dos Conjuntos de Operações, acesse Configurações>Integração de Agenda>Conjuntos de Operações.
  • Para revisar os erros gerados a partir do serviço de agendamento do Project, vá para Configurações>Integração de Agenda>Logs de Erros do PSS.

Editando Contornos da Atribuição de Recurso

Diferentemente de todas as outras APIs de agendamento de projeto que atualizam uma entidade, a API de contorno de atribuição de recurso é a única responsável por atualizações em um único campo, msdyn_plannedwork, em uma única entidade, msydn_resourceassignment.

O modo de agendamento fornecido é:

  • unidades fixas.
  • O calendário do projeto vai das 09h às 17h (horário do Pacífico), segunda, terça, quinta e sexta-feira. (Não há trabalho às quartas-feiras.)
  • O calendário do projeto vai das 09h às 17h (horário do Pacífico), de segunda a sexta-feira.

Esta tarefa é para uma semana, quatro horas por dia. Isso ocorre porque o calendário de recursos vai das 9h às 13h (horário do Pacífico), ou quatro horas por dia.

  Tarefa Data de Início Data de Término Quantidade 13/06/2022 14/06/2022 15/06/2022 16/06/2022 17/06/2022
Trabalhador 9 a 1 T1 13/06/2022 17/06/2022 20 4 4 4 4 4

Por exemplo, se você quiser que o trabalhador trabalhe somente três horas por dia nesta semana e permitir uma hora para outras tarefas.

Conteúdo de amostra UpdatedContours

[{

"minutes":900.0,

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

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

}]

Esta é a atribuição após a execução da API Atualizar Agenda de Contorno.

  Tarefa Data de Início Data de Término Quantidade 13/06/2022 14/06/2022 15/06/2022 16/06/2022 17/06/2022
Trabalhador 9 a 1 T1 13/06/2022 17/06/2022 15 3 3 3 3 3

Cenário de exemplo

Nesse cenário, você cria um projeto, um membro da equipe, quatro tarefas e duas atribuições de recurso. Em seguida, você atualiza uma tarefa, o projeto, atualiza um contorno de atribuição de recurso, exclui uma tarefa, exclui uma atribuição de recurso e cria uma dependência de tarefa.

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

Amostras adicionais

#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