Skip to content

Commit

Permalink
WIP: bulk person API
Browse files Browse the repository at this point in the history
  • Loading branch information
gunndabad committed Aug 19, 2024
1 parent ced2453 commit a8e671d
Show file tree
Hide file tree
Showing 29 changed files with 559 additions and 160 deletions.
4 changes: 2 additions & 2 deletions TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.Validation;
using TeachingRecordSystem.Core.Dqt;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Infrastructure;
using TeachingRecordSystem.Core.Services.Certificates;
using TeachingRecordSystem.Core.Services.GetAnIdentityApi;
Expand Down Expand Up @@ -215,7 +216,6 @@ public static void Main(string[] args)

services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>());
services.AddSingleton<ICurrentClientProvider, ClaimsPrincipalCurrentClientProvider>();
services.AddSingleton<IClock, Clock>();
services.AddMemoryCache();
services.AddSingleton<AddTrnToSentryScopeResourceFilter>();
services.AddTransient<TrnRequestHelper>();
Expand All @@ -241,7 +241,7 @@ public static void Main(string[] args)

services.AddAccessYourTeachingQualificationsOptions(configuration, env);
services.AddCertificateGeneration();
services.AddCrmQueries();
services.AddTrsBaseServices();

if (!env.IsUnitTests())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ public record FindPersonByLastNameAndDateOfBirthResultItem
public required string LastName { get; init; }
public required IReadOnlyCollection<SanctionInfo> Sanctions { get; init; }
public required IReadOnlyCollection<NameInfo> PreviousNames { get; init; }
public required InductionStatusInfo? InductionStatus { get; init; }
public required QtsInfo? Qts { get; init; }
public required EytsInfo? Eyts { get; init; }
}

public class FindPersonByLastNameAndDateOfBirthHandler(ICrmQueryDispatcher crmQueryDispatcher, IConfiguration configuration)
public class FindPersonByLastNameAndDateOfBirthHandler(
ICrmQueryDispatcher crmQueryDispatcher,
PreviousNameHelper previousNameHelper,
ReferenceDataCache referenceDataCache)
{
private readonly TimeSpan _concurrentNameChangeWindow = TimeSpan.FromSeconds(configuration.GetValue("ConcurrentNameChangeWindowSeconds", 5));

public async Task<FindPersonByLastNameAndDateOfBirthResult> Handle(FindPersonByLastNameAndDateOfBirthCommand command)
{
var contacts = await crmQueryDispatcher.ExecuteQuery(
var matched = await crmQueryDispatcher.ExecuteQuery(
new GetActiveContactsByLastNameAndDateOfBirthQuery(
command.LastName!,
command.DateOfBirth!.Value,
Expand All @@ -40,9 +44,10 @@ public async Task<FindPersonByLastNameAndDateOfBirthResult> Handle(FindPersonByL
Contact.Fields.LastName,
Contact.Fields.dfeta_StatedFirstName,
Contact.Fields.dfeta_StatedMiddleName,
Contact.Fields.dfeta_StatedLastName)));
Contact.Fields.dfeta_StatedLastName,
Contact.Fields.dfeta_InductionStatus)));

var contactsById = contacts.ToDictionary(r => r.Id, r => r);
var contactsById = matched.ToDictionary(r => r.Id, r => r);

