Skip to content

Commit

Permalink
Add MQ Backend and UI (#929)
Browse files Browse the repository at this point in the history
Added CRM related tests

Added UI tests

Fixed file corruption caused by rebase

Fixed PR issues

Fixed PR issues

Tweaked filter requiring a personId

Tweaks based on updated designs

Added support for dfeta_trsdeletedevent field in CRM dfeta_qualification table

Regenerated CRM code
  • Loading branch information
hortha authored Dec 7, 2023
1 parent b8df013 commit 7716cd7
Show file tree
Hide file tree
Showing 41 changed files with 2,581 additions and 16 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,19 @@ public enum dfeta_ittsubject_StatusCode
Inactive = 2,
}

[System.Runtime.Serialization.DataContractAttribute()]
public enum dfeta_mqestablishment_StatusCode
{

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Active", 0)]
Active = 1,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Inactive", 1)]
Inactive = 2,
}

[System.Runtime.Serialization.DataContractAttribute()]
public enum dfeta_MRApplicationDocumentType
{
Expand Down Expand Up @@ -1980,6 +1993,35 @@ public enum dfeta_qualification_dfeta_HLTA_ModerationOutcome
TBD = 389040000,
}

[System.Runtime.Serialization.DataContractAttribute()]
public enum dfeta_qualification_dfeta_MQ_Status
{

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Deferred", 1)]
Deferred = 389040001,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Extended", 2)]
Extended = 389040002,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Failed", 3)]
Failed = 389040003,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("In Progress", 0)]
InProgress = 389040000,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Passed", 4)]
Passed = 389040004,

[System.Runtime.Serialization.EnumMemberAttribute()]
[OptionSetMetadataAttribute("Withdrawn", 5)]
Withdrawn = 389040005,
}

