Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace DigitalLearningSolutions.Data.Migrations
{
using FluentMigrator;

[Migration(202508190845)]
public class AddSelfAssessmentProcessAgreed : Migration
{
public override void Up()
{
Alter.Table("CandidateAssessments").AddColumn("SelfAssessmentProcessAgreed").AsDateTime().Nullable();
}

public override void Down()
{
Delete.Column("SelfAssessmentProcessAgreed").FromTable("CandidateAssessments");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ CandidateAssessments AS CA LEFT OUTER JOIN
SA.LinearNavigation,
SA.UseDescriptionExpanders,
SA.ManageOptionalCompetenciesPrompt,
CAST(CASE WHEN CA.SelfAssessmentProcessAgreed IS NOT NULL THEN 1 ELSE 0 END AS BIT) AS SelfAssessmentProcessAgreed,
CAST(CASE WHEN SA.SupervisorSelfAssessmentReview = 1 OR SA.SupervisorResultsReview = 1 THEN 1 ELSE 0 END AS BIT) AS IsSupervised,
CASE
WHEN (SELECT COUNT(*) FROM SelfAssessmentSupervisorRoles WHERE SelfAssessmentID = @selfAssessmentId AND AllowDelegateNomination = 1) > 0
Expand Down Expand Up @@ -241,7 +242,7 @@ GROUP BY
CA.LaunchCount, CA.SubmittedDate, SA.LinearNavigation, SA.UseDescriptionExpanders,
SA.ManageOptionalCompetenciesPrompt, SA.SupervisorSelfAssessmentReview, SA.SupervisorResultsReview,
SA.ReviewerCommentsLabel,SA.EnforceRoleRequirementsForSignOff, SA.ManageSupervisorsDescription,CA.NonReportable,
U.FirstName , U.LastName,SA.MinimumOptionalCompetencies",
U.FirstName , U.LastName,SA.MinimumOptionalCompetencies, CA.SelfAssessmentProcessAgreed",
new { delegateUserId, selfAssessmentId }
);
}
Expand Down Expand Up @@ -325,6 +326,22 @@ public void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime
}
}

public void MarkProgressAgreed(int selfAssessmentId, int delegateUserId)
{
var numberOfAffectedRows = connection.Execute(
@"UPDATE CandidateAssessments SET SelfAssessmentProcessAgreed = GETDATE()
WHERE SelfAssessmentID = @selfAssessmentId AND DelegateUserID = @delegateUserId",
new { selfAssessmentId, delegateUserId }
);
if (numberOfAffectedRows < 1)
{
logger.LogWarning(
"SelfAssessmentProcessAgreed not set as db update failed. " +
$"Self assessment id: {selfAssessmentId}, Delegate User id: {delegateUserId}"
);
}
}

public void SetUpdatedFlag(int selfAssessmentId, int delegateUserId, bool status)
{
var numberOfAffectedRows = connection.Execute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ int competencyId

void SetBookmark(int selfAssessmentId, int delegateUserId, string bookmark);

void MarkProgressAgreed(int selfAssessmentId, int delegateUserId);
IEnumerable<CandidateAssessment> GetCandidateAssessments(int delegateUserId, int selfAssessmentId);

// SelfAssessmentSupervisorDataService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ public class CurrentSelfAssessment : SelfAssessment
public int? DelegateUserId { get; set; }
public string? DelegateName { get; set; }
public string? EnrolledByFullName { get; set; }
public bool SelfAssessmentProcessAgreed { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -870,5 +870,86 @@ public void SelfAssessmentOverview_Should_Return_View_With_Optional_Filter_Appli

result.Should().BeViewResult().ModelAs<SelfAssessmentOverviewViewModel>().CompetencyGroups.ToList()[0].Count().Should().Be(1);
}

[Test]
public void SelfAssessment_should_return_process_agreement_view_when_not_agreed_and_supervised()
{
// Given
var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment();
selfAssessment.IsSupervised = true;
selfAssessment.SelfAssessmentProcessAgreed = false;
A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId))
.Returns(selfAssessment);
A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId))
.Returns(new List<SelfAssessmentSupervisor>());

// When
var result = controller.SelfAssessment(SelfAssessmentId);

// Then
result.Should().BeViewResult()
.WithViewName("SelfAssessments/AgreeSelfAssessmentProcess")
.Model.Should().BeOfType<SelfAssessmentProcessViewModel>()
.Which.SelfAssessmentID.Should().Be(SelfAssessmentId);
}