var sanctions = await crmQueryDispatcher.ExecuteQuery(
new GetSanctionsByContactIdsQuery(
Expand All @@ -53,35 +58,57 @@ public async Task<FindPersonByLastNameAndDateOfBirthResult> Handle(FindPersonByL
var previousNames = (await crmQueryDispatcher.ExecuteQuery(new GetPreviousNamesByContactIdsQuery(contactsById.Keys)))
.ToDictionary(
kvp => kvp.Key,
kvp => PreviousNameHelper.GetFullPreviousNames(kvp.Value, contactsById[kvp.Key], _concurrentNameChangeWindow));
kvp => previousNameHelper.GetFullPreviousNames(kvp.Value, contactsById[kvp.Key]));

var qtsRegistrations = await crmQueryDispatcher.ExecuteQuery(
new GetActiveQtsRegistrationsByContactIdsQuery(
contactsById.Keys,
new ColumnSet(
dfeta_qtsregistration.Fields.CreatedOn,
dfeta_qtsregistration.Fields.dfeta_EarlyYearsStatusId,
dfeta_qtsregistration.Fields.dfeta_EYTSDate,
dfeta_qtsregistration.Fields.dfeta_QTSDate,
dfeta_qtsregistration.Fields.dfeta_PersonId,
dfeta_qtsregistration.Fields.dfeta_TeacherStatusId)));

return new FindPersonByLastNameAndDateOfBirthResult(
Total: contacts.Length,
Items: contacts.Select(r => new FindPersonByLastNameAndDateOfBirthResultItem()
{
Trn = r.dfeta_TRN,
DateOfBirth = r.BirthDate!.Value.ToDateOnlyWithDqtBstFix(isLocalTime: false),
FirstName = r.ResolveFirstName(),
MiddleName = r.ResolveMiddleName(),
LastName = r.ResolveLastName(),
Sanctions = sanctions[r.Id]
.Where(s => Constants.ExposableSanctionCodes.Contains(s.SanctionCode))
.Select(s => new SanctionInfo()
{
Code = s.SanctionCode,
StartDate = s.Sanction.dfeta_StartDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true)
})
.AsReadOnly(),
PreviousNames = previousNames[r.Id]
.Select(name => new NameInfo()
{
FirstName = name.FirstName,
MiddleName = name.MiddleName,
LastName = name.LastName
})
.AsReadOnly()
})
.OrderBy(c => c.Trn)
.AsReadOnly());
Total: matched.Length,
Items: await matched
.ToAsyncEnumerable()
.SelectAwait(async r => new FindPersonByLastNameAndDateOfBirthResultItem()
{
Trn = r.dfeta_TRN,
DateOfBirth = r.BirthDate!.Value.ToDateOnlyWithDqtBstFix(isLocalTime: false),
FirstName = r.ResolveFirstName(),
MiddleName = r.ResolveMiddleName(),
LastName = r.ResolveLastName(),
Sanctions = sanctions[r.Id]
.Where(s => Constants.ExposableSanctionCodes.Contains(s.SanctionCode))
.Select(s => new SanctionInfo()
{
Code = s.SanctionCode,
StartDate = s.Sanction.dfeta_StartDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true)
})
.AsReadOnly(),
PreviousNames = previousNames[r.Id]
.Select(name => new NameInfo()
{
FirstName = name.FirstName,
MiddleName = name.MiddleName,
LastName = name.LastName
})
.AsReadOnly(),
InductionStatus = r.dfeta_InductionStatus?.ConvertToInductionStatus() is InductionStatus inductionStatus ?
new InductionStatusInfo()
{
Status = inductionStatus,
StatusDescription = inductionStatus.GetDescription()
} :
null,
Qts = await QtsInfo.Create(qtsRegistrations[r.Id].OrderBy(qr => qr.CreatedOn).FirstOrDefault(s => s.dfeta_QTSDate is not null), referenceDataCache),
Eyts = await EytsInfo.Create(qtsRegistrations[r.Id].OrderBy(qr => qr.CreatedOn).FirstOrDefault(s => s.dfeta_EYTSDate is not null), referenceDataCache),
})
.OrderBy(c => c.Trn)
.ToListAsync());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Collections.Immutable;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Api.V3.Core.SharedModels;
using TeachingRecordSystem.Core.Dqt;
using TeachingRecordSystem.Core.Dqt.Models;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Api.V3.Core.Operations;

public record FindPersonsByTrnAndDateOfBirthCommand(IEnumerable<(string Trn, DateOnly DateOfBirth)> Persons);

public record FindPersonsByTrnAndDateOfBirthResult(int Total, IReadOnlyCollection<FindPersonsByTrnAndDateOfBirthResultItem> Items);

public record FindPersonsByTrnAndDateOfBirthResultItem
{
public required string Trn { get; init; }
public required DateOnly DateOfBirth { get; init; }
public required string FirstName { get; init; }
public required string MiddleName { get; init; }
public required string LastName { get; init; }
public required IReadOnlyCollection<SanctionInfo> Sanctions { get; init; }
public required IReadOnlyCollection<NameInfo> PreviousNames { get; init; }
public required InductionStatusInfo? InductionStatus { get; init; }
public required QtsInfo? Qts { get; init; }
public required EytsInfo? Eyts { get; init; }
}

