Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #246

Merged
merged 25 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5220394
Prevent search if candidate < 18 years
samgibsonmoj Sep 6, 2024
6b31196
Remove duplication from right to work dialog
samgibsonmoj Sep 5, 2024
b9e7e4e
Avoid alphabetisms
carlsixsmith-moj Sep 6, 2024
409b681
WIP: Registration details
samgibsonmoj Sep 4, 2024
1b642c6
Remove mappa level/category
samgibsonmoj Sep 5, 2024
3cdf6a5
Persist registration details and display informational messages on risk
samgibsonmoj Sep 5, 2024
70cc100
Add event handler for registration details update (currently does not…
samgibsonmoj Sep 5, 2024
f23dbcd
deps: bump the all-dependencies group with 10 updates
dependabot[bot] Sep 8, 2024
bbd77b9
Revert "deps: bump the all-dependencies group with 10 updates"
carlsixsmith-moj Sep 9, 2024
717da49
Update local publish script to keep the generated migrations, and to …
carlsixsmith-moj Sep 9, 2024
fe445d8
Prevent inactive users from signing in
samgibsonmoj Sep 9, 2024
47a0e11
Fix for component life cycle duplicate key issue
samgibsonmoj Sep 9, 2024
f4bc4c9
Disable dormant accounts after 30 days
samgibsonmoj Sep 9, 2024
8c15851
Feedback for risk
samgibsonmoj Sep 6, 2024
c438cbf
Fix for referral date on dialog
samgibsonmoj Sep 10, 2024
967cc2d
Display warning for inactive login and add entry to audit trail on login
samgibsonmoj Sep 11, 2024
4cc8c05
Send account deactivation reminders
samgibsonmoj Sep 11, 2024
53cf98f
Rename "Review" to "Complete" for Tasks and Objectives
samgibsonmoj Sep 12, 2024
13aad23
Enrolment status renames
samgibsonmoj Sep 12, 2024
c61ff62
More renaming of statuses
samgibsonmoj Sep 12, 2024
6c7ee72
Review icons for statuses
samgibsonmoj Sep 12, 2024
f0fee5e
Fix for right to work file size (referencing consent)
samgibsonmoj Sep 12, 2024
9134f5f
added DueOn to Risk and display warning from 14 day from due date
vks333 Sep 4, 2024
eb78b43
Added RiskDue to Participant, removed Due from Risk. Added event hand…
vks333 Sep 11, 2024
57804d0
review updates
vks333 Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions LocalPublish.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ dotnet test --configuration Release --no-build --verbosity normal
Write-Host "Publishing package" -ForegroundColor Green
dotnet publish --no-build --configuration Release --output $publishPath

Write-Host "Generting indempotent migration script" -ForegroundColor Green
Write-Host "Generating indempotent migration script" -ForegroundColor Green
dotnet ef migrations script --no-build --configuration Release --project src/Migrators/Migrators.MSSQL/Migrators.MSSQL.csproj --startup-project src/Server.UI/Server.UI.csproj --context Cfo.Cats.Infrastructure.Persistence.ApplicationDbContext --idempotent -o ./publish/Migration.sql

Write-Host "Compressing artifacts" -ForegroundColor Green
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath

Write-Host "Cleaning publish folder" -ForegroundColor Green

Get-ChildItem -Path $publishPath -Exclude "build-artifacts.zip" | ForEach-Object {
Get-ChildItem -Path $publishPath -Exclude build-artifacts.zip,Migration.sql | ForEach-Object {
if ($_.FullName -ne $destinationPath) {
Remove-Item -Recurse -Force $_.FullName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Cfo.Cats.Application.Common.Interfaces;

public interface ICommunicationsService
{
Task SendAccountDeactivationEmail(string email);
Task SendSmsCodeAsync(string mobileNumber, string code);
Task SendEmailCodeAsync(string email, string code);
}
4 changes: 4 additions & 0 deletions src/Application/Features/Candidates/DTOs/CandidateDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public class CandidateDto
/// </summary>
public required string Primary { get; set; }

public string[] RegistrationDetails { get; set; } = [];

public string? EstCode { get; set; }
public string? OrgCode { get; set; }

Expand All @@ -78,5 +80,7 @@ public class CandidateDto

public string? LocationDescription { get; set; }

public string? RegistrationDetailsJson { get; set; }

public int MappedLocationId { get; set; } = 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public CandidateSearchQueryValidator()

RuleFor(q => q.DateOfBirth)
.NotNull()
.WithMessage("Date of birth is required");
.WithMessage("Date of birth is required")
.LessThanOrEqualTo(DateTime.Today.AddYears(-18))
.WithMessage("Must be at least 18 years");

RuleFor(q => q.ExternalIdentifier)
.NotEmpty()
Expand Down
12 changes: 6 additions & 6 deletions src/Application/Features/Dashboard/Queries/GetDashboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ into grp
dto.ApprovedCases = result.Count;
continue;
}
if (result.Status == EnrolmentStatus.PendingStatus)
if (result.Status == EnrolmentStatus.IdentifiedStatus)
{
dto.PendingCases = result.Count;
dto.IdentifiedCases = result.Count;
continue;
}
if(result.Status == EnrolmentStatus.EnrolmentConfirmedStatus)
if(result.Status == EnrolmentStatus.EnrollingStatus)
{
dto.ConfirmedCases = result.Count;
dto.EnrollingCases = result.Count;
continue;
}
if (result.Status == EnrolmentStatus.SubmittedToProviderStatus)
Expand Down Expand Up @@ -76,8 +76,8 @@ public Validator()

public class DashboardDto
{
public int PendingCases { get; set; }
public int ConfirmedCases { get; set; }
public int IdentifiedCases { get; set; }
public int EnrollingCases { get; set; }
public int CasesAtPqa { get; set; }
public int CasesAtCfo { get; set; }
public int ApprovedCases { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public static IdentityAuditNotification LoginSucceededTwoFactorRequired(string
public static IdentityAuditNotification UserLockedOut(string userName, string ipAddress)
=> new(IdentityActionType.UserAccountLockedOut, ipAddress,userName);

public static IdentityAuditNotification UserInactive(string userName, string ipAddress)
=> new(IdentityActionType.UserInactive, ipAddress, userName);

public static IdentityAuditNotification PasswordReset(string userName, string ipAddress, string? performedBy = null)
=> new(IdentityActionType.PasswordReset,ipAddress , userName, performedBy);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<Result> Handle(Command request, CancellationToken cancellation

var supervisor = mapper.Map(request.Supervisor, participant.Supervisor);

participant.AddOrUpdateSupervisor(supervisor);
participant.UpdateSupervisor(supervisor);

return Result.Success();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async Task<Result<string>> Handle(Command request, CancellationToken canc
$"Right to work evidence for {request.ParticipantId}",
DocumentType.PDF);

long maxSizeBytes = Convert.ToInt64(ByteSize.FromMegabytes(Infrastructure.Constants.Documents.Consent.MaximumSizeInMegabytes).Bytes);
long maxSizeBytes = Convert.ToInt64(ByteSize.FromMegabytes(Infrastructure.Constants.Documents.RightToWork.MaximumSizeInMegabytes).Bytes);
await using var stream = request.Document.OpenReadStream(maxSizeBytes);
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, cancellationToken);
Expand Down Expand Up @@ -134,7 +134,7 @@ private async Task<bool> BePdfFile(IBrowserFile? file, CancellationToken cancell
if (file.ContentType != "application/pdf")
return false;

long maxSizeBytes = Convert.ToInt64(ByteSize.FromMegabytes(Infrastructure.Constants.Documents.Consent.MaximumSizeInMegabytes).Bytes);
long maxSizeBytes = Convert.ToInt64(ByteSize.FromMegabytes(Infrastructure.Constants.Documents.RightToWork.MaximumSizeInMegabytes).Bytes);

// Check file signature (magic numbers)
using (var stream = file.OpenReadStream(maxSizeBytes))
Expand Down
1 change: 1 addition & 0 deletions src/Application/Features/Participants/Commands/AddRisk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class Handler(IUnitOfWork unitOfWork) : IRequestHandler<Command, Result<G
public async Task<Result<Guid>> Handle(Command request, CancellationToken cancellationToken)
{
Risk? risk = await unitOfWork.DbContext.Risks
.Include(x => x.Participant)
.OrderByDescending(r => r.Created)
.FirstOrDefaultAsync(r => r.ParticipantId == request.ParticipantId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public async Task<Result> Handle(Command request, CancellationToken cancellation
return Result.Failure("Participant not found");
}

participant.TransitionTo(EnrolmentStatus.EnrolmentConfirmedStatus);
participant.TransitionTo(EnrolmentStatus.EnrollingStatus);

return Result.Success();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public async Task<Result<string>> Handle(Command request, CancellationToken canc
lastName: candidate.LastName,
gender: candidate.Gender,
dateOfBirth: candidate.DateOfBirth,
registrationDetailsJson: candidate.RegistrationDetailsJson,
activeInFeed: candidate.IsActive,
referralSource: request.ReferralSource!,
referralComments: request.ReferralComments,
Expand Down
4 changes: 3 additions & 1 deletion src/Application/Features/Participants/Commands/SaveRisk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public class Handler(IUnitOfWork unitOfWork, IMapper mapper, ICurrentUserService
{
public async Task<Result<Guid>> Handle(Command request, CancellationToken cancellationToken)
{
var risk = await unitOfWork.DbContext.Risks.FindAsync(request.RiskId);
var risk = await unitOfWork.DbContext.Risks
.Include(x => x.Participant)
.FirstOrDefaultAsync(x => x.Id == request.RiskId);

if(risk is null)
{
Expand Down
6 changes: 5 additions & 1 deletion src/Application/Features/Participants/DTOs/ParticipantDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class ParticipantDto
public string? MiddleName { get; set; }
public string? LastName { get; set; }
public DateOnly? DateOfBirth { get; set; }
public DateTime? RiskDue { get; set; }
public int? RiskDueInDays { get; set; }

[Description("Enrolment Status")]
public EnrolmentStatus? EnrolmentStatus { get; set; }
Expand Down Expand Up @@ -59,7 +61,9 @@ public Mapping()
.ForMember(target => target.TenantId, options => options.MapFrom(s => s.Owner!.TenantId))
.ForMember(target => target.ExternalIdentifiers, options => options.MapFrom(s => s.ExternalIdentifiers.ToArray()))
#nullable disable
.ForMember(target => target.SupportWorker, options => options.MapFrom(source => source.Owner.DisplayName));
.ForMember(target => target.SupportWorker, options => options.MapFrom(source => source.Owner.DisplayName))
.ForMember(dest => dest.RiskDue, opt => opt.MapFrom(src => src.RiskDue))
.ForMember(dest => dest.RiskDueInDays, opt => opt.MapFrom(src => src.RiskDueInDays()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ public class ParticipantSummaryDto
/// The participant's date of birth
/// </summary>
public required DateOnly DateOfBirth { get; set; }

public DateTime? RiskDue { get; set; }
public int? RiskDueInDays { get; set; }
/// <summary>
/// The current enrolment status of the participant
/// </summary>
public EnrolmentStatus EnrolmentStatus { get; set; } = EnrolmentStatus.PendingStatus;
public EnrolmentStatus EnrolmentStatus { get; set; } = EnrolmentStatus.IdentifiedStatus;

/// <summary>
/// The person who "owns" this participant's case. Usually the support worker.
Expand All @@ -59,7 +60,9 @@ public Mapping()
.ForMember(target => target.EnrolmentLocation, options => options.MapFrom(source => source.EnrolmentLocation.Name))
#pragma warning restore CS8602 // Dereference of a possibly null reference.
.ForMember(target => target.OwnerName, options => options.MapFrom(source => source.Owner!.DisplayName))
.ForMember(target => target.ParticipantName, options => options.MapFrom(source => source.FirstName + ' ' + source.LastName));
.ForMember(target => target.ParticipantName, options => options.MapFrom(source => source.FirstName + ' ' + source.LastName))
.ForMember(dest => dest.RiskDue, opt => opt.MapFrom(src => src.RiskDue))
.ForMember(dest => dest.RiskDueInDays, opt => opt.MapFrom(src => src.RiskDueInDays()));

CreateMap<ParticipantAssessment, AssessmentSummaryDto>()
.ForMember(target => target.AssessmentId, options => options.MapFrom(source => source.Id))
Expand Down
87 changes: 41 additions & 46 deletions src/Application/Features/Participants/DTOs/RiskDto.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Cfo.Cats.Application.Common.Validators;
using Cfo.Cats.Domain.Entities.Participants;
using Newtonsoft.Json;

namespace Cfo.Cats.Application.Features.Participants.DTOs;

Expand Down Expand Up @@ -51,26 +52,38 @@ public class RiskDto
[Description("Specific Risk(s)")]
public string? SpecificRisk { get; set; }

public MappaCategory? MappaCategory { get; set; }
public MappaLevel? MappaLevel { get; set; }

[Description("Referrer Name")]
[Description("Full Name")]
public string? ReferrerName { get; set; }

[Description("Referrer Email")]
[Description("Email Address")]
public string? ReferrerEmail { get; set; }

[Description("Referred on")]
[Description("Date Completed")]
public DateTime? ReferredOn { get; set; }

[Description("Declaration")]
public bool DeclarationSigned { get; set; } = false;
public string[] RegistrationDetails { get; set; } = [];

private class Mapping : Profile
{
public Mapping()
{
CreateMap<Risk, RiskDto>(MemberList.None)
.ForMember(dest => dest.RegistrationDetails, opt => opt.MapFrom((dest, src) =>
{
string? json;

if(dest.Completed.HasValue)
{
json = dest.RegistrationDetailsJson;
}
else
{
json = dest.Participant?.RegistrationDetailsJson;
}

json ??= JsonConvert.Null;

return JsonConvert.DeserializeObject<string[]>(json) ?? [];
}))
.ForMember(dest => dest.CommunityRiskDetail, opt => opt.MapFrom(src => new RiskDetail
{
RiskToChildren = src.RiskToChildrenInCommunity,
Expand All @@ -90,6 +103,10 @@ public Mapping()
RiskToOtherPrisoners = src.RiskToOtherPrisonersInCustody,
}))
.ReverseMap()
.ForMember(src => src.RegistrationDetailsJson, opt => opt.MapFrom((dest, src) =>
{
return src.Participant?.RegistrationDetailsJson;
}))
.ForPath(src => src.RiskToChildrenInCommunity, opt => opt.MapFrom(dest => dest.CommunityRiskDetail.RiskToChildren))
.ForPath(src => src.RiskToPublicInCommunity, opt => opt.MapFrom(dest => dest.CommunityRiskDetail.RiskToPublic))
.ForPath(src => src.RiskToKnownAdultInCommunity, opt => opt.MapFrom(dest => dest.CommunityRiskDetail.RiskToKnownAdult))
Expand Down Expand Up @@ -237,20 +254,6 @@ public Validator()
.SetValidator(new RiskDetail.Validator());
});

RuleFor(x => x.MappaCategory)
.NotNull()
.WithMessage("You must answer")
.NotEqual(MappaCategory.NotApplicable)
.When(x => x.MappaLevel != MappaLevel.NotApplicable)
.WithMessage("This answer is incompatible with the selected level");

RuleFor(x => x.MappaLevel)
.NotNull()
.WithMessage("You must answer")
.NotEqual(MappaLevel.NotApplicable)
.When(x => x.MappaCategory != MappaCategory.NotApplicable)
.WithMessage("This answer is incompatible with the selected category");

RuleFor(x => x.IsSubjectToSHPO)
.NotNull()
.WithMessage("You must answer");
Expand All @@ -259,31 +262,23 @@ public Validator()
.NotNull()
.WithMessage("You must answer");

RuleFor(x => x.DeclarationSigned)
.Equal(true)
.WithMessage("You must confirm");
RuleFor(x => x.ReferrerName)
.NotEmpty()
.WithMessage("You must provide the name")
.Matches(ValidationConstants.NameCompliantWithDMS)
.WithMessage(string.Format(ValidationConstants.NameCompliantWithDMSMessage, "Name"));

When(x => x.DeclarationSigned, () =>
{
RuleFor(x => x.ReferrerName)
.NotEmpty()
.WithMessage("You must provide the referrers name")
.Matches(ValidationConstants.NameCompliantWithDMS)
.WithMessage(string.Format(ValidationConstants.NameCompliantWithDMSMessage, "Referrer Name"));

RuleFor(x => x.ReferrerEmail)
.NotEmpty()
.WithMessage("You must provide the referrers email")
.EmailAddress()
.WithMessage("Must be a valid email address");

RuleFor(x => x.ReferredOn)
.NotEmpty()
.WithMessage("You must provide the referral date")
.LessThanOrEqualTo(DateTime.UtcNow.Date)
.WithMessage(ValidationConstants.DateMustBeInPast);
});
RuleFor(x => x.ReferrerEmail)
.NotEmpty()
.WithMessage("You must provide the email")
.EmailAddress()
.WithMessage("Must be a valid email address");

RuleFor(x => x.ReferredOn)
.NotEmpty()
.WithMessage("You must provide the date")
.LessThanOrEqualTo(DateTime.UtcNow.Date)
.WithMessage(ValidationConstants.DateMustBeInPast);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Application/Features/Participants/DTOs/RiskSummaryDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ public class RiskSummaryDto
public required RiskReviewReason ReviewReason { get; set; }
public required DateTime Created { get; set; }
public required string CreatedBy { get; set; }
public DateTime? ReferredOn { get; set; }

[Description("Date Risk form completed")]
public DateTime? ReferredOn { get; set; }
private class Mapping : Profile
{
public Mapping()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Cfo.Cats.Domain.Events;

namespace Cfo.Cats.Application.Features.Participants.EventHandlers;

public class ConsentCreated(IUnitOfWork unitOfWork):INotificationHandler<ConsentCreatedDomainEvent>
{
public async Task Handle(ConsentCreatedDomainEvent notification, CancellationToken cancellationToken)
{
var participant = await unitOfWork.DbContext.Participants
.Where(p => p.Id == notification.ParticipantId)
.FirstOrDefaultAsync(cancellationToken);

if (participant is { Consents.Count: 1 })
{
//Only on first Consent submit
participant.SetRiskDue(notification.ConsentDate.AddDays(14));
unitOfWork.DbContext.Participants.Update(participant);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class ParticipantCreated(IUnitOfWork unitOfWork) : INotificationHandler<P
{
public async Task Handle(ParticipantCreatedDomainEvent notification, CancellationToken cancellationToken)
{
var history = ParticipantEnrolmentHistory.Create(notification.Item.Id, EnrolmentStatus.PendingStatus);
var history = ParticipantEnrolmentHistory.Create(notification.Item.Id, EnrolmentStatus.IdentifiedStatus);
await unitOfWork.DbContext.ParticipantEnrolmentHistories.AddAsync(history, cancellationToken);
}
}
Loading
Loading