diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs index 80bd0a4e3e..d0c15ed3b9 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/Program.cs @@ -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; @@ -219,6 +220,7 @@ public static void Main(string[] args) services.AddMemoryCache(); services.AddSingleton(); services.AddTransient(); + services.AddSingleton(); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("EvidenceFiles")) diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonByLastNameAndDateOfBirth.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonByLastNameAndDateOfBirth.cs index b3dcf29886..f80a946e16 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonByLastNameAndDateOfBirth.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonByLastNameAndDateOfBirth.cs @@ -20,15 +20,18 @@ public record FindPersonByLastNameAndDateOfBirthResultItem public required string LastName { get; init; } public required IReadOnlyCollection Sanctions { get; init; } public required IReadOnlyCollection PreviousNames { get; init; } + public required InductionStatusInfo? InductionStatus { get; init; } + public required QtsInfo? Qts { 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 Handle(FindPersonByLastNameAndDateOfBirthCommand command) { - var contacts = await crmQueryDispatcher.ExecuteQuery( + var matched = await crmQueryDispatcher.ExecuteQuery( new GetActiveContactsByLastNameAndDateOfBirthQuery( command.LastName!, command.DateOfBirth!.Value, @@ -40,9 +43,10 @@ public async Task 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( @@ -53,35 +57,56 @@ public async Task 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(), referenceDataCache) + }) + .OrderBy(c => c.Trn) + .ToListAsync()); } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonsByTrnAndDateOfBirth.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonsByTrnAndDateOfBirth.cs new file mode 100644 index 0000000000..03f9cf77c4 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/FindPersonsByTrnAndDateOfBirth.cs @@ -0,0 +1,119 @@ +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 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 Sanctions { get; init; } + public required IReadOnlyCollection PreviousNames { get; init; } + public required InductionStatusInfo? InductionStatus { get; init; } + public required QtsInfo? Qts { get; init; } +} + +public class FindPersonsByTrnAndDateOfBirthHandler( + ICrmQueryDispatcher crmQueryDispatcher, + PreviousNameHelper previousNameHelper, + ReferenceDataCache referenceDataCache) +{ + public async Task 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(), referenceDataCache) + }) + .OrderBy(c => c.Trn) + .ToListAsync()); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/GetPerson.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/GetPerson.cs index 87c186cbb9..6103a4136a 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/GetPerson.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/Operations/GetPerson.cs @@ -39,7 +39,7 @@ public record GetPersonResult public required Option PendingNameChange { get; init; } public required Option PendingDateOfBirthChange { get; init; } public required string? EmailAddress { get; set; } - public required GetPersonResultQts? Qts { get; init; } + public required QtsInfo? Qts { get; init; } public required GetPersonResultEyts? Eyts { get; init; } public required Option Induction { get; init; } public required Option> InitialTeacherTraining { get; init; } @@ -52,13 +52,6 @@ public record GetPersonResult public required Option AllowIdSignInWithProhibitions { get; init; } } -public record GetPersonResultQts -{ - public required DateOnly? Awarded { get; init; } - public required string CertificateUrl { get; init; } - public required string? StatusDescription { get; init; } -} - public record GetPersonResultEyts { public required DateOnly? Awarded { get; init; } @@ -161,12 +154,10 @@ public class GetPersonHandler( ICrmQueryDispatcher crmQueryDispatcher, ReferenceDataCache referenceDataCache, IDataverseAdapter dataverseAdapter, - IConfiguration configuration) + PreviousNameHelper previousNameHelper) { public async Task Handle(GetPersonCommand command) { - var concurrentNameChangeWindow = TimeSpan.FromSeconds(configuration.GetValue("ConcurrentNameChangeWindowSeconds", 5)); - var contactDetail = await crmQueryDispatcher.ExecuteQuery( new GetActiveContactDetailByTrnQuery( command.Trn, @@ -330,7 +321,7 @@ async Task GetSanctions() GetSanctions() : null; - IEnumerable? previousNames = PreviousNameHelper.GetFullPreviousNames(contactDetail.PreviousNames, contactDetail.Contact, concurrentNameChangeWindow) + IEnumerable? previousNames = previousNameHelper.GetFullPreviousNames(contactDetail.PreviousNames, contactDetail.Contact) .Select(name => new NameInfo() { FirstName = name.FirstName, @@ -356,9 +347,10 @@ async Task GetSanctions() var qts = qtsRegistrations.OrderByDescending(x => x.CreatedOn).FirstOrDefault(qts => qts.dfeta_QTSDate is not null); var eyts = qtsRegistrations.OrderByDescending(x => x.CreatedOn).FirstOrDefault(qts => qts.dfeta_EYTSDate is not null); - var eytsTeacherStatus = eyts != null ? await dataverseAdapter.GetEarlyYearsStatus(eyts!.dfeta_EarlyYearsStatusId.Id) : null; var allTeacherStatuses = await referenceDataCache.GetTeacherStatuses(); - var qtsStatus = qts != null ? allTeacherStatuses.Single(x => x.Id == qts.dfeta_TeacherStatusId.Id) : null; + var allEarlyYearsStatuses = await referenceDataCache.GetEytsStatuses(); + var eytsStatus = eyts is not null ? allEarlyYearsStatuses.Single(x => x.Id == eyts.dfeta_EarlyYearsStatusId.Id) : null; + var qtsStatus = qts is not null ? allTeacherStatuses.Single(x => x.Id == qts.dfeta_TeacherStatusId.Id) : null; var allowIdSignInWithProhibitions = command.Include.HasFlag(GetPersonCommandIncludes.AllowIdSignInWithProhibitions) ? Option.Some(contact.dfeta_AllowIDSignInWithProhibitions == true) : @@ -374,8 +366,8 @@ async Task GetSanctions() NationalInsuranceNumber = contact.dfeta_NINumber, PendingNameChange = command.Include.HasFlag(GetPersonCommandIncludes.PendingDetailChanges) ? Option.Some((await getPendingDetailChangesTask!).PendingNameRequest) : default, PendingDateOfBirthChange = command.Include.HasFlag(GetPersonCommandIncludes.PendingDetailChanges) ? Option.Some((await getPendingDetailChangesTask!).PendingDateOfBirthRequest) : default, - Qts = MapQts(qts?.dfeta_QTSDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true), qtsStatus != null ? GetQtsStatusDescription(qtsStatus!.dfeta_Value!, qtsStatus.dfeta_name) : null), - Eyts = MapEyts(eyts?.dfeta_EYTSDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true), eytsTeacherStatus != null ? GetEytsStatusDescription(eytsTeacherStatus!.dfeta_Value!) : null), + Qts = await QtsInfo.Create(qts, referenceDataCache), + Eyts = MapEyts(eyts?.dfeta_EYTSDate?.ToDateOnlyWithDqtBstFix(isLocalTime: true), eytsStatus != null ? GetEytsStatusDescription(eytsStatus!.dfeta_Value!) : null), EmailAddress = contact.EMailAddress1, Induction = command.Include.HasFlag(GetPersonCommandIncludes.Induction) ? Option.Some(MapInduction(await getInductionTask!, contact)) : @@ -443,39 +435,6 @@ async Task GetSanctions() _ => throw new ArgumentException($"Unregonized EYTS status: '{value}'.", nameof(value)) }; - private static string GetQtsStatusDescription(string value, string statusDescription) => value switch - { - "28" => "Qualified", - "50" => "Qualified", - "67" => "Qualified", - "68" => "Qualified", - "69" => "Qualified", - "71" => "Qualified", - "87" => "Qualified", - "90" => "Qualified", - "100" => "Qualified", - "103" => "Qualified", - "104" => "Qualified", - "206" => "Qualified", - "211" => "Trainee teacher", - "212" => "Assessment only route candidate", - "213" => "Qualified", - "214" => "Partial qualified teacher status", - "223" => "Qualified", - _ when statusDescription.StartsWith("Qualified teacher", StringComparison.InvariantCultureIgnoreCase) => "Qualified", - _ => throw new ArgumentException($"Unregonized QTS status: '{value}'.", nameof(value)) - }; - - private static GetPersonResultQts? MapQts(DateOnly? qtsDate, string? statusDescription) => - statusDescription is not null ? - new GetPersonResultQts() - { - Awarded = qtsDate, - CertificateUrl = "/v3/certificates/qts", - StatusDescription = statusDescription - } : - null; - private static GetPersonResultEyts? MapEyts(DateOnly? eytsDate, string? statusDescription) => statusDescription != null ? new GetPersonResultEyts() @@ -486,7 +445,6 @@ statusDescription is not null ? } : null; - private static GetPersonResultInduction? MapInduction((dfeta_induction Induction, dfeta_inductionperiod[] Inductionperiods) data, TeachingRecordSystem.Core.Dqt.Models.Contact contact) { var inductionStatus = contact.dfeta_InductionStatus?.ConvertToInductionStatus(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatus.cs index 432c22c3ef..4bd7717087 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatus.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatus.cs @@ -6,31 +6,44 @@ public enum InductionStatus { Exempt = 1, Fail = 2, - FailedinWales = 3, + FailedInWales = 3, InductionExtended = 4, InProgress = 5, NotYetCompleted = 6, Pass = 7, - PassedinWales = 8, - RequiredtoComplete = 9, + PassedInWales = 8, + RequiredToComplete = 9, } public static class InductionStatusExtensions { - public static InductionStatus ConvertToInductionStatus(this dfeta_InductionStatus input) => - input.ConvertToEnumByName(); + public static InductionStatus ConvertToInductionStatus(this dfeta_InductionStatus input) => input switch + { + dfeta_InductionStatus.Exempt => InductionStatus.Exempt, + dfeta_InductionStatus.Fail => InductionStatus.Fail, + dfeta_InductionStatus.FailedinWales => InductionStatus.FailedInWales, + dfeta_InductionStatus.InductionExtended => InductionStatus.InductionExtended, + dfeta_InductionStatus.InProgress => InductionStatus.InProgress, + dfeta_InductionStatus.NotYetCompleted => InductionStatus.NotYetCompleted, + dfeta_InductionStatus.Pass => InductionStatus.Pass, + dfeta_InductionStatus.PassedinWales => InductionStatus.PassedInWales, + dfeta_InductionStatus.RequiredtoComplete => InductionStatus.RequiredToComplete, + _ => throw new ArgumentException($"Unknown {nameof(InductionStatus)}: '{input}'.") + }; + + public static string GetDescription(this dfeta_InductionStatus input) => ConvertToInductionStatus(input).GetDescription(); - public static string GetDescription(this dfeta_InductionStatus input) => input switch + public static string GetDescription(this InductionStatus input) => input switch { - dfeta_InductionStatus.Exempt => "Exempt", - dfeta_InductionStatus.Fail => "Fail", - dfeta_InductionStatus.FailedinWales => "Failed in Wales", - dfeta_InductionStatus.InductionExtended => "Extended", - dfeta_InductionStatus.InProgress => "In progress", - dfeta_InductionStatus.NotYetCompleted => "Not yet completed", - dfeta_InductionStatus.Pass => "Pass", - dfeta_InductionStatus.PassedinWales => "Passed in Wales", - dfeta_InductionStatus.RequiredtoComplete => "Required to complete", + InductionStatus.Exempt => "Exempt", + InductionStatus.Fail => "Fail", + InductionStatus.FailedInWales => "Failed in Wales", + InductionStatus.InductionExtended => "Extended", + InductionStatus.InProgress => "In progress", + InductionStatus.NotYetCompleted => "Not yet completed", + InductionStatus.Pass => "Pass", + InductionStatus.PassedInWales => "Passed in Wales", + InductionStatus.RequiredToComplete => "Required to complete", _ => throw new ArgumentException($"Unknown {nameof(InductionStatus)}: '{input}'.") }; } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatusInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatusInfo.cs new file mode 100644 index 0000000000..8b8b3ebb1a --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/InductionStatusInfo.cs @@ -0,0 +1,7 @@ +namespace TeachingRecordSystem.Api.V3.Core.SharedModels; + +public record InductionStatusInfo +{ + public required InductionStatus Status { get; init; } + public required string StatusDescription { get; init; } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/QtsInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/QtsInfo.cs new file mode 100644 index 0000000000..2b5d6ac91c --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Core/SharedModels/QtsInfo.cs @@ -0,0 +1,58 @@ +using TeachingRecordSystem.Core.Dqt; +using TeachingRecordSystem.Core.Dqt.Models; + +namespace TeachingRecordSystem.Api.V3.Core.SharedModels; + +public record QtsInfo +{ + public required DateOnly Awarded { get; init; } + public required string CertificateUrl { get; init; } + public required string StatusDescription { get; init; } + + public static async Task Create(dfeta_qtsregistration? qtsRegistration, ReferenceDataCache referenceDataCache) + { + if (qtsRegistration is null) + { + return null; + } + + var awardedDate = qtsRegistration.dfeta_QTSDate.ToDateOnlyWithDqtBstFix(isLocalTime: true); + if (awardedDate is null) + { + return null; + } + + var teacherStatus = await referenceDataCache.GetTeacherStatusById(qtsRegistration.dfeta_TeacherStatusId.Id); + var statusDescription = GetStatusDescription(teacherStatus); + + return new() + { + Awarded = awardedDate!.Value, + CertificateUrl = "/v3/certificates/qts", + StatusDescription = statusDescription, + }; + } + + private static string GetStatusDescription(dfeta_teacherstatus teacherStatus) => teacherStatus.dfeta_Value switch + { + "28" => "Qualified", + "50" => "Qualified", + "67" => "Qualified", + "68" => "Qualified", + "69" => "Qualified", + "71" => "Qualified", + "87" => "Qualified", + "90" => "Qualified", + "100" => "Qualified", + "103" => "Qualified", + "104" => "Qualified", + "206" => "Qualified", + "211" => "Trainee teacher", + "212" => "Assessment only route candidate", + "213" => "Qualified", + "214" => "Partial qualified teacher status", + "223" => "Qualified", + _ when teacherStatus.dfeta_name.StartsWith("Qualified teacher", StringComparison.OrdinalIgnoreCase) => "Qualified", + _ => throw new ArgumentException($"Unregonized QTS status: '{teacherStatus.dfeta_Value}'.", nameof(teacherStatus)) + }; +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240101/Responses/GetTeacherResponse.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240101/Responses/GetTeacherResponse.cs index 6c9adab8ae..36dee8e58c 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240101/Responses/GetTeacherResponse.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240101/Responses/GetTeacherResponse.cs @@ -32,7 +32,7 @@ public record GetTeacherResponse public required Option AllowIdSignInWithProhibitions { get; init; } } -[AutoMap(typeof(GetPersonResultQts))] +[AutoMap(typeof(Core.SharedModels.QtsInfo))] public record GetTeacherResponseQts { public required DateOnly? Awarded { get; init; } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240606/Responses/GetPersonResponse.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240606/Responses/GetPersonResponse.cs index a5ad56c23d..c77b8ac241 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240606/Responses/GetPersonResponse.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240606/Responses/GetPersonResponse.cs @@ -29,7 +29,7 @@ public partial record GetPersonResponse public required Option AllowIdSignInWithProhibitions { get; init; } } -[AutoMap(typeof(GetPersonResultQts))] +[AutoMap(typeof(Core.SharedModels.QtsInfo))] [GenerateVersionedDto(typeof(V20240101.Responses.GetTeacherResponseQts))] public partial record GetPersonResponseQts; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/InductionStatusInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/InductionStatusInfo.cs new file mode 100644 index 0000000000..fd977d1f58 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/InductionStatusInfo.cs @@ -0,0 +1,10 @@ +using TeachingRecordSystem.Api.V3.V20240101.ApiModels; + +namespace TeachingRecordSystem.Api.V3.V20240814.ApiModels; + +[AutoMap(typeof(Core.SharedModels.InductionStatusInfo))] +public record InductionStatusInfo +{ + public required InductionStatus Status { get; init; } + public required string StatusDescription { get; init; } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/QtsInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/QtsInfo.cs new file mode 100644 index 0000000000..8c777ab060 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/ApiModels/QtsInfo.cs @@ -0,0 +1,9 @@ +namespace TeachingRecordSystem.Api.V3.V20240814.ApiModels; + +[AutoMap(typeof(Core.SharedModels.QtsInfo))] +public record QtsInfo +{ + public required DateOnly Awarded { get; init; } + public required string CertificateUrl { get; init; } + public required string StatusDescription { get; init; } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Controllers/PersonsController.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Controllers/PersonsController.cs new file mode 100644 index 0000000000..5e91ca9930 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Controllers/PersonsController.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using TeachingRecordSystem.Api.Infrastructure.Security; +using TeachingRecordSystem.Api.V3.Core.Operations; +using TeachingRecordSystem.Api.V3.V20240814.Requests; +using TeachingRecordSystem.Api.V3.V20240814.Responses; + +namespace TeachingRecordSystem.Api.V3.V20240814.Controllers; + +[Route("persons")] +public class PersonsController(IMapper mapper) : ControllerBase +{ + [HttpPost("find")] + [SwaggerOperation( + OperationId = "FindPersons", + Summary = "Find persons", + Description = "Finds persons matching the specified criteria.")] + [ProducesResponseType(typeof(FindPersonsResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [Authorize(Policy = AuthorizationPolicies.GetPerson)] + public async Task FindTeachers( + [FromBody] FindPersonsRequest request, + [FromServices] FindPersonsByTrnAndDateOfBirthHandler handler) + { + var command = new FindPersonsByTrnAndDateOfBirthCommand(request.Persons.Select(p => (p.Trn, p.DateOfBirth))); + var result = await handler.Handle(command); + var response = mapper.Map(result); + return Ok(response); + } + + [HttpGet("")] + [SwaggerOperation( + OperationId = "FindPerson", + Summary = "Find person", + Description = "Finds a person matching the specified criteria.")] + [ProducesResponseType(typeof(FindPersonResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [Authorize(Policy = AuthorizationPolicies.GetPerson)] + public async Task FindTeachers( + FindPersonRequest request, + [FromServices] FindPersonByLastNameAndDateOfBirthHandler handler) + { + var command = new FindPersonByLastNameAndDateOfBirthCommand(request.LastName!, request.DateOfBirth!.Value); + var result = await handler.Handle(command); + + var response = new FindPersonResponse() + { + Total = result.Total, + Query = request, + Results = result.Items.Select(mapper.Map).AsReadOnly() + }; + + return Ok(response); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonRequest.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonRequest.cs new file mode 100644 index 0000000000..52a2a5f921 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonRequest.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TeachingRecordSystem.Api.V3.V20240814.Requests; + +public record FindPersonRequest +{ + [FromQuery(Name = "findBy")] + public FindPersonFindBy FindBy { get; init; } + [FromQuery(Name = "lastName")] + public string? LastName { get; init; } + [FromQuery(Name = "dateOfBirth")] + public DateOnly? DateOfBirth { get; init; } +} + +public enum FindPersonFindBy +{ + LastNameAndDateOfBirth = 1 +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/FindPersonsRequest.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonsRequest.cs similarity index 82% rename from TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/FindPersonsRequest.cs rename to TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonsRequest.cs index cbc1940d8d..cf723103dc 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/FindPersonsRequest.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Requests/FindPersonsRequest.cs @@ -1,4 +1,4 @@ -namespace TeachingRecordSystem.Api.V3.VNext.Requests; +namespace TeachingRecordSystem.Api.V3.V20240814.Requests; public record FindPersonsRequest { diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonResponse.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonResponse.cs new file mode 100644 index 0000000000..0b742e7aa2 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonResponse.cs @@ -0,0 +1,20 @@ +using TeachingRecordSystem.Api.V3.Core.Operations; +using TeachingRecordSystem.Api.V3.V20240814.ApiModels; +using TeachingRecordSystem.Api.V3.V20240814.Requests; + +namespace TeachingRecordSystem.Api.V3.V20240814.Responses; + +[GenerateVersionedDto(typeof(V20240101.Responses.FindTeachersResponse), excludeMembers: ["Query", "Results"])] +public partial record FindPersonResponse +{ + public required FindPersonRequest Query { get; init; } + public required IReadOnlyCollection Results { get; init; } +} + +[AutoMap(typeof(FindPersonByLastNameAndDateOfBirthResultItem))] +[GenerateVersionedDto(typeof(V20240101.Responses.FindTeachersResponseResult))] +public partial record FindPersonResponseResult +{ + public required InductionStatusInfo InductionStatus { get; init; } + public required QtsInfo? Qts { get; init; } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Responses/FindPersonsResponse.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonsResponse.cs similarity index 57% rename from TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Responses/FindPersonsResponse.cs rename to TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonsResponse.cs index 397256252a..8c35736649 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Responses/FindPersonsResponse.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/V20240814/Responses/FindPersonsResponse.cs @@ -1,13 +1,19 @@ +using AutoMapper.Configuration.Annotations; +using TeachingRecordSystem.Api.V3.Core.Operations; using TeachingRecordSystem.Api.V3.V20240101.ApiModels; +using TeachingRecordSystem.Api.V3.V20240814.ApiModels; -namespace TeachingRecordSystem.Api.V3.VNext.Responses; +namespace TeachingRecordSystem.Api.V3.V20240814.Responses; +[AutoMap(typeof(FindPersonsByTrnAndDateOfBirthResult))] public record FindPersonsResponse { public required int Total { get; init; } + [SourceMember(nameof(FindPersonsByTrnAndDateOfBirthResult.Items))] public required IReadOnlyCollection Results { get; init; } } +[AutoMap(typeof(FindPersonsByTrnAndDateOfBirthResultItem))] public record FindPersonsResponseResult { public required string Trn { get; init; } @@ -17,4 +23,6 @@ public record FindPersonsResponseResult public required string LastName { get; init; } public required IReadOnlyCollection Sanctions { get; init; } public required IReadOnlyCollection PreviousNames { get; init; } + public required InductionStatusInfo InductionStatus { get; init; } + public required QtsInfo? Qts { get; init; } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs index 772ac9b409..144e53d0f3 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs @@ -5,7 +5,6 @@ using TeachingRecordSystem.Api.V3.Core.Operations; using TeachingRecordSystem.Api.V3.VNext.ApiModels; using TeachingRecordSystem.Api.V3.VNext.Requests; -using TeachingRecordSystem.Api.V3.VNext.Responses; namespace TeachingRecordSystem.Api.V3.VNext.Controllers; @@ -47,17 +46,4 @@ public async Task GetQtls( var response = mapper.Map(result); return response is not null ? Ok(response) : NotFound(); } - - [HttpPost("find")] - [SwaggerOperation( - OperationId = "FindPersons", - Summary = "Find persons", - Description = "Finds persons matching the specified criteria.")] - [ProducesResponseType(typeof(FindPersonsResponse), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] - [Authorize(Policy = AuthorizationPolicies.GetPerson)] - public Task FindTeachers([FromBody] FindPersonsRequest request) - { - throw new NotImplementedException(); - } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/VersionRegistry.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/VersionRegistry.cs index 6d33d79592..1b109d544f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/VersionRegistry.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/VersionRegistry.cs @@ -14,6 +14,7 @@ public static class VersionRegistry V3MinorVersions.V20240412, V3MinorVersions.V20240416, V3MinorVersions.V20240606, + V3MinorVersions.V20240814, V3MinorVersions.VNext, ]; @@ -54,6 +55,7 @@ public static class V3MinorVersions public const string V20240412 = "20240412"; public const string V20240416 = "20240416"; public const string V20240606 = "20240606"; + public const string V20240814 = "20240814"; public const string VNext = VNextVersion; } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/PreviousNameHelper.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/PreviousNameHelper.cs index 53b1c4813c..a658dc89cc 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/PreviousNameHelper.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Models/PreviousNameHelper.cs @@ -1,13 +1,14 @@ -namespace TeachingRecordSystem.Core.Dqt.Models; +using Microsoft.Extensions.Configuration; using FullName = (string FirstName, string MiddleName, string LastName); -public static class PreviousNameHelper +namespace TeachingRecordSystem.Core.Dqt.Models; + +public class PreviousNameHelper(IConfiguration configuration) { - public static FullName[] GetFullPreviousNames( - IEnumerable previousNames, - Contact contact, - TimeSpan concurrentNameChangeWindow) + public FullName[] GetFullPreviousNames(IEnumerable previousNames, Contact contact) { + var concurrentNameChangeWindow = TimeSpan.FromSeconds(configuration.GetValue("ConcurrentNameChangeWindowSeconds", 5)); + var result = new List(); var currentFirstName = contact.FirstName!; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveContactsByTrnsQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveContactsByTrnsQuery.cs new file mode 100644 index 0000000000..7595dc218f --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveContactsByTrnsQuery.cs @@ -0,0 +1,6 @@ +using Microsoft.Xrm.Sdk.Query; + +namespace TeachingRecordSystem.Core.Dqt.Queries; + +public record GetActiveContactsByTrnsQuery(IEnumerable Trns, ColumnSet ColumnSet) : + ICrmQuery>; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveQtsRegistrationsByContactIdsQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveQtsRegistrationsByContactIdsQuery.cs new file mode 100644 index 0000000000..d250c0236a --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetActiveQtsRegistrationsByContactIdsQuery.cs @@ -0,0 +1,5 @@ +using Microsoft.Xrm.Sdk.Query; + +namespace TeachingRecordSystem.Core.Dqt.Queries; + +public record GetActiveQtsRegistrationsByContactIdsQuery(IEnumerable ContactIds, ColumnSet ColumnSet) : ICrmQuery>; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveContactsByTrnsHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveContactsByTrnsHandler.cs new file mode 100644 index 0000000000..e04410de65 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveContactsByTrnsHandler.cs @@ -0,0 +1,33 @@ +using AngleSharp.Common; +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk.Query; +using TeachingRecordSystem.Core.Dqt.Queries; + +namespace TeachingRecordSystem.Core.Dqt.QueryHandlers; + +public class GetActiveContactsByTrnsHandler : + ICrmQueryHandler> +{ + public async Task> Execute( + GetActiveContactsByTrnsQuery query, + IOrganizationServiceAsync organizationService) + { + var queryExpression = new QueryExpression(Contact.EntityLogicalName) + { + ColumnSet = query.ColumnSet, + Criteria = new FilterExpression(LogicalOperator.And) + { + Conditions = + { + new ConditionExpression(Contact.Fields.StateCode, ConditionOperator.Equal, (int)ContactState.Active), + new ConditionExpression(Contact.Fields.dfeta_TRN, ConditionOperator.In, query.Trns.ToArray()) + } + } + }; + + var response = await organizationService.RetrieveMultipleAsync(queryExpression); + var contacts = response.Entities.Select(e => e.ToEntity()).ToDictionary(c => c.dfeta_TRN, c => c); + + return query.Trns.ToDictionary(trn => trn, trn => contacts.GetValueOrDefault(trn)); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveQtsRegistrationsByContactIdsHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveQtsRegistrationsByContactIdsHandler.cs new file mode 100644 index 0000000000..575266f4ed --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetActiveQtsRegistrationsByContactIdsHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Messages; +using Microsoft.Xrm.Sdk.Query; +using TeachingRecordSystem.Core.Dqt.Queries; + +namespace TeachingRecordSystem.Core.Dqt.QueryHandlers; + +public class GetActiveQtsRegistrationsByContactIdsHandler : ICrmQueryHandler> +{ + public async Task> Execute(GetActiveQtsRegistrationsByContactIdsQuery query, IOrganizationServiceAsync organizationService) + { + var queryExpression = new QueryExpression() + { + EntityName = dfeta_qtsregistration.EntityLogicalName, + ColumnSet = query.ColumnSet + }; + queryExpression.Criteria.AddCondition(dfeta_qtsregistration.Fields.StateCode, ConditionOperator.Equal, (int)TaskState.Open); + queryExpression.Criteria.AddCondition(dfeta_qtsregistration.Fields.dfeta_PersonId, ConditionOperator.In, query.ContactIds.Cast().ToArray()); + + var request = new RetrieveMultipleRequest() + { + Query = queryExpression + }; + + var response = await organizationService.RetrieveMultipleAsync(queryExpression); + + var qtsRegistrationsByContactIds = response.Entities + .GroupBy(r => r.GetAttributeValue(dfeta_qtsregistration.Fields.dfeta_PersonId).Id) + .ToDictionary(group => group.Key, group => group.Select(e => e.ToEntity()).ToArray()); + + return query.ContactIds.ToDictionary(id => id, id => qtsRegistrationsByContactIds.GetValueOrDefault(id, Array.Empty())); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ReferenceDataCache.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ReferenceDataCache.cs index e7b71d9cba..a17ea68766 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ReferenceDataCache.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ReferenceDataCache.cs @@ -50,6 +50,12 @@ public async Task GetTeacherStatusByValue(string value) return teacherStatuses.Single(ts => ts.dfeta_Value == value, $"Could not find teacher status with value: '{value}'."); } + public async Task GetTeacherStatusById(Guid id) + { + var teacherStatuses = await EnsureTeacherStatuses(); + return teacherStatuses.Single(ts => ts.Id == id, $"Could not find teacher status with id: '{id}'."); + } + public async Task GetTeacherStatuses() { var teacherStatuses = await EnsureTeacherStatuses(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/ServiceCollectionExtensions.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/ServiceCollectionExtensions.cs index b9ad20dba0..a75288d5e3 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/ServiceCollectionExtensions.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/ServiceCollectionExtensions.cs @@ -11,7 +11,8 @@ public static IServiceCollection AddTrsBaseServices(this IServiceCollection serv { return services .AddSingleton() - .AddCrmQueries(); + .AddCrmQueries() + .AddSingleton(); } public static IServiceCollection AddAccessYourTeachingQualificationsOptions( diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Index.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Index.cshtml.cs index e4bac5b0f5..ac761c37ca 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Index.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/Index.cshtml.cs @@ -8,10 +8,8 @@ namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail; public class IndexModel( ICrmQueryDispatcher crmQueryDispatcher, - IConfiguration configuration) : PageModel + PreviousNameHelper previousNameHelper) : PageModel { - private readonly TimeSpan _concurrentNameChangeWindow = TimeSpan.FromSeconds(configuration.GetValue("ConcurrentNameChangeWindowSeconds", 5)); - [FromRoute] public Guid PersonId { get; set; } @@ -48,7 +46,7 @@ public async Task OnGet() Contact.Fields.dfeta_ActiveSanctions))); var contact = contactDetail!.Contact; - var previousNames = PreviousNameHelper.GetFullPreviousNames(contactDetail.PreviousNames, contactDetail.Contact, _concurrentNameChangeWindow); + var previousNames = previousNameHelper.GetFullPreviousNames(contactDetail.PreviousNames, contactDetail.Contact); Person = new PersonInfo() {