Skip to content

Commit

Permalink
Added previous names to v3 endpoint including tests (#917)
Browse files Browse the repository at this point in the history
* Added previous names to v3 endpoint including tests

* Added plugin to mimic how QTS and EYTS dates are updated on Contact in real CRM + updated tests

* more tweaks from PR comments
  • Loading branch information
hortha authored Nov 13, 2023
1 parent e2d69ab commit 4abe3a0
Show file tree
Hide file tree
Showing 22 changed files with 800 additions and 234 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TeachingRecordSystem.Api.V3.ApiModels;

public record NameInfo
{
public required string FirstName { get; init; }
public required string MiddleName { get; init; }
public required string LastName { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using MediatR;
using Microsoft.Xrm.Sdk.Query;
using Optional;
using TeachingRecordSystem.Api.V3.ApiModels;
using TeachingRecordSystem.Api.V3.Requests;
Expand All @@ -16,37 +17,43 @@ public class GetTeacherHandler : IRequestHandler<GetTeacherRequest, GetTeacherRe

private readonly IDataverseAdapter _dataverseAdapter;
private readonly ICrmQueryDispatcher _crmQueryDispatcher;
private readonly TimeSpan _concurrentNameChangeWindow;

public GetTeacherHandler(IDataverseAdapter dataverseAdapter, ICrmQueryDispatcher crmQueryDispatcher)
public GetTeacherHandler(
IDataverseAdapter dataverseAdapter,
ICrmQueryDispatcher crmQueryDispatcher,
IConfiguration configuration)
{
_dataverseAdapter = dataverseAdapter;
_crmQueryDispatcher = crmQueryDispatcher;
_concurrentNameChangeWindow = TimeSpan.FromSeconds(configuration.GetValue<int>("ConcurrentNameChangeWindowSeconds", 5));
}

public async Task<GetTeacherResponse?> Handle(GetTeacherRequest request, CancellationToken cancellationToken)
{
var teacher = await _dataverseAdapter.GetTeacherByTrn(
request.Trn,
columnNames: new[]
{
Contact.Fields.FirstName,
Contact.Fields.MiddleName,
Contact.Fields.LastName,
Contact.Fields.dfeta_StatedFirstName,
Contact.Fields.dfeta_StatedMiddleName,
Contact.Fields.dfeta_StatedLastName,
Contact.Fields.BirthDate,
Contact.Fields.dfeta_NINumber,
Contact.Fields.dfeta_QTSDate,
Contact.Fields.dfeta_EYTSDate,
Contact.Fields.EMailAddress1
});

if (teacher is null)
var contactDetail = await _crmQueryDispatcher.ExecuteQuery(
new GetContactDetailByTrnQuery(
request.Trn,
new ColumnSet(
Contact.Fields.FirstName,
Contact.Fields.MiddleName,
Contact.Fields.LastName,
Contact.Fields.dfeta_StatedFirstName,
Contact.Fields.dfeta_StatedMiddleName,
Contact.Fields.dfeta_StatedLastName,
Contact.Fields.BirthDate,
Contact.Fields.dfeta_NINumber,
Contact.Fields.dfeta_QTSDate,
Contact.Fields.dfeta_EYTSDate,
Contact.Fields.EMailAddress1)));

if (contactDetail is null)
{
return null;
}

var teacher = contactDetail.Contact;

dfeta_induction? induction = default;
dfeta_inductionperiod[]? inductionPeriods = default;

Expand Down Expand Up @@ -199,6 +206,60 @@ public GetTeacherHandler(IDataverseAdapter dataverseAdapter, ICrmQueryDispatcher
sanctions = (await _crmQueryDispatcher.ExecuteQuery(getSanctionsQuery))[teacher.Id];
}

List<NameInfo>? previousNames = null;

if (request.Include.HasFlag(GetTeacherRequestIncludes.PreviousNames))
{
previousNames = new List<NameInfo>();
var currentFirstName = contactDetail.Contact.FirstName;
var currentMiddleName = contactDetail.Contact.MiddleName;
var currentLastName = contactDetail.Contact.LastName;
DateTime? createdOnBaseline = null;

foreach (var previousName in contactDetail.PreviousNames.OrderByDescending(p => p.CreatedOn))
{
if (createdOnBaseline is null)
{
createdOnBaseline = previousName.CreatedOn;
}
else if (createdOnBaseline - previousName.CreatedOn > _concurrentNameChangeWindow)
{
previousNames.Add(new NameInfo()
{
FirstName = currentFirstName,
MiddleName = currentMiddleName ?? string.Empty,
LastName = currentLastName
});
createdOnBaseline = previousName.CreatedOn;
}

switch (previousName.dfeta_Type)
{
case dfeta_NameType.FirstName:
currentFirstName = previousName.dfeta_name;
break;
case dfeta_NameType.MiddleName:
currentMiddleName = previousName.dfeta_name;
break;
case dfeta_NameType.LastName:
currentLastName = previousName.dfeta_name;
break;
default:
break;
}
}

if (createdOnBaseline is not null)
{
previousNames.Add(new NameInfo()
{
FirstName = currentFirstName,
MiddleName = currentMiddleName ?? string.Empty,
LastName = currentLastName
});
}
}

var firstName = teacher.ResolveFirstName();
var middleName = teacher.ResolveMiddleName();
var lastName = teacher.ResolveLastName();
Expand Down Expand Up @@ -270,6 +331,9 @@ public GetTeacherHandler(IDataverseAdapter dataverseAdapter, ICrmQueryDispatcher
AlertType = AlertType.Prohibition,
DqtSanctionCode = s.SanctionCode
})) :
default,
PreviousNames = request.Include.HasFlag(GetTeacherRequestIncludes.PreviousNames) ?
Option.Some(previousNames!.Select(n => n)) :
default
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum GetTeacherRequestIncludes
HigherEducationQualifications = 1 << 5,
Sanctions = 1 << 6,
Alerts = 1 << 7,
PreviousNames = 1 << 8,

All = Induction | InitialTeacherTraining | NpqQualifications | MandatoryQualifications | PendingDetailChanges | HigherEducationQualifications | Sanctions | Alerts
All = Induction | InitialTeacherTraining | NpqQualifications | MandatoryQualifications | PendingDetailChanges | HigherEducationQualifications | Sanctions | Alerts | PreviousNames
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public record GetTeacherResponse
public required Option<IEnumerable<GetTeacherResponseHigherEducationQualification>> HigherEducationQualifications { get; init; }
public required Option<IEnumerable<SanctionInfo>> Sanctions { get; init; }
public required Option<IEnumerable<AlertInfo>> Alerts { get; init; }
public required Option<IEnumerable<NameInfo>> PreviousNames { get; init; }
}

public record GetTeacherResponseQts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"Microsoft.AspNetCore": "Fatal"
}
}
}
},
"ConcurrentNameChangeWindowSeconds": 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@
"ProcessAllEntityTypesConcurrently": true,
"IgnoreInvalidData": false,
"RunService": false
}
},
"ConcurrentNameChangeWindowSeconds": 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetAllEarlyYearsStatusesQuery : ICrmQuery<dfeta_earlyyearsstatus[]>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Microsoft.Xrm.Sdk.Query;

namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetContactDetailByTrnQuery(string Trn, ColumnSet ColumnSet) : ICrmQuery<ContactDetail?>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class GetAllEarlyYearsStatusesHandler : ICrmQueryHandler<GetAllEarlyYearsStatusesQuery, dfeta_earlyyearsstatus[]>
{
public async Task<dfeta_earlyyearsstatus[]> Execute(GetAllEarlyYearsStatusesQuery query, IOrganizationServiceAsync organizationService)
{
var queryExpression = new QueryExpression()
{
EntityName = dfeta_earlyyearsstatus.EntityLogicalName,
ColumnSet = new ColumnSet(
dfeta_earlyyearsstatus.Fields.dfeta_name,
dfeta_earlyyearsstatus.Fields.dfeta_Value)
};
queryExpression.Criteria.AddCondition(dfeta_earlyyearsstatus.Fields.StateCode, ConditionOperator.Equal, (int)dfeta_earlyyearsStatusState.Active);

var request = new RetrieveMultipleRequest()
{
Query = queryExpression
};

var response = await organizationService.RetrieveMultipleAsync(queryExpression);

return response.Entities.Select(e => e.ToEntity<dfeta_earlyyearsstatus>()).ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class GetContactDetailByTrnHandler : ICrmQueryHandler<GetContactDetailByTrnQuery, ContactDetail?>
{
public async Task<ContactDetail?> Execute(GetContactDetailByTrnQuery query, IOrganizationServiceAsync organizationService)
{
var contactFilter = new FilterExpression();
contactFilter.AddCondition(Contact.Fields.dfeta_TRN, ConditionOperator.Equal, query.Trn);
var contactQueryExpression = new QueryExpression(Contact.EntityLogicalName)
{
ColumnSet = query.ColumnSet,
Criteria = contactFilter
};

var contactRequest = new RetrieveMultipleRequest()
{
Query = contactQueryExpression
};

var previousNameFilter = new FilterExpression();
previousNameFilter.AddCondition(dfeta_previousname.Fields.StateCode, ConditionOperator.Equal, (int)dfeta_documentState.Active);
previousNameFilter.AddCondition(dfeta_previousname.Fields.dfeta_Type, ConditionOperator.NotEqual, (int)dfeta_NameType.Title);
var previousNameQueryExpression = new QueryExpression(dfeta_previousname.EntityLogicalName)
{
ColumnSet = new ColumnSet(
dfeta_previousname.PrimaryIdAttribute,
dfeta_previousname.Fields.dfeta_PersonId,
dfeta_previousname.Fields.CreatedOn,
dfeta_previousname.Fields.dfeta_ChangedOn,
dfeta_previousname.Fields.dfeta_name,
dfeta_previousname.Fields.dfeta_Type),
Criteria = previousNameFilter
};

var contactLink = previousNameQueryExpression.AddLink(
Contact.EntityLogicalName,
dfeta_previousname.Fields.dfeta_PersonId,
Contact.PrimaryIdAttribute,
JoinOperator.Inner);

contactLink.Columns = new ColumnSet(
Contact.PrimaryIdAttribute,
Contact.Fields.dfeta_TRN);

contactLink.EntityAlias = Contact.EntityLogicalName;
contactLink.LinkCriteria = contactFilter;

var previousNameRequest = new RetrieveMultipleRequest()
{
Query = previousNameQueryExpression
};

var requestBuilder = RequestBuilder.CreateMultiple(organizationService);
var contactResponse = requestBuilder.AddRequest<RetrieveMultipleResponse>(contactRequest);
var previousNameResponse = requestBuilder.AddRequest<RetrieveMultipleResponse>(previousNameRequest);

await requestBuilder.Execute();

var contact = (await contactResponse.GetResponseAsync()).EntityCollection.Entities.FirstOrDefault()?.ToEntity<Contact>();
var previousNames = (await previousNameResponse.GetResponseAsync()).EntityCollection.Entities.Select(e => e.ToEntity<dfeta_previousname>()).ToArray();

if (contact is null)
{
return null;
}

return new ContactDetail(contact, previousNames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class ReferenceDataCache
private Task<dfeta_sanctioncode[]>? _getSanctionCodesTask;
private Task<Subject[]>? _getSubjectsTask;
private Task<dfeta_teacherstatus[]>? _getTeacherStatusesTask;
private Task<dfeta_earlyyearsstatus[]>? _getEarlyYearsStatusesTask;

public ReferenceDataCache(ICrmQueryDispatcher crmQueryDispatcher)
{
Expand Down Expand Up @@ -45,6 +46,12 @@ public async Task<dfeta_teacherstatus> GetTeacherStatusByValue(string value)
return teacherStatuses.Single(ts => ts.dfeta_Value == value);
}

public async Task<dfeta_earlyyearsstatus> GetEarlyYearsStatusByValue(string value)
{
var earlyYearsStatuses = await EnsureEarlyYearsStatuses();
return earlyYearsStatuses.Single(ey => ey.dfeta_Value == value);
}

private Task<dfeta_sanctioncode[]> EnsureSanctionCodes() =>
LazyInitializer.EnsureInitialized(
ref _getSanctionCodesTask,
Expand All @@ -59,4 +66,9 @@ private Task<dfeta_teacherstatus[]> EnsureTeacherStatuses() =>
LazyInitializer.EnsureInitialized(
ref _getTeacherStatusesTask,
() => _crmQueryDispatcher.ExecuteQuery(new GetAllTeacherStatusesQuery()));

private Task<dfeta_earlyyearsstatus[]> EnsureEarlyYearsStatuses() =>
LazyInitializer.EnsureInitialized(
ref _getEarlyYearsStatusesTask,
() => _crmQueryDispatcher.ExecuteQuery(new GetAllEarlyYearsStatusesQuery()));
}
Loading

0 comments on commit 4abe3a0

Please sign in to comment.