استخدام واجهات برمجة التطبيقات لجدولة المشروع لتنفيذ عمليات مع كيانات الجدولة

ينطبق على: ‏‫Project Operations للسيناريوهات المستندة إلى مورد/غير مخزنة‬، ‏‫التوزيع الخفيف – التعامل مع الفواتير الأولية‬.

كيانات الجدولة

توفر واجهات برمجة التطبيقات لجدولة المشروع القدرة على تنفيذ عمليات إنشاء وتحديث وحذف باستخدام كيانات الجدولة. تُدار هذه الكيانات من خلال محرك جدولة في المشروع للويب. كانت عمليات الإنشاء والتحديث والحذف باستخدام كيانات الجدولة مقيدة في إصدارات 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 الوصف
msdyn_CreateProjectV1 تُستخدم API هذه لإنشاء مشروع. يتم إنشاء المشروع والحاوية الافتراضية للمشروع على الفور. يمكن إنشاء المشروع أيضا بإضافة صف إلى جدول المشروع باستخدام واجهات Dataverse Aبرمجة التطبيقات القياسية. لا تنشئ هذه العملية مستودع افتراضيا للمشروع ولكن قد يكون لها أداء أفضل.
msdyn_CreateTeamMemberV1 تُستخدم API هذه لإنشاء عضو فريق مشروع. يتم إنشاء سجل عضو الفريق على الفور. يمكن أيضًا إنشاء أعضاء الفريق عن طريق إضافة صف إلى جدول أعضاء فريق المشروع باستخدام المعيار Dataverse APIs.
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

بسبب إنشاء السجلات لكل من CreateProjectV1 وCreateTeamMemberV1 على الفور، لا يمكن استخدام واجهات برمجة التطبيقات هذه في OperationSet بشكل مباشر. ومع ذلك، يمكنك استخدامها لإنشاء السجلات المطلوبة وإنشاء OperationSet ثم استخدام السجلات التي تم إنشاؤها مسبقا في OperationSet.

العمليات المدعومة

كيان الجدولة ‏إنشاء ‏‏تحديث حذف اعتبارات هامة
مهمة المشروع نعم نعم نعم يمكن تحرير حقول Progress وEffortCompleted وEffortRemaining في Project for the Web، ولكن لا يمكن تحريرها في Project Operations.
تبعية مهمة المشروع نعم لا نعم لا يتم تحديث سجلات تبعية مهمة المشروع. بدلاً من ذلك، يمكن حذف سجل قديم، ويمكن إنشاء سجل جديد.
تعيين الموارد نعم ‏‏نعم* نعم العمليات مع الحقول التالية غير مدعومة: BookableResourceID وEffort وEffortCompleted وEffortRemaining ةPlannedWork.
مستودع المشروع ‏‏نعم ‏‏نعم ‏‏نعم يتم إنشاء الحاوية الافتراضية باستخدام واجهة برمجة تطبيقات CreateProjectV1. تمت إضافة دعم إنشاء حاويات المشروع وحذفها في تحديث الإصدار 16.
عضو فريق المشروع نعم نعم نعم بالنسبة لعملية الإنشاء، استخدم واجهة برمجة التطبيقات CreateTeamMemberV1.
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

* لا يتم تحديث سجلات تعيين الموارد. بدلاً من ذلك، يمكن حذف السجل القديم، ويمكن إنشاء سجل جديد. يتم توفير واجهة برمجة تطبيقات منفصلة لتحديث ملامح تعيين الموارد.

خاصية المعرف اختيارية. إذا تم توفير خاصية ID، فسيحاول النظام استخدامها ويطرح استثناء إذا تعذر استخدامها. إذا لم يتم توفيرها، فسيقوم النظام بإنشائها.

القيود والمشاكل المعروفة

تعرض القائمة التالية القيود والمشكلات المعروفة:

  • لا يمكن استخدام واجهات برمجة تطبيقات جدولة المشروع إلا بواسطة المستخدمين الذين لديهم ترخيص Microsoft Project. لا يمكن استخدامها بواسطة:

    • مستخدمي التطبيق
    • مستخدمي النظام
    • مستخدمي التكامل
    • المستخدمين الآخرين الذين ليس لديهم الترخيص المطلوب
  • تتضمن كل OperationSet 200عملية كحدٍ أقصى.

  • بإمكان كل مستخدم أن يحصل على عشر OperationSet مفتوحة كحدٍ أقصى.

  • يتم عد كل عملية تحديث مخطط تعيين موارد كعملية واحدة.

  • يمكن لكل قائمة بالمخططات المحدثة أن تحتوي على 100 شريحة وقت كحد أقصى.

  • في الوقت الحالي، لا تتوفر حالة فشل OperationSet وسجلات الفشل.

  • توجد 400 دورة متكررة كحد أقصى لكل مشروع.

  • القيود والحدود على المشاريع والمهام.

معالجة الخطأ

  • لمراجعة الأخطاء التي تم إنشاؤها من مجموعات العمليات، انتقل إلى الإعدادات>تكامل الجدول>مجموعات العمليات.
  • لمراجعة الأخطاء التي تم إنشاؤها من خدمة جدولة المشروع، انتقل إلى الإعدادات>تكامل الجدولة>سجلات أخطاء PSS.

تحرير مخططات تعيين الموارد

على عكس جميع واجهات API الأخرى لجدولة المشروع التي تقوم بتحديث الكيان، إن واجهة API تعيين الموارد هي المسؤولة وحدها عن التحديثات لحقل واحد، msdyn_plannedwork، في كيان واحد، msydn_resourceassignment.

وضع الجدولة المحدد هو:

  • وحدات ثابتة.
  • تقويم المشروع من 9:00 إلى 5:00 مساء (توقيت الباسيفيكي) أيام الإثنين والثلاثاء والخميس والجمعة. (لا عمل أيام الأربعاء.)
  • تقويم المورد من 9:00 إلى 1:00 مساء (توقيت الباسيفيكي) من الإثنين إلى الجمعة.

هذه المهمة لمدة أسبوع واحد ، أربع ساعات في اليوم لأن تقويم الموارد من 9:00 صباحا إلى 1:00 مساء (بتوقيت المحيط الهادئ) ، أو أربع ساعات في اليوم.

  مهمة تاريخ البدء تاريخ الانتهاء الكمية 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
عامل 9-1 T1 6/13/2022 6/17/2022 20 4 4 4 4 4

على سبيل المثال، إذا كنت تريد أن يعمل العامل ثلاث ساعات فقط كل يوم في هذا الأسبوع ويسمح له بساعة واحدة للقيام بمهام أخرى.

حمولة عينة UpdatedContours:

[{

"minutes":900.0,

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

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

}]

هذا هو التعيين بعد تشغيل واجهة API لتحديث جدولة المخططات.

  المهمة تاريخ البدء تاريخ الانتهاء الكمية 6/13/2022 6/14/2022 6/15/2022 6/16/2022 6/17/2022
عامل 9-1 T1 6/13/2022 6/17/2022 15 3 3 3 3 3

سيناريو نموذجي

في هذا السيناريو، ستقوم بإنشاء مشروع، وعضو فريق، وأربع مهام، وعمليتي تعيين للموارد. بعد ذلك، عليك تحديث مهمة واحدة وتحديث المشروع وتحديث محيط تعيين المورد وحذف مهمة واحدة وحذف تعيين مورد واحد وإنشاء تبعية مهمة.

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