프로젝트 일정 API를 사용하여 일정 엔터티로 작업 수행
적용 대상: 리소스/비 재고 기반 시나리오를 위한 Project Operations, Lite 배포 - 견적 송장 처리.
예약 엔터티
프로젝트 일정 API는 일정 엔터티를 사용하여 생성, 업데이트 및 삭제 작업을 수행하는 기능을 제공합니다. 이러한 엔터티는 웹용 프로젝트에서 예약 엔진을 통해 관리됩니다. 예약 엔터티를 사용한 생성, 업데이트 및 삭제 작업은 이전 Dynamics 365 Project Operations 릴리스에서 제한되었습니다.
다음 표는 프로젝트 일정 엔터티의 전체 목록을 제공합니다.
엔터티 이름 | 엔터티 논리적 이름 |
---|---|
Project | 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 사용
CreateProjectV1 및 CreateTeamMemberV1 모두에 대해 레코드가 즉시 생성되므로 이러한 API를 OperationSet에서 직접 사용할 수 없습니다. 하지만 이를 사용하여 필요한 레코드를 생성하고 OperationSet를 생성한 다음 OperationSet에서 미리 생성된 레코드를 사용할 수 있습니다.
지원되는 작업
예약 엔터티 | 만들기 | 엽데이트 | Delete | 중요 사항 |
---|---|---|---|---|
프로젝트 작업 | 네 | 네 | 네 | Progress, EffortCompleted 및 EffortRemaining 필드는 Project for the Web에서 편집할 수 있지만 Project Operations에서는 편집할 수 없습니다. |
프로젝트 작업 종속성 | 네 | 없음 | 네 | 프로젝트 작업 종속성 레코드는 업데이트되지 않습니다. 대신 이전 레코드를 삭제하고 새 레코드를 만들 수 있습니다. |
리소스 할당 | 네 | 예* | 네 | BookableResourceID, Effort, EffortCompleted, EffortRemaining 및 PlannedWork 필드를 사용한 작업은 지원되지 않습니다. |
프로젝트 버킷 | 있음 | 있음 | 있음 | 기본 버킷은 CreateProjectV1 API를 사용하여 생성됩니다. 프로젝트 버킷 생성 및 삭제에 대한 지원이 업데이트 릴리스 16에 추가되었습니다. |
프로젝트 팀원 | 네 | 네 | 네 | 생성 작업의 경우CreateTeamMemberV1 API를 사용합니다. |
Project | 네 | 네 | 없음 | StateCode, BulkGenerationStatus, GlobalRevisionToken, CalendarID, Effort, EffortCompleted, EffortRemaining, Progress, Finish, TaskEarliestStart 및 Duration 필드를 사용한 작업은 지원되지 않습니다. |
프로젝트 검사 목록 | 네 | 네 | 네 | |
프로젝트 레이블 | 없음 | 네 | 없음 | 레이블 이름은 변경할 수 있습니다. 이 기능은 Project for the Web에서만 사용할 수 있습니다. 레이블은 프로젝트를 처음 열 때 생성됩니다. |
레이블을 지정할 프로젝트 작업 | 네 | 없음 | 네 | 이 기능은 Project for the Web에서만 사용할 수 있습니다. |
프로젝트 스프린트 | 네 | 네 | 네 | 시작 필드의 날짜는 완료 필드보다 이전이어야 합니다. 동일한 프로젝트의 스프린트는 서로 겹칠 수 없습니다. 이 기능은 Project for the Web에서만 사용할 수 있습니다. |
프로젝트 목표 | 있음 | 있음 | 있음 | DescriptionPlainText, TaskDisplayOrder 필드가 있는 작업은 지원되지 않습니다 |
프로젝트 목표 작업 | 있음 | 아니요 | 있음 | TaskDisplayOrder 필드가 있는 작업은 지원되지 않습니다 |
* 리소스 할당 레코드는 업데이트되지 않습니다. 대신 이전 레코드를 삭제하고 새 레코드를 만들 수 있습니다. 리소스 할당 등고선을 업데이트하기 위해 별도의 API가 제공됩니다.
ID 속성은 선택 사항입니다. ID 속성이 제공되면 시스템에서 해당 속성을 사용하려고 시도하고 사용할 수 없는 경우 예외를 발생시킵니다. 제공되지 않으면 시스템에서 생성합니다.
제한 사항 및 알려진 문제
다음 목록에는 제한 사항 및 알려진 문제가 나와 있습니다.
프로젝트 일정 API는 Microsoft Project 라이선스가 있는 사용자만 사용할 수 있습니다. 다음 사용자는 사용할 수 없습니다.
- 응용 프로그램 사용자
- 시스템 사용자
- 통합 사용자
- 필요한 라이선스가 없는 다른 사용자
각 OperationSet는 최대 200개의 작업만 가질 수 있습니다.
각 사용자는 최대 10개의 열린 OperationSet만 가질수 있습니다.
각 업데이트 리소스 할당 윤곽선 작업은 단일 작업으로 계산됩니다.
업데이트된 등고선의 각 목록에는 최대 100개의 타임 슬라이스가 포함될 수 있습니다.
OperationSet 실패 상태 및 실패 로그는 현재 사용할 수 없습니다.
프로젝트당 최대 400개의 스프린트가 있습니다.
오류 처리
- 작업 세트에서 생성된 오류를 검토하려면 설정>일정 통합>작업 세트로 이동합니다.
- 프로젝트 일정 서비스에서 생성된 오류를 검토하려면 설정>일정 통합>PSS 오류 로그로 이동합니다.
리소스 할당 윤곽선 편집
엔터티를 업데이트하는 다른 모든 프로젝트 일정 API와 달리 자원 할당 윤곽 API는 단일 엔터티 msydn_resourceassignment에서 단일 필드 msdyn_plannedwork에 대한 업데이트를 단독으로 담당합니다.
주어진 일정 모드는 다음과 같습니다.
- 고정 단위.
- 프로젝트 달력은 월요일, 화요일, 목요일, 금요일 오전 9시부터 오후 5시(태평양 표준시)까지입니다. (수요일에는 일이 없습니다.)
- 리소스 달력은 월요일부터 금요일까지 오전 9시부터 오후 1시(태평양 표준시)까지입니다.
리소스 달력이 오전 9:00부터 오후 1:00까지(태평양 표준시) 또는 하루 4시간이므로 이 배정은 1주일 동안 하루 4시간 동안 진행됩니다.
작업 | 시작 날짜 | 종료 날짜 | 수량 | 2022년 6월 13일 | 2022년 6월 14일 | 2022년 6월 15일 | 2022년 6월 16일 | 2022년 6월 17일 | |
---|---|---|---|---|---|---|---|---|---|
9-1시 작업자 | T1 | 2022년 6월 13일 | 2022년 6월 17일 | 20 | 4 | 4 | 4 | 4 | 4 |
예를 들어, 작업자가 이번 주에 매일 3시간만 일하고 다른 작업에는 1시간을 허용하도록 하려는 경우입니다.
UpdatedContours 샘플 페이로드
[{
"minutes":900.0,
"start":"2022-06-13T00:00:00-07:00",
"end":"2022-06-18T00:00:00-07:00"
}]
컨투어 일정 API 업데이트가 실행된 후의 할당입니다.
작업 | 시작 날짜 | 종료 날짜 | 수량 | 2022년 6월 13일 | 2022년 6월 14일 | 2022년 6월 15일 | 2022년 6월 16일 | 2022년 6월 17일 | |
---|---|---|---|---|---|---|---|---|---|
9-1시 작업자 | T1 | 2022년 6월 13일 | 2022년 6월 17일 | 15 | 3 | 3 | 3 | 3 | 3 |
샘플 시나리오
이 시나리오에서는 프로젝트, 팀 구성원, 4개의 작업 및 2개의 리소스 할당을 만듭니다. 다음으로, 하나의 작업을 업데이트하고, 프로젝트를 업데이트하고, 리소스 할당 윤곽을 업데이트하고, 하나의 작업을 삭제하고, 하나의 리소스 할당을 삭제하고, 작업 종속성을 만듭니다.
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