diff --git a/DigitalLearningSolutions.Data.Migrations/202510021000_AddIsdeletedSelfAssessmentCollaborators.cs b/DigitalLearningSolutions.Data.Migrations/202510021000_AddIsdeletedSelfAssessmentCollaborators.cs new file mode 100644 index 0000000000..870707eb79 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202510021000_AddIsdeletedSelfAssessmentCollaborators.cs @@ -0,0 +1,16 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + [Migration(202510021000)] + public class AddIsdeletedSelfAssessmentCollaborators : Migration + { + public override void Up() + { + Alter.Table("SelfAssessmentCollaborators").AddColumn("IsDeleted").AsBoolean().WithDefaultValue(false); + } + public override void Down() + { + Delete.Column("IsDeleted").FromTable("SelfAssessmentCollaborators"); + } + } +} diff --git a/DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs index ce5d0f1ff9..80919b1730 100644 --- a/DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs @@ -57,6 +57,7 @@ int categoryId bool UpdateSelectCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); + bool UpdateWorkingGroupTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); void MoveCompetencyInSelfAssessment(int competencyAssessmentId, int competencyId, string direction @@ -77,6 +78,10 @@ public bool UpdateCompetencyAssessmentFeaturesTaskStatus(int id, bool descriptio //DELETE DATA bool RemoveFrameworkCompetenciesFromAssessment(int competencyAssessmentId, int frameworkId); bool RemoveCompetencyFromAssessment(int competencyAssessmentId, int competencyId); + IEnumerable GetCollaboratorsForCompetencyAssessmentId(int competencyAssessmentId); + int AddCollaboratorToCompetencyAssessment(int competencyAssessmentId, string? userEmail, bool canModify, int? centreID); + void RemoveCollaboratorFromCompetencyAssessment(int competencyAssessmentId, int id); + CompetencyAssessmentCollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId); } public class CompetencyAssessmentDataService : ICompetencyAssessmentDataService @@ -646,6 +651,23 @@ public bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, } return true; } + public bool UpdateWorkingGroupTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus) + { + var numberOfAffectedRows = connection.Execute( + @"UPDATE SelfAssessmentTaskStatus SET WorkingGroupTaskStatus = @taskStatus + WHERE SelfAssessmentId = @assessmentId AND (@previousStatus IS NULL OR WorkingGroupTaskStatus = @previousStatus)", + new { assessmentId, taskStatus, previousStatus } + ); + if (numberOfAffectedRows < 1) + { + logger.LogWarning( + "Not updating WorkingGroupTaskStatus as db update failed. " + + $"assessmentId: {assessmentId}, taskStatus: {taskStatus}" + ); + return false; + } + return true; + } public IEnumerable GetCompetenciesForCompetencyAssessment(int competencyAssessmentId) { @@ -747,7 +769,7 @@ public void MoveCompetencyInSelfAssessment(int competencyAssessmentId, int compe new { SelfAssessmentID = competencyAssessmentId, CompetencyID = competencyId, Direction = direction }, commandType: CommandType.StoredProcedure ); - + } public void MoveCompetencyGroupInSelfAssessment(int competencyAssessmentId, int groupId, string direction) @@ -811,12 +833,12 @@ FROM SelfAssessments s INNER JOIN WHERE s.ID = @competencyAssessmentId", new { competencyAssessmentId } ); - + } - public void UpdateSelfAssessmentFromFramework( int selfAssessmentId, int? frameworkId) + public void UpdateSelfAssessmentFromFramework(int selfAssessmentId, int? frameworkId) { - + var numberOfAffectedRows = connection.Execute( @"UPDATE s SET @@ -828,22 +850,22 @@ FROM SelfAssessments s INNER JOIN AdminUsers AU ON F.OwnerAdminID = AU.AdminID WHERE s.id = @selfAssessmentId;" , - new {selfAssessmentId, frameworkId } + new { selfAssessmentId, frameworkId } ); } public bool InsertSelfAssessmentStructure(int selfAssessmentId, int? frameworkId) { - var numberOfAffectedRows = connection.Execute( - @"INSERT INTO SelfAssessmentStructure (SelfAssessmentID, CompetencyID, Ordering, CompetencyGroupID) + var numberOfAffectedRows = connection.Execute( + @"INSERT INTO SelfAssessmentStructure (SelfAssessmentID, CompetencyID, Ordering, CompetencyGroupID) SELECT s.ID, FC.CompetencyID, ROW_NUMBER() OVER( ORDER BY FCG.Ordering, FC.Ordering ), FCG.CompetencyGroupID FROM FrameworkCompetencies AS FC INNER JOIN FrameworkCompetencyGroups AS FCG ON FC.FrameworkCompetencyGroupID = FCG.ID INNER JOIN SelfAssessments s ON s.id = @selfAssessmentId WHERE FC.FrameworkID = @frameworkId" - , - new { selfAssessmentId, frameworkId } - ); + , + new { selfAssessmentId, frameworkId } + ); if (numberOfAffectedRows < 1) { logger.LogWarning( @@ -863,5 +885,137 @@ FROM FrameworkCompetencies AS FC ); } + public IEnumerable GetCollaboratorsForCompetencyAssessmentId(int competencyAssessmentId) + { + return connection.Query( + @"SELECT + 0 AS ID, + sa.ID AS SelfAssessmentID, + au.AdminID AS AdminID, + 1 AS CanModify, + au.Email AS UserEmail, + au.Active AS UserActive, + 'Owner' AS CompetencyAssessmentRole + FROM SelfAssessments AS sa + INNER JOIN AdminUsers AS au ON sa.CreatedByAdminID = au.AdminID + WHERE (sa.ID = @competencyAssessmentId) + UNION ALL + SELECT + ID, + SelfAssessmentID, + sc.AdminID, + CanModify, + UserEmail, + au.Active AS UserActive, + CASE WHEN CanModify = 1 THEN 'Contributor' ELSE 'Reviewer' END AS CompetencyAssessmentRole + FROM SelfAssessmentCollaborators sc + INNER JOIN AdminUsers AS au ON sc.AdminID = au.AdminID + AND sc.IsDeleted = 0 + WHERE (SelfAssessmentID = @competencyAssessmentId)", + new { competencyAssessmentId } + ); + } + public int AddCollaboratorToCompetencyAssessment(int competencyAssessmentId, string? userEmail, bool canModify, int? centreID) + { + if (userEmail is null || userEmail.Length == 0) + { + logger.LogWarning( + $"Not adding collaborator to competency assessment as it failed server side valiidation. competencyAssessmentId: {competencyAssessmentId}, userEmail: {userEmail}, canModify:{canModify}" + ); + return -3; + } + + var existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT ID + FROM SelfAssessmentCollaborators + WHERE (SelfAssessmentID = @competencyAssessmentId) AND (UserEmail = @userEmail) AND (IsDeleted=0)), 0) AS ID", + new { competencyAssessmentId, userEmail } + ); + if (existingId > 0) + { + return -2; + } + + var adminId = (int?)connection.ExecuteScalar( + @"SELECT AdminID FROM AdminUsers WHERE Email = @userEmail AND Active = 1 AND CentreID = @centreID", + new { userEmail, centreID } + ); + if (adminId is null) + { + return -4; + } + + var ownerEmail = (string?)connection.ExecuteScalar(@"SELECT AU.Email FROM SelfAssessments SA + INNER JOIN AdminUsers AU ON AU.AdminID = SA.CreatedByAdminID + WHERE SA.ID = @competencyAssessmentId", new { competencyAssessmentId }); + if (ownerEmail == userEmail) + { + return -5; + } + + var numberOfAffectedRows = connection.Execute( + @"INSERT INTO SelfAssessmentCollaborators (SelfAssessmentID, AdminID, UserEmail, CanModify) + VALUES (@competencyAssessmentId, @adminId, @userEmail, @canModify)", + new { competencyAssessmentId, adminId, userEmail, canModify } + ); + if (numberOfAffectedRows < 1) + { + logger.LogWarning( + $"Not inserting framework collaborator as db insert failed. AdminId: {adminId}, userEmail: {userEmail}, competencyAssessmentId: {competencyAssessmentId}, canModify: {canModify}" + ); + return -1; + } + + if (adminId > 0) + { + connection.Execute( + @"UPDATE AdminUsers SET IsWorkforceManager = 1 WHERE AdminId = @adminId AND IsWorkforceManager = 0", + new { adminId } + ); + } + + existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT ID + FROM SelfAssessmentCollaborators + WHERE (SelfAssessmentID = @competencyAssessmentId) AND (UserEmail = @userEmail) AND (IsDeleted=0)), 0) AS AdminID", + new { competencyAssessmentId, adminId, userEmail } + ); + return existingId; + } + + public void RemoveCollaboratorFromCompetencyAssessment(int competencyAssessmentId, int id) + { + var adminId = (int?)connection.ExecuteScalar( + @"SELECT AdminID FROM SelfAssessmentCollaborators WHERE (SelfAssessmentID = @competencyAssessmentId) AND (ID = @id)", + new { competencyAssessmentId, id } + ); + connection.Execute( + @"UPDATE SelfAssessmentCollaborators SET IsDeleted=1 WHERE (SelfAssessmentID = @competencyAssessmentId) AND (ID = @id); + UPDATE AdminUsers SET IsWorkforceManager = 0 WHERE AdminID = @adminId AND AdminID NOT IN (SELECT DISTINCT AdminID FROM SelfAssessmentCollaborators);", + new { competencyAssessmentId, id, adminId } + ); + } + public CompetencyAssessmentCollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId) + { + return connection.Query( + @"SELECT + sc.SelfAssessmentID, + sc.AdminID, + sc.CanModify, + sc.UserEmail, + au.Active AS UserActive, + CASE WHEN sc.CanModify = 1 THEN 'Contributor' ELSE 'Reviewer' END AS CompetencyAssessmentRole, + sa.[Name] AS CompetencyAssessmentName, + (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers AS au1 WHERE (AdminID = @invitedByAdminId)) AS InvitedByName, + (SELECT Email FROM AdminUsers AS au2 WHERE (AdminID = @invitedByAdminId)) AS InvitedByEmail + FROM SelfAssessmentCollaborators AS sc + INNER JOIN SelfAssessments AS sa ON sc.SelfAssessmentID = sa.ID + INNER JOIN AdminUsers AS au ON sc.AdminID = au.AdminID + WHERE (sc.ID = @id) AND (sc.IsDeleted=0)", + new { invitedByAdminId, id } + ).FirstOrDefault(); + } } } diff --git a/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaborator.cs b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaborator.cs new file mode 100644 index 0000000000..9e58b3199e --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaborator.cs @@ -0,0 +1,10 @@ +namespace DigitalLearningSolutions.Data.Models.CompetencyAssessments +{ + public class CompetencyAssessmentCollaborator + { + public int ID { get; set; } + public int SelfAssessmentID { get; set; } + public int? AdminID { get; set; } + public bool CanModify { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorDetail.cs b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorDetail.cs new file mode 100644 index 0000000000..cf353b47eb --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorDetail.cs @@ -0,0 +1,9 @@ +namespace DigitalLearningSolutions.Data.Models.CompetencyAssessments +{ + public class CompetencyAssessmentCollaboratorDetail : CompetencyAssessmentCollaborator + { + public string? UserEmail { get; set; } + public bool? UserActive { get; set; } + public string? CompetencyAssessmentRole { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorNotification.cs b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorNotification.cs new file mode 100644 index 0000000000..f1578157cf --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentCollaboratorNotification.cs @@ -0,0 +1,9 @@ +namespace DigitalLearningSolutions.Data.Models.CompetencyAssessments +{ + public class CompetencyAssessmentCollaboratorNotification : CompetencyAssessmentCollaboratorDetail + { + public string InvitedByEmail { get; set; } = string.Empty; + public string InvitedByName { get; set; } = string.Empty; + public string CompetencyAssessmentName { get; set; } = string.Empty; + } +} diff --git a/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs b/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs index 4464dee99c..d31bb1d2ab 100644 --- a/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs +++ b/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs @@ -160,7 +160,7 @@ public IActionResult SaveProfileName(CompetencyAssessmentBase competencyAssessme return View("Name", competencyAssessmentBase); } competencyAssessmentId = competencyAssessmentService.InsertCompetencyAssessment(adminId, userCentreId, competencyAssessmentBase.CompetencyAssessmentName, frameworkId); - if(frameworkId.HasValue && frameworkId.Value != 0) return RedirectToAction("CompetencyAssessmentFeatures", new { competencyAssessmentId, frameworkId }); + if (frameworkId.HasValue && frameworkId.Value != 0) return RedirectToAction("CompetencyAssessmentFeatures", new { competencyAssessmentId, frameworkId }); } else { @@ -655,18 +655,18 @@ public IActionResult ViewSelectedCompetencies(ViewSelectedCompetenciesFormData m [Route("/CompetencyAssessments/Framework/{frameworkId}/{competencyAssessmentId}/Features")] public IActionResult CompetencyAssessmentFeatures(int competencyAssessmentId, int? frameworkId = null) { - - var adminId = GetAdminID(); + + var adminId = GetAdminID(); var data = GetcompetencyAssessmentFeaturesData(); if (!string.IsNullOrEmpty(data.CompetencyAssessmentName)) return View(data); - var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId); - if (competencyAssessmentBase == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); - if (competencyAssessmentBase.UserRole < 2) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); - var baseModel = new CompetencyAssessmentFeaturesViewModel(competencyAssessmentBase.ID, - competencyAssessmentBase.CompetencyAssessmentName, - competencyAssessmentBase.UserRole, - frameworkId); - return View(baseModel); + var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId); + if (competencyAssessmentBase == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); + if (competencyAssessmentBase.UserRole < 2) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + var baseModel = new CompetencyAssessmentFeaturesViewModel(competencyAssessmentBase.ID, + competencyAssessmentBase.CompetencyAssessmentName, + competencyAssessmentBase.UserRole, + frameworkId); + return View(baseModel); } [HttpPost] [Route("/CompetencyAssessments/Framework/{frameworkId}/{competencyAssessmentId}/Features")] @@ -674,14 +674,14 @@ public IActionResult CompetencyAssessmentFeatures(CompetencyAssessmentFeaturesVi { if (featuresViewModel == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); SetcompetencyAssessmentFeaturesData(featuresViewModel); - return RedirectToAction("CompetencyAssessmentSummary", new { competencyAssessmentId = featuresViewModel.ID,featuresViewModel.FrameworkId }); + return RedirectToAction("CompetencyAssessmentSummary", new { competencyAssessmentId = featuresViewModel.ID, featuresViewModel.FrameworkId }); } [Route("/CompetencyAssessments/Framework/{frameworkId}/{competencyAssessmentId}/Summary")] public IActionResult CompetencyAssessmentSummary(int competencyAssessmentId, int? frameworkId = null) { if (competencyAssessmentService.GetSelfAssessmentStructure(competencyAssessmentId) != 0) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); - if (competencyAssessmentId == 0) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + if (competencyAssessmentId == 0) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); var data = GetcompetencyAssessmentFeaturesData(); if (data == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); SetcompetencyAssessmentFeaturesData(data); @@ -701,14 +701,92 @@ public IActionResult CompetencyAssessmentSummary(CompetencyAssessmentFeaturesVie data.WorkingGroupStatus, data.AllframeworkCompetenciesStatus); if (!features) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); - competencyAssessmentService.UpdateSelfAssessmentFromFramework(data.ID , data.FrameworkId ); - var insertSelfAssessment = competencyAssessmentService.InsertSelfAssessmentStructure(data.ID, data.FrameworkId); + competencyAssessmentService.UpdateSelfAssessmentFromFramework(data.ID, data.FrameworkId); + var insertSelfAssessment = competencyAssessmentService.InsertSelfAssessmentStructure(data.ID, data.FrameworkId); if (!insertSelfAssessment) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 500 }); multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("AssessmentFeaturesDataCWF"), TempData); TempData.Clear(); return RedirectToAction("ManageCompetencyAssessment", new { competencyAssessmentId = competency.ID, competency.FrameworkId }); } + [Route("/CompetencyAssessments/{competencyAssessmentId}/{actionName}")] + public IActionResult AssessmentWorkingGroup(int competencyAssessmentId, string actionName) + { + var adminId = GetAdminID(); + + var collaborators = competencyAssessmentService.GetCollaboratorsForCompetencyAssessmentId(competencyAssessmentId); + var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId); + if (competencyAssessmentBase == null) return StatusCode(404); + if (competencyAssessmentBase.UserRole < 2) + return StatusCode(403); + var taskStatus = competencyAssessmentService.GetCompetencyAssessmentTaskStatus(competencyAssessmentId, null); + var model = new WorkingGroupCollaboratorsViewModel() + { + CompetencyAssessmentID = competencyAssessmentId, + Collaborators = collaborators, + CompetencyAssessmentTaskStatus = taskStatus.WorkingGroupTaskStatus, + UserEmail = null, + Error = false, + }; + if (TempData["CompetencyAssessmentError"] != null) + { + ModelState.AddModelError("userEmail", TempData.Peek("CompetencyAssessmentError").ToString()); + } + return View("CompetencyAssessmentWorkingGroup", model); + } + + [HttpPost] + [Route("/CompetencyAssessments/{competencyAssessmentId}/{actionName}")] + public IActionResult AssessmentWorkingGroup(WorkingGroupCollaboratorsViewModel model, bool canModify, string actionName) + { + int? centreID = GetCentreId(); + if (actionName == "Collaborators") + { + var collaboratorId = competencyAssessmentService.AddCollaboratorToCompetencyAssessment(model.CompetencyAssessmentID, model.UserEmail, canModify, centreID); + if (collaboratorId > 0) + { + selfAssessmentNotificationService.SendCompetencyAssessmentCollaboratorInvite(collaboratorId, GetAdminID()); + } + else + { + if (collaboratorId == -3) + { + TempData["CompetencyAssessmentError"] = "Email address should not be empty"; + + } + else if (collaboratorId == -2) + { + TempData["CompetencyAssessmentError"] = $"A user with the email address has been previously added"; + } + else if (collaboratorId == -4) + { + TempData["CompetencyAssessmentError"] = $"The email address must match a registered DLS Admin account"; + } + else if (collaboratorId == -5) + { + TempData["CompetencyAssessmentError"] = $"The owner cannot be the collaborator of the competency assessment."; + } + else + { + TempData["CompetencyAssessmentError"] = "User not added,Kindly try again;"; + } + } + return RedirectToAction("AssessmentWorkingGroup", "CompetencyAssessments", new { model.CompetencyAssessmentID, actionName = actionName }); + + } + else + { + competencyAssessmentService.UpdateWorkingGroupTaskStatus(model.CompetencyAssessmentID, model.CompetencyAssessmentTaskStatus ?? false, null); + return RedirectToAction("ManageCompetencyAssessment", "CompetencyAssessments", new { model.CompetencyAssessmentID }); + } + } + + public IActionResult RemoveCollaborator(int competencyAssessmentId, int id, string actionName) + { + competencyAssessmentService.RemoveCollaboratorFromCompetencyAssessment(competencyAssessmentId, id); + return RedirectToAction("AssessmentWorkingGroup", "CompetencyAssessments", new { competencyAssessmentId, actionName = actionName }); + } + private void SetcompetencyAssessmentFeaturesData(CompetencyAssessmentFeaturesViewModel data) { multiPageFormService.SetMultiPageFormData( diff --git a/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessmentsController.cs b/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessmentsController.cs index a07a957ee0..a3069c974c 100644 --- a/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessmentsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessmentsController.cs @@ -15,6 +15,7 @@ public partial class CompetencyAssessmentsController : Controller private readonly IFrameworkService frameworkService; private readonly ICommonService commonService; private readonly IFrameworkNotificationService frameworkNotificationService; + private readonly ISelfAssessmentNotificationService selfAssessmentNotificationService; private readonly ILogger logger; private readonly IConfiguration config; private readonly IMultiPageFormService multiPageFormService; @@ -23,6 +24,7 @@ public CompetencyAssessmentsController( IFrameworkService frameworkService, ICommonService commonService, IFrameworkNotificationService frameworkNotificationService, + ISelfAssessmentNotificationService selfAssessmentNotificationService, ILogger logger, IConfiguration config, IMultiPageFormService multiPageFormService) @@ -31,6 +33,7 @@ public CompetencyAssessmentsController( this.frameworkService = frameworkService; this.commonService = commonService; this.frameworkNotificationService = frameworkNotificationService; + this.selfAssessmentNotificationService = selfAssessmentNotificationService; this.logger = logger; this.config = config; this.multiPageFormService = multiPageFormService; diff --git a/DigitalLearningSolutions.Web/Services/CompetencyAssessmentService.cs b/DigitalLearningSolutions.Web/Services/CompetencyAssessmentService.cs index 33d55b7fed..60f8f6118a 100644 --- a/DigitalLearningSolutions.Web/Services/CompetencyAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/CompetencyAssessmentService.cs @@ -46,6 +46,7 @@ public interface ICompetencyAssessmentService bool UpdateSelectCompetenciesTaskStatus(int competencyAssessmentId, bool taskStatus, bool? previousStatus); bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); + bool UpdateWorkingGroupTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus); void MoveCompetencyInSelfAssessment(int competencyAssessmentId, int competencyId, string direction @@ -67,7 +68,11 @@ bool UpdateCompetencyAssessmentFeaturesTaskStatus(int id, bool descriptionStatus //DELETE DATA bool RemoveFrameworkCompetenciesFromAssessment(int competencyAssessmentId, int frameworkId); bool RemoveCompetencyFromAssessment(int competencyAssessmentId, int competencyId); - } + IEnumerable GetCollaboratorsForCompetencyAssessmentId(int competencyAssessmentId); + int AddCollaboratorToCompetencyAssessment(int competencyAssessmentId, string? userEmail, bool canModify, int? centreID); + void RemoveCollaboratorFromCompetencyAssessment(int competencyAssessmentId, int id); + CompetencyAssessmentCollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId); + } public class CompetencyAssessmentService : ICompetencyAssessmentService { private readonly ICompetencyAssessmentDataService competencyAssessmentDataService; @@ -233,6 +238,10 @@ public bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, { return competencyAssessmentDataService.UpdateRoleRequirementsTaskStatus(assessmentId, taskStatus, previousStatus); } + public bool UpdateWorkingGroupTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus) + { + return competencyAssessmentDataService.UpdateWorkingGroupTaskStatus(assessmentId, taskStatus, previousStatus); + } public IEnumerable GetCompetenciesForCompetencyAssessment(int competencyAssessmentId) { @@ -284,11 +293,27 @@ public bool InsertSelfAssessmentStructure(int selfAssessmentId, int? frameworkId } public void UpdateSelfAssessmentFromFramework(int selfAssessmentId, int? frameworkId) { - competencyAssessmentDataService.UpdateSelfAssessmentFromFramework(selfAssessmentId, frameworkId); + competencyAssessmentDataService.UpdateSelfAssessmentFromFramework(selfAssessmentId, frameworkId); } public int? GetSelfAssessmentStructure(int competencyAssessmentId) { return competencyAssessmentDataService.GetSelfAssessmentStructure(competencyAssessmentId); } + public IEnumerable GetCollaboratorsForCompetencyAssessmentId(int competencyAssessmentId) + { + return competencyAssessmentDataService.GetCollaboratorsForCompetencyAssessmentId(competencyAssessmentId); + } + public int AddCollaboratorToCompetencyAssessment(int competencyAssessmentId, string? userEmail, bool canModify, int? centreID) + { + return competencyAssessmentDataService.AddCollaboratorToCompetencyAssessment(competencyAssessmentId, userEmail, canModify, centreID); + } + public void RemoveCollaboratorFromCompetencyAssessment(int competencyAssessmentId, int id) + { + competencyAssessmentDataService.RemoveCollaboratorFromCompetencyAssessment(competencyAssessmentId, id); + } + public CompetencyAssessmentCollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId) + { + return competencyAssessmentDataService.GetCollaboratorNotification(id, invitedByAdminId); } + } } diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentNotificationService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentNotificationService.cs new file mode 100644 index 0000000000..caa3910c70 --- /dev/null +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentNotificationService.cs @@ -0,0 +1,70 @@ +namespace DigitalLearningSolutions.Web.Services +{ + using System; + using DigitalLearningSolutions.Data.Constants; + using DigitalLearningSolutions.Data.DataServices; + using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Data.Models.Email; + using MimeKit; + + public interface ISelfAssessmentNotificationService + { + void SendCompetencyAssessmentCollaboratorInvite(int id, int invitedByAdminId); + } + + public class SelfAssessmentNotificationService : ISelfAssessmentNotificationService + { + + private readonly IConfigDataService configDataService; + private readonly IEmailService emailService; + private readonly ICompetencyAssessmentService competencyAssessmentService; + public SelfAssessmentNotificationService( + ICompetencyAssessmentService competencyAssessmentService, + IConfigDataService configDataService, + IEmailService emailService + ) + { + this.competencyAssessmentService = competencyAssessmentService; + this.configDataService = configDataService; + this.emailService = emailService; + } + public void SendCompetencyAssessmentCollaboratorInvite(int id, int invitedByAdminId) + { + var collaboratorNotification = competencyAssessmentService.GetCollaboratorNotification(id, invitedByAdminId); + if (collaboratorNotification == null) + { + throw new NotificationDataException($"No record found when trying to fetch collaboratorNotification Data. id: {id}, invitedByAdminId: {invitedByAdminId})"); + } + + var competencyAssessmentUrl = GetCompetencyAssessmentkUrl(collaboratorNotification.SelfAssessmentID, "Manage"); + string emailSubjectLine = $"Competency assessment {collaboratorNotification.CompetencyAssessmentRole} Invitation - Digital Learning Solutions"; + + var builder = new BodyBuilder + { + TextBody = $@"Dear colleague, + You have been identified as a {collaboratorNotification.CompetencyAssessmentRole} for the competency assessment, {collaboratorNotification.CompetencyAssessmentName}, by {collaboratorNotification.InvitedByName} ({collaboratorNotification.InvitedByEmail}). + To access the competency assessment, visit this url: {competencyAssessmentUrl}. You will need to be registered on the Digital Learning Solutions platform to view the competency assessment.", + HtmlBody = $@" +

Dear colleague,

+

You have been identified as a {collaboratorNotification.CompetencyAssessmentRole} for the competency assessment, {collaboratorNotification.CompetencyAssessmentName}, by {collaboratorNotification.InvitedByName}.

+

Click here to access the competency assessment. You will need to be registered on the Digital Learning Solutions platform to view the competency assessment.

+ ", + }; + emailService.SendEmail(new Email(emailSubjectLine, builder, collaboratorNotification.UserEmail, collaboratorNotification.InvitedByEmail)); + } + + public string GetCompetencyAssessmentkUrl(int competencyAssessmentID, string tab) + { + var competencyAssessmentUrl = GetDLSUriBuilder(); + competencyAssessmentUrl.Path += $"CompetencyAssessments/{competencyAssessmentID}/{tab}/"; + return competencyAssessmentUrl.Uri.ToString(); + } + public UriBuilder GetDLSUriBuilder() + { + var trackingSystemBaseUrl = configDataService.GetConfigValue(ConfigConstants.AppBaseUrl) ?? + throw new ConfigValueMissingException(configDataService.GetConfigValueMissingExceptionMessage("AppBaseUrl")); + return new UriBuilder(trackingSystemBaseUrl); + } + + } +} diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index 9b820a8045..9281b55c2a 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -469,6 +469,7 @@ private static void RegisterServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void RegisterDataServices(IServiceCollection services) diff --git a/DigitalLearningSolutions.Web/ViewModels/CompetencyAssessments/WorkingGroupCollaboratorsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/CompetencyAssessments/WorkingGroupCollaboratorsViewModel.cs new file mode 100644 index 0000000000..f073056e01 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/CompetencyAssessments/WorkingGroupCollaboratorsViewModel.cs @@ -0,0 +1,15 @@ +namespace DigitalLearningSolutions.Web.ViewModels.CompetencyAssessments +{ + using DigitalLearningSolutions.Data.Models.CompetencyAssessments; + using System.Collections.Generic; + + public class WorkingGroupCollaboratorsViewModel + { + public int CompetencyAssessmentID { get; set; } + public IEnumerable? Collaborators { get; set; } + public int AdminID { get; set; } + public bool? CompetencyAssessmentTaskStatus { get; set; } + public string? UserEmail { get; set; } + public bool Error { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/CompetencyAssessments/CompetencyAssessmentWorkingGroup.cshtml b/DigitalLearningSolutions.Web/Views/CompetencyAssessments/CompetencyAssessmentWorkingGroup.cshtml new file mode 100644 index 0000000000..e6242c0fff --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/CompetencyAssessments/CompetencyAssessmentWorkingGroup.cshtml @@ -0,0 +1,119 @@ +@using DigitalLearningSolutions.Web.ViewModels.CompetencyAssessments +@model WorkingGroupCollaboratorsViewModel +@{ + ViewData["Title"] = "Assign working group"; + ViewData["Application"] = "Competency Assessment Service"; + ViewData["HeaderPathName"] = "Competency Assessment Service"; + string errMsg = TempData["CompetencyAssessmentError"]?.ToString(); + if (!string.IsNullOrWhiteSpace(errMsg)) { Model.Error = true; } +} + +@section NavMenuItems { + +} +@if (Model.Error) +{ + +} +@section NavBreadcrumbs { + +} +

Choose working group members

+
+

Working Group Members

+ + + + + + @if (Model.Collaborators.Count() > 1) + { + + } + + + + @foreach (var collaborator in Model.Collaborators) + { + + + + @if (Model.Collaborators.Count() > 1) + { + + } + + } + + +
+
+ +
+
+ + +
+ Provide the email address of a user with a registered DLS admin account to add as a Contributor (to help create your competency assessment) or Reviewer (to review your competency assessment and mark it as ready for publication). +
+ @if (Model.Error) + { + + Error: @errMsg + + } + +
+
+ + + +
+ +
+
+ diff --git a/DigitalLearningSolutions.Web/Views/CompetencyAssessments/ManageCompetencyAssessment.cshtml b/DigitalLearningSolutions.Web/Views/CompetencyAssessments/ManageCompetencyAssessment.cshtml index f13c584c33..073dfd8d9e 100644 --- a/DigitalLearningSolutions.Web/Views/CompetencyAssessments/ManageCompetencyAssessment.cshtml +++ b/DigitalLearningSolutions.Web/Views/CompetencyAssessments/ManageCompetencyAssessment.cshtml @@ -53,7 +53,7 @@ - +