Comparteix via


Utilitzar les API de planificació de projectes per fer operacions amb entitats de planificació

S'aplica a: Operacions de projecte integrades amb ERP, Project Operations Core.

Entitats de planificació

Les API de planificació de projectes proporcionen la possibilitat de realitzar operacions de creació, actualització i supressió amb entitats de planificació. Aquestes entitats s'administren mitjançant el motor de Planificació del Projecte for the web. Les operacions de creació, actualització i supressió amb entitats de planificació estaven restringides en versions anteriors Dynamics 365 Project Operations .

A la taula següent es proporciona una llista completa de les entitats de planificació de projectes.

Nom de l’entitat Nom lògic de l’entitat
Projecte msdyn_project
Tasca del projecte msdyn_projecttask
Dependència de les tasques del projecte msdyn_projecttaskdependency
Assignació de recursos msdyn_resourceassignment
Dipòsit de projecte msdyn_projectbucket
Membre de l'equip del projecte msdyn_projectteam
Llistes de comprovació del projecte msdyn_projectchecklist
Etiqueta del projecte msdyn_projectlabel
Tasca de projecte que s'ha d'etiqueta msdyn_projecttasktolabel
Esprint del projecte msdyn_projectsprint

Conjunt d'operacions

OperationSet és un patró d'unitat de treball que es pot utilitzar quan diverses sol·licituds que afecten la planificació s'han de processar dins d'una transacció.

API de planificació de projectes

A continuació es mostra una llista de les API de planificació de projectes actuals.

API Descripció
msdyn_CreateProjectV1 Aquesta API s'utilitza per crear un projecte. El projecte i el dipòsit per defecte del projecte es creen immediatament. La creació del projecte també es pot fer afegint una fila a la taula del projecte mitjançant API estàndard Dataverse . Aquest procés no crea un cub per defecte per al projecte, però pot tenir un millor rendiment.
msdyn_CreateTeamMemberV1 Aquesta API s'utilitza per crear un membre de l'equip del projecte. El registre del membre de l'equip es crea immediatament. La creació de membres de l'equip també es pot fer afegint una fila a la taula Membre de l'equip del projecte mitjançant API estàndard Dataverse .
msdyn_CreateOperationSetV1 Aquesta API s'utilitza per planificar diverses sol·licituds que s'han de fer dins d'una transacció.
msdyn_PssCreateV1 Aquesta API s'utilitza per crear una entitat. L'entitat pot ser qualsevol de les entitats de planificació de projectes que admeten l'operació de creació.
msdyn_PssCreateV2 Aquesta API s'utilitza per crear una entitat. Funciona com msdyn_PssCreateV1, però es poden crear diverses entitats en una sola acció.
msdyn_PssUpdateV1 Aquesta API s'utilitza per actualitzar una entitat. L'entitat pot ser qualsevol de les entitats de planificació de projectes que admeten l'operació d'actualització.
msdyn_PssUpdateV2 Aquesta API s'utilitza per actualitzar entitats. Funciona com msdyn_PssUpdateV1, però es poden actualitzar diverses entitats en una sola acció.
msdyn_PssDeleteV1 Aquesta API s'utilitza per suprimir una entitat. L'entitat pot ser qualsevol de les entitats de planificació de projectes que admeten l'operació de supressió.
msdyn_PssDeleteV2 Aquesta API s'utilitza per suprimir entitats. Funciona com msdyn_PssDeleteV1, però es poden suprimir diverses entitats en una sola acció.
msdyn_ExecuteOperationSetV1 Aquesta API s'utilitza per executar totes les operacions dins del conjunt d'operacions determinat.
msdyn_PssUpdateResourceAssignmentV1 Aquesta API s'utilitza per actualitzar un contorn de treball planificat d'una assignació de recursos.

Ús de les API de planificació del projecte amb OperationSet

Com que els registres es creen immediatament tant per a CreateProjectV1 com per a CreateTeamMemberV1 , aquestes API no es poden utilitzar directament a l'OperationSet . Tanmateix, podeu utilitzar-los per crear els registres necessaris, crear un OperationSet i, a continuació, utilitzar els registres creats prèviament a l'OperationSet .

Operacions admeses

