diff --git a/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs new file mode 100644 index 0000000000..95a0eb6020 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202503111500_AddLastAccessedColumn.cs @@ -0,0 +1,26 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202503111500)] + public class AddLastAccessedColumn : Migration + { + public override void Up() + { + Alter.Table("Users").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("DelegateAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + Alter.Table("AdminAccounts").AddColumn("LastAccessed").AsDateTime().Nullable(); + + Execute.Sql("UPDATE u SET LastAccessed = (SELECT MAX(s.LoginTime) FROM DelegateAccounts da JOIN Sessions s ON da.ID = s.CandidateId WHERE da.UserID = u.ID) FROM users u;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT MAX(s.LoginTime) FROM Sessions s WHERE s.CandidateId = da.ID) FROM DelegateAccounts da;"); + Execute.Sql("UPDATE da SET LastAccessed = (SELECT ca.LastAccessed FROM CandidateAssessments ca WHERE ca.ID = da.ID) FROM DelegateAccounts da where da.LastAccessed IS NULL;"); + Execute.Sql("UPDATE AA SET LastAccessed = (SELECT MAX(AdS.LoginTime) FROM AdminSessions AdS WHERE AdS.AdminID = AA.ID) FROM AdminAccounts AA;"); + } + public override void Down() + { + Delete.Column("LastAccessed").FromTable("Users"); + Delete.Column("LastAccessed").FromTable("DelegateAccounts"); + Delete.Column("LastAccessed").FromTable("AdminAccounts"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs b/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs new file mode 100644 index 0000000000..02bcb19b10 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/2025041161520_Alter_SendExpiredTBCReminders_AppendCourseName.cs @@ -0,0 +1,19 @@ + + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(2025041161520)] + public class Alter_SendExpiredTBCReminders_AppendCourseName : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_5514_Alter_SendExpiredTBCReminders_Up); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_5514_Alter_SendExpiredTBCReminders_Down); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202504281045_Alter_ReorderFrameworkCompetency.cs b/DigitalLearningSolutions.Data.Migrations/202504281045_Alter_ReorderFrameworkCompetency.cs new file mode 100644 index 0000000000..2759e056ac --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202504281045_Alter_ReorderFrameworkCompetency.cs @@ -0,0 +1,19 @@ + + +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202504281045)] + public class Alter_ReorderFrameworkCompetency : Migration + { + public override void Up() + { + Execute.Sql(Properties.Resources.TD_5447_Alter_ReorderFrameworkCompetency_Up); + } + public override void Down() + { + Execute.Sql(Properties.Resources.TD_5447_Alter_ReorderFrameworkCompetency_Down); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202504281115_UpdateFrameworkCompetenciesOrdering.cs b/DigitalLearningSolutions.Data.Migrations/202504281115_UpdateFrameworkCompetenciesOrdering.cs new file mode 100644 index 0000000000..e64cdd3f6f --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202504281115_UpdateFrameworkCompetenciesOrdering.cs @@ -0,0 +1,21 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + [Migration(202504281115)] + public class UpdateFrameworkCompetenciesOrdering : ForwardOnlyMigration + { + public override void Up() + { + Execute.Sql(@"WITH Ranked AS ( +    SELECT ID, +            ROW_NUMBER() OVER (PARTITION BY FrameworkID ORDER BY SysStartTime) AS NewOrder +    FROM FrameworkCompetencies + Where FrameworkCompetencyGroupID is null + ) + UPDATE fc + SET fc.Ordering = r.NewOrder + FROM FrameworkCompetencies fc + JOIN Ranked r ON fc.ID = r.ID;"); + } + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/202504290900_UpdateNotifications.cs b/DigitalLearningSolutions.Data.Migrations/202504290900_UpdateNotifications.cs new file mode 100644 index 0000000000..a662824279 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202504290900_UpdateNotifications.cs @@ -0,0 +1,14 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202504290900)] + public class UpdateNotifications : ForwardOnlyMigration + { + public override void Up() + { + Execute.Sql(@$"UPDATE Notifications SET NotificationName = 'Completed course follow-up feedback requests' where NotificationID = 13"); + } + + } +} diff --git a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs index 1b4edf4c6f..f5949119d4 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs @@ -2436,6 +2436,94 @@ internal static string TD_5412_Alter_SendExpiredTBCReminders_Up { } } + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[ReorderFrameworkCompetency] Script Date: 24/04/2025 09:23:17 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 04/01/2021 + ///-- Description: Reorders the FrameworkCompetencies in a given FrameworkCompetencyGroup - moving the given competency up or down. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[ReorderFrameworkCompetency] + /// [rest of string was truncated]";. + /// + internal static string TD_5447_Alter_ReorderFrameworkCompetency_Down { + get { + return ResourceManager.GetString("TD_5447_Alter_ReorderFrameworkCompetency_Down", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[ReorderFrameworkCompetency] Script Date: 24/04/2025 09:23:17 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 04/01/2021 + ///-- Description: Reorders the FrameworkCompetencies in a given FrameworkCompetencyGroup - moving the given competency up or down. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[ReorderFrameworkCompetency] + /// [rest of string was truncated]";. + /// + internal static string TD_5447_Alter_ReorderFrameworkCompetency_Up { + get { + return ResourceManager.GetString("TD_5447_Alter_ReorderFrameworkCompetency_Up", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendExpiredTBCReminders] Script Date: 16/04/2025 10:50:12 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 17/08/2018 + ///-- Description: Uses DB mail to send reminders to delegates on courses with a TBC date within 1 month. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendExpiredTBCReminders] + /// -- Add the parameters for the stor [rest of string was truncated]";. + /// + internal static string TD_5514_Alter_SendExpiredTBCReminders_Down { + get { + return ResourceManager.GetString("TD_5514_Alter_SendExpiredTBCReminders_Down", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to /****** Object: StoredProcedure [dbo].[SendExpiredTBCReminders] Script Date: 16/04/2025 10:50:12 ******/ + ///SET ANSI_NULLS ON + ///GO + /// + ///SET QUOTED_IDENTIFIER ON + ///GO + /// + ///-- ============================================= + ///-- Author: Kevin Whittaker + ///-- Create date: 17/08/2018 + ///-- Description: Uses DB mail to send reminders to delegates on courses with a TBC date within 1 month. + ///-- ============================================= + ///ALTER PROCEDURE [dbo].[SendExpiredTBCReminders] + /// -- Add the parameters for the stor [rest of string was truncated]";. + /// + internal static string TD_5514_Alter_SendExpiredTBCReminders_Up { + get { + return ResourceManager.GetString("TD_5514_Alter_SendExpiredTBCReminders_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 a951c296c9..c0b77842ad 100644 --- a/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx +++ b/DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx @@ -475,4 +475,16 @@ ..\Scripts\TD-5412-Alter_SendExpiredTBCReminders_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + ..\Scripts\TD-5514-Alter_SendExpiredTBCReminders_Down.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5514-Alter_SendExpiredTBCReminders_Up.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5447-Alter_ReorderFrameworkCompetency_Down.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-16 + + + ..\Scripts\TD-5447-Alter_ReorderFrameworkCompetency_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-5447-Alter_ReorderFrameworkCompetency_Down.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5447-Alter_ReorderFrameworkCompetency_Down.sql new file mode 100644 index 0000000000..56bac72b67 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5447-Alter_ReorderFrameworkCompetency_Down.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5447-Alter_ReorderFrameworkCompetency_Up.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5447-Alter_ReorderFrameworkCompetency_Up.sql new file mode 100644 index 0000000000..bd170f6b9e Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5447-Alter_ReorderFrameworkCompetency_Up.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql new file mode 100644 index 0000000000..f80def01d5 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Down.sql differ diff --git a/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql new file mode 100644 index 0000000000..448ba42946 Binary files /dev/null and b/DigitalLearningSolutions.Data.Migrations/Scripts/TD-5514-Alter_SendExpiredTBCReminders_Up.sql differ diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 3bdf62f412..b6c03802f5 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -252,9 +252,9 @@ string direction void RemoveCustomFlag(int flagId); void RemoveCollaboratorFromFramework(int frameworkId, int id); - void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int adminId); + void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int frameworkId, int adminId); - void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId); + void DeleteFrameworkCompetency(int frameworkCompetencyId, int? frameworkCompetencyGroupId, int frameworkId, int adminId); void DeleteFrameworkDefaultQuestion( int frameworkId, @@ -277,14 +277,36 @@ public class FrameworkDataService : IFrameworkDataService OwnerAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers WHERE (AdminID = FW.OwnerAdminID)) AS Owner, BrandID, - CategoryID, + FW.CategoryID, TopicID, CreatedDate, PublishStatusID, UpdatedByAdminID, (SELECT Forename + ' ' + Surname + (CASE WHEN Active = 1 THEN '' ELSE ' (Inactive)' END) AS Expr1 FROM AdminUsers AS AdminUsers_1 WHERE (AdminID = FW.UpdatedByAdminID)) AS UpdatedBy, - CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole, - fwr.ID AS FrameworkReviewID"; + CASE + WHEN (aa.UserID = (SELECT UserID FROM AdminAccounts WHERE ID = @adminId)) THEN 3 + WHEN (fwc.CanModify = 1) OR + (SELECT COUNT(*) + FROM FrameworkCollaborators fc + JOIN AdminAccounts aa1 ON fc.AdminID = aa1.ID + WHERE fc.FrameworkID = fw.ID + AND fc.CanModify = 1 AND fc.IsDeleted = 0 + AND aa1.UserID = (SELECT aa2.UserID FROM AdminAccounts aa2 WHERE aa2.ID = @adminId)) > 0 THEN 2 + WHEN (fwc.CanModify = 0) OR + (SELECT COUNT(*) + FROM FrameworkCollaborators fc + JOIN AdminAccounts aa3 ON fc.AdminID = aa3.ID + WHERE fc.FrameworkID = fw.ID + AND fc.CanModify = 0 AND fc.IsDeleted = 0 + AND aa3.UserID = (SELECT aa4.UserID FROM AdminAccounts aa4 WHERE aa4.ID = @adminId)) > 0 THEN 1 + ELSE 0 + END AS UserRole, + (SELECT fwr.ID + FROM FrameworkCollaborators fc + INNER JOIN AdminAccounts aa3 ON fc.AdminID = aa3.ID + LEFT OUTER JOIN FrameworkReviews AS fwr ON fc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL + WHERE fc.FrameworkID = fw.ID AND fc.IsDeleted = 0 + AND aa3.UserID = (SELECT aa4.UserID FROM AdminAccounts aa4 WHERE aa4.ID = @adminId)) AS FrameworkReviewID"; private const string BrandedFrameworkFields = @",(SELECT BrandName @@ -304,9 +326,8 @@ FROM CourseTopics private const string FlagFields = @"fl.ID AS FlagId, fl.FrameworkId, fl.FlagName, fl.FlagGroup, fl.FlagTagClass"; private const string FrameworkTables = - @"Frameworks AS FW LEFT OUTER JOIN - FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 - LEFT OUTER JOIN FrameworkReviews AS fwr ON fwc.ID = fwr.FrameworkCollaboratorID AND fwr.Archived IS NULL AND fwr.ReviewComplete IS NULL"; + @"Frameworks AS FW INNER JOIN AdminAccounts AS aa ON aa.ID = fw.OwnerAdminID + LEFT OUTER JOIN FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND COALESCE(IsDeleted, 0) = 0 "; private const string AssessmentQuestionFields = @"SELECT AQ.ID, AQ.Question, AQ.MinValue, AQ.MaxValue, AQ.AssessmentQuestionInputTypeID, AQI.InputTypeName, AQ.AddedByAdminId, CASE WHEN AQ.AddedByAdminId = @adminId THEN 1 ELSE 0 END AS UserIsOwner, AQ.CommentsPrompt, AQ.CommentsHint"; @@ -680,9 +701,11 @@ public int InsertFrameworkCompetency( var numberOfAffectedRows = connection.Execute( @"INSERT INTO FrameworkCompetencies ([CompetencyID], FrameworkCompetencyGroupID, UpdatedByAdminID, Ordering, FrameworkID) VALUES (@competencyId, @frameworkCompetencyGroupID, @adminId, COALESCE - ((SELECT MAX(Ordering) AS OrderNum - FROM [FrameworkCompetencies] - WHERE ([FrameworkCompetencyGroupID] = @frameworkCompetencyGroupID)), 0)+1, @frameworkId)", + ((SELECT MAX(Ordering) AS OrderNum + FROM [FrameworkCompetencies] + WHERE ((@frameworkCompetencyGroupID IS NULL AND FrameworkCompetencyGroupID IS NULL) OR + (@frameworkCompetencyGroupID IS NOT NULL AND FrameworkCompetencyGroupID = @frameworkCompetencyGroupID)) AND + FrameworkID = @frameworkId ), 0)+1, @frameworkId)", new { competencyId, frameworkCompetencyGroupID, adminId, frameworkId } ); if (numberOfAffectedRows < 1) @@ -707,7 +730,7 @@ FROM [FrameworkCompetencies] new { competencyId, frameworkCompetencyGroupID } ); } - if(addDefaultQuestions) + if (addDefaultQuestions) { AddDefaultQuestionsToCompetency(competencyId, frameworkId); } @@ -1033,7 +1056,7 @@ int adminId { var numberOfAffectedRows = connection.Execute( @"UPDATE CompetencyGroups SET Name = @name, UpdatedByAdminID = @adminId, Description = @description - WHERE ID = @competencyGroupId AND (Name <> @name OR Description <> @description)", + WHERE ID = @competencyGroupId AND (Name <> @name OR ISNULL(Description, '') <> ISNULL(@description, ''))", new { name, adminId, competencyGroupId, description } ); if (numberOfAffectedRows < 1) @@ -1134,7 +1157,7 @@ public void MoveFrameworkCompetency(int frameworkCompetencyId, bool singleStep, ); } - public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int adminId) + public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int frameworkId, int adminId) { if ((frameworkCompetencyGroupId < 1) | (adminId < 1)) { @@ -1165,7 +1188,23 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c new { frameworkCompetencyGroupId } ); - if (numberOfAffectedRows < 1) + if (numberOfAffectedRows > 0) + { + connection.Execute( + @"WITH Ranked AS ( +    SELECT ID, +            ROW_NUMBER() OVER (PARTITION BY FrameworkID ORDER BY Ordering) AS NewOrder +    FROM FrameworkCompetencyGroups + Where FrameworkID = @frameworkID + ) + UPDATE fcg + SET fcg.Ordering = r.NewOrder + FROM FrameworkCompetencyGroups fcg + JOIN Ranked r ON fcg.ID = r.ID;", + new { frameworkId } + ); + } + else { logger.LogWarning( "Not deleting framework competency group as db update failed. " + @@ -1210,7 +1249,7 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c } } - public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) + public void DeleteFrameworkCompetency(int frameworkCompetencyId, int? frameworkCompetencyGroupId, int frameworkId, int adminId) { var competencyId = connection.QuerySingle( @"SELECT CompetencyID FROM FrameworkCompetencies WHERE ID = @frameworkCompetencyId", @@ -1234,7 +1273,24 @@ public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) @"DELETE FROM FrameworkCompetencies WHERE ID = @frameworkCompetencyId", new { frameworkCompetencyId } ); - if (numberOfAffectedRows < 1) + if (numberOfAffectedRows > 0) + { + connection.Execute( + @"WITH Ranked AS ( +    SELECT ID, +            ROW_NUMBER() OVER (PARTITION BY FrameworkID ORDER BY Ordering) AS NewOrder +    FROM FrameworkCompetencies + Where (FrameworkCompetencyGroupID = @frameworkCompetencyGroupID) OR (FrameworkCompetencyGroupID IS NULL AND @frameworkCompetencyGroupID IS NULL) AND + FrameworkID = @frameworkID + ) + UPDATE fc + SET fc.Ordering = r.NewOrder + FROM FrameworkCompetencies fc + JOIN Ranked r ON fc.ID = r.ID;", + new { frameworkCompetencyGroupId, frameworkId } + ); + } + else { logger.LogWarning( "Not deleting framework competency as db update failed. " + @@ -1838,9 +1894,20 @@ FROM Competencies AS C INNER JOIN public int GetAdminUserRoleForFrameworkId(int adminId, int frameworkId) { return connection.QuerySingle( - @"SELECT CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN COALESCE (fwc.CanModify, 0) = 1 THEN 2 WHEN COALESCE (fwc.CanModify, 0) = 0 THEN 1 ELSE 0 END AS UserRole - FROM Frameworks AS FW LEFT OUTER JOIN - FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND fwc.IsDeleted = 0 + @"SELECT CASE + WHEN (aa.UserID = (SELECT UserID FROM AdminAccounts WHERE ID = @adminId)) THEN 3 + WHEN (fwc.CanModify = 1) OR + (SELECT COUNT(*) + FROM FrameworkCollaborators fc + JOIN AdminAccounts aa1 ON fc.AdminID = aa1.ID + WHERE fc.FrameworkID = fw.ID + AND fc.CanModify = 1 AND fc.IsDeleted = 0 + AND aa1.UserID = (SELECT aa2.UserID FROM AdminAccounts aa2 WHERE aa2.ID = @adminId)) > 0 THEN 2 + WHEN fwc.CanModify = 0 THEN 1 ELSE 0 + END AS UserRole + FROM Frameworks AS FW INNER JOIN + AdminAccounts AS aa ON aa.ID = fw.OwnerAdminID LEFT OUTER JOIN + FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId AND fwc.IsDeleted = 0 WHERE (FW.ID = @frameworkId)", new { adminId, frameworkId } ); @@ -2064,10 +2131,13 @@ FROM FrameworkReviews AS FR INNER JOIN { return connection.Query( @"SELECT FR.ID, FR.FrameworkID, FR.FrameworkCollaboratorID, FC.UserEmail, CAST(CASE WHEN FC.AdminID IS NULL THEN 0 ELSE 1 END AS bit) AS IsRegistered, FR.ReviewRequested, FR.ReviewComplete, FR.SignedOff, FR.FrameworkCommentID, FC1.Comments AS Comment, FR.SignOffRequired - FROM FrameworkReviews AS FR INNER JOIN - FrameworkCollaborators AS FC ON FR.FrameworkCollaboratorID = FC.ID LEFT OUTER JOIN - FrameworkComments AS FC1 ON FR.FrameworkCommentID = FC1.ID - WHERE FR.ID = @reviewId AND FR.FrameworkID = @frameworkId AND FC.AdminID = @adminId AND FR.Archived IS NULL AND IsDeleted = 0", + FROM FrameworkReviews AS FR INNER JOIN + FrameworkCollaborators AS FC ON FR.FrameworkCollaboratorID = FC.ID INNER JOIN + AdminAccounts AS aa ON aa.ID = FC.AdminID LEFT OUTER JOIN + FrameworkComments AS FC1 ON FR.FrameworkCommentID = FC1.ID + WHERE FR.ID = @reviewId AND FR.FrameworkID = @frameworkId AND + aa.UserID = (SELECT aa1.UserID FROM AdminAccounts aa1 WHERE aa1.ID = @adminId) AND + FR.Archived IS NULL AND IsDeleted = 0", new { frameworkId, adminId, reviewId } ).FirstOrDefault(); } diff --git a/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs new file mode 100644 index 0000000000..109d5a9273 --- /dev/null +++ b/DigitalLearningSolutions.Data/DataServices/LoginDataService.cs @@ -0,0 +1,63 @@ +namespace DigitalLearningSolutions.Data.DataServices +{ + using Dapper; + using System.Data; + + public interface ILoginDataService + { + void UpdateLastAccessedForUsersTable(int Id); + + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); + } + + public class LoginDataService : ILoginDataService + { + private readonly IDbConnection connection; + + public LoginDataService(IDbConnection connection) + { + this.connection = connection; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + connection.Execute( + @"UPDATE Users SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } + + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + connection.Execute( + @"UPDATE DelegateAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + connection.Execute( + @"UPDATE AdminAccounts SET + LastAccessed = GetUtcDate() + WHERE ID = @Id", + new + { + Id + } + ); + } + } +} diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/DCSAReportDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/DCSAReportDataService.cs index ed3800215e..744580c411 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/DCSAReportDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/DCSAReportDataService.cs @@ -1,128 +1,129 @@ -namespace DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService -{ - using Dapper; - using DigitalLearningSolutions.Data.Models.SelfAssessments.Export; - using Microsoft.Extensions.Logging; - using System.Collections.Generic; - using System.Data; - - public interface IDCSAReportDataService - { - IEnumerable GetDelegateCompletionStatusForCentre(int centreId); - IEnumerable GetOutcomeSummaryForCentre(int centreId); - } - public partial class DCSAReportDataService : IDCSAReportDataService - { - private readonly IDbConnection connection; - private readonly ILogger logger; - - public DCSAReportDataService(IDbConnection connection, ILogger logger) - { - this.connection = connection; - this.logger = logger; - } - - public IEnumerable GetDelegateCompletionStatusForCentre(int centreId) - { - return connection.Query( - @"SELECT DATEPART(month, ca.StartedDate) AS EnrolledMonth, DATEPART(yyyy, ca.StartedDate) AS EnrolledYear, u.FirstName, u.LastName, COALESCE (ucd.Email, u.PrimaryEmail) AS Email, da.Answer1 AS CentreField1, da.Answer2 AS CentreField2, da.Answer3 AS CentreField3, - CASE WHEN (ca.SubmittedDate IS NOT NULL) THEN 'Submitted' WHEN (ca.UserBookmark LIKE N'/LearningPortal/SelfAssessment/1/Review' AND ca.SubmittedDate IS NULL) THEN 'Reviewing' ELSE 'Incomplete' END AS Status - FROM CandidateAssessments AS ca INNER JOIN - DelegateAccounts AS da ON ca.DelegateUserID = da.UserID INNER JOIN - Users AS u ON da.UserID = u.ID LEFT OUTER JOIN - UserCentreDetails AS ucd ON da.CentreID = ucd.CentreID AND u.ID = ucd.UserID - WHERE (ca.SelfAssessmentID = 1) AND (da.CentreID = @centreId) AND (u.Active = 1) AND (da.Active = 1)", - new { centreId } - ); - } - - public IEnumerable GetOutcomeSummaryForCentre(int centreId) - { - return connection.Query( - @"SELECT DATEPART(month, ca.StartedDate) AS EnrolledMonth, DATEPART(yyyy, ca.StartedDate) AS EnrolledYear, jg.JobGroupName AS JobGroup, da.Answer1 AS CentreField1, da.Answer2 AS CentreField2, da.Answer3 AS CentreField3, CASE WHEN (ca.SubmittedDate IS NOT NULL) - THEN 'Submitted' WHEN (ca.UserBookmark LIKE N'/LearningPortal/SelfAssessment/1/Review' AND ca.SubmittedDate IS NULL) THEN 'Reviewing' ELSE 'Incomplete' END AS Status, - (SELECT COUNT(*) AS LearningLaunched - FROM CandidateAssessmentLearningLogItems AS calli INNER JOIN - LearningLogItems AS lli ON calli.LearningLogItemID = lli.LearningLogItemID - WHERE (NOT (lli.LearningResourceReferenceID IS NULL)) AND (calli.CandidateAssessmentID = ca.ID)) + - (SELECT COUNT(*) AS FilteredLearning - FROM FilteredLearningActivity AS fla - WHERE (CandidateId = da.ID)) AS LearningCompleted, - (SELECT COUNT(*) AS LearningLaunched - FROM CandidateAssessmentLearningLogItems AS calli INNER JOIN - LearningLogItems AS lli ON calli.LearningLogItemID = lli.LearningLogItemID - WHERE (NOT (lli.LearningResourceReferenceID IS NULL)) AND (calli.CandidateAssessmentID = ca.ID) AND (NOT (lli.CompletedDate IS NULL))) + - (SELECT COUNT(*) AS FilteredCompleted - FROM FilteredLearningActivity AS fla - WHERE (CandidateId = da.ID) AND (CompletedDate IS NOT NULL)) AS LearningCompleted, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 1)) AS DataInformationAndContentConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 1)) AS DataInformationAndContentRelevance, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 2)) AS TeachingLearningAndSelfDevelopmentConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 2)) AS TeachingLearningAndSelfDevelopmentRelevance, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 3)) AS CommunicationCollaborationAndParticipationConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 3)) AS CommunicationCollaborationAndParticipationRelevance, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 4)) AS TechnicalProficiencyConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 4)) AS TechnicalProficiencyRelevance, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 5)) AS CreationInnovationAndResearchConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 5)) AS CreationInnovationAndResearchRelevance, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 6)) AS DigitalIdentityWellbeingSafetyAndSecurityConfidence, - (SELECT AVG(sar.Result) AS AvgConfidence - FROM SelfAssessmentResults AS sar INNER JOIN - Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN - SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID - WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 6)) AS DigitalIdentityWellbeingSafetyAndSecurityRelevance - FROM CandidateAssessments AS ca INNER JOIN - DelegateAccounts AS da ON ca.DelegateUserID = da.UserID INNER JOIN - Users AS u ON da.UserID = u.ID INNER JOIN - JobGroups AS jg ON u.JobGroupID = jg.JobGroupID - WHERE (ca.SelfAssessmentID = 1) AND (da.CentreID = @centreId) AND (u.Active = 1) AND (da.Active = 1)", - new { centreId } - ); - } - - } -} +namespace DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService +{ + using Dapper; + using DigitalLearningSolutions.Data.Models.SelfAssessments.Export; + using Microsoft.Extensions.Logging; + using System.Collections.Generic; + using System.Data; + + public interface IDCSAReportDataService + { + IEnumerable GetDelegateCompletionStatusForCentre(int centreId); + IEnumerable GetOutcomeSummaryForCentre(int centreId); + } + public partial class DCSAReportDataService : IDCSAReportDataService + { + private readonly IDbConnection connection; + private readonly ILogger logger; + + public DCSAReportDataService(IDbConnection connection, ILogger logger) + { + this.connection = connection; + this.logger = logger; + } + + public IEnumerable GetDelegateCompletionStatusForCentre(int centreId) + { + return connection.Query( + @"SELECT DATEPART(month, ca.StartedDate) AS EnrolledMonth, DATEPART(yyyy, ca.StartedDate) AS EnrolledYear, u.FirstName, u.LastName, COALESCE (ucd.Email, u.PrimaryEmail) AS Email, da.Answer1 AS RegistrationAnswer1, da.Answer2 AS RegistrationAnswer2, da.Answer3 AS RegistrationAnswer3, + da.Answer4 AS RegistrationAnswer4, da.Answer5 AS RegistrationAnswer5, da.Answer6 AS RegistrationAnswer6, CASE WHEN (ca.SubmittedDate IS NOT NULL) THEN 'Submitted' WHEN (ca.UserBookmark LIKE N'/LearningPortal/SelfAssessment/1/Review' AND ca.SubmittedDate IS NULL) THEN 'Reviewing' ELSE 'Incomplete' END AS Status + FROM CandidateAssessments AS ca INNER JOIN + DelegateAccounts AS da ON ca.DelegateUserID = da.UserID INNER JOIN + Users AS u ON da.UserID = u.ID LEFT OUTER JOIN + UserCentreDetails AS ucd ON da.CentreID = ucd.CentreID AND u.ID = ucd.UserID + WHERE (ca.SelfAssessmentID = 1) AND (da.CentreID = @centreId) AND (u.Active = 1) AND (da.Active = 1)", + new { centreId } + ); + } + + public IEnumerable GetOutcomeSummaryForCentre(int centreId) + { + return connection.Query( + @"SELECT DATEPART(month, ca.StartedDate) AS EnrolledMonth, DATEPART(yyyy, ca.StartedDate) AS EnrolledYear, jg.JobGroupName AS JobGroup, da.Answer1 AS RegistrationAnswer1, da.Answer2 AS RegistrationAnswer2, da.Answer3 AS RegistrationAnswer3, + da.Answer4 AS RegistrationAnswer4, da.Answer5 AS RegistrationAnswer5, da.Answer6 AS RegistrationAnswer6, CASE WHEN (ca.SubmittedDate IS NOT NULL) + THEN 'Submitted' WHEN (ca.UserBookmark LIKE N'/LearningPortal/SelfAssessment/1/Review' AND ca.SubmittedDate IS NULL) THEN 'Reviewing' ELSE 'Incomplete' END AS Status, + (SELECT COUNT(*) AS LearningLaunched + FROM CandidateAssessmentLearningLogItems AS calli INNER JOIN + LearningLogItems AS lli ON calli.LearningLogItemID = lli.LearningLogItemID + WHERE (NOT (lli.LearningResourceReferenceID IS NULL)) AND (calli.CandidateAssessmentID = ca.ID)) + + (SELECT COUNT(*) AS FilteredLearning + FROM FilteredLearningActivity AS fla + WHERE (CandidateId = da.ID)) AS LearningCompleted, + (SELECT COUNT(*) AS LearningLaunched + FROM CandidateAssessmentLearningLogItems AS calli INNER JOIN + LearningLogItems AS lli ON calli.LearningLogItemID = lli.LearningLogItemID + WHERE (NOT (lli.LearningResourceReferenceID IS NULL)) AND (calli.CandidateAssessmentID = ca.ID) AND (NOT (lli.CompletedDate IS NULL))) + + (SELECT COUNT(*) AS FilteredCompleted + FROM FilteredLearningActivity AS fla + WHERE (CandidateId = da.ID) AND (CompletedDate IS NOT NULL)) AS LearningCompleted, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 1)) AS DataInformationAndContentConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 1)) AS DataInformationAndContentRelevance, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 2)) AS TeachingLearningAndSelfDevelopmentConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 2)) AS TeachingLearningAndSelfDevelopmentRelevance, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 3)) AS CommunicationCollaborationAndParticipationConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 3)) AS CommunicationCollaborationAndParticipationRelevance, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 4)) AS TechnicalProficiencyConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 4)) AS TechnicalProficiencyRelevance, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 5)) AS CreationInnovationAndResearchConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 5)) AS CreationInnovationAndResearchRelevance, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 1) AND (sas.CompetencyGroupID = 6)) AS DigitalIdentityWellbeingSafetyAndSecurityConfidence, + (SELECT AVG(sar.Result) AS AvgConfidence + FROM SelfAssessmentResults AS sar INNER JOIN + Competencies AS co ON sar.CompetencyID = co.ID INNER JOIN + SelfAssessmentStructure AS sas ON co.ID = sas.CompetencyID + WHERE (sar.DelegateUserID = da.UserID) AND (sar.AssessmentQuestionID = 2) AND (sas.CompetencyGroupID = 6)) AS DigitalIdentityWellbeingSafetyAndSecurityRelevance + FROM CandidateAssessments AS ca INNER JOIN + DelegateAccounts AS da ON ca.DelegateUserID = da.UserID INNER JOIN + Users AS u ON da.UserID = u.ID INNER JOIN + JobGroups AS jg ON u.JobGroupID = jg.JobGroupID + WHERE (ca.SelfAssessmentID = 1) AND (da.CentreID = @centreId) AND (u.Active = 1) AND (da.Active = 1)", + new { centreId } + ); + } + + } +} diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAsssessmentReportDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAsssessmentReportDataService.cs index 50168d4e64..0bd5d38f37 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAsssessmentReportDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAsssessmentReportDataService.cs @@ -1,135 +1,139 @@ -namespace DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService -{ - using Dapper; - using DigitalLearningSolutions.Data.Models.SelfAssessments.Export; - using DigitalLearningSolutions.Data.Models.SelfAssessments; - using Microsoft.Extensions.Logging; - using System.Collections.Generic; - using System.Data; - - public interface ISelfAssessmentReportDataService - { - IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId); - IEnumerable GetSelfAssessmentReportDataForCentre(int centreId, int selfAssessmentId); - } - public partial class SelfAssessmentReportDataService : ISelfAssessmentReportDataService - { - private readonly IDbConnection connection; - private readonly ILogger logger; - - public SelfAssessmentReportDataService(IDbConnection connection, ILogger logger) - { - this.connection = connection; - this.logger = logger; - } - - public IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId) - { - return connection.Query( - @"SELECT csa.SelfAssessmentID AS Id, sa.Name, - (SELECT COUNT (DISTINCT da.UserID) AS Learners - FROM CandidateAssessments AS ca1 INNER JOIN - DelegateAccounts AS da ON ca1.DelegateUserID = da.UserID - WHERE (da.CentreID = @centreId) AND (ca1.RemovedDate IS NULL) AND (ca1.SelfAssessmentID = csa.SelfAssessmentID) AND ca1.NonReportable=0) AS LearnerCount - FROM CentreSelfAssessments AS csa INNER JOIN - SelfAssessments AS sa ON csa.SelfAssessmentID = sa.ID - WHERE (csa.CentreID = @centreId) AND (sa.CategoryID = @categoryId) AND (sa.SupervisorResultsReview = 1) AND (sa.ArchivedDate IS NULL) OR - (csa.CentreID = @centreId) AND (sa.CategoryID = @categoryId) AND (sa.ArchivedDate IS NULL) AND (sa.SupervisorSelfAssessmentReview = 1) OR - (csa.CentreID = @centreId) AND (sa.SupervisorResultsReview = 1) AND (sa.ArchivedDate IS NULL) AND (@categoryId = 0) OR - (csa.CentreID = @centreId) AND (sa.ArchivedDate IS NULL) AND (sa.SupervisorSelfAssessmentReview = 1) AND (@categoryId = 0) - ORDER BY sa.Name", - new { centreId, categoryId = categoryId ??= 0 } - ); - } - - public IEnumerable GetSelfAssessmentReportDataForCentre(int centreId, int selfAssessmentId) - { - return connection.Query( - @"WITH LatestAssessmentResults AS - ( - SELECT s.DelegateUserID - , CASE WHEN COALESCE (rr.LevelRAG, 0) = 3 THEN s.ID ELSE NULL END AS SelfAssessed - , CASE WHEN sv.Verified IS NOT NULL AND sv.SignedOff = 1 AND COALESCE (rr.LevelRAG, 0) = 3 THEN s.ID ELSE NULL END AS Confirmed - , CASE WHEN sas.Optional = 1 THEN s.CompetencyID ELSE NULL END AS Optional - FROM SelfAssessmentResults AS s LEFT OUTER JOIN - SelfAssessmentStructure AS sas ON sas.SelfAssessmentID = @selfAssessmentId AND s.CompetencyID = sas.CompetencyID LEFT OUTER JOIN - SelfAssessmentResultSupervisorVerifications AS sv ON s.ID = sv.SelfAssessmentResultId AND sv.Superceded = 0 LEFT OUTER JOIN - CompetencyAssessmentQuestionRoleRequirements AS rr ON s.CompetencyID = rr.CompetencyID AND s.AssessmentQuestionID = rr.AssessmentQuestionID AND sas.SelfAssessmentID = rr.SelfAssessmentID AND s.Result = rr.LevelValue - WHERE (sas.SelfAssessmentID = @selfAssessmentId) - ) - SELECT - sa.Name AS SelfAssessment - , u.LastName + ', ' + u.FirstName AS Learner - , da.Active AS LearnerActive - , u.ProfessionalRegistrationNumber AS PRN - , jg.JobGroupName AS JobGroup - , CASE WHEN c.CustomField1PromptID = 10 THEN da.Answer1 WHEN c.CustomField2PromptID = 10 THEN da.Answer2 WHEN c.CustomField3PromptID = 10 THEN da.Answer3 WHEN c.CustomField4PromptID = 10 THEN da.Answer4 WHEN c.CustomField5PromptID = 10 THEN da.Answer5 WHEN c.CustomField6PromptID = 10 THEN da.Answer6 ELSE '' END AS 'ProgrammeCourse' - , CASE WHEN c.CustomField1PromptID = 4 THEN da.Answer1 WHEN c.CustomField2PromptID = 4 THEN da.Answer2 WHEN c.CustomField3PromptID = 4 THEN da.Answer3 WHEN c.CustomField4PromptID = 4 THEN da.Answer4 WHEN c.CustomField5PromptID = 4 THEN da.Answer5 WHEN c.CustomField6PromptID = 4 THEN da.Answer6 ELSE '' END AS 'Organisation' - , CASE WHEN c.CustomField1PromptID = 1 THEN da.Answer1 WHEN c.CustomField2PromptID = 1 THEN da.Answer2 WHEN c.CustomField3PromptID = 1 THEN da.Answer3 WHEN c.CustomField4PromptID = 1 THEN da.Answer4 WHEN c.CustomField5PromptID = 1 THEN da.Answer5 WHEN c.CustomField6PromptID = 1 THEN da.Answer6 ELSE '' END AS 'DepartmentTeam' - , dbo.GetOtherCentresForSelfAssessment(da.UserID, @SelfAssessmentID, c.CentreID) AS OtherCentres - , CASE - WHEN aa.ID IS NULL THEN 'Learner' - WHEN aa.IsCentreManager = 1 THEN 'Centre Manager' - WHEN aa.IsCentreAdmin = 1 AND aa.IsCentreManager = 0 THEN 'Centre Admin' - WHEN aa.IsSupervisor = 1 THEN 'Supervisor' - WHEN aa.IsNominatedSupervisor = 1 THEN 'Nominated supervisor' - END AS DLSRole - , da.DateRegistered AS Registered - , ca.StartedDate AS Started - , ca.LastAccessed - , COALESCE(COUNT(DISTINCT LAR.Optional), NULL) AS [OptionalProficienciesAssessed] - , COALESCE(COUNT(DISTINCT LAR.SelfAssessed), NULL) AS [SelfAssessedAchieved] - , COALESCE(COUNT(DISTINCT LAR.Confirmed), NULL) AS [ConfirmedResults] - , max(casv.Requested) AS SignOffRequested - , max(1*casv.SignedOff) AS SignOffAchieved - , min(casv.Verified) AS ReviewedDate - FROM - CandidateAssessments AS ca INNER JOIN - DelegateAccounts AS da ON ca.DelegateUserID = da.UserID and da.CentreID = @centreId INNER JOIN - Users as u ON u.ID = da.UserID INNER JOIN - SelfAssessments AS sa INNER JOIN - CentreSelfAssessments AS csa ON sa.ID = csa.SelfAssessmentID INNER JOIN - Centres AS c ON csa.CentreID = c.CentreID ON da.CentreID = c.CentreID AND ca.SelfAssessmentID = sa.ID INNER JOIN - JobGroups AS jg ON u.JobGroupID = jg.JobGroupID LEFT OUTER JOIN - AdminAccounts AS aa ON da.UserID = aa.UserID AND aa.CentreID = da.CentreID AND aa.Active = 1 LEFT OUTER JOIN - CandidateAssessmentSupervisors AS cas ON ca.ID = cas.CandidateAssessmentID left JOIN - CandidateAssessmentSupervisorVerifications AS casv ON casv.CandidateAssessmentSupervisorID = cas.ID LEFT JOIN - SupervisorDelegates AS sd ON cas.SupervisorDelegateId = sd.ID - LEFT OUTER JOIN LatestAssessmentResults AS LAR ON LAR.DelegateUserID = ca.DelegateUserID - WHERE - (sa.ID = @SelfAssessmentID) AND (sa.ArchivedDate IS NULL) AND (c.Active = 1) AND (ca.RemovedDate IS NULL AND ca.NonReportable = 0) - Group by sa.Name - , u.LastName + ', ' + u.FirstName - , da.Active - , u.ProfessionalRegistrationNumber - , c.CustomField1PromptID - , c.CustomField2PromptID - , c.CustomField3PromptID - , c.CustomField4PromptID - , c.CustomField5PromptID - , c.CustomField6PromptID - , c.CentreID - , jg.JobGroupName - , da.ID - , da.Answer1 - , da.Answer2 - , da.Answer3 - , da.Answer4 - , da.Answer5 - , da.Answer6 - , da.DateRegistered - , da.UserID - , aa.ID - , aa.IsCentreManager - , aa.IsCentreAdmin - , aa.IsSupervisor - , aa.IsNominatedSupervisor - , ca.StartedDate - , ca.LastAccessed - ORDER BY - SelfAssessment, u.LastName + ', ' + u.FirstName", - new { centreId, selfAssessmentId } - ); - } - } -} +namespace DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService +{ + using Dapper; + using DigitalLearningSolutions.Data.Models.SelfAssessments.Export; + using DigitalLearningSolutions.Data.Models.SelfAssessments; + using Microsoft.Extensions.Logging; + using System.Collections.Generic; + using System.Data; + using ClosedXML.Excel; + + public interface ISelfAssessmentReportDataService + { + IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId); + IEnumerable GetSelfAssessmentReportDataForCentre(int centreId, int selfAssessmentId); + } + public partial class SelfAssessmentReportDataService : ISelfAssessmentReportDataService + { + private readonly IDbConnection connection; + private readonly ILogger logger; + + public SelfAssessmentReportDataService(IDbConnection connection, ILogger logger) + { + this.connection = connection; + this.logger = logger; + } + + public IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId) + { + return connection.Query( + @"SELECT csa.SelfAssessmentID AS Id, sa.Name, + (SELECT COUNT (DISTINCT da.UserID) AS Learners + FROM CandidateAssessments AS ca1 INNER JOIN + DelegateAccounts AS da ON ca1.DelegateUserID = da.UserID + WHERE (da.CentreID = @centreId) AND (ca1.RemovedDate IS NULL) AND (ca1.SelfAssessmentID = csa.SelfAssessmentID) AND ca1.NonReportable=0) AS LearnerCount + FROM CentreSelfAssessments AS csa INNER JOIN + SelfAssessments AS sa ON csa.SelfAssessmentID = sa.ID + WHERE (csa.CentreID = @centreId) AND (sa.CategoryID = @categoryId) AND (sa.SupervisorResultsReview = 1) AND (sa.ArchivedDate IS NULL) OR + (csa.CentreID = @centreId) AND (sa.CategoryID = @categoryId) AND (sa.ArchivedDate IS NULL) AND (sa.SupervisorSelfAssessmentReview = 1) OR + (csa.CentreID = @centreId) AND (sa.SupervisorResultsReview = 1) AND (sa.ArchivedDate IS NULL) AND (@categoryId = 0) OR + (csa.CentreID = @centreId) AND (sa.ArchivedDate IS NULL) AND (sa.SupervisorSelfAssessmentReview = 1) AND (@categoryId = 0) + ORDER BY sa.Name", + new { centreId, categoryId = categoryId ??= 0 } + ); + } + + public IEnumerable GetSelfAssessmentReportDataForCentre(int centreId, int selfAssessmentId) + { + return connection.Query( + @"WITH LatestAssessmentResults AS + ( + SELECT s.DelegateUserID + , CASE WHEN COALESCE (rr.LevelRAG, 0) = 3 THEN s.ID ELSE NULL END AS SelfAssessed + , CASE WHEN sv.Verified IS NOT NULL AND sv.SignedOff = 1 AND COALESCE (rr.LevelRAG, 0) = 3 THEN s.ID ELSE NULL END AS Confirmed + , CASE WHEN sas.Optional = 1 THEN s.CompetencyID ELSE NULL END AS Optional + FROM SelfAssessmentResults AS s LEFT OUTER JOIN + SelfAssessmentStructure AS sas ON sas.SelfAssessmentID = @selfAssessmentId AND s.CompetencyID = sas.CompetencyID LEFT OUTER JOIN + SelfAssessmentResultSupervisorVerifications AS sv ON s.ID = sv.SelfAssessmentResultId AND sv.Superceded = 0 LEFT OUTER JOIN + CompetencyAssessmentQuestionRoleRequirements AS rr ON s.CompetencyID = rr.CompetencyID AND s.AssessmentQuestionID = rr.AssessmentQuestionID AND sas.SelfAssessmentID = rr.SelfAssessmentID AND s.Result = rr.LevelValue + WHERE (sas.SelfAssessmentID = @selfAssessmentId) + ) + SELECT + sa.Name AS SelfAssessment + , u.LastName + ', ' + u.FirstName AS Learner + , da.Active AS LearnerActive + , u.ProfessionalRegistrationNumber AS PRN + , jg.JobGroupName AS JobGroup + , da.Answer1 AS RegistrationAnswer1 + , da.Answer2 AS RegistrationAnswer2 + , da.Answer3 AS RegistrationAnswer3 + , da.Answer4 AS RegistrationAnswer4 + , da.Answer5 AS RegistrationAnswer5 + , da.Answer6 AS RegistrationAnswer6 + , dbo.GetOtherCentresForSelfAssessment(da.UserID, @SelfAssessmentID, c.CentreID) AS OtherCentres + , CASE + WHEN aa.ID IS NULL THEN 'Learner' + WHEN aa.IsCentreManager = 1 THEN 'Centre Manager' + WHEN aa.IsCentreAdmin = 1 AND aa.IsCentreManager = 0 THEN 'Centre Admin' + WHEN aa.IsSupervisor = 1 THEN 'Supervisor' + WHEN aa.IsNominatedSupervisor = 1 THEN 'Nominated supervisor' + END AS DLSRole + , da.DateRegistered AS Registered + , ca.StartedDate AS Started + , ca.LastAccessed + , COALESCE(COUNT(DISTINCT LAR.Optional), NULL) AS [OptionalProficienciesAssessed] + , COALESCE(COUNT(DISTINCT LAR.SelfAssessed), NULL) AS [SelfAssessedAchieved] + , COALESCE(COUNT(DISTINCT LAR.Confirmed), NULL) AS [ConfirmedResults] + , max(casv.Requested) AS SignOffRequested + , max(1*casv.SignedOff) AS SignOffAchieved + , min(casv.Verified) AS ReviewedDate + FROM + CandidateAssessments AS ca INNER JOIN + DelegateAccounts AS da ON ca.DelegateUserID = da.UserID and da.CentreID = @centreId INNER JOIN + Users as u ON u.ID = da.UserID INNER JOIN + SelfAssessments AS sa INNER JOIN + CentreSelfAssessments AS csa ON sa.ID = csa.SelfAssessmentID INNER JOIN + Centres AS c ON csa.CentreID = c.CentreID ON da.CentreID = c.CentreID AND ca.SelfAssessmentID = sa.ID INNER JOIN + JobGroups AS jg ON u.JobGroupID = jg.JobGroupID LEFT OUTER JOIN + AdminAccounts AS aa ON da.UserID = aa.UserID AND aa.CentreID = da.CentreID AND aa.Active = 1 LEFT OUTER JOIN + CandidateAssessmentSupervisors AS cas ON ca.ID = cas.CandidateAssessmentID left JOIN + CandidateAssessmentSupervisorVerifications AS casv ON casv.CandidateAssessmentSupervisorID = cas.ID LEFT JOIN + SupervisorDelegates AS sd ON cas.SupervisorDelegateId = sd.ID + LEFT OUTER JOIN LatestAssessmentResults AS LAR ON LAR.DelegateUserID = ca.DelegateUserID + WHERE + (sa.ID = @SelfAssessmentID) AND (sa.ArchivedDate IS NULL) AND (c.Active = 1) AND (ca.RemovedDate IS NULL AND ca.NonReportable = 0) + Group by sa.Name + , u.LastName + ', ' + u.FirstName + , da.Active + , u.ProfessionalRegistrationNumber + , c.CustomField1PromptID + , c.CustomField2PromptID + , c.CustomField3PromptID + , c.CustomField4PromptID + , c.CustomField5PromptID + , c.CustomField6PromptID + , c.CentreID + , jg.JobGroupName + , da.ID + , da.Answer1 + , da.Answer2 + , da.Answer3 + , da.Answer4 + , da.Answer5 + , da.Answer6 + , da.DateRegistered + , da.UserID + , aa.ID + , aa.IsCentreManager + , aa.IsCentreAdmin + , aa.IsSupervisor + , aa.IsNominatedSupervisor + , ca.StartedDate + , ca.LastAccessed + ORDER BY + SelfAssessment, u.LastName + ', ' + u.FirstName", + new { centreId, selfAssessmentId } + ); + } + } +} diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs index d93094c97b..9f293ad394 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/AdminUserDataService.cs @@ -105,6 +105,7 @@ FROM AdminAccounts AS aa aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, + aa.LastAccessed, CASE WHEN aa.CategoryID IS NULL THEN 'All' ELSE cc.CategoryName @@ -369,7 +370,7 @@ public IEnumerable GetAdminAccountsByUserId(int userId) } string BaseSelectQuery = $@"SELECT aa.ID, aa.UserID, aa.CentreID, aa.Active, aa.IsCentreAdmin, aa.IsReportsViewer, aa.IsSuperAdmin, aa.IsCentreManager, - aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, + aa.LastAccessed, aa.IsContentManager, aa.IsContentCreator, aa.IsSupervisor, aa.IsTrainer, aa.CategoryID, aa.IsFrameworkDeveloper, aa.IsFrameworkContributor,aa.ImportOnly, aa.IsWorkforceManager, aa.IsWorkforceContributor, aa.IsLocalWorkforceManager, aa.IsNominatedSupervisor, u.ID, u.PrimaryEmail, u.FirstName, u.LastName, u.Active, u.FailedLoginCount, c.CentreID, c.CentreName, diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs index 8e2a424ed5..f2068623a9 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserCardDataService.cs @@ -87,6 +87,7 @@ FROM DelegateAccounts AS da c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, @@ -124,6 +125,7 @@ FROM AdminAccounts aa c.CentreName, da.CentreID, da.DateRegistered, + da.LastAccessed, da.RegistrationConfirmationHash, c.Active AS CentreActive, COALESCE(ucd.Email, u.PrimaryEmail) AS EmailAddress, @@ -350,6 +352,8 @@ public List GetDelegateUserCardsForExportByCentreId(String sea if (sortBy == "SearchableName") orderBy = " ORDER BY LTRIM(LastName) " + sortDirection + ", LTRIM(FirstName) "; + else if(sortBy == "LastAccessed") + orderBy = " ORDER BY LastAccessed " + sortDirection; else orderBy = " ORDER BY DateRegistered " + sortDirection; diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs index ca48e7e67b..dd583caffa 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/DelegateUserDataService.cs @@ -111,6 +111,7 @@ FROM DelegateAccounts AS da ce.CentreName, ce.Active AS CentreActive, da.DateRegistered, + da.LastAccessed, da.CandidateNumber, da.Answer1, da.Answer2, diff --git a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs index aba887ccf5..178e2f62dc 100644 --- a/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/UserDataService/UserDataService.cs @@ -308,6 +308,7 @@ public partial class UserDataService : IUserDataService u.ProfessionalRegistrationNumber, u.ProfileImage, u.Active, + u.LastAccessed, u.ResetPasswordID, u.TermsAgreed, u.FailedLoginCount, @@ -619,6 +620,7 @@ public void UpdateUserDetailsAccount(string firstName, string lastName, string p ce.CentreName, ce.Active AS CentreActive, da.DateRegistered, + da.LastAccessed, da.CandidateNumber, da.Approved, da.SelfReg, diff --git a/DigitalLearningSolutions.Data/Helpers/GenericSortingHelper.cs b/DigitalLearningSolutions.Data/Helpers/GenericSortingHelper.cs index 7f4380d941..0165d19655 100644 --- a/DigitalLearningSolutions.Data/Helpers/GenericSortingHelper.cs +++ b/DigitalLearningSolutions.Data/Helpers/GenericSortingHelper.cs @@ -250,6 +250,9 @@ public static class DelegateSortByOptions public static readonly (string DisplayText, string PropertyName) Name = ("Name", nameof(DelegateUserCard.SearchableName)); + public static readonly (string DisplayText, string PropertyName) LastAccessed = + ("Last accessed date", nameof(DelegateUserCard.LastAccessed)); + public static readonly (string DisplayText, string PropertyName) RegistrationDate = ("Registration Date", nameof(DelegateUserCard.DateRegistered)); } diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSADelegateCompletionStatus.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSADelegateCompletionStatus.cs index 31a69d5bc1..ee33332727 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSADelegateCompletionStatus.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSADelegateCompletionStatus.cs @@ -1,16 +1,31 @@ -namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export -{ - using System; - public class DCSADelegateCompletionStatus - { - public int? EnrolledMonth { get; set; } - public int? EnrolledYear { get; set; } - public string? FirstName { get; set; } - public string? LastName { get; set; } - public string? Email { get; set; } - public string? CentreField1 { get; set; } - public string? CentreField2 { get; set; } - public string? CentreField3 { get; set; } - public string? Status { get; set; } - } -} +namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export +{ + using System; + public class DCSADelegateCompletionStatus + { + public int? EnrolledMonth { get; set; } + public int? EnrolledYear { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Email { get; set; } + public string? RegistrationAnswer1 { get; set; } + public string? RegistrationAnswer2 { get; set; } + public string? RegistrationAnswer3 { get; set; } + public string? RegistrationAnswer4 { get; set; } + public string? RegistrationAnswer5 { get; set; } + public string? RegistrationAnswer6 { get; set; } + public string? Status { get; set; } + + + public string?[] CentreRegistrationPrompts => + new[] + { + RegistrationAnswer1, + RegistrationAnswer2, + RegistrationAnswer3, + RegistrationAnswer4, + RegistrationAnswer5, + RegistrationAnswer6, + }; + } +} diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSAOutcomeSummary.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSAOutcomeSummary.cs index adac779073..673130a83f 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSAOutcomeSummary.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/DCSAOutcomeSummary.cs @@ -1,28 +1,42 @@ -namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export -{ - using System; - public class DCSAOutcomeSummary - { - public int? EnrolledMonth { get; set; } - public int? EnrolledYear { get; set; } - public string? JobGroup { get; set; } - public string? CentreField1 { get; set; } - public string? CentreField2 { get; set; } - public string? CentreField3 { get; set; } - public string? Status { get; set; } - public int? LearningLaunched { get; set; } - public int? LearningCompleted { get; set; } - public int? DataInformationAndContentConfidence { get; set; } - public int? DataInformationAndContentRelevance { get; set; } - public int? TeachinglearningAndSelfDevelopmentConfidence { get; set; } - public int? TeachinglearningAndSelfDevelopmentRelevance { get; set; } - public int? CommunicationCollaborationAndParticipationConfidence { get; set; } - public int? CommunicationCollaborationAndParticipationRelevance { get; set; } - public int? TechnicalProficiencyConfidence { get; set; } - public int? TechnicalProficiencyRelevance { get; set; } - public int? CreationInnovationAndResearchConfidence { get; set; } - public int? CreationInnovationAndResearchRelevance { get; set; } - public int? DigitalIdentityWellbeingSafetyAndSecurityConfidence { get; set; } - public int? DigitalIdentityWellbeingSafetyAndSecurityRelevance { get; set; } - } -} +namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export +{ + using System; + public class DCSAOutcomeSummary + { + public int? EnrolledMonth { get; set; } + public int? EnrolledYear { get; set; } + public string? JobGroup { get; set; } + public string? RegistrationAnswer1 { get; set; } + public string? RegistrationAnswer2 { get; set; } + public string? RegistrationAnswer3 { get; set; } + public string? RegistrationAnswer4 { get; set; } + public string? RegistrationAnswer5 { get; set; } + public string? RegistrationAnswer6 { get; set; } + public string? Status { get; set; } + public int? LearningLaunched { get; set; } + public int? LearningCompleted { get; set; } + public int? DataInformationAndContentConfidence { get; set; } + public int? DataInformationAndContentRelevance { get; set; } + public int? TeachinglearningAndSelfDevelopmentConfidence { get; set; } + public int? TeachinglearningAndSelfDevelopmentRelevance { get; set; } + public int? CommunicationCollaborationAndParticipationConfidence { get; set; } + public int? CommunicationCollaborationAndParticipationRelevance { get; set; } + public int? TechnicalProficiencyConfidence { get; set; } + public int? TechnicalProficiencyRelevance { get; set; } + public int? CreationInnovationAndResearchConfidence { get; set; } + public int? CreationInnovationAndResearchRelevance { get; set; } + public int? DigitalIdentityWellbeingSafetyAndSecurityConfidence { get; set; } + public int? DigitalIdentityWellbeingSafetyAndSecurityRelevance { get; set; } + + public string?[] CentreRegistrationPrompts => + new[] + { + RegistrationAnswer1, + RegistrationAnswer2, + RegistrationAnswer3, + RegistrationAnswer4, + RegistrationAnswer5, + RegistrationAnswer6, + }; + } +} diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/SelfAssessmentReportData.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/SelfAssessmentReportData.cs index 27c3d31bc7..847162bf47 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/SelfAssessmentReportData.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/Export/SelfAssessmentReportData.cs @@ -1,26 +1,41 @@ -namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export -{ - using System; - public class SelfAssessmentReportData - { - public string? SelfAssessment { get; set; } - public string? Learner { get; set; } - public bool LearnerActive { get; set; } - public string? PRN { get; set; } - public string? JobGroup { get; set; } - public string? ProgrammeCourse { get; set; } - public string? Organisation { get; set; } - public string? DepartmentTeam { get; set; } - public string? OtherCentres { get; set; } - public string? DLSRole { get; set; } - public DateTime? Registered { get; set; } - public DateTime? Started { get; set; } - public DateTime? LastAccessed { get; set; } - public int? OptionalProficienciesAssessed { get; set; } - public int? SelfAssessedAchieved { get; set; } - public int? ConfirmedResults { get; set; } - public DateTime? SignOffRequested { get; set; } - public bool SignOffAchieved { get; set; } - public DateTime? ReviewedDate { get; set; } - } -} +namespace DigitalLearningSolutions.Data.Models.SelfAssessments.Export +{ + using System; + public class SelfAssessmentReportData + { + public string? SelfAssessment { get; set; } + public string? Learner { get; set; } + public bool LearnerActive { get; set; } + public string? PRN { get; set; } + public string? JobGroup { get; set; } + public string? RegistrationAnswer1 { get; set; } + public string? RegistrationAnswer2 { get; set; } + public string? RegistrationAnswer3 { get; set; } + public string? RegistrationAnswer4 { get; set; } + public string? RegistrationAnswer5 { get; set; } + public string? RegistrationAnswer6 { get; set; } + public string? OtherCentres { get; set; } + public string? DLSRole { get; set; } + public DateTime? Registered { get; set; } + public DateTime? Started { get; set; } + public DateTime? LastAccessed { get; set; } + public int? OptionalProficienciesAssessed { get; set; } + public int? SelfAssessedAchieved { get; set; } + public int? ConfirmedResults { get; set; } + public DateTime? SignOffRequested { get; set; } + public bool SignOffAchieved { get; set; } + public DateTime? ReviewedDate { get; set; } + + // we need this for iteration across the registration answers from Delegate Accounts which match the custom fields of Centres. + public string?[] CentreRegistrationPrompts => + new[] + { + RegistrationAnswer1, + RegistrationAnswer2, + RegistrationAnswer3, + RegistrationAnswer4, + RegistrationAnswer5, + RegistrationAnswer6, + }; + } +} diff --git a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs index a18268f104..aac978b35a 100644 --- a/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/SuperAdmin/SuperAdminDelegateAccount.cs @@ -19,6 +19,7 @@ public SuperAdminDelegateAccount(DelegateEntity delegateEntity) LearningHubAuthId = delegateEntity.UserAccount.LearningHubAuthId; RegistrationConfirmationHash = delegateEntity.DelegateAccount.RegistrationConfirmationHash; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; SelfReg = delegateEntity.DelegateAccount.SelfReg; Active = delegateEntity.DelegateAccount.Active; EmailVerified = delegateEntity.UserAccount.EmailVerified; diff --git a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs index eaff1da52f..51a109785b 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminAccount.cs @@ -1,5 +1,7 @@ namespace DigitalLearningSolutions.Data.Models.User { + using System; + public class AdminAccount { public int Id { get; set; } @@ -26,6 +28,7 @@ public class AdminAccount public bool IsWorkforceContributor { get; set; } public bool IsLocalWorkforceManager { get; set; } public bool IsNominatedSupervisor { get; set; } + public DateTime? LastAccessed { get; set; } public bool IsCmsAdministrator => ImportOnly && IsContentManager; public bool IsCmsManager => IsContentManager && !ImportOnly; diff --git a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs index e070aca124..a9b71e5143 100644 --- a/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs +++ b/DigitalLearningSolutions.Data/Models/User/AdminEntity.cs @@ -3,6 +3,7 @@ using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.SearchSortFilterPaginate; + using System; public class AdminEntity : BaseSearchableItem { @@ -72,6 +73,7 @@ public override string SearchableName public bool IsSuperAdmin => AdminAccount.IsSuperAdmin; public bool IsReportsViewer => AdminAccount.IsReportsViewer; public bool IsActive => AdminAccount.Active; + public DateTime? LastAccessed => AdminAccount.LastAccessed; } } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs index 3f720e350b..66d458fd13 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateAccount.cs @@ -12,6 +12,7 @@ public class DelegateAccount public bool CentreActive { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime DateRegistered { get; set; } + public DateTime? LastAccessed { get; set; } public string? Answer1 { get; set; } public string? Answer2 { get; set; } public string? Answer3 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs index 7033b8d13f..f007b355b1 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUser.cs @@ -9,6 +9,7 @@ public class DelegateUser : User public int UserId { get; set; } public string CandidateNumber { get; set; } = string.Empty; public DateTime? DateRegistered { get; set; } + public DateTime? LastAccessed { get; set; } public int JobGroupId { get; set; } public string? JobGroupName { get; set; } public string? Answer1 { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs index ed04402308..1e9caf4e9d 100644 --- a/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs +++ b/DigitalLearningSolutions.Data/Models/User/DelegateUserCard.cs @@ -23,6 +23,7 @@ public DelegateUserCard(DelegateEntity delegateEntity) Password = delegateEntity.UserAccount.PasswordHash; CandidateNumber = delegateEntity.DelegateAccount.CandidateNumber; DateRegistered = delegateEntity.DelegateAccount.DateRegistered; + LastAccessed = delegateEntity.DelegateAccount.LastAccessed; JobGroupId = delegateEntity.UserAccount.JobGroupId; JobGroupName = delegateEntity.UserAccount.JobGroupName; Answer1 = delegateEntity.DelegateAccount.Answer1; diff --git a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs index 4e1c975bf1..6afca34e14 100644 --- a/DigitalLearningSolutions.Data/Models/User/UserAccount.cs +++ b/DigitalLearningSolutions.Data/Models/User/UserAccount.cs @@ -14,6 +14,7 @@ public class UserAccount public string? ProfessionalRegistrationNumber { get; set; } public byte[]? ProfileImage { get; set; } public bool Active { get; set; } + public DateTime? LastAccessed { get; set; } public int? ResetPasswordId { get; set; } public DateTime? TermsAgreed { get; set; } public int FailedLoginCount { get; set; } diff --git a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs index f8dad9821e..a77c38f7b8 100644 --- a/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Services/LoginServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; @@ -32,6 +33,7 @@ private static readonly (string?, List<(int centreId, string centreName, string private LoginService loginService = null!; private IUserService userService = null!; private IUserVerificationService userVerificationService = null!; + private ILoginDataService loginDataService = null!; [SetUp] public void Setup() @@ -39,7 +41,7 @@ public void Setup() userVerificationService = A.Fake(x => x.Strict()); userService = A.Fake(x => x.Strict()); - loginService = new LoginService(userService, userVerificationService); + loginService = new LoginService(userService, userVerificationService, loginDataService); } [Test] diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/AssessmentQuestions.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/AssessmentQuestions.cs index f4ffce91d1..519c8881bd 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/AssessmentQuestions.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/AssessmentQuestions.cs @@ -456,6 +456,7 @@ public IActionResult EditAssessmentQuestionOptions(AssessmentQuestionDetail asse return RedirectToAction("EditAssessmentQuestionOptions", "Frameworks", new { frameworkId, assessmentQuestionId, frameworkCompetencyId }); } assessmentQuestionDetail.ScoringInstructions = SanitizerHelper.SanitizeHtmlData(assessmentQuestionDetail.ScoringInstructions); + if (string.IsNullOrWhiteSpace(StringHelper.StripHtmlTags(assessmentQuestionDetail?.ScoringInstructions))) { assessmentQuestionDetail.ScoringInstructions = null; } SessionAssessmentQuestion sessionAssessmentQuestion = multiPageFormService .GetMultiPageFormData(MultiPageFormDataFeature.EditAssessmentQuestion, TempData) .GetAwaiter() diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs index 6afd88f86a..01c95404e3 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs @@ -65,6 +65,7 @@ public IActionResult AddEditFrameworkCompetencyGroup(int frameworkId, Competency var adminId = GetAdminId(); var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId); if (userRole < 2) return StatusCode(403); + if (string.IsNullOrWhiteSpace(StringHelper.StripHtmlTags(competencyGroupBase?.Description))) { competencyGroupBase.Description = null; } if (competencyGroupBase.ID > 0) { frameworkService.UpdateFrameworkCompetencyGroup(frameworkCompetencyGroupId, competencyGroupBase.CompetencyGroupID, competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData @@ -102,7 +103,7 @@ public IActionResult DeleteFrameworkCompetencyGroup(int frameworkId, int compete var adminId = GetAdminId(); - frameworkService.DeleteFrameworkCompetencyGroup(frameworkCompetencyGroupId, competencyGroupId, adminId); + frameworkService.DeleteFrameworkCompetencyGroup(frameworkCompetencyGroupId, competencyGroupId, frameworkId, adminId); return new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId, frameworkCompetencyGroupId })); } @@ -141,7 +142,7 @@ public IActionResult AddEditFrameworkCompetency(int frameworkId, FrameworkCompet frameworkCompetency.Description?.Trim(); var description = HttpUtility.HtmlDecode(HttpUtility.HtmlDecode(frameworkCompetency.Description)); var detailFramework = frameworkService.GetDetailFrameworkByFrameworkId(frameworkId, GetAdminId()); - if (string.IsNullOrWhiteSpace(description)) { frameworkCompetency.Description = null; } + if (string.IsNullOrWhiteSpace(StringHelper.StripHtmlTags(description))) { frameworkCompetency.Description = null; } if (!ModelState.IsValid) { ModelState.Remove(nameof(FrameworkCompetency.Name)); @@ -235,7 +236,7 @@ public IActionResult DeleteFrameworkCompetency(int frameworkId, int frameworkCom { var userRole = frameworkService.GetAdminUserRoleForFrameworkId(GetAdminId(), frameworkId); if (userRole < 2) return StatusCode(403); - frameworkService.DeleteFrameworkCompetency(frameworkCompetencyId, GetAdminId()); + frameworkService.DeleteFrameworkCompetency(frameworkCompetencyId, frameworkCompetencyGroupId, frameworkId, GetAdminId()); return frameworkCompetencyGroupId != null ? new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId, frameworkCompetencyGroupId }) + "#fcgroup-" + frameworkCompetencyGroupId.ToString()) : new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId }) + "#fc-ungrouped"); } [Route("/Frameworks/{frameworkId}/Competency/{frameworkCompetencyGroupId:int=0}/{frameworkCompetencyId}/Preview/")] diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index 2cdda2862c..f78ef7a44f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -582,14 +582,33 @@ public IActionResult EditFrameworkFlag(CustomFlagViewModel model, int frameworkI { if (ModelState.IsValid) { + var flags = frameworkService.GetCustomFlagsByFrameworkId(frameworkId, null) + .Where(fn => fn.FlagName?.Trim().ToLower() == model.FlagName?.Trim().ToLower()).ToList(); + + bool nameExists = flags.Any(x => x.FlagName?.Trim().ToLower() == model.FlagName?.Trim().ToLower()); + bool idExists = flags.Any(x => x.FlagId == flagId); + if (actionname == "Edit") { - frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists && !idExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.UpdateFrameworkCustomFlag(frameworkId, model.Id, model.FlagName?.Trim(), model.FlagGroup?.Trim(), model.FlagTagClass); } else { - frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName, model.FlagGroup, model.FlagTagClass); + if (nameExists) + { + ModelState.AddModelError(nameof(model.FlagName), "A custom flag already exists."); + return View("Developer/EditCustomFlag", model); + } + else + frameworkService.AddCustomFlagToFramework(frameworkId, model.FlagName?.Trim(), model.FlagGroup?.Trim(), model.FlagTagClass); } + return RedirectToAction("EditFrameworkFlags", "Frameworks", new { frameworkId }); } return View("Developer/EditCustomFlag", model); diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 9a49cffa85..e272f6da1e 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -246,6 +246,7 @@ public IActionResult AddQuestionsToWhichCompetencies(int AddAssessmentQuestionsO [Route("/Framework/{frameworkId}/{tabname}/Import/Summary")] public IActionResult ImportSummary() { + if (!TempData.Any()) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); var data = GetBulkUploadData(); var model = new ImportSummaryViewModel(data); return View("Developer/Import/ImportSummary", model); diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Review.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Review.cs index 16c2665150..e9a5304e65 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Review.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Review.cs @@ -63,6 +63,7 @@ public IActionResult LoadReview(int frameworkId, int reviewId) { var adminId = GetAdminId(); var framework = frameworkService.GetBaseFrameworkByFrameworkId(frameworkId, adminId); + if (framework.FrameworkReviewID == 0 || framework.FrameworkReviewID == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); if (framework == null) return StatusCode(404); if (framework.UserRole < 1) return StatusCode(403); var frameworkName = framework.FrameworkName; @@ -81,6 +82,8 @@ public IActionResult LoadReview(int frameworkId, int reviewId) public IActionResult SubmitFrameworkReview(int frameworkId, int reviewId, string? comment, bool signedOff) { var adminId = GetAdminId(); + var framework = frameworkService.GetBaseFrameworkByFrameworkId(frameworkId, adminId); + if (framework.FrameworkReviewID == 0 || framework.FrameworkReviewID == null) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 410 }); int? commentId = null; if (!string.IsNullOrWhiteSpace(comment)) commentId = frameworkService.InsertComment(frameworkId, adminId, comment, null); frameworkService.SubmitFrameworkReview(frameworkId, reviewId, signedOff, commentId); diff --git a/DigitalLearningSolutions.Web/Controllers/LoginController.cs b/DigitalLearningSolutions.Web/Controllers/LoginController.cs index 20280e18a8..80984a593b 100644 --- a/DigitalLearningSolutions.Web/Controllers/LoginController.cs +++ b/DigitalLearningSolutions.Web/Controllers/LoginController.cs @@ -82,6 +82,14 @@ public async Task Index(LoginViewModel model, string timeZone = " var loginResult = loginService.AttemptLogin(model.Username!.Trim(), model.Password!); + if (loginResult.LoginAttemptResult == LoginAttemptResult.LogIntoSingleCentre || + loginResult.LoginAttemptResult == LoginAttemptResult.ChooseACentre || + loginResult.LoginAttemptResult == LoginAttemptResult.UnverifiedEmail) + { + loginService.UpdateLastAccessedForUsersTable(loginResult.UserEntity.UserAccount.Id); + } + + switch (loginResult.LoginAttemptResult) { case LoginAttemptResult.InvalidCredentials: @@ -219,11 +227,17 @@ int centreIdToLogInto IsPersistent = rememberMe, IssuedUtc = clockUtility.UtcNow, }; + var centreAccountSet = userEntity?.GetCentreAccountSet(centreIdToLogInto); - var adminAccount = userEntity!.GetCentreAccountSet(centreIdToLogInto)?.AdminAccount; + if (centreAccountSet?.DelegateAccount?.Id != null) + { + loginService.UpdateLastAccessedForDelegatesAccountsTable(centreAccountSet.DelegateAccount.Id); + } + var adminAccount = centreAccountSet?.AdminAccount; if (adminAccount?.Active == true) { + loginService.UpdateLastAccessedForAdminAccountsTable(adminAccount.Id); sessionService.StartAdminSession(adminAccount.Id); } diff --git a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs index 7274173d88..f0bba93403 100644 --- a/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs +++ b/DigitalLearningSolutions.Web/Services/DelegateDownloadFileService.cs @@ -39,6 +39,7 @@ public class DelegateDownloadFileService : IDelegateDownloadFileService private const string ProfessionalRegistrationNumber = "Professional Registration Number"; private const string JobGroup = "Job group"; private const string RegisteredDate = "Registered"; + private const string LastAccessed = "Last Accessed Date"; private const string RegistrationComplete = "Registration complete"; private const string Active = "Active"; private const string Approved = "Approved"; @@ -333,6 +334,7 @@ DataTable dataTable new DataColumn(ProfessionalRegistrationNumber), new DataColumn(JobGroup), new DataColumn(RegisteredDate), + new DataColumn(LastAccessed), } ); @@ -374,7 +376,7 @@ CentreRegistrationPrompts registrationPrompts ); row[JobGroup] = delegateRecord.JobGroupName; row[RegisteredDate] = delegateRecord.DateRegistered?.Date; - + row[LastAccessed] = delegateRecord.LastAccessed?.Date; var delegateAnswers = delegateRecord.GetRegistrationFieldAnswers(); foreach (var prompt in registrationPrompts.CustomPrompts) @@ -402,7 +404,7 @@ CentreRegistrationPrompts registrationPrompts private static void FormatAllDelegateWorksheetColumns(IXLWorkbook workbook, DataTable dataTable) { ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, RegisteredDate, XLDataType.DateTime); - + ClosedXmlHelper.FormatWorksheetColumn(workbook, dataTable, LastAccessed, XLDataType.DateTime); var boolColumns = new[] { RegistrationComplete, Active, Approved, IsAdmin }; foreach (var columnName in boolColumns) { diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index b407be417f..6e2c1790e9 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -247,9 +247,9 @@ string direction void RemoveCustomFlag(int flagId); void RemoveCollaboratorFromFramework(int frameworkId, int id); - void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int adminId); + void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int frameworkId, int adminId); - void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId); + void DeleteFrameworkCompetency(int frameworkCompetencyId, int? frameworkCompetencyGroupId, int frameworkId, int adminId); void DeleteFrameworkDefaultQuestion( int frameworkId, @@ -316,14 +316,14 @@ public void DeleteCompetencyLearningResource(int competencyLearningResourceId, i frameworkDataService.DeleteCompetencyLearningResource(competencyLearningResourceId, adminId); } - public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) + public void DeleteFrameworkCompetency(int frameworkCompetencyId, int? frameworkCompetencyGroupId, int frameworkId, int adminId) { - frameworkDataService.DeleteFrameworkCompetency(frameworkCompetencyId, adminId); + frameworkDataService.DeleteFrameworkCompetency(frameworkCompetencyId, frameworkCompetencyGroupId, frameworkId, adminId); } - public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int adminId) + public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, int frameworkId, int adminId) { - frameworkDataService.DeleteFrameworkCompetencyGroup(frameworkCompetencyGroupId, competencyGroupId, adminId); + frameworkDataService.DeleteFrameworkCompetencyGroup(frameworkCompetencyGroupId, competencyGroupId, frameworkId, adminId); } public void DeleteFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool deleteFromExisting) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index dcb388f3ae..5ca64febc2 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -262,7 +262,7 @@ CompetencyTableRow competencyRow { foreach (var frameworkFlag in frameworkFlags) { - if (frameworkFlag.FlagName == flag) + if (frameworkFlag.FlagName?.Trim().ToLower() == flag?.Trim().ToLower()) { flagId = frameworkFlag.FlagId; break; @@ -271,7 +271,7 @@ CompetencyTableRow competencyRow } if (flagId == 0) { - flagId = frameworkService.AddCustomFlagToFramework(frameworkId, flag, "Flag", "nhsuk-tag--white"); + flagId = frameworkService.AddCustomFlagToFramework(frameworkId, flag?.Trim(), "Flag", "nhsuk-tag--white"); } flagIds.Add(flagId); } diff --git a/DigitalLearningSolutions.Web/Services/LoginService.cs b/DigitalLearningSolutions.Web/Services/LoginService.cs index 35d8cc7a16..dc52aca0b5 100644 --- a/DigitalLearningSolutions.Web/Services/LoginService.cs +++ b/DigitalLearningSolutions.Web/Services/LoginService.cs @@ -5,10 +5,12 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; + using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Enums; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models; using DigitalLearningSolutions.Data.Models.User; + using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Data.ViewModels; using DigitalLearningSolutions.Web.Helpers; using Microsoft.AspNetCore.Authentication; @@ -28,6 +30,12 @@ List idsOfCentresWithUnverifiedEmails bool CentreEmailIsVerified(int userId, int centreIdIfLoggingIntoSingleCentre); + void UpdateLastAccessedForUsersTable(int Id); + + void UpdateLastAccessedForDelegatesAccountsTable(int Id); + + void UpdateLastAccessedForAdminAccountsTable(int Id); + Task HandleLoginResult( LoginResult loginResult, TicketReceivedContext context, @@ -41,11 +49,28 @@ public class LoginService : ILoginService { private readonly IUserService userService; private readonly IUserVerificationService userVerificationService; + private readonly ILoginDataService loginDataService; - public LoginService(IUserService userService, IUserVerificationService userVerificationService) + public LoginService(IUserService userService, IUserVerificationService userVerificationService, ILoginDataService loginDataService) { this.userService = userService; this.userVerificationService = userVerificationService; + this.loginDataService = loginDataService; + } + + public void UpdateLastAccessedForUsersTable(int Id) + { + loginDataService.UpdateLastAccessedForUsersTable(Id); + } + + public void UpdateLastAccessedForDelegatesAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForDelegatesAccountsTable(Id); + } + + public void UpdateLastAccessedForAdminAccountsTable(int Id) + { + loginDataService.UpdateLastAccessedForAdminAccountsTable(Id); } public LoginResult AttemptLogin(string username, string password) diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentReportService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentReportService.cs index 18b6c46cdc..aff36d83f9 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentReportService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentReportService.cs @@ -1,129 +1,495 @@ -namespace DigitalLearningSolutions.Data.Services -{ - using ClosedXML.Excel; - using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; - using DigitalLearningSolutions.Data.Models.Email; - using DigitalLearningSolutions.Data.Models.SelfAssessments; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - - public interface ISelfAssessmentReportService - { - byte[] GetSelfAssessmentExcelExportForCentre(int centreId, int selfAssessmentId); - byte[] GetDigitalCapabilityExcelExportForCentre(int centreId); - IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId); - } - public class SelfAssessmentReportService : ISelfAssessmentReportService - { - private readonly IDCSAReportDataService dcsaReportDataService; - private readonly ISelfAssessmentReportDataService selfAssessmentReportDataService; - public SelfAssessmentReportService( - IDCSAReportDataService dcsaReportDataService, - ISelfAssessmentReportDataService selfAssessmentReportDataService - ) - { - this.dcsaReportDataService = dcsaReportDataService; - this.selfAssessmentReportDataService = selfAssessmentReportDataService; - } - private static void AddSheetToWorkbook(IXLWorkbook workbook, string sheetName, IEnumerable? dataObjects) - { - var sheet = workbook.Worksheets.Add(sheetName); - var table = sheet.Cell(1, 1).InsertTable(dataObjects); - table.Theme = XLTableTheme.TableStyleLight9; - sheet.Columns().AdjustToContents(); - } - - public IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId) - { - return selfAssessmentReportDataService.GetSelfAssessmentsForReportList(centreId, categoryId); - } - - public byte[] GetSelfAssessmentExcelExportForCentre(int centreId, int selfAssessmentId) - { - var selfAssessmentReportData = selfAssessmentReportDataService.GetSelfAssessmentReportDataForCentre(centreId, selfAssessmentId); - var reportData = selfAssessmentReportData.Select( - x => new - { - x.SelfAssessment, - x.Learner, - x.LearnerActive, - x.PRN, - x.JobGroup, - x.ProgrammeCourse, - x.Organisation, - x.DepartmentTeam, - x.OtherCentres, - x.DLSRole, - x.Registered, - x.Started, - x.LastAccessed, - x.OptionalProficienciesAssessed, - x.SelfAssessedAchieved, - x.ConfirmedResults, - x.SignOffRequested, - x.SignOffAchieved, - x.ReviewedDate - } - ); - using var workbook = new XLWorkbook(); - AddSheetToWorkbook(workbook, "SelfAssessmentLearners", reportData); - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - public byte[] GetDigitalCapabilityExcelExportForCentre(int centreId) - { - var delegateCompletionStatus = dcsaReportDataService.GetDelegateCompletionStatusForCentre(centreId); - var outcomeSummary = dcsaReportDataService.GetOutcomeSummaryForCentre(centreId); - var summary = delegateCompletionStatus.Select( - x => new - { - x.EnrolledMonth, - x.EnrolledYear, - x.FirstName, - x.LastName, - Email = (Guid.TryParse(x.Email, out _) ? string.Empty : x.Email), - x.CentreField1, - x.CentreField2, - x.CentreField3, - x.Status - } - ); - var details = outcomeSummary.Select( - x => new - { - x.EnrolledMonth, - x.EnrolledYear, - x.JobGroup, - x.CentreField1, - x.CentreField2, - x.CentreField3, - x.Status, - x.LearningLaunched, - x.LearningCompleted, - x.DataInformationAndContentConfidence, - x.DataInformationAndContentRelevance, - x.TeachinglearningAndSelfDevelopmentConfidence, - x.TeachinglearningAndSelfDevelopmentRelevance, - x.CommunicationCollaborationAndParticipationConfidence, - x.CommunicationCollaborationAndParticipationRelevance, - x.TechnicalProficiencyConfidence, - x.TechnicalProficiencyRelevance, - x.CreationInnovationAndResearchConfidence, - x.CreationInnovationAndResearchRelevance, - x.DigitalIdentityWellbeingSafetyAndSecurityConfidence, - x.DigitalIdentityWellbeingSafetyAndSecurityRelevance - } - ); - using var workbook = new XLWorkbook(); - AddSheetToWorkbook(workbook, "Delegate Completion Status", summary); - AddSheetToWorkbook(workbook, "Assessment Outcome Summary", outcomeSummary); - using var stream = new MemoryStream(); - workbook.SaveAs(stream); - return stream.ToArray(); - } - } - -} +namespace DigitalLearningSolutions.Data.Services +{ + using ClosedXML.Excel; + using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; + using DigitalLearningSolutions.Data.Models.CustomPrompts; + using DigitalLearningSolutions.Data.Models.SelfAssessments; + using DigitalLearningSolutions.Data.Models.SelfAssessments.Export; + using DigitalLearningSolutions.Web.Services; + using System; + using System.Collections.Generic; + using System.Data; + using System.IO; + using System.Linq; + + public interface ISelfAssessmentReportService + { + byte[] GetSelfAssessmentExcelExportForCentre(int centreId, int selfAssessmentId); + byte[] GetDigitalCapabilityExcelExportForCentre(int centreId); + IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId); + } + public class SelfAssessmentReportService : ISelfAssessmentReportService + { + private const string SelfAssessment = "SelfAssessment"; + private const string Learner = "Leaner"; + private const string LearnerActive = "LearnerActive"; + private const string PRN = "PRN"; + private const string JobGroup = "JobGroup"; + private const string OtherCentres = "OtherCentres"; + private const string DLSRole = "DLSRole"; + private const string Registered = "Registered"; + private const string Started = "Started"; + private const string LastAccessed = "LastAccessed"; + private const string OptionalProficienciesAssessed = "OptionalProficienciesAssessed"; + private const string SelfAssessedAchieved = "SelfAssessedAchieved"; + private const string ConfirmedResults = "ConfirmedResults"; + private const string SignOffRequested = "SignOffRequested"; + private const string SignOffAchieved = "SignOffAchieved"; + private const string ReviewedDate = "ReviewedDate"; + private const string EnrolledMonth = "EnrolledMonth"; + private const string EnrolledYear = "EnrolledYear"; + private const string FirstName = "FirstName"; + private const string LastName = "LastName"; + private const string Email = "Email"; + private const string Status = "Status"; + private const string LearningLaunched = "LearningLaunched"; + private const string LearningCompleted = "LearningCompleted"; + private const string DataInformationAndContentConfidence = "DataInformationAndContentConfidence"; + private const string DataInformationAndContentRelevance = "DataInformationAndContentRelevance"; + private const string TeachinglearningAndSelfDevelopmentConfidence = "TeachinglearningAndSelfDevelopmentConfidence"; + private const string TeachinglearningAndSelfDevelopmentRelevance = "TeachinglearningAndSelfDevelopmentRelevance"; + private const string CommunicationCollaborationAndParticipationConfidence = "CommunicationCollaborationAndParticipationConfidence"; + private const string CommunicationCollaborationAndParticipationRelevance = "CommunicationCollaborationAndParticipationRelevance"; + private const string TechnicalProficiencyConfidence = "TechnicalProficiencyConfidence"; + private const string TechnicalProficiencyRelevance = "TechnicalProficiencyRelevance"; + private const string CreationInnovationAndResearchConfidence = "CreationInnovationAndResearchConfidence"; + private const string CreationInnovationAndResearchRelevance = "CreationInnovationAndResearchRelevance"; + private const string DigitalIdentityWellbeingSafetyAndSecurityConfidence = "DigitalIdentityWellbeingSafetyAndSecurityConfidence"; + private const string DigitalIdentityWellbeingSafetyAndSecurityRelevance = "DigitalIdentityWellbeingSafetyAndSecurityRelevance"; + + + + private readonly IDCSAReportDataService dcsaReportDataService; + private readonly ISelfAssessmentReportDataService selfAssessmentReportDataService; + private readonly ICentreRegistrationPromptsService registrationPromptsService; + public SelfAssessmentReportService( + IDCSAReportDataService dcsaReportDataService, + ISelfAssessmentReportDataService selfAssessmentReportDataService, + ICentreRegistrationPromptsService registrationPromptsService + ) + { + this.dcsaReportDataService = dcsaReportDataService; + this.selfAssessmentReportDataService = selfAssessmentReportDataService; + this.registrationPromptsService = registrationPromptsService; + } + private static void AddSheetToWorkbook(IXLWorkbook workbook, string sheetName, IEnumerable? dataObjects) + { + var sheet = workbook.Worksheets.Add(sheetName); + var table = sheet.Cell(1, 1).InsertTable(dataObjects); + table.Theme = XLTableTheme.TableStyleLight9; + sheet.Columns().AdjustToContents(); + } + + public IEnumerable GetSelfAssessmentsForReportList(int centreId, int? categoryId) + { + return selfAssessmentReportDataService.GetSelfAssessmentsForReportList(centreId, categoryId); + } + + public byte[] GetSelfAssessmentExcelExportForCentre(int centreId, int selfAssessmentId) + { + using var workbook = new XLWorkbook(); + var selfAssessmentReportData = selfAssessmentReportDataService.GetSelfAssessmentReportDataForCentre(centreId, selfAssessmentId); + PopulateSelfAssessmentSheetForCentre(workbook, centreId, selfAssessmentReportData); + using var stream = new MemoryStream(); + workbook.SaveAs(stream); + return stream.ToArray(); + } + public byte[] GetDigitalCapabilityExcelExportForCentre(int centreId) + { + + using var workbook = new XLWorkbook(); + GetDelegateCompletionStatusForCentre(workbook, centreId); + GetOutcomeSummaryForCentre(workbook, centreId); + using var stream = new MemoryStream(); + workbook.SaveAs(stream); + return stream.ToArray(); + } + + private void GetOutcomeSummaryForCentre(XLWorkbook workbook, int centreId) + { + var outcomeSummary = dcsaReportDataService.GetOutcomeSummaryForCentre(centreId); + + var sheet = workbook.Worksheets.Add("Assessment Outcome Summary"); + // Set sheet to have outlining expand buttons at the top of the expanded section. + sheet.Outline.SummaryVLocation = XLOutlineSummaryVLocation.Top; + var customRegistrationPrompts = + registrationPromptsService.GetCentreRegistrationPromptsByCentreId(centreId); + var dataTable = new DataTable(); + + var details = outcomeSummary.Select( + x => new + { + x.EnrolledMonth, + x.EnrolledYear, + x.JobGroup, + x.RegistrationAnswer1, + x.RegistrationAnswer2, + x.RegistrationAnswer3, + x.RegistrationAnswer4, + x.RegistrationAnswer5, + x.RegistrationAnswer6, + x.Status, + x.LearningLaunched, + x.LearningCompleted, + x.DataInformationAndContentConfidence, + x.DataInformationAndContentRelevance, + x.TeachinglearningAndSelfDevelopmentConfidence, + x.TeachinglearningAndSelfDevelopmentRelevance, + x.CommunicationCollaborationAndParticipationConfidence, + x.CommunicationCollaborationAndParticipationRelevance, + x.TechnicalProficiencyConfidence, + x.TechnicalProficiencyRelevance, + x.CreationInnovationAndResearchConfidence, + x.CreationInnovationAndResearchRelevance, + x.DigitalIdentityWellbeingSafetyAndSecurityConfidence, + x.DigitalIdentityWellbeingSafetyAndSecurityRelevance + } + ); + + // set the common header table for the excel sheet + SetUpCommonTableColumnsForOutcomeSummary(customRegistrationPrompts, dataTable); + + // insert the header table into the sheet starting at the first position + var headerTable = sheet.Cell(1, 1).InsertTable(dataTable); + + foreach (var report in outcomeSummary) + { + //iterate and add every record from the query to the datatable + AddOutcomeSummaryReportToSheet(sheet, dataTable, customRegistrationPrompts, report); + } + + var insertedDataRange = sheet.Cell(GetNextEmptyRowNumber(sheet), 1).InsertData(dataTable.Rows); + if (dataTable.Rows.Count > 0) + { + sheet.Rows(insertedDataRange.FirstRow().RowNumber(), insertedDataRange.LastRow().RowNumber()) + .Group(true); + } + + //format the sheet rows and content + sheet.Rows().Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + headerTable.Theme = XLTableTheme.TableStyleLight9; + + sheet.Columns().AdjustToContents(); + + + } + + private void AddOutcomeSummaryReportToSheet(IXLWorksheet sheet, DataTable dataTable, CentreRegistrationPrompts customRegistrationPrompts, DCSAOutcomeSummary report) + { + var row = dataTable.NewRow(); + row[EnrolledMonth] = report.EnrolledMonth; + row[EnrolledYear] = report.EnrolledYear; + row[JobGroup] = report.JobGroup; + + // map the individual registration fields with the centre registration custom prompts + foreach (var prompt in customRegistrationPrompts.CustomPrompts) + { + if (dataTable.Columns.Contains($"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})")) + { + row[$"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})"] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + else + { + row[prompt.PromptText] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + } + row[Status] = report.Status; + row[LearningLaunched] = report.LearningLaunched; + row[LearningCompleted] = report.LearningCompleted; + row[DataInformationAndContentConfidence] = report.DataInformationAndContentConfidence; + row[DataInformationAndContentRelevance] = report.DataInformationAndContentRelevance; + row[TeachinglearningAndSelfDevelopmentConfidence] = report.TeachinglearningAndSelfDevelopmentConfidence; + row[TeachinglearningAndSelfDevelopmentRelevance] = report.TeachinglearningAndSelfDevelopmentRelevance; + row[CommunicationCollaborationAndParticipationConfidence] = report.CommunicationCollaborationAndParticipationConfidence; + row[CommunicationCollaborationAndParticipationRelevance] = report.CommunicationCollaborationAndParticipationRelevance; + row[TechnicalProficiencyConfidence] = report.TechnicalProficiencyConfidence; + row[TechnicalProficiencyRelevance] = report.TechnicalProficiencyRelevance; + row[CreationInnovationAndResearchConfidence] = report.CreationInnovationAndResearchConfidence; + row[CreationInnovationAndResearchRelevance] = report.CreationInnovationAndResearchRelevance; + row[DigitalIdentityWellbeingSafetyAndSecurityConfidence] = report.DigitalIdentityWellbeingSafetyAndSecurityConfidence; + row[DigitalIdentityWellbeingSafetyAndSecurityRelevance] = report.DigitalIdentityWellbeingSafetyAndSecurityRelevance; + dataTable.Rows.Add(row); + } + + private void SetUpCommonTableColumnsForOutcomeSummary(CentreRegistrationPrompts customRegistrationPrompts, DataTable dataTable) + { + dataTable.Columns.AddRange( + new[] { + new DataColumn(EnrolledMonth), new DataColumn(EnrolledYear), new DataColumn(JobGroup) + } + ); + + foreach (var prompt in customRegistrationPrompts.CustomPrompts) + { + dataTable.Columns.Add( + !dataTable.Columns.Contains(prompt.PromptText) + ? prompt.PromptText + : $"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})" + ); + } + + dataTable.Columns.AddRange( + new[] + { + new DataColumn(Status), new DataColumn(LearningLaunched),new DataColumn(LearningCompleted),new DataColumn(DataInformationAndContentConfidence), + new DataColumn(DataInformationAndContentRelevance), new DataColumn(TeachinglearningAndSelfDevelopmentConfidence), + new DataColumn(TeachinglearningAndSelfDevelopmentRelevance),new DataColumn(CommunicationCollaborationAndParticipationConfidence), + new DataColumn(CommunicationCollaborationAndParticipationRelevance), + new DataColumn(TechnicalProficiencyConfidence), + new DataColumn(TechnicalProficiencyRelevance), + new DataColumn(CreationInnovationAndResearchConfidence), + new DataColumn(CreationInnovationAndResearchRelevance), + new DataColumn(DigitalIdentityWellbeingSafetyAndSecurityConfidence), + new DataColumn(DigitalIdentityWellbeingSafetyAndSecurityRelevance) + } + ); + } + + private void GetDelegateCompletionStatusForCentre(XLWorkbook workbook, int centreId) + { + + var delegateCompletionStatus = dcsaReportDataService.GetDelegateCompletionStatusForCentre(centreId); + + var sheet = workbook.Worksheets.Add("Delegate Completion Status"); + // Set sheet to have outlining expand buttons at the top of the expanded section. + sheet.Outline.SummaryVLocation = XLOutlineSummaryVLocation.Top; + var customRegistrationPrompts = + registrationPromptsService.GetCentreRegistrationPromptsByCentreId(centreId); + var dataTable = new DataTable(); + + // did this to sequqence the element into a new form based on the order below + var summary = delegateCompletionStatus.Select( + x => new + { + x.EnrolledMonth, + x.EnrolledYear, + x.FirstName, + x.LastName, + Email = (Guid.TryParse(x.Email, out _) ? string.Empty : x.Email), + x.RegistrationAnswer1, + x.RegistrationAnswer2, + x.RegistrationAnswer3, + x.RegistrationAnswer4, + x.RegistrationAnswer5, + x.RegistrationAnswer6, + x.Status + } + + ); + + // set the common header table for the excel sheet + SetUpCommonTableColumnsForDelegateCompletion(customRegistrationPrompts, dataTable); + + // insert the header table into the sheet starting at the first position + var headerTable = sheet.Cell(1, 1).InsertTable(dataTable); + + foreach (var report in delegateCompletionStatus) + { + //iterate and add every record from the query to the datatable + AddDelegateCompletionReportToSheet(sheet, dataTable, customRegistrationPrompts, report); + } + + var insertedDataRange = sheet.Cell(GetNextEmptyRowNumber(sheet), 1).InsertData(dataTable.Rows); + if (dataTable.Rows.Count > 0) + { + sheet.Rows(insertedDataRange.FirstRow().RowNumber(), insertedDataRange.LastRow().RowNumber()) + .Group(true); + } + + //format the sheet rows and content + sheet.Rows().Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + headerTable.Theme = XLTableTheme.TableStyleLight9; + + sheet.Columns().AdjustToContents(); + + + } + + private void PopulateSelfAssessmentSheetForCentre(IXLWorkbook workbook, int centreId, IEnumerable selfAssessmentReportData) + { + var sheet = workbook.Worksheets.Add("SelfAssessmentLearners"); + // Set sheet to have outlining expand buttons at the top of the expanded section. + sheet.Outline.SummaryVLocation = XLOutlineSummaryVLocation.Top; + var customRegistrationPrompts = + registrationPromptsService.GetCentreRegistrationPromptsByCentreId(centreId); + var dataTable = new DataTable(); + + // did this to sequqence the element into a new form based on the order below + var reportData = selfAssessmentReportData.Select( + x => new + { + x.SelfAssessment, + x.Learner, + x.LearnerActive, + x.PRN, + x.JobGroup, + x.OtherCentres, + x.DLSRole, + x.Registered, + x.Started, + x.LastAccessed, + x.OptionalProficienciesAssessed, + x.SelfAssessedAchieved, + x.ConfirmedResults, + x.SignOffRequested, + x.SignOffAchieved, + x.ReviewedDate + } + ); + + // set the common header table for the excel sheet + SetUpCommonTableColumnsForSelfAssessment(customRegistrationPrompts, dataTable); + + // insert the header table into the sheet starting at the first position + var headerTable = sheet.Cell(1, 1).InsertTable(dataTable); + + foreach (var report in selfAssessmentReportData) + { + //iterate and add every record from the query to the datatable + AddSelfAssessmentReportToSheet(sheet, dataTable, customRegistrationPrompts, report); + } + + var insertedDataRange = sheet.Cell(GetNextEmptyRowNumber(sheet), 1).InsertData(dataTable.Rows); + if (dataTable.Rows.Count > 0) + { + sheet.Rows(insertedDataRange.FirstRow().RowNumber(), insertedDataRange.LastRow().RowNumber()) + .Group(true); + } + + //format the sheet rows and content + sheet.Rows().Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + headerTable.Theme = XLTableTheme.TableStyleLight9; + + sheet.Columns().AdjustToContents(); + } + + private static void SetUpCommonTableColumnsForSelfAssessment(CentreRegistrationPrompts centreRegistrationPrompts, DataTable dataTable) + { + dataTable.Columns.AddRange( + new[] {new DataColumn(SelfAssessment), new DataColumn(Learner), new DataColumn(LearnerActive), new DataColumn(PRN), + new DataColumn(JobGroup)} + ); + foreach (var prompt in centreRegistrationPrompts.CustomPrompts) + { + dataTable.Columns.Add( + !dataTable.Columns.Contains(prompt.PromptText) + ? prompt.PromptText + : $"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})" + ); + } + + dataTable.Columns.AddRange( + new[] + { + new DataColumn(OtherCentres), + new DataColumn(DLSRole), + new DataColumn(Registered), + new DataColumn(Started), + new DataColumn(LastAccessed), + new DataColumn(OptionalProficienciesAssessed), + new DataColumn(SelfAssessedAchieved), + new DataColumn(ConfirmedResults), + new DataColumn(SignOffRequested), + new DataColumn(SignOffAchieved), + new DataColumn(ReviewedDate) + } + ); + } + + private static void SetUpCommonTableColumnsForDelegateCompletion(CentreRegistrationPrompts centreRegistrationPrompts, DataTable dataTable) + { + dataTable.Columns.AddRange( + new[] {new DataColumn(EnrolledMonth), new DataColumn(EnrolledYear), new DataColumn(FirstName), new DataColumn(LastName), + new DataColumn(Email)} + ); + foreach (var prompt in centreRegistrationPrompts.CustomPrompts) + { + dataTable.Columns.Add( + !dataTable.Columns.Contains(prompt.PromptText) + ? prompt.PromptText + : $"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})" + ); + } + + dataTable.Columns.AddRange( + new[] + { + new DataColumn(Status) + } + ); + } + + private static void AddSelfAssessmentReportToSheet(IXLWorksheet sheet, DataTable dataTable, CentreRegistrationPrompts centreRegistrationPrompts, + SelfAssessmentReportData report) + { + var row = dataTable.NewRow(); + row[SelfAssessment] = report.SelfAssessment; + row[Learner] = report.Learner; + row[LearnerActive] = report.LearnerActive; + row[PRN] = report.PRN; + row[JobGroup] = report.JobGroup; + + // map the individual registration fields with the centre registration custom prompts + foreach (var prompt in centreRegistrationPrompts.CustomPrompts) + { + if (dataTable.Columns.Contains($"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})")) + { + row[$"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})"] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + else + { + row[prompt.PromptText] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + } + row[OtherCentres] = report.OtherCentres; + row[DLSRole] = report.DLSRole; + row[Registered] = report.Registered?.ToString("dd/MM/yyyy"); + row[Started] = report.Started?.ToString("dd/MM/yyyy"); + row[LastAccessed] = report.LastAccessed?.ToString("dd/MM/yyyy"); + row[OptionalProficienciesAssessed] = report.OptionalProficienciesAssessed; + row[SelfAssessedAchieved] = report.SelfAssessedAchieved; + row[ConfirmedResults] = report.ConfirmedResults; + row[SignOffRequested] = report.SignOffRequested?.ToString("dd/MM/yyyy"); + row[SignOffAchieved] = report.SignOffAchieved ? "Yes" : "No"; + row[ReviewedDate] = report.ReviewedDate?.ToString("dd/MM/yyyy"); + dataTable.Rows.Add(row); + } + + private static void AddDelegateCompletionReportToSheet(IXLWorksheet sheet, DataTable dataTable, CentreRegistrationPrompts centreRegistrationPrompts, + DCSADelegateCompletionStatus report) + { + var row = dataTable.NewRow(); + row[EnrolledMonth] = report.EnrolledMonth; + row[EnrolledYear] = report.EnrolledYear; + row[FirstName] = report.FirstName; + row[LastName] = report.LastName; + row[Email] = report.Email; + + // map the individual registration fields with the centre registration custom prompts + foreach (var prompt in centreRegistrationPrompts.CustomPrompts) + { + if (dataTable.Columns.Contains($"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})")) + { + row[$"{prompt.PromptText} (Prompt {prompt.RegistrationField.Id})"] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + else + { + row[prompt.PromptText] = + report.CentreRegistrationPrompts[prompt.RegistrationField.Id - 1]; + } + } + row[Status] = report.Status; + dataTable.Rows.Add(row); + } + + private static int GetNextEmptyRowNumber(IXLWorksheet sheet) + { + return sheet.LastRowUsed().RowNumber() + 1; + } + } +} diff --git a/DigitalLearningSolutions.Web/Startup.cs b/DigitalLearningSolutions.Web/Startup.cs index bdc34b4d79..07b0c84237 100644 --- a/DigitalLearningSolutions.Web/Startup.cs +++ b/DigitalLearningSolutions.Web/Startup.cs @@ -534,6 +534,7 @@ private static void RegisterDataServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void RegisterHelpers(IServiceCollection services) diff --git a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss index 3f796924e8..47fb8f20d5 100644 --- a/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss +++ b/DigitalLearningSolutions.Web/Styles/trackingSystem/delegates/courseDelegates.scss @@ -26,3 +26,31 @@ } } } + +.nhsuk-expander[open] details.nhsuk-details[open] .nhsuk-details__summary-text::before { + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + clip-path: polygon(0% 0%, 50% 100%, 100% 0%); + border-width: 12.124px 7px 0 7px; + border-top-color: inherit; +} + +.nhsuk-details[open] .nhsuk-details .nhsuk-details__summary-text::before { + bottom: 0; + content: ""; + left: 0; + margin: auto; + position: absolute; + top: 0; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + clip-path: polygon(0% 0%, 100% 50%, 0% 100%); + border-width: 7px 0 7px 12.124px; + border-left-color: inherit; +} diff --git a/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs b/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs new file mode 100644 index 0000000000..bf484f426e --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewComponents/DlsFooterBannerTextViewComponent.cs @@ -0,0 +1,34 @@ +using System.Security.Claims; +using DigitalLearningSolutions.Data.DataServices; +using DigitalLearningSolutions.Web.Helpers; +using Microsoft.AspNetCore.Mvc; +using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents; +using DigitalLearningSolutions.Web.Services; +using System; +using System.Threading.Tasks; + +namespace DigitalLearningSolutions.Web.ViewComponents +{ + public class DlsFooterBannerTextViewComponent : ViewComponent + { + private readonly ICentresService centresService; + + public DlsFooterBannerTextViewComponent(ICentresService centresService) + { + this.centresService = centresService; + } + + public IViewComponentResult Invoke() + { + var centreId = ((ClaimsPrincipal)User).GetCustomClaimAsInt(CustomClaimTypes.UserCentreId); + if (centreId == null) + { + return View(new DlsFooterBannerTextViewModel(null)); + } + + var bannerText = centresService.GetBannerText(Convert.ToInt32(centreId)); + var model = new DlsFooterBannerTextViewModel(bannerText); + return View(model); + } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs new file mode 100644 index 0000000000..3a0fdcdd92 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DlsFooterBannerTextViewModel.cs @@ -0,0 +1,13 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +{ + public class DlsFooterBannerTextViewModel + { + public readonly string? BannerText; + + public DlsFooterBannerTextViewModel(string bannerText) + { + BannerText = bannerText; + } + + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs index 0afcfe7498..88f5ea2889 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Administrators/SearchableAdminAccountsViewModel.cs @@ -5,7 +5,7 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; - + using DateHelper = Helpers.DateHelper; public class SearchableAdminAccountsViewModel : BaseFilterableViewModel { public readonly bool CanShowDeleteAdminButton; @@ -28,7 +28,10 @@ ReturnPageQuery returnPageQuery IsLocked = admin.UserAccount?.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsAdminActive = admin.AdminAccount.Active; IsUserActive = admin.UserAccount.Active; - + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = IsAdminActive && admin.AdminIdReferenceCount > 0; CanShowDeleteAdminButton = admin.AdminIdReferenceCount == 0; @@ -47,6 +50,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public bool IsAdminActive { get; set; } public bool IsUserActive { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs index 8ea0ed22e5..a615461e73 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Delegates/SearchableDelegatesViewModel.cs @@ -28,6 +28,7 @@ ReturnPageQuery returnPageQuery LearningHubID = delegates.LearningHubAuthId; AccountClaimed = delegates.RegistrationConfirmationHash; DateRegistered = delegates.DateRegistered?.ToString(Data.Helpers.DateHelper.StandardDateFormat); + LastAccessed = delegates.LastAccessed?.ToString(Data.Helpers.DateHelper.StandardDateFormat); SelRegistered = delegates.SelfReg; IsDelegateActive = delegates.Active; IsCentreEmailVerified = delegates.CentreEmailVerified == null ? false : true; @@ -52,6 +53,7 @@ ReturnPageQuery returnPageQuery public bool IsLocked { get; set; } public string AccountClaimed { get; set; } public string? DateRegistered { get; set; } + public string? LastAccessed { get; set; } public bool SelRegistered { get; set; } public bool IsDelegateActive { get; set; } public bool IsUserActive { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs index 5b1e7e63ac..263e008165 100644 --- a/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/SuperAdmin/Users/SearchableUserAccountViewModel.cs @@ -27,6 +27,10 @@ ReturnPageQuery returnPageQuery ProfessionalRegistrationNumber = user.UserAccount.ProfessionalRegistrationNumber; LearningHubAuthId = user.UserAccount.LearningHubAuthId; ReturnPageQuery = returnPageQuery; + if (user.UserAccount.LastAccessed.HasValue) + { + LastAccessed = user.UserAccount.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } } public int Id { get; set; } @@ -51,6 +55,7 @@ ReturnPageQuery returnPageQuery public int? LearningHubAuthId { get; set; } + public string? LastAccessed { get; set; } public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs index 7e4ab30b47..c3da68c395 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Centre/Administrator/SearchableAdminViewModel.cs @@ -5,6 +5,8 @@ using DigitalLearningSolutions.Data.Models.User; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.ViewModels.Common.SearchablePage; + using System; + using DateHelper = Helpers.DateHelper; public class SearchableAdminViewModel : BaseFilterableViewModel { @@ -23,6 +25,10 @@ ReturnPageQuery returnPageQuery EmailAddress = admin.EmailForCentreNotifications; IsLocked = admin.UserAccount.FailedLoginCount >= AuthHelper.FailedLoginThreshold; IsActive = admin.AdminAccount.Active; + if (admin.LastAccessed.HasValue) + { + LastAccessed = admin.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } CanShowDeactivateAdminButton = UserPermissionsHelper.LoggedInAdminCanDeactivateUser(admin.AdminAccount, loggedInAdminAccount); @@ -46,6 +52,8 @@ ReturnPageQuery returnPageQuery public bool IsActive { get; set; } + public string? LastAccessed { get; set; } + public ReturnPageQuery ReturnPageQuery { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/AllDelegates/AllDelegatesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/AllDelegates/AllDelegatesViewModel.cs index 63a027e4d4..f6a8141dcc 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/AllDelegates/AllDelegatesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/AllDelegates/AllDelegatesViewModel.cs @@ -44,6 +44,7 @@ IEnumerable availableFilters public override IEnumerable<(string, string)> SortOptions { get; } = new[] { DelegateSortByOptions.Name, + DelegateSortByOptions.LastAccessed, DelegateSortByOptions.RegistrationDate, }; diff --git a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs index 504fd4b454..814e488ac9 100644 --- a/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/TrackingSystem/Delegates/Shared/DelegateInfoViewModel.cs @@ -41,6 +41,10 @@ IEnumerable delegateRegistrationPrompts { RegistrationDate = delegateUser.DateRegistered.Value.ToString(DateHelper.StandardDateFormat); } + if (delegateUser.LastAccessed.HasValue) + { + LastAccessed = delegateUser.LastAccessed.Value.ToString(DateHelper.StandardDateFormat); + } DelegateRegistrationPrompts = delegateRegistrationPrompts; RegistrationConfirmationHash = delegateUser.RegistrationConfirmationHash; @@ -61,6 +65,7 @@ IEnumerable delegateRegistrationPrompts public int JobGroupId { get; set; } public string? JobGroup { get; set; } public string? RegistrationDate { get; set; } + public string? LastAccessed { get; set; } public string ProfessionalRegistrationNumber { get; set; } public string? RegistrationConfirmationHash { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/EditCustomFlag.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/EditCustomFlag.cshtml index 0e076104db..b5ca92ee33 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/EditCustomFlag.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/EditCustomFlag.cshtml @@ -1,65 +1,65 @@ @using DigitalLearningSolutions.Web.ViewModels.Frameworks @model CustomFlagViewModel @{ - var addOrEdit = Model.Id == 0 ? "Add" : "Edit"; - var frameworkId = ViewContext.RouteData.Values["frameworkId"]; - ViewData["Title"] = "Framework Custom Flags"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; + var addOrEdit = Model.Id == 0 ? "Add" : "Edit"; + var frameworkId = ViewContext.RouteData.Values["frameworkId"]; + ViewData["Title"] = "Framework Custom Flags"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -

@addOrEdit custom flag

-
+

@addOrEdit custom flag

+ @if (!ViewData.ModelState.IsValid) - { - - } - - - - - - - - - - - -
- Tag colour + { + + } + + + + + + + + + + + +
+ Tag colour +
+ + +
+ + - - - - - - + diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Comments.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Comments.cshtml index ee121d4441..5ec0bb8048 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Comments.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Comments.cshtml @@ -31,7 +31,7 @@ else Add a comment - + - - } + + Reply + + @if (!Model.Replies.Any() && Model.UserIsCommenter) + { + + Delete + + } + } + else + { +
+
+
+ + + +
+ +
+ } +
+ - - diff --git a/DigitalLearningSolutions.Web/Views/LearningMenu/CoursePassword.cshtml b/DigitalLearningSolutions.Web/Views/LearningMenu/CoursePassword.cshtml index 2699f054e9..7f892b6015 100644 --- a/DigitalLearningSolutions.Web/Views/LearningMenu/CoursePassword.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningMenu/CoursePassword.cshtml @@ -36,7 +36,6 @@

