From 4eaef7aca795fc8c2ee820db7b63c21f474f29d6 Mon Sep 17 00:00:00 2001 From: Sherif Olaboye Date: Tue, 28 Jan 2025 09:58:28 +0000 Subject: [PATCH 01/71] TD-5265 Issue showing 'confirmation requests/confirm self assessments link' when added optional proficiencies are removed --- .../CompetencyDataService.cs | 35 +++++++++++++++++++ .../SelfAssessmentDataService.cs | 6 ++++ .../SelfAssessment.cs | 18 +++++++++- .../Services/SelfAssessmentService.cs | 32 ++++++++++++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs index 845a00b07c..76a1a199bd 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CompetencyDataService.cs @@ -615,6 +615,41 @@ FROM SelfAssessmentResults ); } + public IEnumerable GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ) + { + return connection.Query( + @"SELECT + s.ID, + s.SelfAssessmentID, + s.CompetencyID, + s.AssessmentQuestionID, + s.Result, + s.DateTime, + s.SupportingComments, + s.DelegateUserId + FROM SelfAssessmentResults s inner join + SelfAssessmentResultSupervisorVerifications sv ON s.ID = sv.SelfAssessmentResultId AND sv.Superceded = 0 + WHERE s.CompetencyID = @competencyId + AND s.SelfAssessmentID = @selfAssessmentId + AND s.DelegateUserID = @delegateUserId", + new { selfAssessmentId, delegateUserId, competencyId } + ); + } + + public void RemoveReviewCandidateAssessmentOptionalCompetencies(int id) + { + + connection.Execute(@"UPDATE SelfAssessmentResults SET Result = NULL WHERE ID = @id", new { id}); + + connection.Execute( + @"delete from SelfAssessmentResultSupervisorVerifications WHERE SelfAssessmentResultId = @id", new { id }); + + } + private static string PrintResult( int competencyId, int selfAssessmentId, diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs index 0528c7f907..d4b87c6f78 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs @@ -173,6 +173,12 @@ int GetSelfAssessmentActivityDelegatesExportCount(string searchString, string so bool IsCentreSelfAssessment(int selfAssessmentId, int centreId); bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId); int GetSelfAssessmentCategoryId(int selfAssessmentId); + void RemoveReviewCandidateAssessmentOptionalCompetencies(int id); + public IEnumerable GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ); } public partial class SelfAssessmentDataService : ISelfAssessmentDataService { diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs index cac172a725..2234f7d9eb 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs @@ -1532,6 +1532,22 @@ ManageOptionalCompetenciesViewModel model ); } } + var optionalCompetency = + (selfAssessmentService.GetCandidateAssessmentOptionalCompetencies(selfAssessmentId, delegateUserId)).Where(x => !x.IncludedInSelfAssessment); + if (optionalCompetency.Any()) + { + foreach (var optinal in optionalCompetency) + { + var selfAssessmentResults = selfAssessmentService.GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency(delegateUserId, selfAssessmentId, optinal.Id); + if (selfAssessmentResults.Any()) + { + foreach (var item in selfAssessmentResults) + { + selfAssessmentService.RemoveReviewCandidateAssessmentOptionalCompetencies(item.Id); + } + } + } + } if (model.GroupOptionalCompetenciesChecked != null) { var optionalCompetencies = @@ -1549,7 +1565,7 @@ ManageOptionalCompetenciesViewModel model } } - + return RedirectToAction("SelfAssessmentOverview", new { selfAssessmentId, vocabulary }); } diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs index 37b44e8e34..ce7afea305 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs @@ -150,7 +150,17 @@ public int GetSelfAssessmentActivityDelegatesExportCount(string searchString, st bool IsCentreSelfAssessment(int selfAssessmentId, int centreId); bool HasMinimumOptionalCompetencies(int selfAssessmentId, int delegateUserId); public int GetSelfAssessmentCategoryId(int selfAssessmentId); - + IEnumerable GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ); + public IEnumerable GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ); + void RemoveReviewCandidateAssessmentOptionalCompetencies(int id); } public class SelfAssessmentService : ISelfAssessmentService @@ -575,5 +585,25 @@ public int GetSelfAssessmentCategoryId(int selfAssessmentId) { return selfAssessmentDataService.GetSelfAssessmentCategoryId(selfAssessmentId); } + public IEnumerable GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ) + { + return selfAssessmentDataService.GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency(delegateUserId, selfAssessmentId, competencyId); + } + public IEnumerable GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency( + int delegateUserId, + int selfAssessmentId, + int competencyId + ) + { + return selfAssessmentDataService.GetSelfAssessmentResultswithSupervisorVerificationsForDelegateSelfAssessmentCompetency(delegateUserId, selfAssessmentId, competencyId); + } + public void RemoveReviewCandidateAssessmentOptionalCompetencies(int id) + { + selfAssessmentDataService.RemoveReviewCandidateAssessmentOptionalCompetencies(id); + } } } From 5fd814c1914dea62bc643735fa1233d08f5a574a Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 30 Jan 2025 22:21:52 +0000 Subject: [PATCH 02/71] TD-4169 Corrected wrong AdminID in FrameworkCollaborators Table --- .../DataServices/FrameworkDataService.cs | 8 ++++---- .../Controllers/FrameworksController/Frameworks.cs | 3 ++- DigitalLearningSolutions.Web/Services/FrameworkService.cs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index a8bee31697..58ec21aa8a 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -130,7 +130,7 @@ bool zeroBased int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); - int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify); + int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify, int? centreID); void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); @@ -751,7 +751,7 @@ FROM FrameworkCollaborators fc ); } - public int AddCollaboratorToFramework(int frameworkId, string? userEmail, bool canModify) + public int AddCollaboratorToFramework(int frameworkId, string? userEmail, bool canModify, int? centreID) { if (userEmail is null || userEmail.Length == 0) { @@ -774,8 +774,8 @@ FROM FrameworkCollaborators } var adminId = (int?)connection.ExecuteScalar( - @"SELECT AdminID FROM AdminUsers WHERE Email = @userEmail AND Active = 1", - new { userEmail } + @"SELECT AdminID FROM AdminUsers WHERE Email = @userEmail AND Active = 1 AND CentreID = @centreID", + new { userEmail, centreID } ); if (adminId is null) { diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index bdaa3d4e20..61dd301da7 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -641,7 +641,8 @@ public IActionResult AddCollaborators(string actionname, int frameworkId, bool e [Route("/Frameworks/Collaborators/{actionname}/{frameworkId}/")] public IActionResult AddCollaborator(string actionname, string userEmail, bool canModify, int frameworkId) { - var collaboratorId = frameworkService.AddCollaboratorToFramework(frameworkId, userEmail, canModify); + int? centreID = GetCentreId(); + var collaboratorId = frameworkService.AddCollaboratorToFramework(frameworkId, userEmail, canModify, centreID); if (collaboratorId > 0) { frameworkNotificationService.SendFrameworkCollaboratorInvite(collaboratorId, GetAdminId()); diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 7ddb9b6169..9aa59127dd 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -124,7 +124,7 @@ bool zeroBased int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); - int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify); + int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify, int? centreID); void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); @@ -264,9 +264,9 @@ public FrameworkService(IFrameworkDataService frameworkDataService) this.frameworkDataService = frameworkDataService; } - public int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify) + public int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify, int? centreID) { - return frameworkDataService.AddCollaboratorToFramework(frameworkId, userEmail, canModify); + return frameworkDataService.AddCollaboratorToFramework(frameworkId, userEmail, canModify, centreID); } public void AddCompetencyAssessmentQuestion(int frameworkCompetencyId, int assessmentQuestionId, int adminId) From 3c7168b87621a54be283526f6dc88ea3bdeb7768 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 30 Jan 2025 22:32:13 +0000 Subject: [PATCH 03/71] TD 4169 Run the formatter --- .../Controllers/FrameworksController/Frameworks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index 61dd301da7..7133838e07 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -166,7 +166,7 @@ public IActionResult CreateNewFramework(string actionname, int frameworkId = 0) var sessionNewFramework = multiPageFormService.GetMultiPageFormData( MultiPageFormDataFeature.AddNewFramework, TempData - ).GetAwaiter().GetResult(); + ).GetAwaiter().GetResult(); multiPageFormService.SetMultiPageFormData(sessionNewFramework, MultiPageFormDataFeature.AddNewFramework, TempData); detailFramework = sessionNewFramework.DetailFramework; } @@ -252,7 +252,7 @@ public IActionResult SaveNewFramework(string frameworkname, string actionname) { return StatusCode(500); } - var sessionNewFramework = multiPageFormService.GetMultiPageFormData(MultiPageFormDataFeature.AddNewFramework, TempData).GetAwaiter().GetResult(); + var sessionNewFramework = multiPageFormService.GetMultiPageFormData(MultiPageFormDataFeature.AddNewFramework, TempData).GetAwaiter().GetResult(); multiPageFormService.SetMultiPageFormData( sessionNewFramework, MultiPageFormDataFeature.AddNewFramework, From a0382c0e385fad070b13bc33dce7eb0109ec35d6 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 3 Feb 2025 16:18:34 +0000 Subject: [PATCH 04/71] TD-5329 Moves fwc.isdeleted to join instead of where clause to ensure value is returned --- .../DataServices/FrameworkDataService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 58ec21aa8a..57405d8bc7 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -1823,11 +1823,11 @@ FROM Competencies AS C INNER JOIN public int GetAdminUserRoleForFrameworkId(int adminId, int frameworkId) { - return (int)connection.ExecuteScalar( - @"SELECT CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole - FROM Frameworks AS FW LEFT OUTER JOIN - FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId - WHERE FW.ID = @frameworkId and FWC.IsDeleted=0", + 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 + WHERE (FW.ID = @frameworkId)", new { adminId, frameworkId } ); } From 24625b6e3773060e482b7e13ae921ca0040bb220 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Thu, 6 Feb 2025 08:52:34 +0000 Subject: [PATCH 05/71] TD-5288 Correcting YML files To Point To DOTNET8 --- .github/workflows/build-and-deploy-production.yml | 4 ++-- .github/workflows/build-and-deploy-uat.yml | 4 ++-- .github/workflows/continuous-integration-workflow.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-deploy-production.yml b/.github/workflows/build-and-deploy-production.yml index 38d34a51c2..b1579fb2d1 100644 --- a/.github/workflows/build-and-deploy-production.yml +++ b/.github/workflows/build-and-deploy-production.yml @@ -19,10 +19,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET Core SDK 6.0 + - name: Setup .NET Core SDK 8.0 uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Setup node uses: actions/setup-node@v4 with: diff --git a/.github/workflows/build-and-deploy-uat.yml b/.github/workflows/build-and-deploy-uat.yml index 9a82da00a9..e06b88c727 100644 --- a/.github/workflows/build-and-deploy-uat.yml +++ b/.github/workflows/build-and-deploy-uat.yml @@ -22,10 +22,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET Core SDK 6.0 + - name: Setup .NET Core SDK 8.0 uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Setup node uses: actions/setup-node@v4 with: diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index d0019ac854..3b0ed25380 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET Core SDK 6.0 + - name: Setup .NET Core SDK 8.0 uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Add TechnologyEnhancedLearning as nuget package source run: dotnet nuget add source https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json --name TechnologyEnhancedLearning --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text From feb38a14a88b0afea6fe791e43a766a0a83ed828 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 19 Dec 2024 11:37:16 +0000 Subject: [PATCH 06/71] TD-5153 Implements page changes and starts to implement download actions --- .../Frameworks/Import/BulkCompetency.cs | 19 +++ .../Frameworks/Import/CompetencyTableRow.cs | 15 +- .../FrameworksController.cs | 6 +- .../ImportCompetencies.cs | 16 +++ .../ImportCompetenciesFromFileService.cs | 18 ++- .../Developer/ImportCompetencies.cshtml | 135 +++++++++++------- 6 files changed, 149 insertions(+), 60 deletions(-) create mode 100644 DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs new file mode 100644 index 0000000000..63c66f9049 --- /dev/null +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DigitalLearningSolutions.Data.Models.Frameworks.Import +{ + public class BulkCompetency + { + public int? id { get; set; } + public string? CompetencyGroup { get; set; } + public string? GroupDescription { get; set; } + public string? Competency { get; set; } + public string? CompetencyDescription { get; set; } + public bool? AlwaysShowDescription { get; set; } + public string? FlagsCsv { get; set; } + } +} diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 78e70194ec..eabf53b956 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -12,7 +12,7 @@ public enum RowStatus CompetencyGroupAndCompetencyInserted, CompetencyInserted } - public class CompetencyTableRow + public class CompetencyTableRow : BulkCompetency { public CompetencyTableRow(IXLTable table, IXLRangeRow row) { @@ -23,28 +23,25 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) } RowNumber = row.RowNumber(); - CompetencyGroupName = FindFieldValue("competency group"); - CompetencyName = FindFieldValue("competency name"); + CompetencyGroup = FindFieldValue("competency group"); + Competency = FindFieldValue("competency name"); CompetencyDescription = FindFieldValue("competency description"); RowStatus = RowStatus.NotYetProcessed; } public int RowNumber { get; set; } - public string? CompetencyGroupName { get; set; } - public string? CompetencyName { get; set; } - public string? CompetencyDescription { get; set; } public ImportCompetenciesResult.ErrorReason? Error { get; set; } public RowStatus RowStatus { get; set; } public bool Validate() { - if (string.IsNullOrEmpty(CompetencyName)) + if (string.IsNullOrEmpty(Competency)) { Error = ImportCompetenciesResult.ErrorReason.MissingCompetencyName; } - else if (CompetencyGroupName?.Length > 255) + else if (CompetencyGroup?.Length > 255) { Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName; } - else if (CompetencyName.Length > 500) + else if (Competency.Length > 500) { Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName; } diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs index fd2b21bced..37c212f9e3 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using GDS.MultiPageFormData; + using DigitalLearningSolutions.Data.Utilities; [Authorize(Policy = CustomPolicies.UserFrameworksAdminOnly)] [SetDlsSubApplication(nameof(DlsSubApplication.Frameworks))] @@ -25,6 +26,7 @@ public partial class FrameworksController : Controller private readonly ILearningHubApiClient learningHubApiClient; private readonly ISearchSortFilterPaginateService searchSortFilterPaginateService; private readonly IMultiPageFormService multiPageFormService; + private readonly IClockUtility clockUtility; public FrameworksController( IFrameworkService frameworkService, @@ -35,7 +37,8 @@ public FrameworksController( ICompetencyLearningResourcesService competencyLearningResourcesService, ILearningHubApiClient learningHubApiClient, ISearchSortFilterPaginateService searchSortFilterPaginateService, - IMultiPageFormService multiPageFormService + IMultiPageFormService multiPageFormService, + IClockUtility clockUtility ) { this.frameworkService = frameworkService; @@ -47,6 +50,7 @@ IMultiPageFormService multiPageFormService this.learningHubApiClient = learningHubApiClient; this.searchSortFilterPaginateService = searchSortFilterPaginateService; this.multiPageFormService = multiPageFormService; + this.clockUtility = clockUtility; } private int? GetCentreId() diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 85b6da73fa..71a4a301ca 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,4 +1,7 @@ using DigitalLearningSolutions.Data.Exceptions; +using DigitalLearningSolutions.Data.Utilities; +using DigitalLearningSolutions.Web.Helpers; +using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks; using Microsoft.AspNetCore.Mvc; @@ -19,6 +22,19 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname) }; return View("Developer/ImportCompetencies", model); } + [Route("DownloadDelegates")] + public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) + { + string fileName = DownloadOption == 2 ? $"DLS Competencies for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : "DLS Delegates for Bulk Registration.xlsx"; + var content = importCompetenciesFromFileService.GetCompetencyFileForFramework( + frameworkId, DownloadOption == 2 ? false : true + ); + return File( + content, + FileHelper.GetContentTypeFromFileName(fileName), + fileName + ); + } [HttpPost] [Route("/Framework/{frameworkId}/{tabname}/Import")] public IActionResult StartImport(ImportCompetenciesViewModel model) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index ca2bbb7e03..b2dae72570 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -5,16 +5,19 @@ namespace DigitalLearningSolutions.Web.Services { using System.Collections.Generic; + using System.IO; using System.Linq; using ClosedXML.Excel; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Exceptions; + using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.Frameworks.Import; using Microsoft.AspNetCore.Http; public interface IImportCompetenciesFromFileService { - + byte[] GetCompetencyFileForFramework(int frameworkId, bool v); public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService @@ -115,5 +118,18 @@ private static bool ValidateHeaders(IXLTable table) var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x); return actualHeaders.SequenceEqual(expectedHeaders); } + + public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank) + { + using var workbook = new XLWorkbook(); + PopulateCompetenciesSheet(workbook, frameworkId, blank); + if (blank) + { + ClosedXmlHelper.HideWorkSheetColumn(workbook, "DelegateID"); + } + using var stream = new MemoryStream(); + workbook.SaveAs(stream); + return stream.ToArray(); + } } } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml index 65e65b9838..5525e84144 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml @@ -2,59 +2,96 @@ @using DigitalLearningSolutions.Web.ViewModels.Frameworks @model ImportCompetenciesViewModel @{ - ViewData["Title"] = "Framework - Import Competencies"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Bulk upload competencies" : "Bulk upload competencies"; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -
+
- @if (errorHasOccurred) - { - - } -

Import from Excel

- -

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

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

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

Bulk upload or update competencies

+ +

+ To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below. +

+
+
+ +
+ +

+ What would you like to download? +

+
+
+ Select one option +
+
+
+ + +
+
+
+ This Excel file will be empty.
+ New competencies can be added by including their details on a blank row. +
+ + Download template + +
+
+ + +
+
+
+ This Excel file will include all existing competencies whose details you can update.
+ New competencies can be added by including their details on a blank row. +
+ + Download competencies + +
+
+
+ +
+
+

Upload file

+

+ Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process. +

+
+ + + + + +
From 31a6ee97cd9479d8d9d5b46bb03aa80c78c17959 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 19 Dec 2024 11:48:34 +0000 Subject: [PATCH 07/71] Update FrameworkDataService.cs --- .../DataServices/FrameworkDataService.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 57405d8bc7..7f45181ce8 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -8,6 +8,7 @@ using DigitalLearningSolutions.Data.Models.Common; using DigitalLearningSolutions.Data.Models.Email; using DigitalLearningSolutions.Data.Models.Frameworks; + using DigitalLearningSolutions.Data.Models.Frameworks.Import; using DigitalLearningSolutions.Data.Models.SelfAssessments; using Microsoft.Extensions.Logging; using AssessmentQuestion = DigitalLearningSolutions.Data.Models.Frameworks.AssessmentQuestion; @@ -98,6 +99,7 @@ bool zeroBased ); Competency? GetFrameworkCompetencyForPreview(int frameworkCompetencyId); + IEnumerable GetBulkCompetenciesForFramework(int frameworkId); // Comments: IEnumerable GetCommentsForFrameworkId(int frameworkId, int adminId); @@ -2396,5 +2398,22 @@ FROM FrameworkDefaultQuestions new { competencyId, frameworkId } ); } + + public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) + { + return connection.Query( + @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STRING_AGG(f.FlagName, ', ') AS FlagsCsv + FROM Flags AS f RIGHT OUTER JOIN + CompetencyFlags AS cf ON f.ID = cf.FlagID RIGHT OUTER JOIN + Competencies AS c INNER JOIN + FrameworkCompetencies AS fc ON c.ID = fc.CompetencyID INNER JOIN + FrameworkCompetencyGroups AS fcg ON fc.FrameworkCompetencyGroupID = fcg.ID INNER JOIN + CompetencyGroups AS cg ON fcg.CompetencyGroupID = cg.ID ON cf.CompetencyID = c.ID + WHERE (fc.FrameworkID = @frameworkId) + GROUP BY fc.ID, cg.Name, cg.Description, c.Name, c.Description, c.AlwaysShowDescription, fcg.Ordering, fc.Ordering + ORDER BY fcg.Ordering, fc.Ordering", + new { frameworkId } + ); + } } } From 5c64d2532ce038a41525303aa23f37371f2e2b64 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 20 Dec 2024 15:17:04 +0000 Subject: [PATCH 08/71] TD-5153 Implements download competencies and templates functions and upload action --- .../DataServices/FrameworkDataService.cs | 18 +++-- .../FrameworksController.cs | 7 +- .../ImportCompetencies.cs | 66 +++++++++++++++---- .../Helpers/CommonValidationErrorMessages.cs | 1 + .../Models/BulkCompetenciesData.cs | 31 +++++++++ .../Models/BulkCompetenciesResult.cs | 6 ++ .../Services/FrameworkService.cs | 7 ++ .../ImportCompetenciesFromFileService.cs | 33 ++++++++-- .../ImportCompetenciesPreProcessViewModel.cs | 6 ++ .../Developer/ImportCompetencies.cshtml | 4 +- .../Frameworks/Developer/UploadResults.cshtml | 64 ++++++++++++++++++ 11 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs create mode 100644 DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 7f45181ce8..1663fcc455 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -2401,8 +2401,16 @@ FROM FrameworkDefaultQuestions public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) { - return connection.Query( - @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STRING_AGG(f.FlagName, ', ') AS FlagsCsv + if(frameworkId < 1) + { + return connection.Query( + @"SELECT NULL AS ID, '' AS CompetencyGroup, '' AS GroupDescription, '' AS Competency, '' AS CompetencyDescription, NULL AS AlwaysShowDescription, '' AS FlagsCsv" + ); + } + else + { + return connection.Query( + @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STRING_AGG(f.FlagName, ', ') AS FlagsCsv FROM Flags AS f RIGHT OUTER JOIN CompetencyFlags AS cf ON f.ID = cf.FlagID RIGHT OUTER JOIN Competencies AS c INNER JOIN @@ -2412,8 +2420,10 @@ Competencies AS c INNER JOIN WHERE (fc.FrameworkID = @frameworkId) GROUP BY fc.ID, cg.Name, cg.Description, c.Name, c.Description, c.AlwaysShowDescription, fcg.Ordering, fc.Ordering ORDER BY fcg.Ordering, fc.Ordering", - new { frameworkId } - ); + new { frameworkId } + ); + } + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs index 37c212f9e3..c173233f6c 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/FrameworksController.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using GDS.MultiPageFormData; using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; [Authorize(Policy = CustomPolicies.UserFrameworksAdminOnly)] [SetDlsSubApplication(nameof(DlsSubApplication.Frameworks))] @@ -27,7 +28,7 @@ public partial class FrameworksController : Controller private readonly ISearchSortFilterPaginateService searchSortFilterPaginateService; private readonly IMultiPageFormService multiPageFormService; private readonly IClockUtility clockUtility; - + private readonly IWebHostEnvironment webHostEnvironment; public FrameworksController( IFrameworkService frameworkService, ICommonService commonService, @@ -38,7 +39,8 @@ public FrameworksController( ILearningHubApiClient learningHubApiClient, ISearchSortFilterPaginateService searchSortFilterPaginateService, IMultiPageFormService multiPageFormService, - IClockUtility clockUtility + IClockUtility clockUtility, + IWebHostEnvironment webHostEnvironment ) { this.frameworkService = frameworkService; @@ -51,6 +53,7 @@ IClockUtility clockUtility this.searchSortFilterPaginateService = searchSortFilterPaginateService; this.multiPageFormService = multiPageFormService; this.clockUtility = clockUtility; + this.webHostEnvironment = webHostEnvironment; } private int? GetCentreId() diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 71a4a301ca..d1d8034f09 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,8 +1,13 @@ -using DigitalLearningSolutions.Data.Exceptions; +using ClosedXML.Excel; +using DigitalLearningSolutions.Data.Exceptions; +using DigitalLearningSolutions.Data.Migrations; using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Web.Helpers; +using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks; +using GDS.MultiPageFormData.Enums; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; namespace DigitalLearningSolutions.Web.Controllers.FrameworksController @@ -22,10 +27,9 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname) }; return View("Developer/ImportCompetencies", model); } - [Route("DownloadDelegates")] public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) { - string fileName = DownloadOption == 2 ? $"DLS Competencies for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : "DLS Delegates for Bulk Registration.xlsx"; + string fileName = DownloadOption == 2 ? $"DLS Competencies for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : "DLS Competencies for Bulk Upload.xlsx"; var content = importCompetenciesFromFileService.GetCompetencyFileForFramework( frameworkId, DownloadOption == 2 ? false : true ); @@ -37,24 +41,64 @@ public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) } [HttpPost] [Route("/Framework/{frameworkId}/{tabname}/Import")] - public IActionResult StartImport(ImportCompetenciesViewModel model) + public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname) { if (!ModelState.IsValid) return View("Developer/ImportCompetencies", model); try { - var results = importCompetenciesFromFileService.ProcessCompetenciesFromFile( - model.ImportFile!, - GetAdminId(), - model.FrameworkId - ); - var resultsModel = new ImportCompetenciesResultsViewModel(results); - return View("Developer/ImportCompleted", resultsModel); + var adminUserID = User.GetAdminIdKnownNotNull(); + var workbook = new XLWorkbook(model.ImportFile.OpenReadStream()); + if (!workbook.Worksheets.Contains(ImportCompetenciesFromFileService.CompetenciesSheetName)) + { + ModelState.AddModelError("InvalidWorksheet", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile); + return View("Developer/ImportCompetencies", model); + } + var competenciesFileName = FileHelper.UploadFile(webHostEnvironment, model.ImportFile); + setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname); + + return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId = model.FrameworkId, tabname }); + } + catch (DocumentFormat.OpenXml.Packaging.OpenXmlPackageException) + { + ModelState.AddModelError("DelegatesFile", "The Excel file has at least one cell containing an invalid hyperlink or email address."); + return View("Index", model); } catch (InvalidHeadersException) { return View("Developer/ImportFailed"); } } + [Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")] + public IActionResult ImportCompleted() + { + return View("Developer/ImportCompleted"); + } + + private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName) + { + TempData.Clear(); + multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), TempData); + var today = clockUtility.UtcToday; + var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName); + setBulkUploadData(bulkUploadData); + } + private void setBulkUploadData(BulkCompetenciesData bulkUploadData) + { + multiPageFormService.SetMultiPageFormData( + bulkUploadData, + MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), + TempData + ); + } + + private BulkCompetenciesData GetBulkUploadData() + { + var data = multiPageFormService.GetMultiPageFormData( + MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), + TempData + ).GetAwaiter().GetResult(); + return data; + } } } diff --git a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs index 9cd480a9dd..e76eec613c 100644 --- a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs +++ b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs @@ -36,6 +36,7 @@ public static class CommonValidationErrorMessages public const string CentreNameAlreadyExist = "The centre name you have entered already exists, please enter a different centre name"; public const string MaxBulkUploadRowsLimit = "File must contain no more than {0} rows"; public const string InvalidBulkUploadExcelFile = "The uploaded file must contain a \"DelegatesBulkUpload\" worksheet. Use the \"Download delegates\" button to generate a template."; + public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download competencies\" button to generate a template."; public const string ReportFilterReturnsTooManyRows = "The report frequency is too high for the date range. Choose a lower report frequency (or shorten the date range)"; } } diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs new file mode 100644 index 0000000000..f81e85f952 --- /dev/null +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; + +namespace DigitalLearningSolutions.Web.Models +{ + public class BulkCompetenciesData + { + public BulkCompetenciesData() { } + public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName) + { + FrameworkId = frameworkId; + AdminUserId = adminUserId; + CompetenciesFileName = competenciesFileName; + TabName = tabName; + } + public int FrameworkId { get; set; } + public string TabName { get; set; } + public int AdminUserId { get; set; } + public string CompetenciesFileName { get; set; } + public List AssessmentQuestionIDs { get; set; } + public int? AddAssessmentQuestionOption { get; set; } + public int ToProcessCount { get; set; } + public int ToAddCount { get; set; } + public int ToUpdateCount { get; set; } + public int LastRowProcessed { get; set; } + public int SubtotalCompetenciesAdded { get; set; } + public int SubtotalCompetenciesUpdated { get; set; } + public int SubTotalSkipped { get; set; } + public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } = Enumerable.Empty<(int, string)>(); + } +} diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs new file mode 100644 index 0000000000..bc8b12ecdd --- /dev/null +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs @@ -0,0 +1,6 @@ +namespace DigitalLearningSolutions.Web.Models +{ + public class BulkCompetenciesResult + { + } +} diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 9aa59127dd..2a49a0a8b3 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -2,6 +2,7 @@ using DigitalLearningSolutions.Data.Models.Common; using DigitalLearningSolutions.Data.Models.Email; using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; using DigitalLearningSolutions.Data.Models.SelfAssessments; using System.Collections.Generic; using AssessmentQuestion = DigitalLearningSolutions.Data.Models.Frameworks.AssessmentQuestion; @@ -55,6 +56,7 @@ public interface IFrameworkService int GetMaxFrameworkCompetencyID(); int GetMaxFrameworkCompetencyGroupID(); + IEnumerable GetBulkCompetenciesForFramework(int frameworkId); // Assessment questions: IEnumerable GetAllCompetencyQuestions(int adminId); @@ -379,6 +381,11 @@ public IEnumerable GetAssessmentQuestionsForCompetency(int fr return frameworkDataService.GetBrandedFrameworkByFrameworkId(frameworkId, adminId); } + public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) + { + return frameworkDataService.GetBulkCompetenciesForFramework(frameworkId); + } + public CollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId) { return frameworkDataService.GetCollaboratorNotification(id, invitedByAdminId); diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index b2dae72570..0ba48b08d7 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -4,14 +4,13 @@ namespace DigitalLearningSolutions.Web.Services { + using System; using System.Collections.Generic; using System.IO; using System.Linq; using ClosedXML.Excel; - using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Helpers; - using DigitalLearningSolutions.Data.Models.Centres; using DigitalLearningSolutions.Data.Models.Frameworks.Import; using Microsoft.AspNetCore.Http; @@ -23,6 +22,8 @@ public interface IImportCompetenciesFromFileService public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService { private readonly IFrameworkService frameworkService; + private static readonly XLTableTheme TableTheme = XLTableTheme.TableStyleLight9; + public const string CompetenciesSheetName = "CompetenciesBulkUpload"; public ImportCompetenciesFromFileService( IFrameworkService frameworkService ) @@ -76,9 +77,9 @@ CompetencyTableRow competencyRow } //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID int? frameworkCompetencyGroupId = null; - if (competencyRow.CompetencyGroupName != null) + if (competencyRow.CompetencyGroup != null) { - var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroupName, null, adminUserId); + var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, null, adminUserId); if (newCompetencyGroupId > 0) { frameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminUserId); @@ -91,7 +92,7 @@ CompetencyTableRow competencyRow } //Check if competency already exists in framework competency group and add if not - var newCompetencyId = frameworkService.InsertCompetency(competencyRow.CompetencyName, competencyRow.CompetencyDescription, adminUserId); + var newCompetencyId = frameworkService.InsertCompetency(competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId); if (newCompetencyId > 0) { var newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId); @@ -125,11 +126,31 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank) PopulateCompetenciesSheet(workbook, frameworkId, blank); if (blank) { - ClosedXmlHelper.HideWorkSheetColumn(workbook, "DelegateID"); + ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID"); } using var stream = new MemoryStream(); workbook.SaveAs(stream); return stream.ToArray(); } + private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bool blank) + { + + + var competencyRecords = frameworkService.GetBulkCompetenciesForFramework(blank ? 0 : frameworkId); + var competencies = competencyRecords.Select( + x => new + { + ID = x.id, + x.CompetencyGroup, + x.GroupDescription, + x.Competency, + x.CompetencyDescription, + x.AlwaysShowDescription, + FlagsCSV = x.FlagsCsv, + } + ); + + ClosedXmlHelper.AddSheetToWorkbook(workbook, CompetenciesSheetName, competencies, TableTheme); + } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs new file mode 100644 index 0000000000..c7ae81ca88 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -0,0 +1,6 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +{ + public class ImportCompetenciesPreProcessViewModel + { + } +} diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml index 5525e84144..9eede68e22 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml @@ -59,7 +59,7 @@ This Excel file will be empty.
New competencies can be added by including their details on a blank row. - + Download template @@ -74,7 +74,7 @@ This Excel file will include all existing competencies whose details you can update.
New competencies can be added by including their details on a blank row. - + Download competencies diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml new file mode 100644 index 0000000000..fefffeae4f --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml @@ -0,0 +1,64 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks +@using Microsoft.Extensions.Configuration +@model ImportCompetenciesResultsViewModel +@{ + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} + @section NavBreadcrumbs { + +} + +
+
+

Import competencies complete

+ +

Summary of results:

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

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

+

The lines below were skipped due to errors during processing:

+
    + @foreach (var (rowNumber, errorMessage) in Model.Errors) + { +
  • Line @rowNumber: @errorMessage
  • + } +
+
+ } + + +
+
From c2b35fd5ff488aa77ccde3be2a6d2a5bfeaafae9 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 20 Dec 2024 15:18:35 +0000 Subject: [PATCH 09/71] Update ImportCompleted.cshtml --- .../Developer/ImportCompleted.cshtml | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml index fefffeae4f..f0b1627510 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml @@ -2,7 +2,7 @@ @using DigitalLearningSolutions.Web.Extensions @using DigitalLearningSolutions.Web.ViewModels.Frameworks @using Microsoft.Extensions.Configuration -@model ImportCompetenciesResultsViewModel +@model ImportCompetenciesPreProcessViewModel @{ ViewData["Title"] = "Framework - Import Competencies"; ViewData["Application"] = "Framework Service"; @@ -26,39 +26,3 @@ } - -
-
-

Import competencies complete

- -

Summary of results:

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

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

-

The lines below were skipped due to errors during processing:

-
    - @foreach (var (rowNumber, errorMessage) in Model.Errors) - { -
  • Line @rowNumber: @errorMessage
  • - } -
-
- } - - -
-
From 393ceb1c6e9189aec60090a81a40cf59cff9e9e8 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 20 Dec 2024 16:26:50 +0000 Subject: [PATCH 10/71] Implements models for handling upload --- .../Frameworks/Import/CompetencyTableRow.cs | 19 ++++---- .../Frameworks/FrameworkControllerTests.cs | 11 ++++- .../RedirectToErrorEmptySessionDataTests.cs | 6 ++- .../Models/BulkCompetenciesResult.cs | 36 +++++++++++++++- .../ImportCompetenciesPreProcessViewModel.cs | 43 ++++++++++++++++++- 5 files changed, 102 insertions(+), 13 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index eabf53b956..bace71a8c4 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -1,16 +1,16 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { using ClosedXML.Excel; - using System; - using System.Collections.Generic; - using System.Text; public enum RowStatus { NotYetProcessed, Skipped, - CompetencyGroupInserted, CompetencyGroupAndCompetencyInserted, - CompetencyInserted + CompetencyInserted, + CompetencyUpdated, + CompetencyGroupInserted, + CompetencyGroupUpdated, + CompetencyGroupAndCompetencyUpdated } public class CompetencyTableRow : BulkCompetency { @@ -23,9 +23,12 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) } RowNumber = row.RowNumber(); - CompetencyGroup = FindFieldValue("competency group"); - Competency = FindFieldValue("competency name"); - CompetencyDescription = FindFieldValue("competency description"); + id = int.Parse(FindFieldValue("ID")); + CompetencyGroup = FindFieldValue("CompetencyGroup"); + Competency = FindFieldValue("Competency"); + CompetencyDescription = FindFieldValue("CompetencyDescription"); + GroupDescription = FindFieldValue("CompetencyGroupDescription"); + FlagsCsv = FindFieldValue("FlagsCSV"); RowStatus = RowStatus.NotYetProcessed; } public int RowNumber { get; set; } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs index beeeb66bdb..ef0e2c3224 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/Frameworks/FrameworkControllerTests.cs @@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging; using NUnit.Framework; using GDS.MultiPageFormData; + using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; public partial class FrameworkControllerTests { @@ -28,6 +30,8 @@ public partial class FrameworkControllerTests private ILearningHubApiClient learningHubApiClient = null!; private ISearchSortFilterPaginateService searchSortFilterPaginateService = null!; private IMultiPageFormService multiPageFormService = null!; + private IClockUtility clockUtility = null!; + private IWebHostEnvironment webHostEnvironment = null!; [SetUp] public void SetUp() @@ -42,7 +46,8 @@ public void SetUp() learningHubApiClient = A.Fake(); searchSortFilterPaginateService = A.Fake(); multiPageFormService = A.Fake(); - + clockUtility = A.Fake(); + webHostEnvironment = A.Fake(); A.CallTo(() => config["CurrentSystemBaseUrl"]).Returns(BaseUrl); var user = new ClaimsPrincipal( @@ -65,7 +70,9 @@ public void SetUp() competencyLearningResourcesService, learningHubApiClient, searchSortFilterPaginateService, - multiPageFormService + multiPageFormService, + clockUtility, + webHostEnvironment ) { ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = user } }, diff --git a/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs b/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs index 4bf31af462..057f143bc6 100644 --- a/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs +++ b/DigitalLearningSolutions.Web.Tests/ServiceFilter/RedirectToErrorEmptySessionDataTests.cs @@ -22,6 +22,8 @@ using NUnit.Framework; using GDS.MultiPageFormData; using GDS.MultiPageFormData.Enums; + using DigitalLearningSolutions.Data.Utilities; + using Microsoft.AspNetCore.Hosting; public class RedirectToErrorEmptySessionDataTests { @@ -41,7 +43,9 @@ public void Setup() A.Fake(), A.Fake(), A.Fake(), - A.Fake() + A.Fake(), + A.Fake(), + A.Fake() ) .WithDefaultContext().WithMockTempData(); diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs index bc8b12ecdd..e770ac39b7 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs @@ -1,6 +1,40 @@ -namespace DigitalLearningSolutions.Web.Models +using DigitalLearningSolutions.Data.Models.Frameworks.Import; +using System.Collections.Generic; +using System.Linq; + +namespace DigitalLearningSolutions.Web.Models { public class BulkCompetenciesResult { + public enum ErrorReason + { + InvalidId, + MissingCompetencyName, + TooLongCompetencyName, + TooLongCompetencyGroupName, + InvalidAlwaysShowDescription + } + + public BulkCompetenciesResult() { } + + public BulkCompetenciesResult( + IReadOnlyCollection competencyRows + ) + { + ProcessedCount = competencyRows.Count; + CompetencyAddedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyInserted || cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + GroupAddedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + GroupUpdatedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyGroupUpdated || cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated); + SkippedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.Skipped); + Errors = (IEnumerable<(int RowNumber, ErrorReason Reason)>)competencyRows.Where(cr => cr.Error.HasValue).Select(cr => (cr.RowNumber, cr.Error!.Value)); + } + + public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; } + public int ProcessedCount { get; set; } + public int CompetencyAddedCount { get; set; } + public int CompetencyUpdatedCount { get; set; } + public int GroupAddedCount { get; set; } + public int GroupUpdatedCount { get; set; } + public int SkippedCount { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs index c7ae81ca88..5817ecf0c6 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -1,6 +1,47 @@ -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +using System.Collections.Generic; +using System; +using DigitalLearningSolutions.Web.Models; +using System.Linq; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks { public class ImportCompetenciesPreProcessViewModel { + public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompetenciesResult) + { + ToProcessCount = bulkCompetenciesResult.ProcessedCount; + CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; + CompetenciesToUpdateCount = bulkCompetenciesResult.CompetencyUpdatedCount; + CompetencyGroupsToAddCount = bulkCompetenciesResult.GroupAddedCount; + CompetencyGroupsToUpdateCount = bulkCompetenciesResult.GroupUpdatedCount; + Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); + } + + public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } + public int ErrorCount => Errors.Count(); + public int ToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToUpdateCount { get; set; } + public int CompetencyGroupsToAddCount { get; set; } + public int CompetencyGroupsToUpdateCount { get; set; } + public int ToUpdateOrSkipCount { get; set; } + + private static string MapReasonToErrorMessage(BulkCompetenciesResult.ErrorReason reason) + { + return reason switch + { + BulkCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => + "Group name must be 255 characters or less.", + BulkCompetenciesResult.ErrorReason.MissingCompetencyName => + "Competency is blank. Competency is a required field and cannot be left blank", + BulkCompetenciesResult.ErrorReason.InvalidId => + "The ID provided does not match a Competency ID in this Framework", + BulkCompetenciesResult.ErrorReason.TooLongCompetencyName => + "Competency must be 255 characters or less.", + BulkCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => + "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", + _ => "Unspecified error.", + }; + } } } From 319e4bf909768f7f85f802c76864cdb4b6389b89 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 20 Dec 2024 16:31:07 +0000 Subject: [PATCH 11/71] Formatter changes --- .../DataServices/FrameworkDataService.cs | 4 +-- .../Frameworks/Import/BulkCompetency.cs | 2 +- .../Models/BulkCompetenciesData.cs | 2 +- .../ImportCompetenciesPreProcessViewModel.cs | 2 +- .../Developer/ImportCompleted.cshtml | 34 +++++++++---------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 1663fcc455..068da4ceec 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -2401,7 +2401,7 @@ FROM FrameworkDefaultQuestions public IEnumerable GetBulkCompetenciesForFramework(int frameworkId) { - if(frameworkId < 1) + if (frameworkId < 1) { return connection.Query( @"SELECT NULL AS ID, '' AS CompetencyGroup, '' AS GroupDescription, '' AS Competency, '' AS CompetencyDescription, NULL AS AlwaysShowDescription, '' AS FlagsCsv" @@ -2423,7 +2423,7 @@ Competencies AS c INNER JOIN new { frameworkId } ); } - + } } } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs index 63c66f9049..298639d4c7 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs @@ -8,7 +8,7 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { public class BulkCompetency { - public int? id { get; set; } + public int? id { get; set; } public string? CompetencyGroup { get; set; } public string? GroupDescription { get; set; } public string? Competency { get; set; } diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index f81e85f952..173b1d0a52 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -11,7 +11,7 @@ public BulkCompetenciesData(int frameworkId, int adminUserId, string competencie FrameworkId = frameworkId; AdminUserId = adminUserId; CompetenciesFileName = competenciesFileName; - TabName = tabName; + TabName = tabName; } public int FrameworkId { get; set; } public string TabName { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs index 5817ecf0c6..45f72c2bca 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -25,7 +25,7 @@ public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompeten public int CompetencyGroupsToAddCount { get; set; } public int CompetencyGroupsToUpdateCount { get; set; } public int ToUpdateOrSkipCount { get; set; } - + private static string MapReasonToErrorMessage(BulkCompetenciesResult.ErrorReason reason) { return reason switch diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml index f0b1627510..716575afc8 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml @@ -4,25 +4,25 @@ @using Microsoft.Extensions.Configuration @model ImportCompetenciesPreProcessViewModel @{ - ViewData["Title"] = "Framework - Import Competencies"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } From 13e025f4435abcb865a3d95352807da72aa1c3e1 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 20 Dec 2024 16:49:08 +0000 Subject: [PATCH 12/71] Fixes form input id --- .../Views/Frameworks/Developer/ImportCompetencies.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml index 9eede68e22..c91ec018ca 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml @@ -49,7 +49,7 @@
- + From c8310e8b64415cd89006d6fa638246252a52c589 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 23 Dec 2024 12:21:13 +0000 Subject: [PATCH 13/71] TD-5154 Implements basic pre-process logic and view and adds validation to exported sheet --- .../Frameworks/Import/CompetencyTableRow.cs | 8 +- .../Import/ImportCompetenciesResult.cs | 15 ++- .../ImportCompetencies.cs | 29 ++++- .../Models/BulkCompetenciesData.cs | 6 +- .../Models/BulkCompetenciesResult.cs | 40 ------- .../ImportCompetenciesFromFileService.cs | 52 +++++++-- .../ImportCompetenciesPreProcessViewModel.cs | 16 +-- .../ImportCompetenciesResultsViewModel.cs | 4 +- .../Frameworks/ImportCompetenciesViewModel.cs | 1 + .../Developer/ImportCompetencies.cshtml | 105 ++++++++++-------- .../Developer/ImportCompleted.cshtml | 53 +++++++++ .../Frameworks/Developer/_Structure.cshtml | 2 +- 12 files changed, 208 insertions(+), 123 deletions(-) delete mode 100644 DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index bace71a8c4..374e016334 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -1,6 +1,8 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { using ClosedXML.Excel; + using Org.BouncyCastle.Asn1.X509; + public enum RowStatus { NotYetProcessed, @@ -18,16 +20,16 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) { string? FindFieldValue(string name) { - var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name).ColumnNumber(); + var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name.ToLower()).ColumnNumber(); return row.Cell(colNumber).GetValue(); } RowNumber = row.RowNumber(); - id = int.Parse(FindFieldValue("ID")); + id = row.Cell(1).GetValue(); CompetencyGroup = FindFieldValue("CompetencyGroup"); Competency = FindFieldValue("Competency"); CompetencyDescription = FindFieldValue("CompetencyDescription"); - GroupDescription = FindFieldValue("CompetencyGroupDescription"); + GroupDescription = FindFieldValue("GroupDescription"); FlagsCsv = FindFieldValue("FlagsCSV"); RowStatus = RowStatus.NotYetProcessed; } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 7f78d6f883..3c2bc8e287 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -8,10 +8,11 @@ public class ImportCompetenciesResult { public enum ErrorReason { + InvalidId, MissingCompetencyName, - TooLongCompetencyGroupName, TooLongCompetencyName, - AlreadyExists + TooLongCompetencyGroupName, + InvalidAlwaysShowDescription } public ImportCompetenciesResult() { } @@ -20,16 +21,18 @@ IReadOnlyCollection competencyTableRows ) { ProcessedCount = competencyTableRows.Count; - CompetenciesInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - CompetencyGroupsInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); } public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; } public int ProcessedCount { get; set; } - public int CompetenciesInsertedCount { get; set; } - public int CompetencyGroupsInsertedCount { get; set; } + public int CompetencyAddedCount { get; set; } + public int CompetencyUpdatedCount { get; set; } + public int GroupAddedCount { get; set; } + public int GroupUpdatedCount { get; set; } public int SkippedCount { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index d1d8034f09..5fd03a7a09 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,21 +1,19 @@ using ClosedXML.Excel; using DigitalLearningSolutions.Data.Exceptions; -using DigitalLearningSolutions.Data.Migrations; -using DigitalLearningSolutions.Data.Utilities; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks; using GDS.MultiPageFormData.Enums; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using System.IO; namespace DigitalLearningSolutions.Web.Controllers.FrameworksController { public partial class FrameworksController { [Route("/Framework/{frameworkId}/{tabname}/Import")] - public IActionResult ImportCompetencies(int frameworkId, string tabname) + public IActionResult ImportCompetencies(int frameworkId, string tabname, bool isNotBlank) { var adminId = GetAdminId(); var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId); @@ -23,7 +21,8 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname) return StatusCode(403); var model = new ImportCompetenciesViewModel() { - FrameworkId = frameworkId + FrameworkId = frameworkId, + IsNotBlank = isNotBlank }; return View("Developer/ImportCompetencies", model); } @@ -72,7 +71,25 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna [Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")] public IActionResult ImportCompleted() { - return View("Developer/ImportCompleted"); + var data = GetBulkUploadData(); + var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\"); + var filePath = Path.Combine(uploadDir, data.CompetenciesFileName); + var workbook = new XLWorkbook(filePath); + try + { + var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook); + var resultsModel = new ImportCompetenciesPreProcessViewModel(results); + data.CompetenciesToProcessCount = resultsModel.ToProcessCount; + data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; + data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount; + setBulkUploadData(data); + return View("Developer/ImportCompleted", resultsModel); + } + catch (InvalidHeadersException) + { + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + return View("ImportFailed"); + } } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName) diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 173b1d0a52..e853675c9a 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -19,9 +19,9 @@ public BulkCompetenciesData(int frameworkId, int adminUserId, string competencie public string CompetenciesFileName { get; set; } public List AssessmentQuestionIDs { get; set; } public int? AddAssessmentQuestionOption { get; set; } - public int ToProcessCount { get; set; } - public int ToAddCount { get; set; } - public int ToUpdateCount { get; set; } + public int CompetenciesToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToUpdateCount { get; set; } public int LastRowProcessed { get; set; } public int SubtotalCompetenciesAdded { get; set; } public int SubtotalCompetenciesUpdated { get; set; } diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs deleted file mode 100644 index e770ac39b7..0000000000 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -using DigitalLearningSolutions.Data.Models.Frameworks.Import; -using System.Collections.Generic; -using System.Linq; - -namespace DigitalLearningSolutions.Web.Models -{ - public class BulkCompetenciesResult - { - public enum ErrorReason - { - InvalidId, - MissingCompetencyName, - TooLongCompetencyName, - TooLongCompetencyGroupName, - InvalidAlwaysShowDescription - } - - public BulkCompetenciesResult() { } - - public BulkCompetenciesResult( - IReadOnlyCollection competencyRows - ) - { - ProcessedCount = competencyRows.Count; - CompetencyAddedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyInserted || cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - GroupAddedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - GroupUpdatedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.CompetencyGroupUpdated || cr.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated); - SkippedCount = competencyRows.Count(cr => cr.RowStatus == RowStatus.Skipped); - Errors = (IEnumerable<(int RowNumber, ErrorReason Reason)>)competencyRows.Where(cr => cr.Error.HasValue).Select(cr => (cr.RowNumber, cr.Error!.Value)); - } - - public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; } - public int ProcessedCount { get; set; } - public int CompetencyAddedCount { get; set; } - public int CompetencyUpdatedCount { get; set; } - public int GroupAddedCount { get; set; } - public int GroupUpdatedCount { get; set; } - public int SkippedCount { get; set; } - } -} diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 0ba48b08d7..d4ffd9a37e 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -12,12 +12,14 @@ namespace DigitalLearningSolutions.Web.Services using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Frameworks.Import; + using DigitalLearningSolutions.Web.Models; using Microsoft.AspNetCore.Http; public interface IImportCompetenciesFromFileService { byte[] GetCompetencyFileForFramework(int frameworkId, bool v); - public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId); + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook); + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService { @@ -30,17 +32,38 @@ IFrameworkService frameworkService { this.frameworkService = frameworkService; } - public ImportCompetenciesResult ProcessCompetenciesFromFile(IFormFile file, int adminUserId, int frameworkId) + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook) + { + var table = OpenCompetenciesTable(workbook); + var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); + foreach (var competencyRow in competencyRows) + { + PreProcessCompetencyRow(competencyRow); + } + return new ImportCompetenciesResult(competencyRows); + } + private void PreProcessCompetencyRow(CompetencyTableRow competencyRow) + { + if (competencyRow.id == null) + { + competencyRow.RowStatus = RowStatus.CompetencyInserted; + } + else + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } + } + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId) { int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID(); int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID(); - var table = OpenCompetenciesTable(file); + var table = OpenCompetenciesTable(workbook); return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId); } - internal IXLTable OpenCompetenciesTable(IFormFile file) + internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook) { - var workbook = new XLWorkbook(file.OpenReadStream()); var worksheet = workbook.Worksheet(1); + worksheet.Columns(1, 15).Unhide(); if (worksheet.Tables.Count() == 0) { throw new InvalidHeadersException(); @@ -112,11 +135,15 @@ private static bool ValidateHeaders(IXLTable table) { var expectedHeaders = new List { - "competency group", - "competency name", - "competency description" + "ID", + "CompetencyGroup", + "GroupDescription", + "Competency", + "CompetencyDescription", + "AlwaysShowDescription", + "FlagsCSV" }.OrderBy(x => x); - var actualHeaders = table.Fields.Select(x => x.Name.ToLower()).OrderBy(x => x); + var actualHeaders = table.Fields.Select(x => x.Name).OrderBy(x => x); return actualHeaders.SequenceEqual(expectedHeaders); } @@ -128,6 +155,13 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank) { ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID"); } + var options = new List { "TRUE", "FALSE" }; + ClosedXmlHelper.AddValidationListToWorksheetColumn(workbook, 6, options); + var rowCount = workbook.Worksheet(1).RangeUsed().RowCount(); + ClosedXmlHelper.AddValidationRangeToWorksheetColumn(workbook, 1, 1, rowCount, 1); + // Calculate the workbook + workbook.CalculateMode = XLCalculateMode.Auto; + workbook.RecalculateAllFormulas(); using var stream = new MemoryStream(); workbook.SaveAs(stream); return stream.ToArray(); diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs index 45f72c2bca..8690f31013 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -2,12 +2,13 @@ using System; using DigitalLearningSolutions.Web.Models; using System.Linq; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; namespace DigitalLearningSolutions.Web.ViewModels.Frameworks { public class ImportCompetenciesPreProcessViewModel { - public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompetenciesResult) + public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompetenciesResult) { ToProcessCount = bulkCompetenciesResult.ProcessedCount; CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; @@ -25,20 +26,21 @@ public ImportCompetenciesPreProcessViewModel(BulkCompetenciesResult bulkCompeten public int CompetencyGroupsToAddCount { get; set; } public int CompetencyGroupsToUpdateCount { get; set; } public int ToUpdateOrSkipCount { get; set; } + public string? CompetenciesFileName { get; set; } - private static string MapReasonToErrorMessage(BulkCompetenciesResult.ErrorReason reason) + private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason) { return reason switch { - BulkCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => + ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => "Group name must be 255 characters or less.", - BulkCompetenciesResult.ErrorReason.MissingCompetencyName => + ImportCompetenciesResult.ErrorReason.MissingCompetencyName => "Competency is blank. Competency is a required field and cannot be left blank", - BulkCompetenciesResult.ErrorReason.InvalidId => + ImportCompetenciesResult.ErrorReason.InvalidId => "The ID provided does not match a Competency ID in this Framework", - BulkCompetenciesResult.ErrorReason.TooLongCompetencyName => + ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => "Competency must be 255 characters or less.", - BulkCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => + ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", _ => "Unspecified error.", }; diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs index 8267146c71..d76b0338ae 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs @@ -9,8 +9,8 @@ public class ImportCompetenciesResultsViewModel public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult) { ProcessedCount = importCompetenciesResult.ProcessedCount; - CompetenciesInsertedCount = importCompetenciesResult.CompetenciesInsertedCount; - CompetencyGroupsInsertedCount = importCompetenciesResult.CompetencyGroupsInsertedCount; + CompetenciesInsertedCount = importCompetenciesResult.CompetencyAddedCount; + CompetencyGroupsInsertedCount = importCompetenciesResult.GroupAddedCount; SkippedCount = importCompetenciesResult.SkippedCount; Errors = importCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs index eb44330a20..27fa9f38b6 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs @@ -7,6 +7,7 @@ public class ImportCompetenciesViewModel { public int FrameworkId { get; set; } + public bool IsNotBlank { get; set; } [Required(ErrorMessage = "Import competencies file is required")] [AllowedExtensions(new[] { ".xlsx" }, "Import competencies file must be in xlsx format")] [MaxFileSize(5 * 1024 * 1024, "Maximum allowed file size is 5MB")] diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml index c91ec018ca..90935a7f1c 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml @@ -30,59 +30,72 @@ { } -

Bulk upload or update competencies

+

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

-

- To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below. -

-
-
- -
- -

- What would you like to download? -

-
-
- Select one option -
-
-
- - +
+ @if(Model.IsNotBlank) + { + +

+ To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below. +

+ +
+ +

+ What would you like to download? +

+
+
+ Select one option
-
-
- This Excel file will be empty.
- New competencies can be added by including their details on a blank row. +
+
+ +
- - Download template - -
-
- - -
-
-
- This Excel file will include all existing competencies whose details you can update.
- New competencies can be added by including their details on a blank row. +
+
+ This Excel file will be empty.
+ New competencies can be added by including their details on a blank row. +
+ + Download template + +
+
+ + +
+
+
+ This Excel file will include all existing competencies whose details you can update.
+ New competencies can be added by including their details on a blank row. +
+ + Download competencies +
- - Download competencies -
-
-
+
+ + } + else + { +

+ Download a blank template for bulk adding competencies to your framework. This Excel file will be empty. New competencies can be added by including their details on a blank row. +

+ + Download template + + }
- +

Upload file

Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process. diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml index 716575afc8..e943966a5d 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml @@ -26,3 +26,56 @@

} +

