Skip to content

Commit 6f9e4fe

Browse files
authored
Merge pull request #3250 from TechnologyEnhancedLearning/Develop/Features/TD-5307-SelectFrameworkSources
TD-5307 Implement select framework sources for competency framework
2 parents 5407a45 + 5c6015c commit 6f9e4fe

File tree

12 files changed

+624
-14
lines changed

12 files changed

+624
-14
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace DigitalLearningSolutions.Data.Migrations
2+
{
3+
using FluentMigrator;
4+
[Migration(202504241517)]
5+
public class AddFieldIsPrimaryToSelfAssessmentFrameworksTable : AutoReversingMigration
6+
{
7+
public override void Up()
8+
{
9+
Alter.Table("SelfAssessmentFrameworks").AddColumn("IsPrimary").AsBoolean().NotNullable().WithDefaultValue(1);
10+
}
11+
}
12+
}

DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs

Lines changed: 185 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ public interface ICompetencyAssessmentDataService
2828

2929
CompetencyAssessmentTaskStatus GetOrInsertAndReturnAssessmentTaskStatus(int assessmentId, bool frameworkBased);
3030

31+
int[] GetLinkedFrameworkIds (int assessmentId);
32+
33+
int? GetPrimaryLinkedFrameworkId(int assessmentId);
34+
35+
int GetCompetencyCountByFrameworkId(int assessmentId, int frameworkId);
36+
3137
//UPDATE DATA
3238
bool UpdateCompetencyAssessmentName(int competencyAssessmentId, int adminId, string competencyAssessmentName);
3339

@@ -44,10 +50,19 @@ int categoryId
4450
bool UpdateBrandingTaskStatus(int assessmentId, bool taskStatus);
4551
bool UpdateVocabularyTaskStatus(int assessmentId, bool taskStatus);
4652
bool UpdateRoleProfileLinksTaskStatus(int assessmentId, bool taskStatus);
53+
bool UpdateFrameworkLinksTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
54+
bool RemoveSelfAssessmentFramework(int assessmentId, int frameworkId, int adminId);
55+
bool UpdateSelectCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
56+
bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
57+
bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
58+
4759
//INSERT DATA
4860
int InsertCompetencyAssessment(int adminId, int centreId, string competencyAssessmentName);
4961
bool InsertSelfAssessmentFramework(int adminId, int selfAssessmentId, int frameworkId);
62+
5063
//DELETE DATA
64+
bool RemoveFrameworkCompetenciesFromAssessment(int competencyAssessmentId, int frameworkId);
65+
5166
}
5267