@Model.Title

@Model.Description

-

@Model.BannerText

diff --git a/DigitalLearningSolutions.Web/Views/LearningMenu/Index.cshtml b/DigitalLearningSolutions.Web/Views/LearningMenu/Index.cshtml index eadf1f5d7b..a01dc12393 100644 --- a/DigitalLearningSolutions.Web/Views/LearningMenu/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningMenu/Index.cshtml @@ -42,7 +42,6 @@ }

@Model.CentreName

-

@Model.BannerText

diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml index 5fc25aa747..dcb270c4a7 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Available/Available.cshtml @@ -6,58 +6,54 @@ @{ - ViewData["Application"] = "Learning Portal"; - ViewData["Title"] = "Available Activities"; - ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Available"; - ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["Application"] = "Learning Portal"; + ViewData["Title"] = "Available Activities"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Available"; + ViewData["HeaderPathName"] = "Learning Portal"; } @section NavMenuItems { - + } - @if (Model.JavascriptSearchSortFilterPaginateEnabled) +@if (Model.JavascriptSearchSortFilterPaginateEnabled) { - + }
-
-

Available Activities

- @if (Model.BannerText != null) - { -

@Model.BannerText

- } +
+

Available Activities

- + -
- @if (Model.NoDataFound) - { - - } - else - { - - - - -
- @foreach (var availableCourse in Model.AvailableCourses) +
+ @if (Model.NoDataFound) { - + } -
- } - -
+ else + { + + + + +
+ @foreach (var availableCourse in Model.AvailableCourses) + { + + } +
+ } + +
@if (Model.JavascriptSearchSortFilterPaginateEnabled) { - @section scripts { - -} + @section scripts { + + } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml index 034b347511..bd2e5ae5af 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Completed/Completed.cshtml @@ -6,67 +6,63 @@ @{ - ViewData["Application"] = "Learning Portal"; - ViewData["Title"] = "My Completed Activities"; - ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Completed"; - ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["Application"] = "Learning Portal"; + ViewData["Title"] = "My Completed Activities"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Completed"; + ViewData["HeaderPathName"] = "Learning Portal"; } @if (Model.JavascriptSearchSortFilterPaginateEnabled) { - + }
-
-

My Completed Activities

- @if (Model.BannerText != null) - { -

@Model.BannerText

- } - - @if (!Model.ApiIsAccessible) - { - - } +
+

My Completed Activities

- + @if (!Model.ApiIsAccessible) + { + + } -
- @if (Model.NoDataFound) - { - - } - else - { - - - + -
- @foreach (var completedLearningItem in Model.CompletedActivities) +
+ @if (Model.NoDataFound) { - if (completedLearningItem is CompletedCourseViewModel) - { - - } - else - { - - } + + } + else + { + + + + +
+ @foreach (var completedLearningItem in Model.CompletedActivities) + { + if (completedLearningItem is CompletedCourseViewModel) + { + + } + else + { + + } + } +
} -
- } - -
+ +
@if (Model.JavascriptSearchSortFilterPaginateEnabled) { - @section scripts { - -} + @section scripts { + + } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml index 4a13b7b85a..8f1366568c 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/Current/Current.cshtml @@ -8,75 +8,71 @@ @{ - ViewData["Application"] = "Learning Portal"; - ViewData["Title"] = "My Current Activities"; - ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Current"; - ViewData["HeaderPathName"] = "Learning Portal"; + ViewData["Application"] = "Learning Portal"; + ViewData["Title"] = "My Current Activities"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/LearningPortal/Current"; + ViewData["HeaderPathName"] = "Learning Portal"; } @section NavMenuItems { - + } - @if (Model.JavascriptSearchSortFilterPaginateEnabled) +@if (Model.JavascriptSearchSortFilterPaginateEnabled) { - + }
-
-

My Current Activities

- @if (Model.BannerText != null) - { -

@Model.BannerText

- } - - @if (!Model.ApiIsAccessible) - { - - } +
+

My Current Activities

- + @if (!Model.ApiIsAccessible) + { + + } - @if (Model.NoDataFound) - { - - } - else - { -
- - - + -
- @foreach (var currentLearningItem in Model.CurrentActivities) + @if (Model.NoDataFound) { - if (currentLearningItem is SelfAssessmentCardViewModel) - { - - } - else if (currentLearningItem is CurrentLearningResourceViewModel) - { - - } - else - { - - } + + } + else + { +
+ + + + +
+ @foreach (var currentLearningItem in Model.CurrentActivities) + { + if (currentLearningItem is SelfAssessmentCardViewModel) + { + + } + else if (currentLearningItem is CurrentLearningResourceViewModel) + { + + } + else + { + + } + } +
} -
- } - -
+ +
@if (Model.JavascriptSearchSortFilterPaginateEnabled) { - @section scripts { - -} + @section scripts { + + } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml index 346322f8e3..16ad859a89 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/SelfAssessmentDescription.cshtml @@ -29,31 +29,14 @@ { } -@if (Model.IsSupervised) -{ -
-
- @(Html.Raw(Model.Description)) -
-
- -
-
-} -else -{ -
@(Html.Raw(Model.Description))
-} - @if (Model.LinearNavigation) { - View @Model.VocabPlural + View @Model.VocabPlural @if (Model.UserBookmark != null) { if (bookmarkIsRelevant) { - + Continue where I left off } @@ -72,6 +55,19 @@ else Continue where I left off } } - - - +@if (Model.IsSupervised) +{ +
+
+ @(Html.Raw(Model.Description)) +
+
+ +
+
+} +else +{ +
@(Html.Raw(Model.Description))
+} diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml index 7c51b25169..be40c4aa32 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/_OverviewTable.cshtml @@ -49,7 +49,7 @@ model="competency.CompetencyFlags" />
- @if (competency.Description != null && !competency.AlwaysShowDescription) + @if (!string.IsNullOrWhiteSpace(competency.Description) && !competency.AlwaysShowDescription) {
@@ -64,10 +64,10 @@ } else { -

+

@competency.Name

- @if (competency.Description != null) + @if (!string.IsNullOrWhiteSpace(competency.Description)) {

@(Html.Raw(competency.Description)) diff --git a/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml new file mode 100644 index 0000000000..c6ae013148 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Shared/Components/DlsFooterBannerText/Default.cshtml @@ -0,0 +1,16 @@ +@using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +@model DlsFooterBannerTextViewModel +@{ + if (!String.IsNullOrEmpty(Model.BannerText)) + { +

Need help?

+ + } +} + diff --git a/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml b/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml index e6247132a5..ce244973fa 100644 --- a/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml +++ b/DigitalLearningSolutions.Web/Views/Shared/_DlsFooter.cshtml @@ -1,6 +1,7 @@
diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml index b05ac0e229..a5376e461d 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/Shared/_DelegateSelfAssessmentDetails.cshtml @@ -45,14 +45,28 @@
@if (Model.Supervisors.Any()) { - @foreach (var supervisor in Model.Supervisors.Take(3)) - { -

@supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)

- } - @if (Model.Supervisors.Count() > 3) - { -

+@(Model.Supervisors.Count() - 3) more

- } +
+ + @{ + var supervisors = string.Join(", ", new[] { + new { Role = "Educator/Manager", Count = Model.Supervisors.Count(x => x.RoleName == "Educator/Manager") }, + new { Role = "Assessor", Count = Model.Supervisors.Count(x => x.RoleName == "Assessor") }, + new { Role = "Supervisor", Count = Model.Supervisors.Count(x => x.RoleName == "Supervisor") } + } + .Where(x => x.Count > 0) + .Select(x => $"{x.Count} {x.Role}{(x.Count > 1 ? "s" : "")}")); + } + @supervisors + +
+
    + @foreach (var supervisor in Model.Supervisors) + { +
  • @supervisor.SupervisorName, @supervisor.RoleName (@supervisor.CentreName)
  • + } +
+
+
} else { diff --git a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/ViewDelegate/Index.cshtml b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/ViewDelegate/Index.cshtml index 861adb1911..cdefdfc3a3 100644 --- a/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/ViewDelegate/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/TrackingSystem/Delegates/ViewDelegate/Index.cshtml @@ -76,6 +76,14 @@ +
+
+ Last accessed date +
+ + + +
Job group