diff --git a/DigitalLearningSolutions.Data.Migrations/202507221424_AddNewColumnsInSelfAssessments.cs b/DigitalLearningSolutions.Data.Migrations/202507221424_AddNewColumnsInSelfAssessments.cs new file mode 100644 index 0000000000..7b6c381573 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202507221424_AddNewColumnsInSelfAssessments.cs @@ -0,0 +1,22 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + [Migration(202507221424)] + public class AddNewColumnsInSelfAssessments : Migration + { + public override void Up() + { + Alter.Table("SelfAssessments") + .AddColumn("RetirementDate").AsDateTime().Nullable() + .AddColumn("EnrolmentCutoffDate").AsDateTime().Nullable() + .AddColumn("RetirementReason").AsString(2000).Nullable(); + } + + public override void Down() + { + Delete.Column("RetirementDate").FromTable("SelfAssessments"); + Delete.Column("EnrolmentCutoffDate").FromTable("SelfAssessments"); + Delete.Column("RetirementReason").FromTable("SelfAssessments"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202507240953_Alter_GetActivitiesForDelegateEnrolment_Retired_SA.cs b/DigitalLearningSolutions.Data.Migrations/202507240953_Alter_GetActivitiesForDelegateEnrolment_Retired_SA.cs new file mode 100644 index 0000000000..abf874dd9b --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202507240953_Alter_GetActivitiesForDelegateEnrolment_Retired_SA.cs @@ -0,0 +1,18 @@ +using FluentMigrator; + +namespace DigitalLearningSolutions.Data.Migrations +{ + [Migration(202507240953)] + public class Alter_GetActivitiesForDelegateEnrolment_Retired_SA : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_5535_Alter_GetActivitiesForDelegateEnrolment_Up); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_5535_Alter_GetActivitiesForDelegateEnrolment_Down); + } + } +} + diff --git a/DigitalLearningSolutions.Data.Migrations/202508191440_CreateSendRetiringSelfAssessmentNotificationSP.cs b/DigitalLearningSolutions.Data.Migrations/202508191440_CreateSendRetiringSelfAssessmentNotificationSP.cs new file mode 100644 index 0000000000..e657f3d384 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202508191440_CreateSendRetiringSelfAssessmentNotificationSP.cs @@ -0,0 +1,31 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + [Migration(202508191440)] + public class CreateSendRetiringSelfAssessmentNotificationSP : Migration + { + public override void Up() + { + string generalTELTeam = + @"[ + { ""FirstName"":"""", ""LastName"":"""", ""Email"":""Support@dls.nhs.uk"" }, + { ""FirstName"":""Anna"", ""LastName"":""Athanasopoulou"", ""Email"":""anna.athanasopoulou@nhs.net"" }, + { ""FirstName"":""Benjamin"", ""LastName"":""Witton"", ""Email"":""Benjamin.witton1@nhs.net"" } + ]"; + + Execute.Sql(@$"IF NOT EXISTS (SELECT ConfigID FROM Config WHERE ConfigName = 'GeneralTELTeam') + BEGIN + INSERT INTO Config VALUES ('GeneralTELTeam', '{generalTELTeam}', 0,GETDATE(), GETDATE()) + END" + ); + + Execute.Sql(Properties.Resources.TD_5552_SendRetiringNotification); + } + public override void Down() + { + Execute.Sql(@"DELETE FROM Config WHERE ConfigName = N'GeneralTELTeam'"); + + Execute.Sql("DROP PROCEDURE [dbo].[SendRetiringSelfAssessmentNotification]"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202509180915_Alter_SendRetiringSelfAssessmentNotification.cs b/DigitalLearningSolutions.Data.Migrations/202509180915_Alter_SendRetiringSelfAssessmentNotification.cs new file mode 100644 index 0000000000..8e870cd933 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202509180915_Alter_SendRetiringSelfAssessmentNotification.cs @@ -0,0 +1,19 @@ + + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202509180915)] + public class Alter_SendRetiringSelfAssessmentNotification : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_5552_Alter_SendRetiringSelfAssessmentNotification_Up); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_5552_Alter_SendRetiringSelfAssessmentNotification_Down); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs index 0f99da6fea..ec509516b1 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs @@ -478,8 +478,7 @@ internal static string DLSV2_272_AlterGetLinkedFieldNameFunction_UP { ///-- Create date: 15/10/2021 ///-- Description: Reorders the CompetencyAssessmentQuestions - moving the given competency question up or down. ///-- ============================================= - ///CREATE OR ALTER PROCEDURE [dbo].[ReorderCompetencyAssessmentQuestion] - /// [rest of string was truncated]";. + ///CREATE OR ALTER PROCEDURE [dbo].[ReorderCompetencyAssessmentQuestion] /// [rest of string was truncated]";. /// internal static string DLSV2_379_ReorderCompetencyAssessmentQuestionsSP { get { @@ -1505,8 +1504,7 @@ internal static string TD_3190_SendOneMonthSelfAssessmentOverdueRemindersSP { /// @EmailProfileName nvarchar(100), /// @TestOnly bit ///AS - ///BEGIN - /// [rest of string was truncated]";. + ///BEGIN /// [rest of string was truncated]";. /// internal static string TD_3190_SendOneMonthSelfAssessmentTBCRemindersSP { get { @@ -2526,17 +2524,150 @@ internal static string TD_5514_Alter_SendExpiredTBCReminders_Up { } } + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetActivitiesForDelegateEnrolment] Script Date: 24/07/2025 02:06:43 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 24/01/2023 + ///-- Description: Returns active available for delegate enrolment based on original GetActiveAvailableCustomisationsForCentreFiltered_V6 sproc but adjusted for user account refactor and filters properly for category. + ///-- ========= [rest of string was truncated]";. + /// + internal static string TD_5535_Alter_GetActivitiesForDelegateEnrolment_Down { + get { + return ResourceManager.GetString("TD_5535_Alter_GetActivitiesForDelegateEnrolment_Down", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[GetActivitiesForDelegateEnrolment] Script Date: 24/07/2025 02:06:43 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 24/01/2023 + ///-- Description: Returns active available for delegate enrolment based on original GetActiveAvailableCustomisationsForCentreFiltered_V6 sproc but adjusted for user account refactor and filters properly for category. + ///-- ========= [rest of string was truncated]";. + /// + internal static string TD_5535_Alter_GetActivitiesForDelegateEnrolment_Up { + get { + return ResourceManager.GetString("TD_5535_Alter_GetActivitiesForDelegateEnrolment_Up", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendRetiringSelfAssessmentNotification] Script Date: 18/09/2025 09:03:21 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Auldrin Possa + ///-- Create date: 04/08/2015 + ///-- Description: Uses DB mail to send notification to delegates on retiring self assessment. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendRetiringSelfAssessmentNotification] + /// @SelfAssessmentId [rest of string was truncated]";. + /// + internal static string TD_5552_Alter_SendRetiringSelfAssessmentNotification_Down { + get { + return ResourceManager.GetString("TD_5552_Alter_SendRetiringSelfAssessmentNotification_Down", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendRetiringSelfAssessmentNotification] Script Date: 18/09/2025 09:03:21 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Auldrin Possa + ///-- Create date: 04/08/2015 + ///-- Description: Uses DB mail to send notification to delegates on retiring self assessment. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendRetiringSelfAssessmentNotification] + /// @SelfAssessmentId [rest of string was truncated]";. + /// + internal static string TD_5552_Alter_SendRetiringSelfAssessmentNotification_Up { + get { + return ResourceManager.GetString("TD_5552_Alter_SendRetiringSelfAssessmentNotification_Up", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + /// + /// + ///-- ============================================= + ///-- Author: Auldrin Possa + ///-- Create date: 04/08/2015 + ///-- Description: Uses DB mail to send notification to delegates on retiring self assessment. + ///-- ============================================= + ///CREATE PROCEDURE [dbo].[SendRetiringSelfAssessmentNotification] + /// @SelfAssessmentId int, + /// @TestOnly bit + ///AS + ///BEGIN + /// -- SET NOCOUNT ON added to prevent extra result sets from + /// -- interfering with SELEC [rest of string was truncated]";. + /// + internal static string TD_5552_SendRetiringNotification { + get { + return ResourceManager.GetString("TD_5552_SendRetiringNotification", resourceCulture); + } + } + /// /// Looks up a localized string similar to IF OBJECT_ID('dbo.IndexOptimize', 'P') IS NOT NULL DROP PROCEDURE dbo.IndexOptimize; ///IF OBJECT_ID('dbo.CommandExecute', 'P') IS NOT NULL DROP PROCEDURE dbo.CommandExecute; + ///IF OBJECT_ID('dbo.sp_purge_commandlog', 'P') IS NOT NULL DROP PROCEDURE dbo.sp_purge_commandlog; ///IF OBJECT_ID('dbo.CommandLog', 'U') IS NOT NULL DROP TABLE dbo.CommandLog; ///. /// internal static string TD_5670_MaintenanceScripts_DOWN { get { return ResourceManager.GetString("TD-5670-MaintenanceScripts_DOWN", resourceCulture); - } + } + } + + /// + /// Looks up a localized string similar to -- ============================================ + ///-- Drop if exists (for clean redeploy) + ///-- ============================================ + ///IF OBJECT_ID('dbo.IndexOptimize', 'P') IS NOT NULL DROP PROCEDURE dbo.IndexOptimize; + ///IF OBJECT_ID('dbo.DatabaseIntegrityCheck', 'P') IS NOT NULL DROP PROCEDURE dbo.DatabaseIntegrityCheck; + ///IF OBJECT_ID('dbo.CommandExecute', 'P') IS NOT NULL DROP PROCEDURE dbo.CommandExecute; + ///IF OBJECT_ID('dbo.CommandLog', 'U') IS NOT NULL DROP TABLE dbo.CommandLog; + ///GO + /// + ///-- =========== [rest of string was truncated]";. + /// + internal static string TD_5670_MaintenanceScripts_UP { + get { + return ResourceManager.GetString("TD_5670_MaintenanceScripts_UP", resourceCulture); + } } + + /// /// Looks up a localized string similar to CREATE OR ALTER PROCEDURE [dbo].[usp_GetSelfAssessmentReport] /// @SelfAssessmentID INT, /// @CentreID INT @@ -2561,27 +2692,6 @@ internal static string TD_5759_CreateOrAlterSelfAssessmentReportSPandTVF_Fix_UP } /// - /// Looks up a localized string similar to -- ============================================ - ///-- CommandLog table - ///-- ============================================ - ///IF OBJECT_ID('dbo.CommandLog', 'U') IS NOT NULL DROP TABLE dbo.CommandLog; - ///CREATE TABLE dbo.CommandLog ( - /// ID INT IDENTITY PRIMARY KEY, - /// DatabaseName SYSNAME NULL, - /// SchemaName SYSNAME NULL, - /// ObjectName SYSNAME NULL, - /// ObjectType CHAR(2) NULL, - /// IndexName SYSNAME NULL, - /// IndexType TINYINT NULL, - /// StatisticsName SYSNAME NULL, - /// PartitionNumber INT NULL, - /// Ext [rest of string was truncated]";. - /// - internal static string TD_5670_MaintenanceScripts_UP { - get { - return ResourceManager.GetString("TD_5670_MaintenanceScripts_UP", resourceCulture); - } - } /// Looks up a localized string similar to CREATE OR ALTER FUNCTION dbo.GetOtherCentresForSelfAssessmentTVF ///( /// @UserID INT, diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx index 55d97e6ce3..bb42e0ec70 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx @@ -499,4 +499,19 @@ ..\Scripts\TD-5759_CreateOrAlterSelfAssessmentReportSPandTVF-Fix_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + ..\Scripts\TD-5535-Alter_GetActivitiesForDelegateEnrolment_Down.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5535-Alter_GetActivitiesForDelegateEnrolment_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5552-SendRetiringNotification.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5552-Alter_SendRetiringSelfAssessmentNotification_Down.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5552-Alter_SendRetiringSelfAssessmentNotification_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-5535-Alter_GetActivitiesForDelegateEnrolment_Down.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5535-Alter_GetActivitiesForDelegateEnrolment_Down.sql new file mode 100644 index 0000000000..d2f8374a09 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5535-Alter_GetActivitiesForDelegateEnrolment_Down.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5535-Alter_GetActivitiesForDelegateEnrolment_Up.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5535-Alter_GetActivitiesForDelegateEnrolment_Up.sql new file mode 100644 index 0000000000..0e8c76138f Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5535-Alter_GetActivitiesForDelegateEnrolment_Up.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Down.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Down.sql new file mode 100644 index 0000000000..c416e54e50 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Down.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Up.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Up.sql new file mode 100644 index 0000000000..6ee6c7c21c Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-Alter_SendRetiringSelfAssessmentNotification_Up.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-SendRetiringNotification.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-SendRetiringNotification.sql new file mode 100644 index 0000000000..a5c5437375 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5552-SendRetiringNotification.sql differ diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs index 7009b70e53..4f3677edca 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs @@ -12,28 +12,31 @@ public IEnumerable GetSelfAssessmentsForCandidate(int del { return connection.Query( @"SELECT SelfAssessment.Id, - SelfAssessment.Name, - SelfAssessment.Description, - SelfAssessment.IncludesSignposting, - SelfAssessment.IncludeRequirementsFilters, - SelfAssessment. IsSupervisorResultsReviewed, - SelfAssessment.ReviewerCommentsLabel, - SelfAssessment. Vocabulary, - SelfAssessment. NumberOfCompetencies, - SelfAssessment.StartedDate, - SelfAssessment.LastAccessed, - SelfAssessment.CompleteByDate, - SelfAssessment.CandidateAssessmentId, - SelfAssessment.UserBookmark, - SelfAssessment.UnprocessedUpdates, - SelfAssessment.LaunchCount, - SelfAssessment. IsSelfAssessment, - SelfAssessment.SubmittedDate, - SelfAssessment. CentreName, - SelfAssessment.EnrolmentMethodId, - Signoff.SignedOff, - Signoff.Verified, - EnrolledByForename +' '+EnrolledBySurname AS EnrolledByFullName + SelfAssessment.Name, + SelfAssessment.Description, + SelfAssessment.IncludesSignposting, + SelfAssessment.IncludeRequirementsFilters, + SelfAssessment. IsSupervisorResultsReviewed, + SelfAssessment.ReviewerCommentsLabel, + SelfAssessment. Vocabulary, + SelfAssessment. NumberOfCompetencies, + SelfAssessment.StartedDate, + SelfAssessment.LastAccessed, + SelfAssessment.CompleteByDate, + SelfAssessment.CandidateAssessmentId, + SelfAssessment.UserBookmark, + SelfAssessment.UnprocessedUpdates, + SelfAssessment.LaunchCount, + SelfAssessment. IsSelfAssessment, + SelfAssessment.SubmittedDate, + SelfAssessment. CentreName, + SelfAssessment.EnrolmentMethodId, + SelfAssessment.RetirementDate, + SelfAssessment.EnrolmentCutoffDate, + SelfAssessment.RetirementReason, + Signoff.SignedOff, + Signoff.Verified, + EnrolledByForename +' '+EnrolledBySurname AS EnrolledByFullName FROM (SELECT CA.SelfAssessmentID AS Id, SA.Name, @@ -56,7 +59,10 @@ public IEnumerable GetSelfAssessmentsForCandidate(int del CR.CentreName AS CentreName, CA.EnrolmentMethodId, uEnrolledBy.FirstName AS EnrolledByForename, - uEnrolledBy.LastName AS EnrolledBySurname + uEnrolledBy.LastName AS EnrolledBySurname, + SA.RetirementDate, + SA.EnrolmentCutoffDate, + SA.RetirementReason FROM Centres AS CR INNER JOIN CandidateAssessments AS CA INNER JOIN SelfAssessments AS SA ON CA.SelfAssessmentID = SA.ID ON CR.CentreID = CA.CentreID INNER JOIN @@ -71,7 +77,7 @@ Competencies AS C RIGHT OUTER JOIN AND (ISNULL(@adminIdCategoryID, 0) = 0 OR sa.CategoryID = @adminIdCategoryId) GROUP BY CA.SelfAssessmentID, SA.Name, SA.Description, SA.IncludesSignposting, SA.SupervisorResultsReview, - SA.ReviewerCommentsLabel, SA.IncludeRequirementsFilters, + SA.ReviewerCommentsLabel, SA.IncludeRequirementsFilters, SA.RetirementDate,SA.EnrolmentCutoffDate,SA.RetirementReason, COALESCE(SA.Vocabulary, 'Capability'), CA.StartedDate, CA.LastAccessed, CA.CompleteByDate, CA.ID, CA.UserBookmark, CA.UnprocessedUpdates, CA.LaunchCount, CA.SubmittedDate, CR.CentreName,CA.EnrolmentMethodId, @@ -417,7 +423,7 @@ public void SetSubmittedDateNow(int selfAssessmentId, int delegateUserId) public void RemoveEnrolment(int selfAssessmentId, int delegateUserId) { connection.Execute( - @"UPDATE CandidateAssessments SET RemovedDate = GETDATE() + @"UPDATE CandidateAssessments SET RemovedDate = GETDATE(), SelfAssessmentProcessAgreed = NULL WHERE SelfAssessmentID = @selfAssessmentId AND DelegateUserID = @delegateUserId", new { selfAssessmentId, delegateUserId } ); @@ -507,11 +513,13 @@ public IEnumerable GetAccessor(int selfAssessmentId, int delegateUserI { return connection.Query( @"SELECT CASE WHEN AccessorPRN IS NOT NULL THEN AccessorName+', '+AccessorPRN ELSE AccessorName END AS AccessorList,AccessorName,AccessorPRN - FROM (SELECT COALESCE(au.Forename + ' ' + au.Surname + (CASE WHEN au.Active = 1 THEN '' ELSE ' (Inactive)' END), sd.SupervisorEmail) AS AccessorName, + FROM (SELECT DISTINCT COALESCE(au.Forename + ' ' + au.Surname + (CASE WHEN au.Active = 1 THEN '' ELSE ' (Inactive)' END), sd.SupervisorEmail) AS AccessorName, u.ProfessionalRegistrationNumber AS AccessorPRN FROM SupervisorDelegates AS sd INNER JOIN CandidateAssessmentSupervisors AS cas ON sd.ID = cas.SupervisorDelegateId + INNER JOIN SelfAssessmentResultSupervisorVerifications AS srsv + ON cas.ID = srsv.CandidateAssessmentSupervisorID INNER JOIN CandidateAssessments AS ca ON cas.CandidateAssessmentID = ca.ID LEFT OUTER JOIN AdminUsers AS au @@ -521,7 +529,8 @@ LEFT OUTER JOIN SelfAssessmentSupervisorRoles AS sasr ON cas.SelfAssessmentSupervisorRoleID = sasr.ID INNER JOIN Users AS u ON U.PrimaryEmail = au.Email WHERE - (sd.Removed IS NULL) AND (cas.Removed IS NULL) AND (ca.DelegateUserID = @DelegateUserID) AND (ca.SelfAssessmentID = @selfAssessmentId)) Accessor + (ca.DelegateUserID = @DelegateUserID) AND (ca.SelfAssessmentID = @selfAssessmentId) + AND (srsv.Verified IS NOT NULL)) Accessor ORDER BY AccessorName, AccessorPRN DESC", new { selfAssessmentId, delegateUserID } ); diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs index dd5dc5233c..bc47f8eb21 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs @@ -16,6 +16,8 @@ public interface ISelfAssessmentDataService { //Self Assessments string? GetSelfAssessmentNameById(int selfAssessmentId); + SelfAssessment? GetSelfAssessmentById(int selfAssessmentId); + SelfAssessment GetSelfAssessmentRetirementDateById(int selfAssessmentId); // CompetencyDataService IEnumerable GetCompetencyIdsForSelfAssessment(int selfAssessmentId); @@ -205,6 +207,67 @@ FROM SelfAssessments return name; } + public SelfAssessment? GetSelfAssessmentById(int selfAssessmentId) + { + return connection.Query( + @"SELECT [ID] + ,[Name] + ,[Description] + ,[IncludesSignposting] + ,[BrandID] + ,[CreatedDate] + ,[CreatedByCentreID] + ,[CreatedByAdminID] + ,[ArchivedDate] + ,[ArchivedByAdminID] + ,[IncludeDevelopment] + ,[ParentSelfAssessmentID] + ,[NRPProfessionalGroupID] + ,[NRPSubGroupID] + ,[NRPRoleID] + ,[PublishStatusID] + ,[UpdatedByAdminID] + ,[National] + ,[Public] + ,[Archived] + ,[LastEdit] + ,[SupervisorSelfAssessmentReview] + ,[SupervisorResultsReview] + ,[RAGResults] + ,[LinearNavigation] + ,[CategoryID] + ,[UseDescriptionExpanders] + ,[ManageOptionalCompetenciesPrompt] + ,[Vocabulary] + ,[SignOffRequestorStatement] + ,[SignOffSupervisorStatement] + ,[QuestionLabel] + ,[DescriptionLabel] + ,[EnforceRoleRequirementsForSignOff] + ,[ReviewerCommentsLabel] + ,[ManageSupervisorsDescription] + ,[IncludeRequirementsFilters] + ,[MinimumOptionalCompetencies] + ,[RetirementDate] + ,[EnrolmentCutoffDate] + ,[RetirementReason] + FROM SelfAssessments + WHERE ID = @selfAssessmentId", + new { selfAssessmentId } + ).SingleOrDefault(); + } + public SelfAssessment GetSelfAssessmentRetirementDateById(int selfAssessmentId) + { + var date = connection.QueryFirstOrDefault( + @"SELECT Id,Name,[RetirementDate] + FROM SelfAssessments + WHERE ID = @selfAssessmentId" + , + new { selfAssessmentId } + ); + return date; + } + public (IEnumerable, int) GetSelfAssessmentDelegates(string searchString, int offSet, int itemsPerPage, string sortBy, string sortDirection, int? selfAssessmentId, int centreId, bool? isDelegateActive, bool? removed, bool? submitted, bool? signedOff) { @@ -682,7 +745,8 @@ public void RemoveDelegateSelfAssessment(int candidateAssessmentsId) connection.Execute( @"BEGIN TRY BEGIN TRANSACTION - UPDATE CandidateAssessments SET RemovedDate = GETUTCDATE(), RemovalMethodID = 2 + UPDATE CandidateAssessments SET RemovedDate = GETUTCDATE(), RemovalMethodID = 2, + SelfAssessmentProcessAgreed = NULL WHERE ID = @candidateAssessmentsId AND RemovedDate IS NULL COMMIT TRANSACTION diff --git a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs index e0218b3e55..0abb090205 100644 --- a/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SupervisorDataService.cs @@ -796,7 +796,8 @@ FROM CandidateAssessments AS CA WHERE (DelegateUserID = @delegateUserId) AND (RemovedDate IS NULL) AND (CompletedDate IS NULL))) AND ((rp.SupervisorSelfAssessmentReview = 1) OR (rp.SupervisorResultsReview = 1)) - AND (ISNULL(@categoryId, 0) = 0 OR rp.CategoryID = @categoryId)", new { delegateUserId, centreId, categoryId } + AND (ISNULL(@categoryId, 0) = 0 OR rp.CategoryID = @categoryId) AND + ((CAST(rp.RetirementDate AS DATE) >= CAST(GETUTCDATE() AS DATE)) OR rp.RetirementDate IS NULL)", new { delegateUserId, centreId, categoryId } ); } @@ -999,7 +1000,8 @@ public bool RemoveCandidateAssessment(int candidateAssessmentId) @" BEGIN TRY BEGIN TRANSACTION - UPDATE CandidateAssessments SET RemovedDate = getUTCDate(), RemovalMethodID = 2 + UPDATE CandidateAssessments SET RemovedDate = getUTCDate(), RemovalMethodID = 2, + SelfAssessmentProcessAgreed = NULL WHERE ID = @candidateAssessmentId AND RemovedDate IS NULL COMMIT TRANSACTION diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs index 1beabfb672..dbd1da0de6 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/SelfAssessment.cs @@ -1,4 +1,6 @@ -namespace DigitalLearningSolutions.Data.Models.SelfAssessments +using System; + +namespace DigitalLearningSolutions.Data.Models.SelfAssessments { public class SelfAssessment : CurrentLearningItem { @@ -11,6 +13,10 @@ public class SelfAssessment : CurrentLearningItem public string? ManageOptionalCompetenciesPrompt { get; set; } public string? QuestionLabel { get; set; } public string? DescriptionLabel { get; set; } + public DateTime? RetirementDate { get; set; } + + public DateTime? EnrolmentCutoffDate { get; set; } + public string? RetirementReason { get; set; } } } diff --git a/DigitalLearningSolutions.Data/Models/SessionData/Supervisor/SessionEnrolOnRoleProfile.cs b/DigitalLearningSolutions.Data/Models/SessionData/Supervisor/SessionEnrolOnRoleProfile.cs index 29b7b1b261..6d2f3e777a 100644 --- a/DigitalLearningSolutions.Data/Models/SessionData/Supervisor/SessionEnrolOnRoleProfile.cs +++ b/DigitalLearningSolutions.Data/Models/SessionData/Supervisor/SessionEnrolOnRoleProfile.cs @@ -6,5 +6,6 @@ public class SessionEnrolOnRoleProfile public int? SelfAssessmentID { get; set; } public DateTime? CompleteByDate { get; set; } public int? SelfAssessmentSupervisorRoleId { get; set; } + public bool ActionConfirmed { get; set; } } } diff --git a/DigitalLearningSolutions.Data/Models/SessionData/Tracking/Delegate/Enrol/SessionEnrolDelegate.cs b/DigitalLearningSolutions.Data/Models/SessionData/Tracking/Delegate/Enrol/SessionEnrolDelegate.cs index 6f53abf300..307379493a 100644 --- a/DigitalLearningSolutions.Data/Models/SessionData/Tracking/Delegate/Enrol/SessionEnrolDelegate.cs +++ b/DigitalLearningSolutions.Data/Models/SessionData/Tracking/Delegate/Enrol/SessionEnrolDelegate.cs @@ -17,5 +17,6 @@ public class SessionEnrolDelegate public bool IsSelfAssessment { get; set; } public int AssessmentVersion { get; set; } public int? AssessmentCategoryID { get; set; } + public bool ActionConfirmed { get; set; } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Delegates/EnrolControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Delegates/EnrolControllerTests.cs index bf5ec33761..60d5f7f65f 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Delegates/EnrolControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Delegates/EnrolControllerTests.cs @@ -22,6 +22,7 @@ public class EnrolControllerTests private ISupervisorService supervisorService = null!; private ICourseService courseService = null!; private IEnrolService enrolService = null!; + private ISelfAssessmentService selfAssessmentService = null!; private HttpRequest httpRequest = null!; private HttpResponse httpResponse = null!; private HttpContext httpContext = null!; @@ -35,6 +36,7 @@ public void Setup() supervisorService = A.Fake(); enrolService = A.Fake(); courseService = A.Fake(); + selfAssessmentService = A.Fake(); sessionEnrolDelegate = A.Fake(); httpRequest = A.Fake(); @@ -46,7 +48,8 @@ public void Setup() multiPageFormService, supervisorService, enrolService, - courseService) + courseService, + selfAssessmentService) .WithMockHttpContext(httpRequest, null, null, httpResponse) .WithMockTempData() .WithDefaultContext() diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Available.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Available.cs index 6e8a5a3d90..b9e16e7ac6 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Available.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/Available.cs @@ -4,8 +4,10 @@ using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; using DigitalLearningSolutions.Web.Attributes; using DigitalLearningSolutions.Web.Helpers; + using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Available; using Microsoft.AspNetCore.Mvc; + using System; using System.Linq; public partial class LearningPortalController @@ -58,8 +60,41 @@ public IActionResult AllAvailableItems() public IActionResult EnrolOnSelfAssessment(int selfAssessmentId) { + var selfAssessment = selfAssessmentService.GetSelfAssessmentRetirementDateById(selfAssessmentId); + if(CheckRetirementDate(selfAssessment.RetirementDate)) return RedirectToAction("ConfirmRetirement", new { selfAssessmentId }); courseService.EnrolOnSelfAssessment(selfAssessmentId, User.GetUserIdKnownNotNull(), User.GetCentreIdKnownNotNull()); return RedirectToAction("SelfAssessment", new { selfAssessmentId }); } + + [Route("/LearningPortal/Retirement/{selfAssessmentId:int}/confirm")] + public IActionResult ConfirmRetirement(int selfAssessmentId) + { + var selfAssessment = selfAssessmentService.GetSelfAssessmentRetirementDateById(selfAssessmentId); + var model = new RetirementViewModel(selfAssessmentId, selfAssessment.RetirementDate, selfAssessment.Name); + return View("Available/ConfirmRetirement", model); + } + [HttpPost] + [Route("/LearningPortal/Retirement/{selfAssessmentId:int}/confirm")] + public IActionResult ConfirmRetirement(RetirementViewModel retirementViewModel) + { + if (!ModelState.IsValid && !retirementViewModel.ActionConfirmed) + { + var selfAssessment = selfAssessmentService.GetSelfAssessmentRetirementDateById(retirementViewModel.SelfAssessmentId); + var model = new RetirementViewModel(retirementViewModel.SelfAssessmentId , selfAssessment.RetirementDate, selfAssessment.Name); + return View("Available/ConfirmRetirement", model); + } + var date = selfAssessmentService.GetSelfAssessmentRetirementDateById(retirementViewModel.SelfAssessmentId); + courseService.EnrolOnSelfAssessment(retirementViewModel.SelfAssessmentId, User.GetUserIdKnownNotNull(), User.GetCentreIdKnownNotNull()); + return RedirectToAction("SelfAssessment", new { retirementViewModel.SelfAssessmentId }); + } + private bool CheckRetirementDate(DateTime? date) + { + if (date == null) + return false; + + DateTime twoWeeksbeforeRetirementdate = DateTime.Today.AddDays(14); + DateTime today = DateTime.Today; + return (date >= today && date <= twoWeeksbeforeRetirementdate); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs index 4fac46e109..b4de042350 100644 --- a/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs +++ b/DigitalLearningSolutions.Web/Controllers/SupervisorController/Supervisor.cs @@ -12,6 +12,7 @@ using DigitalLearningSolutions.Web.Extensions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ServiceFilter; + using DigitalLearningSolutions.Web.ViewModels.Common; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; using DigitalLearningSolutions.Web.ViewModels.Supervisor; using GDS.MultiPageFormData.Enums; @@ -391,7 +392,7 @@ public IActionResult ReviewDelegateSelfAssessment(int supervisorDelegateId, int foreach (var competency in competencies) { competency.CompetencyFlags = flags.Where(f => f.CompetencyId == competency.Id); - }; + } if (superviseDelegate.DelegateUserID != null) { @@ -785,12 +786,22 @@ public IActionResult EnrolSetRoleProfile(int supervisorDelegateId, int selfAsses return View("EnrolDelegateOnProfileAssessment", model); } + if (sessionEnrolOnRoleProfile.SelfAssessmentID != selfAssessmentID) + sessionEnrolOnRoleProfile = new SessionEnrolOnRoleProfile(); + sessionEnrolOnRoleProfile.SelfAssessmentID = selfAssessmentID; multiPageFormService.SetMultiPageFormData( sessionEnrolOnRoleProfile, MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, TempData ); + + var retirementDate = selfAssessmentService.GetSelfAssessmentById(selfAssessmentID).RetirementDate; + if (SelfAssessmentHelper.CheckRetirementDate(retirementDate)) + { + return RedirectToAction("ConfirmRetiringSelfAssessment", "Supervisor", new { supervisorDelegateId }); + } + return RedirectToAction( "EnrolDelegateCompleteBy", "Supervisor", @@ -798,6 +809,60 @@ public IActionResult EnrolSetRoleProfile(int supervisorDelegateId, int selfAsses ); } + [Route("/Supervisor/Staff/{supervisorDelegateId}/ProfileAssessment/Enrol/Confirm")] + public IActionResult ConfirmRetiringSelfAssessment(int supervisorDelegateId) + { + var sessionEnrolOnRoleProfile = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, + TempData + ).GetAwaiter().GetResult(); + + var retirementDate = selfAssessmentService.GetSelfAssessmentById((int)sessionEnrolOnRoleProfile.SelfAssessmentID).RetirementDate; + if (!SelfAssessmentHelper.CheckRetirementDate((retirementDate))) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } + var model = new RetiringSelfAssessmentViewModel() + { + SelfAssessmentID = (int)sessionEnrolOnRoleProfile.SelfAssessmentID, + RouteID = supervisorDelegateId, + RetirementDate = retirementDate, + ActionConfirmed = sessionEnrolOnRoleProfile.ActionConfirmed + }; + return View("ConfirmRetiringSelfAssessment", model); + } + + [HttpPost] + [Route("/Supervisor/Staff/{supervisorDelegateId}/ProfileAssessment/Enrol/Confirm")] + public IActionResult ConfirmRetiringSelfAssessment(RetiringSelfAssessmentViewModel retiringSelfAssessment) + { + var sessionEnrolOnRoleProfile = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, + TempData + ).GetAwaiter().GetResult(); + + sessionEnrolOnRoleProfile.SelfAssessmentID = retiringSelfAssessment.SelfAssessmentID; + sessionEnrolOnRoleProfile.ActionConfirmed = retiringSelfAssessment.ActionConfirmed; + multiPageFormService.SetMultiPageFormData( + sessionEnrolOnRoleProfile, + MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, + TempData + ); + + if (ModelState.IsValid && retiringSelfAssessment.ActionConfirmed) + { + return RedirectToAction( + "EnrolDelegateCompleteBy", + "Supervisor", + new { supervisorDelegateId = retiringSelfAssessment.RouteID } + ); + } + else + { + return View("ConfirmRetiringSelfAssessment", retiringSelfAssessment); + } + } + [Route("/Supervisor/Staff/{supervisorDelegateId}/ProfileAssessment/Enrol/CompleteBy")] [ResponseCache(CacheProfileName = "Never")] [TypeFilter( @@ -810,11 +875,12 @@ public IActionResult EnrolDelegateCompleteBy(int supervisorDelegateId, int? day, MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, TempData ).GetAwaiter().GetResult(); - multiPageFormService.SetMultiPageFormData( - sessionEnrolOnRoleProfile, - MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, - TempData - ); + + var retirementDate = selfAssessmentService.GetSelfAssessmentById((int)sessionEnrolOnRoleProfile.SelfAssessmentID).RetirementDate; + if (SelfAssessmentHelper.CheckRetirementDate(retirementDate) && !sessionEnrolOnRoleProfile.ActionConfirmed) + { + return RedirectToAction("ConfirmRetiringSelfAssessment", "Supervisor", new { supervisorDelegateId }); + } var supervisorDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); var roleProfile = supervisorService.GetRoleProfileById((int)sessionEnrolOnRoleProfile.SelfAssessmentID); @@ -822,7 +888,8 @@ public IActionResult EnrolDelegateCompleteBy(int supervisorDelegateId, int? day, { SupervisorDelegateDetail = supervisorDelegate, RoleProfile = roleProfile, - CompleteByDate = sessionEnrolOnRoleProfile.CompleteByDate + CompleteByDate = sessionEnrolOnRoleProfile.CompleteByDate, + ActionConfirmed = sessionEnrolOnRoleProfile.ActionConfirmed }; if (day != null && month != null && year != null) { @@ -844,19 +911,13 @@ public IActionResult EnrolDelegateSetCompleteBy(int supervisorDelegateId, int da { var validationResult = OldDateValidator.ValidateDate(day, month, year); if (!validationResult.DateValid) - { return RedirectToAction("EnrolDelegateCompleteBy", new { supervisorDelegateId, day, month, year }); - } - else - { - var completeByDate = new DateTime(year, month, day); - sessionEnrolOnRoleProfile.CompleteByDate = completeByDate; - multiPageFormService.SetMultiPageFormData( - sessionEnrolOnRoleProfile, - MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, - TempData - ); - } + + sessionEnrolOnRoleProfile.CompleteByDate = new DateTime(year, month, day); + } + else + { + sessionEnrolOnRoleProfile.CompleteByDate = null; } var supervisorRoles = @@ -968,6 +1029,13 @@ public IActionResult EnrolDelegateSummary(int supervisorDelegateId) MultiPageFormDataFeature.EnrolDelegateOnProfileAssessment, TempData ); + + var retirementDate = selfAssessmentService.GetSelfAssessmentById((int)sessionEnrolOnRoleProfile.SelfAssessmentID).RetirementDate; + if (SelfAssessmentHelper.CheckRetirementDate(retirementDate) && !sessionEnrolOnRoleProfile.ActionConfirmed) + { + return RedirectToAction("ConfirmRetiringSelfAssessment", "Supervisor", new { supervisorDelegateId }); + } + var supervisorDelegate = supervisorService.GetSupervisorDelegateDetailsById(supervisorDelegateId, GetAdminId(), 0); var roleProfile = supervisorService.GetRoleProfileById((int)sessionEnrolOnRoleProfile.SelfAssessmentID); @@ -995,6 +1063,7 @@ public IActionResult EnrolDelegateSummary(int supervisorDelegateId) ViewBag.completeByMonth = TempData["completeByMonth"]; ViewBag.completeByYear = TempData["completeByYear"]; ViewBag.navigatedFrom = TempData["navigatedFrom"]; + ViewBag.actionConfirmed = sessionEnrolOnRoleProfile.ActionConfirmed; return View("EnrolDelegateSummary", model); } diff --git a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EnrolController.cs b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EnrolController.cs index 146aebe824..5f01790de8 100644 --- a/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EnrolController.cs +++ b/DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/EnrolController.cs @@ -16,8 +16,12 @@ namespace DigitalLearningSolutions.Web.Controllers.TrackingSystem.Delegates { + using DigitalLearningSolutions.Data.Models.SessionData.Supervisor; + using DigitalLearningSolutions.Data.Models.Supervisor; using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Web.ServiceFilter; + using DigitalLearningSolutions.Web.ViewModels.Common; + using Pipelines.Sockets.Unofficial; [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] [Authorize(Policy = CustomPolicies.UserCentreAdmin)] @@ -29,18 +33,21 @@ public partial class EnrolController : Controller private readonly ISupervisorService supervisorService; private readonly IEnrolService enrolService; private readonly ICourseService courseService; + private readonly ISelfAssessmentService selfAssessmentService; public EnrolController( IMultiPageFormService multiPageFormService, ISupervisorService supervisorService, IEnrolService enrolService, - ICourseService courseService + ICourseService courseService, + ISelfAssessmentService selfAssessmentService ) { this.multiPageFormService = multiPageFormService; this.supervisorService = supervisorService; this.enrolService = enrolService; this.courseService = courseService; + this.selfAssessmentService = selfAssessmentService; } public IActionResult StartEnrolProcess(int delegateId, int delegateUserId, string delegateName) @@ -116,6 +123,15 @@ public IActionResult Index(int delegateId, EnrolCurrentLearningViewModel enrolCu return View(model); } + if (sessionEnrol.AssessmentID.HasValue && sessionEnrol.AssessmentID != enrolCurrentLearningViewModel.SelectedActivity) + { + var delegateUserID = sessionEnrol.DelegateUserID; + var userName = sessionEnrol.DelegateName; + sessionEnrol = new SessionEnrolDelegate(); + sessionEnrol.DelegateID = delegateId; + sessionEnrol.DelegateName = userName; + sessionEnrol.DelegateUserID = delegateUserID; + } sessionEnrol.AssessmentID = enrolCurrentLearningViewModel.SelectedActivity; var availableCourse = selfAssessments as List; var selectedCourse = availableCourse.Find(x => x.Id == enrolCurrentLearningViewModel.SelectedActivity); @@ -126,10 +142,15 @@ public IActionResult Index(int delegateId, EnrolCurrentLearningViewModel enrolCu multiPageFormService.SetMultiPageFormData( sessionEnrol, - MultiPageFormDataFeature.EnrolDelegateInActivity, + MultiPageFormDataFeature.EnrolDelegateInActivity, TempData ); + if (HasNotConfirmedRetiring(sessionEnrol.IsSelfAssessment, (int)sessionEnrol.AssessmentID, delegateId, sessionEnrol.ActionConfirmed)) + { + return RedirectToAction("ConfirmRetiring", "Enrol", new { delegateId }); + } + return RedirectToAction( "EnrolCompleteBy", "Enrol", @@ -137,6 +158,58 @@ public IActionResult Index(int delegateId, EnrolCurrentLearningViewModel enrolCu ); } + public IActionResult ConfirmRetiring(int delegateId) + { + var sessionEnrol = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.EnrolDelegateInActivity, + TempData + ).GetAwaiter().GetResult(); + + var retirementDate = selfAssessmentService.GetSelfAssessmentById((int)sessionEnrol.AssessmentID).RetirementDate; + if (!SelfAssessmentHelper.CheckRetirementDate((retirementDate))) + { + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); + } + var model = new RetiringSelfAssessmentViewModel() + { + SelfAssessmentID = (int)sessionEnrol.AssessmentID, + RouteID = (int)sessionEnrol.DelegateID, + RetirementDate = retirementDate, + ActionConfirmed = sessionEnrol.ActionConfirmed + }; + return View("ConfirmRetiring", model); + } + + [HttpPost] + public IActionResult ConfirmRetiring(RetiringSelfAssessmentViewModel retiringSelfAssessment) + { + var sessionEnrol = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.EnrolDelegateInActivity, + TempData + ).GetAwaiter().GetResult(); + + sessionEnrol.AssessmentID = retiringSelfAssessment.SelfAssessmentID; + sessionEnrol.ActionConfirmed = retiringSelfAssessment.ActionConfirmed; + multiPageFormService.SetMultiPageFormData( + sessionEnrol, + MultiPageFormDataFeature.EnrolDelegateInActivity, + TempData + ); + + if (ModelState.IsValid && retiringSelfAssessment.ActionConfirmed) + { + return RedirectToAction( + "EnrolCompleteBy", + "Enrol", + new { delegateId = retiringSelfAssessment.RouteID } + ); + } + else + { + return View("ConfirmRetiring", retiringSelfAssessment); + } + } + [HttpGet] [TypeFilter( typeof(RedirectToErrorEmptySessionData), @@ -145,9 +218,15 @@ public IActionResult Index(int delegateId, EnrolCurrentLearningViewModel enrolCu public IActionResult EnrolCompleteBy(int delegateId) { var sessionEnrol = multiPageFormService.GetMultiPageFormData( - MultiPageFormDataFeature.EnrolDelegateInActivity, - TempData - ).GetAwaiter().GetResult(); + MultiPageFormDataFeature.EnrolDelegateInActivity, + TempData + ).GetAwaiter().GetResult(); + + if (HasNotConfirmedRetiring(sessionEnrol.IsSelfAssessment, (int)sessionEnrol.AssessmentID, delegateId, sessionEnrol.ActionConfirmed)) + { + return RedirectToAction("ConfirmRetiring", "Enrol", new { delegateId }); + } + multiPageFormService.SetMultiPageFormData( sessionEnrol, MultiPageFormDataFeature.EnrolDelegateInActivity, @@ -202,6 +281,12 @@ public IActionResult EnrolDelegateSupervisor(int delegateId) var sessionEnrol = multiPageFormService.GetMultiPageFormData( MultiPageFormDataFeature.EnrolDelegateInActivity, TempData).GetAwaiter().GetResult(); + + if (HasNotConfirmedRetiring(sessionEnrol.IsSelfAssessment, (int)sessionEnrol.AssessmentID, delegateId, sessionEnrol.ActionConfirmed)) + { + return RedirectToAction("ConfirmRetiring", "Enrol", new { delegateId }); + } + var supervisorList = supervisorService.GetSupervisorForEnrolDelegate(centreId.Value, sessionEnrol.AssessmentCategoryID.Value); if (!sessionEnrol.IsSelfAssessment) { @@ -283,6 +368,12 @@ public IActionResult EnrolDelegateSupervisor(int delegateId, EnrolSupervisorView public IActionResult EnrolDelegateSummary(int delegateId) { var sessionEnrol = multiPageFormService.GetMultiPageFormData(MultiPageFormDataFeature.EnrolDelegateInActivity, TempData).GetAwaiter().GetResult(); + + if (HasNotConfirmedRetiring(sessionEnrol.IsSelfAssessment, (int)sessionEnrol.AssessmentID, delegateId, sessionEnrol.ActionConfirmed)) + { + return RedirectToAction("ConfirmRetiring", "Enrol", new { delegateId }); + } + var roles = supervisorService.GetSupervisorRolesBySelfAssessmentIdForSupervisor(sessionEnrol.AssessmentID.GetValueOrDefault()).ToArray(); var clockUtility = new ClockUtility(); var monthDiffrence = ""; @@ -303,6 +394,7 @@ public IActionResult EnrolDelegateSummary(int delegateId) model.IsSelfAssessment = sessionEnrol.IsSelfAssessment; model.SupervisorRoleName = sessionEnrol.SelfAssessmentSupervisorRoleName; model.RoleCount = roles.Count(); + ViewBag.actionConfirmed = sessionEnrol.ActionConfirmed; return View(model); } @@ -345,5 +437,15 @@ private int GetAdminID() { return User.GetCustomClaimAsRequiredInt(CustomClaimTypes.UserAdminId); } + + private bool HasNotConfirmedRetiring(bool IsSelfAssessment, int selfAssessmentId, int delegateId, bool actionConfirmed) + { + if (IsSelfAssessment) + { + var retirementDate = selfAssessmentService.GetSelfAssessmentById(selfAssessmentId).RetirementDate; + return SelfAssessmentHelper.CheckRetirementDate(retirementDate) && !actionConfirmed; + } + return false; + } } } diff --git a/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs b/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs index 26b0e181ab..6dccdc57c7 100644 --- a/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/CompetencyFilterHelper.cs @@ -32,30 +32,46 @@ public static IEnumerable FilterCompetencies(IEnumerable private static void ApplyResponseStatusFilters(ref IEnumerable competencies, IEnumerable filters, string searchText = "") { - var filteredCompetencies = competencies; - var appliedResponseStatusFilters = filters.Where(f => IsResponseStatusFilter(f)); + var appliedResponseStatusFilters = filters.Where(IsResponseStatusFilter).ToList(); - if (appliedResponseStatusFilters.Any() || searchText.Length > 0) + if (!appliedResponseStatusFilters.Any() && string.IsNullOrWhiteSpace(searchText)) { - var wordsInSearchText = searchText.Split().Where(w => w != string.Empty); - 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 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() || responseStatusFilterMatchesAll) - select c; + return; } - competencies = filteredCompetencies; - } + // Break search text into words + var wordsInSearchText = searchText? + .Split(' ', StringSplitOptions.RemoveEmptyEntries) + ?? Array.Empty(); + + bool MatchesSearch(Competency c) => + wordsInSearchText.Length == 0 + || wordsInSearchText.All(w => + (c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + || (c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + || (c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false)); + + // Define reusable filter checks + var filterChecks = new Dictionary> + { + [SelfAssessmentCompetencyFilter.RequiresSelfAssessment] = c => c.AssessmentQuestions.Any(q => q.ResultId == null), + [SelfAssessmentCompetencyFilter.SelfAssessed] = c => c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null), + [SelfAssessmentCompetencyFilter.ConfirmationRequested] = c => c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null), + [SelfAssessmentCompetencyFilter.ConfirmationRejected] = c => c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true), + [SelfAssessmentCompetencyFilter.Verified] = c => c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true), + [SelfAssessmentCompetencyFilter.AwaitingConfirmation] = c => c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null && q.UserIsVerifier == true), + [SelfAssessmentCompetencyFilter.PendingConfirmation] = c => c.AssessmentQuestions.Any(q => q.ResultId != null && q.Verified == null && q.Requested != null && q.UserIsVerifier == false), + [SelfAssessmentCompetencyFilter.Optional] = c => c.Optional + }; + + // Require ALL applied filters to match + bool MatchesFilters(Competency c) => + !appliedResponseStatusFilters.Any() + || appliedResponseStatusFilters.All(f => filterChecks[(SelfAssessmentCompetencyFilter)f](c)); + + // Final filtering + competencies = competencies.Where(c => MatchesSearch(c) && MatchesFilters(c)); + } private static void ApplyRequirementsFilters(ref IEnumerable competencies, IEnumerable filters) { var filteredCompetencies = competencies; diff --git a/DigitalLearningSolutions.Web/Helpers/SelfAssessmentHelper.cs b/DigitalLearningSolutions.Web/Helpers/SelfAssessmentHelper.cs new file mode 100644 index 0000000000..50a8d4e2fc --- /dev/null +++ b/DigitalLearningSolutions.Web/Helpers/SelfAssessmentHelper.cs @@ -0,0 +1,17 @@ +namespace DigitalLearningSolutions.Web.Helpers +{ + using System; + + public static class SelfAssessmentHelper + { + public static bool CheckRetirementDate(DateTime? date) + { + if (date == null) + return false; + + DateTime retirementOffsetDate = DateTime.Today.AddDays(14); + DateTime today = DateTime.Today; + return (date >= today && date <= retirementOffsetDate); + } + } +} diff --git a/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs b/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs index 70d1069b5d..58dfd9e95e 100644 --- a/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs +++ b/DigitalLearningSolutions.Web/Helpers/SupervisorCompetencyFilterHelper.cs @@ -28,107 +28,37 @@ public static IEnumerable FilterCompetencies(IEnumerable } return filteredCompetencies; } - private static void ApplyResponseStatusFilters(ref IEnumerable competencies, IEnumerable filters, string searchText = "") { - var filteredCompetencies = competencies; - var appliedResponseStatusFilters = filters.Where(f => IsResponseStatusFilter(f)); - if (appliedResponseStatusFilters.Any() || searchText.Length > 0) + var appliedFilters = filters.Where(f => IsResponseStatusFilter(f)).ToList(); + var wordsInSearchText = searchText.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + if (!appliedFilters.Any() && string.IsNullOrWhiteSpace(searchText)) + return; + + competencies = competencies.Where(c => { - var wordsInSearchText = searchText.Split().Where(w => w != string.Empty); - filters = appliedResponseStatusFilters; - 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; + // Search text match + bool searchTextMatches = !wordsInSearchText.Any() || + wordsInSearchText.All(w => + (c.CompetencyGroup?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) || + (c.Description?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) || + (c.Name?.Contains(w, StringComparison.CurrentCultureIgnoreCase) ?? false) + ); + + // All filters must match (AND) + bool allFiltersMatch = appliedFilters.All(f => + (f == (int)SelfAssessmentCompetencyFilter.Verified && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff == true)) || + (f == (int)SelfAssessmentCompetencyFilter.Optional && c.Optional) || + (f == (int)SelfAssessmentCompetencyFilter.ConfirmationRejected && c.AssessmentQuestions.Any(q => q.Verified.HasValue && q.SignedOff != true)) || + (f == (int)SelfAssessmentCompetencyFilter.PendingConfirmation && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Verified == null && q.Requested != null && q.UserIsVerifier == false)) || + (f == (int)SelfAssessmentCompetencyFilter.AwaitingConfirmation && c.AssessmentQuestions.Any(q => q.Verified == null && q.Requested != null && q.UserIsVerifier == true)) || + (f == (int)SelfAssessmentCompetencyFilter.RequiresSelfAssessment && c.AssessmentQuestions.Any(q => q.ResultId == null)) || + (f == (int)SelfAssessmentCompetencyFilter.SelfAssessed && c.AssessmentQuestions.Any(q => q.ResultId != null && q.Requested == null && q.SignedOff == null)) + ); + + return searchTextMatches && allFiltersMatch; + }).ToList(); } private static void ApplyRequirementsFilters(ref IEnumerable competencies, IEnumerable filters) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 5ca64febc2..0b4b100db4 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -37,11 +37,13 @@ public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook var newCompetencyIds = competencyRows.Select(row => row.ID ?? 0).ToList(); var existingIds = frameworkService.GetFrameworkCompetencyOrder(frameworkId, newCompetencyIds); var existingGroups = frameworkService - .GetFrameworkCompetencyGroups(frameworkId) + .GetFrameworkCompetencyGroups(frameworkId).Where(x => x.FrameworkCompetencies.Any()).ToList() .Select(row => row.Name) .Distinct() .ToList(); - var newGroups = competencyRows.Select(row => row.CompetencyGroup ?? "").Distinct().ToList(); + var newGroups = competencyRows.Select(row => row.CompetencyGroup) + .Where(g => !string.IsNullOrEmpty(g)) + .Distinct().ToList(); foreach (var competencyRow in competencyRows) { PreProcessCompetencyRow(competencyRow, newCompetencyIds, existingIds, existingGroups, newGroups); diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs index e0890e32ec..27ae5e3930 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs @@ -14,6 +14,7 @@ public interface ISelfAssessmentService { //Self Assessments string? GetSelfAssessmentNameById(int selfAssessmentId); + SelfAssessment? GetSelfAssessmentById(int selfAssessmentId); // Candidate Assessments IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId, int? adminIdCategoryID); IEnumerable GetSelfAssessmentsForCandidate(int delegateUserId, int centreId); @@ -163,6 +164,7 @@ public IEnumerable GetSelfAssessmentResultswithSupervisorV int competencyId ); void RemoveReviewCandidateAssessmentOptionalCompetencies(int id); + SelfAssessment GetSelfAssessmentRetirementDateById(int selfAssessmentId); } public class SelfAssessmentService : ISelfAssessmentService @@ -472,6 +474,11 @@ public void RemoveEnrolment(int selfAssessmentId, int delegateUserId) return selfAssessmentDataService.GetSelfAssessmentNameById(selfAssessmentId); } + public SelfAssessment? GetSelfAssessmentById(int selfAssessmentId) + { + return selfAssessmentDataService.GetSelfAssessmentById(selfAssessmentId); + } + public (SelfAssessmentDelegatesData, int?) GetSelfAssessmentDelegatesPerPage(string searchString, int offSet, int itemsPerPage, string sortBy, string sortDirection, int? selfAssessmentId, int centreId, bool? isDelegateActive, bool? removed, bool? submitted, bool? signedOff, int? adminCategoryId) { @@ -616,5 +623,9 @@ public void RemoveReviewCandidateAssessmentOptionalCompetencies(int id) { selfAssessmentDataService.RemoveReviewCandidateAssessmentOptionalCompetencies(id); } + public SelfAssessment GetSelfAssessmentRetirementDateById(int selfAssessmentId) + { + return selfAssessmentDataService.GetSelfAssessmentRetirementDateById(selfAssessmentId); + } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/RetiringSelfAssessmentViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/RetiringSelfAssessmentViewModel.cs new file mode 100644 index 0000000000..9cf3603dc0 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Common/RetiringSelfAssessmentViewModel.cs @@ -0,0 +1,14 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Common +{ + using DigitalLearningSolutions.Web.Attributes; + using System; + + public class RetiringSelfAssessmentViewModel + { + public int SelfAssessmentID { get; set; } + public int RouteID { get; set; } + public DateTime? RetirementDate { get; set; } + [BooleanMustBeTrue(ErrorMessage = "Please tick the checkbox to confirm you wish to perform this action")] + public bool ActionConfirmed { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Available/RetirementViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Available/RetirementViewModel.cs new file mode 100644 index 0000000000..bfbba45cb6 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/Available/RetirementViewModel.cs @@ -0,0 +1,25 @@ +using DigitalLearningSolutions.Web.Attributes; +using System; + +namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.Available +{ + + public class RetirementViewModel + { + public RetirementViewModel() + { + + } + public RetirementViewModel(int selfAssessmentId, DateTime? retirementDate, string name) + { + SelfAssessmentId = selfAssessmentId; + RetirementDate = retirementDate; + Name = name; + } + public string Name { get; set; } = string.Empty; + public int SelfAssessmentId { get; set; } + public DateTime? RetirementDate { get; set; } + [BooleanMustBeTrue(ErrorMessage = "Please tick the checkbox to confirm you wish to perform this action")] + public bool ActionConfirmed { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Supervisor/EnrolDelegateSetCompletByDateViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Supervisor/EnrolDelegateSetCompletByDateViewModel.cs index cac66da25f..935e257fb5 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Supervisor/EnrolDelegateSetCompletByDateViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Supervisor/EnrolDelegateSetCompletByDateViewModel.cs @@ -11,5 +11,6 @@ public class EnrolDelegateSetCompletByDateViewModel public RoleProfile RoleProfile { get; set; } public DateTime? CompleteByDate { get; set; } public OldDateValidator.ValidationResult? CompleteByValidationResult { get; set; } + public bool ActionConfirmed { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Available/ConfirmRetirement.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/ConfirmRetirement.cshtml new file mode 100644 index 0000000000..7eba8f20ff --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/ConfirmRetirement.cshtml @@ -0,0 +1,54 @@ +@using DigitalLearningSolutions.Web.ViewModels.LearningPortal.Available +@using Microsoft.Extensions.Configuration +@model RetirementViewModel; +@inject IConfiguration Configuration; +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Application"] = "Learning Portal"; + ViewData["Title"] = "Retirement Confirmation"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Retirement"; + ViewData["HeaderPathName"] = "Learning Portal"; +} + +@section NavMenuItems { + +} +
+
+
+ @if (errorHasOccurred) + { + + } +

Confirm Retirement

+

You are about to enrol on a retiring @Model.Name self-assessment.

+

Retirement date: @Model.RetirementDate?.ToString("dd MMMM yyyy")

+

After this date, the @Model.Name self-assessment will no longer be accessible.

+ +

Please consider:

+
    +
  • you may have limited time to complete the self-assessment
  • + + +
  • if you do not complete it before the retirement date, your progress may not be saved
  • +
+

To continue, you must acknowledge that you understand the self-assessment is retiring and still wish to enrol.

+ + +

+ +

+ + + Cancel + + @Html.HiddenFor(m => m.SelfAssessmentId) + @Html.HiddenFor(m => m.RetirementDate) +
+
+
+ diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml index 67fde6c257..5ef9742313 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/CompetencySelfAssessmentCertificate.cshtml @@ -45,23 +45,23 @@