ProjectManagementService.CalculatePercentageCompletionAsync
Reference
Task CalculatePercentageCompletionAsync(Guid instanceId, Instance updatedInstance = null)
Recalculates and persists percentage completion for a Project. Optionally accepts updatedInstance (a task/child instance that was changed) so the algorithm can incorporate that change without re-reading stale values.
Purpose
Aggregate task-level percentage completion into parent tasks and the project using duration-weighted averages.
Parameters
instanceId— Guid of the project instance whose completion must be recalculated.updatedInstance— optional Instance object representing a single task whose percentage attribute has just changed. If supplied, the program uses the updated attribute value instead of the stored one for that instance.
Returns
This method does not return a value.
High-level algorithm
- Fetch all related task instances with Attrbutes 'TrackingId', 'STANDARDTaskParent', 'STANDARDPercentCompletion' and 'STANDARDDuration' for the project.
- Relation type is 'ProjectToStandardTasks' and Related Type is 'STANDARDTask'
- Build TaskData structures:
- Map trackingId -> TaskData
- Track parent -> list of child TaskData
- Keep flat list
allTaskData - If
updatedInstanceis supplied and contains percentage attribute, use that value for the matching RelatedInstance when building TaskData.
- Compute order for bottom-up aggregation:
- Initialize a queue with tasks that have zero dependent child count (leaf tasks).
- Dequeue a task, compute its updated percentage (for leaf tasks this is the task's own value; for parent tasks it's the duration-weighted average of children).
- Mark task as calculated and decrement its parent's dependent counter. When a parent counter reaches zero, enqueue parent.
- After processing all tasks, collect instances whose calculated percentage differs from stored value. Exclude
updatedInstance. - Call UpdateInstancesAsync to persist changed percentage attributes.
Aggregation formula:
- For a parent with children: UpdatedPct = (Sum over children (child.Duration * child.UpdatedOrOriginalPercentage)) / (Sum over children child.Duration)
- If total child duration is zero, parent percentage defaults to 0.
Caveats
- Only instances with a numerical difference greater than a small epsilon (1e-6) are updated to avoid noisy writes.
- If no related tasks are found, method logs info and returns without updates.
Sample Codes
var updatedTaskInstance = new Instance()
{
Id = taskId,
InstanceTypeName = 'STANDARDTask',
Attributes = new Attribute[]
{
new Attribute()
{
Name = "STANDARDPercentCompletion",
NumericValue = 1
}
}
};
await ProjectManagementService.CalculatePercentageCompletionAsync(projectId, updatedTaskInstance);
Calculate Percentage Completion in Pre Update Trigger
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System.Linq;
using System.Threading.Tasks;
using Attribute = Prorigo.Protrak.API.Contracts.Attribute;
namespace Prorigo.Customization.Programs
{
public class CalculatePercentageCompletionOfProject : IPreUpdateTriggerProgramAsync
{
public IProjectManagementService ProjectManagementService { get; set; }
public IInstanceService InstanceService { get; set; }
private readonly string TYPE_STANDARD_TASK = "STANDARDTask";
private readonly string ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF = "ProjectToStandardTask"; // Project to tasks reference atttribute
private readonly string ATTRIBUTE_PERCENTAGE_COMPLETION = "STANDARDPercentCompletion"; // Percentage completion attribute on task
public async Task<ProgramResult> RunAsync(Instance instance)
{
bool programResult = true;
var attrPercentageCompletion = GetAttribute(instance.Attributes, ATTRIBUTE_PERCENTAGE_COMPLETION);
// If percentage completion changed on task
if (attrPercentageCompletion != null && attrPercentageCompletion.NumericValue != null)
{
// Get the project instance related to the task instance via the "ProjectToStandardTask" reference attribute
var attributes = new string[] { ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF };
var taskInstance = await InstanceService.GetInstanceAsync(instance.Id, attributes);
// If task linked to project then recalculate percentage completion for the project based on all related tasks and update task
var attrProjectToTaskRef = GetAttribute(taskInstance.Attributes, ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF);
if (attrProjectToTaskRef != null && attrProjectToTaskRef.ReferenceValues != null)
{
var projectInstanceId = attrProjectToTaskRef.ReferenceValues.FirstOrDefault().Id;
var updatedTaskInstance = new Instance()
{
Id = instance.Id,
InstanceTypeName = TYPE_STANDARD_TASK,
Attributes = new Attribute[]
{
new Attribute()
{
Name = ATTRIBUTE_PERCENTAGE_COMPLETION,
NumericValue = attrPercentageCompletion.NumericValue,
}
}
};
await ProjectManagementService.CalculatePercentageCompletionAsync(projectInstanceId, updatedTaskInstance);
}
}
if (programResult)
{
return new ProgramResult() { IsSuccess = true, Errors = null };
}
else
{
return new ProgramResult() { IsSuccess = false, Errors = new string[] { "Sample error message" } };
}
}
private static Attribute GetAttribute(Attribute[] attributes, string attributeName)
{
return attributes.FirstOrDefault(attr => attr.Name == attributeName);
}
}
}
Calculate Percentage Completion in Promote Action Command Trigger
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Linq;
using System.Threading.Tasks;
using Attribute = Prorigo.Protrak.API.Contracts.Attribute;
namespace Prorigo.Customization.Programs
{
public class CalculatePercentageCompletionOfProject : IPromoteActionCommandProgramAsync
{
public IProjectManagementService ProjectManagementService { get; set; }
public IInstanceService InstanceService { get; set; }
private readonly string ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF = "ProjectToStandardTask"; // Project to tasks reference atttribute
private readonly string ATTRIBUTE_PERCENTAGE_COMPLETION = "STANDARDPercentCompletion"; // Percentage completion attribute on task
public async Task RunAsync(Guid instanceId, string actionName)
{
var attributes = new string[] { ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF, ATTRIBUTE_PERCENTAGE_COMPLETION };
var taskInstance = await InstanceService.GetInstanceAsync(instanceId, attributes);
var attrProjectToTaskRef = GetAttribute(taskInstance.Attributes, ATTRIBUTE_PROJECT_TO_STANDARD_TASK_REF);
if (attrProjectToTaskRef != null && attrProjectToTaskRef.ReferenceValues != null)
{
var projectInstanceId = attrProjectToTaskRef.ReferenceValues.FirstOrDefault().Id;
await ProjectManagementService.CalculatePercentageCompletionAsync(projectInstanceId, taskInstance);
}
}
private static Attribute GetAttribute(Attribute[] attributes, string attributeName)
{
return attributes.FirstOrDefault(attr => attr.Name == attributeName);
}
}
}