diff --git a/DigitalLearningSolutions.Data.Migrations/202409051645_Alter_GetCompletedCoursesForCandidate_CourseActive.cs b/DigitalLearningSolutions.Data.Migrations/202409051645_Alter_GetCompletedCoursesForCandidate_CourseActive.cs new file mode 100644 index 0000000000..da53fa6ded --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202409051645_Alter_GetCompletedCoursesForCandidate_CourseActive.cs @@ -0,0 +1,19 @@ + + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202409051645)] + public class Alter_GetCompletedCoursesForCandidate_CourseActive : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_4634_Alter_GetCompletedCoursesForCandidate_UP); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202409110900_AddGroupOptionalCompetenciesToSelfAssessmentStructureTable.cs b/DigitalLearningSolutions.Data.Migrations/202409110900_AddGroupOptionalCompetenciesToSelfAssessmentStructureTable.cs new file mode 100644 index 0000000000..587f76a7ec --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202409110900_AddGroupOptionalCompetenciesToSelfAssessmentStructureTable.cs @@ -0,0 +1,23 @@ + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202409110900)] + public class AddGroupOptionalCompetenciesToSelfAssessmentStructureTable : Migration + { + public override void Up() + { + Alter.Table("SelfAssessmentStructure") + .AddColumn("GroupOptionalCompetencies") + .AsCustom("BIT") + .NotNullable() + .WithDefaultValue(0); + } + + public override void Down() + { + Delete.Column("GroupOptionalCompetencies").FromTable("SelfAssessmentStructure"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202409300912_AlterConstraintForCandidateAssessmentsAddUtcDate.cs b/DigitalLearningSolutions.Data.Migrations/202409300912_AlterConstraintForCandidateAssessmentsAddUtcDate.cs new file mode 100644 index 0000000000..d09444bc2c --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202409300912_AlterConstraintForCandidateAssessmentsAddUtcDate.cs @@ -0,0 +1,20 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202409300912)] + public class AlterConstraintForCandidateAssessmentsAddUtcDate : Migration + { + public override void Up() + { + Execute.Sql(@$"ALTER TABLE [dbo].[CandidateAssessments] DROP CONSTRAINT [DF_CandidateAssessments_StartedDate]; + ALTER TABLE [dbo].[CandidateAssessments] ADD CONSTRAINT [DF_CandidateAssessments_StartedDate] DEFAULT (GETUTCDATE()) FOR [StartedDate];"); + } + public override void Down() + { + Execute.Sql(@$"ALTER TABLE [dbo].[CandidateAssessments] DROP CONSTRAINT [DF_CandidateAssessments_StartedDate]; + ALTER TABLE [dbo].[CandidateAssessments] ADD CONSTRAINT [DF_CandidateAssessments_StartedDate] DEFAULT (GETDATE()) FOR [StartedDate];"); + } + + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/AddMinimumOptionalCompetenciesToSelfAssessmentsTable.cs b/DigitalLearningSolutions.Data.Migrations/AddMinimumOptionalCompetenciesToSelfAssessmentsTable.cs new file mode 100644 index 0000000000..bb6fb5333f --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/AddMinimumOptionalCompetenciesToSelfAssessmentsTable.cs @@ -0,0 +1,19 @@ + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202401081132)] + public class AddMinimumOptionalCompetenciesToSelfAssessmentsTable : Migration + { + public override void Up() + { + Alter.Table("SelfAssessments").AddColumn("MinimumOptionalCompetencies").AsInt32().NotNullable().WithDefaultValue(0); + } + + public override void Down() + { + Delete.Column("MinimumOptionalCompetencies").FromTable("SelfAssessments"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs index a20af7d334..79d378b42e 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs @@ -2036,22 +2036,22 @@ internal static string TD_4243_Alter_GetCurrentCoursesForCandidate_V2_proc_down } /// - /// Looks up a localized string similar to - /// - ////****** Object: StoredProcedure [dbo].[GetCurrentCoursesForCandidate_V2] Script Date: 22/07/2024 10:11:35 ******/ - ///SET ANSI_NULLS ON - ///GO - /// - ///SET QUOTED_IDENTIFIER ON - ///GO - /// - ///-- ============================================= - ///-- Author: Kevin Whittaker - ///-- Create date: 16/12/2016 - ///-- Description: Returns a list of active progress records for the candidate. - ///-- Change 18/09/2018: Adds logic to exclude Removed courses from returned results. - ///-- ============================================= - ///ALTER PROCEDURE [dbo].[Ge [rest of string was truncated]";. + /// Looks up a localized string similar to + /// + ////****** Object: StoredProcedure [dbo].[GetCurrentCoursesForCandidate_V2] Script Date: 22/07/2024 10:11:35 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 16/12/2016 + ///-- Description: Returns a list of active progress records for the candidate. + ///-- Change 18/09/2018: Adds logic to exclude Removed courses from returned results. + ///-- ============================================= + ///ALTER PROC [rest of string was truncated]";. /// internal static string TD_4243_Alter_GetCurrentCoursesForCandidate_V2_proc_up { get { @@ -2193,6 +2193,50 @@ internal static string TD_4436_Alter_uspCreateProgressRecordWithCompleteWithinMo } } + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetCompletedCoursesForCandidate] Script Date: 05/09/2024 16:24:49 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 16/12/2016 + ///-- Description: Returns a list of completed courses for the candidate. + ///-- 21/06/2021: Adds Applications.ArchivedDate field to output. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[GetCompletedCoursesFo [rest of string was truncated]";. + /// + internal static string TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN { + get { + return ResourceManager.GetString("TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetCompletedCoursesForCandidate] Script Date: 05/09/2024 16:24:49 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 16/12/2016 + ///-- Description: Returns a list of completed courses for the candidate. + ///-- 21/06/2021: Adds Applications.ArchivedDate field to output. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[GetCompletedCoursesFo [rest of string was truncated]";. + /// + internal static string TD_4634_Alter_GetCompletedCoursesForCandidate_UP { + get { + return ResourceManager.GetString("TD_4634_Alter_GetCompletedCoursesForCandidate_UP", resourceCulture); + } + } + /// /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetActiveAvailableCustomisationsForCentreFiltered_V6] Script Date: 29/09/2022 19:11:04 ******/ ///SET ANSI_NULLS ON diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx index 9cb68a023c..cf95b25f1e 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx @@ -442,4 +442,10 @@ ..\Scripts\TD-4436-Alter_uspCreateProgressRecordWithCompleteWithinMonths_Quiet_V2_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 - + + ..\Scripts\TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD_4634_Alter_GetCompletedCoursesForCandidate_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + \ No newline at end of file diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN.sql new file mode 100644 index 0000000000..55b1405eec Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_DOWN.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_UP.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_UP.sql new file mode 100644 index 0000000000..3e81b198c0 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD_4634_Alter_GetCompletedCoursesForCandidate_UP.sql differ diff --git a/DigitalLearningSolutions.Data/DataServices/ActivityDataService.cs b/DigitalLearningSolutions.Data/DataServices/ActivityDataService.cs index 71ad86bdb2..56c0301129 100644 --- a/DigitalLearningSolutions.Data/DataServices/ActivityDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/ActivityDataService.cs @@ -16,7 +16,24 @@ IEnumerable GetFilteredActivity( int? courseCategoryId, int? customisationId ); - + int GetActivityDetailRowCount( + int centreId, + DateTime startDate, + DateTime? endDate, + int? jobGroupId, + int? courseCategoryId, + int? customisationId + ); + IEnumerable GetFilteredActivityDetail( + int centreId, + DateTime startDate, + DateTime? endDate, + int? jobGroupId, + int? courseCategoryId, + int? customisationId, + int exportQueryRowLimit, + int currentRun + ); DateTime? GetStartOfActivityForCentre(int centreId, int? courseCategoryId = null); } @@ -47,10 +64,10 @@ public IEnumerable GetFilteredActivity( SUM(CAST(Registered AS Int)) AS Registered, SUM(CAST(Completed AS Int)) AS Completed, SUM(CAST(Evaluated AS Int)) AS Evaluated - FROM tActivityLog AS al + FROM tActivityLog AS al INNER JOIN DelegateAccounts AS da ON al.CandidateID = da.ID AND al.CentreID = da.CentreID WHERE (LogDate >= @startDate AND (@endDate IS NULL OR LogDate <= @endDate) - AND CentreID = @centreId + AND (al.CentreID = @centreId) AND (@jobGroupId IS NULL OR JobGroupID = @jobGroupId) AND (@customisationId IS NULL OR al.CustomisationID = @customisationId) AND (@courseCategoryId IS NULL OR al.CourseCategoryId = @courseCategoryId) @@ -74,7 +91,103 @@ GROUP BY Cast(LogDate As Date), LogYear, } ); } - + public int GetActivityDetailRowCount( + int centreId, + DateTime startDate, + DateTime? endDate, + int? jobGroupId, + int? courseCategoryId, + int? customisationId + ) + { + return connection.QuerySingleOrDefault( + @"SELECT COUNT(1) FROM + tActivityLog AS al INNER JOIN DelegateAccounts AS da ON al.CandidateID = da.ID AND al.CentreID = da.CentreID + WHERE(al.LogDate >= @startDate) AND(@endDate IS NULL OR + al.LogDate <= @endDate) AND(al.CentreID = @centreId) AND (@jobGroupId IS NULL OR + al.JobGroupID = @jobGroupId) AND(@customisationId IS NULL OR + al.CustomisationID = @customisationId) AND(@courseCategoryId IS NULL OR + al.CourseCategoryID = @courseCategoryId) AND(al.Registered = 1 OR + al.Completed = 1 OR + al.Evaluated = 1) AND EXISTS + (SELECT ApplicationID + FROM Applications AS ap + WHERE (ApplicationID = al.ApplicationID) AND + (DefaultContentTypeID<> 4))", + new + { + centreId, + startDate, + endDate, + jobGroupId, + customisationId, + courseCategoryId + } + ); + } + public IEnumerable GetFilteredActivityDetail( + int centreId, + DateTime startDate, + DateTime? endDate, + int? jobGroupId, + int? courseCategoryId, + int? customisationId, + int exportQueryRowLimit, + int currentRun + ) + { + return connection.Query( + @"SELECT al.LogID, + al.LogDate, + a.ApplicationName AS CourseName, + c.CustomisationName, + u.FirstName, + u.LastName, + COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, + da.CandidateNumber AS DelegateId, + da.Answer1, + da.Answer2, + da.Answer3, + da.Answer4, + da.Answer5, + da.Answer6, + al.Registered AS Enrolled, + al.Completed, + al.Evaluated + FROM Applications AS a INNER JOIN + tActivityLog AS al ON a.ApplicationID = al.ApplicationID INNER JOIN + Users AS u INNER JOIN + DelegateAccounts AS da ON u.ID = da.UserID ON al.CandidateID = da.ID AND al.CentreID = da.CentreID INNER JOIN + Customisations AS c ON al.CustomisationID = c.CustomisationID LEFT OUTER JOIN + UserCentreDetails AS ucd ON u.ID = ucd.UserID AND c.CentreID = al.CentreID + WHERE (al.LogDate >= @startDate) AND (@endDate IS NULL OR + al.LogDate <= @endDate) AND (al.CentreID = @centreId) AND (@jobGroupId IS NULL OR + al.JobGroupID = @jobGroupId) AND (@customisationId IS NULL OR + al.CustomisationID = @customisationId) AND (@courseCategoryId IS NULL OR + al.CourseCategoryID = @courseCategoryId) AND (al.Registered = 1 OR + al.Completed = 1 OR + al.Evaluated = 1) AND + (u.PrimaryEmail like '%_@_%' OR ucd.Email IS NOT NULL) AND EXISTS + (SELECT ApplicationID + FROM Applications AS ap + WHERE (ApplicationID = al.ApplicationID) AND (DefaultContentTypeID <> 4)) + ORDER BY al.LogDate DESC + OFFSET @exportQueryRowLimit * (@currentRun - 1) ROWS + FETCH NEXT @exportQueryRowLimit ROWS ONLY" + , + new + { + centreId, + startDate, + endDate, + jobGroupId, + customisationId, + courseCategoryId, + exportQueryRowLimit, + currentRun + } + ); + } public DateTime? GetStartOfActivityForCentre(int centreId, int? courseCategoryId = null) { return connection.QuerySingleOrDefault( diff --git a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs index 7664fd1ee9..b56a099c1e 100644 --- a/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CourseDataService.cs @@ -153,7 +153,7 @@ LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = da.UserID AND u WHERE pr.CustomisationID = cu.CustomisationID AND can.CentreID = @centreId AND RemovedDate IS NULL - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%') AS DelegateCount"; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%') AS DelegateCount"; private const string CompletedCountQuery = @"(SELECT COUNT(pr.CandidateID) @@ -164,7 +164,7 @@ INNER JOIN dbo.Users AS u WITH (NOLOCK) ON u.ID = da.UserID LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = da.UserID AND ucd.centreID = da.centreID WHERE pr.CustomisationID = cu.CustomisationID AND pr.Completed IS NOT NULL AND can.CentreID = @centreId - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%') AS CompletedCount"; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%') AS CompletedCount"; private const string AllAttemptsQuery = @"(SELECT COUNT(aa.AssessAttemptID) @@ -533,8 +533,15 @@ LEFT OUTER JOIN UserCentreDetails AS UCD ON ); } } - - if (candidateAssessmentId > 1) + if (candidateAssessmentId > 1 && supervisorDelegateId == 0) + { + connection.Execute( + @"UPDATE CandidateAssessments SET RemovedDate = NULL, EnrolmentMethodId = @enrolmentMethodId, CompleteByDate = @completeByDateDynamic + WHERE ID = @candidateAssessmentId", + new { candidateAssessmentId, enrolmentMethodId, completeByDateDynamic } + ); + } + if (candidateAssessmentId > 1 && supervisorDelegateId !=0) { string sqlQuery = $@" BEGIN TRANSACTION @@ -542,8 +549,8 @@ BEGIN TRANSACTION WHERE ID = @candidateAssessmentId UPDATE CandidateAssessmentSupervisors SET Removed = NULL - {((selfAssessmentSupervisorRoleId > 0) ? " ,SelfAssessmentSupervisorRoleID = @selfAssessmentSupervisorRoleID" : string.Empty)} - WHERE CandidateAssessmentID = @candidateAssessmentId + {((selfAssessmentSupervisorRoleId > 0) ? " ,SelfAssessmentSupervisorRoleID = @selfAssessmentSupervisorRoleID" : string.Empty)} + WHERE CandidateAssessmentID = @candidateAssessmentId COMMIT TRANSACTION"; @@ -1124,7 +1131,7 @@ AND ap.DefaultContentTypeID <> 4 AND ((@answer3 IS NULL) OR ((@answer3 = 'No option selected' OR @answer3 = 'FREETEXTBLANKVALUE') AND (pr.Answer3 IS NULL OR LTRIM(RTRIM(pr.Answer3)) = '')) OR ((@answer3 = 'FREETEXTNOTBLANKVALUE' AND pr.Answer3 IS NOT NULL AND LTRIM(RTRIM(pr.Answer3)) != '') OR (pr.Answer3 IS NOT NULL AND pr.Answer3 = @answer3))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'"; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%'"; string orderBy; string sortOrder; @@ -1632,7 +1639,7 @@ FROM DelegateAccounts AS da WHERE da.CentreID = @centreId AND p.CustomisationID = @customisationId AND ap.DefaultContentTypeID <> 4 - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'", + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%'", new { customisationId, centreId } ); } @@ -1678,7 +1685,7 @@ AND ap.DefaultContentTypeID <> 4 AND ((@answer3 IS NULL) OR ((@answer3 = 'No option selected' OR @answer3 = 'FREETEXTBLANKVALUE') AND (pr.Answer3 IS NULL OR LTRIM(RTRIM(pr.Answer3)) = '')) OR ((@answer3 = 'FREETEXTNOTBLANKVALUE' AND pr.Answer3 IS NOT NULL AND LTRIM(RTRIM(pr.Answer3)) != '') OR (pr.Answer3 IS NOT NULL AND pr.Answer3 = @answer3))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'"; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%'"; var mainSql = "SELECT COUNT(*) AS TotalRecords " + fromTableQuery; @@ -1779,7 +1786,7 @@ AND ap.DefaultContentTypeID <> 4 AND ((@answer3 IS NULL) OR ((@answer3 = 'No option selected' OR @answer3 = 'FREETEXTBLANKVALUE') AND (pr.Answer3 IS NULL OR LTRIM(RTRIM(pr.Answer3)) = '')) OR ((@answer3 = 'FREETEXTNOTBLANKVALUE' AND pr.Answer3 IS NOT NULL AND LTRIM(RTRIM(pr.Answer3)) != '') OR (pr.Answer3 IS NOT NULL AND pr.Answer3 = @answer3))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'"; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%'"; string orderBy; string sortOrder; @@ -1964,7 +1971,7 @@ FROM dbo.CandidateAssessments AS can WITH (NOLOCK) INNER JOIN Users AS u WITH (NOLOCK) ON u.ID = can.DelegateUserID LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = u.ID AND ucd.centreID = can.CentreID WHERE can.CentreID = @centreId AND can.SelfAssessmentID = csa.SelfAssessmentID - AND can.RemovedDate IS NULL AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%') AS DelegateCount, + AND can.RemovedDate IS NULL AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%') AS DelegateCount, (Select COUNT(*) FROM (SELECT can.ID FROM dbo.CandidateAssessments AS can WITH (NOLOCK) LEFT JOIN dbo.CandidateAssessmentSupervisors AS cas ON can.ID = cas.CandidateAssessmentID @@ -2018,7 +2025,7 @@ FROM Customisations WHERE CustomisationID = @customisationID ", new { customisationId }).FirstOrDefault(); - + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/GroupsDataService.cs b/DigitalLearningSolutions.Data/DataServices/GroupsDataService.cs index 687dc46683..f3fc7e3738 100644 --- a/DigitalLearningSolutions.Data/DataServices/GroupsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/GroupsDataService.cs @@ -174,7 +174,7 @@ JOIN DelegateAccounts AS da WITH (NOLOCK) ON da.ID = gd.DelegateID JOIN Users AS u WITH (NOLOCK) ON u.ID = da.UserID LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = u.ID AND ucd.CentreID = da.CentreID WHERE gd.GroupID = g.GroupID - AND (u.PrimaryEmail like '%_@_%.__%' OR ucd.Email is NOT NULL) + AND (u.PrimaryEmail like '%_@_%' OR ucd.Email is NOT NULL) AND da.Approved = 1 AND da.Active = 1) AS DelegateCount, ({CourseCountSql}) AS CoursesCount, g.CreatedByAdminUserID AS AddedByAdminId, @@ -213,7 +213,7 @@ public IEnumerable GetGroupsForCentre(int centreId) new { centreId } ); } -public IEnumerable GetGroupsForRegistrationResponse(int centreId, string? answer1, string? answer2, string? answer3, string? jobGroup, string? answer4, string? answer5, string? answer6) + public IEnumerable GetGroupsForRegistrationResponse(int centreId, string? answer1, string? answer2, string? answer3, string? jobGroup, string? answer4, string? answer5, string? answer6) { return connection.Query( @$"{groupsSql} @@ -337,7 +337,7 @@ FROM GroupDelegates AS gd JOIN Users AS u ON u.ID = da.UserID LEFT JOIN UserCentreDetails AS ucd ON ucd.UserID = u.ID AND ucd.CentreID = da.CentreID WHERE gd.GroupID = @groupId - AND (u.PrimaryEmail like '%_@_%.__%' OR ucd.Email is NOT NULL) + AND (u.PrimaryEmail like '%_@_%' OR ucd.Email is NOT NULL) AND da.Approved = 1 AND da.Active = 1", new { groupId } ); diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs index c724e1d53d..b5437bba3c 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs @@ -137,7 +137,8 @@ FROM SelfAssessmentSupervisorRoles AS SelfAssessmentSupervisorRoles_1 SA.SignOffRequestorStatement, SA.ManageSupervisorsDescription, CA.NonReportable, - U.FirstName +' '+ U.LastName AS DelegateName + U.FirstName +' '+ U.LastName AS DelegateName, + SA.MinimumOptionalCompetencies FROM CandidateAssessments CA JOIN SelfAssessments SA ON CA.SelfAssessmentID = SA.ID @@ -162,7 +163,7 @@ GROUP BY CA.LaunchCount, CA.SubmittedDate, SA.LinearNavigation, SA.UseDescriptionExpanders, SA.ManageOptionalCompetenciesPrompt, SA.SupervisorSelfAssessmentReview, SA.SupervisorResultsReview, SA.ReviewerCommentsLabel,SA.EnforceRoleRequirementsForSignOff, SA.ManageSupervisorsDescription,CA.NonReportable, - U.FirstName , U.LastName", + U.FirstName , U.LastName,SA.MinimumOptionalCompetencies", new { delegateUserId, selfAssessmentId } ); } @@ -328,7 +329,8 @@ public IEnumerable GetCandidateAssessments(int delegateUser DelegateUserID, SelfAssessmentID, CompletedDate, - RemovedDate + RemovedDate, + CentreId FROM CandidateAssessments WHERE SelfAssessmentID = @selfAssessmentId AND DelegateUserID = @delegateUserId", diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs index 333b29cbe9..8304334d0c 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs @@ -456,7 +456,8 @@ public IEnumerable GetCandidateAssessmentOptionalCompetencies(int se CG.ID AS CompetencyGroupID, 'Capability' AS Vocabulary, SAS.Optional, - COALESCE (CAOC.IncludedInSelfAssessment, 0) AS IncludedInSelfAssessment + COALESCE (CAOC.IncludedInSelfAssessment, 0) AS IncludedInSelfAssessment, + SAS.GroupOptionalCompetencies FROM Competencies AS C INNER JOIN CandidateAssessments AS CA ON CA.SelfAssessmentID = @selfAssessmentId AND CA.DelegateUserID = @delegateUserId AND CA.RemovedDate IS NULL diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs index ac3461ca67..1f3e4fc367 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs @@ -170,6 +170,8 @@ int GetSelfAssessmentActivityDelegatesExportCount(string searchString, string so IEnumerable GetAccessor(int selfAssessmentId, int delegateUserID); ActivitySummaryCompetencySelfAssesment? GetActivitySummaryCompetencySelfAssesment(int CandidateAssessmentSupervisorVerificationsId); bool IsUnsupervisedSelfAssessment(int selfAssessmentId); + bool IsCentreSelfAssessment(int selfAssessmentId, int centreId); + bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId); } public partial class SelfAssessmentDataService : ISelfAssessmentDataService @@ -246,7 +248,7 @@ LEFT JOIN dbo.CandidateAssessmentSupervisorVerifications AS casv WITH (NOLOCK) O AND ((@isDelegateActive IS NULL) OR (@isDelegateActive = 1 AND (da.Active = 1)) OR (@isDelegateActive = 0 AND (da.Active = 0))) AND ((@removed IS NULL) OR (@removed = 1 AND (ca.RemovedDate IS NOT NULL)) OR (@removed = 0 AND (ca.RemovedDate IS NULL))) AND ((@submitted IS NULL) OR (@submitted = 1 AND (ca.SubmittedDate IS NOT NULL)) OR (@submitted = 0 AND (ca.SubmittedDate IS NULL))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%' "; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%' "; var groupBy = $@" GROUP BY da.CandidateNumber, @@ -381,7 +383,7 @@ LEFT JOIN dbo.CandidateAssessmentSupervisors AS cas WITH (NOLOCK) ON ca.ID = cas var whereQuery = $@" WHERE sa.ID = @selfAssessmentId AND da.CentreID = @centreID AND csa.CentreID = @centreID AND (ca.RemovedDate IS NULL) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%' "; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%' "; var groupBy = $@" GROUP BY da.CandidateNumber, @@ -471,7 +473,7 @@ LEFT OUTER JOIN AdminAccounts AS aaEnrolledBy WITH (NOLOCK) ON aaEnrolledBy.ID = AND ((@isDelegateActive IS NULL) OR (@isDelegateActive = 1 AND (da.Active = 1)) OR (@isDelegateActive = 0 AND (da.Active = 0))) AND ((@removed IS NULL) OR (@removed = 1 AND (ca.RemovedDate IS NOT NULL)) OR (@removed = 0 AND (ca.RemovedDate IS NULL))) AND ((@submitted IS NULL) OR (@submitted = 1 AND (ca.SubmittedDate IS NOT NULL)) OR (@submitted = 0 AND (ca.SubmittedDate IS NULL))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%' "; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%' "; var groupBy = $@" GROUP BY da.CandidateNumber, @@ -575,7 +577,7 @@ LEFT OUTER JOIN AdminAccounts AS aaEnrolledBy WITH (NOLOCK) ON aaEnrolledBy.ID = AND ((@isDelegateActive IS NULL) OR (@isDelegateActive = 1 AND (da.Active = 1)) OR (@isDelegateActive = 0 AND (da.Active = 0))) AND ((@removed IS NULL) OR (@removed = 1 AND (ca.RemovedDate IS NOT NULL)) OR (@removed = 0 AND (ca.RemovedDate IS NULL))) AND ((@submitted IS NULL) OR (@submitted = 1 AND (ca.SubmittedDate IS NOT NULL)) OR (@submitted = 0 AND (ca.SubmittedDate IS NULL))) - AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%' "; + AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%' "; var groupBy = $@" GROUP BY da.CandidateNumber, @@ -719,5 +721,33 @@ public bool IsUnsupervisedSelfAssessment(int selfAssessmentId) ); return ResultCount > 0; } + + public bool IsCentreSelfAssessment(int selfAssessmentId, int centreId) + { + var ResultCount = connection.ExecuteScalar( + @"SELECT count(*) FROM CentreSelfAssessments WHERE SelfAssessmentID = @selfAssessmentId and CentreID = @centreId", + new { selfAssessmentId, centreId } + ); + return ResultCount > 0; + } + + public bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId) + { + return connection.ExecuteScalar( + @"SELECT CASE WHEN COUNT(SAS.ID)>=(SELECT MinimumOptionalCompetencies FROM SelfAssessments WHERE ID = @selfAssessmentId) + THEN 1 ELSE 0 END AS HasMinimumOptionalCompetencies + FROM CandidateAssessmentOptionalCompetencies AS CAOC + INNER JOIN CandidateAssessments AS CA + ON CAOC.CandidateAssessmentID = CA.ID AND CA.SelfAssessmentID = @selfAssessmentId + AND CA.DelegateUserID = @delegateUserId AND CA.RemovedDate IS NULL + INNER JOIN SelfAssessmentStructure AS SAS + ON CAOC.CompetencyID = SAS.CompetencyID AND CAOC.CompetencyGroupID = SAS.CompetencyGroupID + AND SAS.SelfAssessmentID = @selfAssessmentId + INNER JOIN SelfAssessments AS SA + ON SAS.SelfAssessmentID = SA.ID + WHERE (CAOC.IncludedInSelfAssessment = 1)", + new { selfAssessmentId, delegateUserId } + ); + } } } diff --git a/DigitalLearningSolutions.Data/DataServices/SessionDataService.cs b/DigitalLearningSolutions.Data/DataServices/SessionDataService.cs index c7c0cd0d12..3cc404004f 100644 --- a/DigitalLearningSolutions.Data/DataServices/SessionDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SessionDataService.cs @@ -57,11 +57,29 @@ public void StopDelegateSession(int candidateId) public int UpdateDelegateSessionDuration(int sessionId, DateTime currentUtcTime) { - return connection.Execute( - @"UPDATE Sessions SET Duration = DATEDIFF(minute, LoginTime, @currentUtcTime) - WHERE [SessionID] = @sessionId AND Active = 1;", - new { sessionId, currentUtcTime } - ); + int retryCount = 3; + bool success = false; + var rowsCount = 0; + while (retryCount > 0 && !success) + { + try + { + rowsCount = connection.Execute( + @"UPDATE Sessions SET Duration = DATEDIFF(minute, LoginTime, @currentUtcTime) + WHERE [SessionID] = @sessionId AND Active = 1;", + new { sessionId, currentUtcTime }); + success = true; + } + catch (Exception) + { + retryCount--; + if (retryCount == 0) + { + throw; + } + } + } + return rowsCount; } public int StartAdminSession(int adminId) diff --git a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs index d3af4f6573..da369d6b42 100644 --- a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs @@ -238,7 +238,7 @@ LEFT JOIN UserCentreDetails ucd AND da.CentreID = @centreId", new { delegateEmail, centreId }); } - int existingId = (int)connection.ExecuteScalar( + int existingId = Convert.ToInt32(connection.ExecuteScalar( @" SELECT COALESCE ((SELECT Top 1 ID @@ -253,7 +253,7 @@ FROM SupervisorDelegates sd supervisorAdminId = supervisorAdminId ?? 0, delegateUserId = delegateUserId ?? 0 } - ); + )); if (existingId > 0) { @@ -263,16 +263,6 @@ FROM SupervisorDelegates sd } else { - if (supervisorAdminId == null) - { - supervisorAdminId = (int?)connection.ExecuteScalar( - @"SELECT AdminID FROM AdminUsers WHERE Email = @supervisorEmail AND Active = 1 AND CentreID = @centreId", new { supervisorEmail, centreId } - ); - } - if (supervisorAdminId != null) - { - connection.Execute(@"UPDATE AdminUsers SET Supervisor = 1 WHERE AdminID = @supervisorAdminId AND Supervisor = 0", new { supervisorAdminId }); - } var numberOfAffectedRows = connection.Execute( @"INSERT INTO SupervisorDelegates (SupervisorAdminID, DelegateEmail, DelegateUserID, SupervisorEmail, AddedByDelegate) VALUES (@supervisorAdminId, @delegateEmail, @delegateUserId, @supervisorEmail, @addedByDelegate)", @@ -285,7 +275,7 @@ FROM SupervisorDelegates sd return -1; } - existingId = (int)connection.ExecuteScalar( + existingId = Convert.ToInt32(connection.ExecuteScalar( @" SELECT COALESCE ((SELECT ID @@ -302,7 +292,7 @@ FROM SupervisorDelegates sd supervisorAdminId = supervisorAdminId ?? 0, delegateUserId = delegateUserId ?? 0 } - ); return existingId; + )); return existingId; } } @@ -393,13 +383,21 @@ LEFT JOIN UserCentreDetails ucd public IEnumerable GetSupervisorForEnrolDelegate(int CustomisationID, int CentreID) { return connection.Query( - $@"SELECT AdminID, Forename + ' ' + Surname + ' (' + Email +'),' + ' ' + CentreName AS Name, Email FROM AdminUsers AS au - WHERE (Supervisor = 1) AND (CentreID = @CentreID) AND (CategoryID = 0 OR - CategoryID = (SELECT au.CategoryID FROM Applications AS a INNER JOIN - Customisations AS c ON a.ApplicationID = c.ApplicationID - WHERE (c.CustomisationID = @CustomisationID))) AND (Active = 1) AND (Approved = 1) - GROUP BY AdminID, Surname, Forename, Email, CentreName - ORDER BY Surname, Forename", + $@"SELECT aa.ID AS AdminID, + u.FirstName + ' ' + u.LastName + ' (' + COALESCE(ucd.Email, u.PrimaryEmail) +')' AS Name, + COALESCE(ucd.Email, u.PrimaryEmail) AS Email + FROM AdminAccounts AS aa INNER JOIN + Users AS u ON aa.UserID = u.ID INNER JOIN + Centres AS c ON aa.CentreID = c.CentreID LEFT OUTER JOIN + UserCentreDetails AS ucd ON u.ID = ucd.UserID AND c.CentreID = ucd.CentreID + WHERE (aa.IsSupervisor = 1) AND (c.CentreID = @CentreID) AND + (ISNULL(aa.CategoryID, 0) = 0 OR CategoryID = + (SELECT aa.CategoryID FROM Applications AS a INNER JOIN + Customisations AS c ON a.ApplicationID = c.ApplicationID + WHERE (c.CustomisationID = @CustomisationID))) AND + (aa.Active = 1) + GROUP BY aa.ID, u.LastName, u.FirstName, COALESCE(ucd.Email, u.PrimaryEmail), CentreName + ORDER BY u.FirstName, u.LastName", new { CentreID, CustomisationID }); } @@ -794,15 +792,15 @@ FROM CandidateAssessments } public int InsertCandidateAssessmentSupervisor(int delegateUserId, int supervisorDelegateId, int selfAssessmentId, int? selfAssessmentSupervisorRoleId) { - int candidateAssessmentId = (int)connection.ExecuteScalar( + int candidateAssessmentId = Convert.ToInt32(connection.ExecuteScalar( @"SELECT COALESCE ((SELECT ID FROM CandidateAssessments WHERE (SelfAssessmentID = @selfAssessmentId) AND (DelegateUserID = @delegateUserId) AND (RemovedDate IS NULL) AND (CompletedDate IS NULL)), 0) AS CandidateAssessmentID", - new { selfAssessmentId, delegateUserId }); + new { selfAssessmentId, delegateUserId })); if (candidateAssessmentId > 0) { - var candidateAssessmentSupervisorsId = (int)connection.ExecuteScalar( + var candidateAssessmentSupervisorsId = Convert.ToInt32(connection.ExecuteScalar( @" SELECT COALESCE ((SELECT ID @@ -810,7 +808,7 @@ FROM CandidateAssessmentSupervisors WHERE (CandidateAssessmentID = @candidateAssessmentId) AND (SupervisorDelegateId = @supervisorDelegateId) AND ((SelfAssessmentSupervisorRoleID IS NULL) OR (SelfAssessmentSupervisorRoleID = @selfAssessmentSupervisorRoleId))), 0) AS CandidateAssessmentSupervisorID", new - { candidateAssessmentId, supervisorDelegateId, selfAssessmentSupervisorRoleId }); + { candidateAssessmentId, supervisorDelegateId, selfAssessmentSupervisorRoleId })); if (candidateAssessmentSupervisorsId == 0) { @@ -1067,7 +1065,7 @@ SelfAssessmentStructure AS sas1 INNER JOIN (ca1.ID = ca.ID) AND (caoc1.IncludedInSelfAssessment = 1) AND (NOT (sar1.Result IS NULL)) AND (sasrv.SignedOff = 1) AND (caqrr1.LevelRAG = 3) OR (ca1.ID = ca.ID) AND (sas1.Optional = 0) AND (NOT (sar1.SupportingComments IS NULL)) AND (sasrv.SignedOff = 1) AND (caqrr1.LevelRAG = 3) OR (ca1.ID = ca.ID) AND (caoc1.IncludedInSelfAssessment = 1) AND (NOT (sar1.SupportingComments IS NULL)) AND (sasrv.SignedOff = 1) AND (caqrr1.LevelRAG = 3)) AS MeetingCount, - sa.SignOffSupervisorStatement + sa.SignOffSupervisorStatement,ca.DelegateUserID FROM NRPProfessionalGroups AS npg RIGHT OUTER JOIN NRPSubGroups AS nsg RIGHT OUTER JOIN SelfAssessmentSupervisorRoles AS sasr RIGHT OUTER JOIN diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index ea3ad5759b..687aeec180 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -218,19 +218,41 @@ public List GetAdminUsersByCentreId(int centreId) { var users = connection.Query( @$"{BaseSelectAdminQuery} - WHERE au.Active = 1 AND au.Approved = 1 AND au.CentreId = @centreId", + WHERE au.Active = 1 AND au.Approved = 1 AND au.CentreId = @centreId + ORDER BY au.Forename, au.Surname", new { centreId } ).ToList(); return users; } + public List GetAdminUsersAtCentreForCategory(int centreId, int categoryId) + { + var users = connection.Query( + @$"SELECT + aa.ID AS Id, + COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, + u.FirstName, + u.LastName + FROM AdminAccounts AS aa INNER JOIN + Users AS u ON aa.UserID = u.ID INNER JOIN + Centres AS c ON c.CentreID = aa.CentreID LEFT OUTER JOIN + UserCentreDetails AS ucd ON u.ID = ucd.UserID AND c.CentreID = ucd.CentreID LEFT OUTER JOIN + CourseCategories AS cc ON cc.CourseCategoryID = aa.CategoryID + WHERE aa.Active = 1 AND aa.CentreId = @centreId AND aa.IsSupervisor = 1 AND + (aa.CategoryId = @categoryId OR aa.CategoryId IS NULL) + ORDER BY u.FirstName, u.LastName", + new { centreId, categoryId } + ).ToList(); + + return users; + } public int GetNumberOfAdminsAtCentre(int centreId) { - var count = connection.ExecuteScalar( - @"SELECT COUNT(*) FROM AdminUsers WHERE CentreID = @centreId", - new { centreId } - ); + var count = connection.ExecuteScalar( + @"SELECT COUNT(*) FROM AdminUsers WHERE CentreID = @centreId", + new { centreId } + ); return Convert.ToInt32(count); } diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 5576a5adae..8e2a424ed5 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -187,7 +187,7 @@ LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = da.UserID AND u AND Approved = 1 - AND EmailAddress LIKE '%_@_%.__%'"; + AND EmailAddress LIKE '%_@_%'"; public DelegateUserCard? GetDelegateUserCardById(int id) { var user = connection.Query( @@ -224,7 +224,7 @@ public int GetCountDelegateUserCardsForExportByCentreId(String searchString, str { searchString = searchString.Trim(); } - + if (groupId.HasValue) { var groupDelegatesForCentre = $@"SELECT DelegateID FROM GroupDelegates WHERE GroupID in ( @@ -232,7 +232,7 @@ public int GetCountDelegateUserCardsForExportByCentreId(String searchString, str )"; DelegatewhereConditon += "AND D.ID IN ( " + groupDelegatesForCentre + " AND GroupID = @groupId )"; } - + var delegateCountQuery = @$"SELECT COUNT(*) AS Matches FROM ( " + DelegateUserExportSelectQuery + " ) D " + DelegatewhereConditon; @@ -339,7 +339,7 @@ public List GetDelegateUserCardsForExportByCentreId(String sea )"; if (groupId.HasValue) DelegatewhereConditon += "AND D.ID IN ( " + groupDelegatesForCentre + " AND GroupID = @groupId )"; - + string orderBy; @@ -424,7 +424,7 @@ public List GetDelegatesNotRegisteredForGroupByGroupId(int gro WHERE da.CentreId = @centreId AND da.Approved = 1 AND da.Active = 1 - AND (u.PrimaryEmail like '%_@_%.__%' OR ucd.Email IS NOT NULL) + AND (u.PrimaryEmail like '%_@_%' OR ucd.Email IS NOT NULL) AND NOT EXISTS (SELECT DelegateID FROM GroupDelegates WHERE DelegateID = da.ID AND GroupID = @groupId)", new diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs index 8b1266362a..ca48e7e67b 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs @@ -389,7 +389,15 @@ public void DeactivateDelegateUser(int delegateId) new { delegateId } ); } - + public void DeactivateAdminAccount(int userId ,int centreId) + { + connection.Execute( + @"UPDATE AdminAccounts + SET Active =0 + WHERE UserID = @userId AND CentreID = @centreId", + new { userId, centreId } + ); + } public void ActivateDelegateUser(int delegateId) { connection.Execute( @@ -419,7 +427,15 @@ FROM Candidates new { delegateId } ).Single(); } - + public int? CheckDelegateIsActive(int delegateId) + { + return connection.Query( + @"SELECT CandidateID + FROM Candidates + WHERE CandidateID = @delegateId AND Active =1", + new { delegateId } + ).FirstOrDefault(); + } public void SetDelegateUserLearningHubAuthId(int delegateId, int learningHubAuthId) { connection.Execute( diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index b3cf74f145..aba887ccf5 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -25,6 +25,7 @@ public interface IUserDataService AdminUser? GetAdminUserById(int id); List GetAdminUsersByCentreId(int centreId); + List GetAdminUsersAtCentreForCategory(int centreId, int categoryId); AdminUser? GetAdminUserByEmailAddress(string emailAddress); @@ -290,6 +291,8 @@ int centreId public bool PrimaryEmailInUseAtCentres(string email); public int? GetUserIdFromLearningHubAuthId(int learningHubAuthId); + void DeactivateAdminAccount(int userId, int centreId); + int? CheckDelegateIsActive(int delegateId); } public partial class UserDataService : IUserDataService diff --git a/DigitalLearningSolutions.Data/Enums/SelfAssessmentCompetencyFilter.cs b/DigitalLearningSolutions.Data/Enums/SelfAssessmentCompetencyFilter.cs index 550c3d09b2..f22774a09a 100644 --- a/DigitalLearningSolutions.Data/Enums/SelfAssessmentCompetencyFilter.cs +++ b/DigitalLearningSolutions.Data/Enums/SelfAssessmentCompetencyFilter.cs @@ -2,13 +2,14 @@ { public enum SelfAssessmentCompetencyFilter { - AwaitingConfirmation = -10, - PendingConfirmation = -9, - RequiresSelfAssessment = -8, - SelfAssessed = -7, - ConfirmationRequested = -6, - Verified = -5, /* Confirmed */ - ConfirmationRejected = -4, + AwaitingConfirmation = -11, + PendingConfirmation = -10, + RequiresSelfAssessment = -9, + SelfAssessed = -8, + ConfirmationRequested = -7, + Verified = -6, /* Confirmed */ + ConfirmationRejected = -5, + Optional = -4, MeetingRequirements = -3, PartiallyMeetingRequirements = -2, NotMeetingRequirements = -1 diff --git a/DigitalLearningSolutions.Data/Extensions/ConfigurationExtensions.cs b/DigitalLearningSolutions.Data/Extensions/ConfigurationExtensions.cs index 7d2e2447a5..2dd6ed5cb4 100644 --- a/DigitalLearningSolutions.Data/Extensions/ConfigurationExtensions.cs +++ b/DigitalLearningSolutions.Data/Extensions/ConfigurationExtensions.cs @@ -49,7 +49,17 @@ public static class ConfigurationExtensions private const string LearningHubUserAPIUserAPIUrl = "LearningHubUserApi:UserApiUrl"; private const string UserResearchUrlName = "UserResearchUrl"; - + private const string TableauSectionKey = "TableauDashboards"; + private const string TableauClientId = "ClientId"; + private const string TableauClientSecretId = "ClientSecretId"; + private const string TableauClientSecret = "ClientSecret"; + private const string TableauUsername = "Username"; + private const string TableauClientName = "ClientName"; + private const string TableauSiteUrl = "SiteUrl"; + private const string TableauWorkbookName = "WorkBookName"; + private const string TableauViewName = "ViewName"; + private const string TableauSiteName = "SiteName"; + private const string TableauAuthApi = "AuthApiPath"; public static string GetAppRootPath(this IConfiguration config) { return config[AppRootPathName]!; @@ -180,7 +190,7 @@ public static int GetExportQueryRowLimit(this IConfiguration config) } public static int GetMaxBulkUploadRowsLimit(this IConfiguration config) { - int.TryParse(config[MaxBulkUploadRowsLimitKey],out int limitKey); + int.TryParse(config[MaxBulkUploadRowsLimitKey], out int limitKey); return limitKey; } @@ -201,7 +211,7 @@ public static string GetLearningHubAuthenticationClientSecret(this IConfiguratio public static long GetFreshdeskCreateTicketGroupId(this IConfiguration config) { - long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId); + long.TryParse(config[FreshdeskCreateTicketGroupId], out long ticketGroupId); return ticketGroupId; } public static long GetFreshdeskCreateTicketProductId(this IConfiguration config) @@ -218,5 +228,46 @@ public static string GetUserResearchUrl(this IConfiguration config) { return config[UserResearchUrlName]!; } + public static string GetTableauClientName(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauClientName}"]!; + } + public static string GetTableauClientId(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauClientId}"]!; + } + public static string GetTableauClientSecret(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauClientSecret}"]!; + } + public static string GetTableauClientSecretId(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauClientSecretId}"]!; + } + public static string GetTableauUser(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauUsername}"]!; + } + public static string GetTableauSiteUrl(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauSiteUrl}"]!; + } + public static string GetTableauAuthApi(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauAuthApi}"]!; + } + public static string GetTableauSiteName(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauSiteName}"]!; + } + public static string GetTableauWorkbookName(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauWorkbookName}"]!; + } + public static string GetTableauViewName(this IConfiguration config) + { + return config[$"{TableauSectionKey}:{TableauViewName}"]!; + } + } } diff --git a/DigitalLearningSolutions.Data/Helpers/NewlineSeparatedStringListHelper.cs b/DigitalLearningSolutions.Data/Helpers/NewlineSeparatedStringListHelper.cs index e00f8d560a..12dd4ff09d 100644 --- a/DigitalLearningSolutions.Data/Helpers/NewlineSeparatedStringListHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/NewlineSeparatedStringListHelper.cs @@ -17,7 +17,7 @@ public static string RemoveStringFromNewlineSeparatedList(string list, int index public static string AddStringToNewlineSeparatedList(string? list, string newItem) { var options = list != null ? SplitNewlineSeparatedList(list) : new List(); - options.Add(newItem.Trim()); + options.Add(newItem?.Trim()); return JoinNewlineSeparatedList(options); } diff --git a/DigitalLearningSolutions.Data/Models/BaseLearningItem.cs b/DigitalLearningSolutions.Data/Models/BaseLearningItem.cs index 8364077728..5fb3037ba5 100644 --- a/DigitalLearningSolutions.Data/Models/BaseLearningItem.cs +++ b/DigitalLearningSolutions.Data/Models/BaseLearningItem.cs @@ -12,6 +12,7 @@ public abstract class BaseLearningItem : BaseSearchableItem public bool IsSelfAssessment { get; set; } public bool SelfRegister { get; set; } public bool IncludesSignposting { get; set; } + public bool Active { get; set; } public int? CurrentVersion { get; set; } public override string SearchableName { diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/CandidateAssessment.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/CandidateAssessment.cs index 7d4cd027a5..807dcf1d9c 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/CandidateAssessment.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/CandidateAssessment.cs @@ -13,5 +13,6 @@ public class CandidateAssessment public DateTime? CompletedDate { get; set; } public DateTime? RemovedDate { get; set; } + public int CentreId { get; set; } } } diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/Competency.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/Competency.cs index 9926565d12..654d507a40 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/Competency.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/Competency.cs @@ -16,6 +16,7 @@ public class Competency public string CompetencyGroupDescription { get; set; } = string.Empty; public string? Vocabulary { get; set; } public bool Optional { get; set; } + public bool GroupOptionalCompetencies { get; set; } public bool AlwaysShowDescription { get; set; } public bool IncludedInSelfAssessment { get; set; } public DateTime? Verified { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencySummary.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencySummary.cs new file mode 100644 index 0000000000..96c61f77c0 --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencySummary.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DigitalLearningSolutions.Data.Models.SelfAssessments +{ + public class CompetencySummary + { + public int VerifiedCount { get; set; } + public int QuestionsCount { get; set; } + public bool CanViewCertificate { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs index b668efa91e..1beabfb672 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs @@ -4,6 +4,7 @@ public class SelfAssessment : CurrentLearningItem { public string Description { get; set; } = string.Empty; public int NumberOfCompetencies { get; set; } + public int MinimumOptionalCompetencies { get; set; } public bool LinearNavigation { get; set; } public bool HasDelegateNominatedRoles { get; set; } public bool UseDescriptionExpanders { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/TrackingSystem/ActivityLogDetail.cs b/DigitalLearningSolutions.Data/Models/TrackingSystem/ActivityLogDetail.cs new file mode 100644 index 0000000000..8cc5771dbc --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/TrackingSystem/ActivityLogDetail.cs @@ -0,0 +1,24 @@ +namespace DigitalLearningSolutions.Data.Models.TrackingSystem +{ + using System; + + public class ActivityLogDetail + { + public DateTime LogDate { get; set; } + public string? CourseName { get; set; } + public string? CustomisationName { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? EmailAddress { get; set; } + public string? DelegateId { get; set; } + public string? Answer1 { get; set; } + public string? Answer2 { get; set; } + public string? Answer3 { get; set; } + public string? Answer4 { get; set; } + public string? Answer5 { get; set; } + public string? Answer6 { get; set; } + public bool Enrolled { get; set; } + public bool Completed { get; set; } + public bool Evaluated { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/SuperAdmin/FaqControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/SuperAdmin/FaqControllerTests.cs index c57da88cc7..cc77371ffb 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/SuperAdmin/FaqControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/SuperAdmin/FaqControllerTests.cs @@ -12,6 +12,8 @@ namespace DigitalLearningSolutions.Web.Tests.Controllers.SuperAdmin using FluentAssertions; using FluentAssertions.AspNetCore.Mvc; using NUnit.Framework; + using DigitalLearningSolutions.Data.Extensions; + using Microsoft.Extensions.Configuration; public class FaqControllerTests { @@ -24,12 +26,14 @@ public class FaqControllerTests private SuperAdminFaqsController controller = null!; private IFaqsService faqService = null!; + private IConfiguration configuration = null!; [SetUp] public void Setup() { faqService = A.Fake(); - controller = new SuperAdminFaqsController(faqService); + configuration = A.Fake(); + controller = new SuperAdminFaqsController(faqService, configuration); A.CallTo(() => faqService.GetAllFaqs()) .Returns(faqs); } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/SupervisorController/SupervisorControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/SupervisorController/SupervisorControllerTests.cs index 3a15a7e400..4b7163e7b9 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/SupervisorController/SupervisorControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/SupervisorController/SupervisorControllerTests.cs @@ -31,6 +31,7 @@ public class SupervisorControllerTests private IEmailService emailService = null!; private IClockUtility clockUtility = null!; private ICandidateAssessmentDownloadFileService candidateAssessmentDownloadFileService = null!; + private IPdfService pdfService = null!; [SetUp] public void Setup() @@ -53,7 +54,7 @@ public void Setup() emailService = A.Fake(); clockUtility = A.Fake(); candidateAssessmentDownloadFileService = A.Fake(); - + pdfService = A.Fake(); A.CallTo(() => candidateAssessmentDownloadFileService.GetCandidateAssessmentDownloadFileForCentre(A._, A._, A._)) .Returns(new byte[] { }); } @@ -81,7 +82,8 @@ public void ExportCandidateAssessment_should_return_file_object_with_file_name_i emailGenerationService, emailService, candidateAssessmentDownloadFileService, - clockUtility + clockUtility, + pdfService ); string expectedFileName = $"{((selfAssessmentName.Length > 30) ? selfAssessmentName.Substring(0, 30) : selfAssessmentName)} - {delegateName} - {clockUtility.UtcNow:yyyy-MM-dd}.xlsx"; diff --git a/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessmentTests.cs b/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessmentTests.cs index f8879d45d0..da8ee88292 100644 --- a/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessmentTests.cs +++ b/DigitalLearningSolutions.Web.Tests/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessmentTests.cs @@ -36,7 +36,7 @@ public void Returns_Redirect_to_access_denied_if_delegate_does_not_have_self_ass { // Given var context = GetDefaultContext(); - A.CallTo(() => selfAssessmentService.CanDelegateAccessSelfAssessment(A._, A._)).Returns(false); + A.CallTo(() => selfAssessmentService.CanDelegateAccessSelfAssessment(A._, A._, A._)).Returns(false); // When new VerifyDelegateUserCanAccessSelfAssessment(selfAssessmentService, logger).OnActionExecuting(context); @@ -54,7 +54,7 @@ public void Does_not_return_access_denied_if_delegate_has_self_assessment() { // Given var context = GetDefaultContext(); - A.CallTo(() => selfAssessmentService.CanDelegateAccessSelfAssessment(A._, A._)).Returns(true); + A.CallTo(() => selfAssessmentService.CanDelegateAccessSelfAssessment(A._, A._, A._)).Returns(true); // When new VerifyDelegateUserCanAccessSelfAssessment(selfAssessmentService, logger).OnActionExecuting(context); diff --git a/DigitalLearningSolutions.Web.Tests/Services/ActivityServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/ActivityServiceTests.cs index a731922457..ee4b6328e7 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/ActivityServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/ActivityServiceTests.cs @@ -18,6 +18,7 @@ using FluentAssertions; using FluentAssertions.Execution; using NUnit.Framework; + using Microsoft.Extensions.Configuration; public class ActivityServiceTests { @@ -33,7 +34,8 @@ public class ActivityServiceTests private ICommonService commonService = null!; private IClockUtility clockUtility = null!; private IReportFilterService reportFilterService = null!; - + private IConfiguration configuration = null!; + private ICentreRegistrationPromptsService registrationPromptsService = null!; [SetUp] public void SetUp() { @@ -46,12 +48,16 @@ public void SetUp() selfAssessmentDataService = A.Fake(); commonService = A.Fake(); clockUtility = A.Fake(); + configuration = A.Fake(); + registrationPromptsService = A.Fake(); activityService = new ActivityService( activityDataService, jobGroupsDataService, courseCategoriesDataService, courseDataService, - clockUtility + clockUtility, + configuration, + registrationPromptsService ); reportFilterService = new ReportFilterService( courseCategoriesDataService, @@ -430,7 +436,7 @@ public void GetActivityDataFileForCentre_returns_expected_excel_data() var filterData = new ActivityFilterData( DateTime.Parse("2020-9-1"), - DateTime.Parse("2021-9-1"), + DateTime.Parse("2021-9-30"), null, null, null, diff --git a/DigitalLearningSolutions.Web.Tests/Services/SelfAssessmentServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/SelfAssessmentServiceTests.cs index 3ee3e8ae4c..6b64645920 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/SelfAssessmentServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/SelfAssessmentServiceTests.cs @@ -31,11 +31,12 @@ public void CanDelegateAccessSelfAssessment_returns_true_with_no_completed_or_re .With(ca => ca.CompletedDate = null) .With(ca => ca.RemovedDate = null) .Build().ToList(); + A.CallTo(() => selfAssessmentDataService.IsCentreSelfAssessment(A._, A._)).Returns(true); A.CallTo(() => selfAssessmentDataService.GetCandidateAssessments(A._, A._)) .Returns(candidateAssessments); // When - var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1); + var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1, 1); // Then result.Should().BeTrue(); @@ -54,11 +55,13 @@ public void CanDelegateAccessSelfAssessment_returns_true_with_at_least_one_valid .With(ca => ca.CompletedDate = null) .With(ca => ca.RemovedDate = null) .Build().ToList(); + + A.CallTo(() => selfAssessmentDataService.IsCentreSelfAssessment(A._, A._)).Returns(true); A.CallTo(() => selfAssessmentDataService.GetCandidateAssessments(A._, A._)) .Returns(candidateAssessments); // When - var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1); + var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1, 1); // Then result.Should().BeTrue(); @@ -78,7 +81,7 @@ public void CanDelegateAccessSelfAssessment_returns_false_with_only_completed_or .Returns(candidateAssessments); // When - var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1); + var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1, 1); // Then result.Should().BeFalse(); @@ -92,7 +95,7 @@ public void CanDelegateAccessSelfAssessment_returns_false_no_assessments() .Returns(new List()); // When - var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1); + var result = selfAssessmentService.CanDelegateAccessSelfAssessment(1, 1, 1); // Then result.Should().BeFalse(); diff --git a/DigitalLearningSolutions.Web.Tests/Services/UserServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/UserServiceTests.cs index 0d822251ce..a3270f664e 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/UserServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/UserServiceTests.cs @@ -8,7 +8,7 @@ using DigitalLearningSolutions.Data.DataServices.UserDataService; using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Models; - using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.Tests.TestHelpers; @@ -735,15 +735,13 @@ public void GetSupervisorsAtCentreForCategory_returns_expected_admins() .With(au => au.IsSupervisor = true) .With(au => au.CategoryId = 2) .TheRest().With(au => au.IsSupervisor = false).Build().ToList(); - A.CallTo(() => userDataService.GetAdminUsersByCentreId(A._)).Returns(adminUsers); + A.CallTo(() => userDataService.GetAdminUsersAtCentreForCategory(A._, A._)).Returns(adminUsers); // When var result = userService.GetSupervisorsAtCentreForCategory(1, 1).ToList(); // Then - result.Should().HaveCount(5); - result.Should().OnlyContain(au => au.IsSupervisor); - result.Should().OnlyContain(au => au.CategoryId == null || au.CategoryId == 1); + result.Should().BeEquivalentTo(adminUsers); } [Test] diff --git a/DigitalLearningSolutions.Web.Tests/TestData/ActivityDataDownloadTest.xlsx b/DigitalLearningSolutions.Web.Tests/TestData/ActivityDataDownloadTest.xlsx index 66a79ed788..3a20ee29a6 100644 Binary files a/DigitalLearningSolutions.Web.Tests/TestData/ActivityDataDownloadTest.xlsx and b/DigitalLearningSolutions.Web.Tests/TestData/ActivityDataDownloadTest.xlsx differ diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index b8de66aa8d..bdaa3d4e20 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -59,6 +59,7 @@ public IActionResult ViewFrameworks(string? searchString = null, var adminId = GetAdminId(); var isFrameworkDeveloper = GetIsFrameworkDeveloper(); var isFrameworkContributor = GetIsFrameworkContributor(); + searchString = searchString == null ? null : searchString.Trim(); IEnumerable frameworks; if (tabname == "All") frameworks = frameworkService.GetAllFrameworks(adminId); diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs index 7a47df3a9e..6b959dfc50 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs @@ -23,10 +23,6 @@ using Microsoft.Extensions.Logging; using GDS.MultiPageFormData.Enums; using DigitalLearningSolutions.Data.Helpers; - using DigitalLearningSolutions.Web.Services; - using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.ViewDelegate; - using DocumentFormat.OpenXml.EMMA; - using DigitalLearningSolutions.Data.Models.Supervisor; using DigitalLearningSolutions.Data.Models.Common; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; @@ -56,6 +52,7 @@ from assessmentQuestion in competency.AssessmentQuestions } [NoCaching] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}")] public IActionResult SelfAssessment(int selfAssessmentId) { @@ -81,6 +78,7 @@ public IActionResult SelfAssessment(int selfAssessmentId) return View("SelfAssessments/SelfAssessmentDescription", model); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}")] public IActionResult SelfAssessmentCompetency(int selfAssessmentId, int competencyNumber) { @@ -150,6 +148,7 @@ public IActionResult SelfAssessmentCompetency(int selfAssessmentId, int competen } [HttpPost] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}")] public IActionResult SelfAssessmentCompetency( int selfAssessmentId, @@ -192,9 +191,8 @@ public IActionResult SelfAssessmentCompetency( return SubmitSelfAssessment(assessment, selfAssessmentId, competencyNumber, competencyId, competencyGroupId, updatedAssessmentQuestions, delegateUserId, delegateId); } - [Route( - "/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}/confirm" - )] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] + [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}/confirm")] [HttpGet] public IActionResult ConfirmOverwriteSelfAssessment( int selfAssessmentId, int competencyNumber @@ -229,6 +227,7 @@ public IActionResult ConfirmOverwriteSelfAssessment( "/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}/confirm" )] [HttpPost] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] public IActionResult ConfirmOverwriteSelfAssessment(int selfAssessmentId, int competencyNumber, int competencyId, @@ -314,6 +313,7 @@ IActionResult SubmitSelfAssessment(CurrentSelfAssessment assessment, int selfAss ); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route( "/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Proficiencies/{competencyNumber:int}/{resultId:int}/ViewNotes" )] @@ -421,6 +421,54 @@ public IActionResult AddSelfAssessmentOverviewFilter(SearchSelfAssessmentOvervie ); return RedirectToAction("FilteredSelfAssessmentGroups", model); } + + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] + [Route("LearningPortal/SelfAssessment/{selfAssessmentId}/{vocabulary}/AddOptional")] + public IActionResult AddOptionalCompetencies(int selfAssessmentId, string vocabulary) + { + var delegateUserId = User.GetUserIdKnownNotNull(); + var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId); + if (assessment == null) + { + logger.LogWarning( + $"Attempt to display self assessment overview for user {delegateUserId} with no self assessment" + ); + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + } + + var optionalCompetencies = selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId); + if (optionalCompetencies.Any()) + { + if (!selfAssessmentService.HasMinimumOptionalCompetencies(selfAssessmentId, delegateUserId)) + { + var model = new AddOptionalCompetenciesViewModel { SelfAssessment = assessment }; + return View("SelfAssessments/AddOptionalCompetencies", model); + } + } + + return RedirectToAction("SelfAssessmentOverview", new { selfAssessmentId, vocabulary }); + } + + [HttpPost] + [Route("LearningPortal/SelfAssessment/{selfAssessmentId}/{vocabulary}/AddOptional")] + public IActionResult AddOptionalCompetencies(string vocabulary, int selfAssessmentId) + { + var delegateUserId = User.GetUserIdKnownNotNull(); + var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId); + if (assessment == null) + { + logger.LogWarning( + $"Attempt to display self assessment overview for user {delegateUserId} with no self assessment" + ); + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + } + + TempData["FromAddOptional"] = "true"; + + return RedirectToAction("ManageOptionalCompetencies", new { selfAssessmentId, vocabulary }); + } + + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("LearningPortal/SelfAssessment/{selfAssessmentId}/{vocabulary}/{competencyGroupId}")] [Route("LearningPortal/SelfAssessment/{selfAssessmentId}/{vocabulary}")] public IActionResult SelfAssessmentOverview(int selfAssessmentId, string vocabulary, int? competencyGroupId = null, SearchSelfAssessmentOverviewViewModel searchModel = null) @@ -473,6 +521,7 @@ public IActionResult SelfAssessmentOverview(int selfAssessmentId, string vocabul CompetencyGroups = competencies.GroupBy(competency => competency.CompetencyGroup), PreviousCompetencyNumber = Math.Max(competencies.Count(), 1), NumberOfOptionalCompetencies = optionalCompetencies.Count(), + NumberOfSelfAssessedOptionalCompetencies = optionalCompetencies.Count(x => x.IncludedInSelfAssessment), SupervisorSignOffs = supervisorSignOffs, SearchViewModel = searchViewModel }; @@ -483,12 +532,13 @@ public IActionResult SelfAssessmentOverview(int selfAssessmentId, string vocabul { searchModel.IsSupervisorResultsReviewed = assessment.IsSupervisorResultsReviewed; } - - ViewBag.CanViewCertificate = CertificateHelper.CanViewCertificate(recentResults, model.SupervisorSignOffs); + var competencySummaries = CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs); + ViewBag.CanViewCertificate = competencySummaries.CanViewCertificate; ViewBag.SupervisorSelfAssessmentReview = assessment.SupervisorSelfAssessmentReview; return View("SelfAssessments/SelfAssessmentOverview", model); } [HttpPost] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [SetDlsSubApplication(nameof(DlsSubApplication.LearningPortal))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/CompleteBy")] public IActionResult SetSelfAssessmentCompleteByDate(int selfAssessmentId, EditCompleteByDateFormData formData) @@ -525,6 +575,7 @@ public IActionResult SetSelfAssessmentCompleteByDate(int selfAssessmentId, EditC return RedirectToAction("Current"); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [SetDlsSubApplication(nameof(DlsSubApplication.LearningPortal))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/CompleteBy")] public IActionResult SetSelfAssessmentCompleteByDate(int selfAssessmentId, ReturnPageQuery returnPageQuery) @@ -553,6 +604,7 @@ public IActionResult SetSelfAssessmentCompleteByDate(int selfAssessmentId, Retur return View("Current/SetCompleteByDate", model); } [NoCaching] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Supervisors")] public IActionResult ManageSupervisors(int selfAssessmentId) { @@ -616,6 +668,7 @@ public IActionResult QuickAddSupervisor(int selfAssessmentId, int supervisorDele return RedirectToAction("ManageSupervisors", new { selfAssessmentId }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] public IActionResult StartAddNewSupervisor(int selfAssessmentId) { TempData.Clear(); @@ -654,6 +707,7 @@ public IActionResult StartAddNewSupervisor(int selfAssessmentId) return RedirectToAction("AddNewSupervisor", new { selfAssessmentId }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Supervisors/Add/{page=1:int}")] public IActionResult AddNewSupervisor(int selfAssessmentId, string? searchString = null, @@ -803,6 +857,7 @@ public IActionResult SetSupervisorName(AddSupervisorViewModel model) return RedirectToAction("AddSupervisorSummary", new { model.SelfAssessmentID }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Supervisors/Centre")] public IActionResult SelectSupervisorCentre(int selfAssessmentId) { @@ -886,6 +941,7 @@ public IActionResult SelectSupervisorCentre(SupervisorCentresViewModel model) return RedirectToAction("AddNewSupervisor", new { model.SelfAssessmentID }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route( "/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Supervisors/QuickAdd/{supervisorDelegateId}/Role" )] @@ -993,6 +1049,7 @@ public IActionResult SetSupervisorRole(SetSupervisorRoleViewModel model) return RedirectToAction("ManageSupervisors", new { model.SelfAssessmentID }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/Supervisors/Add/Summary")] [ResponseCache(CacheProfileName = "Never")] public IActionResult AddSupervisorSummary(int selfAssessmentId) @@ -1097,6 +1154,7 @@ public IActionResult SendSupervisorReminder(int selfAssessmentId, int supervisor return RedirectToAction("ManageSupervisors", new { selfAssessmentId }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] public IActionResult StartRequestVerification(int selfAssessmentId) { TempData.Clear(); @@ -1124,6 +1182,7 @@ public IActionResult StartRequestVerification(int selfAssessmentId) return RedirectToAction("VerificationPickSupervisor", new { selfAssessmentId }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/ConfirmationRequests")] public IActionResult ReviewConfirmationRequests(int selfAssessmentId) { @@ -1148,6 +1207,7 @@ public IActionResult ReviewConfirmationRequests(int selfAssessmentId) return View("SelfAssessments/ReviewConfirmationRequests", model); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/ConfirmationRequests/New/ChooseSupervisor")] [ResponseCache(CacheProfileName = "Never")] [TypeFilter( @@ -1220,10 +1280,11 @@ public IActionResult VerificationPickSupervisor(VerificationPickSupervisorViewMo ); return RedirectToAction("VerificationPickResults", new { sessionRequestVerification.SelfAssessmentID }); } + + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/ConfirmationRequests/New/PickResults")] [ResponseCache(CacheProfileName = "Never")] - [TypeFilter( - typeof(RedirectToErrorEmptySessionData), + [TypeFilter(typeof(RedirectToErrorEmptySessionData), Arguments = new object[] { nameof(MultiPageFormDataFeature.AddSelfAssessmentRequestVerification) } )] public IActionResult VerificationPickResults(int selfAssessmentId) @@ -1290,6 +1351,7 @@ public IActionResult VerificationPickResults(VerificationPickResultsViewModel mo return RedirectToAction("VerificationSummary", new { sessionRequestVerification.SelfAssessmentID }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/ConfirmationRequests/New/Summary")] [ResponseCache(CacheProfileName = "Never")] [TypeFilter( @@ -1418,6 +1480,7 @@ public IActionResult SubmitVerification() ); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{vocabulary}/Optional")] public IActionResult ManageOptionalCompetencies(int selfAssessmentId) { @@ -1442,6 +1505,13 @@ public IActionResult ManageOptionalCompetencies(int selfAssessmentId) CompetencyGroups = optionalCompetencies.GroupBy(competency => competency.CompetencyGroup), IncludedSelfAssessmentStructureIds = includedSelfAssessmentStructureIds, }; + + if (TempData["FromAddOptional"] != null) + { + ViewBag.FromAddOptionalPage = "true"; + TempData.Remove("FromAddOptional"); + } + return View("SelfAssessments/ManageOptionalCompetencies", model); } @@ -1468,10 +1538,28 @@ ManageOptionalCompetenciesViewModel model ); } } + if (model.GroupOptionalCompetenciesChecked != null) + { + var optionalCompetencies = + selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId); + foreach (var competencyGroup in model.GroupOptionalCompetenciesChecked) + { + var IncludedSelfAssessmentStructureIds = optionalCompetencies.Where(x => x.CompetencyGroup == competencyGroup).Select(x => x.SelfAssessmentStructureId).ToList(); + foreach (var selfAssessmentStructureId in IncludedSelfAssessmentStructureIds) + { + selfAssessmentService.UpdateCandidateAssessmentOptionalCompetencies( + selfAssessmentStructureId.Value, + delegateUserId + ); + } + } + + } return RedirectToAction("SelfAssessmentOverview", new { selfAssessmentId, vocabulary }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{vocabulary}/RequestSignOff")] public IActionResult RequestSignOff(int selfAssessmentId) { @@ -1479,15 +1567,18 @@ public IActionResult RequestSignOff(int selfAssessmentId) var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId); var supervisors = selfAssessmentService.GetSignOffSupervisorsForSelfAssessmentId(selfAssessmentId, delegateUserId); + var optionalCompetencies = selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId); var model = new RequestSignOffViewModel { SelfAssessment = assessment, Supervisors = supervisors, + NumberOfSelfAssessedOptionalCompetencies = optionalCompetencies.Count(x => x.IncludedInSelfAssessment) }; return View("SelfAssessments/RequestSignOff", model); } [HttpPost] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{vocabulary}/RequestSignOff")] public IActionResult RequestSignOff(int selfAssessmentId, string vocabulary, RequestSignOffViewModel model) { @@ -1497,10 +1588,12 @@ public IActionResult RequestSignOff(int selfAssessmentId, string vocabulary, Req var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId); var supervisors = selfAssessmentService.GetSignOffSupervisorsForSelfAssessmentId(selfAssessmentId, delegateUserId); + var optionalCompetencies = selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId); var newModel = new RequestSignOffViewModel { SelfAssessment = assessment, Supervisors = supervisors, + NumberOfSelfAssessedOptionalCompetencies = optionalCompetencies.Count(x => x.IncludedInSelfAssessment) }; return View("SelfAssessments/RequestSignOff", newModel); } @@ -1536,6 +1629,7 @@ string vocabulary return RedirectToAction("SelfAssessmentOverview", new { selfAssessmentId, vocabulary }); } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{vocabulary}/SignOffHistory")] public IActionResult SignOffHistory(int selfAssessmentId, string vocabulary) { @@ -1617,91 +1711,52 @@ public IActionResult WithdrawSupervisorSignOffRequest( ); } - + [Route("/LearningPortal/selfAssessments/{CandidateAssessmentId:int}/{vocabulary}/Certificate")] public IActionResult CompetencySelfAssessmentCertificate(int CandidateAssessmentId, string vocabulary) { int supervisorDelegateId = 0; var adminId = User.GetAdminId(); - var competencymaindata = selfAssessmentService.GetCompetencySelfAssessmentCertificate(CandidateAssessmentId); - if ((competencymaindata == null)|| ( competencymaindata.LearnerId != User.GetUserIdKnownNotNull()) || (CandidateAssessmentId == 0)) + var userId = User.GetUserIdKnownNotNull(); + var competencymaindata = selfAssessmentService.GetCompetencySelfAssessmentCertificate(CandidateAssessmentId); + if ((competencymaindata == null) || (competencymaindata.LearnerId != User.GetUserIdKnownNotNull()) || (CandidateAssessmentId == 0) || (userId != competencymaindata.LearnerId)) { return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); } var delegateUserId = competencymaindata.LearnerId; - if (vocabulary == "Supervise") - { - var supervisorDelegateDetails = supervisorService.GetSupervisorDelegateDetailsForAdminId(adminId.Value); - var checkSupervisorDelegate = supervisorDelegateDetails.Where(x=> x.DelegateUserID == competencymaindata.LearnerId).FirstOrDefault(); - if (checkSupervisorDelegate == null) - { - return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); - } - var supervisorDelegate = supervisorService.GetSupervisorDelegate(User.GetAdminIdKnownNotNull(), delegateUserId); - supervisorDelegateId = supervisorDelegate.ID; - } var recentResults = selfAssessmentService.GetMostRecentResults(competencymaindata.SelfAssessmentID, competencymaindata.LearnerDelegateAccountId).ToList(); var supervisorSignOffs = selfAssessmentService.GetSupervisorSignOffsForCandidateAssessment(competencymaindata.SelfAssessmentID, delegateUserId); - - if (!CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs)) + var competencySummaries = CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs); + if (!competencySummaries.CanViewCertificate) { return RedirectToAction("StatusCode", "LearningSolutions", new { code = 401 }); } - var competencycount = selfAssessmentService.GetCompetencyCountSelfAssessmentCertificate(competencymaindata.CandidateAssessmentID); var accessors = selfAssessmentService.GetAccessor(competencymaindata.SelfAssessmentID, competencymaindata.LearnerId); var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, competencymaindata.SelfAssessmentID); var competencyIds = recentResults.Select(c => c.Id).ToArray(); var competencyFlags = frameworkService.GetSelectedCompetencyFlagsByCompetecyIds(competencyIds); - var competencies = CompetencyFilterHelper.FilterCompetencies(recentResults, competencyFlags, null); - foreach (var competency in competencies) - { - competency.QuestionLabel = assessment.QuestionLabel; - foreach (var assessmentQuestion in competency.AssessmentQuestions) - { - if (assessmentQuestion.AssessmentQuestionInputTypeID != 2) - { - assessmentQuestion.LevelDescriptors = selfAssessmentService - .GetLevelDescriptorsForAssessmentQuestion( - assessmentQuestion.Id, - assessmentQuestion.MinValue, - assessmentQuestion.MaxValue, - assessmentQuestion.MinValue == 0 - ).ToList(); - } - } - } - - var CompetencyGroups = competencies.GroupBy(competency => competency.CompetencyGroup); - var competencySummaries = from g in CompetencyGroups - let questions = g.SelectMany(c => c.AssessmentQuestions).Where(q => q.Required) - let selfAssessedCount = questions.Count(q => q.Result.HasValue) - let verifiedCount = questions.Count(q => !((q.Result == null || q.Verified == null || q.SignedOff != true) && q.Required)) - - select new - { - SelfAssessedCount = selfAssessedCount, - VerifiedCount = verifiedCount, - Questions = questions.Count() - }; - - int sumVerifiedCount = competencySummaries.Sum(item => item.VerifiedCount); - int sumQuestions = competencySummaries.Sum(item => item.Questions); + int sumVerifiedCount = competencySummaries.VerifiedCount; + int sumQuestions = competencySummaries.QuestionsCount; var activitySummaryCompetencySelfAssesment = selfAssessmentService.GetActivitySummaryCompetencySelfAssesment(competencymaindata.Id); var model = new CompetencySelfAssessmentCertificateViewModel(competencymaindata, competencycount, vocabulary, accessors, activitySummaryCompetencySelfAssesment, sumQuestions, sumVerifiedCount, supervisorDelegateId); return View("SelfAssessments/CompetencySelfAssessmentCertificate", model); } - [Route("DownloadCertificate")] + + [Route("/LearningPortal/selfAssessments/{CandidateAssessmentId:int}/Proficiencies/DownloadCertificate")] public async Task DownloadCertificate(int candidateAssessmentId) { PdfReportStatusResponse pdfReportStatusResponse = new PdfReportStatusResponse(); var delegateId = User.GetCandidateIdKnownNotNull(); + var userId = User.GetUserIdKnownNotNull(); + var competencymaindata = selfAssessmentService.GetCompetencySelfAssessmentCertificate(candidateAssessmentId); - if (competencymaindata == null || candidateAssessmentId == 0) + if (competencymaindata == null || candidateAssessmentId == 0 || userId == 0) { return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); } + if (userId != competencymaindata.LearnerId) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); var delegateUserId = competencymaindata.LearnerId; var competencycount = selfAssessmentService.GetCompetencyCountSelfAssessmentCertificate(candidateAssessmentId); var accessors = selfAssessmentService.GetAccessor(competencymaindata.SelfAssessmentID, competencymaindata.LearnerId); @@ -1711,38 +1766,14 @@ public async Task DownloadCertificate(int candidateAssessmentId) var competencyIds = recentResults.Select(c => c.Id).ToArray(); var competencyFlags = frameworkService.GetSelectedCompetencyFlagsByCompetecyIds(competencyIds); var competencies = CompetencyFilterHelper.FilterCompetencies(recentResults, competencyFlags, null); - foreach (var competency in competencies) + var supervisorSignOffs = selfAssessmentService.GetSupervisorSignOffsForCandidateAssessment(competencymaindata.SelfAssessmentID, delegateUserId); + var competencySummaries = CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs); + if (!competencySummaries.CanViewCertificate) { - competency.QuestionLabel = assessment.QuestionLabel; - foreach (var assessmentQuestion in competency.AssessmentQuestions) - { - if (assessmentQuestion.AssessmentQuestionInputTypeID != 2) - { - assessmentQuestion.LevelDescriptors = selfAssessmentService - .GetLevelDescriptorsForAssessmentQuestion( - assessmentQuestion.Id, - assessmentQuestion.MinValue, - assessmentQuestion.MaxValue, - assessmentQuestion.MinValue == 0 - ).ToList(); - } - } + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 401 }); } - - var CompetencyGroups = competencies.GroupBy(competency => competency.CompetencyGroup); - var competencySummaries = from g in CompetencyGroups - let questions = g.SelectMany(c => c.AssessmentQuestions).Where(q => q.Required) - let selfAssessedCount = questions.Count(q => q.Result.HasValue) - let verifiedCount = questions.Count(q => !((q.Result == null || q.Verified == null || q.SignedOff != true) && q.Required)) - select new - { - SelfAssessedCount = selfAssessedCount, - VerifiedCount = verifiedCount, - Questions = questions.Count() - }; - - int sumVerifiedCount = competencySummaries.Sum(item => item.VerifiedCount); - int sumQuestions = competencySummaries.Sum(item => item.Questions); + int sumVerifiedCount = competencySummaries.VerifiedCount; + int sumQuestions = competencySummaries.QuestionsCount; var model = new CompetencySelfAssessmentCertificateViewModel(competencymaindata, competencycount, "Proficiencies", accessors, activitySummaryCompetencySelfAssesment, sumQuestions, sumVerifiedCount, null); var renderedViewHTML = RenderRazorViewToString(this, "SelfAssessments/DownloadCompetencySelfAssessmentCertificate", model); @@ -1766,7 +1797,7 @@ public async Task DownloadCertificate(int candidateAssessmentId) return View("SelfAssessments/CompetencySelfAssessmentCertificate", model); } - public static string RenderRazorViewToString(Controller controller, string viewName, object model = null) + private static string RenderRazorViewToString(Controller controller, string viewName, object model = null) { controller.ViewData.Model = model; using (var sw = new StringWriter()) diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 0a0f859c63..20280e18a8 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -179,6 +179,7 @@ public async Task ChooseCentre(int centreId, string? returnUrl) { var userEntity = userService.GetUserById(User.GetUserIdKnownNotNull()); var centreAccountSet = userEntity?.GetCentreAccountSet(centreId); + DateHelper.userTimeZone ??= User.GetUserTimeZone(CustomClaimTypes.UserTimeZone); if (centreAccountSet?.IsCentreActive != true) { diff --git a/DigitalLearningSolutions.Web/Controllers/SuperAdmin/SuperAdminFaqsController.cs b/DigitalLearningSolutions.Web/Controllers/SuperAdmin/SuperAdminFaqsController.cs index d72da53a84..bba66f2c43 100644 --- a/DigitalLearningSolutions.Web/Controllers/SuperAdmin/SuperAdminFaqsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/SuperAdmin/SuperAdminFaqsController.cs @@ -11,6 +11,8 @@ namespace DigitalLearningSolutions.Web.Controllers.SuperAdmin using Microsoft.AspNetCore.Mvc; using Microsoft.FeatureManagement.Mvc; using DigitalLearningSolutions.Web.ViewModels.SuperAdmin.Faqs; + using DigitalLearningSolutions.Data.Extensions; + using Microsoft.Extensions.Configuration; [FeatureGate(FeatureFlags.RefactoredSuperAdminInterface)] [SetSelectedTab(nameof(NavMenuTab.System))] @@ -20,10 +22,14 @@ namespace DigitalLearningSolutions.Web.Controllers.SuperAdmin public class SuperAdminFaqsController : Controller { private readonly IFaqsService faqsService; + private readonly IConfiguration configuration; + private readonly string legacyUrl; - public SuperAdminFaqsController(IFaqsService faqsService) + public SuperAdminFaqsController(IFaqsService faqsService, IConfiguration configuration) { this.faqsService = faqsService; + this.configuration = configuration; + legacyUrl = configuration.GetCurrentSystemBaseUrl(); } public IActionResult Index() @@ -37,5 +43,25 @@ public IActionResult Index() return View("SuperAdminFaqs", model); } + [Route("Faqs/Manage")] + public IActionResult ManageFaqs() + { + return Redirect(legacyUrl + "/tracking/admin-faqs"); + } + [Route("Resources")] + public IActionResult Resources() + { + return Redirect(legacyUrl + "/tracking/admin-resources"); + } + [Route("Notifications")] + public IActionResult Notifications() + { + return Redirect(legacyUrl + "/tracking/admin-notifications"); + } + [Route("Brands")] + public IActionResult Brands() + { + return Redirect(legacyUrl + "/tracking/admin-landing"); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs index 7b645c8bb9..7217924776 100644 --- a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs +++ b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs @@ -3,6 +3,7 @@ using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; + using DigitalLearningSolutions.Data.Models.Common; using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; using DigitalLearningSolutions.Data.Models.SelfAssessments; using DigitalLearningSolutions.Data.Models.SessionData.Supervisor; @@ -11,13 +12,19 @@ using DigitalLearningSolutions.Web.Extensions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ServiceFilter; + using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; using DigitalLearningSolutions.Web.ViewModels.Supervisor; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.ViewEngines; + using Microsoft.AspNetCore.Mvc.ViewFeatures; using System; using System.Collections.Generic; + using System.IO; using System.Linq; + using System.Threading.Tasks; public partial class SupervisorController { @@ -384,8 +391,8 @@ public IActionResult ReviewDelegateSelfAssessment(int supervisorDelegateId, int (int)superviseDelegate.DelegateUserID ); } - - ViewBag.CanViewCertificate = CertificateHelper.CanViewCertificate(reviewedCompetencies, model.SupervisorSignOffs); + var competencySummaries = CertificateHelper.CanViewCertificate(reviewedCompetencies, model.SupervisorSignOffs); + model.CompetencySummaries = competencySummaries; ViewBag.SupervisorSelfAssessmentReview = delegateSelfAssessment.SupervisorSelfAssessmentReview; ViewBag.navigatedFrom = selfAssessmentResultId == null; TempData["CertificateSupervisorDelegateId"] = supervisorDelegateId; @@ -1167,13 +1174,15 @@ public IActionResult SignOffProfileAssessment(int supervisorDelegateId, int cand supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); IEnumerable? verificationsSummary = supervisorService.GetCandidateAssessmentSupervisorVerificationSummaries(candidateAssessmentId); + var optionalCompetencies = selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentSummary.SelfAssessmentID, selfAssessmentSummary.DelegateUserID); SignOffProfileAssessmentViewModel? model = new SignOffProfileAssessmentViewModel() { SelfAssessmentResultSummary = selfAssessmentSummary, SupervisorDelegate = supervisorDelegate, CandidateAssessmentSupervisorVerificationId = selfAssessmentSummary?.CandidateAssessmentSupervisorVerificationId, - CandidateAssessmentSupervisorVerificationSummaries = verificationsSummary + CandidateAssessmentSupervisorVerificationSummaries = verificationsSummary, + NumberOfSelfAssessedOptionalCompetencies = optionalCompetencies.Count(x => x.IncludedInSelfAssessment) }; return View("SignOffProfileAssessment", model); } @@ -1186,7 +1195,7 @@ public IActionResult SignOffProfileAssessment( SignOffProfileAssessmentViewModel model ) { - if (!ModelState.IsValid) + if ((!ModelState.IsValid) && (model.NumberOfSelfAssessedOptionalCompetencies > 0) && (!model.OptionalCompetenciesChecked)) { SelfAssessmentResultSummary? selfAssessmentSummary = supervisorService.GetSelfAssessmentResultSummary(candidateAssessmentId, supervisorDelegateId); @@ -1194,13 +1203,15 @@ SignOffProfileAssessmentViewModel model supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); IEnumerable? verificationsSummary = supervisorService.GetCandidateAssessmentSupervisorVerificationSummaries(candidateAssessmentId); + var optionalCompetencies = selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentSummary.SelfAssessmentID, selfAssessmentSummary.DelegateUserID); SignOffProfileAssessmentViewModel? newModel = new SignOffProfileAssessmentViewModel() { SelfAssessmentResultSummary = selfAssessmentSummary, SupervisorDelegate = supervisorDelegate, CandidateAssessmentSupervisorVerificationId = selfAssessmentSummary.CandidateAssessmentSupervisorVerificationId, - CandidateAssessmentSupervisorVerificationSummaries = verificationsSummary + CandidateAssessmentSupervisorVerificationSummaries = verificationsSummary, + NumberOfSelfAssessedOptionalCompetencies = optionalCompetencies.Count(x => x.IncludedInSelfAssessment) }; return View("SignOffProfileAssessment", newModel); } @@ -1381,7 +1392,8 @@ public IActionResult CompetencySelfAssessmentCertificatesupervisor(int candidate var delegateUserId = competencymaindata.LearnerId; var recentResults = selfAssessmentService.GetMostRecentResults(competencymaindata.SelfAssessmentID, competencymaindata.LearnerDelegateAccountId).ToList(); var supervisorSignOffs = selfAssessmentService.GetSupervisorSignOffsForCandidateAssessment(competencymaindata.SelfAssessmentID, delegateUserId); - if (!CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs)) + var competencySummaries = CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs); + if (!competencySummaries.CanViewCertificate) { return RedirectToAction("StatusCode", "LearningSolutions", new { code = 401 }); } @@ -1390,44 +1402,86 @@ public IActionResult CompetencySelfAssessmentCertificatesupervisor(int candidate var accessors = selfAssessmentService.GetAccessor(competencymaindata.SelfAssessmentID, competencymaindata.LearnerId); var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, competencymaindata.SelfAssessmentID); var competencyIds = recentResults.Select(c => c.Id).ToArray(); + int sumVerifiedCount = competencySummaries.VerifiedCount; + int sumQuestions = competencySummaries.QuestionsCount; + var activitySummaryCompetencySelfAssesment = selfAssessmentService.GetActivitySummaryCompetencySelfAssesment(competencymaindata.Id); + var model = new ViewModels.LearningPortal.SelfAssessments.CompetencySelfAssessmentCertificateViewModel(competencymaindata, competencycount, "ProfileAssessment", accessors, activitySummaryCompetencySelfAssesment, sumQuestions, sumVerifiedCount, supervisorDelegateId); + return View("SelfAssessments/CompetencySelfAssessmentCertificate", model); + } + [Route("/Supervisor/Staff/{CandidateAssessmentId:int}/ProfileAssessment/DownloadCertificate")] + public async Task DownloadCertificate(int candidateAssessmentId) + { + PdfReportStatusResponse pdfReportStatusResponse = new PdfReportStatusResponse(); + var delegateId = User.GetCandidateIdKnownNotNull(); + var adminId = User.GetAdminId(); + var competencymaindata = selfAssessmentService.GetCompetencySelfAssessmentCertificate(candidateAssessmentId); + if (competencymaindata == null || candidateAssessmentId == 0 || adminId == 0) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + } + var supervisorDelegateDetails = supervisorService.GetSupervisorDelegateDetailsForAdminId(adminId.Value); + var checkSupervisorDelegate = supervisorDelegateDetails.Where(x => x.DelegateUserID == competencymaindata.LearnerId).FirstOrDefault(); + if (checkSupervisorDelegate == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + var delegateUserId = competencymaindata.LearnerId; + var competencycount = selfAssessmentService.GetCompetencyCountSelfAssessmentCertificate(candidateAssessmentId); + var accessors = selfAssessmentService.GetAccessor(competencymaindata.SelfAssessmentID, competencymaindata.LearnerId); + var activitySummaryCompetencySelfAssesment = selfAssessmentService.GetActivitySummaryCompetencySelfAssesment(competencymaindata.Id); + var assessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, competencymaindata.SelfAssessmentID); + var recentResults = selfAssessmentService.GetMostRecentResults(competencymaindata.SelfAssessmentID, competencymaindata.LearnerDelegateAccountId).ToList(); + var competencyIds = recentResults.Select(c => c.Id).ToArray(); var competencyFlags = frameworkService.GetSelectedCompetencyFlagsByCompetecyIds(competencyIds); var competencies = CompetencyFilterHelper.FilterCompetencies(recentResults, competencyFlags, null); - foreach (var competency in competencies) + var supervisorSignOffs = selfAssessmentService.GetSupervisorSignOffsForCandidateAssessment(competencymaindata.SelfAssessmentID, delegateUserId); + var competencySummaries = CertificateHelper.CanViewCertificate(recentResults, supervisorSignOffs); + if (!competencySummaries.CanViewCertificate) { - competency.QuestionLabel = assessment.QuestionLabel; - foreach (var assessmentQuestion in competency.AssessmentQuestions) + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 401 }); + } + int sumVerifiedCount = competencySummaries.VerifiedCount; + int sumQuestions = competencySummaries.QuestionsCount; + var model = new ViewModels.LearningPortal.SelfAssessments.CompetencySelfAssessmentCertificateViewModel(competencymaindata, competencycount, "Proficiencies", accessors, activitySummaryCompetencySelfAssesment, sumQuestions, sumVerifiedCount, null); + var renderedViewHTML = RenderRazorViewToString(this, "SelfAssessments/DownloadCompetencySelfAssessmentCertificate", model); + + var pdfReportResponse = await pdfService.PdfReport(candidateAssessmentId.ToString(), renderedViewHTML, delegateId); + if (pdfReportResponse != null) + { + do { - if (assessmentQuestion.AssessmentQuestionInputTypeID != 2) - { - assessmentQuestion.LevelDescriptors = selfAssessmentService - .GetLevelDescriptorsForAssessmentQuestion( - assessmentQuestion.Id, - assessmentQuestion.MinValue, - assessmentQuestion.MaxValue, - assessmentQuestion.MinValue == 0 - ).ToList(); - } + pdfReportStatusResponse = await pdfService.PdfReportStatus(pdfReportResponse); + } while (pdfReportStatusResponse.Id == 1); + + var pdfReportFile = await pdfService.GetPdfReportFile(pdfReportResponse); + if (pdfReportFile != null) + { + var nameTextLength = string.IsNullOrEmpty(model.CompetencySelfAssessmentCertificates.LearnerName) ? 0 : model.CompetencySelfAssessmentCertificates.LearnerName.Length; + var isPrnExist = !string.IsNullOrEmpty(model.CompetencySelfAssessmentCertificates.LearnerPRN); + var fileName = $"Competency Certificate - {model.CompetencySelfAssessmentCertificates.LearnerName.Substring(0, nameTextLength >= 15 ? 15 : nameTextLength)}" + (isPrnExist ? $" - {model.CompetencySelfAssessmentCertificates.LearnerPRN}.pdf" : ".pdf"); + return File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); } } - - var CompetencyGroups = competencies.GroupBy(competency => competency.CompetencyGroup); - var competencySummaries = from g in CompetencyGroups - let questions = g.SelectMany(c => c.AssessmentQuestions).Where(q => q.Required) - let selfAssessedCount = questions.Count(q => q.Result.HasValue) - let verifiedCount = questions.Count(q => !((q.Result == null || q.Verified == null || q.SignedOff != true) && q.Required)) - - select new - { - SelfAssessedCount = selfAssessedCount, - VerifiedCount = verifiedCount, - Questions = questions.Count() - }; - - int sumVerifiedCount = competencySummaries.Sum(item => item.VerifiedCount); - int sumQuestions = competencySummaries.Sum(item => item.Questions); - var activitySummaryCompetencySelfAssesment = selfAssessmentService.GetActivitySummaryCompetencySelfAssesment(competencymaindata.Id); - var model = new ViewModels.LearningPortal.SelfAssessments.CompetencySelfAssessmentCertificateViewModel(competencymaindata, competencycount, "ProfileAssessment", accessors, activitySummaryCompetencySelfAssesment, sumQuestions, sumVerifiedCount, supervisorDelegateId); return View("SelfAssessments/CompetencySelfAssessmentCertificate", model); } + private static string RenderRazorViewToString(Controller controller, string viewName, object model = null) + { + controller.ViewData.Model = model; + using (var sw = new StringWriter()) + { + IViewEngine viewEngine = + controller.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as + ICompositeViewEngine; + ViewEngineResult viewResult = viewEngine.FindView(controller.ControllerContext, viewName, false); + + ViewContext viewContext = new ViewContext( + controller.ControllerContext, + viewResult.View, + controller.ViewData, + controller.TempData, + sw, + new HtmlHelperOptions() + ); + viewResult.View.RenderAsync(viewContext); + return sw.GetStringBuilder().ToString(); + } + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/SupervisorController/SupervisorController.cs b/DigitalLearningSolutions.Web/Controllers/SupervisorController/SupervisorController.cs index 01aaaf5289..459bbe7f0e 100644 --- a/DigitalLearningSolutions.Web/Controllers/SupervisorController/SupervisorController.cs +++ b/DigitalLearningSolutions.Web/Controllers/SupervisorController/SupervisorController.cs @@ -26,6 +26,7 @@ public partial class SupervisorController : Controller private readonly IEmailService emailService; private readonly ICandidateAssessmentDownloadFileService candidateAssessmentDownloadFileService; private readonly IClockUtility clockUtility; + private readonly IPdfService pdfService; public SupervisorController( ISupervisorService supervisorService, @@ -45,7 +46,8 @@ public SupervisorController( IEmailGenerationService emailGenerationService, IEmailService emailService, ICandidateAssessmentDownloadFileService candidateAssessmentDownloadFileService, - IClockUtility clockUtility + IClockUtility clockUtility, + IPdfService pdfService ) { this.supervisorService = supervisorService; @@ -62,6 +64,7 @@ IClockUtility clockUtility this.emailService = emailService; this.candidateAssessmentDownloadFileService = candidateAssessmentDownloadFileService; this.clockUtility = clockUtility; + this.pdfService = pdfService; } private int GetCentreId() diff --git a/DigitalLearningSolutions.Web/Controllers/Support/RequestSupportTicketController.cs b/DigitalLearningSolutions.Web/Controllers/Support/RequestSupportTicketController.cs index 8853fbc9be..3f58a7ec7f 100644 --- a/DigitalLearningSolutions.Web/Controllers/Support/RequestSupportTicketController.cs +++ b/DigitalLearningSolutions.Web/Controllers/Support/RequestSupportTicketController.cs @@ -234,10 +234,14 @@ public IActionResult DeleteImage(DlsSubApplication dlsSubApplication, string ima [Route("/{dlsSubApplication}/RequestSupport/SupportSummary")] public IActionResult SupportSummary(DlsSubApplication dlsSubApplication, SupportSummaryViewModel supportSummaryViewModel) { + if (!TempData.Any()) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } var data = multiPageFormService.GetMultiPageFormData( MultiPageFormDataFeature.AddCustomWebForm("RequestSupportTicketCWF"), TempData - ).GetAwaiter().GetResult(); ; + ).GetAwaiter().GetResult(); var model = new SupportSummaryViewModel(data); return View("SupportTicketSummaryPage", model); } @@ -245,12 +249,15 @@ public IActionResult SupportSummary(DlsSubApplication dlsSubApplication, Support [HttpPost] [Route("/{dlsSubApplication}/RequestSupport/SubmitSupportSummary")] public IActionResult SubmitSupportSummary(DlsSubApplication dlsSubApplication, SupportSummaryViewModel model) - { + if (!TempData.Any()) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 401 }); + } var data = multiPageFormService.GetMultiPageFormData( MultiPageFormDataFeature.AddCustomWebForm("RequestSupportTicketCWF"), TempData - ).GetAwaiter().GetResult(); ; + ).GetAwaiter().GetResult(); data.GroupId = configuration.GetFreshdeskCreateTicketGroupId(); data.ProductId = configuration.GetFreshdeskCreateTicketProductId(); List RequestAttachmentList = new List(); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs index e6de70428d..9bb965bd66 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/Dashboard/DashboardController.cs @@ -29,7 +29,7 @@ ISystemNotificationsService systemNotificationsService this.dashboardInformationService = dashboardInformationService; this.systemNotificationsService = systemNotificationsService; } - + [NoCaching] public IActionResult Index() { var adminId = User.GetAdminId()!.Value; diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SelfAssessmentReports/SelfAssessmentReportsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SelfAssessmentReports/SelfAssessmentReportsController.cs index 8955b4b2a6..dccb1d0301 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SelfAssessmentReports/SelfAssessmentReportsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SelfAssessmentReports/SelfAssessmentReportsController.cs @@ -11,6 +11,10 @@ using System; using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports; + using DigitalLearningSolutions.Web.Helpers.ExternalApis; + using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; + using DigitalLearningSolutions.Data.Extensions; [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] [Authorize(Policy = CustomPolicies.UserCentreAdmin)] @@ -20,15 +24,26 @@ public class SelfAssessmentReportsController : Controller { private readonly ISelfAssessmentReportService selfAssessmentReportService; + private readonly ITableauConnectionHelperService tableauConnectionHelper; private readonly IClockUtility clockUtility; - + private readonly string tableauUrl; + private readonly string tableauSiteName; + private readonly string workbookName; + private readonly string viewName; public SelfAssessmentReportsController( ISelfAssessmentReportService selfAssessmentReportService, - IClockUtility clockUtility + ITableauConnectionHelperService tableauConnectionHelper, + IClockUtility clockUtility, + IConfiguration config ) { this.selfAssessmentReportService = selfAssessmentReportService; + this.tableauConnectionHelper = tableauConnectionHelper; this.clockUtility = clockUtility; + tableauUrl = config.GetTableauSiteUrl(); + tableauSiteName = config.GetTableauSiteName(); + workbookName = config.GetTableauWorkbookName(); + viewName = config.GetTableauViewName(); } public IActionResult Index() { @@ -63,5 +78,19 @@ public IActionResult DownloadSelfAssessmentReport(int selfAssessmentId) fileName ); } + [HttpGet] + [Route("TableauCompetencyDashboard")] + public IActionResult TableauCompetencyDashboard() + { + var userEmail = User.GetUserPrimaryEmail(); + var jwt = tableauConnectionHelper.GetTableauJwt(userEmail); + ViewBag.SiteName = tableauSiteName; + ViewBag.TableauServerUrl = tableauUrl; + ViewBag.WorkbookName = workbookName; + ViewBag.ViewName = viewName; + ViewBag.JwtToken = jwt; + + return View(); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SystemNotificationsController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SystemNotificationsController.cs index 3b90799836..25961c87f8 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SystemNotificationsController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Centre/SystemNotificationsController.cs @@ -36,6 +36,7 @@ ISearchSortFilterPaginateService searchSortFilterPaginateService } [HttpGet] + [NoCaching] [Route("{page:int=1}")] public IActionResult Index(int page = 1) { diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs index 70b7d60f2f..b8f86fa872 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs @@ -13,18 +13,16 @@ using DigitalLearningSolutions.Web.Models.Enums; using DigitalLearningSolutions.Web.ServiceFilter; using DigitalLearningSolutions.Web.Services; - using DigitalLearningSolutions.Web.ViewModels.Supervisor; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates; using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.FeatureManagement.Mvc; - using Pipelines.Sockets.Unofficial; using System; using System.Collections.Generic; using System.Linq; - using System.Net; + using DateHelper = DigitalLearningSolutions.Web.Helpers.DateHelper; [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] [Authorize(Policy = CustomPolicies.UserCentreAdmin)] @@ -224,11 +222,17 @@ public IActionResult Index( (courseDelegatesData, resultCount) = courseDelegatesService.GetCoursesAndCourseDelegatesPerPageForCentre(searchString ?? string.Empty, offSet, itemsPerPage ?? 0, sortBy, sortDirection, customisationId, centreId, adminCategoryId, isDelegateActive, isProgressLocked, removed, hasCompleted, answer1, answer2, answer3); } + foreach (var courseDelegate in courseDelegatesData.Delegates) + { + courseDelegate.Enrolled = (DateTime)DateHelper.GetLocalDateTime(courseDelegate.Enrolled); + courseDelegate.LastUpdated = DateHelper.GetLocalDateTime(courseDelegate.LastUpdated); + courseDelegate.Completed = courseDelegate.Completed?.TimeOfDay == TimeSpan.Zero ? courseDelegate.Completed : DateHelper.GetLocalDateTime(courseDelegate.Completed); + } } else { (selfAssessmentDelegatesData, resultCount) = selfAssessmentService.GetSelfAssessmentDelegatesPerPage(searchString ?? string.Empty, offSet, itemsPerPage ?? 0, sortBy, sortDirection, - selfAssessmentId, centreId, isDelegateActive, removed, submitted, signedOff); + selfAssessmentId, centreId, isDelegateActive, removed, submitted, signedOff); if (selfAssessmentDelegatesData?.Delegates?.Count() == 0 && resultCount > 0) { @@ -239,19 +243,22 @@ public IActionResult Index( var adminId = User.GetCustomClaimAsRequiredInt(CustomClaimTypes.UserAdminId); - foreach (var delagate in selfAssessmentDelegatesData.Delegates ?? Enumerable.Empty()) + foreach (var saDelegate in selfAssessmentDelegatesData.Delegates ?? Enumerable.Empty()) { - var competencies = selfAssessmentService.GetCandidateAssessmentResultsById(delagate.CandidateAssessmentsId, adminId).ToList(); + var competencies = selfAssessmentService.GetCandidateAssessmentResultsById(saDelegate.CandidateAssessmentsId, adminId).ToList(); if (competencies?.Count() > 0) { var questions = competencies.SelectMany(c => c.AssessmentQuestions).Where(q => q.Required); var selfAssessedCount = questions.Count(q => q.Result.HasValue); var verifiedCount = questions.Count(q => !((q.Result == null || q.Verified == null || q.SignedOff != true) && q.Required)); - delagate.Progress = "Self assessed: " + selfAssessedCount + " / " + questions.Count() + Environment.NewLine + + saDelegate.Progress = "Self assessed: " + selfAssessedCount + " / " + questions.Count() + Environment.NewLine + "Confirmed: " + verifiedCount + " / " + questions.Count(); - } + saDelegate.StartedDate = (DateTime)DateHelper.GetLocalDateTime(saDelegate.StartedDate); + saDelegate.LastAccessed = DateHelper.GetLocalDateTime(saDelegate.LastAccessed); + saDelegate.SubmittedDate = DateHelper.GetLocalDateTime(saDelegate.SubmittedDate); + saDelegate.RemovedDate = DateHelper.GetLocalDateTime(saDelegate.RemovedDate); } } @@ -503,6 +510,7 @@ public IActionResult RemoveDelegateSelfAssessment(DelegateSelfAssessmenteViewMod } [HttpGet] + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("{selfAssessmentId:int}/EditCompleteByDate")] public IActionResult EditCompleteByDate( int delegateUserId, diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/BulkUploadController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/BulkUploadController.cs index 862bb67f95..c8e078e24d 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/BulkUploadController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/BulkUploadController.cs @@ -147,6 +147,10 @@ public IActionResult UploadComplete() [Route("WelcomeEmail")] public IActionResult WelcomeEmail() { + if (!TempData.Any()) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } var data = GetBulkUploadData(); var model = new WelcomeEmailViewModel() { Day = data.Day, Month = data.Month, Year = data.Year, DelegatesToRegister = data.ToRegisterActiveCount + data.ToRegisterInactiveCount }; return View(model); @@ -265,6 +269,10 @@ public IActionResult SubmitAddWhoToGroup(AddWhoToGroupViewModel model) [Route("UploadSummary")] public IActionResult UploadSummary() { + if (!TempData.Any()) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } var data = GetBulkUploadData(); var centreId = User.GetCentreIdKnownNotNull(); string? groupName; @@ -295,6 +303,10 @@ public IActionResult UploadSummary() [HttpPost] public IActionResult StartProcessing() { + if (!TempData.Any()) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } var centreId = User.GetCentreIdKnownNotNull(); var data = GetBulkUploadData(); var adminId = User.GetAdminIdKnownNotNull(); diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DeactivateDelegateController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DeactivateDelegateController.cs new file mode 100644 index 0000000000..c0493c085d --- /dev/null +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/DeactivateDelegateController.cs @@ -0,0 +1,95 @@ + + +namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Delegates +{ + using System.Collections.Generic; + using System.Linq; + using DigitalLearningSolutions.Data.Enums; + using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Web.Attributes; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.Models.Enums; + using DigitalLearningSolutions.Web.ServiceFilter; + using DigitalLearningSolutions.Web.Services; + using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.DeactivateDelegate; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.FeatureManagement.Mvc; + + + [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] + [Authorize(Policy = CustomPolicies.UserCentreAdmin)] + [ServiceFilter(typeof(VerifyAdminUserCanAccessDelegateUser))] + [Route("TrackingSystem/Delegates/{delegateId:int}/Deactivate")] + [SetDlsSubApplication(nameof(DlsSubApplication.TrackingSystem))] + [SetSelectedTab(nameof(NavMenuTab.Delegates))] + public class DeactivateDelegateController : Controller + { + private readonly IUserService userService; + public DeactivateDelegateController( + IUserService userService + ) + { + this.userService = userService; + } + [HttpGet] + public IActionResult Index(int delegateId) + { + var checkDelegate = userService.CheckDelegateIsActive(delegateId); + if (checkDelegate != delegateId) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } + var centreId = User.GetCentreId(); + var delegateEntity = userService.GetDelegateById(delegateId)!; + var userEntity = userService.GetUserById(delegateEntity.DelegateAccount.UserId); + var adminAccount = userEntity!.GetCentreAccountSet(centreId)?.AdminAccount; + var roles = GetRoles(adminAccount, userEntity); + var model = new DeactivateDelegateAccountViewModel + { + DelegateId = delegateId, + Name = delegateEntity.UserAccount.FirstName + " " + delegateEntity.UserAccount.LastName, + Roles = roles, + Email = delegateEntity.UserAccount.PrimaryEmail, + UserId = delegateEntity.UserAccount.Id + }; + return View(model); + } + + [HttpPost] + public IActionResult Index(DeactivateDelegateAccountViewModel deactivateDelegateAccountViewModel) + { + var centreId = User.GetCentreId(); + if (!ModelState.IsValid) + { + var delegateEntity = userService.GetDelegateById(deactivateDelegateAccountViewModel.DelegateId)!; + var userEntity = userService.GetUserById(delegateEntity.DelegateAccount.UserId); + var adminAccount = userEntity!.GetCentreAccountSet(centreId)?.AdminAccount; + var roles = GetRoles(adminAccount, userEntity); + deactivateDelegateAccountViewModel.Roles = roles; + return View(deactivateDelegateAccountViewModel); + } + + if (deactivateDelegateAccountViewModel.Deactivate == true ) + { + userService.DeactivateDelegateUser(deactivateDelegateAccountViewModel.DelegateId); + return RedirectToAction("Index", "ViewDelegate", new { deactivateDelegateAccountViewModel.DelegateId }); + } + userService.DeactivateDelegateUser(deactivateDelegateAccountViewModel.DelegateId); + userService.DeactivateAdminAccount(deactivateDelegateAccountViewModel.UserId, centreId.Value); + return RedirectToAction("Index", "ViewDelegate", new { deactivateDelegateAccountViewModel.DelegateId }); + + } + private List? GetRoles(AdminAccount? adminAccount, UserEntity userEntity) + { + var roles = new List(); + if (adminAccount != null) + { + var adminentity = new AdminEntity(adminAccount, userEntity.UserAccount, null); + roles = FilterableTagHelper.GetCurrentTagsForAdmin(adminentity).Where(s => s.Hidden == false && s.DisplayText != "Active") + .Select(d => d.DisplayText).ToList(); + } + return roles; + } + } +} diff --git a/DigitalLearningSolutions.Web/Controllers/UserFeedbackController.cs b/DigitalLearningSolutions.Web/Controllers/UserFeedbackController.cs index 7e974d7049..f3d5178667 100644 --- a/DigitalLearningSolutions.Web/Controllers/UserFeedbackController.cs +++ b/DigitalLearningSolutions.Web/Controllers/UserFeedbackController.cs @@ -30,7 +30,7 @@ IMultiPageFormService multiPageFormService this._userFeedbackService = userFeedbackService; this._multiPageFormService = multiPageFormService; this._userFeedbackViewModel = new UserFeedbackViewModel(); - this.config = config; + this.config = config; } [Route("/Index")] @@ -38,7 +38,7 @@ public IActionResult Index(string sourceUrl, string sourcePageTitle) { ViewData[LayoutViewDataKeys.DoNotDisplayUserFeedbackBar] = true; _multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddUserFeedback, TempData); - + _userFeedbackViewModel = new() { UserId = User.GetUserId(), @@ -51,13 +51,13 @@ public IActionResult Index(string sourceUrl, string sourcePageTitle) TaskRating = null, }; - if(sourcePageTitle == "Digital Learning Solutions - Page no longer available") + if (sourcePageTitle == "Digital Learning Solutions - Page no longer available") { var url = ContentUrlHelper.ReplaceUrlSegment(sourceUrl); - _userFeedbackViewModel.SourceUrl = url; + _userFeedbackViewModel.SourceUrl = url; _userFeedbackViewModel.SourcePageTitle = "Welcome"; } - + if (_userFeedbackViewModel.UserId == null || _userFeedbackViewModel.UserId == 0) { return GuestFeedbackStart(_userFeedbackViewModel); @@ -247,6 +247,9 @@ public IActionResult UserFeedbackTaskDifficulty(UserFeedbackViewModel userFeedba )] public IActionResult UserFeedbackTaskDifficultySet(UserFeedbackViewModel userFeedbackViewModel) { + //set the SourceURL to blank so we can retrieve the original sourceURL whether current/completed/available + userFeedbackViewModel.SourceUrl = null; + userFeedbackViewModel = MapMultiformDataToViewModel(userFeedbackViewModel); SaveMultiPageFormData(userFeedbackViewModel); @@ -277,7 +280,6 @@ public IActionResult UserFeedbackSave(UserFeedbackViewModel userFeedbackViewMode transaction.Complete(); - userFeedbackViewModel.SourceUrl = data.SourceUrl; return RedirectToAction("UserFeedbackComplete", userFeedbackViewModel); } diff --git a/DigitalLearningSolutions.Web/Extensions/EnumExtensions.cs b/DigitalLearningSolutions.Web/Extensions/EnumExtensions.cs index 34c7fdc5c8..b0d6366737 100644 --- a/DigitalLearningSolutions.Web/Extensions/EnumExtensions.cs +++ b/DigitalLearningSolutions.Web/Extensions/EnumExtensions.cs @@ -19,6 +19,8 @@ public static string GetDescription(this SelfAssessmentCompetencyFilter status, { switch (status) { + case SelfAssessmentCompetencyFilter.Optional: + return "Optional"; case SelfAssessmentCompetencyFilter.RequiresSelfAssessment: return "Requires self assessment"; case SelfAssessmentCompetencyFilter.SelfAssessed: diff --git a/DigitalLearningSolutions.Web/Helpers/CertificateHelper.cs b/DigitalLearningSolutions.Web/Helpers/CertificateHelper.cs index b5ffe50544..7d107df12f 100644 --- a/DigitalLearningSolutions.Web/Helpers/CertificateHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/CertificateHelper.cs @@ -10,11 +10,9 @@ namespace DigitalLearningSolutions.Web.Helpers { public class CertificateHelper { - public static bool CanViewCertificate(List reviewedCompetencies, IEnumerable? SupervisorSignOffs) + public static CompetencySummary CanViewCertificate(List reviewedCompetencies, IEnumerable? SupervisorSignOffs) { - var CompetencyGroups = reviewedCompetencies.GroupBy(competency => competency.CompetencyGroup); - var competencySummaries = CompetencyGroups.Select(g => { var questions = g.SelectMany(c => c.AssessmentQuestions).Where(q => q.Required); @@ -38,9 +36,15 @@ public static bool CanViewCertificate(List reviewedCompetencies, IEn var allComptConfirmed = competencySummaries.Count() == 0 ? false : competencySummaries.Sum(c => c.VerifiedCount) == competencySummaries.Sum(c => c.QuestionsCount); - return SupervisorSignOffs?.FirstOrDefault()?.Verified != null && - SupervisorSignOffs.FirstOrDefault().SignedOff && - allComptConfirmed && latestResult <= latestSignoff; + var model = new CompetencySummary() + { + VerifiedCount = competencySummaries.Sum(item => item.VerifiedCount), + QuestionsCount = competencySummaries.Sum(item => item.QuestionsCount), + CanViewCertificate = SupervisorSignOffs?.FirstOrDefault()?.Verified != null && + SupervisorSignOffs.FirstOrDefault().SignedOff && + allComptConfirmed && latestResult <= latestSignoff + }; + return model; } } } diff --git a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs index d91f9934b8..9cd480a9dd 100644 --- a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs +++ b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs @@ -20,7 +20,7 @@ public static class CommonValidationErrorMessages "A user with this email address is already registered; if this is you, please log in using the button below"; public const string WrongEmailForCentreDuringAdminRegistration = - "This email address does not match the one held by the centre; either your primary email or centre email must match the one held by the centre"; + "This email address does not match the one held by the centre; your primary email must match the one held by the centre"; public const string PasswordRegex = @"(?=.*?[^\w\s])(?=.*?[0-9])(?=.*?[A-Z])(?=.*?[a-z]).*"; public const string PasswordInvalidCharacters = "Password must contain at least 1 uppercase and 1 lowercase letter, 1 number and 1 symbol"; diff --git a/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs b/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs index 7ae5111afe..26b0e181ab 100644 --- a/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs @@ -34,22 +34,23 @@ private static void ApplyResponseStatusFilters(ref IEnumerable compe { var filteredCompetencies = competencies; var appliedResponseStatusFilters = filters.Where(f => IsResponseStatusFilter(f)); + if (appliedResponseStatusFilters.Any() || searchText.Length > 0) { var wordsInSearchText = searchText.Split().Where(w => w != string.Empty); - filters = appliedResponseStatusFilters; filteredCompetencies = from c in competencies let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) - let responseStatusFilterMatchesAnyQuestion = - (filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) && c.AssessmentQuestions.Any(q => q.ResultId == null)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRequested) && c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true)) + let responseStatusFilterMatchesAll = + (!filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) || c.AssessmentQuestions.Any(q => q.ResultId == null)) + && (!filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) || c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null)) + && (!filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRequested) || c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null)) + && (!filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) || c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true)) + && (!filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) || c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true)) + && (!filters.Contains((int)SelfAssessmentCompetencyFilter.Optional) || c.Optional) where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) - && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAll) select c; } competencies = filteredCompetencies; @@ -111,6 +112,7 @@ public static bool IsResponseStatusFilter(int filter) { var responseStatusFilters = new int[] { + (int)SelfAssessmentCompetencyFilter.Optional, (int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment, (int)SelfAssessmentCompetencyFilter.SelfAssessed, (int)SelfAssessmentCompetencyFilter.Verified, diff --git a/DigitalLearningSolutions.Web/Helpers/ExternalApis/FilteredApiHelper.cs b/DigitalLearningSolutions.Web/Helpers/ExternalApis/FilteredApiHelper.cs index d0f4ae16f0..7c39bbe027 100644 --- a/DigitalLearningSolutions.Web/Helpers/ExternalApis/FilteredApiHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/ExternalApis/FilteredApiHelper.cs @@ -6,12 +6,10 @@ using System.Net.Http; using System.Security.Claims; using System.Text; - using System.Text.Json; using System.Threading.Tasks; using Newtonsoft.Json; using System.Net.Http.Headers; using DigitalLearningSolutions.Data.Models.External.Filtered; - using System.Collections; using System.Collections.Generic; using System.Linq; using DigitalLearningSolutions.Data.Utilities; diff --git a/DigitalLearningSolutions.Web/Helpers/ExternalApis/TableauConnectionHelper.cs b/DigitalLearningSolutions.Web/Helpers/ExternalApis/TableauConnectionHelper.cs new file mode 100644 index 0000000000..ffe5240748 --- /dev/null +++ b/DigitalLearningSolutions.Web/Helpers/ExternalApis/TableauConnectionHelper.cs @@ -0,0 +1,53 @@ +namespace DigitalLearningSolutions.Web.Helpers.ExternalApis +{ + using Microsoft.IdentityModel.Tokens; + using System.IdentityModel.Tokens.Jwt; + using System.Text; + using System; + using Microsoft.Extensions.Configuration; + using DigitalLearningSolutions.Data.Extensions; + + public interface ITableauConnectionHelperService + { + string GetTableauJwt(string email); + } + public class TableauConnectionHelper : ITableauConnectionHelperService + { + private readonly string connectedAppSecretKey; + private readonly string connectedAppSecretId; + private readonly string connectedAppClientId; + private readonly string user; + public TableauConnectionHelper(IConfiguration config) + { + connectedAppClientId = config.GetTableauClientId(); + connectedAppSecretId = config.GetTableauClientSecretId(); + connectedAppSecretKey = config.GetTableauClientSecret(); + user = config.GetTableauUser(); + } + public string GetTableauJwt(string email) + { + var key = Encoding.UTF8.GetBytes(connectedAppSecretKey); + var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256); + var header = new JwtHeader(signingCredentials) + { + { "kid", connectedAppSecretId }, + { "iss", connectedAppClientId } + }; + + var payload = new JwtPayload + { + { "jti", Guid.NewGuid().ToString()}, + { "iss", connectedAppClientId }, + { "aud", "tableau" }, + { "exp", new DateTimeOffset(DateTime.UtcNow.AddMinutes(5)).ToUnixTimeSeconds() }, + { "sub", user }, + { "scp", new[] { "tableau:views:embed" } }, + { "ExernalUserEmail", new [] { email } } + }; + var token = new JwtSecurityToken(header, payload); + var tokenHandler = new JwtSecurityTokenHandler(); + var tokenString = tokenHandler.WriteToken(token); + return tokenString; + } + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs b/DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs index 5390708ea2..e88d41d79e 100644 --- a/DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs +++ b/DigitalLearningSolutions.Web/Helpers/FeatureFlags.cs @@ -14,5 +14,6 @@ public static class FeatureFlags public const string UserFeedbackBar = "UserFeedbackBar"; public const string ShowSelfAssessmentProgressButtons = "ShowSelfAssessmentProgressButtons"; public const string LoginWithLearningHub = "LoginWithLearningHub"; + public const string TableauSelfAssessmentDashboards = "TableauSelfAssessmentDashboards"; } } diff --git a/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs b/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs index 33a5426c25..70d1069b5d 100644 --- a/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs @@ -37,20 +37,96 @@ private static void ApplyResponseStatusFilters(ref IEnumerable compe { var wordsInSearchText = searchText.Split().Where(w => w != string.Empty); filters = appliedResponseStatusFilters; - filteredCompetencies = from c in competencies - let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) - let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) - let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) - let responseStatusFilterMatchesAnyQuestion = - (filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) && c.AssessmentQuestions.Any(q => q.ResultId == null)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.PendingConfirmation) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Verified == null && q.Requested != null&&q.UserIsVerifier==false)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.AwaitingConfirmation) && c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null && q.UserIsVerifier == true)) - || (filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true)) - where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) - && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) - select c; + if (filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else if (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else if (filters.Contains((int)SelfAssessmentCompetencyFilter.PendingConfirmation) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.PendingConfirmation) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Verified == null && q.Requested != null && q.UserIsVerifier == false) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else if (filters.Contains((int)SelfAssessmentCompetencyFilter.AwaitingConfirmation) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.AwaitingConfirmation) && c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null && q.UserIsVerifier == true) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else if (filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) && c.AssessmentQuestions.Any(q => q.ResultId == null) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else if (filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) && filters.Contains((int)SelfAssessmentCompetencyFilter.Optional)) + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } + else + { + filteredCompetencies = from c in competencies + let searchTextMatchesGroup = wordsInSearchText.All(w => c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyDescription = wordsInSearchText.All(w => c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let searchTextMatchesCompetencyName = wordsInSearchText.All(w => c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + let responseStatusFilterMatchesAnyQuestion = + (filters.Contains((int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment) && c.AssessmentQuestions.Any(q => q.ResultId == null)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.SelfAssessed) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.PendingConfirmation) && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Verified == null && q.Requested != null && q.UserIsVerifier == false)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.ConfirmationRejected) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.AwaitingConfirmation) && c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null && q.UserIsVerifier == true)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.Verified) && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true)) + || (filters.Contains((int)SelfAssessmentCompetencyFilter.Optional) && c.Optional) + where (wordsInSearchText.Count() == 0 || searchTextMatchesGroup || searchTextMatchesCompetencyDescription || searchTextMatchesCompetencyName) + && (!appliedResponseStatusFilters.Any() || responseStatusFilterMatchesAnyQuestion) + select c; + } } competencies = filteredCompetencies; } @@ -111,6 +187,7 @@ public static bool IsResponseStatusFilter(int filter) { var responseStatusFilters = new int[] { + (int)SelfAssessmentCompetencyFilter.Optional, (int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment, (int)SelfAssessmentCompetencyFilter.SelfAssessed, (int)SelfAssessmentCompetencyFilter.Verified, diff --git a/DigitalLearningSolutions.Web/ServiceFilter/IsCentreAuthorizedSelfAssessment.cs b/DigitalLearningSolutions.Web/ServiceFilter/IsCentreAuthorizedSelfAssessment.cs new file mode 100644 index 0000000000..f5bcb5c88b --- /dev/null +++ b/DigitalLearningSolutions.Web/ServiceFilter/IsCentreAuthorizedSelfAssessment.cs @@ -0,0 +1,42 @@ +namespace DigitalLearningSolutions.Web.ServiceFilter +{ + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Filters; + using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.Services; + using Microsoft.Extensions.Logging; + + public class IsCentreAuthorizedSelfAssessment : IActionFilter + { + private readonly ISelfAssessmentService selfAssessmentService; + private readonly ILogger logger; + + public IsCentreAuthorizedSelfAssessment(ISelfAssessmentService selfAssessmentService, + ILogger logger) + { + this.selfAssessmentService = selfAssessmentService; + this.logger = logger; + } + + public void OnActionExecuted(ActionExecutedContext context) { } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (!(context.Controller is Controller controller)) + { + return; + } + var centreId = controller.User.GetCentreIdKnownNotNull(); + var selfAssessmentId = int.Parse(context.ActionArguments["selfAssessmentId"].ToString()!); + + if (centreId > 0 && selfAssessmentId > 0) + { + if (!selfAssessmentService.IsCentreSelfAssessment(selfAssessmentId, centreId)) + { + logger.LogWarning($"Attempt to access restricted self assessment {selfAssessmentId} by user {controller.User.GetUserIdKnownNotNull()}"); + context.Result = new RedirectToActionResult("AccessDenied", "LearningSolutions", new { code = 403 }); + } + } + } + } +} diff --git a/DigitalLearningSolutions.Web/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessment.cs b/DigitalLearningSolutions.Web/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessment.cs index 0b73ae9305..32b7b67e0d 100644 --- a/DigitalLearningSolutions.Web/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessment.cs +++ b/DigitalLearningSolutions.Web/ServiceFilter/VerifyDelegateUserCanAccessSelfAssessment.cs @@ -31,8 +31,9 @@ public void OnActionExecuting(ActionExecutingContext context) var selfAssessmentId = int.Parse(context.RouteData.Values["selfAssessmentId"].ToString()!); var delegateUserId = controller.User.GetUserIdKnownNotNull(); + var centreId = controller.User.GetCentreIdKnownNotNull(); var canAccessSelfAssessment = - selfAssessmentService.CanDelegateAccessSelfAssessment(delegateUserId, selfAssessmentId); + selfAssessmentService.CanDelegateAccessSelfAssessment(delegateUserId, selfAssessmentId, centreId); if (!canAccessSelfAssessment) { diff --git a/DigitalLearningSolutions.Web/Services/ActivityService.cs b/DigitalLearningSolutions.Web/Services/ActivityService.cs index 55ad09e4f5..5dee278ed3 100644 --- a/DigitalLearningSolutions.Web/Services/ActivityService.cs +++ b/DigitalLearningSolutions.Web/Services/ActivityService.cs @@ -8,9 +8,9 @@ using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; - using DigitalLearningSolutions.Data.Models.Courses; using DigitalLearningSolutions.Data.Models.TrackingSystem; using DigitalLearningSolutions.Data.Utilities; + using Microsoft.Extensions.Configuration; public interface IActivityService { @@ -27,20 +27,24 @@ int centreId public class ActivityService : IActivityService { - private const string SheetName = "Usage Statistics"; + private const string SheetName = "Usage summary"; private static readonly XLTableTheme TableTheme = XLTableTheme.TableStyleLight9; private readonly IActivityDataService activityDataService; private readonly ICourseCategoriesDataService courseCategoriesDataService; private readonly ICourseDataService courseDataService; private readonly IJobGroupsDataService jobGroupsDataService; private readonly IClockUtility clockUtility; + private readonly IConfiguration configuration; + private readonly ICentreRegistrationPromptsService registrationPromptsService; public ActivityService( IActivityDataService activityDataService, IJobGroupsDataService jobGroupsDataService, ICourseCategoriesDataService courseCategoriesDataService, ICourseDataService courseDataService, - IClockUtility clockUtility + IClockUtility clockUtility, + IConfiguration configuration, + ICentreRegistrationPromptsService registrationPromptsService ) { this.activityDataService = activityDataService; @@ -48,7 +52,19 @@ IClockUtility clockUtility this.courseCategoriesDataService = courseCategoriesDataService; this.courseDataService = courseDataService; this.clockUtility = clockUtility; + this.configuration = configuration; + this.registrationPromptsService = registrationPromptsService; } + public int GetActivityDetailRowCount(int centreId, ActivityFilterData filterData) + { + return activityDataService.GetActivityDetailRowCount(centreId, + filterData.StartDate, + filterData.EndDate, + filterData.JobGroupId, + filterData.CourseCategoryId, + filterData.CustomisationId); + } + public IEnumerable GetFilteredActivity(int centreId, ActivityFilterData filterData) { var activityData = activityDataService @@ -124,7 +140,7 @@ public byte[] GetActivityDataFileForCentre(int centreId, ActivityFilterData filt last.DateInformation.GetDateRangeLabel( DateHelper.StandardDateFormat, filterData.EndDate ?? clockUtility.UtcNow, - true + false ), last.Enrolments, last.Completions, @@ -146,7 +162,7 @@ public byte[] GetActivityDataFileForCentre(int centreId, ActivityFilterData filt var table = sheet.Cell(1, 1).InsertTable(workbookData); table.Theme = TableTheme; sheet.Columns().AdjustToContents(); - + AddActivityDetailSheet(workbook, centreId, filterData); using var stream = new MemoryStream(); workbook.SaveAs(stream); return stream.ToArray(); @@ -207,6 +223,53 @@ ReportInterval interval ) ); } + private void AddActivityDetailSheet(XLWorkbook workbook, int centreId, ActivityFilterData filterData) + { + var itemsPerPage = Data.Extensions.ConfigurationExtensions.GetExportQueryRowLimit(configuration); + var resultCount = GetActivityDetailRowCount(centreId, filterData); + if (resultCount == 0) + { + return; + } + int totalRun = (int)(resultCount / itemsPerPage) + ((resultCount % itemsPerPage) > 0 ? 1 : 0); + int currentRun = 1; + List activityLogDetails = new List(); + while (totalRun >= currentRun) + { + activityLogDetails.AddRange(activityDataService.GetFilteredActivityDetail( + centreId, + filterData.StartDate, + filterData.EndDate, + filterData.JobGroupId, + filterData.CourseCategoryId, + filterData.CustomisationId, + itemsPerPage, + currentRun)); + currentRun++; + } + var customRegistrationPrompts = registrationPromptsService.GetCentreRegistrationPromptsByCentreId(centreId); + var sheet = workbook.Worksheets.Add("Usage detail"); + var table = sheet.Cell(1, 1).InsertTable(activityLogDetails); + table.Theme = TableTheme; + table.Field(0).Name = "Date"; + foreach (var prompt in customRegistrationPrompts.CustomPrompts) + { + var promptName = prompt.PromptText; + var fieldNum = prompt.RegistrationField.ToString().Last(); + var promptLabel = "Answer" + fieldNum; + table.Field(promptLabel).Name = promptName; + } + for (int i = 1; i < 7; i++) + { + var answerLabel = "Answer" + i.ToString(); + + if (table.Fields.Any(f => f.Name == answerLabel)) + { + table.Field(answerLabel).Delete(); + } + } + sheet.Columns().AdjustToContents(); + } private static int GetFirstMonthOfQuarter(int quarter) { return quarter * 3 - 2; diff --git a/DigitalLearningSolutions.Web/Services/AdminDownloadFileService.cs b/DigitalLearningSolutions.Web/Services/AdminDownloadFileService.cs index 5c42b15ba3..91c1f66b28 100644 --- a/DigitalLearningSolutions.Web/Services/AdminDownloadFileService.cs +++ b/DigitalLearningSolutions.Web/Services/AdminDownloadFileService.cs @@ -3,18 +3,14 @@ using ClosedXML.Excel; using DigitalLearningSolutions.Data.DataServices.UserDataService; using DigitalLearningSolutions.Data.Helpers; - using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.User; - using DocumentFormat.OpenXml.Spreadsheet; using Microsoft.Extensions.Configuration; - using StackExchange.Redis; using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; - using System.Threading.Tasks; - using ConfigurationExtensions = DigitalLearningSolutions.Data.Extensions.ConfigurationExtensions; + using ConfigurationExtensions = Data.Extensions.ConfigurationExtensions; public interface IAdminDownloadFileService { public byte[] GetAllAdminsFile( @@ -88,7 +84,7 @@ public byte[] GetAllAdminsFile( return stream.ToArray(); } - private void PopulateAllAdminsSheet( + private void PopulateAllAdminsSheet( IXLWorkbook workbook, string? searchString, string? filterString @@ -119,7 +115,7 @@ private void PopulateAllAdminsSheet( FormatAllDelegateWorksheetColumns(workbook, dataTable); } - private IEnumerable GetAdminsToExport( + private IEnumerable GetAdminsToExport( string? searchString, string? filterString ) @@ -173,7 +169,7 @@ private IEnumerable GetAdminsToExport( } } } - var exportQueryRowLimit =ConfigurationExtensions.GetExportQueryRowLimit(configuration); + var exportQueryRowLimit = ConfigurationExtensions.GetExportQueryRowLimit(configuration); int resultCount = userDataService.RessultCount(AdminId, Search ?? string.Empty, CentreId, UserStatus, AuthHelper.FailedLoginThreshold, Role); int totalRun = (int)(resultCount / exportQueryRowLimit) + ((resultCount % exportQueryRowLimit) > 0 ? 1 : 0); @@ -181,7 +177,7 @@ private IEnumerable GetAdminsToExport( List admins = new List(); while (totalRun >= currentRun) { - admins.AddRange( this.userDataService.GetAllAdminsExport(Search ?? string.Empty, 0, 999999, AdminId, UserStatus, Role, CentreId, AuthHelper.FailedLoginThreshold, exportQueryRowLimit, currentRun)); + admins.AddRange(this.userDataService.GetAllAdminsExport(Search ?? string.Empty, 0, 999999, AdminId, UserStatus, Role, CentreId, AuthHelper.FailedLoginThreshold, exportQueryRowLimit, currentRun)); currentRun++; } return admins; @@ -257,7 +253,7 @@ AdminEntity adminRecord row[IsCMSAdministrator] = adminRecord.AdminAccount.IsContentManager && adminRecord.AdminAccount.ImportOnly; row[IsCMSManager] = adminRecord.AdminAccount.IsContentManager && !adminRecord.AdminAccount.ImportOnly; - + row[IsSuperAdmin] = adminRecord.AdminAccount?.IsSuperAdmin; row[IsReportsViewer] = adminRecord.AdminAccount?.IsReportsViewer; @@ -276,7 +272,7 @@ AdminEntity adminRecord private static void FormatAllDelegateWorksheetColumns(IXLWorkbook workbook, DataTable dataTable) { - var integerColumns = new[] { AdminID, UserID, CentreID, CategoryID}; + var integerColumns = new[] { AdminID, UserID, CentreID, CategoryID }; foreach (var columnName in integerColumns) { ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, columnName, XLDataType.Number); diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs index 43da37aa20..ff7ef8d53a 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; - using AngleSharp.Attributes; using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; - using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.Common.Users; using DigitalLearningSolutions.Data.Models.External.Filtered; using DigitalLearningSolutions.Data.Models.Frameworks; @@ -33,7 +31,7 @@ public interface ISelfAssessmentService void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime? completeByDate); - bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId); + bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId, int centreId); // Competencies IEnumerable GetCandidateAssessmentResultsById(int candidateAssessmentId, int adminId, int? selfAssessmentResultId = null); @@ -149,6 +147,8 @@ public int GetSelfAssessmentActivityDelegatesExportCount(string searchString, st ActivitySummaryCompetencySelfAssesment GetActivitySummaryCompetencySelfAssesment(int CandidateAssessmentSupervisorVerificationsId); bool IsUnsupervisedSelfAssessment(int selfAssessmentId); IEnumerable GetCandidateAssessments(int delegateUserId, int selfAssessmentId); + bool IsCentreSelfAssessment(int selfAssessmentId, int centreId); + bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId); } @@ -339,11 +339,12 @@ int delegateUserId return selfAssessmentDataService.GetCandidateAssessmentExportDetails(candidateAssessmentId, delegateUserId); } - public bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId) + public bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId, int centreId) { var candidateAssessments = selfAssessmentDataService.GetCandidateAssessments(delegateUserId, selfAssessmentId); - return candidateAssessments.Any(ca => ca.CompletedDate == null && ca.RemovedDate == null); + return candidateAssessments.Any(ca => ca.CompletedDate == null && ca.RemovedDate == null && + selfAssessmentDataService.IsCentreSelfAssessment(selfAssessmentId, centreId)); } public IEnumerable GetLevelDescriptorsForAssessmentQuestion( @@ -545,7 +546,17 @@ public bool IsUnsupervisedSelfAssessment(int selfAssessmentId) } public IEnumerable GetCandidateAssessments(int delegateUserId, int selfAssessmentId) { - return selfAssessmentDataService.GetCandidateAssessments(delegateUserId,selfAssessmentId); + return selfAssessmentDataService.GetCandidateAssessments(delegateUserId, selfAssessmentId); + } + + public bool IsCentreSelfAssessment(int selfAssessmentId, int centreId) + { + return selfAssessmentDataService.IsCentreSelfAssessment(selfAssessmentId, centreId); + } + + public bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId) + { + return selfAssessmentDataService.HasMinimumOptionalCompetencies(selfAssessmentId, delegateUserId); } } } diff --git a/DigitalLearningSolutions.Web/Services/UserService.cs b/DigitalLearningSolutions.Web/Services/UserService.cs index 87db1ad6c6..6465b9a2c4 100644 --- a/DigitalLearningSolutions.Web/Services/UserService.cs +++ b/DigitalLearningSolutions.Web/Services/UserService.cs @@ -194,6 +194,8 @@ bool isWorkforceManager string search, int offset, int rows, int jobGroupId, string userStatus, string emailStatus, int userId, int failedLoginThreshold ); void UpdateUserDetailsAccount(string firstName, string lastName, string primaryEmail, int jobGroupId, string? prnNumber, DateTime? emailVerified, int userId); + void DeactivateAdminAccount(int userId, int centreId); + int? CheckDelegateIsActive(int delegateId); } public class UserService : IUserService @@ -275,7 +277,7 @@ public void UpdateFailedLoginCount(UserAccount userAccount) public IEnumerable GetDelegateUserCardsForWelcomeEmail(int centreId) { return userDataService.GetDelegateUserCardsByCentreId(centreId).Where( - user => user.Approved && !user.SelfReg && string.IsNullOrEmpty(user.Password) && + user => user.Approved && !user.SelfReg && !string.IsNullOrEmpty(user.EmailAddress) && !Guid.TryParse(user.EmailAddress, out _) && user.RegistrationConfirmationHash != null @@ -317,8 +319,8 @@ public IEnumerable GetSupervisorsAtCentre(int centreId) public IEnumerable GetSupervisorsAtCentreForCategory(int centreId, int categoryId) { - return userDataService.GetAdminUsersByCentreId(centreId).Where(au => au.IsSupervisor) - .Where(au => au.CategoryId == categoryId || au.CategoryId == null); + return userDataService.GetAdminUsersAtCentreForCategory(centreId, categoryId); + } public bool DelegateUserLearningHubAccountIsLinked(int delegateId) @@ -894,7 +896,7 @@ public void ApproveDelegateUsers(params int[] ids) { userDataService.ApproveDelegateUsers(ids); } - + public (IEnumerable, int) GetAllAdmins(string search, int offset, int rows, int? adminId, string userStatus, string role, int? centreId, int failedLoginThreshold) { return userDataService.GetAllAdmins(search, offset, rows, adminId, userStatus, role, centreId, failedLoginThreshold); @@ -902,7 +904,7 @@ public void ApproveDelegateUsers(params int[] ids) public void UpdateAdminUserAndSpecialPermissions(int adminId, bool isCentreAdmin, bool isSupervisor, bool isNominatedSupervisor, bool isTrainer, bool isContentCreator, bool isContentManager, bool importOnly, int? categoryId, bool isCentreManager, bool isSuperAdmin, bool isReportsViewer, bool isLocalWorkforceManager, bool isFrameworkDeveloper, bool isWorkforceManager) { - userDataService.UpdateAdminUserAndSpecialPermissions(adminId, isCentreAdmin, isSupervisor, isNominatedSupervisor, isTrainer, isContentCreator, isContentManager,importOnly, categoryId, isCentreManager, isSuperAdmin, isReportsViewer, isLocalWorkforceManager, isFrameworkDeveloper, isWorkforceManager); + userDataService.UpdateAdminUserAndSpecialPermissions(adminId, isCentreAdmin, isSupervisor, isNominatedSupervisor, isTrainer, isContentCreator, isContentManager, importOnly, categoryId, isCentreManager, isSuperAdmin, isReportsViewer, isLocalWorkforceManager, isFrameworkDeveloper, isWorkforceManager); } public int GetUserIdFromAdminId(int adminId) @@ -929,7 +931,7 @@ public bool IsUserAlreadyAdminAtCentre(int? userId, int centreId) { return userDataService.IsUserAlreadyAdminAtCentre(userId, centreId); } - + public IEnumerable GetAdminsByCentreId(int centreId) { return userDataService.GetAdminsByCentreId(centreId); @@ -956,5 +958,13 @@ public void UpdateUserDetailsAccount(string firstName, string lastName, string p { userDataService.UpdateUserDetailsAccount(firstName, lastName, primaryEmail, jobGroupId, prnNumber, emailVerified, userId); } + public void DeactivateAdminAccount(int userId, int centreId) + { + userDataService.DeactivateAdminAccount(userId, centreId); + } + public int? CheckDelegateIsActive(int delegateId) + { + return userDataService.CheckDelegateIsActive(delegateId); + } } } diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index c7c3ec544f..193bdd1d2f 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -46,21 +46,13 @@ namespace DigitalLearningSolutions.Web using Microsoft.Extensions.Hosting; using Microsoft.FeatureManagement; using Microsoft.IdentityModel.Protocols.OpenIdConnect; - using Microsoft.IdentityModel.Tokens; - using Microsoft.AspNetCore.Http; - using System.Linq; using Microsoft.AspNetCore.Identity; - using AspNetCoreRateLimit; using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService; using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService; using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService; using System; using IsolationLevel = System.Transactions.IsolationLevel; - using System.Collections.Concurrent; using Serilog; - using static DigitalLearningSolutions.Data.DataServices.ICentreApplicationsDataService; - using static DigitalLearningSolutions.Web.Services.ICentreApplicationsService; - using static DigitalLearningSolutions.Web.Services.ICentreSelfAssessmentsService; public class Startup { @@ -560,6 +552,7 @@ private static void RegisterHttpClients(IServiceCollection services) services.AddHttpClient(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void RegisterWebServiceFilters(IServiceCollection services) @@ -587,16 +580,18 @@ private static void RegisterWebServiceFilters(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } public void Configure(IApplicationBuilder app, IMigrationRunner migrationRunner, IFeatureManager featureManager) { + var tableauServerUrl = config.GetTableauSiteUrl(); app.UseMiddleware(); app.Use(async (context, next) => { context.Response.Headers.Add("content-security-policy", "default-src 'self'; " + - "script-src 'self' 'unsafe-hashes' 'sha256-oywvD6W6okwID679n4cvPJtWLowSS70Pz87v1ryS0DU=' 'sha256-kbHtQyYDQKz4SWMQ8OHVol3EC0t3tHEJFPCSwNG9NxQ' 'sha256-YoDy5WvNzQHMq2kYTFhDYiGnEgPrvAY5Il6eUu/P4xY=' 'sha256-/n13APBYdqlQW71ZpWflMB/QoXNSUKDxZk1rgZc+Jz8=' https://script.hotjar.com https://www.google-analytics.com https://static.hotjar.com https://www.googletagmanager.com https://cdnjs.cloudflare.com 'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU=' 'sha256-VQKp2qxuvQmMpqE/U/ASQ0ZQ0pIDvC3dgQPPCqDlvBo=';" + + $"script-src 'self' 'nonce-random772362' https://script.hotjar.com https://www.google-analytics.com https://static.hotjar.com https://www.googletagmanager.com https://cdnjs.cloudflare.com {tableauServerUrl} 'unsafe-hashes' 'sha256-oywvD6W6okwID679n4cvPJtWLowSS70Pz87v1ryS0DU=' 'sha256-kbHtQyYDQKz4SWMQ8OHVol3EC0t3tHEJFPCSwNG9NxQ' 'sha256-YoDy5WvNzQHMq2kYTFhDYiGnEgPrvAY5Il6eUu/P4xY=' 'sha256-/n13APBYdqlQW71ZpWflMB/QoXNSUKDxZk1rgZc+Jz8=' 'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU=' 'sha256-VQKp2qxuvQmMpqE/U/ASQ0ZQ0pIDvC3dgQPPCqDlvBo=';" + "style-src 'self' 'unsafe-inline' https://use.fontawesome.com; " + "font-src https://script.hotjar.com https://assets.nhs.uk/; " + "connect-src 'self' http: ws:; " + diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/BaseLearningItemViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/BaseLearningItemViewModel.cs index f5b8337830..838e054765 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/BaseLearningItemViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/BaseLearningItemViewModel.cs @@ -14,6 +14,7 @@ protected BaseLearningItemViewModel(BaseLearningItem course) IsSelfAssessment = course.IsSelfAssessment; SelfRegister = course.SelfRegister; IncludesSignposting = course.IncludesSignposting; + Active = course.Active; } public string Name { get; set; } @@ -24,5 +25,6 @@ protected BaseLearningItemViewModel(BaseLearningItem course) public bool IsSelfAssessment { get; } public bool SelfRegister { get; } public bool IncludesSignposting { get; } + public bool Active { get; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/AddOptionalCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/AddOptionalCompetenciesViewModel.cs new file mode 100644 index 0000000000..d8957898db --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/AddOptionalCompetenciesViewModel.cs @@ -0,0 +1,14 @@ +using DigitalLearningSolutions.Data.Models.SelfAssessments; +using DigitalLearningSolutions.Web.Helpers; + +namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments +{ + public class AddOptionalCompetenciesViewModel + { + public CurrentSelfAssessment SelfAssessment { get; set; } + public string VocabPlural() + { + return FrameworkVocabularyHelper.VocabularyPlural(SelfAssessment.Vocabulary); + } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/ManageOptionalCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/ManageOptionalCompetenciesViewModel.cs index 94f52b17f4..0fc3adb9b7 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/ManageOptionalCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/ManageOptionalCompetenciesViewModel.cs @@ -10,6 +10,7 @@ public class ManageOptionalCompetenciesViewModel public CurrentSelfAssessment? SelfAssessment { get; set; } public IEnumerable>? CompetencyGroups { get; set; } public List? IncludedSelfAssessmentStructureIds { get; set; } + public List? GroupOptionalCompetenciesChecked { get; set; } public string VocabPlural() { return FrameworkVocabularyHelper.VocabularyPlural(SelfAssessment.Vocabulary); diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/RequestSignOffViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/RequestSignOffViewModel.cs index c3a85a21c4..2e21adfd49 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/RequestSignOffViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/RequestSignOffViewModel.cs @@ -14,6 +14,11 @@ public class RequestSignOffViewModel [Required] [Range(1, 1, ErrorMessage = "Please tick to confirm that you understand the request sign-off statement")] public bool StatementChecked { get; set; } + [Required] + [Range(1, 1, ErrorMessage = "Please tick to confirm that you have include optional competencies that are appropriate to your role before requesting sign-off")] + public bool OptionalCompetenciesChecked { get; set; } + public int NumberOfSelfAssessedOptionalCompetencies { get; set; } + public string VocabPlural() { if (SelfAssessment != null) diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentOverviewViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentOverviewViewModel.cs index e5f3b029c1..f833876501 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentOverviewViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentOverviewViewModel.cs @@ -13,6 +13,8 @@ public class SelfAssessmentOverviewViewModel public IEnumerable? SupervisorSignOffs { get; set; } public int PreviousCompetencyNumber { get; set; } public int NumberOfOptionalCompetencies { get; set; } + public int NumberOfSelfAssessedOptionalCompetencies { get; set; } + public bool AllQuestionsVerifiedOrNotRequired { get; set; } public SearchSelfAssessmentOverviewViewModel SearchViewModel { get; set; } public string VocabPlural() diff --git a/DigitalLearningSolutions.Web/ViewModels/Supervisor/ReviewSelfAssessmentViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Supervisor/ReviewSelfAssessmentViewModel.cs index 8bce7133c3..ea19f92d35 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Supervisor/ReviewSelfAssessmentViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Supervisor/ReviewSelfAssessmentViewModel.cs @@ -21,5 +21,6 @@ public string VocabPlural(string vocabulary) } public int CandidateAssessmentId { get; set; } public bool ExportToExcelHide { get; set; } + public CompetencySummary CompetencySummaries { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Supervisor/SignOffProfileAssessmentViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Supervisor/SignOffProfileAssessmentViewModel.cs index d37ffb0e31..c44a39a4c7 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Supervisor/SignOffProfileAssessmentViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Supervisor/SignOffProfileAssessmentViewModel.cs @@ -17,5 +17,9 @@ public class SignOffProfileAssessmentViewModel [RequiredWhen("SignedOff", false, AllowEmptyStrings = false, ErrorMessage = "Comments are required when rejecting a self assessment (when Sign-off is unchecked).")] public string? SupervisorComments { get; set; } public bool SignedOff { get; set; } + [Required] + [Range(1, 1, ErrorMessage = "Please tick to confirm that you have reviewed the optional competencies included in this self assessment and they are appropriate to the learner’s role.")] + public bool OptionalCompetenciesChecked { get; set; } + public int NumberOfSelfAssessedOptionalCompetencies { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/AdminRoleInputs.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/AdminRoleInputs.cs index 0f17e399a9..41788bde7f 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/AdminRoleInputs.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/AdminRoleInputs.cs @@ -15,7 +15,7 @@ public class AdminRoleInputs public static CheckboxListItemViewModel CentreManagerCheckbox = new CheckboxListItemViewModel( nameof(EditRolesViewModel.IsCenterManager), "Centre manager", - "Manages user access permissions for administrators at the centre, sees all support tickets for the centre in addition to having all of the permissions of a centre administrator." + "Manages user access permissions for administrators at the centre, in addition to having all of the permissions of a centre administrator." ); public static CheckboxListItemViewModel SupervisorCheckbox = new CheckboxListItemViewModel( diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs index 0949508854..cb92d6d0f4 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/CourseSetup/AdminFieldAnswersViewModel.cs @@ -23,7 +23,6 @@ public AdminFieldAnswersViewModel( public List Options => NewlineSeparatedStringListHelper.SplitNewlineSeparatedList(OptionsString); - [Required(ErrorMessage = "Enter a response")] [MaxLength(100, ErrorMessage = "Response must be 100 characters or fewer")] public string? Answer { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DeactivateDelegate/DeactivateDelegateAccountViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DeactivateDelegate/DeactivateDelegateAccountViewModel.cs new file mode 100644 index 0000000000..c6a3a5f1f3 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DeactivateDelegate/DeactivateDelegateAccountViewModel.cs @@ -0,0 +1,17 @@ +using FluentMigrator.Infrastructure; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.DeactivateDelegate +{ + public class DeactivateDelegateAccountViewModel + { + public int DelegateId { get; set; } + public int UserId { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public List Roles { get; set; } + [Required(ErrorMessage = "Please select an account you want to deactivate.")] + public bool? Deactivate { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateProgress/EditSupervisorViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateProgress/EditSupervisorViewModel.cs index 4efd057dc6..f46283428c 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateProgress/EditSupervisorViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/DelegateProgress/EditSupervisorViewModel.cs @@ -47,7 +47,7 @@ private static IEnumerable PopulateSupervisors( IEnumerable supervisors ) { - var supervisorIdNames = supervisors.Select(s => (s.Id, s.FullName)); + var supervisorIdNames = supervisors.Select(s => (s.Id, s.FullName + " (" + s.EmailAddress + ")")); return SelectListHelper.MapOptionsToSelectListItems( supervisorIdNames, supervisorId diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/CompletedCourseCard/_CompletedCourseCard.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/CompletedCourseCard/_CompletedCourseCard.cshtml index 319dd1fa76..bbca9c6251 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/CompletedCourseCard/_CompletedCourseCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/CompletedCourseCard/_CompletedCourseCard.cshtml @@ -31,7 +31,7 @@
- @if (Model.ArchivedDate == null && Model.RemovedDate == null && Model.CheckUnpublishedCourse > 0) + @if (Model.ArchivedDate == null && Model.RemovedDate == null && Model.CheckUnpublishedCourse > 0 && Model.Active) { + @(Model.SelfAssessment.Name) introduction + +
  • Add optional @(Model.VocabPlural().ToLower())?
  • +} + +@section mobilebacklink +{ +

    + + Back to @Model.SelfAssessment.Name + +

    +} +
    +
    +
    +

    Add optional @Model.VocabPlural().ToLower() to your assessment?

    +
    +
    +

    During your assessment you will need to add one or more optional @Model.VocabPlural().ToLower() to your assessment to be certified within your role

    +
    + + + What are optional @Model.VocabPlural().ToLower()? + + +
    +

    Optional @Model.VocabPlural().ToLower() refer to specific skills or competencies that are not part of the core requirements but may be added based on your role, specialisation, or the needs of your workplace.

    +
    +
    +

    These @Model.VocabPlural().ToLower() might be different depending on your role or organization so you may need to discuss this with your educator or manager

    + + + Remind me later +
    +
    +
    diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml index 68c5269896..9c3c3152c0 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml @@ -76,14 +76,26 @@

    Certificate

    @Model.CompetencySelfAssessmentCertificates.SelfAssessment - @Model.CompetencySelfAssessmentCertificates.LearnerName

    - - Download certificate + } + @if (Model.Vocabulary == "ProfileAssessment") + { + + Download certificate + + }
    diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/ManageOptionalCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/ManageOptionalCompetencies.cshtml index 7806c53130..c635dc4bc9 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/ManageOptionalCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/ManageOptionalCompetencies.cshtml @@ -1,13 +1,31 @@ @using DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments +@using DigitalLearningSolutions.Web.Extensions @model ManageOptionalCompetenciesViewModel @{ var errorHasOccurred = !ViewData.ModelState.IsValid; Layout = "SelfAssessments/_Layout"; ViewData["Title"] = "Self Assessment - Optional Proficiencies"; ViewData["SelfAssessmentTitle"] = @Model.SelfAssessment.Name; + var backLinkData = Html.GetRouteValues(); } - -@section breadcrumbs { + +@if (ViewBag.FromAddOptionalPage != null) +{ + @section breadcrumbs { +
  • +
    + +
    +
  • + } +} +else +{ + @section breadcrumbs {
  • @(Model.SelfAssessment.Name) introduction
  • @@ -15,17 +33,32 @@ @Model.VocabPlural() home
  • Manage optional @Model.VocabPlural()
  • + } } + @section mobilebacklink { -

    - - Back to @Model.VocabPlural() - -

    + @if (ViewBag.FromAddOptionalPage != null) + { +

    + + Back to add optional @Model.VocabPlural().ToLower()? + +

    + } + else + { +

    + + Back to @Model.VocabPlural() + +

    + } } @@ -54,41 +87,93 @@ @foreach (var competencyGroup in Model.CompetencyGroups) {
    - - - @competencyGroup.Key - - + @if (competencyGroup.Count() > 1) { - - } - -
    - @foreach (var competency in competencyGroup) + @if (competencyGroup.Any(x => x.GroupOptionalCompetencies)) { +
    - -
    +
    + +

    + + + What’s included in the @competencyGroup.Key + + + +

    +
    + +
    +
    + + @foreach (var competency in competencyGroup) + {
    + +
    + @foreach (var flag in competency.CompetencyFlags) + { + + + @flag.FlagName + + + } + @competency.Name +
    +
    + } +
    +
    +
    + } + else + { + + + @competencyGroup.Key + + + +
    + @foreach (var competency in competencyGroup) + { +
    + + +
    + + } +
    } -
    + }
    } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/RequestSignOff.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/RequestSignOff.cshtml index 57d8d14d66..0510866fbb 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/RequestSignOff.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/RequestSignOff.cshtml @@ -60,6 +60,17 @@
    + @if (Model.NumberOfSelfAssessedOptionalCompetencies >= Model.SelfAssessment.MinimumOptionalCompetencies) + { + +
    + + +
    +
    + } diff --git a/DigitalLearningSolutions.Web/Views/Support/RequestSupportTicket/Request.cshtml b/DigitalLearningSolutions.Web/Views/Support/RequestSupportTicket/Request.cshtml index 8b7ac8ec9b..d2f42906af 100644 --- a/DigitalLearningSolutions.Web/Views/Support/RequestSupportTicket/Request.cshtml +++ b/DigitalLearningSolutions.Web/Views/Support/RequestSupportTicket/Request.cshtml @@ -20,18 +20,19 @@

    Once you have submitted a ticket, you will receive an email when a member of support team responds to it.

    Start - -
    - Information: -

    - If you raised any tickets in the old support ticket system, you can view them here: - - View old support tickets - -

    -
    + +
    + Information: +

    + If you raised any tickets in the old support ticket system, you can view them here: + + View old support tickets + +

    +
    +
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/Index.cshtml index 58f32cd637..d61717e107 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/Index.cshtml @@ -1,5 +1,6 @@ @using DigitalLearningSolutions.Web.Models.Enums @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Centre.Reports +@using DigitalLearningSolutions.Web.Helpers @model SelfAssessmentReportsViewModel @{ ViewData["Title"] = "Self assessment reports"; @@ -18,7 +19,14 @@ - @ViewData["Title"] -

    Use this page to download Excel learner activity reports for competency self assessments at your centre.

    + + @if (Model.SelfAssessmentSelects.Any()) + { + + } + +

    Excel learner activity reports

    +

    Download Excel competency self assessments activity reports for your centre.

    + + diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/TableauCompetencyDashboard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/TableauCompetencyDashboard.cshtml new file mode 100644 index 0000000000..163d6d7b46 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Centre/SelfAssessmentReports/TableauCompetencyDashboard.cshtml @@ -0,0 +1,33 @@ +@using DigitalLearningSolutions.Web.Helpers +@{ + var tableauServerUrl = ViewBag.TableauServerUrl; + var workbookName = ViewBag.WorkbookName; + var viewName = ViewBag.ViewName; + var jwtToken = ViewBag.JwtToken; + var siteName = ViewBag.SiteName; + var srcUrl = $"{tableauServerUrl}/t/{siteName}/views/{workbookName}/{viewName}"; + ViewData["Title"] = "Supervised self assessments dashboard"; +} + +

    @ViewData["Title"]

    + + + If the dashboard doesn't appear after a few seconds, reload the page + + @section scripts { + @* We are not using Yarn/npm for the Tableau JS becaue of errors during installation relating to a missing dependency *@ + + } + + +

    Oops! We are still working on this area of the site

    +

    This feature is under development and should be available soon.

    +
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml index a14f590c4b..fa3505a2ee 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/AddAdminField.cshtml @@ -46,7 +46,7 @@
    -
    - @if (errorHasOccurred) { - - } - -

    Configure responses in bulk

    - -
    - - - - - - - - -
    +
    + @if (errorHasOccurred) + { + + } + +

    Configure responses in bulk

    + +
    + + + + The complete list of responses must be 1000 characters or fewer and each response must be 100 characters or fewer + + + + + + + +
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml index 76103e79f5..6e9712781d 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/CourseSetup/AdminFields/EditAdminField.cshtml @@ -6,65 +6,66 @@ @{ - var errorHasOccurred = !ViewData.ModelState.IsValid; - ViewData["Title"] = errorHasOccurred ? "Error: Edit course admin field" : "Edit course admin field"; - var cancelLinkData = Html.GetRouteValues(); + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Edit course admin field" : "Edit course admin field"; + var cancelLinkData = Html.GetRouteValues(); }
    -
    - @if (errorHasOccurred) { - - } -

    Edit course admin field

    +
    + @if (errorHasOccurred) + { + + } +

    Edit course admin field

    -
    -
    - -
    + +
    + +
    - - - + + + - + - @if (string.IsNullOrEmpty(Model.OptionsString)) - { - - } - else - { - - } + @if (string.IsNullOrEmpty(Model.OptionsString)) + { + + } + else + { + + } -
    -
    - - -
    -
    +
    +
    + + +
    +
    -
    -
    -

    Want to edit responses in bulk?

    - -
    -
    +
    +
    +

    Want to edit responses in bulk?

    + +
    +
    -
    - -
    - +
    + +
    + - -
    + +
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DeactivateDelegate/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DeactivateDelegate/Index.cshtml new file mode 100644 index 0000000000..535158413d --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DeactivateDelegate/Index.cshtml @@ -0,0 +1,84 @@ +@using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.DeactivateDelegate +@model DeactivateDelegateAccountViewModel + + +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Deactivate account" : "Deactivate account"; +} +
    +
    + @if (errorHasOccurred) + { + + } + +

    Deactivate account - @Model.Name (@Model.Email)

    +
    +
    + @Model.Name has an active admin account at your centre with following admin roles: +
    +
    +
    +
    +
      + @foreach (var role in Model.Roles) + { +
    • @role
    • + } +
    +
    +
    + +
    +
    + +

    + Which accounts would you like deactivate? +

    +
    + + +
    +
    + + +
    + The user will still be able to login to your centre with the above admin roles +
    +
    +
    + + +
    + The user will no longer be able to login to your centre +
    +
    + +
    +
    +
    + + + + + + +
    + +
    +
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/Index.cshtml index 01a32e2538..af8e76a57c 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/Index.cshtml @@ -54,7 +54,7 @@ { if (course is SearchableDelegateAssessmentStatisticsViewModel) { - + } else if (course is SearchableDelegateCourseStatisticsViewModel) { diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssesmentCard.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssessmentCard.cshtml similarity index 96% rename from DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssesmentCard.cshtml rename to DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssessmentCard.cshtml index aae4dc2936..c49874cb96 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssesmentCard.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateCourses/_CentreAssessmentCard.cshtml @@ -22,7 +22,7 @@ asp-controller="ActivityDelegates" asp-action="Index" asp-route-selfAssessmentId="@Model.SelfAssessmentId"> - View self assesment delegates + View self assessment delegates
    diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/DownloadProgress.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/DownloadProgress.cshtml index 425d268dd4..4166b353aa 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/DownloadProgress.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/DownloadProgress.cshtml @@ -254,32 +254,35 @@ } -

    Achievements

    -

    The following post learning assessments have been passed:

    -
    - - - - - - - @foreach (var entry in Model.SectionDetails.Where(pass => pass.PLPassed)) - { - - - - - } - -
    - Assessment - - Outcome -
    - @entry.SectionName - - @("PASSED") -
    + @if (Model.SectionDetails.Where(pass => pass.PLPassed).Any()) + { +

    Achievements

    +

    The following post learning assessments have been passed:

    +
    + + + + + + + @foreach (var entry in Model.SectionDetails.Where(pass => pass.PLPassed)) + { + + + + + } + +
    + Assessment + + Outcome +
    + @entry.SectionName + + @("PASSED") +
    + } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/ViewDelegateProgress.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/ViewDelegateProgress.cshtml index d982f93511..da6fa33022 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/ViewDelegateProgress.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/DelegateProgress/ViewDelegateProgress.cshtml @@ -42,15 +42,15 @@ } /*.p-4 { - padding: 1.5rem !important - } + padding: 1.5rem !important + } - #Logo { - max-width: 500px; - max-height: 90px; - height: auto; - width: auto; - }*/ + #Logo { + max-width: 500px; + max-height: 90px; + height: auto; + width: auto; + }*/ .heading2 { font-size: 2rem; @@ -212,36 +212,39 @@ } -
    -

    Achievements

    -

    The following post learning assessments have been passed:

    - - - - - - - - - @foreach (var entry in Model.SectionDetails.Where(pass => pass.PLPassed)) - { - - - + @if (Model.SectionDetails.Where(pass => pass.PLPassed).Any()) + { +
    +

    Achievements

    +

    The following post learning assessments have been passed:

    +
    - Assessment - - Outcome -
    - Assessment - @entry.SectionName - - Outcome - @("PASSED") -
    + + + + - } - -
    + Assessment + + Outcome +
    + + + @foreach (var entry in Model.SectionDetails.Where(pass => pass.PLPassed)) + { + + + Assessment + @entry.SectionName + + + Outcome + @("PASSED") + + + } + + + } diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateCourseProgressDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateCourseProgressDetails.cshtml index 0f2b291402..3c7182f8fb 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateCourseProgressDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateCourseProgressDetails.cshtml @@ -1,7 +1,14 @@ @using DigitalLearningSolutions.Data.Helpers +@using DateHelper = DigitalLearningSolutions.Web.Helpers.DateHelper @using DigitalLearningSolutions.Web.Models.Enums @using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.Shared @model DelegateCourseInfoViewModel +@{ + var courseCompleted = Model.Completed != null ? + Convert.ToDateTime(Model.Completed).TimeOfDay == TimeSpan.Zero ? Convert.ToDateTime(Model.Completed).ToString(DateHelper.StandardDateFormat) : + Convert.ToDateTime(Model.Completed).ToString(DateHelper.StandardDateAndTimeFormat) + : "-"; +}
    @if (Model.AccessedVia.Equals(DelegateAccessRoute.ActivityDelegates)) @@ -55,7 +62,7 @@ Complete by
    - @(Model.CompleteBy ?? "-") + @(Model.CompleteBy != null ? Convert.ToDateTime(Model.CompleteBy).ToString(DateHelper.StandardDateFormat) : "-")
    - @(Model.Completed ?? "-") + @(courseCompleted)
    + role="button" + asp-controller="EditDelegate" + asp-action="Index" + asp-route-delegateId="@Model.DelegateInfo.Id"> Edit details + role="button" + asp-controller="SetDelegatePassword" + asp-action="Index" + asp-route-delegateId="@Model.DelegateInfo.Id" + asp-route-isFromViewDelegatePage="true"> Set password @if (User.HasCentreManagerPermissions() && !Model.DelegateInfo.IsAdmin && !string.IsNullOrWhiteSpace(Model.DelegateInfo.Email) && !string.IsNullOrWhiteSpace(Model.DelegateInfo.Name)) { + role="button" + asp-controller="PromoteToAdmin" + asp-action="Index" + asp-route-delegateId="@Model.DelegateInfo.Id"> Promote to admin } @@ -132,19 +132,31 @@ {
    } else { -
    - - +
    + } + else + { +
    + +
    + } } } else @@ -153,7 +165,7 @@ {
    @@ -186,7 +198,7 @@ if (delegateCourseInfoViewModel.ProgressId != null) { - } + } } @foreach (var delegateSelfAssessmentInfoViewModel in Model.SelfAssessments) { @@ -199,12 +211,12 @@ @if (Model.DelegateInfo.IsActive && !string.IsNullOrEmpty(Model.DelegateInfo.Email)) { + role="button" + asp-controller="Enrol" + asp-action="StartEnrolProcess" + asp-route-delegateId="@Model.DelegateInfo.Id" + asp-route-delegateUserId="@Model.DelegateInfo.UserId" + asp-route-delegateName="@Model.DelegateInfo.Name"> Enrol on activity } diff --git a/DigitalLearningSolutions.Web/Views/UserFeedback/GuestFeedbackStart.cshtml b/DigitalLearningSolutions.Web/Views/UserFeedback/GuestFeedbackStart.cshtml index 11ee0fb1a8..c450dd32a8 100644 --- a/DigitalLearningSolutions.Web/Views/UserFeedback/GuestFeedbackStart.cshtml +++ b/DigitalLearningSolutions.Web/Views/UserFeedback/GuestFeedbackStart.cshtml @@ -4,73 +4,76 @@ @model UserFeedbackViewModel; @{ - ViewData["Title"] = "Feedback"; + ViewData["Title"] = "Feedback"; } @section NavBreadcrumbs { - } -
    +
    -

    Give page or website feedback

    -

    Give us feedback about what you were trying to achieve

    +

    Give page or website feedback

    +

    Give us feedback about what you were trying to achieve

    -
    + - + - + - + -
    - -
    - +
    + +
    +
    -
    +
    diff --git a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackComplete.cshtml b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackComplete.cshtml index 976b75cdf0..6484606fb5 100644 --- a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackComplete.cshtml +++ b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackComplete.cshtml @@ -13,18 +13,12 @@ } diff --git a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAchieved.cshtml b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAchieved.cshtml index e56c72cfdb..b665f16f5b 100644 --- a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAchieved.cshtml +++ b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAchieved.cshtml @@ -1,91 +1,96 @@ @using DigitalLearningSolutions.Web.ViewModels.UserFeedback +@using Microsoft.AspNetCore.Http.Extensions @using Microsoft.AspNetCore.Mvc.TagHelpers @model UserFeedbackViewModel; @{ - ViewData["Title"] = "Feedback"; + ViewData["Title"] = "Feedback"; } @section NavBreadcrumbs { - + } -
    +
    -

    Give page or website feedback

    -

    Did you achieve everything you came to do today?

    +

    Give page or website feedback

    +

    Did you achieve everything you came to do today?

    -
    -

    - - Important: - We do not reply to this inbox. - -

    -

    If you need support please contact your centre. This might be your Centre Manager or Clinical Centre Manager.

    -
    +
    +

    + + Important: + We do not reply to this inbox. + +

    +

    If you need support please contact your centre. This might be your Centre Manager or Clinical Centre Manager.

    +
    -

    Step 1 of 4

    +

    Step 1 of 4

    -
    - - -
    - Did you achieve everything you came to do today? -
    -
    -
    -
    - - + + @* changing the value of source URL to the current route *@ + +
    + Did you achieve everything you came to do today?
    -
    - - +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    -
    -
    -
    - -
    -
    +
    -
    +
    diff --git a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAttempted.cshtml b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAttempted.cshtml index bd087cb045..ec01a514b6 100644 --- a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAttempted.cshtml +++ b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskAttempted.cshtml @@ -1,73 +1,72 @@ @using DigitalLearningSolutions.Web.ViewModels.UserFeedback +@using Microsoft.AspNetCore.Http.Extensions @using Microsoft.AspNetCore.Mvc.TagHelpers @model UserFeedbackViewModel; @{ - ViewData["Title"] = "Feedback"; + ViewData["Title"] = "Feedback"; } @section NavBreadcrumbs { - + }
    -
    -

    Give page or website feedback

    -

    Give us feedback about what you were trying to achieve

    -
    -
    -
    - - +
    +

    Give page or website feedback

    +

    Give us feedback about what you were trying to achieve

    +
    +
    + + + -

    Step 2 of 4

    +

    Step 2 of 4

    - + - + -
    - -
    - -
    +
    + +
    + +
    diff --git a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskDifficulty.cshtml b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskDifficulty.cshtml index e33caa7e90..1129f444dc 100644 --- a/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskDifficulty.cshtml +++ b/DigitalLearningSolutions.Web/Views/UserFeedback/UserFeedbackTaskDifficulty.cshtml @@ -1,99 +1,98 @@ @using DigitalLearningSolutions.Web.ViewModels.UserFeedback +@using Microsoft.AspNetCore.Http.Extensions @using Microsoft.AspNetCore.Mvc.TagHelpers @model UserFeedbackViewModel; @{ - ViewData["Title"] = "Feedback"; + ViewData["Title"] = "Feedback"; } @section NavBreadcrumbs { - + }
    -
    -

    Give page or website feedback

    -

    How easy or difficult was it to achieve your task?

    -
    -
    -

    Step 3 of 4

    - -
    - - +
    +

    Give page or website feedback

    +

    How easy or difficult was it to achieve your task?

    +
    +
    +

    Step 3 of 4

    -
    - Please select an option -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    - -
    +
    + + + +
    + Please select an option +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    diff --git a/DigitalLearningSolutions.Web/appSettings.UAT.json b/DigitalLearningSolutions.Web/appSettings.UAT.json index 901b5faf5c..3c6921bb01 100644 --- a/DigitalLearningSolutions.Web/appSettings.UAT.json +++ b/DigitalLearningSolutions.Web/appSettings.UAT.json @@ -11,7 +11,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "WorkforceManagerInterface": false, "SupervisorProfileAssessmentInterface": false, "RefactoredSuperAdminInterface": true, diff --git a/DigitalLearningSolutions.Web/appsettings.Development.json b/DigitalLearningSolutions.Web/appsettings.Development.json index 38bb47ba6e..cdff87642d 100644 --- a/DigitalLearningSolutions.Web/appsettings.Development.json +++ b/DigitalLearningSolutions.Web/appsettings.Development.json @@ -11,7 +11,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "SupervisorProfileAssessmentInterface": true, "WorkforceManagerInterface": true, "RefactoredSuperAdminInterface": true, diff --git a/DigitalLearningSolutions.Web/appsettings.Production.json b/DigitalLearningSolutions.Web/appsettings.Production.json index 4ca78c763d..02a9d9fd44 100644 --- a/DigitalLearningSolutions.Web/appsettings.Production.json +++ b/DigitalLearningSolutions.Web/appsettings.Production.json @@ -11,7 +11,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "WorkforceManagerInterface": false, "SupervisorProfileAssessmentInterface": false, "RefactoredSuperAdminInterface": true, @@ -22,7 +22,8 @@ "UserFeedbackBar": true, "ExportQueryRowLimit": 250, "MaxBulkUploadRows": 200, - "LoginWithLearningHub": true + "LoginWithLearningHub": true, + "TableauSelfAssessmentDashboards": false }, "LearningHubOpenAPIBaseUrl": "https://learninghubnhsuk-openapi-prod.azurewebsites.net", "LearningHubReportAPIConfig": { diff --git a/DigitalLearningSolutions.Web/appsettings.SIT.json b/DigitalLearningSolutions.Web/appsettings.SIT.json index eca16765dc..a606afc359 100644 --- a/DigitalLearningSolutions.Web/appsettings.SIT.json +++ b/DigitalLearningSolutions.Web/appsettings.SIT.json @@ -10,7 +10,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "SupervisorProfileAssessmentInterface": true, "WorkforceManagerInterface": true, "RefactoredSuperAdminInterface": true, diff --git a/DigitalLearningSolutions.Web/appsettings.Test.json b/DigitalLearningSolutions.Web/appsettings.Test.json index fc362e3a44..a386df209d 100644 --- a/DigitalLearningSolutions.Web/appsettings.Test.json +++ b/DigitalLearningSolutions.Web/appsettings.Test.json @@ -11,7 +11,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "SupervisorProfileAssessmentInterface": true, "WorkforceManagerInterface": true, "RefactoredSuperAdminInterface": true, diff --git a/DigitalLearningSolutions.Web/appsettings.UarTest.json b/DigitalLearningSolutions.Web/appsettings.UarTest.json index 20754edd85..82b0d9423f 100644 --- a/DigitalLearningSolutions.Web/appsettings.UarTest.json +++ b/DigitalLearningSolutions.Web/appsettings.UarTest.json @@ -11,7 +11,7 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": true, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "WorkforceManagerInterface": false, "SupervisorProfileAssessmentInterface": false, "RefactoredSuperAdminInterface": false, diff --git a/DigitalLearningSolutions.Web/appsettings.json b/DigitalLearningSolutions.Web/appsettings.json index d409a45007..4d7000b3e3 100644 --- a/DigitalLearningSolutions.Web/appsettings.json +++ b/DigitalLearningSolutions.Web/appsettings.json @@ -12,12 +12,13 @@ "MonthsToPromptUserDetailsCheck": 6, "FeatureManagement": { "RefactoredTrackingSystem": false, - "ShowAppCardForLegacyTrackingSystem": true, + "ShowAppCardForLegacyTrackingSystem": false, "RefactoredSuperAdminInterface": false, "UseSignposting": true, "PricingPage": true, "ShowSelfAssessmentProgressButtons": false, - "LoginWithLearningHub": true + "LoginWithLearningHub": true, + "TableauSelfAssessmentDashboards": true }, "LearningHubOpenAPIBaseUrl": "https://uks-learninghubnhsuk-openapi-test.azurewebsites.net", "LearningHubOpenAPIKey": "", @@ -78,5 +79,18 @@ "LearningHubUserApi": { "UserApiUrl": "https://userapi.learninghub.nhs.uk/api/" }, + "TableauDashboards": { + "SiteUrl": "https://tabuat.data.england.nhs.uk", + "SiteName": "monitor", + "AuthApiPath": "/api/3.21/auth/signin", + "WorkBookName": "DLSIdentifiableDataNHSEUAT", + "ViewName": "Cover", + "CompetencyDashboardUrl": "https://tabuat.data.england.nhs.uk/#/site/monitor/views/DLSIdentifiableDataNHSEUAT/Cover", + "Username": "svc-tel-dls", + "ClientName": "tel_dls", + "ClientId": "a7906ce3-e0c9-403e-a169-8eb78d858f8a", + "ClientSecretId": "38b99058-a806-4ee2-bf7a-ad1933a81ae5", + "ClientSecret": "" + }, "UserResearchUrl": "https://forms.office.com/e/nKcK8AdHRX" }