Entitat de planificació Creació Actualitza Suprimeix Consideracions importants
Tasca del projecte Els camps EffortCompleted i EffortRemaining es poden editar al Project per al web, però no es poden editar al Project Operations.
Dependència de les tasques del projecte No Els registres de dependència de les tasques del projecte no s'actualitzen. En lloc d'això, es pot suprimir un registre antic i crear-ne un de nou.
Assignació de recursos Sí* No s'admeten les operacions amb els camps següents: BookableResourceID,Effort,EffortCompleted,EffortRemaining i PlannedWork.
Dipòsit de projecte El cub per defecte es crea mitjançant l'API CreateProjectV1 . La compatibilitat per crear i suprimir dipòsits de projectes s'ha afegit a la versió 16 d'actualització.
Membre de l'equip del projecte Per a l'operació de creació, utilitzeu l'API CreateTeamMemberV1 .
Projecte No No s'admeten les operacions amb els camps següents: StateCode,BulkGenerationStatus,GlobalRevisionToken,CalendarID,Effort,EffortCompleted,EffortRemaining,Progress,Finish,TaskEarliestStart i Duration.
Llistes de comprovació del projecte
Etiqueta del projecte No No Els noms de les etiquetes es poden canviar. Aquesta funció només està disponible per a Project for the Web. Les etiquetes es creen el primer cop s'obre un projecte.
Tasca de projecte que s'ha d'etiqueta No Aquesta funció només està disponible per a Project for the Web.
Esprint del projecte El camp Inici ha de tenir una data anterior al camp Finalització . Els sprints d'un mateix projecte no es poden superposar entre si. Aquesta funció només està disponible per a Project for the Web.
Objectiu del projecte No s'admeten les operacions amb els camps següents: DescriptionPlainText, TaskDisplayOrder
Tasca de projecte per als objectius No No s'admeten les operacions amb els camps següents: TaskDisplayOrder

* Els registres d'assignació de recursos no s'actualitzen. En lloc d'això, es pot suprimir el registre antic i crear-ne un de nou. Es proporciona una API independent per actualitzar els contorns d'assignació de recursos.

La propietat ID és opcional. Si es proporciona la propietat ID, el sistema intenta utilitzar-la i llança una excepció si no es pot utilitzar. Si no es proporciona, el sistema el genera.

Limitacions i problemes coneguts

La llista següent mostra les limitacions i els problemes coneguts:

  • Les API de planificació de projectes només poden ser utilitzades per usuaris amb llicència de projecte Microsoft. No les poden utilitzar:

    • Usuaris de l'aplicació
    • Usuaris del sistema
    • Usuaris d'integració
    • Altres usuaris que no tenen la llicència necessària
  • Cada OperationSet només pot tenir un màxim de 200 operacions.

  • Cada usuari només pot tenir un màxim de 10 OperationSets oberts .

  • Cada operació Actualitza el contorn d'assignació de recursos es compta com una única operació.

  • Cada llista de contorns actualitzats pot contenir un màxim de 100 porcions de temps.

  • L'estat d'error i els registres d'errors d'OperationSet no estan disponibles actualment.

  • Hi ha un màxim de 400 sprints per projecte.

  • Límits i límits en projectes i tasques.

Gestió d'errors

  • Per revisar els errors generats a partir dels conjunts d'operacions, aneu a Configuració>Programar conjunts> d'operacions d'integració.
  • Per revisar els errors generats des del servei de planificació del projecte, aneu a Registres> d'errors de PSS d'integració>de la planificació de la configuració.

Edició dels contorns d'assignació de recursos

A diferència de la resta d'API de planificació de projectes que actualitzen una entitat, l'API de contorn d'assignació de recursos només és responsable de les actualitzacions d'un sol camp, msdyn_plannedwork, en una única entitat, msydn_resourceassignment.

El mode de planificació proporcionat és:

  • unitats fixes.
  • El calendari del projecte és de 9:00 a 5:00 PM (hora del Pacífic) dilluns, dimarts, dijous i divendres. (No hi ha feina els dimecres.)
  • El calendari de recursos és de 9:00 a 13:00 (hora del Pacífic) de dilluns a divendres.

Aquesta tasca és d'una setmana, quatre hores al dia perquè el calendari de recursos és de 9:00 a 13:00 (hora del Pacífic), o quatre hores al dia.

  Tasca Data d’inici Data d’acabament Quantitat 13/6/2022 14/6/2022 15/6/2022 16/6/2022 17/6/2022
9-1 treballador T1 13/6/2022 17/6/2022 20 4 4 4 4 4

Per exemple, si voleu que el treballador només treballi tres hores cada dia aquesta setmana i tingui una hora per a altres tasques.

Càrrega útil d'exemple d'UpdatedContours

[{

"minutes":900.0,

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

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

}]

Aquesta és l'assignació després d'executar l'API de planificació de contorn d'actualització.

  Tasca Data d’inici Data d’acabament Quantitat 13/6/2022 14/6/2022 15/6/2022 16/6/2022 17/6/2022
9-1 treballador T1 13/6/2022 17/6/2022 15 3 3 3 3 3

Escenari d'exemple

En aquest escenari, creeu un projecte, un membre de l'equip, quatre tasques i dues assignacions de recursos. A continuació, actualitzeu una tasca, actualitzeu el projecte, actualitzeu un contorn d'assignació de recursos, suprimiu una tasca, suprimiu una assignació de recursos i creeu una dependència de tasca.

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

Mostres addicionals

#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