[System.Runtime.Serialization.DataContractAttribute()]
public enum dfeta_qualification_dfeta_Type
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record CreateMandatoryQualificationQuery : ICrmQuery<Guid>
{
public required Guid ContactId { get; init; }
public required Guid MqEstablishmentId { get; init; }
public required Guid SpecialismId { get; init; }
public required DateOnly StartDate { get; init; }
public required dfeta_qualification_dfeta_MQ_Status Result { get; init; }
public required DateOnly? EndDate { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetAllMqEstablishmentsQuery : ICrmQuery<dfeta_mqestablishment[]>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetAllSpecialismsQuery : ICrmQuery<dfeta_specialism[]>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class CreateMandatoryQualificationHandler : ICrmQueryHandler<CreateMandatoryQualificationQuery, Guid>
{
public async Task<Guid> Execute(CreateMandatoryQualificationQuery query, IOrganizationServiceAsync organizationService)
{
var qualification = new dfeta_qualification()
{
Id = Guid.NewGuid(),
dfeta_Type = dfeta_qualification_dfeta_Type.MandatoryQualification,
dfeta_name = "Mandatory Qualification",
dfeta_PersonId = query.ContactId.ToEntityReference(Contact.EntityLogicalName),
dfeta_MQ_MQEstablishmentId = query.MqEstablishmentId.ToEntityReference(dfeta_mqestablishment.EntityLogicalName),
dfeta_MQ_SpecialismId = query.SpecialismId.ToEntityReference(dfeta_specialism.EntityLogicalName),
dfeta_MQStartDate = query.StartDate.FromDateOnlyWithDqtBstFix(isLocalTime: true),
dfeta_MQ_Status = query.Result,
dfeta_MQ_Date = query.EndDate?.FromDateOnlyWithDqtBstFix(isLocalTime: true),
};

var qualificationId = await organizationService.CreateAsync(qualification);
return qualificationId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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 GetAllMqEstablishmentsHandler : ICrmQueryHandler<GetAllMqEstablishmentsQuery, dfeta_mqestablishment[]>
{
public async Task<dfeta_mqestablishment[]> Execute(GetAllMqEstablishmentsQuery query, IOrganizationServiceAsync organizationService)
{
var filter = new FilterExpression(LogicalOperator.And);
filter.AddCondition(dfeta_mqestablishment.Fields.StateCode, ConditionOperator.Equal, (int)dfeta_mqestablishmentState.Active);

var queryExpression = new QueryExpression
{
EntityName = dfeta_mqestablishment.EntityLogicalName,
ColumnSet = new ColumnSet(
dfeta_mqestablishment.PrimaryIdAttribute,
dfeta_mqestablishment.Fields.dfeta_name,
dfeta_mqestablishment.Fields.dfeta_Value),
Criteria = filter,
};

var request = new RetrieveMultipleRequest
{
Query = queryExpression
};

var response = await organizationService.RetrieveMultipleAsync(queryExpression);
return response.Entities.Select(x => x.ToEntity<dfeta_mqestablishment>()).ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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 GetAllSpecialismsHandler : ICrmQueryHandler<GetAllSpecialismsQuery, dfeta_specialism[]>
{
public async Task<dfeta_specialism[]> Execute(GetAllSpecialismsQuery query, IOrganizationServiceAsync organizationService)
{
var filter = new FilterExpression(LogicalOperator.And);
filter.AddCondition(dfeta_specialism.Fields.StateCode, ConditionOperator.Equal, (int)dfeta_specialismState.Active);
filter.AddCondition(dfeta_specialism.Fields.dfeta_Value, ConditionOperator.In, "Hearing", "Multi-Sensory", "Visual");

var queryExpression = new QueryExpression
{
EntityName = dfeta_specialism.EntityLogicalName,
ColumnSet = new ColumnSet(
dfeta_specialism.PrimaryIdAttribute,
dfeta_specialism.Fields.dfeta_name,
dfeta_specialism.Fields.dfeta_Value),
Criteria = filter,
};

var request = new RetrieveMultipleRequest
{
Query = queryExpression
};

var response = await organizationService.RetrieveMultipleAsync(queryExpression);
return response.Entities.Select(x => x.ToEntity<dfeta_specialism>()).ToArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ namespace TeachingRecordSystem.Core.Dqt;
public class ReferenceDataCache
{
private readonly ICrmQueryDispatcher _crmQueryDispatcher;
private Task<dfeta_mqestablishment[]>? _mqEstablishmentsTask;
private Task<dfeta_sanctioncode[]>? _getSanctionCodesTask;
private Task<Subject[]>? _getSubjectsTask;
private Task<dfeta_teacherstatus[]>? _getTeacherStatusesTask;
private Task<dfeta_earlyyearsstatus[]>? _getEarlyYearsStatusesTask;
private Task<dfeta_specialism[]>? _getSpecialismsTask;

public ReferenceDataCache(ICrmQueryDispatcher crmQueryDispatcher)
{
Expand Down Expand Up @@ -52,6 +54,32 @@ public async Task<dfeta_earlyyearsstatus> GetEarlyYearsStatusByValue(string valu
return earlyYearsStatuses.Single(ey => ey.dfeta_Value == value);
}

public async Task<dfeta_specialism[]> GetSpecialisms()
{
var specialisms = await EnsureSpecialisms();
return specialisms.ToArray();
}

public async Task<dfeta_specialism> GetSpecialismByValue(string value)
{
var specialisms = await EnsureSpecialisms();
// build environment has some duplicate Specialisms, which prevent us using Single() here
return specialisms.First(s => s.dfeta_Value == value);
}

public async Task<dfeta_mqestablishment[]> GetMqEstablishments()
{
var mqEstablishments = await EnsureMqEstablishments();
return mqEstablishments.ToArray();
}

public async Task<dfeta_mqestablishment> GetMqEstablishmentByValue(string value)
{
var mqEstablishments = await EnsureMqEstablishments();
// build environment has some duplicate MQ Establishments, which prevent us using Single() here
return mqEstablishments.First(s => s.dfeta_Value == value);
}

private Task<dfeta_sanctioncode[]> EnsureSanctionCodes() =>
LazyInitializer.EnsureInitialized(
ref _getSanctionCodesTask,
Expand All @@ -71,4 +99,14 @@ private Task<dfeta_earlyyearsstatus[]> EnsureEarlyYearsStatuses() =>
LazyInitializer.EnsureInitialized(
ref _getEarlyYearsStatusesTask,
() => _crmQueryDispatcher.ExecuteQuery(new GetAllEarlyYearsStatusesQuery()));

private Task<dfeta_specialism[]> EnsureSpecialisms() =>
LazyInitializer.EnsureInitialized(
ref _getSpecialismsTask,
() => _crmQueryDispatcher.ExecuteQuery(new GetAllSpecialismsQuery()));

private Task<dfeta_mqestablishment[]> EnsureMqEstablishments() =>
LazyInitializer.EnsureInitialized(
ref _mqEstablishmentsTask,
() => _crmQueryDispatcher.ExecuteQuery(new GetAllMqEstablishmentsQuery()));
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,53 @@ namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class CheckPersonExistsFilter : IAsyncResourceFilter, IOrderedFilter
{
private readonly bool _requireQts;

public int Order => -200;

public CheckPersonExistsFilter(bool requireQts = false)
{
_requireQts = requireQts;
}

public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var personId = context.RouteData.Values["personId"] as string;
if (personId is not null)
var personIdParam = context.RouteData.Values["personId"] as string ?? context.HttpContext.Request.Query["personId"];
if (personIdParam is null || !Guid.TryParse(personIdParam, out Guid personId))
{
context.Result = new BadRequestResult();
return;
}

var crmQueryDispatcher = context.HttpContext.RequestServices.GetRequiredService<ICrmQueryDispatcher>();
var person = await crmQueryDispatcher.ExecuteQuery(
new GetContactDetailByIdQuery(
personId,
new ColumnSet(
Contact.Fields.Id,
Contact.Fields.FirstName,
Contact.Fields.MiddleName,
Contact.Fields.LastName,
Contact.Fields.dfeta_StatedFirstName,
Contact.Fields.dfeta_StatedLastName,
Contact.Fields.dfeta_StatedMiddleName,
Contact.Fields.dfeta_QTSDate)));
if (person is null)
{
var crmQueryDispatcher = context.HttpContext.RequestServices.GetRequiredService<ICrmQueryDispatcher>();
var person = await crmQueryDispatcher.ExecuteQuery(new GetContactDetailByIdQuery(Guid.Parse(personId), new ColumnSet(Contact.Fields.Id)));
if (person is null)
context.Result = new NotFoundResult();
return;
}
else
{
if (_requireQts && person.Contact.dfeta_QTSDate is null)
{
context.Result = new NotFoundResult();
context.Result = new BadRequestResult();
return;
}
}

context.HttpContext.Items["CurrentPersonDetail"] = person;

await next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public static class JourneyNames
public const string CloseAlert = nameof(CloseAlert);
public const string EditName = nameof(EditName);
public const string EditDateOfBirth = nameof(EditDateOfBirth);
public const string AddMq = nameof(AddMq);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using TeachingRecordSystem.Core.Dqt.Models;

namespace TeachingRecordSystem.SupportUi.Pages.Mqs.AddMq;

public class AddMqState
{
public string? MqEstablishmentValue { get; set; }

public string? SpecialismValue { get; set; }

public DateOnly? StartDate { get; set; }

public dfeta_qualification_dfeta_MQ_Status? Result { get; set; }

public DateOnly? EndDate { get; set; }

[JsonIgnore]
[MemberNotNullWhen(true, nameof(MqEstablishmentValue), nameof(SpecialismValue), nameof(StartDate), nameof(Result))]
public bool IsComplete => !string.IsNullOrWhiteSpace(MqEstablishmentValue) &&
!string.IsNullOrEmpty(SpecialismValue) &&
StartDate.HasValue &&
Result.HasValue &&
(Result!.Value != dfeta_qualification_dfeta_MQ_Status.Passed || (Result.Value == dfeta_qualification_dfeta_MQ_Status.Passed && EndDate.HasValue));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@page "/mqs/add/check-answers/{handler?}"
@using TeachingRecordSystem.Core.Dqt.Models;
@model TeachingRecordSystem.SupportUi.Pages.Mqs.AddMq.CheckAnswersModel
@{
ViewBag.Title = "Check details and confirm mandatory qualification";
}

@section BeforeContent {
<govuk-back-link href="@LinkGenerator.MqAddResult(Model.PersonId, Model.JourneyInstance!.InstanceId)" />
}

<span class="govuk-caption-l">Add a mandatory qualification - @Model.PersonName</span>
<h1 class="govuk-heading-l" data-testid="title">@ViewBag.Title</h1>

<div class="govuk-grid-row">
<div class="govuk-grid-column-full-from-desktop">
<form action="@LinkGenerator.MqAddCheckAnswers(Model.PersonId, Model.JourneyInstance!.InstanceId)" method="post">
<govuk-summary-list>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Training provider</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="provider">@Model.MqEstablishment!.dfeta_name</govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action href="@LinkGenerator.MqAddProvider(Model.PersonId, Model.JourneyInstance!.InstanceId)">Change</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Specialism</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="specialism">@Model.Specialism!.dfeta_name</govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action href="@LinkGenerator.MqAddSpecialism(Model.PersonId, Model.JourneyInstance!.InstanceId)">Change</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Start date</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="start-date">@Model.StartDate!.Value.ToString("d MMMM yyyy")</govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action href="@LinkGenerator.MqAddStartDate(Model.PersonId, Model.JourneyInstance!.InstanceId)">Change</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>Result</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="result">@Model.Result</govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action href="@LinkGenerator.MqAddResult(Model.PersonId, Model.JourneyInstance!.InstanceId)">Change</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
<govuk-summary-list-row>
<govuk-summary-list-row-key>End date</govuk-summary-list-row-key>
<govuk-summary-list-row-value data-testid="end-date">@(Model.EndDate.HasValue ? Model.EndDate.Value.ToString("d MMMM yyyy") : "None")</govuk-summary-list-row-value>
<govuk-summary-list-row-actions>
<govuk-summary-list-row-action href="@LinkGenerator.MqAddResult(Model.PersonId, Model.JourneyInstance!.InstanceId)">Change</govuk-summary-list-row-action>
</govuk-summary-list-row-actions>
</govuk-summary-list-row>
</govuk-summary-list>

<div class="govuk-button-group">
<govuk-button type="submit">Confirm mandatory qualification</govuk-button>
<govuk-button formaction="@LinkGenerator.MqAddCheckAnswersCancel(Model.PersonId, Model.JourneyInstance!.InstanceId)" class="govuk-button--secondary" type="submit">Cancel and return to record</govuk-button>
</div>
</form>
</div>
</div>
Loading

0 comments on commit 7716cd7

Please sign in to comment.