Project Service Automation – Validate Hours for Time Entry

This blog post covers how you can validate time entry in PSA, Project Service Automation, using a custom real time workflow to limit the amount of time a user can enter for any day.  The functionality of PSA is is pretty comprehensive however many users find it frustrating you cannot limit the total duration a user can book for a single day.

Project Service Automation - Time Entry Validation

To accomplish this I wrote a custom workflow activity that runs in realtime after a PSA time entry records is created or updated.

Project Service Automation - Time Entry Validation Workflow

The workflow takes four parameters which are self explanatory

Project Service Automation - Time Entry Validation ParametersThe code is pretty simple and simply runs an aggegrate FetchXml query to get the total duration for the given day and resource and checks it does not exceed the maximum for a day. You can get the code and the solution from GitHub https://github.com/joegilldotcom/PsaUtils

public class ValidateTimeEntry : CodeActivity
{
[RequiredArgument]
[Input(“Resource”)]
[ReferenceTarget(“bookableresource”)]
public InArgument<EntityReference> Resource { get; set; }

[RequiredArgument]
[Input(“Entry Date”)]
public InArgument<DateTime> EntryDate { get; set; }

[RequiredArgument]
[Input(“Max Minutes per Day”)]
public InArgument<int> MaxMinutes { get; set; }

[RequiredArgument]
[Input(“Max Minutes Warning Message”)]
public InArgument<string> MaxMinutesWarning { get; set; }

protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracer = executionContext.GetExtension<ITracingService>();
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

EntityReference resource = this.Resource.Get(executionContext);
DateTime entryDate = this.EntryDate.Get(executionContext);
int maxMinutes = this.MaxMinutes.Get(executionContext);
string maxWarning = this.MaxMinutesWarning.Get(executionContext);

string fetchXml = @”<fetch version=’1.0′ distinct=’false’ mapping=’logical’ aggregate=’true’>
<entity name=’msdyn_timeentry’ >
<attribute name=’msdyn_duration’ aggregate=’sum’ alias =’durationtotal’/>
<filter type=’and’ >
<condition attribute=’msdyn_bookableresource’ operator=’eq’ value='{0}’ />
<condition attribute = ‘msdyn_date’ operator= ‘on’ value = ‘{1}’ />
</filter>
</entity>
</fetch>”;

string formatXml = string.Format(fetchXml, resource.Id.ToString(), entryDate.ToString(“yyyy-MM-dd”));
tracer.Trace(formatXml);

EntityCollection eColl = service.RetrieveMultiple(new FetchExpression(formatXml));

foreach (var c in eColl.Entities)
{
int? total = ((int?)((AliasedValue)c[“durationtotal”]).Value);
if (total != null & total > maxMinutes)
throw new InvalidPluginExecutionException(maxWarning);
}
}
}

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *