diff --git a/DigitalLearningSolutions.Data.Migrations/202412020900_UpdateCandidateAssessmentSupervisorsTable.cs b/DigitalLearningSolutions.Data.Migrations/202412020900_UpdateCandidateAssessmentSupervisorsTable.cs new file mode 100644 index 0000000000..0304c7aade --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202412020900_UpdateCandidateAssessmentSupervisorsTable.cs @@ -0,0 +1,36 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202412020900)] + public class UpdateCandidateAssessmentSupervisorsTable : ForwardOnlyMigration + { + public override void Up() + { + Execute.Sql($@"UPDATE cas + SET SelfAssessmentSupervisorRoleID = (SELECT ID FROM SelfAssessmentSupervisorRoles + WHERE SelfAssessmentID = ssr.SelfAssessmentID and AllowDelegateNomination = 1) + FROM CandidateAssessmentSupervisors cas INNER JOIN + SelfAssessmentSupervisorRoles ssr ON cas.SelfAssessmentSupervisorRoleID = ssr.ID + AND cas.Removed IS NULL AND ssr.AllowDelegateNomination = 0 INNER JOIN + SupervisorDelegates sd ON cas.SupervisorDelegateId = sd.ID INNER JOIN + AdminAccounts aa ON sd.SupervisorAdminID = aa.ID + WHERE aa.IsSupervisor = 0 AND aa.IsNominatedSupervisor = 1 + -- to exclude duplicate CandidateAssessmentID from update + AND cas.CandidateAssessmentID NOT IN ( + SELECT CandidateAssessmentID FROM CandidateAssessmentSupervisors WHERE CandidateAssessmentID in ( + SELECT Cas2.CandidateAssessmentID + FROM CandidateAssessmentSupervisors cas2 with (nolock) INNER JOIN + SelfAssessmentSupervisorRoles ssr2 with (nolock) ON cas2.SelfAssessmentSupervisorRoleID = ssr2.ID + AND cas2.Removed IS NULL AND ssr2.AllowDelegateNomination = 0 INNER JOIN + SupervisorDelegates sd2 with (nolock) ON cas2.SupervisorDelegateId = sd2.ID INNER JOIN + AdminAccounts aa2 with (nolock) ON sd2.SupervisorAdminID = aa2.ID + WHERE aa2.IsSupervisor = 0 AND aa2.IsNominatedSupervisor = 1 + ) + GROUP BY CandidateAssessmentID,SupervisorDelegateId + HAVING COUNT(*)>1 + )" + ); + } + } +} diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index df086021b6..181a331259 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -542,7 +542,7 @@ LEFT OUTER JOIN UserCentreDetails AS UCD ON new { candidateAssessmentId, enrolmentMethodId, completeByDateDynamic } ); } - if (candidateAssessmentId > 1 && supervisorDelegateId !=0) + if (candidateAssessmentId > 1 && supervisorDelegateId != 0) { string sqlQuery = $@" BEGIN TRANSACTION @@ -551,17 +551,17 @@ BEGIN TRANSACTION UPDATE CandidateAssessmentSupervisors SET Removed = NULL {((selfAssessmentSupervisorRoleId > 0) ? " ,SelfAssessmentSupervisorRoleID = @selfAssessmentSupervisorRoleID" : string.Empty)} - WHERE CandidateAssessmentID = @candidateAssessmentId + WHERE CandidateAssessmentID = @candidateAssessmentId AND SupervisorDelegateId = @supervisorDelegateId COMMIT TRANSACTION"; connection.Execute(sqlQuery - , new { candidateAssessmentId, selfAssessmentSupervisorRoleId, enrolmentMethodId, completeByDateDynamic }); + , new { candidateAssessmentId, selfAssessmentSupervisorRoleId, enrolmentMethodId, completeByDateDynamic, supervisorDelegateId }); } if (supervisorId > 0) { - + var adminUserId = Convert.ToInt32(connection.ExecuteScalar(@"SELECT UserID FROM AdminAccounts WHERE (AdminAccounts.ID = @supervisorId)", new { supervisorId }) ); diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 7539d73753..20dc80033e 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -8,6 +8,7 @@ using DigitalLearningSolutions.Data.Models.Common; using DigitalLearningSolutions.Data.Models.Email; using DigitalLearningSolutions.Data.Models.Frameworks; + using DigitalLearningSolutions.Data.Models.Frameworks.Import; using DigitalLearningSolutions.Data.Models.SelfAssessments; using Microsoft.Extensions.Logging; using AssessmentQuestion = DigitalLearningSolutions.Data.Models.Frameworks.AssessmentQuestion; @@ -98,6 +99,8 @@ bool zeroBased ); Competency? GetFrameworkCompetencyForPreview(int frameworkCompetencyId); + IEnumerable GetBulkCompetenciesForFramework(int frameworkId); + List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds); // Comments: IEnumerable GetCommentsForFrameworkId(int frameworkId, int adminId); @@ -120,7 +123,7 @@ bool zeroBased //INSERT DATA BrandedFramework CreateFramework(DetailFramework detailFramework, int adminId); - int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId); + int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId); int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); @@ -128,10 +131,10 @@ bool zeroBased int InsertCompetency(string name, string? description, int adminId); - int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); + int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false); int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify); - void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); + int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting); @@ -202,8 +205,8 @@ void UpdateFrameworkCompetencyGroup( int adminId ); - void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId); - void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); + void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription); + int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -472,7 +475,7 @@ public BrandedFramework CreateFramework(DetailFramework detailFramework, int adm return new BrandedFramework(); } - var existingFrameworks = (int)connection.ExecuteScalar( + var existingFrameworks = connection.QuerySingle( @"SELECT COUNT(*) FROM Frameworks WHERE FrameworkName = @frameworkName", new { frameworkName } ); @@ -545,7 +548,7 @@ int adminId return GetBrandedFrameworkByFrameworkId(frameworkId, adminId); } - public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId) + public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId) { if ((groupName.Length == 0) | (adminId < 1)) { @@ -555,9 +558,16 @@ public int InsertCompetencyGroup(string groupName, string? groupDescription, int return -2; } groupDescription = (groupDescription?.Trim() == "" ? null : groupDescription); - var existingId = (int)connection.ExecuteScalar( - @"SELECT COALESCE ((SELECT TOP(1)ID FROM CompetencyGroups WHERE [Name] = @groupName AND (@groupDescription IS NULL OR Description = @groupDescription)), 0) AS CompetencyGroupID", - new { groupName, groupDescription } + var existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT TOP (1) ID + FROM CompetencyGroups + WHERE (Name = @groupName) AND EXISTS + (SELECT 1 AS Expr1 + FROM FrameworkCompetencyGroups + WHERE (CompetencyGroupID = CompetencyGroups.ID) AND (FrameworkID = @frameworkId) OR + (CompetencyGroupID = CompetencyGroups.ID) AND (@frameworkId IS NULL))), 0) AS CompetencyGroupID", + new { groupName, groupDescription, frameworkId } ); if (existingId > 0) { @@ -578,8 +588,15 @@ public int InsertCompetencyGroup(string groupName, string? groupDescription, int return -1; } - existingId = (int)connection.ExecuteScalar( - @"SELECT COALESCE ((SELECT TOP(1)ID FROM CompetencyGroups WHERE [Name] = @groupName AND (@groupDescription IS NULL OR Description = @groupDescription)), 0) AS CompetencyGroupID", + existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT TOP (1) ID + FROM CompetencyGroups + WHERE (Name = @groupName) AND EXISTS + (SELECT 1 AS Expr1 + FROM FrameworkCompetencyGroups + WHERE (CompetencyGroupID = CompetencyGroups.ID) AND (FrameworkID = @frameworkId) OR + (CompetencyGroupID = CompetencyGroups.ID) AND (@frameworkId IS NULL))), 0) AS CompetencyGroupID", new { groupName, groupDescription } ); return existingId; @@ -595,7 +612,7 @@ public int InsertFrameworkCompetencyGroup(int groupId, int frameworkId, int admi return -2; } - var existingId = (int)connection.ExecuteScalar( + var existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencyGroups WHERE CompetencyGroupID = @groupID AND FrameworkID = @frameworkID), 0) AS FrameworkCompetencyGroupID", new { groupId, frameworkId } ); @@ -621,7 +638,7 @@ FROM [FrameworkCompetencyGroups] return -1; } - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencyGroups WHERE CompetencyGroupID = @groupID AND FrameworkID = @frameworkID), 0) AS FrameworkCompetencyGroupID", new { groupId, frameworkId } ); @@ -653,7 +670,8 @@ public int InsertFrameworkCompetency( int competencyId, int? frameworkCompetencyGroupID, int adminId, - int frameworkId + int frameworkId, + bool alwaysShowDescription = false ) { if ((competencyId < 1) | (adminId < 1) | (frameworkId < 1)) @@ -667,14 +685,14 @@ int frameworkId var existingId = 0; if (frameworkCompetencyGroupID == null) { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID IS NULL), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); } else { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID = @frameworkCompetencyGroupID), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); @@ -703,14 +721,14 @@ FROM [FrameworkCompetencies] if (frameworkCompetencyGroupID == null) { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID IS NULL), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); } else { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID = @frameworkCompetencyGroupID), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); @@ -761,7 +779,7 @@ public int AddCollaboratorToFramework(int frameworkId, string? userEmail, bool c return -3; } - var existingId = (int)connection.ExecuteScalar( + var existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCollaborators @@ -811,7 +829,7 @@ FROM FrameworkCollaborators ); } - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCollaborators @@ -903,7 +921,7 @@ public bool UpdateFrameworkName(int frameworkId, int adminId, string frameworkNa return false; } - var existingFrameworks = (int)connection.ExecuteScalar( + var existingFrameworks = connection.QuerySingle( @"SELECT COUNT(*) FROM Frameworks WHERE FrameworkName = @frameworkName AND ID <> @frameworkId", new { frameworkName, frameworkId } ); @@ -975,7 +993,7 @@ FROM FrameworkCompetencyGroups AS fcg public FrameworkCompetency? GetFrameworkCompetencyById(int Id) { return connection.QueryFirstOrDefault( - @"SELECT fc.ID, c.ID AS CompetencyID, c.Name, c.Description, fc.Ordering + @"SELECT fc.ID, c.ID AS CompetencyID, c.Name, c.Description, fc.Ordering, c.AlwaysShowDescription FROM FrameworkCompetencies AS fc INNER JOIN Competencies AS c ON fc.CompetencyID = c.ID WHERE fc.ID = @Id", @@ -999,7 +1017,7 @@ int adminId return; } - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencyGroups WHERE CompetencyGroupId = @competencyGroupId AND ID <> @frameworkCompetencyGroupId", @@ -1007,7 +1025,7 @@ int adminId ); if (usedElsewhere > 0) { - var newCompetencyGroupId = InsertCompetencyGroup(name, description, adminId); + var newCompetencyGroupId = InsertCompetencyGroup(name, description, adminId, null); if (newCompetencyGroupId > 0) { var numberOfAffectedRows = connection.Execute( @@ -1042,7 +1060,7 @@ int adminId } } - public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId) + public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription) { if ((frameworkCompetencyId < 1) | (adminId < 1) | (name.Length < 3)) { @@ -1054,10 +1072,10 @@ public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, st //DO WE NEED SOMETHING IN HERE TO CHECK WHETHER IT IS USED ELSEWHERE AND WARN THE USER? var numberOfAffectedRows = connection.Execute( - @"UPDATE Competencies SET Name = @name, Description = @description, UpdatedByAdminID = @adminId + @"UPDATE Competencies SET Name = @name, Description = @description, UpdatedByAdminID = @adminId, AlwaysShowDescription = CASE WHEN @alwaysShowDescription IS NULL THEN AlwaysShowDescription ELSE @alwaysShowDescription END FROM Competencies INNER JOIN FrameworkCompetencies AS fc ON Competencies.ID = fc.CompetencyID WHERE (fc.Id = @frameworkCompetencyId)", - new { name, description, adminId, frameworkCompetencyId } + new { name, description, adminId, frameworkCompetencyId, alwaysShowDescription} ); if (numberOfAffectedRows < 1) { @@ -1068,13 +1086,14 @@ public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, st } } - public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) + public int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) { + int totalRowsAffected = 0; string? commaSeparatedSelectedFlagIds = null; if (selectedFlagIds?.Length > 0) { commaSeparatedSelectedFlagIds = String.Join(',', selectedFlagIds); - connection.Execute( + totalRowsAffected += connection.Execute( @$"INSERT INTO CompetencyFlags(CompetencyID, FlagID, Selected) SELECT @competencyId, f.ID, 1 FROM Flags f @@ -1084,18 +1103,20 @@ SELECT FlagID FROM CompetencyFlags )", new { competencyId, selectedFlagIds }); } - connection.Execute( + totalRowsAffected += connection.Execute( @$"UPDATE CompetencyFlags SET Selected = (CASE WHEN FlagID IN ({commaSeparatedSelectedFlagIds ?? "null"}) THEN 1 ELSE 0 END) - WHERE CompetencyID = @competencyId", + WHERE CompetencyID = @competencyId AND Selected <> (CASE WHEN FlagID IN ({commaSeparatedSelectedFlagIds ?? "null"}) THEN 1 ELSE 0 END)", new { competencyId, frameworkId }); + return totalRowsAffected; } - public void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) + public int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) { - connection.Execute( - @$"INSERT INTO Flags(FrameworkID, FlagName, FlagGroup, FlagTagClass) - VALUES(@frameworkId, @flagName, @flagGroup, @flagTagClass)", + return connection.QuerySingle( + @"INSERT INTO Flags(FrameworkID, FlagName, FlagGroup, FlagTagClass) + OUTPUT INSERTED.ID + VALUES(@frameworkId, @flagName, @flagGroup, @flagTagClass);", new { frameworkId, flagName, flagGroup, flagTagClass }); } @@ -1108,7 +1129,7 @@ public void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, new { frameworkId, id, flagName, flagGroup, flagTagClass }); } - public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction) + public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction) // Valid directions are 'UP' and 'DOWN' { connection.Execute( "ReorderFrameworkCompetencyGroup", @@ -1117,7 +1138,7 @@ public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool si ); } - public void MoveFrameworkCompetency(int frameworkCompetencyId, bool singleStep, string direction) + public void MoveFrameworkCompetency(int frameworkCompetencyId, bool singleStep, string direction) // Valid directions are 'UP' and 'DOWN' { connection.Execute( "ReorderFrameworkCompetency", @@ -1166,14 +1187,14 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c } //Check if used elsewhere and delete competency group if not: - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencyGroups WHERE CompetencyGroupId = @competencyGroupId", new { competencyGroupId } ); if (usedElsewhere == 0) { - usedElsewhere = (int)connection.ExecuteScalar( + usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM SelfAssessmentStructure WHERE CompetencyGroupId = @competencyGroupId", new { competencyGroupId } @@ -1204,7 +1225,7 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) { - var competencyId = (int)connection.ExecuteScalar( + var competencyId = connection.QuerySingle( @"SELECT CompetencyID FROM FrameworkCompetencies WHERE ID = @frameworkCompetencyId", new { frameworkCompetencyId } ); @@ -1235,14 +1256,14 @@ public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) } //Check if used elsewhere and delete competency group if not: - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencies WHERE CompetencyID = @competencyId", new { competencyId } ); if (usedElsewhere == 0) { - usedElsewhere = (int)connection.ExecuteScalar( + usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM SelfAssessmentStructure WHERE CompetencyID = @competencyId", new { competencyId } @@ -1511,7 +1532,13 @@ public void AddCompetencyAssessmentQuestion(int frameworkCompetencyId, int asses FROM [CompetencyAssessmentQuestions] WHERE ([CompetencyId] = fc.CompetencyID)), 0)+1 FROM FrameworkCompetencies AS fc - WHERE Id = @frameworkCompetencyId", + WHERE Id = @frameworkCompetencyId + AND NOT EXISTS ( + SELECT 1 + FROM CompetencyAssessmentQuestions AS caq + WHERE caq.CompetencyId = fc.CompetencyID + AND caq.AssessmentQuestionID = @assessmentQuestionId + );", new { frameworkCompetencyId, assessmentQuestionId } ); if (numberOfAffectedRows < 1) @@ -1823,7 +1850,7 @@ FROM Competencies AS C INNER JOIN public int GetAdminUserRoleForFrameworkId(int adminId, int frameworkId) { - return (int)connection.ExecuteScalar( + return connection.QuerySingle( @"SELECT CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole FROM Frameworks AS FW LEFT OUTER JOIN FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId @@ -2396,5 +2423,47 @@ FROM FrameworkDefaultQuestions new { competencyId, frameworkId } ); } + + public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) + { + if (frameworkId < 1) + { + return connection.Query( + @"SELECT NULL AS ID, '' AS CompetencyGroup, '' AS GroupDescription, '' AS Competency, '' AS CompetencyDescription, 0 AS AlwaysShowDescription, '' AS FlagsCsv" + ); + } + else + { + return connection.Query( + @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STUFF(( + SELECT ', ' + f.FlagName + FROM Flags AS f + INNER JOIN CompetencyFlags AS cf ON f.ID = cf.FlagID + WHERE cf.CompetencyID = c.ID + FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS FlagsCsv + FROM + Competencies AS c INNER JOIN + FrameworkCompetencies AS fc ON c.ID = fc.CompetencyID INNER JOIN + FrameworkCompetencyGroups AS fcg ON fc.FrameworkCompetencyGroupID = fcg.ID INNER JOIN + CompetencyGroups AS cg ON fcg.CompetencyGroupID = cg.ID + WHERE (fc.FrameworkID = @frameworkId) + GROUP BY fc.ID, c.ID, cg.Name, cg.Description, c.Name, c.Description, c.AlwaysShowDescription, fcg.Ordering, fc.Ordering + ORDER BY fcg.Ordering, fc.Ordering", + new { frameworkId } + ); + } + + } + public List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds) + { + return connection.Query( + @"SELECT fc.ID + FROM FrameworkCompetencies AS fc INNER JOIN + FrameworkCompetencyGroups AS fcg ON fc.FrameworkCompetencyGroupID = fcg.ID + WHERE (fc.FrameworkID = @frameworkId) AND (fc.ID IN @frameworkCompetencyIds) + ORDER BY fcg.Ordering, fc.Ordering", + new { frameworkId, frameworkCompetencyIds } + ).ToList(); + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs index 1da83a6d65..2390d54147 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs @@ -8,7 +8,7 @@ public partial class SelfAssessmentDataService { - public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminCategoryId) + public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminIdCategoryID) { return connection.Query( @"SELECT SelfAssessment.Id, @@ -67,7 +67,8 @@ Competencies AS C RIGHT OUTER JOIN CandidateAssessmentSupervisorVerifications AS casv ON casv.CandidateAssessmentSupervisorID = cas.ID LEFT OUTER JOIN AdminAccounts AS aaEnrolledBy ON aaEnrolledBy.ID = CA.EnrolledByAdminID LEFT OUTER JOIN Users AS uEnrolledBy ON uEnrolledBy.ID = aaEnrolledBy.UserID - WHERE (CA.DelegateUserID = @delegateUserId) AND (CA.RemovedDate IS NULL) AND (CA.CompletedDate IS NULL) AND (ISNULL(@adminCategoryId, 0) = 0 OR sa.CategoryID = @adminCategoryId) + WHERE (CA.DelegateUserID = @delegateUserId) AND (CA.RemovedDate IS NULL) AND (CA.CompletedDate IS NULL) + AND (ISNULL(@adminIdCategoryID, 0) = 0 OR sa.CategoryID = @adminIdCategoryId) GROUP BY CA.SelfAssessmentID, SA.Name, SA.Description, SA.IncludesSignposting, SA.SupervisorResultsReview, SA.ReviewerCommentsLabel, SA.IncludeRequirementsFilters, @@ -83,10 +84,87 @@ CandidateAssessments AS CA LEFT OUTER JOIN (casv.Verified IS NOT NULL) GROUP BY SelfAssessmentID,casv.SignedOff )Signoff ON SelfAssessment.Id =Signoff.SelfAssessmentID", - new { delegateUserId, centreId, adminCategoryId } + new { delegateUserId, centreId, adminIdCategoryID } + ); + } + public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId) + { + return connection.Query( + @"SELECT SelfAssessment.Id, + SelfAssessment.Name, + SelfAssessment.Description, + SelfAssessment.IncludesSignposting, + SelfAssessment.IncludeRequirementsFilters, + SelfAssessment. IsSupervisorResultsReviewed, + SelfAssessment.ReviewerCommentsLabel, + SelfAssessment. Vocabulary, + SelfAssessment. NumberOfCompetencies, + SelfAssessment.StartedDate, + SelfAssessment.LastAccessed, + SelfAssessment.CompleteByDate, + SelfAssessment.CandidateAssessmentId, + SelfAssessment.UserBookmark, + SelfAssessment.UnprocessedUpdates, + SelfAssessment.LaunchCount, + SelfAssessment. IsSelfAssessment, + SelfAssessment.SubmittedDate, + SelfAssessment. CentreName, + SelfAssessment.EnrolmentMethodId, + Signoff.SignedOff, + Signoff.Verified, + EnrolledByForename +' '+EnrolledBySurname AS EnrolledByFullName + FROM (SELECT + CA.SelfAssessmentID AS Id, + SA.Name, + SA.Description, + SA.IncludesSignposting, + SA.IncludeRequirementsFilters, + SA.SupervisorResultsReview AS IsSupervisorResultsReviewed, + SA.ReviewerCommentsLabel, + COALESCE(SA.Vocabulary, 'Capability') AS Vocabulary, + COUNT(C.ID) AS NumberOfCompetencies, + CA.StartedDate, + CA.LastAccessed, + CA.CompleteByDate, + CA.ID AS CandidateAssessmentId, + CA.UserBookmark, + CA.UnprocessedUpdates, + CA.LaunchCount, + 1 AS IsSelfAssessment, + CA.SubmittedDate, + CR.CentreName AS CentreName, + CA.EnrolmentMethodId, + uEnrolledBy.FirstName AS EnrolledByForename, + uEnrolledBy.LastName AS EnrolledBySurname + FROM Centres AS CR INNER JOIN + CandidateAssessments AS CA INNER JOIN + SelfAssessments AS SA ON CA.SelfAssessmentID = SA.ID ON CR.CentreID = CA.CentreID INNER JOIN + CentreSelfAssessments AS csa ON csa.SelfAssessmentID = SA.ID AND csa.CentreID = @centreId LEFT OUTER JOIN + Competencies AS C RIGHT OUTER JOIN + SelfAssessmentStructure AS SAS ON C.ID = SAS.CompetencyID ON CA.SelfAssessmentID = SAS.SelfAssessmentID LEFT OUTER JOIN + CandidateAssessmentSupervisors AS cas ON ca.ID =cas.CandidateAssessmentID LEFT OUTER JOIN + CandidateAssessmentSupervisorVerifications AS casv ON casv.CandidateAssessmentSupervisorID = cas.ID LEFT OUTER JOIN + AdminAccounts AS aaEnrolledBy ON aaEnrolledBy.ID = CA.EnrolledByAdminID LEFT OUTER JOIN + Users AS uEnrolledBy ON uEnrolledBy.ID = aaEnrolledBy.UserID + WHERE (CA.DelegateUserID = @delegateUserId) AND (CA.RemovedDate IS NULL) AND (CA.CompletedDate IS NULL) + GROUP BY + CA.SelfAssessmentID, SA.Name, SA.Description, SA.IncludesSignposting, SA.SupervisorResultsReview, + SA.ReviewerCommentsLabel, SA.IncludeRequirementsFilters, + COALESCE(SA.Vocabulary, 'Capability'), CA.StartedDate, CA.LastAccessed, CA.CompleteByDate, + CA.ID, + CA.UserBookmark, CA.UnprocessedUpdates, CA.LaunchCount, CA.SubmittedDate, CR.CentreName,CA.EnrolmentMethodId, + uEnrolledBy.FirstName,uEnrolledBy.LastName)SelfAssessment LEFT OUTER JOIN + (SELECT SelfAssessmentID,casv.SignedOff,MAX(casv.Verified) Verified FROM + CandidateAssessments AS CA LEFT OUTER JOIN + CandidateAssessmentSupervisors AS cas ON ca.ID =cas.CandidateAssessmentID LEFT OUTER JOIN + CandidateAssessmentSupervisorVerifications AS casv ON casv.CandidateAssessmentSupervisorID = cas.ID + WHERE (CA.DelegateUserID = @delegateUserId) AND (CA.RemovedDate IS NULL) AND (CA.CompletedDate IS NULL) AND (casv.SignedOff = 1) AND + (casv.Verified IS NOT NULL) + GROUP BY SelfAssessmentID,casv.SignedOff + )Signoff ON SelfAssessment.Id =Signoff.SelfAssessmentID", + new { delegateUserId, centreId } ); } - public CurrentSelfAssessment? GetSelfAssessmentForCandidateById(int delegateUserId, int selfAssessmentId) { return connection.QueryFirstOrDefault( diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs index ceb322b467..845a00b07c 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs @@ -288,7 +288,7 @@ public IEnumerable GetResultSupervisorVerifications(int selfAssessme INNER JOIN DelegateAccounts DA1 ON CA.DelegateUserID = DA1.UserID AND au.CentreID = DA1.CentreID AND DA1.Active=1 WHERE (LAR.Verified IS NULL)  AND ((LAR.Result IS NOT NULL) OR (LAR.SupportingComments IS NOT NULL))  - AND (LAR.Requested IS NOT NULL) + AND (LAR.Requested IS NOT NULL) AND (au.CategoryID = 0 OR au.CategoryID = (select CategoryID from SelfAssessments where ID = @selfAssessmentId)) ORDER BY SupervisorVerificationRequested DESC, C.Name", (competency, assessmentQuestion) => { diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs index 346fa62112..0528c7f907 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs @@ -72,8 +72,8 @@ int competencyId // CandidateAssessmentsDataService - IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminCategoryId); - + IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminIdCategoryID); + IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId); CurrentSelfAssessment? GetSelfAssessmentForCandidateById(int delegateUserId, int selfAssessmentId); void UpdateLastAccessed(int selfAssessmentId, int delegateUserId); @@ -174,7 +174,6 @@ int GetSelfAssessmentActivityDelegatesExportCount(string searchString, string so bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId); int GetSelfAssessmentCategoryId(int selfAssessmentId); } - public partial class SelfAssessmentDataService : ISelfAssessmentDataService { private readonly IDbConnection connection; diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentSupervisorDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentSupervisorDataService.cs index 7c3a5d118f..0c79c20586 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentSupervisorDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentSupervisorDataService.cs @@ -78,7 +78,7 @@ int delegateUserId return connection.Query( @$"{SelectSelfAssessmentSupervisorQuery} WHERE (sd.Removed IS NULL) AND (cas.Removed IS NULL) AND (ca.DelegateUserID = @delegateUserId) - AND (ca.SelfAssessmentID = @selfAssessmentId) + AND (ca.SelfAssessmentID = @selfAssessmentId) AND (au.Supervisor = 1 or au.NominatedSupervisor = 1) AND (au.CategoryID = 0 OR au.CategoryID IN (select CategoryID from SelfAssessments where ID = @selfAssessmentId)) ORDER BY SupervisorName", new { selfAssessmentId, delegateUserId } @@ -95,7 +95,7 @@ int delegateUserId WHERE (sd.Removed IS NULL) AND (cas.Removed IS NULL) AND (sd.DelegateUserID = @delegateUserId) AND (ca.SelfAssessmentID = @selfAssessmentId) AND (sd.SupervisorAdminID IS NOT NULL) AND (coalesce(sasr.ResultsReview, 1) = 1) - AND au.Active = 1 + AND au.Active = 1 AND (au.Supervisor = 1 or au.NominatedSupervisor = 1) AND (au.CategoryID = 0 OR au.CategoryID IN (select CategoryID from SelfAssessments where ID = @selfAssessmentId)) ORDER BY SupervisorName", new { selfAssessmentId, delegateUserId } diff --git a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs index e06de8deb1..5c52d18b1e 100644 --- a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs @@ -22,7 +22,7 @@ public interface ISupervisorDataService DelegateSelfAssessment? GetSelfAssessmentByCandidateAssessmentId(int candidateAssessmentId, int adminId, int? adminIdCategoryId); IEnumerable GetSupervisorDashboardToDoItemsForRequestedSignOffs(int adminId); IEnumerable GetSupervisorDashboardToDoItemsForRequestedReviews(int adminId); - DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId); + DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId, int? adminIdCategoryId); IEnumerable GetAvailableRoleProfilesForDelegate(int candidateId, int centreId, int? categoryId); RoleProfile? GetRoleProfileById(int selfAssessmentId); IEnumerable GetSupervisorRolesForSelfAssessment(int selfAssessmentId); @@ -594,7 +594,7 @@ FROM CandidateAssessmentSupervisors AS cas INNER JOIN WHERE (ca.RemovedDate IS NULL) AND (cas.SupervisorDelegateId = @supervisorDelegateId) AND (cas.Removed IS NULL) AND (sa.ID = @selfAssessmentId)", new { selfAssessmentId, supervisorDelegateId } ).FirstOrDefault(); } - public DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId) + public DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId, int? adminIdCategoryId) { return connection.Query( @$"SELECT ca.ID, sa.ID AS SelfAssessmentID, sa.Name AS RoleName, sa.QuestionLabel, sa.DescriptionLabel, sa.ReviewerCommentsLabel, @@ -611,7 +611,7 @@ FROM SelfAssessmentResultSupervisorVerifications AS sarsv FROM CandidateAssessmentSupervisors AS cas INNER JOIN CandidateAssessments AS ca ON cas.CandidateAssessmentID = ca.ID INNER JOIN SelfAssessments AS sa ON sa.ID = ca.SelfAssessmentID - WHERE (ca.ID = @candidateAssessmentId)", new { candidateAssessmentId } + WHERE (ca.ID = @candidateAssessmentId) AND (ISNULL(@adminIdCategoryID, 0) = 0 OR sa.CategoryID = @adminIdCategoryId)", new { candidateAssessmentId, adminIdCategoryId } ).FirstOrDefault(); } public DelegateSelfAssessment? GetSelfAssessmentBySupervisorDelegateCandidateAssessmentId(int candidateAssessmentId, int supervisorDelegateId) diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index 687aeec180..d93094c97b 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -279,7 +279,10 @@ bool isCentreManager IsContentCreator = @isContentCreator, IsContentManager = @isContentManager, ImportOnly = @importOnly, - CategoryID = @categoryId, + CategoryID = CASE + WHEN @categoryID = 0 THEN NULL + ELSE @categoryID + END, IsCentreManager = @isCentreManager WHERE ID = @adminId", new diff --git a/DigitalLearningSolutions.Data/Helpers/FilteringHelper.cs b/DigitalLearningSolutions.Data/Helpers/FilteringHelper.cs index a806df3f5d..cdd42ca825 100644 --- a/DigitalLearningSolutions.Data/Helpers/FilteringHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/FilteringHelper.cs @@ -36,8 +36,8 @@ public static string BuildFilterValueString(string group, string propertyName, s { return existingFilterString; } - - return existingFilterString + FilterSeparator + newFilterToAdd; + var filterString = existingFilterString + FilterSeparator + newFilterToAdd; + return RemoveDuplicateFilters(filterString, newFilterToAdd); } public static string? GetFilterString( @@ -46,7 +46,8 @@ public static string BuildFilterValueString(string group, string propertyName, s bool clearFilters, HttpRequest request, string cookieName, - string? defaultFilterValue = null + string? defaultFilterValue = null, + IEnumerable? availableFilters = null ) { var cookieHasBeenSet = request.Cookies.ContainsKey(cookieName); @@ -59,17 +60,19 @@ public static string BuildFilterValueString(string group, string propertyName, s if (cookieHasBeenSet && noFiltersInQueryParams) { - return request.Cookies[cookieName] == EmptyFiltersCookieValue ? null : request.Cookies[cookieName]; + return request.Cookies[cookieName] == EmptyFiltersCookieValue ? null : GetValidFilters(request.Cookies[cookieName], availableFilters); } - return noFiltersInQueryParams - ? defaultFilterValue - : AddNewFilterToFilterString(existingFilterString, newFilterToAdd); + var filterString = noFiltersInQueryParams + ? defaultFilterValue + : AddNewFilterToFilterString(existingFilterString, newFilterToAdd); + + return GetValidFilters(filterString, availableFilters); } - public static string? GetCategoryAndTopicFilterString( - string? categoryFilterString, - string? topicFilterString - ) + public static string? GetCategoryAndTopicFilterString( + string? categoryFilterString, + string? topicFilterString + ) { if (categoryFilterString == null && topicFilterString == null) { @@ -167,34 +170,33 @@ public static string GetFilterValueForRegistrationPrompt(int promptNumber, strin : answer; return BuildFilterValueString(group, group.Split('(')[0], propertyValue); } - public static string? GetValidFilters(string existingFilterString, string newFilterToAdd, IEnumerable availableFilters, HttpRequest request, string cookieName) + + public static string? GetValidFilters(string? existingFilterString, IEnumerable? availableFilters) { - var cookieValue = request.Cookies[cookieName]; - if (string.IsNullOrEmpty(cookieValue) || cookieValue == EmptyFiltersCookieValue) + if (string.IsNullOrEmpty(existingFilterString) || availableFilters == null) { - return existingFilterString; + return null; } - var existingFilters = cookieValue.Split(FilterSeparator); + var existingFilters = existingFilterString.Split(FilterSeparator); var validFilterValues = availableFilters - .SelectMany(filter => filter.FilterOptions) - .Select(option => option.FilterValue) - .ToHashSet(); - + .SelectMany(filter => filter.FilterOptions) + .Select(option => option.FilterValue) + .ToHashSet(); + var filteredResults = existingFilters - .Where(entry => IsFilterInvalid(entry, validFilterValues)) - .ToList(); - var newCookieValue = string.Join(FilterSeparator, filteredResults); - if (string.IsNullOrEmpty(newCookieValue)) return null; - newCookieValue = AddNewFilterToFilterString(newCookieValue, newFilterToAdd); - return RemoveDuplicateFilters( newFilterToAdd, newCookieValue); + .Where(entry => IsFilterInvalid(entry, validFilterValues)) + .ToList(); + var newFilterString = string.Join(FilterSeparator, filteredResults); + + return string.IsNullOrEmpty(newFilterString) ? null : newFilterString; } - private static bool IsFilterInvalid(string filterEntry, HashSet validFilterValues) + private static bool IsFilterInvalid(string filterEntry, HashSet validFilterValues) { if (validFilterValues.Contains(filterEntry)) return true; return false; } - public static string RemoveDuplicateFilters(string newFilterToAdd, string? existingFilterString) + public static string RemoveDuplicateFilters(string? existingFilterString, string newFilterToAdd) { if (string.IsNullOrEmpty(existingFilterString)) { diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs index 56bd78ddc1..a42d3d10fe 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs @@ -14,6 +14,7 @@ public class FrameworkCompetency : BaseSearchableItem public int AssessmentQuestions { get; set; } public int CompetencyLearningResourcesCount { get; set; } public string? FrameworkName { get; set; } + public bool? AlwaysShowDescription { get; set; } public override string SearchableName { diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs new file mode 100644 index 0000000000..e59ba301be --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DigitalLearningSolutions.Data.Models.Frameworks.Import +{ + public class BulkCompetency + { + public int? ID { get; set; } + public string? CompetencyGroup { get; set; } + public string? GroupDescription { get; set; } + public string? Competency { get; set; } + public string? CompetencyDescription { get; set; } + public bool? AlwaysShowDescription { get; set; } + public string? FlagsCsv { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 78e70194ec..1dd67ed007 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -1,53 +1,69 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { using ClosedXML.Excel; - using System; - using System.Collections.Generic; - using System.Text; + public enum RowStatus { NotYetProcessed, Skipped, - CompetencyGroupInserted, CompetencyGroupAndCompetencyInserted, - CompetencyInserted + CompetencyInserted, + CompetencyUpdated, + CompetencyGroupInserted, + CompetencyGroupUpdated, + CompetencyGroupAndCompetencyUpdated, + InvalidAlwaysShowDescription, + InvalidId } - public class CompetencyTableRow + public class CompetencyTableRow : BulkCompetency { public CompetencyTableRow(IXLTable table, IXLRangeRow row) { string? FindFieldValue(string name) { - var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name).ColumnNumber(); + var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name.ToLower()).ColumnNumber(); return row.Cell(colNumber).GetValue(); } RowNumber = row.RowNumber(); - CompetencyGroupName = FindFieldValue("competency group"); - CompetencyName = FindFieldValue("competency name"); - CompetencyDescription = FindFieldValue("competency description"); + ID = row.Cell(1).GetValue(); + CompetencyGroup = row.Cell(2).GetValue(); + GroupDescription = row.Cell(3).GetValue(); + Competency = row.Cell(4).GetValue(); + CompetencyDescription = row.Cell(5).GetValue(); + AlwaysShowDescriptionRaw = FindFieldValue("AlwaysShowDescription"); + AlwaysShowDescription = bool.TryParse(AlwaysShowDescriptionRaw, out var hasPrn) ? hasPrn : (bool?)null; + FlagsCsv = FindFieldValue("FlagsCSV"); RowStatus = RowStatus.NotYetProcessed; } public int RowNumber { get; set; } - public string? CompetencyGroupName { get; set; } - public string? CompetencyName { get; set; } - public string? CompetencyDescription { get; set; } + public int CompetencyOrderNumber { get; set; } + public string? AlwaysShowDescriptionRaw { get; set; } public ImportCompetenciesResult.ErrorReason? Error { get; set; } public RowStatus RowStatus { get; set; } + public bool Reordered { get; set; } = false; public bool Validate() { - if (string.IsNullOrEmpty(CompetencyName)) + if (string.IsNullOrEmpty(Competency)) { Error = ImportCompetenciesResult.ErrorReason.MissingCompetencyName; } - else if (CompetencyGroupName?.Length > 255) + else if (CompetencyGroup?.Length > 255) { Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName; } - else if (CompetencyName.Length > 500) + else if (Competency.Length > 500) { Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName; } + else if (!string.IsNullOrWhiteSpace(AlwaysShowDescriptionRaw) && !bool.TryParse(AlwaysShowDescriptionRaw, out _)) + { + Error = ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription; + } + else if (RowStatus == RowStatus.InvalidId) + { + Error = ImportCompetenciesResult.ErrorReason.InvalidId; + } return !Error.HasValue; } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 7f78d6f883..5ca4536119 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -8,10 +8,11 @@ public class ImportCompetenciesResult { public enum ErrorReason { + InvalidId, MissingCompetencyName, - TooLongCompetencyGroupName, TooLongCompetencyName, - AlreadyExists + TooLongCompetencyGroupName, + InvalidAlwaysShowDescription } public ImportCompetenciesResult() { } @@ -20,16 +21,39 @@ IReadOnlyCollection competencyTableRows ) { ProcessedCount = competencyTableRows.Count; - CompetenciesInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - CompetencyGroupsInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); + CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated); + GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped && dr.Reordered == false); + CompetencyReorderedCount = competencyTableRows.Count(dr => dr.Reordered == true); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); + FlagCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv)) + .SelectMany(static row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Count(); + DistinctFlagsCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv)) + .SelectMany(row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Select(flag => flag.Trim()) + .Distinct() + .Count(); + CompetencyGroupCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(static row => row.CompetencyGroup) + .Distinct() + .Count(); } public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; } public int ProcessedCount { get; set; } - public int CompetenciesInsertedCount { get; set; } - public int CompetencyGroupsInsertedCount { get; set; } + public int CompetencyAddedCount { get; set; } + public int CompetencyUpdatedCount { get; set; } + public int CompetencyReorderedCount { get; set; } + public int GroupAddedCount { get; set; } + public int GroupUpdatedCount { get; set; } public int SkippedCount { get; set; } + public int FlagCount { get; set; } + public int DistinctFlagsCount { get; set; } + public int CompetencyGroupCount { get; set; } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs index beeeb66bdb..ef0e2c3224 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs @@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging; using NUnit.Framework; using GDS.MultiPageFormData; + using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; public partial class FrameworkControllerTests { @@ -28,6 +30,8 @@ public partial class FrameworkControllerTests private ILearningHubApiClient learningHubApiClient = null!; private ISearchSortFilterPaginateService searchSortFilterPaginateService = null!; private IMultiPageFormService multiPageFormService = null!; + private IClockUtility clockUtility = null!; + private IWebHostEnvironment webHostEnvironment = null!; [SetUp] public void SetUp() @@ -42,7 +46,8 @@ public void SetUp() learningHubApiClient = A.Fake(); searchSortFilterPaginateService = A.Fake(); multiPageFormService = A.Fake(); - + clockUtility = A.Fake(); + webHostEnvironment = A.Fake(); A.CallTo(() => config["CurrentSystemBaseUrl"]).Returns(BaseUrl); var user = new ClaimsPrincipal( @@ -65,7 +70,9 @@ public void SetUp() competencyLearningResourcesService, learningHubApiClient, searchSortFilterPaginateService, - multiPageFormService + multiPageFormService, + clockUtility, + webHostEnvironment ) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = user } }, diff --git a/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs b/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs index 4bf31af462..057f143bc6 100644 --- a/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs +++ b/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs @@ -22,6 +22,8 @@ using NUnit.Framework; using GDS.MultiPageFormData; using GDS.MultiPageFormData.Enums; + using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; public class RedirectToErrorEmptySessionDataTests { @@ -41,7 +43,9 @@ public void Setup() A.Fake(), A.Fake(), A.Fake(), - A.Fake() + A.Fake(), + A.Fake(), + A.Fake() ) .WithDefaultContext().WithMockTempData(); diff --git a/DigitalLearningSolutions.Web/Controllers/FindYourCentreController.cs b/DigitalLearningSolutions.Web/Controllers/FindYourCentreController.cs index 6ae7adab72..51cdb69978 100644 --- a/DigitalLearningSolutions.Web/Controllers/FindYourCentreController.cs +++ b/DigitalLearningSolutions.Web/Controllers/FindYourCentreController.cs @@ -57,25 +57,27 @@ public async Task Index( return View("Index", model); } - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - FindCentreFilterCookieName - ); - var centreSummaries = centresService.GetAllCentreSummariesForFindCentre(); var regions = regionService.GetRegionsAlphabetical(); var availableFilters = FindYourCentreViewModelFilterOptions .GetFindCentreFilterModels(regions).ToList(); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + FindCentreFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( new SearchOptions(searchString, searchMatchCutoff: 90), null, new FilterOptions( - existingFilterString, + filterString, availableFilters ), new PaginationOptions(page, itemsPerPage) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs index 328082ec28..0d26aee6a3 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs @@ -71,7 +71,7 @@ public IActionResult AddEditFrameworkCompetencyGroup(int frameworkId, Competency (competencyGroupBase.Description), adminId); return new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId }) + "#fcgroup-" + frameworkCompetencyGroupId.ToString()); } - var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId); + var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId, frameworkId); if (newCompetencyGroupId > 0) { var newFrameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminId); diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs index fd2b21bced..c173233f6c 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs @@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using GDS.MultiPageFormData; + using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; [Authorize(Policy = CustomPolicies.UserFrameworksAdminOnly)] [SetDlsSubApplication(nameof(DlsSubApplication.Frameworks))] @@ -25,7 +27,8 @@ public partial class FrameworksController : Controller private readonly ILearningHubApiClient learningHubApiClient; private readonly ISearchSortFilterPaginateService searchSortFilterPaginateService; private readonly IMultiPageFormService multiPageFormService; - + private readonly IClockUtility clockUtility; + private readonly IWebHostEnvironment webHostEnvironment; public FrameworksController( IFrameworkService frameworkService, ICommonService commonService, @@ -35,7 +38,9 @@ public FrameworksController( ICompetencyLearningResourcesService competencyLearningResourcesService, ILearningHubApiClient learningHubApiClient, ISearchSortFilterPaginateService searchSortFilterPaginateService, - IMultiPageFormService multiPageFormService + IMultiPageFormService multiPageFormService, + IClockUtility clockUtility, + IWebHostEnvironment webHostEnvironment ) { this.frameworkService = frameworkService; @@ -47,6 +52,8 @@ IMultiPageFormService multiPageFormService this.learningHubApiClient = learningHubApiClient; this.searchSortFilterPaginateService = searchSortFilterPaginateService; this.multiPageFormService = multiPageFormService; + this.clockUtility = clockUtility; + this.webHostEnvironment = webHostEnvironment; } private int? GetCentreId() diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 85b6da73fa..846eeca19a 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,44 +1,295 @@ -using DigitalLearningSolutions.Data.Exceptions; -using DigitalLearningSolutions.Web.ViewModels.Frameworks; +using ClosedXML.Excel; +using DigitalLearningSolutions.Data.Exceptions; +using DigitalLearningSolutions.Web.Helpers; +using DigitalLearningSolutions.Web.Models; +using DigitalLearningSolutions.Web.Services; +using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; +using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace DigitalLearningSolutions.Web.Controllers.FrameworksController { public partial class FrameworksController { [Route("/Framework/{frameworkId}/{tabname}/Import")] - public IActionResult ImportCompetencies(int frameworkId, string tabname) + public IActionResult ImportCompetencies(int frameworkId, string tabname, bool isNotBlank) { var adminId = GetAdminId(); + var framework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminId); var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId); if (userRole < 2) return StatusCode(403); - var model = new ImportCompetenciesViewModel() - { - FrameworkId = frameworkId - }; - return View("Developer/ImportCompetencies", model); + var model = new ImportCompetenciesViewModel(framework, isNotBlank); + + return View("Developer/Import/Index", model); + } + public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption, string vocabulary) + { + string fileName = DownloadOption == 2 ? $"DLS {FrameworkVocabularyHelper.VocabularyPlural(vocabulary)} for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : $"DLS {FrameworkVocabularyHelper.VocabularyPlural(vocabulary)} for Bulk Upload.xlsx"; + var content = importCompetenciesFromFileService.GetCompetencyFileForFramework( + frameworkId, DownloadOption == 2 ? false : true, vocabulary + ); + return File( + content, + FileHelper.GetContentTypeFromFileName(fileName), + fileName + ); } [HttpPost] [Route("/Framework/{frameworkId}/{tabname}/Import")] - public IActionResult StartImport(ImportCompetenciesViewModel model) + [Route("/Framework/{frameworkId}/{tabname}/Import/Uploaded")] + public IActionResult StartImport(ImportCompetenciesFormData model, int frameworkId, string tabname, bool isNotBlank) { if (!ModelState.IsValid) - return View("Developer/ImportCompetencies", model); + return View("Developer/Import/Index", model); try { - var results = importCompetenciesFromFileService.ProcessCompetenciesFromFile( - model.ImportFile!, - GetAdminId(), - model.FrameworkId - ); - var resultsModel = new ImportCompetenciesResultsViewModel(results); - return View("Developer/ImportCompleted", resultsModel); + var adminUserID = User.GetAdminIdKnownNotNull(); + var workbook = new XLWorkbook(model.ImportFile.OpenReadStream()); + if (!workbook.Worksheets.Contains(ImportCompetenciesFromFileService.CompetenciesSheetName)) + { + ModelState.AddModelError("ImportFile", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile); + return View("Developer/Import/Index", model); + } + var competenciesFileName = FileHelper.UploadFile(webHostEnvironment, model.ImportFile); + setupBulkUploadData(frameworkId, adminUserID, competenciesFileName, tabname, isNotBlank); + + return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId, tabname }); + } + catch (DocumentFormat.OpenXml.Packaging.OpenXmlPackageException) + { + ModelState.AddModelError("DelegatesFile", "The Excel file has at least one cell containing an invalid hyperlink or email address."); + return View("Index", model); } catch (InvalidHeadersException) { - return View("Developer/ImportFailed"); + return View("Developer/Import/ImportFailed"); + } + } + [Route("/Framework/{frameworkId}/{tabname}/Import/Uploaded")] + public IActionResult ImportCompleted() + { + var data = GetBulkUploadData(); + var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\"); + var filePath = Path.Combine(uploadDir, data.CompetenciesFileName); + var workbook = new XLWorkbook(filePath); + try + { + var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook, data.FrameworkVocubulary, data.FrameworkId); + var resultsModel = new ImportCompetenciesPreProcessViewModel(results, data) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; + data.CompetenciesToProcessCount = resultsModel.ToProcessCount; + data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; + data.CompetenciesToUpdateCount = resultsModel.ToUpdateOrSkipCount; + data.CompetenciesToReorderCount = results.CompetencyReorderedCount; + setBulkUploadData(data); + return View("Developer/Import/ImportCompleted", resultsModel); + } + catch (InvalidHeadersException) + { + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + return View("Developer/Import/ImportFailed"); + } + } + [Route("/Framework/{frameworkId}/{tabname}/Import/Ordering")] + public IActionResult ApplyCompetencyOrdering() + { + var data = GetBulkUploadData(); + if (data.CompetenciesToReorderCount > 0) + { + var model = new ApplyCompetencyOrderingViewModel(data.FrameworkId, data.FrameworkName, data.FrameworkVocubulary, data.CompetenciesToReorderCount, data.ReorderCompetenciesOption); + return View("Developer/Import/ApplyCompetencyOrdering", model); + } + return RedirectToAction("AddAssessmentQuestions", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/Ordering")] + public IActionResult ApplyCompetencyOrdering(int reorderCompetenciesOption) + { + var data = GetBulkUploadData(); + + if (data.ReorderCompetenciesOption != reorderCompetenciesOption) + { + data.ReorderCompetenciesOption = reorderCompetenciesOption; + setBulkUploadData(data); + } + return RedirectToAction("AddAssessmentQuestions", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] + public IActionResult AddAssessmentQuestions() + { + var data = GetBulkUploadData(); + var adminId = GetAdminId(); + var defaultQuestions = frameworkService.GetFrameworkDefaultQuestionsById(data.FrameworkId, adminId); + if (!data.DefaultQuestionIDs.Any() && defaultQuestions.Any() && data.AddDefaultAssessmentQuestions == true) + { + var defaultQuestionsList = new List(); + foreach (var question in defaultQuestions) + { + defaultQuestionsList.Add(question.ID); + } + data.DefaultQuestionIDs = defaultQuestionsList; + setBulkUploadData(data); + } + var questionList = frameworkService.GetAssessmentQuestions(data.FrameworkId, adminId).ToList(); + var questionSelectList = new SelectList(questionList, "ID", "Label"); + var model = new AddAssessmentQuestionsViewModel + ( + data.FrameworkId, + data.FrameworkName, + data.FrameworkVocubulary, + data.PublishStatusID, + data.CompetenciesToAddCount, + data.CompetenciesToUpdateCount, + data.CompetenciesToReorderCount, + defaultQuestions, + questionSelectList + ); + model.AddDefaultAssessmentQuestions = data.AddDefaultAssessmentQuestions; + model.AddCustomAssessmentQuestion = data.AddCustomAssessmentQuestion; + model.DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; + model.CustomAssessmentQuestionID = data.CustomAssessmentQuestionID; + return View("Developer/Import/AddAssessmentQuestions", model); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] + public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model) + { + var data = GetBulkUploadData(); + data.AddDefaultAssessmentQuestions = model.AddDefaultAssessmentQuestions; + if (model.AddDefaultAssessmentQuestions) + { + data.DefaultQuestionIDs = model.DefaultAssessmentQuestionIDs; } + else + { + data.DefaultQuestionIDs = []; + } + data.AddCustomAssessmentQuestion = model.AddCustomAssessmentQuestion; + if (model.AddCustomAssessmentQuestion) + { + data.CustomAssessmentQuestionID = model.CustomAssessmentQuestionID; + } + else + { + data.CustomAssessmentQuestionID = null; + } + if (data.CompetenciesToUpdateCount > 0) + { + data.AddAssessmentQuestionsOption = 2; + setBulkUploadData(data); + return RedirectToAction("AddQuestionsToWhichCompetencies", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + else + { + data.AddAssessmentQuestionsOption = 1; + setBulkUploadData(data); + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + } + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions/Competencies")] + public IActionResult AddQuestionsToWhichCompetencies() + { + var data = GetBulkUploadData(); + if (data.DefaultQuestionIDs.Count ==0 && data.CustomAssessmentQuestionID == null) + { + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + var model = new AddQuestionsToWhichCompetenciesViewModel + ( + data.FrameworkId, + data.FrameworkName, + data.FrameworkVocubulary, + data.DefaultQuestionIDs, + data.CustomAssessmentQuestionID, + data.AddAssessmentQuestionsOption, + data.CompetenciesToProcessCount, + data.CompetenciesToAddCount, + data.CompetenciesToUpdateCount + ); + return View("Developer/Import/AddQuestionsToWhichCompetencies", model); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions/Competencies")] + public IActionResult AddQuestionsToWhichCompetencies(int AddAssessmentQuestionsOption) + { + var data = GetBulkUploadData(); + data.AddAssessmentQuestionsOption = AddAssessmentQuestionsOption; + setBulkUploadData(data); + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [Route("/Framework/{frameworkId}/{tabname}/Import/Summary")] + public IActionResult ImportSummary() + { + var data = GetBulkUploadData(); + var model = new ImportSummaryViewModel(data); + return View("Developer/Import/ImportSummary", model); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/Summary")] + public IActionResult ImportSummarySubmit() + { + var data = GetBulkUploadData(); + var adminId = GetAdminId(); + var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\"); + var filePath = Path.Combine(uploadDir, data.CompetenciesFileName); + var workbook = new XLWorkbook(filePath); + var results = importCompetenciesFromFileService.ProcessCompetenciesFromFile(workbook, adminId, data.FrameworkId, data.FrameworkVocubulary, data.ReorderCompetenciesOption, data.AddAssessmentQuestionsOption, data.AddCustomAssessmentQuestion ? (int)data.CustomAssessmentQuestionID : 0, data.AddDefaultAssessmentQuestions ? data.DefaultQuestionIDs : []); + data.ImportCompetenciesResult = results; + //TO DO apply ordering changes if required: + if (data.ReorderCompetenciesOption == 2 && data.CompetenciesToReorderCount > 0) + { + + } + setBulkUploadData(data); + return RedirectToAction("UploadResults", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [Route("/Framework/{frameworkId}/{tabname}/Import/Results")] + public IActionResult UploadResults() + { + var data = GetBulkUploadData(); + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + TempData.Clear(); + var model = new ImportCompetenciesResultsViewModel(data.ImportCompetenciesResult, data.FrameworkId, data.FrameworkName, data.FrameworkVocubulary); + return View("Developer/Import/UploadResults", model); + } + [Route("CancelImport")] + public IActionResult CancelImport() + { + var data = GetBulkUploadData(); + var frameworkId = data.FrameworkId; + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + TempData.Clear(); + return RedirectToAction("ViewFramework", new { frameworkId, tabname = "Structure" }); + } + private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) + { + TempData.Clear(); + multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), TempData); + var framework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminUserID); + var today = clockUtility.UtcToday; + var bulkUploadData = new BulkCompetenciesData(framework, adminUserID, competenciessFileName, tabName, isNotBlank); + setBulkUploadData(bulkUploadData); + } + private void setBulkUploadData(BulkCompetenciesData bulkUploadData) + { + multiPageFormService.SetMultiPageFormData( + bulkUploadData, + MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), + TempData + ); + } + + private BulkCompetenciesData GetBulkUploadData() + { + var data = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), + TempData + ).GetAwaiter().GetResult(); + return data; } } } diff --git a/DigitalLearningSolutions.Web/Controllers/LearningContentController.cs b/DigitalLearningSolutions.Web/Controllers/LearningContentController.cs index 4ac6dd3e8b..cbbaf62da4 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningContentController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningContentController.cs @@ -53,14 +53,6 @@ public IActionResult Index( } sortBy ??= DefaultSortByOptions.Name.PropertyName; - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - BrandCoursesFilterCookieName - ); - var tutorials = tutorialService.GetPublicTutorialSummariesForBrand(brandId); var applications = courseService.GetApplicationsThatHaveSectionsByBrandId(brandId).ToList(); @@ -69,11 +61,21 @@ public IActionResult Index( var availableFilters = LearningContentViewModelFilterOptions .GetFilterOptions(categories, topics).ToList(); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + BrandCoursesFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( null, new SortOptions(sortBy, sortDirection), new FilterOptions( - existingFilterString, + filterString, availableFilters ), new PaginationOptions(page) diff --git a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs index b7bdc2ef01..ce590a4d25 100644 --- a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs +++ b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs @@ -515,7 +515,7 @@ int resultId ) { var model = ReviewCompetencySelfAsessmentData(supervisorDelegateId, candidateAssessmentId, resultId); - + if (model == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); return View("ReviewCompetencySelfAsessment", model); } @@ -587,8 +587,10 @@ int resultId candidateAssessmentId, adminId ); + var loggedInAdminUser = userService.GetAdminUserById(adminId); var delegateSelfAssessment = - supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId); + supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId, loggedInAdminUser.CategoryId); + if (delegateSelfAssessment == null) return null; var assessmentQuestion = GetLevelDescriptorsForAssessmentQuestion(competency.AssessmentQuestions.First()); competency.CompetencyFlags = frameworkService.GetSelectedCompetencyFlagsByCompetecyId(competency.Id); var model = new ReviewCompetencySelfAsessmentViewModel() @@ -610,10 +612,12 @@ int resultId public IActionResult VerifyMultipleResults(int supervisorDelegateId, int candidateAssessmentId) { var adminId = GetAdminId(); + var loggedInAdminUser = userService.GetAdminUserById(adminId); var superviseDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); var delegateSelfAssessment = - supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId); + supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId, loggedInAdminUser.CategoryId); + if (delegateSelfAssessment == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); var reviewedCompetencies = PopulateCompetencyLevelDescriptors( selfAssessmentService.GetCandidateAssessmentResultsForReviewById(candidateAssessmentId, adminId) .ToList() @@ -638,10 +642,11 @@ List resultChecked if (resultChecked.Count == 0) { var adminId = GetAdminId(); + var loggedInAdminUser = userService.GetAdminUserById(adminId); var superviseDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); var delegateSelfAssessment = - supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId); + supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId, loggedInAdminUser.CategoryId); var reviewedCompetencies = PopulateCompetencyLevelDescriptors( selfAssessmentService.GetCandidateAssessmentResultsForReviewById(candidateAssessmentId, adminId) .ToList() @@ -1260,10 +1265,12 @@ SignOffProfileAssessmentViewModel model public IActionResult SignOffHistory(int supervisorDelegateId, int candidateAssessmentId) { var adminId = GetAdminId(); + var loggedInAdminUser = userService.GetAdminUserById(adminId); var superviseDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); var delegateSelfAssessment = - supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId); + supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId, loggedInAdminUser.CategoryId); + if (delegateSelfAssessment == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); var model = new SignOffHistoryViewModel() { DelegateSelfAssessment = delegateSelfAssessment, diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Administrator/AdministratorController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Administrator/AdministratorController.cs index 77ce1e4971..317eec01ad 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Administrator/AdministratorController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Administrator/AdministratorController.cs @@ -61,13 +61,6 @@ public IActionResult Index( int? itemsPerPage = null ) { - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - AdminFilterCookieName - ); searchString = searchString == null ? null : searchString.Trim(); var centreId = User.GetCentreIdKnownNotNull(); var adminsAtCentre = userService.GetAdminsByCentreId(centreId); @@ -77,10 +70,20 @@ public IActionResult Index( var availableFilters = AdministratorsViewModelFilterOptions.GetAllAdministratorsFilterModels(categories); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + AdminFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( new SearchOptions(searchString), new SortOptions(GenericSortingHelper.DefaultSortOption, GenericSortingHelper.Ascending), - new FilterOptions(existingFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), new PaginationOptions(page, itemsPerPage) ); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseSetupController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseSetupController.cs index 59597441e5..015a232f1c 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseSetupController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/CourseSetup/CourseSetupController.cs @@ -92,47 +92,33 @@ public IActionResult Index( sortBy ??= DefaultSortByOptions.Name.PropertyName; sortDirection ??= GenericSortingHelper.Ascending; - existingFilterString = FilteringHelper.GetFilterString( + var centreId = User.GetCentreIdKnownNotNull(); + var categoryId = User.GetAdminCategoryId(); + var courseCategoryName = this.activityService.GetCourseCategoryNameForActivityFilter(categoryId); + var Categories = courseCategoriesService.GetCategoriesForCentreAndCentrallyManagedCourses(centreId).Select(c => c.CategoryName); + var Topics = courseTopicsService.GetCourseTopicsAvailableAtCentre(centreId).Select(c => c.CourseTopic); + + var availableFilters = CourseStatisticsViewModelFilterOptions + .GetFilterOptions(categoryId.HasValue ? new string[] { } : Categories, Topics).ToList(); + + var filterString = FilteringHelper.GetFilterString( existingFilterString, newFilterToAdd, clearFilters, Request, CourseFilterCookieName, - CourseStatusFilterOptions.IsActive.FilterValue + CourseStatusFilterOptions.IsActive.FilterValue, + availableFilters ); - var centreId = User.GetCentreIdKnownNotNull(); - var categoryId = User.GetAdminCategoryId(); - var courseCategoryName = this.activityService.GetCourseCategoryNameForActivityFilter(categoryId); - var Categories = courseCategoriesService.GetCategoriesForCentreAndCentrallyManagedCourses(centreId).Select(c => c.CategoryName); - var Topics = courseTopicsService.GetCourseTopicsAvailableAtCentre(centreId).Select(c => c.CourseTopic); - int offSet = ((page - 1) * itemsPerPage) ?? 0; string isActive, categoryName, courseTopic, hasAdminFields; isActive = categoryName = courseTopic = hasAdminFields = "Any"; bool? hideInLearnerPortal = null; - if (!string.IsNullOrEmpty(existingFilterString)) + if (!string.IsNullOrEmpty(filterString)) { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - - if (!string.IsNullOrEmpty(newFilterToAdd)) - { - var filterHeader = newFilterToAdd.Split(FilteringHelper.Separator)[0]; - var dupfilters = selectedFilters.Where(x => x.Contains(filterHeader)); - if (dupfilters.Count() > 1) - { - foreach (var filter in selectedFilters) - { - if (filter.Contains(filterHeader)) - { - selectedFilters.Remove(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - break; - } - } - } - } + var selectedFilters = filterString.Split(FilteringHelper.FilterSeparator).ToList(); if (selectedFilters.Count > 0) { @@ -173,14 +159,11 @@ public IActionResult Index( isActive, categoryName, courseTopic, hasAdminFields); } - var availableFilters = CourseStatisticsViewModelFilterOptions - .GetFilterOptions(categoryId.HasValue ? new string[] { } : Categories, Topics).ToList(); - var result = paginateService.Paginate( courses, resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), searchString, sortBy, sortDirection diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs index caf4cdf80a..f806ab1991 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs @@ -95,34 +95,31 @@ public IActionResult Index( sortBy ??= DefaultSortByOptions.Name.PropertyName; sortDirection ??= GenericSortingHelper.Ascending; - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - filterCookieName, - CourseDelegateAccountStatusFilterOptions.Active.FilterValue - ); - - if (isCourseDelegate) - { - if (TempData["actDelCustomisationId"] != null && TempData["actDelCustomisationId"].ToString() != customisationId.ToString() - && existingFilterString != null && existingFilterString.Contains("Answer")) - { - var availableCourseFilters = CourseDelegateViewModelFilterOptions.GetAllCourseDelegatesFilterViewModels(courseAdminFieldsService.GetCourseAdminFieldsForCourse(customisationId.Value).AdminFields); - existingFilterString = FilterHelper.RemoveNonExistingPromptFilters(availableCourseFilters, existingFilterString); - } - } - else + + var availableFilters = isCourseDelegate + ? CourseDelegateViewModelFilterOptions.GetAllCourseDelegatesFilterViewModels(courseAdminFieldsService.GetCourseAdminFieldsForCourse(customisationId.Value).AdminFields) + : SelfAssessmentDelegateViewModelFilterOptions.GetAllSelfAssessmentDelegatesFilterViewModels(); + + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + filterCookieName, + CourseDelegateAccountStatusFilterOptions.Active.FilterValue, + availableFilters + ); + + if (!isCourseDelegate) { isUnsupervisedSelfAssessment = selfAssessmentService.IsUnsupervisedSelfAssessment((int)selfAssessmentId); - if (existingFilterString != null) + if (filterString != null) { var existingfilterList = isUnsupervisedSelfAssessment ? - existingFilterString!.Split(FilteringHelper.FilterSeparator).Where(filter => !filter.Contains("SignedOff")).ToList() : - existingFilterString!.Split(FilteringHelper.FilterSeparator).Where(filter => !filter.Contains("SubmittedDate")).ToList(); + filterString!.Split(FilteringHelper.FilterSeparator).Where(filter => !filter.Contains("SignedOff")).ToList() : + filterString!.Split(FilteringHelper.FilterSeparator).Where(filter => !filter.Contains("SubmittedDate")).ToList(); - existingFilterString = existingfilterList.Any() ? string.Join(FilteringHelper.FilterSeparator, existingfilterList) : null; + filterString = existingfilterList.Any() ? string.Join(FilteringHelper.FilterSeparator, existingfilterList) : null; } } @@ -138,10 +135,10 @@ public IActionResult Index( string? answer1, answer2, answer3; answer1 = answer2 = answer3 = null; - if (!string.IsNullOrEmpty(existingFilterString)) + if (!string.IsNullOrEmpty(filterString)) { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - existingFilterString = FilteringHelper.RemoveDuplicateFilters(newFilterToAdd, existingFilterString); + var selectedFilters = filterString.Split(FilteringHelper.FilterSeparator).ToList(); + if (selectedFilters.Count > 0) { foreach (var filter in selectedFilters) @@ -250,24 +247,17 @@ public IActionResult Index( } } - var availableFilters = isCourseDelegate - ? CourseDelegateViewModelFilterOptions.GetAllCourseDelegatesFilterViewModels(courseDelegatesData.CourseAdminFields) - : SelfAssessmentDelegateViewModelFilterOptions.GetAllSelfAssessmentDelegatesFilterViewModels(); - var activityName = isCourseDelegate ? courseService.GetCourseNameAndApplication((int)customisationId).CourseName : selfAssessmentService.GetSelfAssessmentNameById((int)selfAssessmentId); - if (!string.IsNullOrEmpty(existingFilterString)) - { - existingFilterString = FilteringHelper.GetValidFilters(existingFilterString, newFilterToAdd, availableFilters, Request, filterCookieName); - } + if (isCourseDelegate) { var result = paginateService.Paginate( courseDelegatesData.Delegates, (int)resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters, CourseDelegateAccountStatusFilterOptions.Active.FilterValue), + new FilterOptions(filterString, availableFilters, CourseDelegateAccountStatusFilterOptions.Active.FilterValue), searchString, sortBy, sortDirection); @@ -285,7 +275,7 @@ public IActionResult Index( selfAssessmentDelegatesData.Delegates, (int)resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters, CourseDelegateAccountStatusFilterOptions.Active.FilterValue), + new FilterOptions(filterString, availableFilters, CourseDelegateAccountStatusFilterOptions.Active.FilterValue), searchString, sortBy, sortDirection); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/AllDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/AllDelegatesController.cs index 9bdedeb4c4..77a1170c99 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/AllDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/AllDelegatesController.cs @@ -80,14 +80,6 @@ public IActionResult Index( sortDirection ??= GenericSortingHelper.Ascending; DelegateFilterCookieName += env.EnvironmentName; - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - DelegateFilterCookieName, - DelegateActiveStatusFilterOptions.IsActive.FilterValue - ); int offSet = ((page - 1) * itemsPerPage) ?? 0; @@ -99,41 +91,24 @@ public IActionResult Index( var promptsWithOptions = customPrompts.Where(customPrompt => customPrompt.Options.Count > 0); var availableFilters = AllDelegatesViewModelFilterOptions.GetAllDelegatesFilterViewModels(jobGroups, promptsWithOptions, groups); - if (existingFilterString != null && TempData["allDelegatesCentreId"] != null && - TempData["allDelegatesCentreId"].ToString() != User.GetCentreId().ToString()) - { - if (existingFilterString.Contains("Answer")) - existingFilterString = FilterHelper.RemoveNonExistingPromptFilters(availableFilters, existingFilterString); - if (existingFilterString != null && existingFilterString.Contains("DelegateGroup")) - existingFilterString = FilterHelper.RemoveNonExistingFilterOptions(availableFilters, existingFilterString); - } + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + DelegateFilterCookieName, + DelegateActiveStatusFilterOptions.IsActive.FilterValue, + availableFilters + ); string isActive, isPasswordSet, isAdmin, isUnclaimed, isEmailVerified, registrationType, answer1, answer2, answer3, answer4, answer5, answer6; isActive = isPasswordSet = isAdmin = isUnclaimed = isEmailVerified = registrationType = answer1 = answer2 = answer3 = answer4 = answer5 = answer6 = "Any"; int jobGroupId = 0; int? groupId = null; - if (!string.IsNullOrEmpty(existingFilterString)) + if (!string.IsNullOrEmpty(filterString)) { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - - if (!string.IsNullOrEmpty(newFilterToAdd)) - { - var filterHeader = newFilterToAdd.Split(FilteringHelper.Separator)[1]; - var dupfilters = selectedFilters.Where(x => x.Contains(filterHeader)); - if (dupfilters.Count() > 1) - { - foreach (var filter in selectedFilters) - { - if (filter.Contains(filterHeader)) - { - selectedFilters.Remove(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - break; - } - } - } - } + var selectedFilters = filterString.Split(FilteringHelper.FilterSeparator).ToList(); if (selectedFilters.Count > 0) { @@ -167,11 +142,6 @@ public IActionResult Index( if (filter.Contains("DelegateGroupId")) { groupId = Convert.ToInt32(filterValue); - if (!(groups.Any(g => g.Item1 == groupId))) - { - groupId = null; - existingFilterString = FilterHelper.RemoveNonExistingFilterOptions(availableFilters, existingFilterString); - } } if (filter.Contains("Answer1")) @@ -211,7 +181,7 @@ public IActionResult Index( delegates, resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), + new FilterOptions(filterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), searchString, sortBy, sortDirection @@ -229,9 +199,8 @@ public IActionResult Index( model.TotalPages = (int)(resultCount / itemsPerPage) + ((resultCount % itemsPerPage) > 0 ? 1 : 0); model.MatchingSearchResults = resultCount; - Response.UpdateFilterCookie(DelegateFilterCookieName, existingFilterString); + Response.UpdateFilterCookie(DelegateFilterCookieName, filterString); TempData.Remove("delegateRegistered"); - TempData["allDelegatesCentreId"] = User.GetCentreId().ToString(); return View(model); } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateCoursesController.cs index 70040ebb9c..eda47bdb8a 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateCoursesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateCoursesController.cs @@ -4,14 +4,12 @@ using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; - using DigitalLearningSolutions.Data.Models.SelfAssessments; using DigitalLearningSolutions.Web.Attributes; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Helpers.FilterOptions; using DigitalLearningSolutions.Web.Models.Enums; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.DelegateCourses; - using DocumentFormat.OpenXml.Wordprocessing; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.FeatureManagement.Mvc; @@ -67,15 +65,6 @@ public IActionResult Index( sortBy ??= DefaultSortByOptions.Name.PropertyName; sortDirection ??= GenericSortingHelper.Ascending; - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - CourseFilterCookieName, - CourseStatusFilterOptions.IsActive.FilterValue - ); - var centreId = User.GetCentreIdKnownNotNull(); var categoryId = User.GetAdminCategoryId(); var courseCategoryName = this.activityService.GetCourseCategoryNameForActivityFilter(categoryId); @@ -89,33 +78,19 @@ public IActionResult Index( var availableFilters = DelegateCourseStatisticsViewModelFilterOptions .GetFilterOptions(categoryId.HasValue ? new string[] { } : Categories, Topics).ToList(); - if (TempData["DelegateActivitiesCentreId"] != null && TempData["DelegateActivitiesCentreId"].ToString() != User.GetCentreId().ToString() - && existingFilterString != null) - { - existingFilterString = FilterHelper.RemoveNonExistingFilterOptions(availableFilters, existingFilterString); - } + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + CourseFilterCookieName, + CourseStatusFilterOptions.IsActive.FilterValue, + availableFilters + ); - if (!string.IsNullOrEmpty(existingFilterString)) + if (!string.IsNullOrEmpty(filterString)) { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - - if (!string.IsNullOrEmpty(newFilterToAdd)) - { - var filterHeader = newFilterToAdd.Split(FilteringHelper.Separator)[0]; - var dupfilters = selectedFilters.Where(x => x.Contains(filterHeader)); - if (dupfilters.Count() > 1) - { - foreach (var filter in selectedFilters) - { - if (filter.Contains(filterHeader)) - { - selectedFilters.Remove(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - break; - } - } - } - } + var selectedFilters = filterString.Split(FilteringHelper.FilterSeparator).ToList(); if (selectedFilters.Count > 0) { @@ -177,7 +152,7 @@ public IActionResult Index( allItems, resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), + new FilterOptions(filterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), searchString, sortBy, sortDirection @@ -204,7 +179,6 @@ public IActionResult Index( model.TotalPages = (int)(resultCount / itemsPerPage) + ((resultCount % itemsPerPage) > 0 ? 1 : 0); model.MatchingSearchResults = resultCount; Response.UpdateFilterCookie(CourseFilterCookieName, result.FilterString); - TempData["DelegateActivitiesCentreId"] = centreId; return View(model); } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateGroupsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateGroupsController.cs index 84680c9ba7..12f327cbc7 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateGroupsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DelegateGroupsController.cs @@ -69,15 +69,6 @@ public IActionResult Index( sortDirection = GenericSortingHelper.Ascending; } - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - DelegateGroupsFilterCookieName, - null - ); - var centreId = User.GetCentreIdKnownNotNull(); var addedByAdmins = groupsService.GetAdminsForCentreGroups(centreId); @@ -95,37 +86,23 @@ public IActionResult Index( var availableFilters = DelegateGroupsViewModelFilterOptions .GetDelegateGroupFilterModels(addedByAdmins, registrationPrompts).ToList(); - if (TempData["DelegateGroupCentreId"] != null && TempData["DelegateGroupCentreId"].ToString() != User.GetCentreId().ToString() - && existingFilterString != null) - { - existingFilterString = FilterHelper.RemoveNonExistingFilterOptions(availableFilters, existingFilterString); - } + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + DelegateGroupsFilterCookieName, + null, + availableFilters + ); int offSet = ((page - 1) * itemsPerPage) ?? 0; string filterAddedBy = ""; string filterLinkedField = ""; - if (!string.IsNullOrEmpty(existingFilterString)) + if (!string.IsNullOrEmpty(filterString)) { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - - if (!string.IsNullOrEmpty(newFilterToAdd)) - { - var filterHeader = newFilterToAdd.Split(FilteringHelper.Separator)[0]; - var dupfilters = selectedFilters.Where(x => x.Contains(filterHeader)); - if (dupfilters.Count() > 1) - { - foreach (var filter in selectedFilters) - { - if (filter.Contains(filterHeader)) - { - selectedFilters.Remove(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - break; - } - } - } - } + var selectedFilters = filterString.Split(FilteringHelper.FilterSeparator).ToList(); if (selectedFilters.Count > 0) { @@ -174,7 +151,7 @@ public IActionResult Index( groups, resultCount, new PaginationOptions(page, itemsPerPage), - new FilterOptions(existingFilterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), + new FilterOptions(filterString, availableFilters, DelegateActiveStatusFilterOptions.IsActive.FilterValue), searchString, sortBy, sortDirection @@ -191,7 +168,6 @@ public IActionResult Index( model.TotalPages = (int)(resultCount / itemsPerPage) + ((resultCount % itemsPerPage) > 0 ? 1 : 0); model.MatchingSearchResults = resultCount; Response.UpdateFilterCookie(DelegateGroupsFilterCookieName, result.FilterString); - TempData["DelegateGroupCentreId"] = centreId; return View(model); } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EmailDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EmailDelegatesController.cs index a297f7b7d2..74617c2ba4 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EmailDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EmailDelegatesController.cs @@ -62,13 +62,6 @@ public IActionResult Index( bool selectAll = false ) { - var newFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - EmailDelegateFilterCookieName - ); var jobGroups = jobGroupsService.GetJobGroupsAlphabetical(); var customPrompts = promptsService.GetCentreRegistrationPrompts(User.GetCentreIdKnownNotNull()); var delegateUsers = GetDelegateUserCards(); @@ -79,10 +72,20 @@ public IActionResult Index( promptsWithOptions ); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + EmailDelegateFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( null, null, - new FilterOptions(newFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), null ); @@ -115,13 +118,6 @@ public IActionResult Index( if (!ModelState.IsValid) { - var newFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - EmailDelegateFilterCookieName - ); var jobGroups = jobGroupsService.GetJobGroupsAlphabetical(); var customPrompts = promptsService.GetCentreRegistrationPrompts(User.GetCentreIdKnownNotNull()); @@ -131,10 +127,20 @@ public IActionResult Index( promptsWithOptions ); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + EmailDelegateFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( null, null, - new FilterOptions(newFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), null ); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupCoursesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupCoursesController.cs index a1222156db..313731b5a2 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupCoursesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupCoursesController.cs @@ -114,14 +114,6 @@ public IActionResult AddCourseToGroupSelectCourse( int page = 1 ) { - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - GroupAddCourseFilterCookieName - ); - var centreId = User.GetCentreIdKnownNotNull(); var adminCategoryFilter = User.GetAdminCategoryId(); @@ -136,10 +128,20 @@ public IActionResult AddCourseToGroupSelectCourse( ? AddCourseToGroupViewModelFilterOptions.GetAllCategoriesFilters(categories, topics) : AddCourseToGroupViewModelFilterOptions.GetSingleCategoryFilters(courses)).ToList(); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + GroupAddCourseFilterCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( new SearchOptions(searchString), new SortOptions(nameof(CourseAssessmentDetails.CourseName), GenericSortingHelper.Ascending), - new FilterOptions(existingFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), new PaginationOptions(page) ); var result = searchSortFilterPaginateService.SearchFilterSortAndPaginate( diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupDelegatesController.cs index 6e7b2b4ce4..206c2492c7 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/GroupDelegatesController.cs @@ -78,14 +78,6 @@ public IActionResult SelectDelegate( int page = 1 ) { - existingFilterString = FilteringHelper.GetFilterString( - existingFilterString, - newFilterToAdd, - clearFilters, - Request, - AddGroupDelegateCookieName - ); - var centreId = User.GetCentreIdKnownNotNull(); var jobGroups = jobGroupsService.GetJobGroupsAlphabetical().ToList(); var customPrompts = promptsService.GetCentreRegistrationPrompts(centreId).ToList(); @@ -97,13 +89,23 @@ public IActionResult SelectDelegate( promptsWithOptions ); + var filterString = FilteringHelper.GetFilterString( + existingFilterString, + newFilterToAdd, + clearFilters, + Request, + AddGroupDelegateCookieName, + null, + availableFilters + ); + var searchSortPaginationOptions = new SearchSortFilterAndPaginateOptions( new SearchOptions(searchString), new SortOptions( DefaultSortByOptions.Name.PropertyName, GenericSortingHelper.Ascending ), - new FilterOptions(existingFilterString, availableFilters), + new FilterOptions(filterString, availableFilters), new PaginationOptions(page) ); var result = searchSortFilterPaginateService.SearchFilterSortAndPaginate( diff --git a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs index 9cd480a9dd..531a528e98 100644 --- a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs +++ b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs @@ -36,6 +36,7 @@ public static class CommonValidationErrorMessages public const string CentreNameAlreadyExist = "The centre name you have entered already exists, please enter a different centre name"; public const string MaxBulkUploadRowsLimit = "File must contain no more than {0} rows"; public const string InvalidBulkUploadExcelFile = "The uploaded file must contain a \"DelegatesBulkUpload\" worksheet. Use the \"Download delegates\" button to generate a template."; + public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download template\" button to generate a template."; public const string ReportFilterReturnsTooManyRows = "The report frequency is too high for the date range. Choose a lower report frequency (or shorten the date range)"; } } diff --git a/DigitalLearningSolutions.Web/Helpers/FilterHelper.cs b/DigitalLearningSolutions.Web/Helpers/FilterHelper.cs deleted file mode 100644 index 7a472f4e51..0000000000 --- a/DigitalLearningSolutions.Web/Helpers/FilterHelper.cs +++ /dev/null @@ -1,115 +0,0 @@ -using DigitalLearningSolutions.Data.Helpers; -using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; -using System.Collections.Generic; -using System.Linq; - -namespace DigitalLearningSolutions.Web.Helpers -{ - public static class FilterHelper - { - public static string? RemoveNonExistingPromptFilters(List availableFilters, string existingFilterString) - { - if (existingFilterString != null && existingFilterString.Contains("Answer")) - { - if (availableFilters.Where(x => x.FilterGroupKey == "prompts").ToList().Any()) - { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - var existingPromptFilters = existingFilterString!.Split(FilteringHelper.FilterSeparator).Where(filter => filter.Contains("Answer")).ToList(); - - foreach (var existingPromptFilter in existingPromptFilters) - { - bool isFound = false; - var splitFilter = existingPromptFilter.Split(FilteringHelper.Separator); - var filterHeaderArr = splitFilter[0].SkipWhile(c => c != '(').Skip(1).TakeWhile(c => c != ')').ToArray(); - var filterHeader = string.Join("", filterHeaderArr); //prompt header - var filterOptionText = splitFilter[2] == FilteringHelper.EmptyValue ? - "No option selected" : splitFilter[2]; //prompt option text - var promptDbText = splitFilter[1]; //filter db text eg. Answer1 - - var availableFilterOptions = availableFilters.Where(x => x.FilterGroupKey == "prompts" && x.FilterName == filterHeader) - .Select(o => o.FilterOptions).ToList(); - - foreach (var filterOption in availableFilterOptions) - { - if (filterOption.Any(x => x.DisplayText.Contains(filterOptionText))) - { - var filter = filterOption.Where(x => x.DisplayText.Contains(filterOptionText)).ToList().Select(x => x.FilterValue).FirstOrDefault(); - if (!filter.Contains(promptDbText)) - { //when prompt filter header and selected option match but db coulum (eg. Answer1) does not match - //remove from existing filter and add from available filter - selectedFilters.Remove(existingPromptFilter); - selectedFilters.Add(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - } - isFound = true; break; - } - } - if (!isFound) - { - selectedFilters.Remove(existingPromptFilter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - if (existingFilterString == "") existingFilterString = null; - } - } - } - else - { - var filtersExceptPrompts = existingFilterString!.Split(FilteringHelper.FilterSeparator).Where(filter => !filter.Contains("Answer")).ToList(); - existingFilterString = filtersExceptPrompts.Any() ? string.Join(FilteringHelper.FilterSeparator, filtersExceptPrompts) : null; - } - } - return existingFilterString; - } - - public static string? RemoveNonExistingFilterOptions(List availableFilters, string existingFilterString) - { - var selectedFilters = existingFilterString.Split(FilteringHelper.FilterSeparator).ToList(); - string[] filterGroups = { "LinkedToField", "AddedByAdminId", "CourseTopic", "CategoryName", "DelegateGroup" }; - foreach (var filterGroup in filterGroups) - { - var existingFilters = existingFilterString!.Split(FilteringHelper.FilterSeparator).Where(filter => filter.Contains(filterGroup)).ToList(); - - foreach (var existingFilter in existingFilters) - { - bool isFound = false; - var splitFilter = existingFilter.Split(FilteringHelper.Separator); - var filterHeader = splitFilter[1]; - var filterOptionText = splitFilter[2]; - var availableFilterOptions = availableFilters.Where(x => x.FilterProperty == filterGroup).Select(o => o.FilterOptions).ToList(); - foreach (var availableFilterOption in availableFilterOptions) - { - if (filterGroup == "LinkedToField") - { - if (availableFilterOption.Any(x => x.FilterValue.Contains(filterHeader))) - { - var filter = availableFilterOption.Where(x => x.FilterValue.Contains(filterHeader)).ToList().Select(x => x.FilterValue).FirstOrDefault(); - if (!filter.Contains(filterOptionText)) - { - selectedFilters.Remove(existingFilter); - selectedFilters.Add(filter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - } - isFound = true; break; - } - } - else - { - if (availableFilterOption.Any(x => x.FilterValue.Contains(filterOptionText))) - { - isFound = true; break; - } - } - } - - if (!isFound) - { - selectedFilters.Remove(existingFilter); - existingFilterString = string.Join(FilteringHelper.FilterSeparator, selectedFilters); - } - } - } - if (existingFilterString == "") existingFilterString = null; - return existingFilterString; - } - } -} diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs new file mode 100644 index 0000000000..344c674a89 --- /dev/null +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -0,0 +1,48 @@ +using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace DigitalLearningSolutions.Web.Models +{ + public class BulkCompetenciesData + { + public BulkCompetenciesData() { } + public BulkCompetenciesData(DetailFramework framework, int adminUserId, string competenciesFileName, string tabName, bool isNotBlank) + { + FrameworkId = framework.ID; + FrameworkName = framework.FrameworkName; + PublishStatusID = framework.PublishStatusID; + FrameworkVocubulary = framework.FrameworkConfig; + AdminUserId = adminUserId; + CompetenciesFileName = competenciesFileName; + TabName = tabName; + IsNotBlank = isNotBlank; + } + public int FrameworkId { get; set; } + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocubulary { get; set; } + public string TabName { get; set; } + public int AdminUserId { get; set; } + public bool IsNotBlank { get; set; } + public string CompetenciesFileName { get; set; } + public bool AddDefaultAssessmentQuestions { get; set; } = true; + public bool AddCustomAssessmentQuestion { get; set; } = false; + public List DefaultQuestionIDs { get; set; } = []; + public int? CustomAssessmentQuestionID { get; set; } + public int AddAssessmentQuestionsOption { get; set; } //1 = only added, 2 = added and updated, 3 = all uploaded + public int CompetenciesToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToUpdateCount { get; set; } + public int CompetenciesToReorderCount { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order + public int LastRowProcessed { get; set; } + public int SubtotalCompetenciesAdded { get; set; } + public int SubtotalCompetenciesUpdated { get; set; } + public int SubTotalSkipped { get; set; } + public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } = Enumerable.Empty<(int, string)>(); + public ImportCompetenciesResult ImportCompetenciesResult { get;set;} + } +} diff --git a/DigitalLearningSolutions.Web/Services/FrameworkNotificationService.cs b/DigitalLearningSolutions.Web/Services/FrameworkNotificationService.cs index b1d63c3130..092f38c15d 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkNotificationService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkNotificationService.cs @@ -351,7 +351,7 @@ public void SendResultVerificationRequest(int candidateAssessmentSupervisorId, i int candidateAssessmentId = candidateAssessmentSupervisor.CandidateAssessmentID; var supervisorDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, 0, delegateUserId); string centreName = GetCentreName(centreId); - var delegateSelfAssessment = supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentSupervisor.CandidateAssessmentID); + var delegateSelfAssessment = supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentSupervisor.CandidateAssessmentID, 0); string emailSubjectLine = $"{delegateSelfAssessment.SupervisorRoleTitle} Self Assessment Results Review Request - Digital Learning Solutions"; string? profileReviewUrl = GetSupervisorProfileReviewUrl(supervisorDelegateId, candidateAssessmentId, selfAssessmentResultId); BodyBuilder? builder = new BodyBuilder(); @@ -369,7 +369,7 @@ public void SendSignOffRequest(int candidateAssessmentSupervisorId, int selfAsse int candidateAssessmentId = candidateAssessmentSupervisor.CandidateAssessmentID; var supervisorDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, 0, delegateUserId); string centreName = GetCentreName(centreId); - var delegateSelfAssessment = supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentSupervisor.CandidateAssessmentID); + var delegateSelfAssessment = supervisorService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentSupervisor.CandidateAssessmentID, 0); string emailSubjectLine = $"{delegateSelfAssessment.SupervisorRoleTitle} Self Assessment Sign-off Request - Digital Learning Solutions"; string? profileReviewUrl = GetSupervisorProfileReviewUrl(supervisorDelegateId, candidateAssessmentId); BodyBuilder? builder = new BodyBuilder(); diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 7ddb9b6169..795e8ba966 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -2,6 +2,7 @@ using DigitalLearningSolutions.Data.Models.Common; using DigitalLearningSolutions.Data.Models.Email; using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; using DigitalLearningSolutions.Data.Models.SelfAssessments; using System.Collections.Generic; using AssessmentQuestion = DigitalLearningSolutions.Data.Models.Frameworks.AssessmentQuestion; @@ -55,6 +56,8 @@ public interface IFrameworkService int GetMaxFrameworkCompetencyID(); int GetMaxFrameworkCompetencyGroupID(); + IEnumerable GetBulkCompetenciesForFramework(int frameworkId); + List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds); // Assessment questions: IEnumerable GetAllCompetencyQuestions(int adminId); @@ -114,18 +117,18 @@ bool zeroBased //INSERT DATA BrandedFramework CreateFramework(DetailFramework detailFramework, int adminId); - int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId); + int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId = null); - int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); + int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false); IEnumerable GetAllCompetenciesForAdminId(string name, int adminId); int InsertCompetency(string name, string? description, int adminId); - int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); + int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify); - void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); + int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting); @@ -196,8 +199,8 @@ void UpdateFrameworkCompetencyGroup( int adminId ); - void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId); - void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); + void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription = false); + int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -274,9 +277,9 @@ public void AddCompetencyAssessmentQuestion(int frameworkCompetencyId, int asses frameworkDataService.AddCompetencyAssessmentQuestion(frameworkCompetencyId, assessmentQuestionId, adminId); } - public void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) + public int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) { - frameworkDataService.AddCustomFlagToFramework(frameworkId, flagName, flagGroup, flagTagClass); + return frameworkDataService.AddCustomFlagToFramework(frameworkId, flagName, flagGroup, flagTagClass); } public void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting) @@ -379,6 +382,16 @@ public IEnumerable GetAssessmentQuestionsForCompetency(int fr return frameworkDataService.GetBrandedFrameworkByFrameworkId(frameworkId, adminId); } + public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) + { + return frameworkDataService.GetBulkCompetenciesForFramework(frameworkId); + } + + public List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds) + { + return frameworkDataService.GetFrameworkCompetencyOrder(frameworkId, frameworkCompetencyIds); + } + public CollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId) { return frameworkDataService.GetCollaboratorNotification(id, invitedByAdminId); @@ -584,14 +597,14 @@ public int InsertCompetency(string name, string? description, int adminId) return frameworkDataService.InsertCompetency(name, description, adminId); } - public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId) + public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId) { - return frameworkDataService.InsertCompetencyGroup(groupName, groupDescription, adminId); + return frameworkDataService.InsertCompetencyGroup(groupName, groupDescription, adminId, frameworkId); } - public int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId) + public int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false) { - return frameworkDataService.InsertFrameworkCompetency(competencyId, frameworkCompetencyGroupID, adminId, frameworkId); + return frameworkDataService.InsertFrameworkCompetency(competencyId, frameworkCompetencyGroupID, adminId, frameworkId, alwaysShowDescription); } public int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId) @@ -649,9 +662,9 @@ public void UpdateAssessmentQuestion(int id, string question, int assessmentQues frameworkDataService.UpdateAssessmentQuestion(id, question, assessmentQuestionInputTypeId, maxValueDescription, minValueDescription, scoringInstructions, minValue, maxValue, includeComments, adminId, commentsPrompt, commentsHint); } - public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) + public int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) { - frameworkDataService.UpdateCompetencyFlags(frameworkId, competencyId, selectedFlagIds); + return frameworkDataService.UpdateCompetencyFlags(frameworkId, competencyId, selectedFlagIds); } public BrandedFramework? UpdateFrameworkBranding(int frameworkId, int brandId, int categoryId, int topicId, int adminId) @@ -659,9 +672,9 @@ public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selec return frameworkDataService.UpdateFrameworkBranding(frameworkId, brandId, categoryId, topicId, adminId); } - public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId) - { - frameworkDataService.UpdateFrameworkCompetency(frameworkCompetencyId, name, description, adminId); + public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription) + { + frameworkDataService.UpdateFrameworkCompetency(frameworkCompetencyId, name, description, adminId, alwaysShowDescription); } public void UpdateFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, string name, string? description, int adminId) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index ca2bbb7e03..02b8affac1 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -4,59 +4,153 @@ namespace DigitalLearningSolutions.Web.Services { + using System; using System.Collections.Generic; + using System.IO; using System.Linq; using ClosedXML.Excel; - using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Exceptions; + using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Data.Models.Frameworks; using DigitalLearningSolutions.Data.Models.Frameworks.Import; - using Microsoft.AspNetCore.Http; + using DocumentFormat.OpenXml.Office2010.Excel; public interface IImportCompetenciesFromFileService { - - public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId); + byte[] GetCompetencyFileForFramework(int frameworkId, bool isBlank, string vocabulary); + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary, int frameworkId); + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary, int reorderCompetenciesOption, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService { private readonly IFrameworkService frameworkService; + private static readonly XLTableTheme TableTheme = XLTableTheme.TableStyleLight9; + public const string CompetenciesSheetName = "FrameworkBulkUpload"; public ImportCompetenciesFromFileService( IFrameworkService frameworkService ) { this.frameworkService = frameworkService; } - public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId) + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary, int frameworkId) + { + var table = OpenCompetenciesTable(workbook, vocabulary); + var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); + var newCompetencyIds = competencyRows.Select(row => row.ID ?? 0).ToList(); + var existingIds = frameworkService.GetFrameworkCompetencyOrder(frameworkId, newCompetencyIds); + foreach (var competencyRow in competencyRows) + { + PreProcessCompetencyRow(competencyRow, newCompetencyIds, existingIds); + } + return new ImportCompetenciesResult(competencyRows); + } + private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List newIds, List existingIds) + { + if (competencyRow.ID == null) + { + competencyRow.RowStatus = RowStatus.CompetencyInserted; + } + else + { + var id = (int)(competencyRow?.ID); + if (!existingIds.Contains(id)) + { + competencyRow.RowStatus = RowStatus.InvalidId; + } + else + { + int originalIndex = existingIds.IndexOf(id); + int newIndex = newIds.IndexOf(id); + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + if (originalIndex != newIndex) + { + competencyRow.Reordered = true; + } + } + } + competencyRow.Validate(); + } + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary, int reorderCompetenciesOption, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds) { int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID(); int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID(); - var table = OpenCompetenciesTable(file); - return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId); + var table = OpenCompetenciesTable(workbook, vocabulary); + return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds); } - internal IXLTable OpenCompetenciesTable(IFormFile file) + internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) { - var workbook = new XLWorkbook(file.OpenReadStream()); var worksheet = workbook.Worksheet(1); + worksheet.Columns(1, 15).Unhide(); if (worksheet.Tables.Count() == 0) { throw new InvalidHeadersException(); } var table = worksheet.Tables.Table(0); - if (!ValidateHeaders(table)) + if (!ValidateHeaders(table, vocabulary)) { throw new InvalidHeadersException(); } return table; } - internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId) + internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, int addAssessmentQuestionsOption, int reorderCompetenciesOption, int customAssessmentQuestionID, List defaultQuestionIds) { var competenciesRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); - + int rowCount = 0; + string currentGroup = null; + competenciesRows = competenciesRows + .Select(row => + { + if (row.CompetencyGroup != currentGroup) + { + currentGroup = row.CompetencyGroup; + rowCount = 1; + } + else + { + rowCount++; + } + row.CompetencyOrderNumber = rowCount; + return row; + }) + .ToList(); + var competencyGroupCount = competenciesRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(row => row.CompetencyGroup) + .Distinct() + .Count(); foreach (var competencyRow in competenciesRows) { - maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, competencyRow); + maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); + } + // Check for changes to competency group order and apply them if appropriate: + if (reorderCompetenciesOption == 2) + { + var distinctCompetencyGroups = competenciesRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(row => row.CompetencyGroup) + .Distinct() + .ToList(); + for (int i = 0; i < competencyGroupCount; i++) + { + var existingGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).Select(row => new { row.ID, row.Name }) + .Distinct() + .ToList(); + var placesToMove = Math.Abs(existingGroups.FindIndex(group => group.Name == distinctCompetencyGroups[i])-i); + if (placesToMove > 0) + { + var thisGroup = existingGroups.FirstOrDefault(group => group.Name == distinctCompetencyGroups[i]); + var direction = existingGroups.FindIndex(group => group.Name == distinctCompetencyGroups[i]) > i ? "UP" : "DOWN"; + for (int p = 0; p < placesToMove; p++) + { + frameworkService.MoveFrameworkCompetencyGroup(thisGroup.ID, true, direction); + } + competenciesRows + .Where(row => row.CompetencyGroup == thisGroup.Name) + .ToList() + .ForEach(row => row.Reordered = true); + } + } } - return new ImportCompetenciesResult(competenciesRows); } private int ProcessCompetencyRow( @@ -64,6 +158,10 @@ private int ProcessCompetencyRow( int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, + int addAssessmentQuestionsOption, + int reorderCompetenciesOption, + int customAssessmentQuestionID, + List defaultQuestionIds, CompetencyTableRow competencyRow ) { @@ -71,11 +169,13 @@ CompetencyTableRow competencyRow { return maxFrameworkCompetencyGroupId; } + int newCompetencyId = 0; + int newFrameworkCompetencyId = 0; //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID int? frameworkCompetencyGroupId = null; - if (competencyRow.CompetencyGroupName != null) + if (competencyRow.CompetencyGroup != null) { - var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroupName, null, adminUserId); + int newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId, frameworkId); if (newCompetencyGroupId > 0) { frameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminUserId); @@ -86,34 +186,174 @@ CompetencyTableRow competencyRow } } } + // If FrameworkCompetency ID is supplied, update the competency + if (competencyRow.ID != null) + { + var frameworkCompetency = frameworkService.GetFrameworkCompetencyById((int)competencyRow.ID); + if (frameworkCompetency != null) + { + newCompetencyId = frameworkCompetency.CompetencyID; + if (frameworkCompetency.Name != competencyRow.Competency || frameworkCompetency.Description != competencyRow.CompetencyDescription || frameworkCompetency.AlwaysShowDescription != competencyRow.AlwaysShowDescription) + { + frameworkService.UpdateFrameworkCompetency((int)competencyRow.ID, competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId, competencyRow.AlwaysShowDescription ?? false); + competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyUpdated : RowStatus.CompetencyUpdated); + } + else + { + competencyRow.RowStatus = RowStatus.Skipped; + } + } + } + else + { + //Check if competency already exists in framework competency group and add if not + newCompetencyId = frameworkService.InsertCompetency(competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId); + if (newCompetencyId > 0) + { + newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId, competencyRow.AlwaysShowDescription ?? false); //including always show desc flag + if (newFrameworkCompetencyId > maxFrameworkCompetencyId) + { + competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyInserted : RowStatus.CompetencyInserted); + } + else + { + competencyRow.RowStatus = RowStatus.Skipped; + } + } + } + - //Check if competency already exists in framework competency group and add if not - var newCompetencyId = frameworkService.InsertCompetency(competencyRow.CompetencyName, competencyRow.CompetencyDescription, adminUserId); - if (newCompetencyId > 0) + // If flags are supplied, add them: + if (!string.IsNullOrWhiteSpace(competencyRow.FlagsCsv.Trim())) { - var newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId); - if (newFrameworkCompetencyId > maxFrameworkCompetencyId) + var flags = competencyRow.FlagsCsv.Split(','); + var flagIds = new List(); + foreach (var flag in flags) { - competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyInserted : RowStatus.CompetencyInserted); + int flagId = 0; + var frameworkFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, null); + if (frameworkFlags.Any()) + { + foreach (var frameworkFlag in frameworkFlags) + { + if (frameworkFlag.FlagName == flag) + { + flagId = frameworkFlag.FlagId; + break; + } + } + } + if (flagId == 0) + { + flagId = frameworkService.AddCustomFlagToFramework(frameworkId, flag, "Flag", "nhsuk-tag--white"); + } + flagIds.Add(flagId); } - else + if (flagIds.Count > 0) { - competencyRow.RowStatus = RowStatus.Skipped; + var updated = frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, [.. flagIds]); + if (updated > 0 && competencyRow.RowStatus == RowStatus.Skipped) + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } } } + + // Reorder competencies if required: + if (reorderCompetenciesOption == 2) + { + var frameworkCompetencyId = (int)competencyRow.ID; + var frameworkCompetency = frameworkService.GetFrameworkCompetencyById(frameworkCompetencyId); + var placesToMove = Math.Abs(frameworkCompetency.Ordering - competencyRow.CompetencyOrderNumber); + + if (placesToMove > 0) + { + var direction = frameworkCompetency.Ordering > competencyRow.CompetencyOrderNumber ? "UP" : "DOWN"; + + for (int i = 0; i < placesToMove; i++) + { + frameworkService.MoveFrameworkCompetency(frameworkCompetencyId, true, direction); + } + + competencyRow.Reordered = true; + } + } + + // Add assessment questions if necessary: + if (defaultQuestionIds.Count > 0 | customAssessmentQuestionID > 0) + { + if (competencyRow.RowStatus == RowStatus.CompetencyInserted | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted || addAssessmentQuestionsOption == 2 && competencyRow.RowStatus == RowStatus.CompetencyUpdated | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated || addAssessmentQuestionsOption == 3) + { + foreach (var id in defaultQuestionIds) + { + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, id, adminUserId); + } + if (customAssessmentQuestionID > 0) + { + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, customAssessmentQuestionID, adminUserId); + } + } + } + + + return maxFrameworkCompetencyGroupId; } - private static bool ValidateHeaders(IXLTable table) + private static bool ValidateHeaders(IXLTable table, string Vocabulary) { var expectedHeaders = new List { - "competency group", - "competency name", - "competency description" + "ID", + Vocabulary + "Group", + "GroupDescription", + Vocabulary, + Vocabulary + "Description", + "AlwaysShowDescription", + "FlagsCSV" }.OrderBy(x => x); - var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x); + var actualHeaders = table.Fields.Select(x => x.Name).OrderBy(x => x); return actualHeaders.SequenceEqual(expectedHeaders); } + + public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank, string vocabulary) + { + using var workbook = new XLWorkbook(); + PopulateCompetenciesSheet(workbook, frameworkId, blank, vocabulary); + if (blank) + { + ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID"); + } + var options = new List { "TRUE", "FALSE" }; + ClosedXmlHelper.AddValidationListToWorksheetColumn(workbook, 6, options); + var rowCount = workbook.Worksheet(1).RangeUsed().RowCount(); + ClosedXmlHelper.AddValidationRangeToWorksheetColumn(workbook, 1, 1, rowCount, 1); + // Calculate the workbook + workbook.CalculateMode = XLCalculateMode.Auto; + workbook.RecalculateAllFormulas(); + using var stream = new MemoryStream(); + workbook.SaveAs(stream); + return stream.ToArray(); + } + private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bool blank, string vocabulary) + { + var competencyRecords = frameworkService.GetBulkCompetenciesForFramework(blank ? 0 : frameworkId); + var competencies = competencyRecords.Select( + x => new + { + x.ID, + x.CompetencyGroup, + x.GroupDescription, + x.Competency, + x.CompetencyDescription, + x.AlwaysShowDescription, + FlagsCSV = x.FlagsCsv, + } + ); + ClosedXmlHelper.AddSheetToWorkbook(workbook, CompetenciesSheetName, competencies, TableTheme); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "CompetencyGroup", vocabulary + "Group"); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "Competency", vocabulary); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "CompetencyDescription", vocabulary + "Description"); + } } } diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs index 102af0b0b1..37b44e8e34 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs @@ -15,8 +15,8 @@ public interface ISelfAssessmentService //Self Assessments string? GetSelfAssessmentNameById(int selfAssessmentId); // Candidate Assessments - IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminCategoryId); - + IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminIdCategoryID); + IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId); CurrentSelfAssessment? GetSelfAssessmentForCandidateById(int delegateUserId, int selfAssessmentId); void SetBookmark(int selfAssessmentId, int delegateUserId, string bookmark); @@ -404,11 +404,14 @@ public IEnumerable GetCandidateAssessmentOptionalCompetencies(int se return selfAssessmentDataService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId); } - public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminCategoryId) + public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminIdCategoryID) { - return selfAssessmentDataService.GetSelfAssessmentsForCandidate(delegateUserId, centreId, adminCategoryId); + return selfAssessmentDataService.GetSelfAssessmentsForCandidate(delegateUserId, centreId, adminIdCategoryID); + } + public IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId) + { + return selfAssessmentDataService.GetSelfAssessmentsForCandidate(delegateUserId, centreId); } - public IEnumerable GetMostRecentResults(int selfAssessmentId, int delegateId) { return selfAssessmentDataService.GetMostRecentResults(selfAssessmentId, delegateId); diff --git a/DigitalLearningSolutions.Web/Services/SupervisorService.cs b/DigitalLearningSolutions.Web/Services/SupervisorService.cs index a8afcff576..8cd8d2e16e 100644 --- a/DigitalLearningSolutions.Web/Services/SupervisorService.cs +++ b/DigitalLearningSolutions.Web/Services/SupervisorService.cs @@ -19,7 +19,7 @@ public interface ISupervisorService DelegateSelfAssessment? GetSelfAssessmentByCandidateAssessmentId(int candidateAssessmentId, int adminId, int? adminIdCategoryId); IEnumerable GetSupervisorDashboardToDoItemsForRequestedSignOffs(int adminId); IEnumerable GetSupervisorDashboardToDoItemsForRequestedReviews(int adminId); - DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId); + DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId, int? adminIdCategoryId); IEnumerable GetAvailableRoleProfilesForDelegate(int candidateId, int centreId, int? categoryId); RoleProfile? GetRoleProfileById(int selfAssessmentId); IEnumerable GetSupervisorRolesForSelfAssessment(int selfAssessmentId); @@ -115,9 +115,9 @@ public IEnumerable GetDelegateNominatableSuperviso return supervisorDataService.GetRoleProfileById(selfAssessmentId); } - public DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId) + public DelegateSelfAssessment? GetSelfAssessmentBaseByCandidateAssessmentId(int candidateAssessmentId, int? adminIdCategoryId) { - return supervisorDataService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId); + return supervisorDataService.GetSelfAssessmentBaseByCandidateAssessmentId(candidateAssessmentId, adminIdCategoryId); } public DelegateSelfAssessment? GetSelfAssessmentByCandidateAssessmentId(int candidateAssessmentId, int adminId, int? adminIdCategoryId) @@ -275,7 +275,7 @@ public IEnumerable GetSupervisorDelegateDetailsForAdmi } public SupervisorDelegateDetail GetSupervisorDelegateDetailsByIdWithoutRemoveClause(int supervisorDelegateId, int adminId, int delegateUserId) { - return supervisorDataService.GetSupervisorDelegateDetailsByIdWithoutRemoveClause(supervisorDelegateId,adminId, delegateUserId); + return supervisorDataService.GetSupervisorDelegateDetailsByIdWithoutRemoveClause(supervisorDelegateId, adminId, delegateUserId); } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs new file mode 100644 index 0000000000..4b90f1198c --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddAssessmentQuestionsFormData + { + public bool AddDefaultAssessmentQuestions { get; set; } + public bool AddCustomAssessmentQuestion { get; set; } + public List DefaultAssessmentQuestionIDs { get; set; } + public int? CustomAssessmentQuestionID { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs new file mode 100644 index 0000000000..204f7e49e2 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs @@ -0,0 +1,31 @@ +using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Web.Helpers; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddAssessmentQuestionsViewModel( + int frameworkId, + string frameworkName, + string frameworkVocabulary, + int publishStatusId, + int newCompetencies, + int existingCompetencies, + int competenciesToReorderCount, + IEnumerable defaultQuestions, + SelectList questionSelectList + ) : AddAssessmentQuestionsFormData + { + public int FrameworkID { get; set; } = frameworkId; + public string FrameworkName { get; set; } = frameworkName; + public string FrameworkVocabularySingular { get; set; } = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + public string FrameworkVocabularyPlural { get; set; } = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + public int PublishStatusID { get; set; } = publishStatusId; + public int NewCompetencies { get; set; } = newCompetencies; + public int ExistingCompetencies { get; set; } = existingCompetencies; + public int CompetenciesToReorderCount { get; set; } = competenciesToReorderCount; + public IEnumerable? DefaultQuestions { get; set; } = defaultQuestions; + public SelectList? QuestionSelectList { get; set; } = questionSelectList; + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs new file mode 100644 index 0000000000..fa86ccc30a --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs @@ -0,0 +1,42 @@ +using DigitalLearningSolutions.Web.Helpers; +using System.Collections.Generic; +using System.Runtime.Versioning; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddQuestionsToWhichCompetenciesViewModel + { + public AddQuestionsToWhichCompetenciesViewModel + ( + int frameworkId, + string frameworkName, + string frameworkVocabulary, + List defaultQuestions, + int? customQuestionId, + int addAssessmentQuestionsOption, + int competenciesToProcessCount, + int competenciesToAddCount, + int competenciesToUpdateCount) + { + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + TotalQuestions = defaultQuestions.Count + (customQuestionId != null ? 1 : 0); + AddAssessmentQuestionsOption = addAssessmentQuestionsOption; + CompetenciesToProcessCount = competenciesToProcessCount; + CompetenciesToAddCount = competenciesToAddCount; + CompetenciesToUpdateCount = competenciesToUpdateCount; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int TotalQuestions { get; set; } + public int AddAssessmentQuestionsOption { get; set; } = 1; //1 = only added, 2 = added and updated, 3 = all uploaded + public int CompetenciesToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToUpdateCount { get; set; } + + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs new file mode 100644 index 0000000000..ee912de97f --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs @@ -0,0 +1,30 @@ +using DigitalLearningSolutions.Web.Helpers; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class ApplyCompetencyOrderingViewModel + { + public ApplyCompetencyOrderingViewModel + ( + int frameworkId, + string frameworkName, + string frameworkVocabulary, + int competenciesToReorderCount, + int reorderCompetenciesOption + ) + { + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + CompetenciesToReorderCount = competenciesToReorderCount; + ReorderCompetenciesOption = reorderCompetenciesOption; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order + public int CompetenciesToReorderCount { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs similarity index 74% rename from DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs rename to DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs index eb44330a20..98604cc8ee 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs @@ -1,12 +1,11 @@ -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Web.Attributes; using Microsoft.AspNetCore.Http; using System.ComponentModel.DataAnnotations; - public class ImportCompetenciesViewModel + public class ImportCompetenciesFormData { - public int FrameworkId { get; set; } [Required(ErrorMessage = "Import competencies file is required")] [AllowedExtensions(new[] { ".xlsx" }, "Import competencies file must be in xlsx format")] [MaxFileSize(5 * 1024 * 1024, "Maximum allowed file size is 5MB")] diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs new file mode 100644 index 0000000000..96b7935ea3 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System; +using DigitalLearningSolutions.Web.Models; +using System.Linq; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; +using DigitalLearningSolutions.Web.Helpers; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class ImportCompetenciesPreProcessViewModel + { + public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompetenciesResult, BulkCompetenciesData bulkCompetenciesData) + { + FrameworkName = bulkCompetenciesData.FrameworkName; + PublishStatusID = bulkCompetenciesData.PublishStatusID; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(bulkCompetenciesData.FrameworkVocubulary); + ToProcessCount = bulkCompetenciesResult.ProcessedCount; + CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; + CompetenciesToReorderCount = bulkCompetenciesResult.CompetencyReorderedCount; + ToUpdateOrSkipCount = bulkCompetenciesResult.CompetencyUpdatedCount; + Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason, FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary)))); + FlagCount = bulkCompetenciesResult.FlagCount; + DistinctFlagsCount = bulkCompetenciesResult.DistinctFlagsCount; + CompetencyGroupCount = bulkCompetenciesResult.CompetencyGroupCount; + } + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } + public int ErrorCount => Errors.Count(); + public int ToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToReorderCount { get; set; } + public int ToUpdateOrSkipCount { get; set; } + public string? ImportFile { get; set; } + public bool IsNotBlank { get; set; } + public string TabName { get; set; } + public int FlagCount { get; set; } + public int DistinctFlagsCount { get; set; } + public int CompetencyGroupCount { get; set; } + + private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason, string vocabularySingular) + { + return reason switch + { + ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => + "Group name must be 255 characters or less.", + ImportCompetenciesResult.ErrorReason.MissingCompetencyName => + vocabularySingular + " is blank. " + vocabularySingular + " is a required field and cannot be left blank", + ImportCompetenciesResult.ErrorReason.InvalidId => + "The ID provided does not match a " + vocabularySingular + " ID in this Framework. Leave the ID column blank for new competencies.", + ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => + vocabularySingular + " must be 500 characters or less.", + ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => + "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", + _ => "Unspecified error.", + }; + } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs similarity index 56% rename from DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs rename to DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs index 8267146c71..6bc814b2a2 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs @@ -1,25 +1,40 @@ -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Data.Models.Frameworks.Import; + using DigitalLearningSolutions.Web.Helpers; using System.Collections.Generic; using System.Linq; public class ImportCompetenciesResultsViewModel { - public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult) + public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult, int frameworkId, string frameworkName, string frameworkVocabulary) { ProcessedCount = importCompetenciesResult.ProcessedCount; - CompetenciesInsertedCount = importCompetenciesResult.CompetenciesInsertedCount; - CompetencyGroupsInsertedCount = importCompetenciesResult.CompetencyGroupsInsertedCount; + CompetenciesInsertedCount = importCompetenciesResult.CompetencyAddedCount; + CompetenciesUpdatedCount = importCompetenciesResult.CompetencyUpdatedCount; + CompetencyGroupsInsertedCount = importCompetenciesResult.GroupAddedCount; + CompetencyGroupsUpdatedCount = importCompetenciesResult.GroupUpdatedCount; + CompetenciesReorderedCount = importCompetenciesResult.CompetencyReorderedCount; SkippedCount = importCompetenciesResult.SkippedCount; Errors = importCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); } public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } public int ErrorCount => Errors.Count(); public int ProcessedCount { get; set; } public int CompetenciesInsertedCount { get; set; } + public int CompetenciesUpdatedCount { get; set; } + public int CompetenciesReorderedCount { get; set; } public int CompetencyGroupsInsertedCount { get; set; } + public int CompetencyGroupsUpdatedCount { get; set; } public int SkippedCount { get; set; } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } private string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason) { return reason switch @@ -28,6 +43,7 @@ private string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reas "Competency name was not provided. Competency name is a required field and cannot be left blank", ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => "Competency name must be 500 characters or fewer", ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => "Competency group name must be 255 characters or fewer", + _ => "Unknown error", }; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs new file mode 100644 index 0000000000..698b278dc1 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs @@ -0,0 +1,25 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + using DigitalLearningSolutions.Data.Models.Frameworks; + using DigitalLearningSolutions.Web.Helpers; + + public class ImportCompetenciesViewModel : ImportCompetenciesFormData + { + public ImportCompetenciesViewModel(DetailFramework framework, bool isNotBlank) + { + FrameworkID = framework.ID; + FrameworkName = framework.FrameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(framework.FrameworkConfig); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(framework.FrameworkConfig); + PublishStatusID = framework.PublishStatusID; + IsNotBlank = isNotBlank; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int PublishStatusID { get; set; } + public bool IsNotBlank { get; set; } + + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs new file mode 100644 index 0000000000..f59c14f72d --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs @@ -0,0 +1,41 @@ +using DigitalLearningSolutions.Web.Helpers; +using DigitalLearningSolutions.Web.Models; +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class ImportSummaryViewModel + { + public ImportSummaryViewModel(BulkCompetenciesData data) + { + FrameworkName = data.FrameworkName; + PublishStatusID = data.PublishStatusID; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(data.FrameworkVocubulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(data.FrameworkVocubulary); + ToProcessCount = data.CompetenciesToProcessCount; + CompetenciesToAddCount = data.CompetenciesToAddCount; + CompetenciesToReorderCount = data.CompetenciesToReorderCount; + ToUpdateOrSkipCount = data.CompetenciesToUpdateCount; + AddAssessmentQuestionsOption = data.AddAssessmentQuestionsOption; + AddDefaultAssessmentQuestions = data.AddDefaultAssessmentQuestions; + AddCustomAssessmentQuestion = data.AddCustomAssessmentQuestion; + DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; + CustomAssessmentQuestionID = data.CustomAssessmentQuestionID; + ReorderCompetenciesOption = data.ReorderCompetenciesOption; + } + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int ToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToReorderCount { get; set; } + public int ToUpdateOrSkipCount { get; set; } + public int AddAssessmentQuestionsOption { get; set; } = 1; //1 = only added, 2 = added and updated, 3 = all uploaded + public bool AddDefaultAssessmentQuestions { get; set; } + public bool AddCustomAssessmentQuestion { get; set; } + public List DefaultAssessmentQuestionIDs { get; set; } + public int? CustomAssessmentQuestionID { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order + } +} diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml new file mode 100644 index 0000000000..9e6c443d05 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml @@ -0,0 +1,105 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model AddAssessmentQuestionsViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var checkListErrorClass = !ViewData.ModelState.IsValid && Model.AddDefaultAssessmentQuestions == null ? "nhsuk-form-group nhsuk-form-group--error" : "nhsuk-form-group"; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); + var hintTextString = Model.NewCompetencies != 0 && Model.ExistingCompetencies != 0 ? "new and/or updated " : (Model.NewCompetencies == 0 ? "updated " : "new "); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+
+ @if (errorHasOccurred) + { + + } +
+
+
+ +

+ @ViewData["Title"] +

+
+
+ Which assessment questions would you like to attach to the @hintTextString @Model.FrameworkVocabularyPlural.ToLower()? +
+
+ @if (Model.DefaultQuestions.Any()) + { +
+ + +
+ choose which default questions associated with this framework to add to the imported and/or updated @Model.FrameworkVocabularyPlural.ToLower() +
+
+ +
+
+ @foreach (var (defaultQuestion, index) in Model.DefaultQuestions.Select((t, i) => (t, i))) + { +
+ + +
+ } +
+
+
+
+ } +
+ + +
+
+ + + +
+
+
+
+ Back + +
+ +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml new file mode 100644 index 0000000000..eef8dbabe8 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml @@ -0,0 +1,87 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model AddQuestionsToWhichCompetenciesViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+
+
+
+ +

+ Which @Model.FrameworkVocabularyPlural.ToLower() should the questions be added to? +

+
+
+ Choose which @Model.FrameworkVocabularyPlural.ToLower() you want to add the @Model.TotalQuestions assessment questions to +
+
+ Select one option +
+
+
+ @if (Model.CompetenciesToAddCount > 0) + { +
+ + +
+ @Model.TotalQuestions assessment questions will be added to the @Model.CompetenciesToAddCount new @Model.FrameworkVocabularyPlural.ToLower() +
+
+ } +
+ + +
+ @(Model.CompetenciesToAddCount > 0 ? Model.CompetenciesToAddCount + $" new {@Model.FrameworkVocabularyPlural.ToLower()} and only those of the " : "Only those of the ") @Model.CompetenciesToUpdateCount existing @Model.FrameworkVocabularyPlural.ToLower() that have been modified in the sheet will have the assessment questions added +
+
+
+ + +
+ All @(Model.CompetenciesToProcessCount) @Model.FrameworkVocabularyPlural.ToLower() in the sheet that are will have the assessment questions added to them +
+
+
+
+ +
+ Back + +
+ +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml new file mode 100644 index 0000000000..456961288e --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml @@ -0,0 +1,79 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model ApplyCompetencyOrderingViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+
+
+
+ +

+ Apply @Model.FrameworkVocabularySingular.ToLower() sequence changes? +

+
+
+ Information: +

We strongly recommend including all framework competencies in your uploaded sheet if reordering competencies to ensure that the correct sequence is applied.

+
+
+ Your uploaded file includes changes to the order of existing @Model.FrameworkVocabularyPlural.ToLower(). Choose whether to store the changes to @Model.FrameworkVocabularySingular.ToLower() order during update. +
+
+ Select an option +
+
+
+
+ + +
+ @Model.FrameworkVocabularyPlural will be updated if they have changed but there sequence/order in the framework will not change. +
+
+
+ + +
+ The oder of @Model.FrameworkVocabularyPlural.ToLower() in the framework will be changed to reflect the order in the sheet during processing. +
+
+
+
+ +
+ Back + +
+ +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml new file mode 100644 index 0000000000..3a5cd7aa8d --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -0,0 +1,98 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportCompetenciesPreProcessViewModel +@{ + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+

@Model.FrameworkVocabularySingular file uploaded

+
+

@(Model.ErrorCount == 0 ? "Your file is error free and ready to be processed. Check the information below looks, correct before processing" : "Your file contains the following, including some errors:")

+
    +
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") uploaded
  • + @if (Model.ErrorCount > 0) + { +
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • + } + else + { +
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
  • +
  • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • +
  • In @Model.CompetencyGroupCount @Model.FrameworkVocabularySingular.ToLower() groups
  • +
  • With @Model.FlagCount flags assigned to @Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
  • + @if (Model.CompetenciesToReorderCount > 0) + { + +
  • + Some existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next. +
  • + } +
  • No errors
  • + } +
+ @if (Model.ErrorCount == 0) + { + Continue + } + else + { +

Check the information below. You will need to fix these errors before continuing or remove the rows with errors from your spreadsheet:

+
+ + Error: @Model.ErrorCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ErrorCount == 1 ? "row" : "rows") contain errors and cannot be processed + +
+ @foreach (var (rowNumber, errorMessage) in Model.Errors) + { +
+
+ Row @rowNumber +
+
+ @errorMessage +
+ +
+ } +
+
+

Upload corrected file

+

+ Once you have made corrections to the Excel competency workbook to address the errors above, save and restart the upload process. +

+
+ + + + + + + + } + +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml new file mode 100644 index 0000000000..1001851c70 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml @@ -0,0 +1,45 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportCompetenciesResultsViewModel +@{ + ViewData["Title"] = "Framework - Import Failed"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} + +
+
+

Import failed

+
+

+ The file that you uploaded either does not have the correct column headers on the first row or is not formatted as a table. +

+ +

Refer to the instructions on the Import Competencies page to make sure your Excel file has the correct column headers.

+ + + + +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml new file mode 100644 index 0000000000..ed715f4068 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -0,0 +1,114 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportSummaryViewModel +@{ + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); + var process = Model.CompetenciesToAddCount > 0 && Model.ToUpdateOrSkipCount > 0 ? "add and update" : Model.CompetenciesToAddCount > 0 ? "add" : "update"; + var addQsTo = Model.AddAssessmentQuestionsOption == 1 ? "new" : Model.AddAssessmentQuestionsOption == 2 ? "new and updated" : "all"; +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+

@Model.FrameworkVocabularyPlural import summary

+ @if (Model.PublishStatusID == 3) + { + + } +

Your @Model.FrameworkVocabularySingular.ToLower() sheet is ready to be processed. Please check the details below are correct before proceeding to @process @Model.FrameworkVocabularyPlural.ToLower() in the framework @Model.FrameworkName.

+

Upload summary

+
+ +
+
+ @Model.FrameworkVocabularySingular rows uploaded +
+
+ @Model.ToProcessCount +
+ +
+ +
+
+ @Model.FrameworkVocabularyPlural to add +
+
+ @Model.CompetenciesToAddCount +
+ +
+ +
+
+ @Model.FrameworkVocabularyPlural to update +
+
+ @Model.ToUpdateOrSkipCount +
+
+
+

Additional processing steps

+
+ @if (Model.CompetenciesToReorderCount > 0) + { +
+
+ Changes to @Model.FrameworkVocabularySingular.ToLower() order +
+
+ @(Model.ReorderCompetenciesOption == 1 ? "Ignore" : "Apply") changes to @Model.FrameworkVocabularySingular.ToLower() order +
+
+ + Change @Model.FrameworkVocabularySingular.ToLower() order options + +
+
+ } +
+
+ Add questions to @addQsTo @Model.FrameworkVocabularyPlural.ToLower() in sheet +
+
+ @Model.DefaultAssessmentQuestionIDs.Count() framework default questions @(Model.AddCustomAssessmentQuestion && Model.CustomAssessmentQuestionID != null ? "AND a custom question" : "") +
+
+ + Change assessment question options + +
+
+
+ +
+ Important: +

Once @Model.FrameworkVocabularySingular.ToLower() records are processed, changes cannot be undone.

+
+
+ 0 ? "AddQuestionsToWhichCompetencies" : "AddAssessmentQuestions") role="button" class="nhsuk-button nhsuk-button--secondary">Back + +
+ +
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml new file mode 100644 index 0000000000..7a31d64dca --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -0,0 +1,129 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@model ImportCompetenciesViewModel +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Bulk upload " + Model.FrameworkVocabularyPlural.ToLower() : "Bulk upload " + Model.FrameworkVocabularyPlural.ToLower(); + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+ @if (errorHasOccurred) + { + + } +

Bulk upload @(Model.IsNotBlank ? "or update" : "") @Model.FrameworkVocabularyPlural.ToLower()

+
+ @if (Model.PublishStatusID == 3) + { + + } + +
+ @if (Model.IsNotBlank) + { +
+ Note: +

+ Bulk upload cannot be used to: +

+
    +
  • Delete @Model.FrameworkVocabularyPlural.ToLower() or @Model.FrameworkVocabularySingular.ToLower() groups
  • +
  • Remove assessment questions from existing @Model.FrameworkVocabularyPlural.ToLower()
  • +
  • Add more than one custom assessment question to uploaded @Model.FrameworkVocabularyPlural.ToLower()
  • +
  • Remove flags from @Model.FrameworkVocabularyPlural.ToLower()
  • +
+ +
+

+ To bulk add and/or update @Model.FrameworkVocabularyPlural.ToLower() in the framework, @Model.FrameworkName, download an Excel workbook using one of the options below. +

+
+
+ +

+ What would you like to download? +

+
+
+ Select one option +
+
+
+ + +
+
+
+ This Excel file will be empty.
+ New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row. +
+ + Download template + +
+
+ + +
+
+
+ This Excel file will include all existing @Model.FrameworkVocabularyPlural.ToLower() whose details you can update.
+ New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row, leaving the ID column blank. +
+ + Download @Model.FrameworkVocabularyPlural.ToLower() + +
+
+
+
+ } + else + { + +

+ Download a blank template for bulk adding @Model.FrameworkVocabularyPlural.ToLower() to your framework, @Model.FrameworkName. This Excel file will be empty. New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row. +

+ + Download template + + } + +
+ +

Upload file

+

+ Once you have an Excel @Model.FrameworkVocabularyPlural.ToLower() workbook, add or update @Model.FrameworkVocabularyPlural.ToLower() to the worksheet, save and start the upload process. +

+
+ + + + + +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml new file mode 100644 index 0000000000..87278f516d --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -0,0 +1,67 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportCompetenciesResultsViewModel +@{ + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} + +
+
+

Import competencies complete

+
+

Summary of results:

+
    +
  • @Model.ProcessedCount @(Model.ProcessedCount == 1 ? "line" : "lines") processed
  • +
  • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? $"{Model.FrameworkVocabularySingular.ToLower()} group" : $"{Model.FrameworkVocabularySingular.ToLower()} groups") inserted
  • +
  • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) inserted
  • +
  • @Model.CompetenciesUpdatedCount existing @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
  • +
  • @Model.CompetenciesReorderedCount existing @(Model.CompetenciesReorderedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) reordered
  • +
  • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted or updated but no errors)
  • +
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
  • +
+ + @if (Model.ErrorCount > 0) + { +
+

+ + Important: + The imported Excel worksheet contained errors + +

+

The lines below were skipped due to errors during processing:

+
    + @foreach (var (rowNumber, errorMessage) in Model.Errors) + { +
  • Line @rowNumber: @errorMessage
  • + } +
+
+ } + + +
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml deleted file mode 100644 index 65e65b9838..0000000000 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ /dev/null @@ -1,60 +0,0 @@ -@using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks -@model ImportCompetenciesViewModel -@{ - ViewData["Title"] = "Framework - Import Competencies"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); -} - -@section NavMenuItems { - -} - @section NavBreadcrumbs { - -} -
-
- @if (errorHasOccurred) - { - - } -

Import from Excel

- -

- To import competencies from Excel, you will need an .xlsx worksheet file saved to your computer that meets the following requirements: -

    -
  • Has import data formatted as a table on the first worksheet in the file
  • -
  • - Has the following column titles on the cells in the first row: -
      -
    • Competency group (cell A1)
    • -
    • Competency name (cell B1)
    • -
    • Competency description (cell C1)
    • -
    -
  • -
  • Import data in subsequent rows with no blank rows (including hidden rows)
  • -
  • Import data must be formatted as a table (select all data and choose "Format as Table" in Excel
  • -
  • Import data rows must include a Competency name
  • -
  • If Competency group is left blank, the competency will be imported without a group and listed after grouped competencies in the framework
  • -
-

-
- - - - - -
-
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml deleted file mode 100644 index fefffeae4f..0000000000 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ /dev/null @@ -1,64 +0,0 @@ -@inject IConfiguration Configuration -@using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks -@using Microsoft.Extensions.Configuration -@model ImportCompetenciesResultsViewModel -@{ - ViewData["Title"] = "Framework - Import Competencies"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); -} - -@section NavMenuItems { - -} - @section NavBreadcrumbs { - -} - -
-
-

Import competencies complete

- -

Summary of results:

-
    -
  • @Model.ProcessedCount @(Model.ProcessedCount == 1 ? "line" : "lines") processed
  • -
  • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? "competency group" : "competency groups") inserted
  • -
  • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? "competency" : "competencies") inserted
  • -
  • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "record" : "records") skipped (nothing inserted but no errors)
  • -
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
  • -
- - @if (Model.ErrorCount > 0) - { -
-

- - Important: - The imported Excel worksheet contained errors - -

-

The lines below were skipped due to errors during processing:

-
    - @foreach (var (rowNumber, errorMessage) in Model.Errors) - { -
  • Line @rowNumber: @errorMessage
  • - } -
-
- } - - -
-
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml deleted file mode 100644 index 0176c0b656..0000000000 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml +++ /dev/null @@ -1,44 +0,0 @@ -@inject IConfiguration Configuration -@using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks -@using Microsoft.Extensions.Configuration -@model ImportCompetenciesResultsViewModel -@{ - ViewData["Title"] = "Framework - Import Failed"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); -} - -@section NavMenuItems { - -} - @section NavBreadcrumbs { - -} - -
-
-

Import failed

- -

- The file that you uploaded either does not have the correct column headers on the first row or is not formatted as a table. -

- -

Refer to the instructions on the Import Competencies page to make sure your Excel file has the correct column headers.

- - - - -
-
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml index 7fa159981d..849b5121ba 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml @@ -81,7 +81,7 @@ else }