[Test]
public void SelfAssessment_should_return_description_view_when_process_agreed_or_not_supervised()
{
// Given
var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment();
selfAssessment.IsSupervised = false; // or set SelfAssessmentProcessAgreed = true
A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId))
.Returns(selfAssessment);
A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId))
.Returns(new List<SelfAssessmentSupervisor>());
var expectedModel = new SelfAssessmentDescriptionViewModel(selfAssessment, new List<SelfAssessmentSupervisor>());

// When
var result = controller.SelfAssessment(SelfAssessmentId);

// Then
result.Should().BeViewResult()
.WithViewName("SelfAssessments/SelfAssessmentDescription")
.Model.Should().BeEquivalentTo(expectedModel);
}

[Test]
public void ProcessAgreed_should_return_agree_view_when_modelstate_invalid()
{
// Given
var model = new SelfAssessmentProcessViewModel { SelfAssessmentID = SelfAssessmentId };
controller.ModelState.AddModelError("Test", "Error");

// When
var result = controller.ProcessAgreed(model);

// Then
result.Should().BeViewResult()
.WithViewName("SelfAssessments/AgreeSelfAssessmentProcess")
.Model.Should().Be(model);
}

[Test]
public void ProcessAgreed_should_mark_progress_and_return_description_view()
{
// Given
var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment();
var supervisors = new List<SelfAssessmentSupervisor>();
var model = new SelfAssessmentProcessViewModel { SelfAssessmentID = SelfAssessmentId };
A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId))
.Returns(selfAssessment);
A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId))
.Returns(supervisors);

// When
var result = controller.ProcessAgreed(model);

// Then
A.CallTo(() => selfAssessmentService.MarkProgressAgreed(SelfAssessmentId, DelegateUserId)).MustHaveHappened();
result.Should().BeViewResult()
.WithViewName("SelfAssessments/SelfAssessmentDescription")
.Model.Should().BeEquivalentTo(new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,55 @@ public IActionResult SelfAssessment(int selfAssessmentId)
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 });
}

if (!selfAssessment.SelfAssessmentProcessAgreed && selfAssessment.IsSupervised)
{
var processmodel = new SelfAssessmentProcessViewModel()
{
SelfAssessmentID = selfAssessmentId,
Vocabulary = selfAssessment.Vocabulary,
VocabPlural = FrameworkVocabularyHelper.VocabularyPlural(selfAssessment.Vocabulary)
};
return View("SelfAssessments/AgreeSelfAssessmentProcess", processmodel);
}

selfAssessmentService.IncrementLaunchCount(selfAssessmentId, delegateUserId);
selfAssessmentService.UpdateLastAccessed(selfAssessmentId, delegateUserId);
var supervisors = selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(
selfAssessmentId,
delegateUserId
).ToList();
var model = new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors);

return View("SelfAssessments/SelfAssessmentDescription", model);
}

[HttpPost]
public IActionResult ProcessAgreed(SelfAssessmentProcessViewModel model)
{
if (!ModelState.IsValid)
{
return View("SelfAssessments/AgreeSelfAssessmentProcess", model);
}
var delegateUserId = User.GetUserIdKnownNotNull();
int selfAssessmentId = model.SelfAssessmentID;
var selfAssessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId);
if (selfAssessment == null)
{
logger.LogWarning(
$"Attempt to display self assessment description for user {delegateUserId} with no self assessment"
);
return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 });
}
var supervisors = selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(
selfAssessmentId,
delegateUserId
).ToList();
var selfAssessmentDescriptionViewModel = new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors);
selfAssessmentService.MarkProgressAgreed(selfAssessmentId, delegateUserId);
return View("SelfAssessments/SelfAssessmentDescription", selfAssessmentDescriptionViewModel);

}

[ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))]
[Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}")]
public IActionResult SelfAssessmentCompetency(int selfAssessmentId, int competencyNumber)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface ISelfAssessmentService

void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime? completeByDate);

void MarkProgressAgreed(int selfAssessmentId, int delegateUserId);
bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId, int centreId);

// Competencies
Expand Down Expand Up @@ -216,6 +217,11 @@ public void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime
selfAssessmentDataService.SetCompleteByDate(selfAssessmentId, delegateUserId, completeByDate);
}