Delegate file uploaded

+

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

+
    +
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • +
  • @Model.CompetencyGroupsToAddCount new @(Model.CompetencyGroupsToAddCount == 1 ? "competency" : "competencies") to register
  • +
  • @Model.ToUpdateOrSkipCount delegate @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • + @if (Model.ErrorCount > 0) + { +
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • + } + else + { +
  • No errors
  • + } +
+@if (Model.ErrorCount == 0) +{ + Continue +} +else +{ +

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

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

Upload corrected file

+

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

+
+ + + + + +} + diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml index 7fa159981d..a1097f6e18 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml @@ -81,7 +81,7 @@ else
} From 293431b5bc0eaeb523dacc3a1028259a17a2f823 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 23 Dec 2024 16:16:20 +0000 Subject: [PATCH 14/71] TD-5154 Implements upload results page with validation errors --- .../FrameworksController/ImportCompetencies.cs | 13 +++++++------ .../Helpers/CommonValidationErrorMessages.cs | 2 +- .../Models/BulkCompetenciesData.cs | 4 +++- .../Services/ImportCompetenciesFromFileService.cs | 1 + .../ImportCompetenciesPreProcessViewModel.cs | 4 +++- .../Frameworks/Developer/ImportCompleted.cshtml | 8 +++++--- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 5fd03a7a09..d713235f4f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -40,7 +40,8 @@ public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) } [HttpPost] [Route("/Framework/{frameworkId}/{tabname}/Import")] - public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname) + [Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")] + public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname, bool isNotBlank) { if (!ModelState.IsValid) return View("Developer/ImportCompetencies", model); @@ -50,11 +51,11 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna var workbook = new XLWorkbook(model.ImportFile.OpenReadStream()); if (!workbook.Worksheets.Contains(ImportCompetenciesFromFileService.CompetenciesSheetName)) { - ModelState.AddModelError("InvalidWorksheet", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile); + ModelState.AddModelError("ImportFile", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile); return View("Developer/ImportCompetencies", model); } var competenciesFileName = FileHelper.UploadFile(webHostEnvironment, model.ImportFile); - setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname); + setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname, isNotBlank); return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId = model.FrameworkId, tabname }); } @@ -78,7 +79,7 @@ public IActionResult ImportCompleted() try { var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook); - var resultsModel = new ImportCompetenciesPreProcessViewModel(results); + var resultsModel = new ImportCompetenciesPreProcessViewModel(results) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount; @@ -92,12 +93,12 @@ public IActionResult ImportCompleted() } } - private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName) + private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { TempData.Clear(); multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), TempData); var today = clockUtility.UtcToday; - var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName); + var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName, isNotBlank); setBulkUploadData(bulkUploadData); } private void setBulkUploadData(BulkCompetenciesData bulkUploadData) diff --git a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs index e76eec613c..531a528e98 100644 --- a/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs +++ b/DigitalLearningSolutions.Web/Helpers/CommonValidationErrorMessages.cs @@ -36,7 +36,7 @@ public static class CommonValidationErrorMessages public const string CentreNameAlreadyExist = "The centre name you have entered already exists, please enter a different centre name"; public const string MaxBulkUploadRowsLimit = "File must contain no more than {0} rows"; public const string InvalidBulkUploadExcelFile = "The uploaded file must contain a \"DelegatesBulkUpload\" worksheet. Use the \"Download delegates\" button to generate a template."; - public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download competencies\" button to generate a template."; + public const string InvalidCompetenciesUploadExcelFile = "The uploaded file must contain a \"CompetenciesBulkUpload\" worksheet. Use the \"Download template\" button to generate a template."; public const string ReportFilterReturnsTooManyRows = "The report frequency is too high for the date range. Choose a lower report frequency (or shorten the date range)"; } } diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index e853675c9a..290e263477 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -6,16 +6,18 @@ namespace DigitalLearningSolutions.Web.Models public class BulkCompetenciesData { public BulkCompetenciesData() { } - public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName) + public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName, bool isNotBlank) { FrameworkId = frameworkId; AdminUserId = adminUserId; CompetenciesFileName = competenciesFileName; TabName = tabName; + IsNotBlank = isNotBlank; } public int FrameworkId { get; set; } public string TabName { get; set; } public int AdminUserId { get; set; } + public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } public List AssessmentQuestionIDs { get; set; } public int? AddAssessmentQuestionOption { get; set; } diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index d4ffd9a37e..1746ebe904 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -44,6 +44,7 @@ public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook } private void PreProcessCompetencyRow(CompetencyTableRow competencyRow) { + competencyRow.Validate(); if (competencyRow.id == null) { competencyRow.RowStatus = RowStatus.CompetencyInserted; diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs index 8690f31013..6d96699586 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -26,7 +26,9 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet public int CompetencyGroupsToAddCount { get; set; } public int CompetencyGroupsToUpdateCount { get; set; } public int ToUpdateOrSkipCount { get; set; } - public string? CompetenciesFileName { get; set; } + public string? ImportFile { get; set; } + public bool IsNotBlank { get; set; } + public string TabName { get; set; } private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason) { diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml index e943966a5d..1113f7b31d 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml @@ -30,7 +30,7 @@

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

  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • -
  • @Model.CompetencyGroupsToAddCount new @(Model.CompetencyGroupsToAddCount == 1 ? "competency" : "competencies") to register
  • +
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? "competency" : "competencies") to add
  • @Model.ToUpdateOrSkipCount delegate @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • @if (Model.ErrorCount > 0) { @@ -71,9 +71,11 @@ else

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

    -
    + - + + + From 6498f8739f8b4530e233e85f4a76c084da38df06 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 23 Dec 2024 16:30:48 +0000 Subject: [PATCH 15/71] TD-5154 Adds always show description validation --- .../Models/Frameworks/Import/CompetencyTableRow.cs | 11 +++++++++-- .../ImportCompetenciesPreProcessViewModel.cs | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 374e016334..ab79a40b43 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -1,7 +1,6 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { using ClosedXML.Excel; - using Org.BouncyCastle.Asn1.X509; public enum RowStatus { @@ -12,7 +11,8 @@ public enum RowStatus CompetencyUpdated, CompetencyGroupInserted, CompetencyGroupUpdated, - CompetencyGroupAndCompetencyUpdated + CompetencyGroupAndCompetencyUpdated, + InvalidAlwaysShowDescription } public class CompetencyTableRow : BulkCompetency { @@ -30,10 +30,13 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) Competency = FindFieldValue("Competency"); CompetencyDescription = FindFieldValue("CompetencyDescription"); GroupDescription = FindFieldValue("GroupDescription"); + AlwaysShowDescriptionRaw = FindFieldValue("AlwaysShowDescription"); + AlwaysShowDescription = bool.TryParse(AlwaysShowDescriptionRaw, out var hasPrn) ? hasPrn : (bool?)null; FlagsCsv = FindFieldValue("FlagsCSV"); RowStatus = RowStatus.NotYetProcessed; } public int RowNumber { get; set; } + public string? AlwaysShowDescriptionRaw { get; set; } public ImportCompetenciesResult.ErrorReason? Error { get; set; } public RowStatus RowStatus { get; set; } public bool Validate() @@ -50,6 +53,10 @@ public bool Validate() { Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName; } + else if (!string.IsNullOrWhiteSpace(AlwaysShowDescriptionRaw) && !bool.TryParse(AlwaysShowDescriptionRaw, out _)) + { + Error = ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription; + } return !Error.HasValue; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs index 6d96699586..fac6c884c1 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs @@ -43,7 +43,7 @@ private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReas ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => "Competency must be 255 characters or less.", ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => - "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", + "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", _ => "Unspecified error.", }; } From a87a7c4ea0cfee6c2cb5b5c8a5907fee6d2a0899 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 23 Dec 2024 16:34:06 +0000 Subject: [PATCH 16/71] Formatter changes --- .../Views/Frameworks/Developer/ImportCompetencies.cshtml | 8 ++++---- .../Views/Frameworks/Developer/ImportCompleted.cshtml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml index 90935a7f1c..56ae470de0 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml @@ -33,9 +33,9 @@

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

    - @if(Model.IsNotBlank) + @if (Model.IsNotBlank) { - +

    To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below.

    @@ -94,8 +94,8 @@ } -
    - + +

    Upload file

    Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process. diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml index 1113f7b31d..5b355f572e 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml @@ -75,7 +75,7 @@ else - + From ce89c5cce994b86c747bc2df7d1eac4ca65bf485 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 23 Dec 2024 16:54:56 +0000 Subject: [PATCH 17/71] Fixes query to avoid reliance on new SQL 2017 STRING_AGG function --- .../DataServices/FrameworkDataService.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 068da4ceec..27b90f1adb 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -2410,15 +2410,19 @@ public IEnumerable GetBulkCompetenciesForFramework(int framework else { return connection.Query( - @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STRING_AGG(f.FlagName, ', ') AS FlagsCsv - FROM Flags AS f RIGHT OUTER JOIN - CompetencyFlags AS cf ON f.ID = cf.FlagID RIGHT OUTER JOIN + @"SELECT fc.ID, cg.Name AS CompetencyGroup, cg.Description AS GroupDescription, c.Name AS Competency, c.Description AS CompetencyDescription, c.AlwaysShowDescription, STUFF(( + SELECT ', ' + f.FlagName + FROM Flags AS f + INNER JOIN CompetencyFlags AS cf ON f.ID = cf.FlagID + WHERE cf.CompetencyID = c.ID + FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') AS FlagsCsv + FROM Competencies AS c INNER JOIN FrameworkCompetencies AS fc ON c.ID = fc.CompetencyID INNER JOIN FrameworkCompetencyGroups AS fcg ON fc.FrameworkCompetencyGroupID = fcg.ID INNER JOIN - CompetencyGroups AS cg ON fcg.CompetencyGroupID = cg.ID ON cf.CompetencyID = c.ID + CompetencyGroups AS cg ON fcg.CompetencyGroupID = cg.ID WHERE (fc.FrameworkID = @frameworkId) - GROUP BY fc.ID, cg.Name, cg.Description, c.Name, c.Description, c.AlwaysShowDescription, fcg.Ordering, fc.Ordering + GROUP BY fc.ID, c.ID, cg.Name, cg.Description, c.Name, c.Description, c.AlwaysShowDescription, fcg.Ordering, fc.Ordering ORDER BY fcg.Ordering, fc.Ordering", new { frameworkId } ); From 032da5c2493c4436a2ccd756be55eda231d31853 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 10:41:12 +0000 Subject: [PATCH 18/71] TD-5216 Moves to import namespace and separates form data in viewmodel --- .../ImportCompetencies.cs | 31 ++++--- .../ImportCompetenciesFormData.cs} | 6 +- .../ImportCompetenciesPreProcessViewModel.cs | 2 +- .../ImportCompetenciesResultsViewModel.cs | 2 +- .../Import/ImportCompetenciesViewModel.cs | 13 +++ .../{ => Import}/ImportCompleted.cshtml | 4 +- .../{ => Import}/ImportFailed.cshtml | 88 +++++++++---------- .../Index.cshtml} | 8 +- .../{ => Import}/UploadResults.cshtml | 2 +- 9 files changed, 89 insertions(+), 67 deletions(-) rename DigitalLearningSolutions.Web/ViewModels/Frameworks/{ImportCompetenciesViewModel.cs => Import/ImportCompetenciesFormData.cs} (69%) rename DigitalLearningSolutions.Web/ViewModels/Frameworks/{ => Import}/ImportCompetenciesPreProcessViewModel.cs (97%) rename DigitalLearningSolutions.Web/ViewModels/Frameworks/{ => Import}/ImportCompetenciesResultsViewModel.cs (96%) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs rename DigitalLearningSolutions.Web/Views/Frameworks/Developer/{ => Import}/ImportCompleted.cshtml (95%) rename DigitalLearningSolutions.Web/Views/Frameworks/Developer/{ => Import}/ImportFailed.cshtml (95%) rename DigitalLearningSolutions.Web/Views/Frameworks/Developer/{ImportCompetencies.cshtml => Import/Index.cshtml} (96%) rename DigitalLearningSolutions.Web/Views/Frameworks/Developer/{ => Import}/UploadResults.cshtml (97%) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index d713235f4f..15a1816dbb 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -3,7 +3,7 @@ using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; -using DigitalLearningSolutions.Web.ViewModels.Frameworks; +using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; using System.IO; @@ -16,15 +16,16 @@ public partial class FrameworksController public IActionResult ImportCompetencies(int frameworkId, string tabname, bool isNotBlank) { var adminId = GetAdminId(); + var framework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminId); var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId); if (userRole < 2) return StatusCode(403); var model = new ImportCompetenciesViewModel() { - FrameworkId = frameworkId, + Framework = framework, IsNotBlank = isNotBlank }; - return View("Developer/ImportCompetencies", model); + return View("Developer/Import/Index", model); } public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) { @@ -40,11 +41,11 @@ public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) } [HttpPost] [Route("/Framework/{frameworkId}/{tabname}/Import")] - [Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")] - public IActionResult StartImport(ImportCompetenciesViewModel model, string tabname, bool isNotBlank) + [Route("/Framework/{frameworkId}/{tabname}/Import/Uploaded")] + public IActionResult StartImport(ImportCompetenciesFormData model, int frameworkId, string tabname, bool isNotBlank) { if (!ModelState.IsValid) - return View("Developer/ImportCompetencies", model); + return View("Developer/Import/Index", model); try { var adminUserID = User.GetAdminIdKnownNotNull(); @@ -52,12 +53,12 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna if (!workbook.Worksheets.Contains(ImportCompetenciesFromFileService.CompetenciesSheetName)) { ModelState.AddModelError("ImportFile", CommonValidationErrorMessages.InvalidCompetenciesUploadExcelFile); - return View("Developer/ImportCompetencies", model); + return View("Developer/Import/Index", model); } var competenciesFileName = FileHelper.UploadFile(webHostEnvironment, model.ImportFile); - setupBulkUploadData(model.FrameworkId, adminUserID, competenciesFileName, tabname, isNotBlank); + setupBulkUploadData(frameworkId, adminUserID, competenciesFileName, tabname, isNotBlank); - return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId = model.FrameworkId, tabname }); + return RedirectToAction("ImportCompleted", "Frameworks", new { frameworkId, tabname }); } catch (DocumentFormat.OpenXml.Packaging.OpenXmlPackageException) { @@ -66,10 +67,10 @@ public IActionResult StartImport(ImportCompetenciesViewModel model, string tabna } catch (InvalidHeadersException) { - return View("Developer/ImportFailed"); + return View("Developer/Import/ImportFailed"); } } - [Route("/Framework/{frameworkId}/{tabname}/ImportCompleted")] + [Route("/Framework/{frameworkId}/{tabname}/Import/Uploaded")] public IActionResult ImportCompleted() { var data = GetBulkUploadData(); @@ -84,7 +85,7 @@ public IActionResult ImportCompleted() data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount; setBulkUploadData(data); - return View("Developer/ImportCompleted", resultsModel); + return View("Developer/Import/ImportCompleted", resultsModel); } catch (InvalidHeadersException) { @@ -92,7 +93,13 @@ public IActionResult ImportCompleted() return View("ImportFailed"); } } + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] + public IActionResult AddAssessmentQuestions() + { + var data = GetBulkUploadData(); + return View(); + } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { TempData.Clear(); diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs similarity index 69% rename from DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs rename to DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs index 27fa9f38b6..98604cc8ee 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesFormData.cs @@ -1,13 +1,11 @@ -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Web.Attributes; using Microsoft.AspNetCore.Http; using System.ComponentModel.DataAnnotations; - public class ImportCompetenciesViewModel + public class ImportCompetenciesFormData { - public int FrameworkId { get; set; } - public bool IsNotBlank { get; set; } [Required(ErrorMessage = "Import competencies file is required")] [AllowedExtensions(new[] { ".xlsx" }, "Import competencies file must be in xlsx format")] [MaxFileSize(5 * 1024 * 1024, "Maximum allowed file size is 5MB")] diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs similarity index 97% rename from DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs rename to DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs index fac6c884c1..cb903dd1b2 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -4,7 +4,7 @@ using System.Linq; using DigitalLearningSolutions.Data.Models.Frameworks.Import; -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { public class ImportCompetenciesPreProcessViewModel { diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs similarity index 96% rename from DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs rename to DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs index d76b0338ae..5294f9d51b 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs @@ -1,4 +1,4 @@ -namespace DigitalLearningSolutions.Web.ViewModels.Frameworks +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Data.Models.Frameworks.Import; using System.Collections.Generic; diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs new file mode 100644 index 0000000000..34bb2e65d3 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs @@ -0,0 +1,13 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + using DigitalLearningSolutions.Data.Models.Frameworks; + using DigitalLearningSolutions.Web.Attributes; + using Microsoft.AspNetCore.Http; + using System.ComponentModel.DataAnnotations; + + public class ImportCompetenciesViewModel : ImportCompetenciesFormData + { + public DetailFramework Framework { get; set; } + public bool IsNotBlank { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml similarity index 95% rename from DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml rename to DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 5b355f572e..cbade17bee 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -1,6 +1,6 @@ @inject IConfiguration Configuration @using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import @using Microsoft.Extensions.Configuration @model ImportCompetenciesPreProcessViewModel @{ @@ -43,7 +43,7 @@

@if (Model.ErrorCount == 0) { - Continue + Continue } else { diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml similarity index 95% rename from DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml rename to DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml index 0176c0b656..f170f620dc 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportFailed.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml @@ -1,44 +1,44 @@ -@inject IConfiguration Configuration -@using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks -@using Microsoft.Extensions.Configuration -@model ImportCompetenciesResultsViewModel -@{ - ViewData["Title"] = "Framework - Import Failed"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); -} - -@section NavMenuItems { - -} - @section NavBreadcrumbs { - -} - -
-
-

Import failed

- -

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

- -

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

- - - - -
-
+@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportCompetenciesResultsViewModel +@{ + ViewData["Title"] = "Framework - Import Failed"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} + @section NavBreadcrumbs { + +} + +
+
+

Import failed

+ +

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

+ +

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

+ + + + +
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml similarity index 96% rename from DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml rename to DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 56ae470de0..0d6c3278a4 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/ImportCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -1,5 +1,5 @@ @using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import @model ImportCompetenciesViewModel @{ ViewData["Application"] = "Framework Service"; @@ -31,6 +31,10 @@ }

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

+ @if (Model.Framework.PublishStatusID == 3) + { + + }
@if (Model.IsNotBlank) @@ -101,7 +105,7 @@ Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process.

- + diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml similarity index 97% rename from DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml rename to DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index fefffeae4f..7a7b556f50 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -1,6 +1,6 @@ @inject IConfiguration Configuration @using DigitalLearningSolutions.Web.Extensions -@using DigitalLearningSolutions.Web.ViewModels.Frameworks +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import @using Microsoft.Extensions.Configuration @model ImportCompetenciesResultsViewModel @{ From e5a5a9f125e745ff5b2281c188fbd332cf6b9cbe Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 10:42:24 +0000 Subject: [PATCH 19/71] TD-5216 Load FALSE into AlwaysShowDescription field for downloaded template --- .../DataServices/FrameworkDataService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 27b90f1adb..f43879edcf 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -2404,7 +2404,7 @@ public IEnumerable GetBulkCompetenciesForFramework(int framework if (frameworkId < 1) { return connection.Query( - @"SELECT NULL AS ID, '' AS CompetencyGroup, '' AS GroupDescription, '' AS Competency, '' AS CompetencyDescription, NULL AS AlwaysShowDescription, '' AS FlagsCsv" + @"SELECT NULL AS ID, '' AS CompetencyGroup, '' AS GroupDescription, '' AS Competency, '' AS CompetencyDescription, 0 AS AlwaysShowDescription, '' AS FlagsCsv" ); } else From 8101eee38a0305206a895e965b3f4eb540590489 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 15:30:56 +0000 Subject: [PATCH 20/71] TD-5216 Adds competency vocabulary to views --- .../Import/ImportCompetenciesResult.cs | 1 + .../ImportCompetencies.cs | 12 +- .../Models/BulkCompetenciesData.cs | 14 +- .../ImportCompetenciesPreProcessViewModel.cs | 24 ++- .../ImportCompetenciesResultsViewModel.cs | 1 + .../Import/ImportCompetenciesViewModel.cs | 20 ++- .../Developer/Import/ImportCompleted.cshtml | 96 ++++++------ .../Developer/Import/ImportFailed.cshtml | 55 +++---- .../Frameworks/Developer/Import/Index.cshtml | 148 +++++++++--------- .../Developer/Import/UploadResults.cshtml | 97 ++++++------ 10 files changed, 251 insertions(+), 217 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 3c2bc8e287..53f0beed1a 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -22,6 +22,7 @@ IReadOnlyCollection competencyTableRows { ProcessedCount = competencyTableRows.Count; CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); + CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated); GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 15a1816dbb..9e02407e0e 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -20,11 +20,8 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname, bool is var userRole = frameworkService.GetAdminUserRoleForFrameworkId(adminId, frameworkId); if (userRole < 2) return StatusCode(403); - var model = new ImportCompetenciesViewModel() - { - Framework = framework, - IsNotBlank = isNotBlank - }; + var model = new ImportCompetenciesViewModel(framework, isNotBlank); + return View("Developer/Import/Index", model); } public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) @@ -80,7 +77,7 @@ public IActionResult ImportCompleted() try { var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook); - var resultsModel = new ImportCompetenciesPreProcessViewModel(results) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; + var resultsModel = new ImportCompetenciesPreProcessViewModel(results, data) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount; @@ -104,8 +101,9 @@ private void setupBulkUploadData(int frameworkId, int adminUserID, string compet { TempData.Clear(); multiPageFormService.ClearMultiPageFormData(MultiPageFormDataFeature.AddCustomWebForm("BulkCompetencyDataCWF"), TempData); + var framework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminUserID); var today = clockUtility.UtcToday; - var bulkUploadData = new BulkCompetenciesData(frameworkId, adminUserID, competenciessFileName, tabName, isNotBlank); + var bulkUploadData = new BulkCompetenciesData(framework, adminUserID, competenciessFileName, tabName, isNotBlank); setBulkUploadData(bulkUploadData); } private void setBulkUploadData(BulkCompetenciesData bulkUploadData) diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 290e263477..44ae69f92b 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using DigitalLearningSolutions.Data.Models.Frameworks; +using System.Collections; +using System.Collections.Generic; using System.Linq; namespace DigitalLearningSolutions.Web.Models @@ -6,15 +8,21 @@ namespace DigitalLearningSolutions.Web.Models public class BulkCompetenciesData { public BulkCompetenciesData() { } - public BulkCompetenciesData(int frameworkId, int adminUserId, string competenciesFileName, string tabName, bool isNotBlank) + public BulkCompetenciesData(DetailFramework framework, int adminUserId, string competenciesFileName, string tabName, bool isNotBlank) { - FrameworkId = frameworkId; + FrameworkId = framework.ID; + FrameworkName = framework.FrameworkName; + PublishStatusID = framework.PublishStatusID; + FrameworkVocubulary = framework.FrameworkConfig; AdminUserId = adminUserId; CompetenciesFileName = competenciesFileName; TabName = tabName; IsNotBlank = isNotBlank; } public int FrameworkId { get; set; } + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocubulary { get; set; } public string TabName { get; set; } public int AdminUserId { get; set; } public bool IsNotBlank { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs index cb903dd1b2..f92f056115 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -3,21 +3,29 @@ using DigitalLearningSolutions.Web.Models; using System.Linq; using DigitalLearningSolutions.Data.Models.Frameworks.Import; +using DigitalLearningSolutions.Web.Helpers; namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { public class ImportCompetenciesPreProcessViewModel { - public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompetenciesResult) + public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompetenciesResult, BulkCompetenciesData bulkCompetenciesData) { + FrameworkName = bulkCompetenciesData.FrameworkName; + PublishStatusID = bulkCompetenciesData.PublishStatusID; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(bulkCompetenciesData.FrameworkVocubulary); ToProcessCount = bulkCompetenciesResult.ProcessedCount; CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; - CompetenciesToUpdateCount = bulkCompetenciesResult.CompetencyUpdatedCount; + ToUpdateOrSkipCount = bulkCompetenciesResult.CompetencyUpdatedCount; CompetencyGroupsToAddCount = bulkCompetenciesResult.GroupAddedCount; CompetencyGroupsToUpdateCount = bulkCompetenciesResult.GroupUpdatedCount; - Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); + Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason, FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary)))); } - + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } public int ErrorCount => Errors.Count(); public int ToProcessCount { get; set; } @@ -30,18 +38,18 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet public bool IsNotBlank { get; set; } public string TabName { get; set; } - private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason) + private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason, string vocabularySingular) { return reason switch { ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => "Group name must be 255 characters or less.", ImportCompetenciesResult.ErrorReason.MissingCompetencyName => - "Competency is blank. Competency is a required field and cannot be left blank", + vocabularySingular + " is blank. " + vocabularySingular + " is a required field and cannot be left blank", ImportCompetenciesResult.ErrorReason.InvalidId => - "The ID provided does not match a Competency ID in this Framework", + "The ID provided does not match a " + vocabularySingular + " ID in this Framework", ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => - "Competency must be 255 characters or less.", + vocabularySingular + " must be 500 characters or less.", ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => "Always show description is invalid. The Always show description field must contain 'TRUE' or 'FALSE'", _ => "Unspecified error.", diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs index 5294f9d51b..b47108fe15 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs @@ -28,6 +28,7 @@ private string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reas "Competency name was not provided. Competency name is a required field and cannot be left blank", ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => "Competency name must be 500 characters or fewer", ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName => "Competency group name must be 255 characters or fewer", + _ => "Unknown error", }; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs index 34bb2e65d3..698b278dc1 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesViewModel.cs @@ -1,13 +1,25 @@ namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Data.Models.Frameworks; - using DigitalLearningSolutions.Web.Attributes; - using Microsoft.AspNetCore.Http; - using System.ComponentModel.DataAnnotations; + using DigitalLearningSolutions.Web.Helpers; public class ImportCompetenciesViewModel : ImportCompetenciesFormData { - public DetailFramework Framework { get; set; } + public ImportCompetenciesViewModel(DetailFramework framework, bool isNotBlank) + { + FrameworkID = framework.ID; + FrameworkName = framework.FrameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(framework.FrameworkConfig); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(framework.FrameworkConfig); + PublishStatusID = framework.PublishStatusID; + IsNotBlank = isNotBlank; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int PublishStatusID { get; set; } public bool IsNotBlank { get; set; } + } } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index cbade17bee..1db1e511ba 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -27,57 +27,59 @@ }