5368
public class CompetencyAssessmentDataService : ICompetencyAssessmentDataService
@@ -77,13 +92,15 @@ FROM AdminUsers
7792
sa.Archived,
7893
sa.LastEdit,
7994
STUFF((
80-
SELECT
81-
', ' + f.FrameworkName
82-
FROM
83-
Frameworks f
84-
WHERE
85-
f.ID = saf.FrameworkId
86-
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS LinkedFrameworks,
95+
SELECT
96+
', ' + f.FrameworkName
97+
FROM
98+
SelfAssessmentFrameworks saf2
99+
INNER JOIN Frameworks f ON f.ID = saf2.FrameworkId
100+
WHERE
101+
saf2.SelfAssessmentId = sa.ID
102+
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ''
103+
) AS LinkedFrameworks,
87104
(SELECT ProfessionalGroup
88105
FROM NRPProfessionalGroups
89106
WHERE (ID = sa.NRPProfessionalGroupID)) AS NRPProfessionalGroup,
@@ -100,8 +117,7 @@ FROM NRPRoles
100117

101118
private const string SelfAssessmentTables =
102119
@" LEFT OUTER JOIN
103-
SelfAssessmentReviews AS sar ON sac.ID = sar.SelfAssessmentCollaboratorID AND sar.Archived IS NULL AND sar.ReviewComplete IS NULL
104-
LEFT OUTER JOIN SelfAssessmentFrameworks AS saf ON sa.ID = saf.SelfAssessmentId";
120+
SelfAssessmentReviews AS sar ON sac.ID = sar.SelfAssessmentCollaboratorID AND sar.Archived IS NULL AND sar.ReviewComplete IS NULL";
105121

106122
private readonly IDbConnection connection;
107123
private readonly ILogger<CompetencyAssessmentDataService> logger;
@@ -324,14 +340,28 @@ public bool UpdateCompetencyAssessmentVocabulary(int competencyAssessmentId, int
324340

325341
public bool InsertSelfAssessmentFramework(int adminId, int selfAssessmentId, int frameworkId)
326342
{
343+
bool isPrimary = Convert.ToInt32(connection.ExecuteScalar(
344+
@"SELECT Count(1) FROM SelfAssessmentFrameworks
345+
WHERE SelfAssessmentId = @selfAssessmentId AND IsPrimary = 1", new { selfAssessmentId })) == 0;
346+
327347
var numberOfAffectedRows = connection.Execute(
328-
@"INSERT INTO SelfAssessmentFrameworks (SelfAssessmentId, FrameworkId, CreatedByAdminId)
329-
SELECT @selfAssessmentId, @frameworkId, @adminId
348+
@"INSERT INTO SelfAssessmentFrameworks (SelfAssessmentId, FrameworkId, CreatedByAdminId, IsPrimary)
349+
SELECT @selfAssessmentId, @frameworkId, @adminId, @isPrimary
330350
WHERE NOT EXISTS (SELECT 1 FROM SelfAssessmentFrameworks WHERE SelfAssessmentId = @selfAssessmentId AND FrameworkId = @frameworkId)"
331351
,
332-
new { adminId, selfAssessmentId, frameworkId }
352+
new { adminId, selfAssessmentId, frameworkId, isPrimary }
333353
);
334354
if (numberOfAffectedRows < 1)
355+
{
356+
numberOfAffectedRows = connection.Execute(
357+
@"UPDATE SelfAssessmentFrameworks
358+
SET RemovedDate = NULL, RemovedByAdminId = NULL, AmendedByAdminId = @adminId
359+
WHERE SelfAssessmentId = @selfAssessmentId AND FrameworkId = @frameworkId"
360+
,
361+
new { adminId, selfAssessmentId, frameworkId }
362+
);
363+
}
364+
if (numberOfAffectedRows < 1)
335365
{
336366
logger.LogWarning(
337367
"Not inserting SelfAssessmentFrameworks record as db insert failed. " +
@@ -459,5 +489,148 @@ public bool UpdateRoleProfileLinksTaskStatus(int assessmentId, bool taskStatus)
459489
}
460490
return true;
461491
}
492+
493+
public int[] GetLinkedFrameworkIds(int assessmentId)
494+
{
495+
return [.. connection.Query<int>(
496+
@"SELECT FrameworkId
497+
FROM SelfAssessmentFrameworks
498+
WHERE (SelfAssessmentId = @assessmentId) AND (RemovedDate IS NULL) AND (IsPrimary = 0)
499+
ORDER BY ID",
500+
new { assessmentId }
501+
)];
502+
}
503+
504+
public bool RemoveSelfAssessmentFramework(int assessmentId, int frameworkId, int adminId)
505+
{
506+
var numberOfAffectedRows = connection.Execute(
507+
@"UPDATE SelfAssessmentFrameworks SET RemovedDate = @removedDate, RemovedByAdminId = @adminId
508+
WHERE SelfAssessmentId = @assessmentId AND FrameworkId = @frameworkId",
509+
new { removedDate = DateTime.Now, assessmentId, frameworkId, adminId }
510+
);
511+
if (numberOfAffectedRows < 1)
512+
{
513+
logger.LogWarning(
514+
"Not updating SelfAssessmentFrameworks as db update failed. " +
515+
$"assessmentId: {assessmentId}, frameworkId: {frameworkId}, adminId: {adminId}"
516+
);
517+
return false;
518+
}
519+
return true;
520+
}
521+
522+
public int? GetPrimaryLinkedFrameworkId(int assessmentId)
523+
{
524+
return connection.QuerySingleOrDefault<int?>(
525+
@"SELECT TOP(1) FrameworkId
526+
FROM SelfAssessmentFrameworks
527+
WHERE (SelfAssessmentId = @assessmentId) AND (RemovedDate IS NULL) AND (IsPrimary = 1)
528+
ORDER BY ID DESC",
529+
new { assessmentId }
530+
);
531+
}
532+
533+
public bool UpdateFrameworkLinksTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus)
534+
{
535+
var numberOfAffectedRows = connection.Execute(
536+
@"UPDATE SelfAssessmentTaskStatus SET FrameworkLinksTaskStatus = @taskStatus
537+
WHERE SelfAssessmentId = @assessmentId AND (@previousStatus IS NULL OR FrameworkLinksTaskStatus = @previousStatus)",
538+
new { assessmentId, taskStatus, previousStatus }
539+
);
540+
if (numberOfAffectedRows < 1)
541+
{
542+
logger.LogWarning(
543+
"Not updating FrameworkLinksTaskStatus as db update failed. " +
544+
$"assessmentId: {assessmentId}, taskStatus: {taskStatus}"
545+
);
546+
return false;
547+
}
548+
return true;
549+
}
550+
551+
public int GetCompetencyCountByFrameworkId(int assessmentId, int frameworkId)
552+
{
553+
return connection.ExecuteScalar<int>(
554+
@"SELECT COUNT(sas.CompetencyID) AS Competencies
555+
FROM SelfAssessmentStructure AS sas INNER JOIN
556+
FrameworkCompetencies AS fc ON sas.CompetencyID = fc.CompetencyID INNER JOIN
557+
SelfAssessmentFrameworks AS saf ON fc.FrameworkID = saf.FrameworkId AND sas.SelfAssessmentID = saf.SelfAssessmentId
558+
WHERE (saf.SelfAssessmentId = @assessmentId) AND (saf.FrameworkId = @frameworkId)",
559+
new { assessmentId, frameworkId }
560+
);
561+
}
562+
563+
public bool RemoveFrameworkCompetenciesFromAssessment(int competencyAssessmentId, int frameworkId)
564+
{
565+
var numberOfAffectedRows = connection.Execute(
566+
@"DELETE FROM SelfAssessmentStructure
567+
FROM SelfAssessmentStructure INNER JOIN
568+
FrameworkCompetencies AS fc ON SelfAssessmentStructure.CompetencyID = fc.CompetencyID INNER JOIN
569+
SelfAssessmentFrameworks AS saf ON fc.FrameworkID = saf.FrameworkId AND SelfAssessmentStructure.SelfAssessmentID = saf.SelfAssessmentId
570+
WHERE (saf.SelfAssessmentId = @competencyAssessmentId) AND (saf.FrameworkId = @frameworkId)",
571+
new { competencyAssessmentId, frameworkId }
572+
);
573+
if (numberOfAffectedRows < 1)
574+
{
575+
logger.LogWarning(
576+
"Not removing competencies linked to source framework as db update failed. " +
577+
$"assessmentId: {competencyAssessmentId}, taskStatus: {frameworkId}"
578+
);
579+
return false;
580+
}
581+
return true;
582+
}
583+
584+
public bool UpdateSelectCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus)
585+
{
586+
var numberOfAffectedRows = connection.Execute(
587+
@"UPDATE SelfAssessmentTaskStatus SET SelectCompetenciesTaskStatus = @taskStatus
588+
WHERE SelfAssessmentId = @assessmentId AND (@previousStatus IS NULL OR SelectCompetenciesTaskStatus = @previousStatus)",
589+
new { assessmentId, taskStatus, previousStatus }
590+
);
591+
if (numberOfAffectedRows < 1)
592+
{
593+
logger.LogWarning(
594+
"Not updating SelectCompetenciesTaskStatus as db update failed. " +
595+
$"assessmentId: {assessmentId}, taskStatus: {taskStatus}"
596+
);
597+
return false;
598+
}
599+
return true;
600+
}
601+
public bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus)
602+
{
603+
var numberOfAffectedRows = connection.Execute(
604+
@"UPDATE SelfAssessmentTaskStatus SET OptionalCompetenciesTaskStatus = @taskStatus
605+
WHERE SelfAssessmentId = @assessmentId AND (@previousStatus IS NULL OR OptionalCompetenciesTaskStatus = @previousStatus)",
606+
new { assessmentId, taskStatus, previousStatus }
607+
);
608+
if (numberOfAffectedRows < 1)
609+
{
610+
logger.LogWarning(
611+
"Not updating OptionalCompetenciesTaskStatus as db update failed. " +
612+
$"assessmentId: {assessmentId}, taskStatus: {taskStatus}"
613+
);
614+
return false;
615+
}
616+
return true;
617+
}
618+
public bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus)
619+
{
620+
var numberOfAffectedRows = connection.Execute(
621+
@"UPDATE SelfAssessmentTaskStatus SET RoleRequirementsTaskStatus = @taskStatus
622+
WHERE SelfAssessmentId = @assessmentId AND (@previousStatus IS NULL OR RoleRequirementsTaskStatus = @previousStatus)",
623+
new { assessmentId, taskStatus, previousStatus }
624+
);
625+
if (numberOfAffectedRows < 1)
626+
{
627+
logger.LogWarning(
628+
"Not updating RoleRequirementsTaskStatus as db update failed. " +
629+
$"assessmentId: {assessmentId}, taskStatus: {taskStatus}"
630+
);
631+
return false;
632+
}
633+
return true;
634+
}
462635
}
463636
}

DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
using DigitalLearningSolutions.Data.Models.Centres;
1616
using DigitalLearningSolutions.Data.Models.Frameworks;
1717
using Microsoft.CodeAnalysis.CSharp.Syntax;
18+
using DigitalLearningSolutions.Data.Models.Courses;
19+
using DigitalLearningSolutions.Web.Services;
20+
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.GroupCourses;
1821

1922
public partial class CompetencyAssessmentsController
2023
{
@@ -389,5 +392,94 @@ public IActionResult SaveVocabulary(EditVocabularyViewModel model)
389392
competencyAssessmentService.UpdateVocabularyTaskStatus(model.ID, model.TaskStatus ?? false);
390393
return RedirectToAction("ManageCompetencyAssessment", new { competencyAssessmentId = model.ID });
391394
}
395+
[Route("/CompetencyAssessments/{competencyAssessmentId}/Frameworks/{actionName}")]
396+
public IActionResult SelectFrameworkSources(int competencyAssessmentId, string actionName)
397+
{
398+
var adminId = GetAdminID();
399+
var frameworks = frameworkService.GetAllFrameworks(adminId);
400+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
401+
if (competencyAssessmentBase == null)
402+
{
403+
logger.LogWarning($"Failed to load Vocabulary page for competencyAssessmentId: {competencyAssessmentId} adminId: {adminId}");
404+
return StatusCode(500);
405+
}
406+
if (competencyAssessmentBase.UserRole < 2)
407+
{
408+
return StatusCode(403);
409+
}
410+
var primaryFrameworkId = competencyAssessmentService.GetPrimaryLinkedFrameworkId(competencyAssessmentId);
411+
var additionalFrameworks = competencyAssessmentService.GetLinkedFrameworkIds(competencyAssessmentId);
412+
var competencyAssessmentTaskStatus = competencyAssessmentService.GetCompetencyAssessmentTaskStatus(competencyAssessmentId, null);
413+
var model = new SelectFrameworkSourcesViewModel(competencyAssessmentBase, frameworks, additionalFrameworks, primaryFrameworkId, competencyAssessmentTaskStatus.FrameworkLinksTaskStatus, actionName);
414+
return View(model);
415+
}
416+
[HttpPost]
417+
[Route("/CompetencyAssessments/{competencyAssessmentId}/Frameworks/{actionName}")]
418+
public IActionResult SelectFrameworkSources(SelectFrameworkSourcesFormData model, string actionName)
419+
{
420+
var adminId = GetAdminID();
421+
var competencyAssessmentId = model.CompetencyAssessmentId;
422+
if (!ModelState.IsValid)
423+
{
424+
425+
var frameworks = frameworkService.GetAllFrameworks(adminId);
426+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
427+
if (competencyAssessmentBase == null)
428+
{
429+
logger.LogWarning($"Failed to load Vocabulary page for competencyAssessmentId: {competencyAssessmentId} adminId: {adminId}");
430+
return StatusCode(500);
431+
}
432+
if (competencyAssessmentBase.UserRole < 2)
433+
{
434+
return StatusCode(403);
435+
}
436+
var primaryFrameworkId = competencyAssessmentService.GetPrimaryLinkedFrameworkId(competencyAssessmentId);
437+
var additionalFrameworks = competencyAssessmentService.GetLinkedFrameworkIds(competencyAssessmentId);
438+
var viewModel = new SelectFrameworkSourcesViewModel(competencyAssessmentBase, frameworks, additionalFrameworks, primaryFrameworkId, model.TaskStatus, model.ActionName);
439+
return View("SelectFrameworkSources", viewModel);
440+
}
441+
if (actionName == "AddFramework")
442+
{
443+
competencyAssessmentService.InsertSelfAssessmentFramework(adminId, competencyAssessmentId, model.FrameworkId);
444+
return RedirectToAction("SelectFrameworkSources", new { competencyAssessmentId, actionName = "Summary" });
445+
}
446+
else
447+
{
448+
competencyAssessmentService.UpdateFrameworkLinksTaskStatus(model.CompetencyAssessmentId, model.TaskStatus ?? false, null);
449+
return RedirectToAction("ManageCompetencyAssessment", new { competencyAssessmentId = model.CompetencyAssessmentId });
450+
}
451+
}
452+
[Route("/CompetencyAssessments/{competencyAssessmentId}/Frameworks/{frameworkId}/Remove")]
453+
public IActionResult RemoveFramework(int frameworkId, int competencyAssessmentId)
454+
{
455+
var frameworkCompetencyCount = competencyAssessmentService.GetCompetencyCountByFrameworkId(competencyAssessmentId, frameworkId);
456+
if (frameworkCompetencyCount > 0)
457+
{
458+
var adminId = GetAdminID();
459+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
460+
var framework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminId);
461+
var model = new ConfirmRemoveFrameworkSourceViewModel(competencyAssessmentBase, framework, frameworkCompetencyCount);
462+
return View("ConfirmRemoveFrameworkSource", model);
463+
}
464+
else
465+
{
466+
var adminId = GetAdminID();
467+
competencyAssessmentService.RemoveSelfAssessmentFramework(competencyAssessmentId, frameworkId, adminId);
468+
}
469+
return RedirectToAction("SelectFrameworkSources", new { competencyAssessmentId, actionName = "Summary" });
470+
}
471+
[HttpPost]
472+
[Route("/CompetencyAssessments/{competencyAssessmentId}/Frameworks/{frameworkId}/Remove")]
473+
public IActionResult RemoveFramework(ConfirmRemoveFrameworkSourceViewModel model)
474+
{
475+
if (!ModelState.IsValid)
476+
{
477+
return View("ConfirmRemoveFrameworkSource", model);
478+
}
479+
var adminId = GetAdminID();
480+
competencyAssessmentService.RemoveFrameworkCompetenciesFromAssessment(model.CompetencyAssessmentId, model.FrameworkId);
481+
competencyAssessmentService.RemoveSelfAssessmentFramework(model.CompetencyAssessmentId, model.FrameworkId, adminId);
482+
return RedirectToAction("SelectFrameworkSources", new { model.CompetencyAssessmentId, actionName = "Summary" });
483+
}
392484
}
393485
}

DigitalLearningSolutions.Web/Helpers/DisplayStringHelper.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ public static string GetPluralitySuffix(int number)
7171
{
7272
return number == 1 ? string.Empty : "s";
7373
}
74+
public static string PluraliseStringIfRequired(string input, int number)
75+
{
76+
if (number == 1)
77+
{
78+
return input;
79+
}
80+
else if (input.EndsWith("y"))
81+
{
82+
return input.Substring(0, input.Length - 1) + "ies";
83+
}
84+
else
85+
{
86+
return input + "s";
87+
}
88+
}
7489

7590
public static string? ReplaceNonAlphaNumericSpaceChars(string? input, string replacement)
7691
{

0 commit comments

Comments
 (0)