public class FindPersonsByTrnAndDateOfBirthHandler(
ICrmQueryDispatcher crmQueryDispatcher,
PreviousNameHelper previousNameHelper,
ReferenceDataCache referenceDataCache)
{
public async Task<FindPersonsByTrnAndDateOfBirthResult> Handle(FindPersonsByTrnAndDateOfBirthCommand command)
{
var contacts = await crmQueryDispatcher.ExecuteQuery(
new GetActiveContactsByTrnsQuery(
command.Persons.Select(p => p.Trn),
new ColumnSet(
Contact.Fields.dfeta_TRN,
Contact.Fields.BirthDate,
Contact.Fields.FirstName,
Contact.Fields.MiddleName,
Contact.Fields.LastName,
Contact.Fields.dfeta_StatedFirstName,
Contact.Fields.dfeta_StatedMiddleName,
Contact.Fields.dfeta_StatedLastName,
Contact.Fields.dfeta_InductionStatus)));

// Remove any results where the request DOB doesn't match the contact's DOB
// (we can't easily do this in the query itself).
var matched = contacts
.Where(kvp => kvp.Value is not null)
.Where(kvp => command.Persons.Any(p => p.Trn == kvp.Key && p.DateOfBirth == kvp.Value!.BirthDate?.ToDateOnlyWithDqtBstFix(isLocalTime: false)))
.Select(kvp => kvp.Value!)
.ToArray();

var contactsById = matched.ToDictionary(c => c.Id, c => c);

var sanctions = await crmQueryDispatcher.ExecuteQuery(
new GetSanctionsByContactIdsQuery(
contactsById.Keys,
ActiveOnly: true,
new()));

var previousNames = (await crmQueryDispatcher.ExecuteQuery(new GetPreviousNamesByContactIdsQuery(contactsById.Keys)))
.ToDictionary(
kvp => kvp.Key,
kvp => previousNameHelper.GetFullPreviousNames(kvp.Value, contactsById[kvp.Key]));

var qtsRegistrations = await crmQueryDispatcher.ExecuteQuery(
new GetActiveQtsRegistrationsByContactIdsQuery(
contactsById.Keys,
new ColumnSet(
dfeta_qtsregistration.Fields.CreatedOn,
dfeta_qtsregistration.Fields.dfeta_EarlyYearsStatusId,
dfeta_qtsregistration.Fields.dfeta_EYTSDate,
dfeta_qtsregistration.Fields.dfeta_QTSDate,
dfeta_qtsregistration.Fields.dfeta_PersonId,
dfeta_qtsregistration.Fields.dfeta_TeacherStatusId)));

return new FindPersonsByTrnAndDateOfBirthResult(
Total: matched.Length,
Items: await matched
.ToAsyncEnumerable()
.SelectAwait(async r => new FindPersonsByTrnAndDateOfBirthResultItem()
{
Trn = r.dfeta_TRN,
DateOfBirth = r.BirthDate!.Value.ToDateOnlyWithDqtBstFix(isLocalTime: false),
FirstName = r.ResolveFirstName(),
MiddleName = r.ResolveMiddleName(),
LastName = r.ResolveLastName(),
Sanctions = sanctions[r.Id]
.Where(s => Constants.ExposableSanctionCodes.Contains(s.SanctionCode))
.Select(s => new SanctionInfo()
{
Code = s.SanctionCode,
StartDate = s.Sanction.dfeta_StartDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true)
})
.AsReadOnly(),
PreviousNames = previousNames[r.Id]
.Select(name => new NameInfo()
{
FirstName = name.FirstName,
MiddleName = name.MiddleName,
LastName = name.LastName
})
.AsReadOnly(),
InductionStatus = r.dfeta_InductionStatus?.ConvertToInductionStatus() is InductionStatus inductionStatus ?
new InductionStatusInfo()
{
Status = inductionStatus,
StatusDescription = inductionStatus.GetDescription()
} :
null,
Qts = await QtsInfo.Create(qtsRegistrations[r.Id].OrderBy(qr => qr.CreatedOn).FirstOrDefault(s => s.dfeta_QTSDate is not null), referenceDataCache),
Eyts = await EytsInfo.Create(qtsRegistrations[r.Id].OrderBy(qr => qr.CreatedOn).FirstOrDefault(s => s.dfeta_EYTSDate is not null), referenceDataCache),
})
.OrderBy(c => c.Trn)
.ToListAsync());
}
}
Loading

0 comments on commit a8e671d

Please sign in to comment.