Delegate file uploaded

-

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

-
    -
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • -
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? "competency" : "competencies") to add
  • -
  • @Model.ToUpdateOrSkipCount delegate @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • - @if (Model.ErrorCount > 0) +
    +

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

    +
      +
    • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
    • +
    • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
    • +
    • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
    • + @if (Model.ErrorCount > 0) + { +
    • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
    • + } + else + { +
    • No errors
    • + } +
    + @if (Model.ErrorCount == 0) { -
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • + Continue } else { -
  • No errors
  • - } -
-@if (Model.ErrorCount == 0) -{ - Continue -} -else -{ -

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

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

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

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

Upload corrected file

-

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

-
+
+ } +
+
+

Upload corrected file

+

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

+ - - - + + + - - -} - + + + } + +
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml index f170f620dc..c16803e9df 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml @@ -4,41 +4,42 @@ @using Microsoft.Extensions.Configuration @model ImportCompetenciesResultsViewModel @{ - ViewData["Title"] = "Framework - Import Failed"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); + ViewData["Title"] = "Framework - Import Failed"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -
+
-

Import failed

- -

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

+

Import failed

+
+

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

-

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

+

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

- + - + +
-
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 0d6c3278a4..49ed6c64d6 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -5,7 +5,7 @@ ViewData["Application"] = "Framework Service"; ViewData["HeaderPathName"] = "Framework Service"; var errorHasOccurred = !ViewData.ModelState.IsValid; - ViewData["Title"] = errorHasOccurred ? "Error: Bulk upload competencies" : "Bulk upload competencies"; + ViewData["Title"] = errorHasOccurred ? "Error: Bulk upload " + Model.FrameworkVocabularyPlural.ToLower() : "Bulk upload " + Model.FrameworkVocabularyPlural.ToLower(); var cancelLinkData = Html.GetRouteValues(); } @@ -30,85 +30,87 @@ { } -

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

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

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

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

- To bulk add and/or update competencies in your framework, download an Excel workbook using one of the options below. -

-
-
- -

- What would you like to download? -

-
-
- Select one option -
-
-
- - +

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

+ +
+ +

+ What would you like to download? +

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

- Download a blank template for bulk adding competencies to your framework. This Excel file will be empty. New competencies can be added by including their details on a blank row. -

- - Download template - - } +
+ + } + else + { +

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

+ + Download template + + } -
+
-

Upload file

-

- Once you have an Excel competencies workbook, add or update competencies to the worksheet, save and start the upload process. -

-
- - - - - +

Upload file

+

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

