Usar las API de programación de proyectos para realizar operaciones con entidades de programación
Se aplica a: Project Operations para escenarios basados en recursos/no mantenidos, implementación lite: del acuerdo a la factura proforma.
Entidades de programación
Las API de programación de proyectos ofrecen la capacidad de realizar operaciones de creación, actualización y eliminación con Entidades de programación. Estas entidades se administran a través del motor de programación en Project para la web. Crear, actualizar y eliminar operaciones con Entidades de programación estaba restringido en versiones anteriores de Dynamics 365 Project Operations.
La siguiente tabla proporciona una lista completa de las entidades del programación del proyecto.
Nombre de entidad | Nombre lógico de la entidad |
---|---|
Project | msdyn_project |
Tarea de proyecto | msdyn_projecttask |
Dependencia de tareas de proyecto | msdyn_projecttaskdependency |
Asignación de recursos | msdyn_resourceassignment |
Cubo de proyecto | msdyn_projectbucket |
Miembro del equipo del proyecto | msdyn_projectteam |
Listas de comprobación del proyecto | msdyn_projectchecklist |
Etiqueta de proyecto | msdyn_projectlabel |
Relación de tarea con etiqueta de proyecto | msdyn_projecttasktolabel |
Sprint del proyecto | msdyn_projectsprint |
OperationSet
OperationSet es un patrón de unidad de trabajo que se puede usar cuando se deben procesar varias solicitudes que afectan la programación dentro de una transacción.
API de programación del proyecto
La siguiente es una lista de las API de programación de proyectos actuales.
API | Description |
---|---|
msdyn_CreateProjectV1 | Esta API se utiliza para crear un proyecto. El cubo del proyecto y el proyecto predeterminado se crean inmediatamente. La creación de proyectos también se puede realizar agregando una fila a la tabla del proyecto usando API Dataverse estándar. Este proceso no crea un bucket predeterminado para el proyecto, pero puede tener un mejor rendimiento. |
msdyn_CreateTeamMemberV1 | Esta API se utiliza para crear un miembro de equipo de proyecto. El registro de miembro del equipo se crea inmediatamente. La creación de Miembro del equipo también se puede realizar agregando una fila a la tabla Miembro del equipo del proyecto usando API Dataverse estándar. |
msdyn_CreateOperationSetV1 | Esta API se puede utilizar para programar varias solicitudes que deben realizarse dentro de una transacción. |
msdyn_PssCreateV1 | Esta API se utiliza para crear una entidad. La entidad puede ser cualquiera de las entidades de programación del proyecto que respaldan la operación de creación. |
msdyn_PssCreateV2 | Esta API se utiliza para crear una entidad. Funciona como msdyn_PssCreateV1, pero se pueden crear varias entidades en una sola acción. |
msdyn_PssUpdateV1 | Esta API se utiliza para actualizar una entidad. La entidad puede ser cualquiera de las entidades de programación del proyecto que respaldan la operación de actualización. |
msdyn_PssUpdateV2 | Esta API se utiliza para actualizar entidades. Funciona como msdyn_PssUpdateV1, pero se pueden actualizar varias entidades en una sola acción. |
msdyn_PssDeleteV1 | Esta API se utiliza para eliminar una entidad. La entidad puede ser cualquiera de las entidades de programación del proyecto que respaldan la operación de eliminación. |
msdyn_PssDeleteV2 | Esta API se utiliza para eliminar entidades. Funciona como msdyn_PssDeleteV1, pero se pueden eliminar varias entidades en una sola acción. |
msdyn_ExecuteOperationSetV1 | Esta API se utiliza para ejecutar todas las operaciones dentro del conjunto de operaciones establecido. |
msdyn_PssUpdateResourceAssignmentV1 | Esta API se utiliza para actualizar un contorno de trabajo planificado de asignación de recursos. |
Usar las API de programación de proyectos con OperationSet
Como los registros se crean inmediatamente con ambos CreateProjectV1 y CreateTeamMemberV1, estas API no se pueden utilizar directamente en OperationSet directamente. Sin embargo, puede utilizarlos para crear los registros necesarios, crear un OperationSet y, a continuación, utilizar estos registros creados previamente en OperationSet.
Operaciones admitidas
Entidades de programación | Crear | Update | Delete | Consideraciones importantes |
---|---|---|---|---|
Tarea de proyecto | Sí | Sí | Sí | Los campos Progreso, EffortCompleted y EffortRemaining los campos se pueden editar en Project for the Web, pero no se pueden editar en Project Operations. |
Dependencia de tareas de proyecto | Sí | No | Sí | Los registros de dependencia de tareas del proyecto no se actualizan. En su lugar, se puede eliminar un registro antiguo y se puede crear un registro nuevo. |
Asignación de recursos | Sí | Sí* | Sí | No se admiten las operaciones con los siguientes campos: BookableResourceID, Esfuerzo, EffortCompleted, EffortCompleted y PlannedWork. |
Cubo de proyecto | Sí | Sí | Sí | El cubo predeterminado se crea utilizando la API CreateProjectV1. Se agregó soporte para crear y eliminar cubos de proyectos en la versión de actualización 16. |
Miembro del equipo del proyecto | Sí | Sí | Sí | Para la operación de creación, use la API CreateTeamMemberV1. |
Project | Sí | Sí | No | No se admiten operaciones con los siguientes campos: StateCode, BulkGenerationStatus, GlobalRevisionToken, CalendarID, Esfuerzo, EffortCompleted, EffortRemaining, Progreso, Terminar, TaskEarliestStart, y Duración. |
Listas de comprobación del proyecto | Sí | Sí | Sí | |
Etiqueta de proyecto | No | Sí | No | Los nombres de las etiquetas se pueden cambiar. Esta función solo está disponible para Project for the Web. Las etiquetas se crean la primera vez que abre un proyecto. |
Relación de tarea con etiqueta de proyecto | Sí | No | Sí | Esta función solo está disponible para Project for the Web. |
Sprint del proyecto | Sí | Sí | Sí | El campo Inicio debe tener una fecha anterior al campo Fin. Los sprints para el mismo proyecto no pueden superponerse entre sí. Esta función solo está disponible para Project for the Web. |
Objetivo del proyecto | Sí | Sí | Sí | No se admiten operaciones con los siguientes campos: DescripciónTexto sin formato, TaskDisplayOrder |
Relación de tarea con objetivo de proyecto | Sí | No | Sí | No se admiten operaciones con los siguientes campos: TaskDisplayOrder |
* Los registros de asignación de recursos no se actualizan. En su lugar, se puede eliminar el registro antiguo y se puede crear un registro nuevo. Se proporciona una API independiente para actualizar los contornos de asignación de recursos.
La propiedad del identificador es opcional. Si se proporciona, el sistema intenta usarla y lanza una excepción si no se puede usar. Si no se proporciona, el sistema lo genera.
Limitaciones y problemas conocidos
La siguiente es una lista de limitaciones y problemas conocidos:
Las API de programación del proyecto solo pueden utilizarlas los Usuarios con licencia de Microsoft Project. No pueden ser utilizadas por:
- Usuarios de la aplicación
- Usuarios de sistema
- Usuarios de integración
- Otros usuarios que no tienen la licencia requerida
Cada OperationSet solo puede tener un máximo de 200 operaciones.
Cada usuario solo puede tener un máximo de 10 operaciones OperationSetabiertas.
Cada operación Actualizar contorno de asignación de recursos cuenta como una sola operación.
Cada lista de contornos actualizados puede contener un máximo de 100 intervalos de tiempo.
El estado de error y los registros de error de OperationSet no están disponibles actualmente.
Hay un máximo de 400 sprints por proyecto.
Control de errores
- Para revisar los errores generados por los conjuntos de operaciones, vaya a Configuración>Integración de programación>Conjuntos de operaciones.
- Para revisar los errores generados por el servicio de programación de proyectos, vaya a Ajustes>Integración de programación>Registros de errores de PSS.
Edición de contornos de asignación de recursos
A diferencia de todas las demás API de programación de proyectos que actualizan una entidad, la API de contorno de asignación de recursos es la única responsable de las actualizaciones de un solo campo, msdyn_plannedwork, en una sola entidad, msydn_resourceassignment.
El modo de programación dado es:
- unidades fijas.
- El calendario del proyecto es de 9:00 a 5:00 p. m. (hora del Pacífico) los lunes, martes, jueves y viernes. (No hay trabajo los miércoles.)
- El calendario de recursos es de 9:00 a 1:00 p. m. (hora del Pacífico) los lunes, martes, miércoles, jueves y viernes.
Esta asignación es para una semana, cuatro horas al día porque el calendario de recursos es de 9:00 AM a 1:00 PM (hora del Pacífico), o cuatro horas al día.
Tarea | Fecha de inicio | Fecha de finalización | Cantidad | 13/6/2022 | 14/6/2022 | 15/6/2022 | 16/6/2022 | 17/6/2022 | |
---|---|---|---|---|---|---|---|---|---|
9-1 trabajador | T1 | 13/6/2022 | 17/6/2022 | 20 | 4 | 4 | 4 | 4 | 4 |
Por ejemplo, si desea que el trabajador solo trabaje tres horas cada día esta semana y permita una hora para otras tareas.
Carga útil de ejemplo UpdatedContours
[{
"minutes":900.0,
"start":"2022-06-13T00:00:00-07:00",
"end":"2022-06-18T00:00:00-07:00"
}]
Esta es la asignación después de ejecutar la API de programación de contorno de actualización.
Task | Fecha de inicio | Fecha de finalización | Cantidad | 13/6/2022 | 14/6/2022 | 15/6/2022 | 16/6/2022 | 17/6/2022 | |
---|---|---|---|---|---|---|---|---|---|
9-1 trabajador | T1 | 13/6/2022 | 17/6/2022 | 15 | 3 | 3 | 3 | 3 | 3 |
Escenario de ejemplo
En este escenario, crea un proyecto, un miembro del equipo, cuatro tareas y dos asignaciones de recursos. A continuación, actualiza una tarea, actualizará el proyecto, actualizará un contorno de asignación de recursos, eliminará una tarea, eliminará una asignación de recursos y creará una dependencia de tareas.
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....");
Ejemplos adicionales
#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