public void MarkProgressAgreed(int selfAssessmentId, int delegateUserId)
{
selfAssessmentDataService.MarkProgressAgreed(selfAssessmentId, delegateUserId);
}

public IEnumerable<Competency> GetCandidateAssessmentResultsById(int candidateAssessmentId, int adminId, int? selfAssessmentResultId = null)
{
return selfAssessmentDataService.GetCandidateAssessmentResultsById(candidateAssessmentId, adminId, selfAssessmentResultId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class SelfAssessmentDescriptionViewModel
public readonly string VocabPlural;
public readonly string? Vocabulary;
public readonly bool NonReportable;
public bool SelfAssessmentProcessAgreed { get; set; }

public SelfAssessmentDescriptionViewModel(
CurrentSelfAssessment selfAssessment,
Expand All @@ -37,6 +38,7 @@ List<SelfAssessmentSupervisor> supervisors
Vocabulary = selfAssessment.Vocabulary;
VocabPlural = FrameworkVocabularyHelper.VocabularyPlural(selfAssessment.Vocabulary);
NonReportable = selfAssessment.NonReportable;
SelfAssessmentProcessAgreed = selfAssessment.SelfAssessmentProcessAgreed;
}

public List<SelfAssessmentSupervisor> Supervisors { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments
{
using System.Collections.Generic;
using DigitalLearningSolutions.Data.Models.SelfAssessments;
using DigitalLearningSolutions.Web.Attributes;
using DigitalLearningSolutions.Web.Helpers;

public class SelfAssessmentProcessViewModel
{
public int SelfAssessmentID { get; set; }
[BooleanMustBeTrue(ErrorMessage = "Please tick the checkbox to confirm that you understand and agree to the self-assessment process")]
public bool ActionConfirmed { get; set; }

public string? VocabPlural { get; set; }
public string? Vocabulary { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@using DigitalLearningSolutions.Web.Extensions
@using DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments;
@model SelfAssessmentProcessViewModel
@{
var errorHasOccurred = !ViewData.ModelState.IsValid;
ViewData["Title"] = (errorHasOccurred ? "Error: " : "") + "Agree To Process";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should maybe try to do something cleaner here, maybe we can set this through this view's specific controller method. We can assign values technically on the view pages but they should be simpler and not involving a ternary operation.

ViewData["Application"] = "LearningPortal";
ViewData["HeaderPathName"] = "LearningPortal";
}

@section NavMenuItems {
<partial name="Shared/_NavMenuItems" />
}

<div class="nhsuk-grid-row">
<div class="nhsuk-grid-column-full word-break">
@if (errorHasOccurred)
{
<vc:error-summary order-of-property-names="@(new[] { nameof(Model.ActionConfirmed) })" />
}
<h2>How are @Model.VocabPlural?.ToLower() and frameworks assessed?</h2>

<p>The process is learner-driven but you must be assessed by a supervisor before completing a self-assessment.</p>

<p>Once enrolled in a framework with at least 1 supervisor added, you can complete a digital self-assessment for any @Model.Vocabulary?.ToLower(). However, before doing so, ensure that:</p>

<ol>
<li>Your supervisor can sign-off this @Model.Vocabulary?.ToLower() based on their competence or department requirements.</li>
<li>You have been formally assessed, this could be through observation, discussion, or another agreed method with your supervisor.</li>
<li>You have reflected on the @Model.Vocabulary?.ToLower() description and feel you meet the requirements.</li>
<li>If you don’t fully meet the requirements, you can still document your progress and next steps for future assessment.</li>
</ol>

<form method="post" asp-controller="LearningPortal" asp-action="ProcessAgreed">
<div class="nhsuk-checkboxes__item">
<vc:single-checkbox asp-for="@nameof(Model.ActionConfirmed)"
label=" I understand and agree to the self-assessment process described above."
hint-text="" />
</div>
<button type="submit" class="nhsuk-button nhsuk-u-margin-top-4">
Continue
</button>
<input type="hidden" asp-for="SelfAssessmentID" />
<input type="hidden" asp-for="Vocabulary" />
<input type="hidden" asp-for="VocabPlural" />
</form>
<div class="nhsuk-back-link">
<a class="nhsuk-back-link__link" asp-controller="LearningPortal"
asp-action="Current">
<svg class="nhsuk-icon nhsuk-icon__chevron-left" focusable='false' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
</svg>
Cancel
</a>
</div>
</div>
</div>
Loading