+
+ + + + + +
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index 7a7b556f50..16aac10fde 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -4,61 +4,62 @@ @using Microsoft.Extensions.Configuration @model ImportCompetenciesResultsViewModel @{ - ViewData["Title"] = "Framework - Import Competencies"; - ViewData["Application"] = "Framework Service"; - ViewData["HeaderPathName"] = "Framework Service"; - var errorHasOccurred = !ViewData.ModelState.IsValid; - var cancelLinkData = Html.GetRouteValues(); + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); } @section NavMenuItems { - + } - @section NavBreadcrumbs { - +@section NavBreadcrumbs { + } -
+
-

Import competencies complete

- -

Summary of results:

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

Import competencies complete

+
+

Summary of results:

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

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

-

The lines below were skipped due to errors during processing:

-
    - @foreach (var (rowNumber, errorMessage) in Model.Errors) - { -
  • Line @rowNumber: @errorMessage
  • - } -
-
- } + @if (Model.ErrorCount > 0) + { +
+

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

+

The lines below were skipped due to errors during processing:

+
    + @foreach (var (rowNumber, errorMessage) in Model.Errors) + { +
  • Line @rowNumber: @errorMessage
  • + } +
+
+ } - -
+ +
+
From 89f372f9a7df75a58ea163f961dd5220e1dd913c Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 16:30:15 +0000 Subject: [PATCH 21/71] TD-5216 Uses custom competency vocabulary in Excel file processing --- .../Frameworks/Import/CompetencyTableRow.cs | 8 ++-- .../ImportCompetencies.cs | 8 ++-- .../ImportCompetenciesFromFileService.cs | 40 +++++++++---------- .../Frameworks/Developer/Import/Index.cshtml | 6 +-- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index ab79a40b43..1f99fa9550 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -26,10 +26,10 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) RowNumber = row.RowNumber(); id = row.Cell(1).GetValue(); - CompetencyGroup = FindFieldValue("CompetencyGroup"); - Competency = FindFieldValue("Competency"); - CompetencyDescription = FindFieldValue("CompetencyDescription"); - GroupDescription = FindFieldValue("GroupDescription"); + CompetencyGroup = row.Cell(2).GetValue(); + GroupDescription = row.Cell(3).GetValue(); + Competency = row.Cell(4).GetValue(); + CompetencyDescription = row.Cell(5).GetValue(); AlwaysShowDescriptionRaw = FindFieldValue("AlwaysShowDescription"); AlwaysShowDescription = bool.TryParse(AlwaysShowDescriptionRaw, out var hasPrn) ? hasPrn : (bool?)null; FlagsCsv = FindFieldValue("FlagsCSV"); diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 9e02407e0e..db606548a0 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -24,11 +24,11 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname, bool is return View("Developer/Import/Index", model); } - public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption) + public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption, string vocabulary) { - string fileName = DownloadOption == 2 ? $"DLS Competencies for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : "DLS Competencies for Bulk Upload.xlsx"; + string fileName = DownloadOption == 2 ? $"DLS {FrameworkVocabularyHelper.VocabularyPlural(vocabulary)} for Bulk Update {clockUtility.UtcToday:yyyy-MM-dd}.xlsx" : $"DLS {FrameworkVocabularyHelper.VocabularyPlural(vocabulary)} for Bulk Upload.xlsx"; var content = importCompetenciesFromFileService.GetCompetencyFileForFramework( - frameworkId, DownloadOption == 2 ? false : true + frameworkId, DownloadOption == 2 ? false : true, vocabulary ); return File( content, @@ -76,7 +76,7 @@ public IActionResult ImportCompleted() var workbook = new XLWorkbook(filePath); try { - var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook); + var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook, data.FrameworkVocubulary); var resultsModel = new ImportCompetenciesPreProcessViewModel(results, data) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 1746ebe904..d8611a86f8 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -12,29 +12,27 @@ namespace DigitalLearningSolutions.Web.Services using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Frameworks.Import; - using DigitalLearningSolutions.Web.Models; - using Microsoft.AspNetCore.Http; public interface IImportCompetenciesFromFileService { - byte[] GetCompetencyFileForFramework(int frameworkId, bool v); - public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook); - public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId); + byte[] GetCompetencyFileForFramework(int frameworkId, bool isBlank, string vocabulary); + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary); + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService { private readonly IFrameworkService frameworkService; private static readonly XLTableTheme TableTheme = XLTableTheme.TableStyleLight9; - public const string CompetenciesSheetName = "CompetenciesBulkUpload"; + public const string CompetenciesSheetName = "FrameworkBulkUpload"; public ImportCompetenciesFromFileService( IFrameworkService frameworkService ) { this.frameworkService = frameworkService; } - public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook) + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary) { - var table = OpenCompetenciesTable(workbook); + var table = OpenCompetenciesTable(workbook, vocabulary); var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); foreach (var competencyRow in competencyRows) { @@ -54,14 +52,14 @@ private void PreProcessCompetencyRow(CompetencyTableRow competencyRow) competencyRow.RowStatus = RowStatus.CompetencyUpdated; } } - public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId) + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary) { int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID(); int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID(); - var table = OpenCompetenciesTable(workbook); + var table = OpenCompetenciesTable(workbook, vocabulary); return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId); } - internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook) + internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) { var worksheet = workbook.Worksheet(1); worksheet.Columns(1, 15).Unhide(); @@ -70,7 +68,7 @@ internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook) throw new InvalidHeadersException(); } var table = worksheet.Tables.Table(0); - if (!ValidateHeaders(table)) + if (!ValidateHeaders(table, vocabulary)) { throw new InvalidHeadersException(); } @@ -132,15 +130,15 @@ CompetencyTableRow competencyRow return maxFrameworkCompetencyGroupId; } - private static bool ValidateHeaders(IXLTable table) + private static bool ValidateHeaders(IXLTable table, string Vocabulary) { var expectedHeaders = new List { "ID", - "CompetencyGroup", + Vocabulary + "Group", "GroupDescription", - "Competency", - "CompetencyDescription", + Vocabulary, + Vocabulary + "Description", "AlwaysShowDescription", "FlagsCSV" }.OrderBy(x => x); @@ -148,10 +146,10 @@ private static bool ValidateHeaders(IXLTable table) return actualHeaders.SequenceEqual(expectedHeaders); } - public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank) + public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank, string vocabulary) { using var workbook = new XLWorkbook(); - PopulateCompetenciesSheet(workbook, frameworkId, blank); + PopulateCompetenciesSheet(workbook, frameworkId, blank, vocabulary); if (blank) { ClosedXmlHelper.HideWorkSheetColumn(workbook, "ID"); @@ -167,7 +165,7 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank) workbook.SaveAs(stream); return stream.ToArray(); } - private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bool blank) + private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bool blank, string vocabulary) { @@ -184,8 +182,10 @@ private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bo FlagsCSV = x.FlagsCsv, } ); - ClosedXmlHelper.AddSheetToWorkbook(workbook, CompetenciesSheetName, competencies, TableTheme); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "CompetencyGroup", vocabulary + "Group"); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "Competency", vocabulary); + ClosedXmlHelper.RenameWorksheetColumn(workbook, "CompetencyDescription", vocabulary + "Description"); } } } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 49ed6c64d6..649f7a608f 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -66,7 +66,7 @@ This Excel file will be empty.
New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row. - + Download template @@ -81,7 +81,7 @@ This Excel file will include all existing @Model.FrameworkVocabularyPlural.ToLower() whose details you can update.
New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row. - + Download @Model.FrameworkVocabularyPlural.ToLower() @@ -94,7 +94,7 @@

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

- + Download template } From 992c4715388664dde4dee3e94b9c5a78b7911ef2 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 16:36:34 +0000 Subject: [PATCH 22/71] IDE formatter changes --- .../Controllers/FrameworksController/ImportCompetencies.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index db606548a0..dad1bbd4c1 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -21,7 +21,7 @@ public IActionResult ImportCompetencies(int frameworkId, string tabname, bool is if (userRole < 2) return StatusCode(403); var model = new ImportCompetenciesViewModel(framework, isNotBlank); - + return View("Developer/Import/Index", model); } public IActionResult DownloadCompetencies(int frameworkId, int DownloadOption, string vocabulary) From a0a7e7c8c6b223a4d280af859491ca240608ae33 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 30 Dec 2024 10:43:24 +0000 Subject: [PATCH 23/71] TD-5155 Begins to implement add questions view model --- .../Import/AddAssessmentQuestionsViewModel.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs new file mode 100644 index 0000000000..b50b1dc6a1 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs @@ -0,0 +1,13 @@ +using DigitalLearningSolutions.Data.Models.Frameworks; +using Microsoft.AspNetCore.Mvc.Rendering; +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddAssessmentQuestionsViewModel + { + public DetailFramework Framework { get; set; } + public IEnumerable? FrameworkDefaultQuestions { get; set; } + public SelectList? QuestionSelectList { get; set; } + } +} From 336b97c40d43b9842b4eacdb6ded29fcd91b0e6a Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Tue, 31 Dec 2024 09:37:40 +0000 Subject: [PATCH 24/71] TD-5155 Adds view model and constructor --- .../ImportCompetencies.cs | 22 +++++++++++++++++-- .../Models/BulkCompetenciesData.cs | 1 + .../Import/AddAssessmentQuestionsFormData.cs | 10 +++++++++ .../Import/AddAssessmentQuestionsViewModel.cs | 21 ++++++++++++++---- 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index dad1bbd4c1..d9cbb44318 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -6,7 +6,9 @@ using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; using System.IO; +using System.Linq; namespace DigitalLearningSolutions.Web.Controllers.FrameworksController { @@ -94,8 +96,24 @@ public IActionResult ImportCompleted() public IActionResult AddAssessmentQuestions() { var data = GetBulkUploadData(); - - return View(); + var adminId = GetAdminId(); + var defaultQuestions = frameworkService.GetFrameworkDefaultQuestionsById(data.FrameworkId, adminId); + var questionList = frameworkService.GetAssessmentQuestions(data.FrameworkId, adminId).ToList(); + var questionSelectList = new SelectList(questionList, "ID", "Label"); + var model = new AddAssessmentQuestionsViewModel + ( + data.FrameworkId, + data.FrameworkName, + data.FrameworkVocubulary, + data.PublishStatusID, + data.CompetenciesToAddCount, + data.CompetenciesToUpdateCount, + defaultQuestions, + questionSelectList + ); + model.DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; + model.OtherAssessmentQuestionIDs = data.AssessmentQuestionIDs; + return View(model); } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 44ae69f92b..525437093c 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -27,6 +27,7 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public int AdminUserId { get; set; } public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } + public List DefaultQuestionIDs { get; set; } public List AssessmentQuestionIDs { get; set; } public int? AddAssessmentQuestionOption { get; set; } public int CompetenciesToProcessCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs new file mode 100644 index 0000000000..428f7f22df --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddAssessmentQuestionsFormData + { + public List DefaultAssessmentQuestionIDs { get; set; } + public List OtherAssessmentQuestionIDs { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs index b50b1dc6a1..024378c538 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs @@ -1,13 +1,26 @@ using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Web.Helpers; using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { - public class AddAssessmentQuestionsViewModel + public class AddAssessmentQuestionsViewModel( + DetailFramework framework, + int newCompetencies, + int existingCompetencies, + IEnumerable defaultQuestions, + SelectList questionSelectList + ) : AddAssessmentQuestionsFormData { - public DetailFramework Framework { get; set; } - public IEnumerable? FrameworkDefaultQuestions { get; set; } - public SelectList? QuestionSelectList { get; set; } + public int FrameworkID { get; set; } = framework.ID; + public string FrameworkName { get; set; } = framework.FrameworkName; + public string FrameworkVocabularySingular { get; set; } = FrameworkVocabularyHelper.VocabularySingular(framework.FrameworkConfig); + public string FrameworkVocabularyPlural { get; set; } = FrameworkVocabularyHelper.VocabularyPlural(framework.FrameworkConfig); + public int PublishStatusID { get; set; } = framework.PublishStatusID; + public int NewCompetencies { get; set; } = newCompetencies; + public int ExistingCompetencies { get; set; } = existingCompetencies; + public IEnumerable? DefaultQuestions { get; set; } = defaultQuestions; + public SelectList? QuestionSelectList { get; set; } = questionSelectList; } } From 0c2c9952a15ada59b6be61ec2bd81db944b080e3 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Tue, 31 Dec 2024 16:36:33 +0000 Subject: [PATCH 25/71] TD-5155 Implements conditional preselected checkboxes for default questions --- .../ImportCompetencies.cs | 19 ++- .../Models/BulkCompetenciesData.cs | 7 +- .../Import/AddAssessmentQuestionsFormData.cs | 2 + .../Import/AddAssessmentQuestionsViewModel.cs | 15 ++- .../Import/AddAssessmentQuestions.cshtml | 86 ++++++++++++++ .../Developer/Import/ImportCompleted.cshtml | 108 +++++++++--------- .../Developer/Import/ImportFailed.cshtml | 2 +- .../Frameworks/Developer/Import/Index.cshtml | 2 +- .../Developer/Import/UploadResults.cshtml | 2 +- 9 files changed, 177 insertions(+), 66 deletions(-) create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index d9cbb44318..681d019a2c 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -4,9 +4,12 @@ using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; +using DocumentFormat.OpenXml.Office2010.ExcelAc; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.DotNet.Scaffolding.Shared.Project; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -89,7 +92,7 @@ public IActionResult ImportCompleted() catch (InvalidHeadersException) { FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); - return View("ImportFailed"); + return View("Developer/Import/ImportFailed"); } } [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] @@ -98,6 +101,16 @@ public IActionResult AddAssessmentQuestions() var data = GetBulkUploadData(); var adminId = GetAdminId(); var defaultQuestions = frameworkService.GetFrameworkDefaultQuestionsById(data.FrameworkId, adminId); + if (!data.DefaultQuestionIDs.Any() && defaultQuestions.Any() && data.AddDefaultAssessmentQuestions == true) + { + var defaultQuestionsList = new List(); + foreach (var question in defaultQuestions) + { + defaultQuestionsList.Add(question.ID); + } + data.DefaultQuestionIDs = defaultQuestionsList; + setBulkUploadData(data); + } var questionList = frameworkService.GetAssessmentQuestions(data.FrameworkId, adminId).ToList(); var questionSelectList = new SelectList(questionList, "ID", "Label"); var model = new AddAssessmentQuestionsViewModel @@ -111,9 +124,11 @@ public IActionResult AddAssessmentQuestions() defaultQuestions, questionSelectList ); + model.AddDefaultAssessmentQuestions = data.AddDefaultAssessmentQuestions; + model.AddCustomAssessmentQuestion = data.AddCustomAssessmentQuestion; model.DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; model.OtherAssessmentQuestionIDs = data.AssessmentQuestionIDs; - return View(model); + return View("Developer/Import/AddAssessmentQuestions", model); } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 525437093c..69800b0187 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -27,9 +27,10 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public int AdminUserId { get; set; } public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } - public List DefaultQuestionIDs { get; set; } - public List AssessmentQuestionIDs { get; set; } - public int? AddAssessmentQuestionOption { get; set; } + public List DefaultQuestionIDs { get; set; } = []; + public List AssessmentQuestionIDs { get; set; } = []; + public bool AddDefaultAssessmentQuestions { get; set; } = true; + public bool AddCustomAssessmentQuestion { get; set; } = false; public int CompetenciesToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } public int CompetenciesToUpdateCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs index 428f7f22df..aa1e0ab2ce 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs @@ -4,6 +4,8 @@ namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { public class AddAssessmentQuestionsFormData { + public bool AddDefaultAssessmentQuestions { get; set; } + public bool AddCustomAssessmentQuestion { get; set; } public List DefaultAssessmentQuestionIDs { get; set; } public List OtherAssessmentQuestionIDs { get; set; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs index 024378c538..bfd1cf3d12 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs @@ -6,18 +6,21 @@ namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { public class AddAssessmentQuestionsViewModel( - DetailFramework framework, + int frameworkId, + string frameworkName, + string frameworkVocabulary, + int publishStatusId, int newCompetencies, int existingCompetencies, IEnumerable defaultQuestions, SelectList questionSelectList ) : AddAssessmentQuestionsFormData { - public int FrameworkID { get; set; } = framework.ID; - public string FrameworkName { get; set; } = framework.FrameworkName; - public string FrameworkVocabularySingular { get; set; } = FrameworkVocabularyHelper.VocabularySingular(framework.FrameworkConfig); - public string FrameworkVocabularyPlural { get; set; } = FrameworkVocabularyHelper.VocabularyPlural(framework.FrameworkConfig); - public int PublishStatusID { get; set; } = framework.PublishStatusID; + public int FrameworkID { get; set; } = frameworkId; + public string FrameworkName { get; set; } = frameworkName; + public string FrameworkVocabularySingular { get; set; } = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + public string FrameworkVocabularyPlural { get; set; } = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + public int PublishStatusID { get; set; } = publishStatusId; public int NewCompetencies { get; set; } = newCompetencies; public int ExistingCompetencies { get; set; } = existingCompetencies; public IEnumerable? DefaultQuestions { get; set; } = defaultQuestions; diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml new file mode 100644 index 0000000000..e3a0e29540 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml @@ -0,0 +1,86 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model AddAssessmentQuestionsViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var checkListErrorClass = !ViewData.ModelState.IsValid && Model.AddDefaultAssessmentQuestions == null ? "nhsuk-form-group nhsuk-form-group--error" : "nhsuk-form-group"; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); + var hintTextString = Model.NewCompetencies != 0 && Model.ExistingCompetencies != 0 ? "new and/or updated " : (Model.NewCompetencies == 0 ? "updated " : "new "); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
+
+
+ @if (errorHasOccurred) + { + + } +
+
+
+ +

+ @ViewData["Title"] +

+
+
+ Which assessment questions would you like to attach to the @hintTextString @Model.FrameworkVocabularyPlural.ToLower()? +
+
+ @if (Model.DefaultQuestions.Any()) + { +
+ + +
+
+ +
+
+ @foreach (var (defaultQuestion, index) in Model.DefaultQuestions.Select((t, i) => (t, i))) + { +
+ + +
+ } +
+
+
+ } +
+
+
+
+
+
+
diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 1db1e511ba..fcabc89014 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -26,60 +26,64 @@ } -

Delegate file uploaded

-
-

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

-
    -
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • -
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
  • -
  • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • - @if (Model.ErrorCount > 0) - { -
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • - } - else - { -
  • No errors
  • - } -
- @if (Model.ErrorCount == 0) - { - Continue - } - else - { -

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

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

Delegate file uploaded

+
+

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

+
    +
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • +
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
  • +
  • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • + @if (Model.ErrorCount > 0) { -
    -
    - Row @rowNumber -
    -
    - @errorMessage -
    - -
    +
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • } -
-
-

Upload corrected file

-

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

-
+ else + { +
  • No errors
  • + } + + @if (Model.ErrorCount == 0) + { + Continue + } + else + { +

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

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

    Upload corrected file

    +

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

    + - - - + + + - - - } - + + + } + +
    + diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml index c16803e9df..1001851c70 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportFailed.cshtml @@ -30,7 +30,7 @@

    Import failed

    -
    +

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

    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 649f7a608f..a11a391b24 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -31,7 +31,7 @@ }

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

    -
    +
    @if (Model.PublishStatusID == 3) { diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index 16aac10fde..d83d3ca428 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -30,7 +30,7 @@

    Import competencies complete

    -
    +

    Summary of results:

    • @Model.ProcessedCount @(Model.ProcessedCount == 1 ? "line" : "lines") processed
    • From fa36c9a1c50a5c6e69aa1ae3ba4701adee356cd6 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 2 Jan 2025 11:35:02 +0000 Subject: [PATCH 26/71] TD-5155 Implements add assessment questions form view --- .../ImportCompetencies.cs | 14 ++++- .../Models/BulkCompetenciesData.cs | 2 +- .../Import/AddAssessmentQuestionsFormData.cs | 2 +- .../Import/AddAssessmentQuestions.cshtml | 62 ++++++++++++------- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 681d019a2c..62dbd65315 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -127,9 +127,21 @@ public IActionResult AddAssessmentQuestions() model.AddDefaultAssessmentQuestions = data.AddDefaultAssessmentQuestions; model.AddCustomAssessmentQuestion = data.AddCustomAssessmentQuestion; model.DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; - model.OtherAssessmentQuestionIDs = data.AssessmentQuestionIDs; + model.CustomAssessmentQuestionID = data.CustomAssessmentQuestionID; return View("Developer/Import/AddAssessmentQuestions", model); } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] + public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model) + { + var data = GetBulkUploadData(); + data.AddDefaultAssessmentQuestions = model.AddDefaultAssessmentQuestions; + data.AddCustomAssessmentQuestion = model.AddCustomAssessmentQuestion; + data.DefaultQuestionIDs = model.DefaultAssessmentQuestionIDs; + data.CustomAssessmentQuestionID = model.CustomAssessmentQuestionID; + setBulkUploadData(data); + return RedirectToAction("AddQuestionsToWhichCompetencies"); + } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { TempData.Clear(); diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 69800b0187..2a8e40562d 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -28,7 +28,7 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } public List DefaultQuestionIDs { get; set; } = []; - public List AssessmentQuestionIDs { get; set; } = []; + public int CustomAssessmentQuestionID { get; set; } public bool AddDefaultAssessmentQuestions { get; set; } = true; public bool AddCustomAssessmentQuestion { get; set; } = false; public int CompetenciesToProcessCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs index aa1e0ab2ce..5db7d3b97a 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs @@ -7,6 +7,6 @@ public class AddAssessmentQuestionsFormData public bool AddDefaultAssessmentQuestions { get; set; } public bool AddCustomAssessmentQuestion { get; set; } public List DefaultAssessmentQuestionIDs { get; set; } - public List OtherAssessmentQuestionIDs { get; set; } + public int CustomAssessmentQuestionID { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml index e3a0e29540..ae9cdec7c2 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml @@ -33,11 +33,11 @@
      @if (errorHasOccurred) { - + } -
      +
      -
      +

      @ViewData["Title"] @@ -50,37 +50,55 @@ @if (Model.DefaultQuestions.Any()) {
      - + -
      -
      +
      + choose which default questions associated with this framework to add to the imported and/or updated @Model.FrameworkVocabularyPlural.ToLower() +
      +
      -
      -
      - @foreach (var (defaultQuestion, index) in Model.DefaultQuestions.Select((t, i) => (t, i))) - { -
      - - -
      - } +
      +
      + @foreach (var (defaultQuestion, index) in Model.DefaultQuestions.Select((t, i) => (t, i))) + { +
      + + +
      + } +
      } +
      + + +
      +
      + + + +

      +
      +
    From cdb22d1711cff67ed6938af9d168f7a7b51b8b15 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 2 Jan 2025 11:47:10 +0000 Subject: [PATCH 27/71] Tweaks post data processing to handle storing false values --- .../FrameworksController/ImportCompetencies.cs | 18 ++++++++++++++++-- .../Models/BulkCompetenciesData.cs | 2 +- .../Import/AddAssessmentQuestionsFormData.cs | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 62dbd65315..1e77c8f05c 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -136,9 +136,23 @@ public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model { var data = GetBulkUploadData(); data.AddDefaultAssessmentQuestions = model.AddDefaultAssessmentQuestions; + if (model.AddDefaultAssessmentQuestions) + { + data.DefaultQuestionIDs = model.DefaultAssessmentQuestionIDs; + } + else + { + data.DefaultQuestionIDs = []; + } data.AddCustomAssessmentQuestion = model.AddCustomAssessmentQuestion; - data.DefaultQuestionIDs = model.DefaultAssessmentQuestionIDs; - data.CustomAssessmentQuestionID = model.CustomAssessmentQuestionID; + if (model.AddCustomAssessmentQuestion) + { + data.CustomAssessmentQuestionID = model.CustomAssessmentQuestionID; + } + else + { + data.CustomAssessmentQuestionID = null; + } setBulkUploadData(data); return RedirectToAction("AddQuestionsToWhichCompetencies"); } diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 2a8e40562d..bf7cb9b61d 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -28,7 +28,7 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } public List DefaultQuestionIDs { get; set; } = []; - public int CustomAssessmentQuestionID { get; set; } + public int? CustomAssessmentQuestionID { get; set; } public bool AddDefaultAssessmentQuestions { get; set; } = true; public bool AddCustomAssessmentQuestion { get; set; } = false; public int CompetenciesToProcessCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs index 5db7d3b97a..4b90f1198c 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsFormData.cs @@ -7,6 +7,6 @@ public class AddAssessmentQuestionsFormData public bool AddDefaultAssessmentQuestions { get; set; } public bool AddCustomAssessmentQuestion { get; set; } public List DefaultAssessmentQuestionIDs { get; set; } - public int CustomAssessmentQuestionID { get; set; } + public int? CustomAssessmentQuestionID { get; set; } } } From ed5bc7359757b1deebcda599abc47cfe0e5de360 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 2 Jan 2025 17:19:57 +0000 Subject: [PATCH 28/71] TD-5158 Implements add questions to which competencies form --- .../ImportCompetencies.cs | 47 ++++++++-- .../Models/BulkCompetenciesData.cs | 5 +- ...ddQuestionsToWhichCompetenciesViewModel.cs | 41 +++++++++ .../ImportCompetenciesPreProcessViewModel.cs | 5 -- .../AddQuestionsToWhichCompetencies.cshtml | 87 +++++++++++++++++++ 5 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 1e77c8f05c..1a6150492c 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,14 +1,14 @@ -using ClosedXML.Excel; +using AngleSharp.Dom; +using ClosedXML.Excel; using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; -using DocumentFormat.OpenXml.Office2010.ExcelAc; +using DocumentFormat.OpenXml.EMMA; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.DotNet.Scaffolding.Shared.Project; using System.Collections.Generic; using System.IO; using System.Linq; @@ -85,7 +85,7 @@ public IActionResult ImportCompleted() var resultsModel = new ImportCompetenciesPreProcessViewModel(results, data) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; - data.CompetenciesToUpdateCount = resultsModel.CompetenciesToUpdateCount; + data.CompetenciesToUpdateCount = resultsModel.ToUpdateOrSkipCount; setBulkUploadData(data); return View("Developer/Import/ImportCompleted", resultsModel); } @@ -153,8 +153,45 @@ public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model { data.CustomAssessmentQuestionID = null; } + if (data.CompetenciesToUpdateCount > 0) + { + data.AddAssessmentQuestionsOption = 2; + setBulkUploadData(data); + return RedirectToAction("AddQuestionsToWhichCompetencies", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + else + { + data.AddAssessmentQuestionsOption = 1; + setBulkUploadData(data); + return RedirectToAction("Summary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + } + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions/Competencies")] + public IActionResult AddQuestionsToWhichCompetencies() + { + var data = GetBulkUploadData(); + var model = new AddQuestionsToWhichCompetenciesViewModel + ( + data.FrameworkId, + data.FrameworkName, + data.FrameworkVocubulary, + data.DefaultQuestionIDs, + data.CustomAssessmentQuestionID, + data.AddAssessmentQuestionsOption, + data.CompetenciesToProcessCount, + data.CompetenciesToAddCount, + data.CompetenciesToUpdateCount + ); + return View("Developer/Import/AddQuestionsToWhichCompetencies", model); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions/Competencies")] + public IActionResult AddQuestionsToWhichCompetencies(int AddAssessmentQuestionsOption) + { + var data = GetBulkUploadData(); + data.AddAssessmentQuestionsOption = AddAssessmentQuestionsOption; setBulkUploadData(data); - return RedirectToAction("AddQuestionsToWhichCompetencies"); + return RedirectToAction("Summary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index bf7cb9b61d..738acbf9ea 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -27,10 +27,11 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public int AdminUserId { get; set; } public bool IsNotBlank { get; set; } public string CompetenciesFileName { get; set; } - public List DefaultQuestionIDs { get; set; } = []; - public int? CustomAssessmentQuestionID { get; set; } public bool AddDefaultAssessmentQuestions { get; set; } = true; public bool AddCustomAssessmentQuestion { get; set; } = false; + public List DefaultQuestionIDs { get; set; } = []; + public int? CustomAssessmentQuestionID { get; set; } + public int AddAssessmentQuestionsOption { get; set; } //1 = only added, 2 = added and updated, 3 = all uploaded public int CompetenciesToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } public int CompetenciesToUpdateCount { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs new file mode 100644 index 0000000000..d6cbae84ca --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs @@ -0,0 +1,41 @@ +using DigitalLearningSolutions.Web.Helpers; +using System.Collections.Generic; +using System.Runtime.Versioning; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class AddQuestionsToWhichCompetenciesViewModel + { + public AddQuestionsToWhichCompetenciesViewModel + ( + int frameworkId, + string frameworkName, + string frameworkVocabulary, + List defaultQuestions, + int? customQuestionId, + int addAssessmentQuestionsOption, + int competenciesToProcessCount, + int competenciesToAddCount, + int competenciesToUpdateCount) + { + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + TotalQuestions = defaultQuestions.Count + (customQuestionId != null ? 1 : 0); + AddAssessmentQuestionsOption = addAssessmentQuestionsOption; + CompetenciesToProcessCount = competenciesToProcessCount; + CompetenciesToAddCount = competenciesToAddCount; + CompetenciesToUpdateCount = competenciesToUpdateCount; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int TotalQuestions { get; set; } + public int AddAssessmentQuestionsOption { get; set; } = 1; //1 = only added, 2 = added and updated, 3 = all uploaded + public int CompetenciesToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToUpdateCount { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs index f92f056115..67f604723a 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -18,8 +18,6 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet ToProcessCount = bulkCompetenciesResult.ProcessedCount; CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; ToUpdateOrSkipCount = bulkCompetenciesResult.CompetencyUpdatedCount; - CompetencyGroupsToAddCount = bulkCompetenciesResult.GroupAddedCount; - CompetencyGroupsToUpdateCount = bulkCompetenciesResult.GroupUpdatedCount; Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason, FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary)))); } public string? FrameworkName { get; set; } @@ -30,9 +28,6 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet public int ErrorCount => Errors.Count(); public int ToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } - public int CompetenciesToUpdateCount { get; set; } - public int CompetencyGroupsToAddCount { get; set; } - public int CompetencyGroupsToUpdateCount { get; set; } public int ToUpdateOrSkipCount { get; set; } public string? ImportFile { get; set; } public bool IsNotBlank { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml new file mode 100644 index 0000000000..bb5a3b3a5f --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml @@ -0,0 +1,87 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model AddQuestionsToWhichCompetenciesViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
    +
    +
    +
    +
    + +

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

    +
    +
    + Choose which @Model.FrameworkVocabularyPlural.ToLower() you want to add the @Model.TotalQuestions assessment questions to +
    +
    + Select one option +
    +
    +
    + @if (Model.CompetenciesToAddCount > 0) + { +
    + + +
    + @Model.TotalQuestions assessment questions will be added to the @Model.CompetenciesToAddCount new @Model.FrameworkVocabularyPlural.ToLower() +
    +
    + } +
    + + +
    + @(Model.CompetenciesToAddCount > 0 ? Model.CompetenciesToAddCount + " new @Model.FrameworkVocabularyPlural.ToLower() and only those of the " : "Only those of the ") @Model.CompetenciesToUpdateCount existing @Model.FrameworkVocabularyPlural.ToLower() that have been modified in the sheet will have the assessment questions added +
    +
    +
    + + +
    + All @(Model.CompetenciesToProcessCount) @Model.FrameworkVocabularyPlural.ToLower() in the sheet that are will have the assessment questions added to them +
    +
    +
    +
    + +
    + Back + +
    + +
    +
    +
    From 81ec4fb59de0d420ef3c1918b147727073911132 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 2 Jan 2025 17:22:41 +0000 Subject: [PATCH 29/71] Removes spurious usings --- .../Controllers/FrameworksController/ImportCompetencies.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 1a6150492c..68d3e26d6f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -1,11 +1,9 @@ -using AngleSharp.Dom; -using ClosedXML.Excel; +using ClosedXML.Excel; using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.Services; using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import; -using DocumentFormat.OpenXml.EMMA; using GDS.MultiPageFormData.Enums; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; From 4fe8778f750984884a10503bd4943e6ba3a2fc14 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 3 Jan 2025 11:24:40 +0000 Subject: [PATCH 30/71] TD-5219 Improvements to pre-processing summary and errors --- .../Frameworks/Import/BulkCompetency.cs | 2 +- .../Frameworks/Import/CompetencyTableRow.cs | 9 +++-- .../Import/ImportCompetenciesResult.cs | 18 ++++++++++ .../ImportCompetencies.cs | 2 +- .../ImportCompetenciesFromFileService.cs | 34 +++++++++++++------ .../ImportCompetenciesPreProcessViewModel.cs | 8 ++++- .../Developer/Import/ImportCompleted.cshtml | 2 ++ 7 files changed, 59 insertions(+), 16 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs index 298639d4c7..e59ba301be 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/BulkCompetency.cs @@ -8,7 +8,7 @@ namespace DigitalLearningSolutions.Data.Models.Frameworks.Import { public class BulkCompetency { - public int? id { get; set; } + public int? ID { get; set; } public string? CompetencyGroup { get; set; } public string? GroupDescription { get; set; } public string? Competency { get; set; } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 1f99fa9550..afed88f1e6 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -12,7 +12,8 @@ public enum RowStatus CompetencyGroupInserted, CompetencyGroupUpdated, CompetencyGroupAndCompetencyUpdated, - InvalidAlwaysShowDescription + InvalidAlwaysShowDescription, + InvalidId } public class CompetencyTableRow : BulkCompetency { @@ -25,7 +26,7 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) } RowNumber = row.RowNumber(); - id = row.Cell(1).GetValue(); + ID = row.Cell(1).GetValue(); CompetencyGroup = row.Cell(2).GetValue(); GroupDescription = row.Cell(3).GetValue(); Competency = row.Cell(4).GetValue(); @@ -57,6 +58,10 @@ public bool Validate() { Error = ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription; } + else if (RowStatus == RowStatus.InvalidId) + { + Error = ImportCompetenciesResult.ErrorReason.InvalidId; + } return !Error.HasValue; } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 53f0beed1a..1f99a35a82 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -26,6 +26,21 @@ IReadOnlyCollection competencyTableRows GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); + FlagCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv)) + .SelectMany(static row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Count(); + DistinctFlagsCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv)) + .SelectMany(row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .Select(flag => flag.Trim()) + .Distinct() + .Count(); + CompetencyGroupCount = competencyTableRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(static row => row.CompetencyGroup) + .Distinct() + .Count(); } public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; } @@ -35,5 +50,8 @@ IReadOnlyCollection competencyTableRows public int GroupAddedCount { get; set; } public int GroupUpdatedCount { get; set; } public int SkippedCount { get; set; } + public int FlagCount { get; set; } + public int DistinctFlagsCount { get; set; } + public int CompetencyGroupCount { get; set; } } } diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 68d3e26d6f..ecbb7a2a9e 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -79,7 +79,7 @@ public IActionResult ImportCompleted() var workbook = new XLWorkbook(filePath); try { - var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook, data.FrameworkVocubulary); + var results = importCompetenciesFromFileService.PreProcessCompetenciesTable(workbook, data.FrameworkVocubulary, data.FrameworkId); var resultsModel = new ImportCompetenciesPreProcessViewModel(results, data) { IsNotBlank = data.IsNotBlank, TabName = data.TabName }; data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index d8611a86f8..c5c82281f6 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -16,7 +16,7 @@ namespace DigitalLearningSolutions.Web.Services public interface IImportCompetenciesFromFileService { byte[] GetCompetencyFileForFramework(int frameworkId, bool isBlank, string vocabulary); - public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary); + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary, int frameworkId); public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService @@ -30,27 +30,36 @@ IFrameworkService frameworkService { this.frameworkService = frameworkService; } - public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary) + public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary, int frameworkId) { var table = OpenCompetenciesTable(workbook, vocabulary); var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); + var existingCompetencies = frameworkService.GetBulkCompetenciesForFramework(frameworkId); + var existingIds = existingCompetencies.Select(bc => (int)bc.ID).ToList(); foreach (var competencyRow in competencyRows) { - PreProcessCompetencyRow(competencyRow); + PreProcessCompetencyRow(competencyRow, existingIds); } return new ImportCompetenciesResult(competencyRows); } - private void PreProcessCompetencyRow(CompetencyTableRow competencyRow) + private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List existingIds) { - competencyRow.Validate(); - if (competencyRow.id == null) + if (competencyRow.ID == null) { competencyRow.RowStatus = RowStatus.CompetencyInserted; } else { - competencyRow.RowStatus = RowStatus.CompetencyUpdated; + if (!existingIds.Contains((int)(competencyRow?.ID))) + { + competencyRow.RowStatus = RowStatus.InvalidId; + } + else + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } } + competencyRow.Validate(); } public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary) { @@ -77,7 +86,12 @@ internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId) { var competenciesRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); - + + var competencyGroupCount = competenciesRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(row => row.CompetencyGroup) + .Distinct() + .Count(); foreach (var competencyRow in competenciesRows) { maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, competencyRow); @@ -167,13 +181,11 @@ public byte[] GetCompetencyFileForFramework(int frameworkId, bool blank, string } private void PopulateCompetenciesSheet(IXLWorkbook workbook, int frameworkId, bool blank, string vocabulary) { - - var competencyRecords = frameworkService.GetBulkCompetenciesForFramework(blank ? 0 : frameworkId); var competencies = competencyRecords.Select( x => new { - ID = x.id, + x.ID, x.CompetencyGroup, x.GroupDescription, x.Competency, diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs index 67f604723a..343ad1a74c 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -19,6 +19,9 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; ToUpdateOrSkipCount = bulkCompetenciesResult.CompetencyUpdatedCount; Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason, FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary)))); + FlagCount = bulkCompetenciesResult.FlagCount; + DistinctFlagsCount = bulkCompetenciesResult.DistinctFlagsCount; + CompetencyGroupCount = bulkCompetenciesResult.CompetencyGroupCount; } public string? FrameworkName { get; set; } public int PublishStatusID { get; set; } @@ -32,6 +35,9 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet public string? ImportFile { get; set; } public bool IsNotBlank { get; set; } public string TabName { get; set; } + public int FlagCount { get; set; } + public int DistinctFlagsCount { get; set; } + public int CompetencyGroupCount { get; set; } private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason, string vocabularySingular) { @@ -42,7 +48,7 @@ private static string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReas ImportCompetenciesResult.ErrorReason.MissingCompetencyName => vocabularySingular + " is blank. " + vocabularySingular + " is a required field and cannot be left blank", ImportCompetenciesResult.ErrorReason.InvalidId => - "The ID provided does not match a " + vocabularySingular + " ID in this Framework", + "The ID provided does not match a " + vocabularySingular + " ID in this Framework. Leave the ID column blank for new competencies.", ImportCompetenciesResult.ErrorReason.TooLongCompetencyName => vocabularySingular + " must be 500 characters or less.", ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription => diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index fcabc89014..35e6884155 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -35,6 +35,8 @@
  • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
  • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • +
  • In @Model.CompetencyGroupCount @Model.FrameworkVocabularySingular.ToLower() groups
  • +
  • With a total of @Model.FlagCount flags assigned to Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
  • @if (Model.ErrorCount > 0) {
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • From 0b4d3c1678c3d62c17656d8579e9daa77cdc1c35 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 3 Jan 2025 11:28:30 +0000 Subject: [PATCH 31/71] Adds missing @ to indicate inline code --- .../Views/Frameworks/Developer/Import/ImportCompleted.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 35e6884155..4a967c11fb 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -36,7 +36,7 @@
  • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
  • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
  • In @Model.CompetencyGroupCount @Model.FrameworkVocabularySingular.ToLower() groups
  • -
  • With a total of @Model.FlagCount flags assigned to Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
  • +
  • With a total of @Model.FlagCount flags assigned to @Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
  • @if (Model.ErrorCount > 0) {
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
  • From b7bb294c48fd8e8ad3f17313f448bc05a41e9542 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 3 Jan 2025 16:25:22 +0000 Subject: [PATCH 32/71] TD-5220 Implements store competency ordering option form --- .../Frameworks/Import/CompetencyTableRow.cs | 1 + .../Import/ImportCompetenciesResult.cs | 4 +- .../ImportCompetencies.cs | 26 +++++++ .../Models/BulkCompetenciesData.cs | 2 + .../ImportCompetenciesFromFileService.cs | 19 ++++- .../Import/AddAssessmentQuestionsViewModel.cs | 2 + ...ddQuestionsToWhichCompetenciesViewModel.cs | 1 + .../ApplyCompetencyOrderingViewModel.cs | 30 ++++++++ .../ImportCompetenciesPreProcessViewModel.cs | 2 + .../Import/AddAssessmentQuestions.cshtml | 3 +- .../AddQuestionsToWhichCompetencies.cshtml | 4 +- .../Import/ApplyCompetencyOrdering.cshtml | 75 +++++++++++++++++++ .../Developer/Import/ImportCompleted.cshtml | 21 ++++-- .../Frameworks/Developer/Import/Index.cshtml | 2 +- 14 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index afed88f1e6..16ea0b9e52 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -9,6 +9,7 @@ public enum RowStatus CompetencyGroupAndCompetencyInserted, CompetencyInserted, CompetencyUpdated, + CompetencyUpdatedAndReordered, CompetencyGroupInserted, CompetencyGroupUpdated, CompetencyGroupAndCompetencyUpdated, diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 1f99a35a82..15f5136274 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -22,7 +22,8 @@ IReadOnlyCollection competencyTableRows { ProcessedCount = competencyTableRows.Count; CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated); + CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated | dr.RowStatus == RowStatus.CompetencyUpdatedAndReordered); + CompetencyReorderedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdatedAndReordered); GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); @@ -47,6 +48,7 @@ IReadOnlyCollection competencyTableRows public int ProcessedCount { get; set; } public int CompetencyAddedCount { get; set; } public int CompetencyUpdatedCount { get; set; } + public int CompetencyReorderedCount { get; set; } public int GroupAddedCount { get; set; } public int GroupUpdatedCount { get; set; } public int SkippedCount { get; set; } diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index ecbb7a2a9e..fa2d3a5214 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -84,6 +84,7 @@ public IActionResult ImportCompleted() data.CompetenciesToProcessCount = resultsModel.ToProcessCount; data.CompetenciesToAddCount = resultsModel.CompetenciesToAddCount; data.CompetenciesToUpdateCount = resultsModel.ToUpdateOrSkipCount; + data.CompetenciesToReorderCount = results.CompetencyReorderedCount; setBulkUploadData(data); return View("Developer/Import/ImportCompleted", resultsModel); } @@ -93,6 +94,30 @@ public IActionResult ImportCompleted() return View("Developer/Import/ImportFailed"); } } + [Route("/Framework/{frameworkId}/{tabname}/Import/Ordering")] + public IActionResult ApplyCompetencyOrdering() + { + var data = GetBulkUploadData(); + if (data.CompetenciesToReorderCount > 0) + { + var model = new ApplyCompetencyOrderingViewModel(data.FrameworkId, data.FrameworkName, data.FrameworkVocubulary, data.CompetenciesToReorderCount, data.ReorderCompetenciesOption); + return View("Developer/Import/ApplyCompetencyOrdering", model); + } + return RedirectToAction("AddAssessmentQuestions", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/Ordering")] + public IActionResult ApplyCompetencyOrdering(int reorderCompetenciesOption) + { + var data = GetBulkUploadData(); + + if (data.ReorderCompetenciesOption != reorderCompetenciesOption) + { + data.ReorderCompetenciesOption = reorderCompetenciesOption; + setBulkUploadData(data); + } + return RedirectToAction("AddAssessmentQuestions", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions")] public IActionResult AddAssessmentQuestions() { @@ -119,6 +144,7 @@ public IActionResult AddAssessmentQuestions() data.PublishStatusID, data.CompetenciesToAddCount, data.CompetenciesToUpdateCount, + data.CompetenciesToReorderCount, defaultQuestions, questionSelectList ); diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 738acbf9ea..2d3ce9e4ac 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -35,6 +35,8 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public int CompetenciesToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } public int CompetenciesToUpdateCount { get; set; } + public int CompetenciesToReorderCount { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order public int LastRowProcessed { get; set; } public int SubtotalCompetenciesAdded { get; set; } public int SubtotalCompetenciesUpdated { get; set; } diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index c5c82281f6..592d17c015 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -34,15 +34,16 @@ public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook { var table = OpenCompetenciesTable(workbook, vocabulary); var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); + var newCompetencyIds = competencyRows.Select(row => row.ID ?? 0).ToList(); var existingCompetencies = frameworkService.GetBulkCompetenciesForFramework(frameworkId); var existingIds = existingCompetencies.Select(bc => (int)bc.ID).ToList(); foreach (var competencyRow in competencyRows) { - PreProcessCompetencyRow(competencyRow, existingIds); + PreProcessCompetencyRow(competencyRow, newCompetencyIds, existingIds); } return new ImportCompetenciesResult(competencyRows); } - private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List existingIds) + private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List newIds, List existingIds) { if (competencyRow.ID == null) { @@ -50,13 +51,23 @@ private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List } else { - if (!existingIds.Contains((int)(competencyRow?.ID))) + var id = (int)(competencyRow?.ID); + if (!existingIds.Contains(id)) { competencyRow.RowStatus = RowStatus.InvalidId; } else { - competencyRow.RowStatus = RowStatus.CompetencyUpdated; + int originalIndex = existingIds.IndexOf(id); + int newIndex = newIds.IndexOf(id); + if(originalIndex == newIndex) + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } + else + { + competencyRow.RowStatus = RowStatus.CompetencyUpdatedAndReordered; + } } } competencyRow.Validate(); diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs index bfd1cf3d12..204f7e49e2 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddAssessmentQuestionsViewModel.cs @@ -12,6 +12,7 @@ public class AddAssessmentQuestionsViewModel( int publishStatusId, int newCompetencies, int existingCompetencies, + int competenciesToReorderCount, IEnumerable defaultQuestions, SelectList questionSelectList ) : AddAssessmentQuestionsFormData @@ -23,6 +24,7 @@ SelectList questionSelectList public int PublishStatusID { get; set; } = publishStatusId; public int NewCompetencies { get; set; } = newCompetencies; public int ExistingCompetencies { get; set; } = existingCompetencies; + public int CompetenciesToReorderCount { get; set; } = competenciesToReorderCount; public IEnumerable? DefaultQuestions { get; set; } = defaultQuestions; public SelectList? QuestionSelectList { get; set; } = questionSelectList; } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs index d6cbae84ca..fa86ccc30a 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/AddQuestionsToWhichCompetenciesViewModel.cs @@ -37,5 +37,6 @@ public AddQuestionsToWhichCompetenciesViewModel public int CompetenciesToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } public int CompetenciesToUpdateCount { get; set; } + } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs new file mode 100644 index 0000000000..ee912de97f --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ApplyCompetencyOrderingViewModel.cs @@ -0,0 +1,30 @@ +using DigitalLearningSolutions.Web.Helpers; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class ApplyCompetencyOrderingViewModel + { + public ApplyCompetencyOrderingViewModel + ( + int frameworkId, + string frameworkName, + string frameworkVocabulary, + int competenciesToReorderCount, + int reorderCompetenciesOption + ) + { + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); + CompetenciesToReorderCount = competenciesToReorderCount; + ReorderCompetenciesOption = reorderCompetenciesOption; + } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order + public int CompetenciesToReorderCount { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs index 343ad1a74c..96b7935ea3 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesPreProcessViewModel.cs @@ -17,6 +17,7 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(bulkCompetenciesData.FrameworkVocubulary); ToProcessCount = bulkCompetenciesResult.ProcessedCount; CompetenciesToAddCount = bulkCompetenciesResult.CompetencyAddedCount; + CompetenciesToReorderCount = bulkCompetenciesResult.CompetencyReorderedCount; ToUpdateOrSkipCount = bulkCompetenciesResult.CompetencyUpdatedCount; Errors = bulkCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason, FrameworkVocabularyHelper.VocabularySingular(bulkCompetenciesData.FrameworkVocubulary)))); FlagCount = bulkCompetenciesResult.FlagCount; @@ -31,6 +32,7 @@ public ImportCompetenciesPreProcessViewModel(ImportCompetenciesResult bulkCompet public int ErrorCount => Errors.Count(); public int ToProcessCount { get; set; } public int CompetenciesToAddCount { get; set; } + public int CompetenciesToReorderCount { get; set; } public int ToUpdateOrSkipCount { get; set; } public string? ImportFile { get; set; } public bool IsNotBlank { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml index ae9cdec7c2..9e6c443d05 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddAssessmentQuestions.cshtml @@ -96,9 +96,10 @@
    + Back - +
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml index bb5a3b3a5f..0dcf3bc1f2 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml @@ -29,7 +29,7 @@
    -
    +

    @@ -81,7 +81,7 @@ Back - +

    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml new file mode 100644 index 0000000000..398343f461 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml @@ -0,0 +1,75 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import + +@model ApplyCompetencyOrderingViewModel + +@{ + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Add Assessment Questions" : "Add Assessment Questions"; + var cancelLinkData = Html.GetRouteValues(); +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
    +
    +
    +
    +
    + +

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

    +
    +
    + Your uploaded file includes changes to the sequence of existing @Model.FrameworkVocabularyPlural.ToLower(). Choose whether to store the changes to @Model.FrameworkVocabularySingular.ToLower() sequence during update. +
    +
    + Select an option +
    +
    +
    +
    + + +
    + @Model.FrameworkVocabularyPlural will be updated if they have changed but there sequence/order in the framework will not change. +
    +
    +
    + + +
    + The sequence of @Model.FrameworkVocabularyPlural.ToLower() in the framework will be changed to reflect the order in the sheet during processing. +
    +
    +
    +
    + +
    + Back + +
    + +
    +
    +
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 4a967c11fb..961829e3d6 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -32,23 +32,30 @@

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

      -
    • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") to process
    • -
    • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
    • -
    • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
    • -
    • In @Model.CompetencyGroupCount @Model.FrameworkVocabularySingular.ToLower() groups
    • -
    • With a total of @Model.FlagCount flags assigned to @Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
    • +
    • @Model.ToProcessCount @(Model.ToProcessCount == 1 ? "row" : "rows") uploaded
    • @if (Model.ErrorCount > 0) {
    • @Model.ErrorCount @(Model.ErrorCount == 1 ? "row" : "rows") containing errors that cannot be processed
    • } else { +
    • @Model.CompetenciesToAddCount new @(Model.CompetenciesToAddCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) to add
    • +
    • @Model.ToUpdateOrSkipCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ToUpdateOrSkipCount == 1 ? "record" : "records") to update (or skip if unchanged)
    • +
    • In @Model.CompetencyGroupCount @Model.FrameworkVocabularySingular.ToLower() groups
    • +
    • With @Model.FlagCount flags assigned to @Model.FrameworkVocabularyPlural.ToLower() (@Model.DistinctFlagsCount distinct flags)
    • + @if (Model.CompetenciesToReorderCount > 0) + { + +
    • + @Model.CompetenciesToReorderCount existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next. +
    • + }
    • No errors
    • }
    @if (Model.ErrorCount == 0) { - Continue + Continue } else { @@ -85,7 +92,7 @@ } - +
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index a11a391b24..78a5fb95bd 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -110,7 +110,7 @@ - +
    From 6fd7386cb9c22a4f69f94f02e962e3e5ed4eecf6 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 6 Jan 2025 08:47:24 +0000 Subject: [PATCH 33/71] TD-5220 passes ids of uploaded competencies to get order of stored competencies --- .../DataServices/FrameworkDataService.cs | 12 ++++++++++++ .../Services/FrameworkService.cs | 6 ++++++ .../Services/ImportCompetenciesFromFileService.cs | 3 +-- .../Developer/Import/ImportCompleted.cshtml | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index f43879edcf..63a8bc31a4 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -100,6 +100,7 @@ bool zeroBased Competency? GetFrameworkCompetencyForPreview(int frameworkCompetencyId); IEnumerable GetBulkCompetenciesForFramework(int frameworkId); + List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds); // Comments: IEnumerable GetCommentsForFrameworkId(int frameworkId, int adminId); @@ -2429,5 +2430,16 @@ Competencies AS c INNER JOIN } } + public List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds) + { + return connection.Query( + @"SELECT fc.ID + FROM FrameworkCompetencies AS fc INNER JOIN + FrameworkCompetencyGroups AS fcg ON fc.FrameworkCompetencyGroupID = fcg.ID + WHERE (fc.FrameworkID = @frameworkId) AND (fc.ID IN @frameworkCompetencyIds) + ORDER BY fcg.Ordering, fc.Ordering", + new { frameworkId, frameworkCompetencyIds } + ).ToList(); + } } } diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 2a49a0a8b3..8c7de151d9 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -57,6 +57,7 @@ public interface IFrameworkService int GetMaxFrameworkCompetencyGroupID(); IEnumerable GetBulkCompetenciesForFramework(int frameworkId); + List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds); // Assessment questions: IEnumerable GetAllCompetencyQuestions(int adminId); @@ -386,6 +387,11 @@ public IEnumerable GetBulkCompetenciesForFramework(int framework return frameworkDataService.GetBulkCompetenciesForFramework(frameworkId); } + public List GetFrameworkCompetencyOrder(int frameworkId, List frameworkCompetencyIds) + { + return frameworkDataService.GetFrameworkCompetencyOrder(frameworkId, frameworkCompetencyIds); + } + public CollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId) { return frameworkDataService.GetCollaboratorNotification(id, invitedByAdminId); diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 592d17c015..a26793b651 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -35,8 +35,7 @@ public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook var table = OpenCompetenciesTable(workbook, vocabulary); var competencyRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); var newCompetencyIds = competencyRows.Select(row => row.ID ?? 0).ToList(); - var existingCompetencies = frameworkService.GetBulkCompetenciesForFramework(frameworkId); - var existingIds = existingCompetencies.Select(bc => (int)bc.ID).ToList(); + var existingIds = frameworkService.GetFrameworkCompetencyOrder(frameworkId, newCompetencyIds); foreach (var competencyRow in competencyRows) { PreProcessCompetencyRow(competencyRow, newCompetencyIds, existingIds); diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 961829e3d6..b127e53979 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -59,7 +59,7 @@ } else { -

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

    +

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

    Error: @Model.ErrorCount @Model.FrameworkVocabularySingular.ToLower() @(Model.ErrorCount == 1 ? "row" : "rows") contain errors and cannot be processed From 885067dda5cee299920e7b8efbf8a0b53a4236cb Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 6 Jan 2025 09:10:57 +0000 Subject: [PATCH 34/71] TD-5220 Implements CancelImport method to clear down uploaded file and temp data --- .../FrameworksController/ImportCompetencies.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index fa2d3a5214..133efd717f 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -217,6 +217,15 @@ public IActionResult AddQuestionsToWhichCompetencies(int AddAssessmentQuestionsO setBulkUploadData(data); return RedirectToAction("Summary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); } + [Route("CancelImport")] + public IActionResult CancelImport() + { + var data = GetBulkUploadData(); + var frameworkId = data.FrameworkId; + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + TempData.Clear(); + return RedirectToAction("ViewFramework", new { frameworkId, tabname = "Structure" }); + } private void setupBulkUploadData(int frameworkId, int adminUserID, string competenciessFileName, string tabName, bool isNotBlank) { TempData.Clear(); From 5ab4073e5c631502e00497959d4b04a057af707f Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 6 Jan 2025 16:38:10 +0000 Subject: [PATCH 35/71] TD-5162 Implements import summary screen --- .../ImportCompetencies.cs | 11 +- .../Import/ImportSummaryViewModel.cs | 41 +++++++ .../AddQuestionsToWhichCompetencies.cshtml | 4 +- .../Developer/Import/ImportSummary.cshtml | 110 ++++++++++++++++++ 4 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 133efd717f..08ff1eb0ff 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -187,7 +187,7 @@ public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model { data.AddAssessmentQuestionsOption = 1; setBulkUploadData(data); - return RedirectToAction("Summary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); } } [Route("/Framework/{frameworkId}/{tabname}/Import/AssessmentQuestions/Competencies")] @@ -215,7 +215,14 @@ public IActionResult AddQuestionsToWhichCompetencies(int AddAssessmentQuestionsO var data = GetBulkUploadData(); data.AddAssessmentQuestionsOption = AddAssessmentQuestionsOption; setBulkUploadData(data); - return RedirectToAction("Summary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } + [Route("/Framework/{frameworkId}/{tabname}/Import/Summary")] + public IActionResult ImportSummary() + { + var data = GetBulkUploadData(); + var model = new ImportSummaryViewModel(data); + return View("Developer/Import/ImportSummary", model); } [Route("CancelImport")] public IActionResult CancelImport() diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs new file mode 100644 index 0000000000..f59c14f72d --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportSummaryViewModel.cs @@ -0,0 +1,41 @@ +using DigitalLearningSolutions.Web.Helpers; +using DigitalLearningSolutions.Web.Models; +using System.Collections.Generic; + +namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +{ + public class ImportSummaryViewModel + { + public ImportSummaryViewModel(BulkCompetenciesData data) + { + FrameworkName = data.FrameworkName; + PublishStatusID = data.PublishStatusID; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(data.FrameworkVocubulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(data.FrameworkVocubulary); + ToProcessCount = data.CompetenciesToProcessCount; + CompetenciesToAddCount = data.CompetenciesToAddCount; + CompetenciesToReorderCount = data.CompetenciesToReorderCount; + ToUpdateOrSkipCount = data.CompetenciesToUpdateCount; + AddAssessmentQuestionsOption = data.AddAssessmentQuestionsOption; + AddDefaultAssessmentQuestions = data.AddDefaultAssessmentQuestions; + AddCustomAssessmentQuestion = data.AddCustomAssessmentQuestion; + DefaultAssessmentQuestionIDs = data.DefaultQuestionIDs; + CustomAssessmentQuestionID = data.CustomAssessmentQuestionID; + ReorderCompetenciesOption = data.ReorderCompetenciesOption; + } + public string? FrameworkName { get; set; } + public int PublishStatusID { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } + public int ToProcessCount { get; set; } + public int CompetenciesToAddCount { get; set; } + public int CompetenciesToReorderCount { get; set; } + public int ToUpdateOrSkipCount { get; set; } + public int AddAssessmentQuestionsOption { get; set; } = 1; //1 = only added, 2 = added and updated, 3 = all uploaded + public bool AddDefaultAssessmentQuestions { get; set; } + public bool AddCustomAssessmentQuestion { get; set; } + public List DefaultAssessmentQuestionIDs { get; set; } + public int? CustomAssessmentQuestionID { get; set; } + public int ReorderCompetenciesOption { get; set; } = 1; //1 = ignore order, 2 = apply order + } +} diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml index 0dcf3bc1f2..eef8dbabe8 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/AddQuestionsToWhichCompetencies.cshtml @@ -59,10 +59,10 @@
    - @(Model.CompetenciesToAddCount > 0 ? Model.CompetenciesToAddCount + " new @Model.FrameworkVocabularyPlural.ToLower() and only those of the " : "Only those of the ") @Model.CompetenciesToUpdateCount existing @Model.FrameworkVocabularyPlural.ToLower() that have been modified in the sheet will have the assessment questions added + @(Model.CompetenciesToAddCount > 0 ? Model.CompetenciesToAddCount + $" new {@Model.FrameworkVocabularyPlural.ToLower()} and only those of the " : "Only those of the ") @Model.CompetenciesToUpdateCount existing @Model.FrameworkVocabularyPlural.ToLower() that have been modified in the sheet will have the assessment questions added
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml new file mode 100644 index 0000000000..7fd3ada28f --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -0,0 +1,110 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.Frameworks.Import +@using Microsoft.Extensions.Configuration +@model ImportSummaryViewModel +@{ + ViewData["Title"] = "Framework - Import Competencies"; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; + var errorHasOccurred = !ViewData.ModelState.IsValid; + var cancelLinkData = Html.GetRouteValues(); + var process = Model.CompetenciesToAddCount > 0 && Model.ToUpdateOrSkipCount > 0 ? "add and update" : Model.CompetenciesToAddCount > 0 ? "add" : "update"; + var addQsTo = Model.AddAssessmentQuestionsOption == 1 ? "new" : Model.AddAssessmentQuestionsOption == 2 ? "new and updated" : "all"; +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
    +
    +

    @Model.FrameworkVocabularyPlural import summary

    +

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

    +

    Upload summary

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

    Additional processing steps

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

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

    +
    +
    + Back + +
    + +
    +
    From 9daa34d0ad735fadaa478d5ab79a5f6b7860875e Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 6 Jan 2025 16:41:13 +0000 Subject: [PATCH 36/71] formatter changes --- .../Views/Frameworks/Developer/Import/ImportSummary.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml index 7fd3ada28f..5a782d911f 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -66,7 +66,7 @@

    Additional processing steps

    - @if(Model.CompetenciesToReorderCount > 0) + @if (Model.CompetenciesToReorderCount > 0) {
    From 690e727b97b1b26c61906c0fd4512518508a0f25 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Tue, 7 Jan 2025 16:35:53 +0000 Subject: [PATCH 37/71] TD-5163 Process imported competencies --- .../DataServices/FrameworkDataService.cs | 26 +++-- .../Models/Frameworks/FrameworkCompetency.cs | 1 + .../ImportCompetencies.cs | 14 +++ .../Models/BulkCompetenciesData.cs | 2 + .../Services/FrameworkService.cs | 22 ++-- .../ImportCompetenciesFromFileService.cs | 106 +++++++++++++++--- 6 files changed, 132 insertions(+), 39 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 63a8bc31a4..128ee670b0 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -131,10 +131,10 @@ bool zeroBased int InsertCompetency(string name, string? description, int adminId); - int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); + int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false); int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify, int? centreID); - void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); + int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting); @@ -205,7 +205,7 @@ void UpdateFrameworkCompetencyGroup( int adminId ); - void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId); + void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription); void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -656,7 +656,8 @@ public int InsertFrameworkCompetency( int competencyId, int? frameworkCompetencyGroupID, int adminId, - int frameworkId + int frameworkId, + bool alwaysShowDescription = false ) { if ((competencyId < 1) | (adminId < 1) | (frameworkId < 1)) @@ -978,7 +979,7 @@ FROM FrameworkCompetencyGroups AS fcg public FrameworkCompetency? GetFrameworkCompetencyById(int Id) { return connection.QueryFirstOrDefault( - @"SELECT fc.ID, c.ID AS CompetencyID, c.Name, c.Description, fc.Ordering + @"SELECT fc.ID, c.ID AS CompetencyID, c.Name, c.Description, fc.Ordering, c.AlwaysShowDescription FROM FrameworkCompetencies AS fc INNER JOIN Competencies AS c ON fc.CompetencyID = c.ID WHERE fc.ID = @Id", @@ -1045,7 +1046,7 @@ int adminId } } - public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId) + public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription) { if ((frameworkCompetencyId < 1) | (adminId < 1) | (name.Length < 3)) { @@ -1057,10 +1058,10 @@ public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, st //DO WE NEED SOMETHING IN HERE TO CHECK WHETHER IT IS USED ELSEWHERE AND WARN THE USER? var numberOfAffectedRows = connection.Execute( - @"UPDATE Competencies SET Name = @name, Description = @description, UpdatedByAdminID = @adminId + @"UPDATE Competencies SET Name = @name, Description = @description, UpdatedByAdminID = @adminId, AlwaysShowDescription = CASE WHEN @alwaysShowDescription IS NULL THEN AlwaysShowDescription ELSE @alwaysShowDescription END FROM Competencies INNER JOIN FrameworkCompetencies AS fc ON Competencies.ID = fc.CompetencyID WHERE (fc.Id = @frameworkCompetencyId)", - new { name, description, adminId, frameworkCompetencyId } + new { name, description, adminId, frameworkCompetencyId, alwaysShowDescription} ); if (numberOfAffectedRows < 1) { @@ -1094,11 +1095,12 @@ SELECT FlagID FROM CompetencyFlags new { competencyId, frameworkId }); } - public void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) + public int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) { - connection.Execute( - @$"INSERT INTO Flags(FrameworkID, FlagName, FlagGroup, FlagTagClass) - VALUES(@frameworkId, @flagName, @flagGroup, @flagTagClass)", + return connection.QuerySingle( + @"INSERT INTO Flags(FrameworkID, FlagName, FlagGroup, FlagTagClass) + OUTPUT INSERTED.ID + VALUES(@frameworkId, @flagName, @flagGroup, @flagTagClass);", new { frameworkId, flagName, flagGroup, flagTagClass }); } diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs b/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs index 56bd78ddc1..a42d3d10fe 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/FrameworkCompetency.cs @@ -14,6 +14,7 @@ public class FrameworkCompetency : BaseSearchableItem public int AssessmentQuestions { get; set; } public int CompetencyLearningResourcesCount { get; set; } public string? FrameworkName { get; set; } + public bool? AlwaysShowDescription { get; set; } public override string SearchableName { diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 08ff1eb0ff..735db4a3e5 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -224,6 +224,20 @@ public IActionResult ImportSummary() var model = new ImportSummaryViewModel(data); return View("Developer/Import/ImportSummary", model); } + [HttpPost] + [Route("/Framework/{frameworkId}/{tabname}/Import/Summary")] + public IActionResult ImportSummarySubmit() + { + var data = GetBulkUploadData(); + var adminId = GetAdminId(); + var uploadDir = Path.Combine(webHostEnvironment.WebRootPath, "Uploads\\"); + var filePath = Path.Combine(uploadDir, data.CompetenciesFileName); + var workbook = new XLWorkbook(filePath); + var results = importCompetenciesFromFileService.ProcessCompetenciesFromFile(workbook, adminId, data.FrameworkId, data.FrameworkVocubulary, data.ReorderCompetenciesOption, data.AddAssessmentQuestionsOption, data.AddCustomAssessmentQuestion ? (int)data.CustomAssessmentQuestionID : 0, data.AddDefaultAssessmentQuestions ? data.DefaultQuestionIDs : []); + data.ImportCompetenciesResult = results; + setBulkUploadData(data); + return RedirectToAction("UploadResults", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } [Route("CancelImport")] public IActionResult CancelImport() { diff --git a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs index 2d3ce9e4ac..344c674a89 100644 --- a/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs +++ b/DigitalLearningSolutions.Web/Models/BulkCompetenciesData.cs @@ -1,4 +1,5 @@ using DigitalLearningSolutions.Data.Models.Frameworks; +using DigitalLearningSolutions.Data.Models.Frameworks.Import; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -42,5 +43,6 @@ public BulkCompetenciesData(DetailFramework framework, int adminUserId, string c public int SubtotalCompetenciesUpdated { get; set; } public int SubTotalSkipped { get; set; } public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } = Enumerable.Empty<(int, string)>(); + public ImportCompetenciesResult ImportCompetenciesResult { get;set;} } } diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 8c7de151d9..bf4367b4fe 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -119,16 +119,16 @@ bool zeroBased int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId); - int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); + int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false); IEnumerable GetAllCompetenciesForAdminId(string name, int adminId); int InsertCompetency(string name, string? description, int adminId); - int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId); + int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); int AddCollaboratorToFramework(int frameworkId, string userEmail, bool canModify, int? centreID); - void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); + int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass); void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, string flagGroup, string flagTagClass); void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting); @@ -199,7 +199,7 @@ void UpdateFrameworkCompetencyGroup( int adminId ); - void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId); + void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription = false); void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -277,9 +277,9 @@ public void AddCompetencyAssessmentQuestion(int frameworkCompetencyId, int asses frameworkDataService.AddCompetencyAssessmentQuestion(frameworkCompetencyId, assessmentQuestionId, adminId); } - public void AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) + public int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) { - frameworkDataService.AddCustomFlagToFramework(frameworkId, flagName, flagGroup, flagTagClass); + return frameworkDataService.AddCustomFlagToFramework(frameworkId, flagName, flagGroup, flagTagClass); } public void AddFrameworkDefaultQuestion(int frameworkId, int assessmentQuestionId, int adminId, bool addToExisting) @@ -602,9 +602,9 @@ public int InsertCompetencyGroup(string groupName, string? groupDescription, int return frameworkDataService.InsertCompetencyGroup(groupName, groupDescription, adminId); } - public int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId) + public int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false) { - return frameworkDataService.InsertFrameworkCompetency(competencyId, frameworkCompetencyGroupID, adminId, frameworkId); + return frameworkDataService.InsertFrameworkCompetency(competencyId, frameworkCompetencyGroupID, adminId, frameworkId, alwaysShowDescription); } public int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId) @@ -672,9 +672,9 @@ public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selec return frameworkDataService.UpdateFrameworkBranding(frameworkId, brandId, categoryId, topicId, adminId); } - public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId) - { - frameworkDataService.UpdateFrameworkCompetency(frameworkCompetencyId, name, description, adminId); + public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription) + { + frameworkDataService.UpdateFrameworkCompetency(frameworkCompetencyId, name, description, adminId, alwaysShowDescription); } public void UpdateFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int competencyGroupId, string name, string? description, int adminId) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index a26793b651..2367bcbefd 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -12,12 +12,13 @@ namespace DigitalLearningSolutions.Web.Services using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Helpers; using DigitalLearningSolutions.Data.Models.Frameworks.Import; + using DocumentFormat.OpenXml.Office2010.Excel; public interface IImportCompetenciesFromFileService { byte[] GetCompetencyFileForFramework(int frameworkId, bool isBlank, string vocabulary); public ImportCompetenciesResult PreProcessCompetenciesTable(IXLWorkbook workbook, string vocabulary, int frameworkId); - public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary); + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary, int reorderCompetenciesOption, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds); } public class ImportCompetenciesFromFileService : IImportCompetenciesFromFileService { @@ -71,12 +72,12 @@ private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List } competencyRow.Validate(); } - public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary) + public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook, int adminUserId, int frameworkId, string vocabulary, int reorderCompetenciesOption, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds) { int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID(); int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID(); var table = OpenCompetenciesTable(workbook, vocabulary); - return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId); + return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, customAssessmentQuestionID, defaultQuestionIds); } internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) { @@ -93,7 +94,7 @@ internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) } return table; } - internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId) + internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds) { var competenciesRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); @@ -104,7 +105,7 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a .Count(); foreach (var competencyRow in competenciesRows) { - maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, competencyRow); + maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); } return new ImportCompetenciesResult(competenciesRows); @@ -114,6 +115,9 @@ private int ProcessCompetencyRow( int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, + int addAssessmentQuestionsOption, + int customAssessmentQuestionID, + List defaultQuestionIds, CompetencyTableRow competencyRow ) { @@ -121,11 +125,13 @@ CompetencyTableRow competencyRow { return maxFrameworkCompetencyGroupId; } + int newCompetencyGroupId = 0; + int newCompetencyId = 0; //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID - int? frameworkCompetencyGroupId = null; + int ? frameworkCompetencyGroupId = null; if (competencyRow.CompetencyGroup != null) { - var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, null, adminUserId); + newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId); if (newCompetencyGroupId > 0) { frameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminUserId); @@ -136,21 +142,89 @@ CompetencyTableRow competencyRow } } } - - //Check if competency already exists in framework competency group and add if not - var newCompetencyId = frameworkService.InsertCompetency(competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId); - if (newCompetencyId > 0) + // If FrameworkCompetency ID is supplied, update the competency + if (competencyRow.ID != null) { - var newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId); - if (newFrameworkCompetencyId > maxFrameworkCompetencyId) + var frameworkCompetency = frameworkService.GetFrameworkCompetencyById((int)competencyRow.ID); + if (frameworkCompetency != null) { - competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyInserted : RowStatus.CompetencyInserted); + newCompetencyId = frameworkCompetency.CompetencyID; + if (frameworkCompetency.Name != competencyRow.Competency || frameworkCompetency.Description != competencyRow.CompetencyDescription || frameworkCompetency.AlwaysShowDescription != competencyRow.AlwaysShowDescription ) + { + frameworkService.UpdateFrameworkCompetency((int)competencyRow.ID, competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId, competencyRow.AlwaysShowDescription ?? false); + competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyUpdated: RowStatus.CompetencyUpdated); + } + else + { + competencyRow.RowStatus = RowStatus.Skipped; + } } - else + } + else + { + //Check if competency already exists in framework competency group and add if not + newCompetencyId = frameworkService.InsertCompetency(competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId); + if (newCompetencyId > 0) + { + var newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId, competencyRow.AlwaysShowDescription ?? false); //including always show desc flag + if (newFrameworkCompetencyId > maxFrameworkCompetencyId) + { + competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyInserted : RowStatus.CompetencyInserted); + } + else + { + competencyRow.RowStatus = RowStatus.Skipped; + } + } + } + + + // If flags are supplied, add them: + if (competencyRow.FlagsCsv != null) + { + var flags = competencyRow.FlagsCsv.Split(','); + int[] flagIds = []; + foreach (var flag in flags) { + int flagId = 0; + var frameworkFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, null); + if (frameworkFlags.Any()) + { + foreach (var frameworkFlag in frameworkFlags) + { + if (frameworkFlag.FlagName == flag) + { + flagId = frameworkFlag.FlagId; + } + } + } + if (flagId == 0) + { + flagId = frameworkService.AddCustomFlagToFramework(frameworkId, flag, "Flag", "nhsuk-tag--white"); + } + flagIds.Append(flagId); + } + if (flagIds.Any()) { + frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, flagIds); + } + } + + + // Add assessment questions if necessary: + if (defaultQuestionIds.Count > 0 | customAssessmentQuestionID > 0) + { + if (competencyRow.RowStatus == RowStatus.CompetencyInserted | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted || addAssessmentQuestionsOption == 2 && competencyRow.RowStatus == RowStatus.CompetencyUpdated | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated || addAssessmentQuestionsOption == 3) { - competencyRow.RowStatus = RowStatus.Skipped; + foreach(var id in defaultQuestionIds) + { + frameworkService.AddCompetencyAssessmentQuestion((int)competencyRow.ID, id, adminUserId); + } + if(customAssessmentQuestionID > 0) + { + frameworkService.AddCompetencyAssessmentQuestion((int)competencyRow.ID, customAssessmentQuestionID, adminUserId); + } } } + return maxFrameworkCompetencyGroupId; } From 52b6fdd237ad6872284d7da0158d4d410100cbe6 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Tue, 7 Jan 2025 17:27:55 +0000 Subject: [PATCH 38/71] TD-5163 Implements the upload results view --- .../DataServices/FrameworkDataService.cs | 8 +++++++- .../FrameworksController/ImportCompetencies.cs | 12 ++++++++++++ .../Services/ImportCompetenciesFromFileService.cs | 7 ++++--- .../Import/ImportCompetenciesResultsViewModel.cs | 15 ++++++++++++++- .../Developer/Import/ImportSummary.cshtml | 2 +- .../Developer/Import/UploadResults.cshtml | 7 ++++--- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 128ee670b0..f6053e33bf 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -1516,7 +1516,13 @@ public void AddCompetencyAssessmentQuestion(int frameworkCompetencyId, int asses FROM [CompetencyAssessmentQuestions] WHERE ([CompetencyId] = fc.CompetencyID)), 0)+1 FROM FrameworkCompetencies AS fc - WHERE Id = @frameworkCompetencyId", + WHERE Id = @frameworkCompetencyId + AND NOT EXISTS ( + SELECT 1 + FROM CompetencyAssessmentQuestions AS caq + WHERE caq.CompetencyId = fc.CompetencyID + AND caq.AssessmentQuestionID = @assessmentQuestionId + );", new { frameworkCompetencyId, assessmentQuestionId } ); if (numberOfAffectedRows < 1) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 735db4a3e5..31c34b8e0a 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -235,9 +235,21 @@ public IActionResult ImportSummarySubmit() var workbook = new XLWorkbook(filePath); var results = importCompetenciesFromFileService.ProcessCompetenciesFromFile(workbook, adminId, data.FrameworkId, data.FrameworkVocubulary, data.ReorderCompetenciesOption, data.AddAssessmentQuestionsOption, data.AddCustomAssessmentQuestion ? (int)data.CustomAssessmentQuestionID : 0, data.AddDefaultAssessmentQuestions ? data.DefaultQuestionIDs : []); data.ImportCompetenciesResult = results; + //TO DO apply ordering changes if required: + if (data.ReorderCompetenciesOption == 2 && data.CompetenciesToReorderCount > 0) + { + + } setBulkUploadData(data); return RedirectToAction("UploadResults", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); } + [Route("/Framework/{frameworkId}/{tabname}/Import/Results")] + public IActionResult UploadResults() + { + var data = GetBulkUploadData(); + var model = new ImportCompetenciesResultsViewModel(data.ImportCompetenciesResult, data.FrameworkId, data.FrameworkName, data.FrameworkVocubulary); + return View("Developer/Import/UploadResults", model); + } [Route("CancelImport")] public IActionResult CancelImport() { diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 2367bcbefd..a235f0a991 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -127,6 +127,7 @@ CompetencyTableRow competencyRow } int newCompetencyGroupId = 0; int newCompetencyId = 0; + int newFrameworkCompetencyId = 0; //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID int ? frameworkCompetencyGroupId = null; if (competencyRow.CompetencyGroup != null) @@ -166,7 +167,7 @@ CompetencyTableRow competencyRow newCompetencyId = frameworkService.InsertCompetency(competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId); if (newCompetencyId > 0) { - var newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId, competencyRow.AlwaysShowDescription ?? false); //including always show desc flag + newFrameworkCompetencyId = frameworkService.InsertFrameworkCompetency(newCompetencyId, frameworkCompetencyGroupId, adminUserId, frameworkId, competencyRow.AlwaysShowDescription ?? false); //including always show desc flag if (newFrameworkCompetencyId > maxFrameworkCompetencyId) { competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyInserted : RowStatus.CompetencyInserted); @@ -216,11 +217,11 @@ CompetencyTableRow competencyRow { foreach(var id in defaultQuestionIds) { - frameworkService.AddCompetencyAssessmentQuestion((int)competencyRow.ID, id, adminUserId); + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, id, adminUserId); } if(customAssessmentQuestionID > 0) { - frameworkService.AddCompetencyAssessmentQuestion((int)competencyRow.ID, customAssessmentQuestionID, adminUserId); + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, customAssessmentQuestionID, adminUserId); } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs index b47108fe15..48204ae0d1 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs @@ -1,25 +1,38 @@ namespace DigitalLearningSolutions.Web.ViewModels.Frameworks.Import { using DigitalLearningSolutions.Data.Models.Frameworks.Import; + using DigitalLearningSolutions.Web.Helpers; using System.Collections.Generic; using System.Linq; public class ImportCompetenciesResultsViewModel { - public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult) + public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompetenciesResult, int frameworkId, string frameworkName, string frameworkVocabulary) { ProcessedCount = importCompetenciesResult.ProcessedCount; CompetenciesInsertedCount = importCompetenciesResult.CompetencyAddedCount; + CompetenciesUpdatedCount = importCompetenciesResult.CompetencyUpdatedCount; CompetencyGroupsInsertedCount = importCompetenciesResult.GroupAddedCount; + CompetencyGroupsUpdatedCount = importCompetenciesResult.GroupUpdatedCount; SkippedCount = importCompetenciesResult.SkippedCount; Errors = importCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); + FrameworkID = frameworkId; + FrameworkName = frameworkName; + FrameworkVocabularySingular = FrameworkVocabularyHelper.VocabularySingular(frameworkVocabulary); + FrameworkVocabularyPlural = FrameworkVocabularyHelper.VocabularyPlural(frameworkVocabulary); } public IEnumerable<(int RowNumber, string ErrorMessage)> Errors { get; set; } public int ErrorCount => Errors.Count(); public int ProcessedCount { get; set; } public int CompetenciesInsertedCount { get; set; } + public int CompetenciesUpdatedCount { get; set; } public int CompetencyGroupsInsertedCount { get; set; } + public int CompetencyGroupsUpdatedCount { get; set; } public int SkippedCount { get; set; } + public int FrameworkID { get; set; } + public string FrameworkName { get; set; } + public string FrameworkVocabularySingular { get; set; } + public string FrameworkVocabularyPlural { get; set; } private string MapReasonToErrorMessage(ImportCompetenciesResult.ErrorReason reason) { return reason switch diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml index 5a782d911f..fedee7029a 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -101,7 +101,7 @@ Important:

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

    -
    + Back
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index d83d3ca428..efb819495d 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -34,9 +34,10 @@

    Summary of results:

    • @Model.ProcessedCount @(Model.ProcessedCount == 1 ? "line" : "lines") processed
    • -
    • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? "competency group" : "competency groups") inserted
    • -
    • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? "competency" : "competencies") inserted
    • -
    • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "record" : "records") skipped (nothing inserted but no errors)
    • +
    • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? $"{Model.FrameworkVocabularySingular.ToLower()} group" : $"{Model.FrameworkVocabularySingular.ToLower()} groups") inserted
    • +
    • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) inserted
    • +
    • @Model.CompetenciesUpdatedCount new @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
    • +
    • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted but no errors)
    • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
    From d534e08485a2c5d9fbc7ff4dad48536c05d52b78 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Wed, 8 Jan 2025 09:08:17 +0000 Subject: [PATCH 39/71] TD-5163 Fixes flag assignment during upload processing --- .../ImportCompetenciesFromFileService.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index a235f0a991..9841c5e429 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -11,6 +11,7 @@ namespace DigitalLearningSolutions.Web.Services using ClosedXML.Excel; using DigitalLearningSolutions.Data.Exceptions; using DigitalLearningSolutions.Data.Helpers; + using DigitalLearningSolutions.Data.Models.Frameworks; using DigitalLearningSolutions.Data.Models.Frameworks.Import; using DocumentFormat.OpenXml.Office2010.Excel; @@ -125,14 +126,13 @@ CompetencyTableRow competencyRow { return maxFrameworkCompetencyGroupId; } - int newCompetencyGroupId = 0; int newCompetencyId = 0; int newFrameworkCompetencyId = 0; //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID int ? frameworkCompetencyGroupId = null; if (competencyRow.CompetencyGroup != null) { - newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId); + int newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId); if (newCompetencyGroupId > 0) { frameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminUserId); @@ -181,10 +181,10 @@ CompetencyTableRow competencyRow // If flags are supplied, add them: - if (competencyRow.FlagsCsv != null) + if (!string.IsNullOrWhiteSpace(competencyRow.FlagsCsv.Trim())) { var flags = competencyRow.FlagsCsv.Split(','); - int[] flagIds = []; + var flagIds = new List(); foreach (var flag in flags) { int flagId = 0; var frameworkFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, null); @@ -195,6 +195,7 @@ CompetencyTableRow competencyRow if (frameworkFlag.FlagName == flag) { flagId = frameworkFlag.FlagId; + break; } } } @@ -202,10 +203,14 @@ CompetencyTableRow competencyRow { flagId = frameworkService.AddCustomFlagToFramework(frameworkId, flag, "Flag", "nhsuk-tag--white"); } - flagIds.Append(flagId); + flagIds.Add(flagId); } - if (flagIds.Any()) { - frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, flagIds); + if (flagIds.Count > 0) { + frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, [.. flagIds]); + if (competencyRow.RowStatus == RowStatus.Skipped) + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } } } From 9695907e907503fff72a5e5fe0767d21e1b68dea Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Wed, 8 Jan 2025 09:08:51 +0000 Subject: [PATCH 40/71] TD-5163 General tweaks to improve wording of buttons and body text --- .../Views/Frameworks/Developer/Import/Index.cshtml | 2 +- .../Views/Frameworks/Developer/Import/UploadResults.cshtml | 2 +- .../Views/Frameworks/Developer/_Structure.cshtml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 78a5fb95bd..11fd4f4b48 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -79,7 +79,7 @@
    This Excel file will include all existing @Model.FrameworkVocabularyPlural.ToLower() whose details you can update.
    - New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row. + New @Model.FrameworkVocabularyPlural.ToLower() can be added by including their details on a blank row, leaving the ID column blank.
    Download @Model.FrameworkVocabularyPlural.ToLower() diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index efb819495d..b65e4026f1 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -36,7 +36,7 @@
  • @Model.ProcessedCount @(Model.ProcessedCount == 1 ? "line" : "lines") processed
  • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? $"{Model.FrameworkVocabularySingular.ToLower()} group" : $"{Model.FrameworkVocabularySingular.ToLower()} groups") inserted
  • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) inserted
  • -
  • @Model.CompetenciesUpdatedCount new @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
  • +
  • @Model.CompetenciesUpdatedCount existing @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
  • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted but no errors)
  • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
  • diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml index a1097f6e18..849b5121ba 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml @@ -81,7 +81,7 @@ else
    } From 968cdac0479eea5f17a6cba49fa3701cc5b2e5a7 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Wed, 8 Jan 2025 16:55:34 +0000 Subject: [PATCH 41/71] TD-5163 Corrects heading --- .../Views/Frameworks/Developer/Import/ImportCompleted.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index b127e53979..93abdd4983 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -28,7 +28,7 @@ }
    -

    Delegate file uploaded

    +

    @Model.FrameworkVocabularySingular.ToLower() file uploaded

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

      From 9311b208bfaab571cf69a043e130c11ccab7285d Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Wed, 8 Jan 2025 16:56:32 +0000 Subject: [PATCH 42/71] TD-5163 modifies the insert competency group method to take an optional framework id --- .../DataServices/FrameworkDataService.cs | 68 +++++++++++-------- .../Services/FrameworkService.cs | 6 +- .../ImportCompetenciesFromFileService.cs | 2 +- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index f6053e33bf..0e702f204b 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -123,7 +123,7 @@ bool zeroBased //INSERT DATA BrandedFramework CreateFramework(DetailFramework detailFramework, int adminId); - int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId); + int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId); int InsertFrameworkCompetencyGroup(int groupId, int frameworkID, int adminId); @@ -475,7 +475,7 @@ public BrandedFramework CreateFramework(DetailFramework detailFramework, int adm return new BrandedFramework(); } - var existingFrameworks = (int)connection.ExecuteScalar( + var existingFrameworks = connection.QuerySingle( @"SELECT COUNT(*) FROM Frameworks WHERE FrameworkName = @frameworkName", new { frameworkName } ); @@ -548,7 +548,7 @@ int adminId return GetBrandedFrameworkByFrameworkId(frameworkId, adminId); } - public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId) + public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId) { if ((groupName.Length == 0) | (adminId < 1)) { @@ -558,9 +558,16 @@ public int InsertCompetencyGroup(string groupName, string? groupDescription, int return -2; } groupDescription = (groupDescription?.Trim() == "" ? null : groupDescription); - var existingId = (int)connection.ExecuteScalar( - @"SELECT COALESCE ((SELECT TOP(1)ID FROM CompetencyGroups WHERE [Name] = @groupName AND (@groupDescription IS NULL OR Description = @groupDescription)), 0) AS CompetencyGroupID", - new { groupName, groupDescription } + var existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT TOP (1) ID + FROM CompetencyGroups + WHERE (Name = @groupName) AND EXISTS + (SELECT 1 AS Expr1 + FROM FrameworkCompetencyGroups + WHERE (CompetencyGroupID = CompetencyGroups.ID) AND (FrameworkID = @frameworkId) OR + (CompetencyGroupID = CompetencyGroups.ID) AND (@frameworkId IS NULL))), 0) AS CompetencyGroupID", + new { groupName, groupDescription, frameworkId } ); if (existingId > 0) { @@ -581,8 +588,15 @@ public int InsertCompetencyGroup(string groupName, string? groupDescription, int return -1; } - existingId = (int)connection.ExecuteScalar( - @"SELECT COALESCE ((SELECT TOP(1)ID FROM CompetencyGroups WHERE [Name] = @groupName AND (@groupDescription IS NULL OR Description = @groupDescription)), 0) AS CompetencyGroupID", + existingId = connection.QuerySingle( + @"SELECT COALESCE + ((SELECT TOP (1) ID + FROM CompetencyGroups + WHERE (Name = @groupName) AND EXISTS + (SELECT 1 AS Expr1 + FROM FrameworkCompetencyGroups + WHERE (CompetencyGroupID = CompetencyGroups.ID) AND (FrameworkID = @frameworkId) OR + (CompetencyGroupID = CompetencyGroups.ID) AND (@frameworkId IS NULL))), 0) AS CompetencyGroupID", new { groupName, groupDescription } ); return existingId; @@ -598,7 +612,7 @@ public int InsertFrameworkCompetencyGroup(int groupId, int frameworkId, int admi return -2; } - var existingId = (int)connection.ExecuteScalar( + var existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencyGroups WHERE CompetencyGroupID = @groupID AND FrameworkID = @frameworkID), 0) AS FrameworkCompetencyGroupID", new { groupId, frameworkId } ); @@ -624,7 +638,7 @@ FROM [FrameworkCompetencyGroups] return -1; } - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencyGroups WHERE CompetencyGroupID = @groupID AND FrameworkID = @frameworkID), 0) AS FrameworkCompetencyGroupID", new { groupId, frameworkId } ); @@ -671,14 +685,14 @@ public int InsertFrameworkCompetency( var existingId = 0; if (frameworkCompetencyGroupID == null) { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID IS NULL), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); } else { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID = @frameworkCompetencyGroupID), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); @@ -707,14 +721,14 @@ FROM [FrameworkCompetencies] if (frameworkCompetencyGroupID == null) { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID IS NULL), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); } else { - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCompetencies WHERE [CompetencyID] = @competencyId AND FrameworkCompetencyGroupID = @frameworkCompetencyGroupID), 0) AS FrameworkCompetencyID", new { competencyId, frameworkCompetencyGroupID } ); @@ -765,7 +779,7 @@ public int AddCollaboratorToFramework(int frameworkId, string? userEmail, bool c return -3; } - var existingId = (int)connection.ExecuteScalar( + var existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCollaborators @@ -815,7 +829,7 @@ FROM FrameworkCollaborators ); } - existingId = (int)connection.ExecuteScalar( + existingId = connection.QuerySingle( @"SELECT COALESCE ((SELECT ID FROM FrameworkCollaborators @@ -907,7 +921,7 @@ public bool UpdateFrameworkName(int frameworkId, int adminId, string frameworkNa return false; } - var existingFrameworks = (int)connection.ExecuteScalar( + var existingFrameworks = connection.QuerySingle( @"SELECT COUNT(*) FROM Frameworks WHERE FrameworkName = @frameworkName AND ID <> @frameworkId", new { frameworkName, frameworkId } ); @@ -1003,7 +1017,7 @@ int adminId return; } - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencyGroups WHERE CompetencyGroupId = @competencyGroupId AND ID <> @frameworkCompetencyGroupId", @@ -1171,14 +1185,14 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c } //Check if used elsewhere and delete competency group if not: - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencyGroups WHERE CompetencyGroupId = @competencyGroupId", new { competencyGroupId } ); if (usedElsewhere == 0) { - usedElsewhere = (int)connection.ExecuteScalar( + usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM SelfAssessmentStructure WHERE CompetencyGroupId = @competencyGroupId", new { competencyGroupId } @@ -1209,7 +1223,7 @@ public void DeleteFrameworkCompetencyGroup(int frameworkCompetencyGroupId, int c public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) { - var competencyId = (int)connection.ExecuteScalar( + var competencyId = connection.QuerySingle( @"SELECT CompetencyID FROM FrameworkCompetencies WHERE ID = @frameworkCompetencyId", new { frameworkCompetencyId } ); @@ -1240,14 +1254,14 @@ public void DeleteFrameworkCompetency(int frameworkCompetencyId, int adminId) } //Check if used elsewhere and delete competency group if not: - var usedElsewhere = (int)connection.ExecuteScalar( + var usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM FrameworkCompetencies WHERE CompetencyID = @competencyId", new { competencyId } ); if (usedElsewhere == 0) { - usedElsewhere = (int)connection.ExecuteScalar( + usedElsewhere = connection.QuerySingle( @"SELECT COUNT(*) FROM SelfAssessmentStructure WHERE CompetencyID = @competencyId", new { competencyId } @@ -1835,10 +1849,10 @@ 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 - WHERE (FW.ID = @frameworkId)", + @"SELECT CASE WHEN FW.OwnerAdminID = @adminId THEN 3 WHEN fwc.CanModify = 1 THEN 2 WHEN fwc.CanModify = 0 THEN 1 ELSE 0 END AS UserRole + FROM Frameworks AS FW LEFT OUTER JOIN + FrameworkCollaborators AS fwc ON fwc.FrameworkID = FW.ID AND fwc.AdminID = @adminId + WHERE FW.ID = @frameworkId and FWC.IsDeleted=0", new { adminId, frameworkId } ); } diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index bf4367b4fe..7538d2c4a4 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -117,7 +117,7 @@ bool zeroBased //INSERT DATA BrandedFramework CreateFramework(DetailFramework detailFramework, int adminId); - int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId); + int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId = null); int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false); @@ -597,9 +597,9 @@ public int InsertCompetency(string name, string? description, int adminId) return frameworkDataService.InsertCompetency(name, description, adminId); } - public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId) + public int InsertCompetencyGroup(string groupName, string? groupDescription, int adminId, int? frameworkId) { - return frameworkDataService.InsertCompetencyGroup(groupName, groupDescription, adminId); + return frameworkDataService.InsertCompetencyGroup(groupName, groupDescription, adminId, frameworkId); } public int InsertFrameworkCompetency(int competencyId, int? frameworkCompetencyGroupID, int adminId, int frameworkId, bool alwaysShowDescription = false) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 9841c5e429..2471f61605 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -132,7 +132,7 @@ CompetencyTableRow competencyRow int ? frameworkCompetencyGroupId = null; if (competencyRow.CompetencyGroup != null) { - int newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId); + int newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId, frameworkId); if (newCompetencyGroupId > 0) { frameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminUserId); From b7cb3858bbff80ffad3cbb00da47f2ddc054c847 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Wed, 8 Jan 2025 16:57:06 +0000 Subject: [PATCH 43/71] TD-5163 Clears down the uploaded sheet after processing --- .../Controllers/FrameworksController/ImportCompetencies.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 31c34b8e0a..43c91e9347 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -247,6 +247,8 @@ public IActionResult ImportSummarySubmit() public IActionResult UploadResults() { var data = GetBulkUploadData(); + FileHelper.DeleteFile(webHostEnvironment, data.CompetenciesFileName); + TempData.Clear(); var model = new ImportCompetenciesResultsViewModel(data.ImportCompetenciesResult, data.FrameworkId, data.FrameworkName, data.FrameworkVocubulary); return View("Developer/Import/UploadResults", model); } From c33b7af1f7b75d277e59fa3180d2c19819080ee5 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Thu, 9 Jan 2025 16:59:26 +0000 Subject: [PATCH 44/71] TD-5163 Commits competency order changes if required --- .../DataServices/FrameworkDataService.cs | 18 ++++--- .../Frameworks/Import/CompetencyTableRow.cs | 1 + .../Services/FrameworkService.cs | 6 +-- .../ImportCompetenciesFromFileService.cs | 54 ++++++++++++++++--- .../Import/ApplyCompetencyOrdering.cshtml | 4 ++ .../Developer/Import/ImportCompleted.cshtml | 2 +- .../Developer/Import/UploadResults.cshtml | 2 +- 7 files changed, 68 insertions(+), 19 deletions(-) diff --git a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs index 0e702f204b..acc79af37c 100644 --- a/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs @@ -206,7 +206,7 @@ int adminId ); void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription); - void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); + int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -1025,7 +1025,7 @@ int adminId ); if (usedElsewhere > 0) { - var newCompetencyGroupId = InsertCompetencyGroup(name, description, adminId); + var newCompetencyGroupId = InsertCompetencyGroup(name, description, adminId, null); if (newCompetencyGroupId > 0) { var numberOfAffectedRows = connection.Execute( @@ -1086,13 +1086,14 @@ public void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, st } } - public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) + public int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) { + int totalRowsAffected = 0; string? commaSeparatedSelectedFlagIds = null; if (selectedFlagIds?.Length > 0) { commaSeparatedSelectedFlagIds = String.Join(',', selectedFlagIds); - connection.Execute( + totalRowsAffected += connection.Execute( @$"INSERT INTO CompetencyFlags(CompetencyID, FlagID, Selected) SELECT @competencyId, f.ID, 1 FROM Flags f @@ -1102,11 +1103,12 @@ SELECT FlagID FROM CompetencyFlags )", new { competencyId, selectedFlagIds }); } - connection.Execute( + totalRowsAffected += connection.Execute( @$"UPDATE CompetencyFlags SET Selected = (CASE WHEN FlagID IN ({commaSeparatedSelectedFlagIds ?? "null"}) THEN 1 ELSE 0 END) - WHERE CompetencyID = @competencyId", + WHERE CompetencyID = @competencyId AND Selected <> (CASE WHEN FlagID IN ({commaSeparatedSelectedFlagIds ?? "null"}) THEN 1 ELSE 0 END)", new { competencyId, frameworkId }); + return totalRowsAffected; } public int AddCustomFlagToFramework(int frameworkId, string flagName, string flagGroup, string flagTagClass) @@ -1127,7 +1129,7 @@ public void UpdateFrameworkCustomFlag(int frameworkId, int id, string flagName, new { frameworkId, id, flagName, flagGroup, flagTagClass }); } - public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction) + public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction) // Valid directions are 'UP' and 'DOWN' { connection.Execute( "ReorderFrameworkCompetencyGroup", @@ -1136,7 +1138,7 @@ public void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool si ); } - public void MoveFrameworkCompetency(int frameworkCompetencyId, bool singleStep, string direction) + public void MoveFrameworkCompetency(int frameworkCompetencyId, bool singleStep, string direction) // Valid directions are 'UP' and 'DOWN' { connection.Execute( "ReorderFrameworkCompetency", diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 16ea0b9e52..740ec961ca 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -38,6 +38,7 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) RowStatus = RowStatus.NotYetProcessed; } public int RowNumber { get; set; } + public int CompetencyOrderNumber { get; set; } public string? AlwaysShowDescriptionRaw { get; set; } public ImportCompetenciesResult.ErrorReason? Error { get; set; } public RowStatus RowStatus { get; set; } diff --git a/DigitalLearningSolutions.Web/Services/FrameworkService.cs b/DigitalLearningSolutions.Web/Services/FrameworkService.cs index 7538d2c4a4..9f4018e671 100644 --- a/DigitalLearningSolutions.Web/Services/FrameworkService.cs +++ b/DigitalLearningSolutions.Web/Services/FrameworkService.cs @@ -200,7 +200,7 @@ int adminId ); void UpdateFrameworkCompetency(int frameworkCompetencyId, string name, string? description, int adminId, bool? alwaysShowDescription = false); - void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); + int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds); void MoveFrameworkCompetencyGroup(int frameworkCompetencyGroupId, bool singleStep, string direction); @@ -662,9 +662,9 @@ public void UpdateAssessmentQuestion(int id, string question, int assessmentQues frameworkDataService.UpdateAssessmentQuestion(id, question, assessmentQuestionInputTypeId, maxValueDescription, minValueDescription, scoringInstructions, minValue, maxValue, includeComments, adminId, commentsPrompt, commentsHint); } - public void UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) + public int UpdateCompetencyFlags(int frameworkId, int competencyId, int[] selectedFlagIds) { - frameworkDataService.UpdateCompetencyFlags(frameworkId, competencyId, selectedFlagIds); + return frameworkDataService.UpdateCompetencyFlags(frameworkId, competencyId, selectedFlagIds); } public BrandedFramework? UpdateFrameworkBranding(int frameworkId, int brandId, int categoryId, int topicId, int adminId) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 2471f61605..7cec219ef9 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -78,7 +78,7 @@ public ImportCompetenciesResult ProcessCompetenciesFromFile(IXLWorkbook workbook int maxFrameworkCompetencyId = frameworkService.GetMaxFrameworkCompetencyID(); int maxFrameworkCompetencyGroupId = frameworkService.GetMaxFrameworkCompetencyGroupID(); var table = OpenCompetenciesTable(workbook, vocabulary); - return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, customAssessmentQuestionID, defaultQuestionIds); + return ProcessCompetenciesTable(table, adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds); } internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) { @@ -95,10 +95,28 @@ internal IXLTable OpenCompetenciesTable(IXLWorkbook workbook, string vocabulary) } return table; } - internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, int addAssessmentQuestionsOption, int customAssessmentQuestionID, List defaultQuestionIds) + internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int adminUserId, int frameworkId, int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, int addAssessmentQuestionsOption, int reorderCompetenciesOption, int customAssessmentQuestionID, List defaultQuestionIds) { var competenciesRows = table.Rows().Skip(1).Select(row => new CompetencyTableRow(table, row)).ToList(); - + int rowCount = 0; + string currentGroup = null; + competenciesRows = competenciesRows + .OrderBy(row => row.CompetencyGroup) + .Select(row => + { + if (row.CompetencyGroup != currentGroup) + { + currentGroup = row.CompetencyGroup; + rowCount = 1; + } + else + { + rowCount++; + } + row.CompetencyOrderNumber = rowCount; + return row; + }) + .ToList(); var competencyGroupCount = competenciesRows .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) .Select(row => row.CompetencyGroup) @@ -106,7 +124,7 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a .Count(); foreach (var competencyRow in competenciesRows) { - maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); + maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); } return new ImportCompetenciesResult(competenciesRows); @@ -117,6 +135,7 @@ private int ProcessCompetencyRow( int maxFrameworkCompetencyId, int maxFrameworkCompetencyGroupId, int addAssessmentQuestionsOption, + int reorderCompetenciesOption, int customAssessmentQuestionID, List defaultQuestionIds, CompetencyTableRow competencyRow @@ -206,8 +225,8 @@ CompetencyTableRow competencyRow flagIds.Add(flagId); } if (flagIds.Count > 0) { - frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, [.. flagIds]); - if (competencyRow.RowStatus == RowStatus.Skipped) + var updated = frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, [.. flagIds]); + if (updated > 0 && competencyRow.RowStatus == RowStatus.Skipped) { competencyRow.RowStatus = RowStatus.CompetencyUpdated; } @@ -231,6 +250,29 @@ CompetencyTableRow competencyRow } } + // Reorder competencies if required: + if (reorderCompetenciesOption == 2) + { + var frameworkCompetencyId = (int)competencyRow.ID; + var frameworkCompetency = frameworkService.GetFrameworkCompetencyById(frameworkCompetencyId); + var placesToMove = Math.Abs(frameworkCompetency.Ordering - competencyRow.CompetencyOrderNumber); + + if (placesToMove > 0) + { + var direction = frameworkCompetency.Ordering > competencyRow.CompetencyOrderNumber ? "UP" : "DOWN"; + + for (int i = 0; i < placesToMove; i++) + { + frameworkService.MoveFrameworkCompetency(frameworkCompetencyId, true, direction); + } + + if (competencyRow.RowStatus == RowStatus.Skipped) + { + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + } + } + } + return maxFrameworkCompetencyGroupId; } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml index 398343f461..6b21e8b765 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml @@ -36,6 +36,10 @@ Apply @Model.FrameworkVocabularySingular.ToLower() sequence changes? +
      + Information: +

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

      +
      Your uploaded file includes changes to the sequence of existing @Model.FrameworkVocabularyPlural.ToLower(). Choose whether to store the changes to @Model.FrameworkVocabularySingular.ToLower() sequence during update.
      diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 93abdd4983..059712b059 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -47,7 +47,7 @@ {
    • - @Model.CompetenciesToReorderCount existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next. + Some existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next.
    • }
    • No errors
    • diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index b65e4026f1..3fa9b57408 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -37,7 +37,7 @@
    • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? $"{Model.FrameworkVocabularySingular.ToLower()} group" : $"{Model.FrameworkVocabularySingular.ToLower()} groups") inserted
    • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) inserted
    • @Model.CompetenciesUpdatedCount existing @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
    • -
    • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted but no errors)
    • +
    • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted or updated but no errors)
    • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
    From 39a8d8e6abc08df39ac86b4063b0f890ff11837c Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 10 Jan 2025 08:19:01 +0000 Subject: [PATCH 45/71] TD-5163 Reorders competency groups if needed --- .../ImportCompetenciesFromFileService.cs | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 7cec219ef9..b8b7883f23 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -61,7 +61,7 @@ private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List { int originalIndex = existingIds.IndexOf(id); int newIndex = newIds.IndexOf(id); - if(originalIndex == newIndex) + if (originalIndex == newIndex) { competencyRow.RowStatus = RowStatus.CompetencyUpdated; } @@ -101,7 +101,6 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a int rowCount = 0; string currentGroup = null; competenciesRows = competenciesRows - .OrderBy(row => row.CompetencyGroup) .Select(row => { if (row.CompetencyGroup != currentGroup) @@ -126,7 +125,32 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a { maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); } + // TO DO: Check for changes to competency group order and apply them if appropriate: + if (reorderCompetenciesOption == 2) + { + var distinctCompetencyGroups = competenciesRows + .Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup)) + .Select(row => row.CompetencyGroup) + .Distinct() + .ToList(); + var existingGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).Select(row => new { row.ID, row.Name }) + .Distinct() + .ToList(); + for (int i = 0; i < competencyGroupCount; i++) + { + var placesToMove = Math.Abs(existingGroups.FindIndex(group => group.Name == distinctCompetencyGroups[i])-i); + if (placesToMove > 0) + { + var thisGroup = existingGroups.FirstOrDefault(group => group.Name == distinctCompetencyGroups[i]); + var direction = existingGroups.FindIndex(group => group.Name == distinctCompetencyGroups[i]) > i ? "UP" : "DOWN"; + for (int p = 0; p < placesToMove; p++) + { + frameworkService.MoveFrameworkCompetencyGroup(thisGroup.ID, true, direction); + } + } + } + } return new ImportCompetenciesResult(competenciesRows); } private int ProcessCompetencyRow( @@ -148,7 +172,7 @@ CompetencyTableRow competencyRow int newCompetencyId = 0; int newFrameworkCompetencyId = 0; //If competency group is set, check if competency group exists within framework and add if not and get the Framework Competency Group ID - int ? frameworkCompetencyGroupId = null; + int? frameworkCompetencyGroupId = null; if (competencyRow.CompetencyGroup != null) { int newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyRow.CompetencyGroup, competencyRow.GroupDescription, adminUserId, frameworkId); @@ -169,10 +193,10 @@ CompetencyTableRow competencyRow if (frameworkCompetency != null) { newCompetencyId = frameworkCompetency.CompetencyID; - if (frameworkCompetency.Name != competencyRow.Competency || frameworkCompetency.Description != competencyRow.CompetencyDescription || frameworkCompetency.AlwaysShowDescription != competencyRow.AlwaysShowDescription ) + if (frameworkCompetency.Name != competencyRow.Competency || frameworkCompetency.Description != competencyRow.CompetencyDescription || frameworkCompetency.AlwaysShowDescription != competencyRow.AlwaysShowDescription) { frameworkService.UpdateFrameworkCompetency((int)competencyRow.ID, competencyRow.Competency, competencyRow.CompetencyDescription, adminUserId, competencyRow.AlwaysShowDescription ?? false); - competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyUpdated: RowStatus.CompetencyUpdated); + competencyRow.RowStatus = (competencyRow.RowStatus == RowStatus.CompetencyGroupInserted ? RowStatus.CompetencyGroupAndCompetencyUpdated : RowStatus.CompetencyUpdated); } else { @@ -204,7 +228,8 @@ CompetencyTableRow competencyRow { var flags = competencyRow.FlagsCsv.Split(','); var flagIds = new List(); - foreach (var flag in flags) { + foreach (var flag in flags) + { int flagId = 0; var frameworkFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, null); if (frameworkFlags.Any()) @@ -224,7 +249,8 @@ CompetencyTableRow competencyRow } flagIds.Add(flagId); } - if (flagIds.Count > 0) { + if (flagIds.Count > 0) + { var updated = frameworkService.UpdateCompetencyFlags(frameworkId, newCompetencyId, [.. flagIds]); if (updated > 0 && competencyRow.RowStatus == RowStatus.Skipped) { @@ -233,23 +259,6 @@ CompetencyTableRow competencyRow } } - - // Add assessment questions if necessary: - if (defaultQuestionIds.Count > 0 | customAssessmentQuestionID > 0) - { - if (competencyRow.RowStatus == RowStatus.CompetencyInserted | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted || addAssessmentQuestionsOption == 2 && competencyRow.RowStatus == RowStatus.CompetencyUpdated | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated || addAssessmentQuestionsOption == 3) - { - foreach(var id in defaultQuestionIds) - { - frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, id, adminUserId); - } - if(customAssessmentQuestionID > 0) - { - frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, customAssessmentQuestionID, adminUserId); - } - } - } - // Reorder competencies if required: if (reorderCompetenciesOption == 2) { @@ -273,6 +282,24 @@ CompetencyTableRow competencyRow } } + // Add assessment questions if necessary: + if (defaultQuestionIds.Count > 0 | customAssessmentQuestionID > 0) + { + if (competencyRow.RowStatus == RowStatus.CompetencyInserted | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted || addAssessmentQuestionsOption == 2 && competencyRow.RowStatus == RowStatus.CompetencyUpdated | competencyRow.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated || addAssessmentQuestionsOption == 3) + { + foreach (var id in defaultQuestionIds) + { + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, id, adminUserId); + } + if (customAssessmentQuestionID > 0) + { + frameworkService.AddCompetencyAssessmentQuestion(competencyRow.ID ?? newFrameworkCompetencyId, customAssessmentQuestionID, adminUserId); + } + } + } + + + return maxFrameworkCompetencyGroupId; } From f1ff6c047b3855609ba08c6d02945ba257012fea Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 10 Jan 2025 08:46:33 +0000 Subject: [PATCH 46/71] TD-5163 Fixes navigation and titles --- .../Controllers/FrameworksController/ImportCompetencies.cs | 4 ++++ .../Views/Frameworks/Developer/Import/ImportCompleted.cshtml | 2 +- .../Views/Frameworks/Developer/Import/ImportSummary.cshtml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs index 43c91e9347..846eeca19a 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/ImportCompetencies.cs @@ -194,6 +194,10 @@ public IActionResult AddAssessmentQuestions(AddAssessmentQuestionsFormData model public IActionResult AddQuestionsToWhichCompetencies() { var data = GetBulkUploadData(); + if (data.DefaultQuestionIDs.Count ==0 && data.CustomAssessmentQuestionID == null) + { + return RedirectToAction("ImportSummary", "Frameworks", new { frameworkId = data.FrameworkId, tabname = data.TabName }); + } var model = new AddQuestionsToWhichCompetenciesViewModel ( data.FrameworkId, diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml index 059712b059..3a5cd7aa8d 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportCompleted.cshtml @@ -28,7 +28,7 @@ }
    -

    @Model.FrameworkVocabularySingular.ToLower() file uploaded

    +

    @Model.FrameworkVocabularySingular file uploaded

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

      diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml index fedee7029a..b14052b460 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -102,7 +102,7 @@

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

    - Back + 0 ? "AddQuestionsToWhichCompetencies" : "AddAssessmentQuestions") role="button" class="nhsuk-button nhsuk-button--secondary">Back
    From 657bc2645c4e9fdd0978e23a35788925f635d1a9 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 10 Jan 2025 08:47:35 +0000 Subject: [PATCH 47/71] TD-5163 Fixes competency group reordering by reloading the committed order within the iterator --- .../Services/ImportCompetenciesFromFileService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index b8b7883f23..c29bb1b177 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -133,12 +133,11 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a .Select(row => row.CompetencyGroup) .Distinct() .ToList(); - - var existingGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).Select(row => new { row.ID, row.Name }) - .Distinct() - .ToList(); for (int i = 0; i < competencyGroupCount; i++) { + var existingGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).Select(row => new { row.ID, row.Name }) + .Distinct() + .ToList(); var placesToMove = Math.Abs(existingGroups.FindIndex(group => group.Name == distinctCompetencyGroups[i])-i); if (placesToMove > 0) { From c0e1e1067abc79ea636689a656207cc7a9a710e8 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 10 Jan 2025 09:14:59 +0000 Subject: [PATCH 48/71] Applies BR DLSFW1 to standard insert competency group functionality --- .../Controllers/FrameworksController/Competencies.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs index 328082ec28..0d26aee6a3 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Competencies.cs @@ -71,7 +71,7 @@ public IActionResult AddEditFrameworkCompetencyGroup(int frameworkId, Competency (competencyGroupBase.Description), adminId); return new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId }) + "#fcgroup-" + frameworkCompetencyGroupId.ToString()); } - var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId); + var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId, frameworkId); if (newCompetencyGroupId > 0) { var newFrameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminId); From ef60077b2c5895d5c1ea486ac77cc96f4c62d113 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 10 Jan 2025 14:28:00 +0000 Subject: [PATCH 49/71] Adds published framework warning to summary page --- .../Views/Frameworks/Developer/Import/ImportSummary.cshtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml index b14052b460..ed715f4068 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ImportSummary.cshtml @@ -31,6 +31,10 @@

    @Model.FrameworkVocabularyPlural import summary

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

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

    Upload summary

    From bab3ea786d9decc0c011130c2dae61510405254e Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 08:48:29 +0000 Subject: [PATCH 50/71] TD-5233 Add a limitations paragraph to the start page --- .../Frameworks/Developer/Import/Index.cshtml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 11fd4f4b48..c22a96d60e 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -40,7 +40,18 @@
    @if (Model.IsNotBlank) { - +
    + Note: +

    + Bulk upload cannot be used to: +

    +
      +
    • Delete competencies or competency groups
    • +
    • Remove assessment questions from existing competencies
    • +
    • Add more than one custom assessment question to uploaded competencies
    • +
    + +

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

    @@ -91,6 +102,12 @@ } else { +
    + Note: +

    + Bulk upload cannot be used to add more than one custom assessment question to uploaded competencies. +

    +

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

    From e7198201a3c11b7396d1f41983a903ef4080abef Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 09:12:10 +0000 Subject: [PATCH 51/71] Emphasises cannot --- .../Views/Frameworks/Developer/Import/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index c22a96d60e..1221cb9f4a 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -43,7 +43,7 @@
    Note:

    - Bulk upload cannot be used to: + Bulk upload cannot be used to:

    • Delete competencies or competency groups
    • From 1c5fb7daf3d9aa1b84ffa25e946032f636a44da3 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 09:15:42 +0000 Subject: [PATCH 52/71] Moves single line inset text to view component --- .../Views/Frameworks/Developer/Import/Index.cshtml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index 1221cb9f4a..f94adaf54a 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -36,7 +36,7 @@ { } - @* *@ +
      @if (Model.IsNotBlank) { @@ -102,12 +102,7 @@ } else { -
      - Note: -

      - Bulk upload cannot be used to add more than one custom assessment question to uploaded competencies. -

      -
      +

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

      From 93afda19f042a3adc6398b15c039d23bbf61c642 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 14:56:56 +0000 Subject: [PATCH 53/71] TD-5233 Indicates the number of competencies reordered in the upload results page --- .../Frameworks/Import/CompetencyTableRow.cs | 2 +- .../Import/ImportCompetenciesResult.cs | 6 +++--- .../ImportCompetenciesFromFileService.cs | 20 +++++++++---------- .../ImportCompetenciesResultsViewModel.cs | 2 ++ .../Import/ApplyCompetencyOrdering.cshtml | 6 +++--- .../Developer/Import/UploadResults.cshtml | 1 + 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs index 740ec961ca..1dd67ed007 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/CompetencyTableRow.cs @@ -9,7 +9,6 @@ public enum RowStatus CompetencyGroupAndCompetencyInserted, CompetencyInserted, CompetencyUpdated, - CompetencyUpdatedAndReordered, CompetencyGroupInserted, CompetencyGroupUpdated, CompetencyGroupAndCompetencyUpdated, @@ -42,6 +41,7 @@ public CompetencyTableRow(IXLTable table, IXLRangeRow row) public string? AlwaysShowDescriptionRaw { get; set; } public ImportCompetenciesResult.ErrorReason? Error { get; set; } public RowStatus RowStatus { get; set; } + public bool Reordered { get; set; } = false; public bool Validate() { if (string.IsNullOrEmpty(Competency)) diff --git a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs index 15f5136274..5ca4536119 100644 --- a/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs +++ b/DigitalLearningSolutions.Data/Models/Frameworks/Import/ImportCompetenciesResult.cs @@ -22,10 +22,10 @@ IReadOnlyCollection competencyTableRows { ProcessedCount = competencyTableRows.Count; CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated | dr.RowStatus == RowStatus.CompetencyUpdatedAndReordered); - CompetencyReorderedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdatedAndReordered); + CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated); GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted); - SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped); + SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped && dr.Reordered == false); + CompetencyReorderedCount = competencyTableRows.Count(dr => dr.Reordered == true); Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value)); FlagCount = competencyTableRows .Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv)) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index c29bb1b177..9b2f32c1c9 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -61,13 +61,10 @@ private void PreProcessCompetencyRow(CompetencyTableRow competencyRow, List { int originalIndex = existingIds.IndexOf(id); int newIndex = newIds.IndexOf(id); - if (originalIndex == newIndex) + competencyRow.RowStatus = RowStatus.CompetencyUpdated; + if (originalIndex != newIndex) { - competencyRow.RowStatus = RowStatus.CompetencyUpdated; - } - else - { - competencyRow.RowStatus = RowStatus.CompetencyUpdatedAndReordered; + competencyRow.Reordered = true; } } } @@ -125,7 +122,7 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a { maxFrameworkCompetencyGroupId = ProcessCompetencyRow(adminUserId, frameworkId, maxFrameworkCompetencyId, maxFrameworkCompetencyGroupId, addAssessmentQuestionsOption, reorderCompetenciesOption, customAssessmentQuestionID, defaultQuestionIds, competencyRow); } - // TO DO: Check for changes to competency group order and apply them if appropriate: + // Check for changes to competency group order and apply them if appropriate: if (reorderCompetenciesOption == 2) { var distinctCompetencyGroups = competenciesRows @@ -146,6 +143,10 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a for (int p = 0; p < placesToMove; p++) { frameworkService.MoveFrameworkCompetencyGroup(thisGroup.ID, true, direction); + competenciesRows + .Where(row => row.CompetencyGroup == thisGroup.Name) + .ToList() + .ForEach(row => row.Reordered = true); } } } @@ -274,10 +275,7 @@ CompetencyTableRow competencyRow frameworkService.MoveFrameworkCompetency(frameworkCompetencyId, true, direction); } - if (competencyRow.RowStatus == RowStatus.Skipped) - { - competencyRow.RowStatus = RowStatus.CompetencyUpdated; - } + competencyRow.Reordered = true; } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs index 48204ae0d1..6bc814b2a2 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Frameworks/Import/ImportCompetenciesResultsViewModel.cs @@ -14,6 +14,7 @@ public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompete CompetenciesUpdatedCount = importCompetenciesResult.CompetencyUpdatedCount; CompetencyGroupsInsertedCount = importCompetenciesResult.GroupAddedCount; CompetencyGroupsUpdatedCount = importCompetenciesResult.GroupUpdatedCount; + CompetenciesReorderedCount = importCompetenciesResult.CompetencyReorderedCount; SkippedCount = importCompetenciesResult.SkippedCount; Errors = importCompetenciesResult.Errors.Select(x => (x.RowNumber, MapReasonToErrorMessage(x.Reason))); FrameworkID = frameworkId; @@ -26,6 +27,7 @@ public ImportCompetenciesResultsViewModel(ImportCompetenciesResult importCompete public int ProcessedCount { get; set; } public int CompetenciesInsertedCount { get; set; } public int CompetenciesUpdatedCount { get; set; } + public int CompetenciesReorderedCount { get; set; } public int CompetencyGroupsInsertedCount { get; set; } public int CompetencyGroupsUpdatedCount { get; set; } public int SkippedCount { get; set; } diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml index 6b21e8b765..b0f7fe5845 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml @@ -41,7 +41,7 @@

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

      - Your uploaded file includes changes to the sequence of existing @Model.FrameworkVocabularyPlural.ToLower(). Choose whether to store the changes to @Model.FrameworkVocabularySingular.ToLower() sequence during update. + Your uploaded file includes changes to the order of existing @Model.FrameworkVocabularyPlural.ToLower(). Choose whether to store the changes to @Model.FrameworkVocabularySingular.ToLower() order during update.
      Select an option @@ -51,7 +51,7 @@
      @Model.FrameworkVocabularyPlural will be updated if they have changed but there sequence/order in the framework will not change. @@ -60,7 +60,7 @@
      The sequence of @Model.FrameworkVocabularyPlural.ToLower() in the framework will be changed to reflect the order in the sheet during processing. diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml index 3fa9b57408..87278f516d 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/UploadResults.cshtml @@ -37,6 +37,7 @@
    • @Model.CompetencyGroupsInsertedCount new @(Model.CompetencyGroupsInsertedCount == 1 ? $"{Model.FrameworkVocabularySingular.ToLower()} group" : $"{Model.FrameworkVocabularySingular.ToLower()} groups") inserted
    • @Model.CompetenciesInsertedCount new @(Model.CompetenciesInsertedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) inserted
    • @Model.CompetenciesUpdatedCount existing @(Model.CompetenciesUpdatedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) updated
    • +
    • @Model.CompetenciesReorderedCount existing @(Model.CompetenciesReorderedCount == 1 ? Model.FrameworkVocabularySingular.ToLower() : Model.FrameworkVocabularyPlural.ToLower()) reordered
    • @Model.SkippedCount rows @(Model.SkippedCount == 1 ? "line" : "lines") skipped (nothing inserted or updated but no errors)
    • @Model.ErrorCount @(Model.ErrorCount == 1 ? "line" : "lines") skipped due to errors
    From b7e76bcaa80de06c0f547458e1b2231aa1803cec Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 14:58:48 +0000 Subject: [PATCH 54/71] Update ApplyCompetencyOrdering.cshtml --- .../Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml index b0f7fe5845..456961288e 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/ApplyCompetencyOrdering.cshtml @@ -63,7 +63,7 @@ Apply changes to @Model.FrameworkVocabularySingular.ToLower() order
    - The sequence of @Model.FrameworkVocabularyPlural.ToLower() in the framework will be changed to reflect the order in the sheet during processing. + The oder of @Model.FrameworkVocabularyPlural.ToLower() in the framework will be changed to reflect the order in the sheet during processing.
    From 684156d265a6fe1a9f28144206ff3cdf8aab4200 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 15:13:11 +0000 Subject: [PATCH 55/71] TD-5233 added flags to the list of things that can't be removed and used framework vocab in list --- .../Views/Frameworks/Developer/Import/Index.cshtml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml index f94adaf54a..7a31d64dca 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/Import/Index.cshtml @@ -46,9 +46,10 @@ Bulk upload cannot be used to:

      -
    • Delete competencies or competency groups
    • -
    • Remove assessment questions from existing competencies
    • -
    • Add more than one custom assessment question to uploaded competencies
    • +
    • Delete @Model.FrameworkVocabularyPlural.ToLower() or @Model.FrameworkVocabularySingular.ToLower() groups
    • +
    • Remove assessment questions from existing @Model.FrameworkVocabularyPlural.ToLower()
    • +
    • Add more than one custom assessment question to uploaded @Model.FrameworkVocabularyPlural.ToLower()
    • +
    • Remove flags from @Model.FrameworkVocabularyPlural.ToLower()
    @@ -102,7 +103,7 @@ } else { - +

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

    From df0d06056e67d87a44b0b248dd6c496e3f7f40d0 Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Fri, 17 Jan 2025 17:06:22 +0000 Subject: [PATCH 56/71] Formatting changes --- .../Services/ImportCompetenciesFromFileService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs index 9b2f32c1c9..02b8affac1 100644 --- a/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs +++ b/DigitalLearningSolutions.Web/Services/ImportCompetenciesFromFileService.cs @@ -143,11 +143,11 @@ internal ImportCompetenciesResult ProcessCompetenciesTable(IXLTable table, int a for (int p = 0; p < placesToMove; p++) { frameworkService.MoveFrameworkCompetencyGroup(thisGroup.ID, true, direction); - competenciesRows + } + competenciesRows .Where(row => row.CompetencyGroup == thisGroup.Name) .ToList() .ForEach(row => row.Reordered = true); - } } } } From d1b60fb384df6bc53100e7792bb3f15c0f32188f Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Mon, 20 Jan 2025 17:19:38 +0000 Subject: [PATCH 57/71] Sets up controller, view and navigation for print layout --- .../FrameworksController/Frameworks.cs | 11 +++++++ .../Developer/FrameworkPrintLayout.cshtml | 31 +++++++++++++++++++ .../Frameworks/Developer/_Structure.cshtml | 1 + 3 files changed, 43 insertions(+) create mode 100644 DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index 7133838e07..e4599a94de 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -717,6 +717,17 @@ public IActionResult ViewFramework(string tabname, int frameworkId, int? framewo return View("Developer/Framework", model); } + [Route("/Framework/{frameworkId}/Structure/PrintLayout")] + public IActionResult PrintLayout(int frameworkId) { + var adminId = GetAdminId(); + var detailFramework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminId); + var routeData = new Dictionary { { "frameworkId", detailFramework?.ID.ToString() } }; + var model = new FrameworkViewModel() + { + DetailFramework = detailFramework, + }; + return View("Developer/FrameworkPrintLayout", model); + } [ResponseCache(CacheProfileName = "Never")] public IActionResult InsertFramework() { diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml new file mode 100644 index 0000000000..5d94c62025 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml @@ -0,0 +1,31 @@ +@using DigitalLearningSolutions.Web.ViewModels.Frameworks; +@model FrameworkViewModel; +@{ + ViewData["Title"] = Model.DetailFramework.FrameworkName; + ViewData["Application"] = "Framework Service"; + ViewData["HeaderPathName"] = "Framework Service"; +} + +@section NavMenuItems { + +} +@section NavBreadcrumbs { + +} +
    + +
    +

    + @Model.DetailFramework.FrameworkName +

    +
    +
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml index 849b5121ba..f6d566ad64 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml @@ -82,6 +82,7 @@ else Add @Model.VocabSingular().ToLower() group Add ungrouped @Model.VocabSingular().ToLower() Bulk upload/update @Model.VocabPlural().ToLower() + View for print
    } From af13d29f96a4f332bb4f4e89877d475f3ee5ad5f Mon Sep 17 00:00:00 2001 From: kevwhitt-hee Date: Tue, 21 Jan 2025 16:48:11 +0000 Subject: [PATCH 58/71] TD-5263 Implements "view for print" option for frameworks --- .../FrameworksController/Frameworks.cs | 3 + .../Styles/frameworks/frameworksShared.scss | 34 ++++++ .../Styles/layout.scss | 6 ++ .../Developer/FrameworkPrintLayout.cshtml | 100 +++++++++++++++++- .../Frameworks/Developer/_Structure.cshtml | 2 +- .../Views/Shared/_CookieConsentPartial.cshtml | 4 +- .../Views/Shared/_Layout.cshtml | 2 +- .../Shared/_UserFeedbackBarPartial.cshtml | 4 +- 8 files changed, 147 insertions(+), 8 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs index e4599a94de..de09607df3 100644 --- a/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs +++ b/DigitalLearningSolutions.Web/Controllers/FrameworksController/Frameworks.cs @@ -726,6 +726,9 @@ public IActionResult PrintLayout(int frameworkId) { { DetailFramework = detailFramework, }; + model.FrameworkCompetencyGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).ToList(); + model.CompetencyFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, selected: true); + model.FrameworkCompetencies = frameworkService.GetFrameworkCompetenciesUngrouped(frameworkId); return View("Developer/FrameworkPrintLayout", model); } [ResponseCache(CacheProfileName = "Never")] diff --git a/DigitalLearningSolutions.Web/Styles/frameworks/frameworksShared.scss b/DigitalLearningSolutions.Web/Styles/frameworks/frameworksShared.scss index a6cd8f8277..b452ab9178 100644 --- a/DigitalLearningSolutions.Web/Styles/frameworks/frameworksShared.scss +++ b/DigitalLearningSolutions.Web/Styles/frameworks/frameworksShared.scss @@ -251,3 +251,37 @@ h1.truncate-overflow::after { .float-right{ float:right; } +@media print { + + address, p, .nhsuk-body-m, ol, ul, td, .nhsuk-tag { + font-size: 12px !important; + } + + h1 { + font-size: 20px !important; + break-after: avoid; + } + + h2 { + font-size: 18px !important; + break-after: avoid; + } + + h3, table { + break-after: avoid; + break-before: auto; + } + + h3 { + font-size: 16px !important; + } + + h4, th { + font-size: 14px !important; + } + + tr { + break-inside: avoid; + page-break-inside: avoid; + } +} diff --git a/DigitalLearningSolutions.Web/Styles/layout.scss b/DigitalLearningSolutions.Web/Styles/layout.scss index 7e252f49ed..c0ef2ada6a 100644 --- a/DigitalLearningSolutions.Web/Styles/layout.scss +++ b/DigitalLearningSolutions.Web/Styles/layout.scss @@ -398,3 +398,9 @@ nav, .nhsuk-header__navigation, #header-navigation { .field-validation-valid { display: none !important; } + +@media print { + .no-print { + display: none; + } +} diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml index 5d94c62025..69ec377b44 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/FrameworkPrintLayout.cshtml @@ -4,6 +4,7 @@ ViewData["Title"] = Model.DetailFramework.FrameworkName; ViewData["Application"] = "Framework Service"; ViewData["HeaderPathName"] = "Framework Service"; + int groupNum = 0; } @section NavMenuItems { @@ -14,7 +15,7 @@
    1. Frameworks
    2. -
    3. Framework @ViewContext.RouteData.Values["tabname"]
    4. +
    5. Framework structure
    6. Print layout

    Back to Framework

    @@ -27,5 +28,100 @@

    @Model.DetailFramework.FrameworkName

    -
    + @if (!String.IsNullOrEmpty(Model.DetailFramework.Description)) + { +
    +
    +

    + Framework description + +

    +

    + @(Html.Raw(Model.DetailFramework.Description)) +

    +
    +
    + } +

    Framework @Model.VocabPlural().ToLower()

    + + @if (Model.FrameworkCompetencyGroups != null) + { + if (Model.FrameworkCompetencyGroups.Any()) + { + @foreach (var frameworkCompetencyGroup in Model.FrameworkCompetencyGroups) + { + groupNum++; +

    @frameworkCompetencyGroup.Name

    + if (frameworkCompetencyGroup.Description != null) + { +

    + @frameworkCompetencyGroup.Description +

    + } + + int compNum = 0; + if (frameworkCompetencyGroup.FrameworkCompetencies[0] != null) + { + + + + + + + + + @foreach (var frameworkCompetency in frameworkCompetencyGroup.FrameworkCompetencies) + { + compNum++; + + + + + } + +
    + @Model.VocabSingular() + + Date and Signature +
    + @frameworkCompetency.Name + + + @if (frameworkCompetency.Description != null) + { +

    + @Html.Raw(frameworkCompetency.Description) +

    + } +
    + +
    + } + } + } + } + @if (Model.FrameworkCompetencies != null) + { + if (Model.FrameworkCompetencies.Any()) + { + groupNum++; + int compNum = 0; +

    Ungrouped competencies

    + foreach (var frameworkCompetency in Model.FrameworkCompetencies) + { + compNum++; +
    +

    @frameworkCompetency.Name

    + + @if (frameworkCompetency.Description != null) + { +

    + @frameworkCompetency.Description +

    + } +
    + } + } + } +
    diff --git a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml index f6d566ad64..ca13087c27 100644 --- a/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml +++ b/DigitalLearningSolutions.Web/Views/Frameworks/Developer/_Structure.cshtml @@ -82,7 +82,7 @@ else Add @Model.VocabSingular().ToLower() group Add ungrouped @Model.VocabSingular().ToLower() Bulk upload/update @Model.VocabPlural().ToLower() - View for print + View for print
    } diff --git a/DigitalLearningSolutions.Web/Views/Shared/_CookieConsentPartial.cshtml b/DigitalLearningSolutions.Web/Views/Shared/_CookieConsentPartial.cshtml index 9500f932e9..bffbbc36f8 100644 --- a/DigitalLearningSolutions.Web/Views/Shared/_CookieConsentPartial.cshtml +++ b/DigitalLearningSolutions.Web/Views/Shared/_CookieConsentPartial.cshtml @@ -9,7 +9,7 @@ @if (showCookieBanner == null && validateCookieBannerViaTempData == null) // [BY] Show cookie banner when the value is null. if the user consent yes or no then we dont display the banner { -