From c25004def711ba9a62bc36a032463070216143cf Mon Sep 17 00:00:00 2001 From: Tom Karasch Date: Wed, 28 Feb 2024 20:03:19 -0500 Subject: [PATCH] Fixes 336 and 346 Form Validation --- FMS.Domain/Dto/Facility/FacilityCreateDto.cs | 3 + FMS/Helpers/FormValidationHelper.cs | 125 +++++++++++++++++++ FMS/Pages/Facilities/Add.cshtml | 5 + FMS/Pages/Facilities/Add.cshtml.cs | 45 ++++--- FMS/Pages/Facilities/Details.cshtml | 8 +- FMS/Pages/Facilities/Details.cshtml.cs | 2 +- FMS/Pages/Facilities/Edit.cshtml | 5 + FMS/Pages/Facilities/Edit.cshtml.cs | 75 ++++++----- FMS/Pages/Facilities/Index.cshtml | 5 +- FMS/Pages/Facilities/Index.cshtml.cs | 2 + FMS/Pages/Facilities/Map.cshtml | 2 +- FMS/Properties/launchSettings.json | 62 ++++----- FMS/wwwroot/js/formAdd.js | 8 ++ FMS/wwwroot/js/formEdit.js | 8 ++ 14 files changed, 269 insertions(+), 86 deletions(-) create mode 100644 FMS/Helpers/FormValidationHelper.cs diff --git a/FMS.Domain/Dto/Facility/FacilityCreateDto.cs b/FMS.Domain/Dto/Facility/FacilityCreateDto.cs index 9f8ecb4a..4d4eb01a 100644 --- a/FMS.Domain/Dto/Facility/FacilityCreateDto.cs +++ b/FMS.Domain/Dto/Facility/FacilityCreateDto.cs @@ -111,6 +111,9 @@ public class FacilityCreateDto [Display(Name = "Has Electronic Records")] public bool HasERecord { get; set; } + [Display(Name = "Is Retained Onsite")] + public bool IsRetained { get; set; } + public void TrimAll() { FacilityNumber = FacilityNumber?.Trim(); diff --git a/FMS/Helpers/FormValidationHelper.cs b/FMS/Helpers/FormValidationHelper.cs new file mode 100644 index 00000000..a270241f --- /dev/null +++ b/FMS/Helpers/FormValidationHelper.cs @@ -0,0 +1,125 @@ +using FMS.Domain.Dto; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Text.RegularExpressions; + +namespace FMS.Helpers +{ + public static class FormValidationHelper + { + public static ModelErrorCollection ValidateFacilityEditForm(FacilityEditDto facility) + { + ModelErrorCollection errCol = []; + DateOnly minDate = new DateOnly(1990, 1, 1); + DateOnly maxDate = DateOnly.FromDateTime(DateTime.Today); + string rnPattern = @"^\bRN\d{4}$"; + string hsiPattern = @"^\d{5}$"; + Regex rnRegex = new(rnPattern); + Regex hsiRegex = new(hsiPattern); + + // Make sure GeoCoordinates are withing the State of Georgia or both Zero + GeoCoordHelper.CoordinateValidation EnumVal = GeoCoordHelper.ValidateCoordinates(facility.Latitude, facility.Longitude); + string ValidationString = GeoCoordHelper.GetDescription(EnumVal); + + if (EnumVal != GeoCoordHelper.CoordinateValidation.Valid) + { + if (EnumVal == GeoCoordHelper.CoordinateValidation.LongNotInGeorgia) + { + errCol.Add(new ModelError(string.Concat("Facility.Longitude", "^", ValidationString))); + } + else + { + errCol.Add(new ModelError(string.Concat("Facility.Latitude", "^", ValidationString))); + } + } + + // Check all things related to Release Notifications + if (facility.FacilityTypeName == "RN") + { + // Check Date Received + if ( facility.RNDateReceived is null) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date Received must be entered."))); + } + else if (facility.RNDateReceived > maxDate) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date must not be beyond today."))); + } + else if (facility.RNDateReceived < minDate) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date must not be before 1/1/1990."))); + } + // Check Facility Number + if (!rnRegex.IsMatch(facility.FacilityNumber)) + { + errCol.Add(new ModelError(string.Concat("Facility.FacilityNumber", "^", "Facility Number must be in the form 'RNdddd'"))); + } + // Check HSI Number + if (!facility.HSInumber.IsNullOrEmpty() && !hsiRegex.IsMatch(facility.HSInumber)) + { + errCol.Add(new ModelError(string.Concat("Facility.HSInumber", "^", "HSI Number must be 5 digits Only."))); + } + } + + return errCol; + } + + public static ModelErrorCollection ValidateFacilityAddForm(FacilityCreateDto facility) + { + ModelErrorCollection errCol = []; + DateOnly minDate = new DateOnly(1990, 1, 1); + DateOnly maxDate = DateOnly.FromDateTime(DateTime.Today); + string rnPattern = @"^\bRN\d{4}$"; + string hsiPattern = @"^\d{5}$"; + Regex rnRegex = new(rnPattern); + Regex hsiRegex = new(hsiPattern); + + // Make sure GeoCoordinates are withing the State of Georgia or both Zero + GeoCoordHelper.CoordinateValidation EnumVal = GeoCoordHelper.ValidateCoordinates(facility.Latitude, facility.Longitude); + string ValidationString = GeoCoordHelper.GetDescription(EnumVal); + + if (EnumVal != GeoCoordHelper.CoordinateValidation.Valid) + { + if (EnumVal == GeoCoordHelper.CoordinateValidation.LongNotInGeorgia) + { + errCol.Add(new ModelError(string.Concat("Facility.Longitude", "^", ValidationString))); + } + else + { + errCol.Add(new ModelError(string.Concat("Facility.Latitude", "^", ValidationString))); + } + } + + // Check all things related to Release Notifications + if (facility.FacilityTypeName == "RN") + { + // Check Date Received + if (facility.RNDateReceived is null) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date Received must be entered."))); + } + else if (facility.RNDateReceived > maxDate) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date must not be beyond today."))); + } + else if (facility.RNDateReceived < minDate) + { + errCol.Add(new ModelError(string.Concat("Facility.RNDateReceived", "^", "Date must not be before 1/1/1990."))); + } + // Check Facility Number + if (!rnRegex.IsMatch(facility.FacilityNumber)) + { + errCol.Add(new ModelError(string.Concat("Facility.FacilityNumber", "^", "Facility Number must be in the form 'RNdddd'"))); + } + // Check HSI Number + if (!facility.HSInumber.IsNullOrEmpty() && !hsiRegex.IsMatch(facility.HSInumber)) + { + errCol.Add(new ModelError(string.Concat("Facility.HSInumber", "^", "HSI Number must be 5 digits Only."))); + } + } + + return errCol; + } + } +} diff --git a/FMS/Pages/Facilities/Add.cshtml b/FMS/Pages/Facilities/Add.cshtml index b1ecb442..ffac7bcc 100644 --- a/FMS/Pages/Facilities/Add.cshtml +++ b/FMS/Pages/Facilities/Add.cshtml @@ -143,6 +143,10 @@ +
+ + +
@@ -171,6 +175,7 @@
+
diff --git a/FMS/Pages/Facilities/Add.cshtml.cs b/FMS/Pages/Facilities/Add.cshtml.cs index 51d19408..289de7b4 100644 --- a/FMS/Pages/Facilities/Add.cshtml.cs +++ b/FMS/Pages/Facilities/Add.cshtml.cs @@ -1,13 +1,14 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; using FMS.Domain.Data; using FMS.Domain.Dto; using FMS.Domain.Entities.Users; using FMS.Domain.Repositories; +using FMS.Helpers; using FMS.Platform.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; @@ -70,26 +71,17 @@ public async Task OnPostAsync() Facility.TrimAll(); - // Make sure Release Notifications have a "Date Received" - if (Facility.FacilityTypeName == "RN" && Facility.RNDateReceived is null) - { - ModelState.AddModelError("Facility.RNDateReceived", "Date Received must be entered."); - } - - // Make sure GeoCoordinates are withing the State of Georgia or both Zero - GeoCoordHelper.CoordinateValidation EnumVal = GeoCoordHelper.ValidateCoordinates(Facility.Latitude, Facility.Longitude); - string ValidationString = GeoCoordHelper.GetDescription(EnumVal); - - if (EnumVal != GeoCoordHelper.CoordinateValidation.Valid) - { - if (EnumVal == GeoCoordHelper.CoordinateValidation.LongNotInGeorgia) - { - ModelState.AddModelError("Facility.Longitude", ValidationString); - } - else + // Validate User input based on Business Logic + // Populate FacilityTypeName to use for User Input validity + Facility.FacilityTypeName = await _repositoryType.GetFacilityTypeNameAsync(Facility.FacilityTypeId); + ModelErrorCollection errors = FormValidationHelper.ValidateFacilityAddForm(Facility); + if (errors.Count > 0) + { + foreach (ModelError error in errors) { - ModelState.AddModelError("Facility.Latitude", ValidationString); - } + string[] errMsg = error.ErrorMessage.Split("^"); + ModelState.AddModelError(errMsg[0].ToString(), errMsg[1].ToString()); + } } // If File Label is provided, make sure it exists @@ -157,6 +149,19 @@ public async Task OnPostConfirmAsync() } Facility.TrimAll(); + // Validate User input based on Business Logic + // Populate FacilityTypeName to use for User Input validity + Facility.FacilityTypeName = await _repositoryType.GetFacilityTypeNameAsync(Facility.FacilityTypeId); + ModelErrorCollection errors = FormValidationHelper.ValidateFacilityAddForm(Facility); + if (errors.Count > 0) + { + foreach (ModelError error in errors) + { + string[] errMsg = error.ErrorMessage.Split("^"); + ModelState.AddModelError(errMsg[0].ToString(), errMsg[1].ToString()); + } + } + // If File Label is provided, make sure it exists if (!string.IsNullOrWhiteSpace(Facility.FileLabel) && Facility.FileLabel != "none" && !await _repository.FileLabelExists(Facility.FileLabel)) diff --git a/FMS/Pages/Facilities/Details.cshtml b/FMS/Pages/Facilities/Details.cshtml index 900b6ee6..e5f13e28 100644 --- a/FMS/Pages/Facilities/Details.cshtml +++ b/FMS/Pages/Facilities/Details.cshtml @@ -66,6 +66,12 @@
@Html.DisplayFor(model => model.FacilityDetail.Cabinets, "Cabinets", new { Model.FacilityDetail.IsRetained })
+
+ @Html.DisplayNameFor(model => model.FacilityDetail.IsRetained) +
+
+ @Html.DisplayFor(model => model.FacilityDetail.IsRetained) +
@Html.DisplayNameFor(model => model.FacilityDetail.HasERecord)
@@ -224,7 +230,7 @@ } @if (!string.IsNullOrEmpty(Model.RNHSIFolderLink)) { - RN HSI Folder + HSI Folder }
diff --git a/FMS/Pages/Facilities/Details.cshtml.cs b/FMS/Pages/Facilities/Details.cshtml.cs index 3f19ab0e..63a59388 100644 --- a/FMS/Pages/Facilities/Details.cshtml.cs +++ b/FMS/Pages/Facilities/Details.cshtml.cs @@ -65,7 +65,7 @@ public async Task OnGetAsync(Guid? id, Guid? hr) if (FacilityDetail.FacilityType.Name == "RN") { - if (FacilityDetail.DeterminationLetterDate.HasValue) + if (FacilityDetail.DeterminationLetterDate.HasValue && string.IsNullOrEmpty(FacilityDetail.HSInumber)) { NotificationFolderLink = UrlHelper.GetNotificationFolderLink(FacilityDetail.FacilityNumber); } diff --git a/FMS/Pages/Facilities/Edit.cshtml b/FMS/Pages/Facilities/Edit.cshtml index 0ee18332..87cb0ee4 100644 --- a/FMS/Pages/Facilities/Edit.cshtml +++ b/FMS/Pages/Facilities/Edit.cshtml @@ -143,6 +143,10 @@ +
+ + +
@@ -171,6 +175,7 @@
+
diff --git a/FMS/Pages/Facilities/Edit.cshtml.cs b/FMS/Pages/Facilities/Edit.cshtml.cs index ef0e949e..655de9dd 100644 --- a/FMS/Pages/Facilities/Edit.cshtml.cs +++ b/FMS/Pages/Facilities/Edit.cshtml.cs @@ -6,11 +6,13 @@ using FMS.Domain.Entities.Users; using FMS.Domain.Repositories; using FMS.Platform.Extensions; +using FMS.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace FMS.Pages.Facilities { @@ -82,46 +84,36 @@ public async Task OnGetAsync(Guid? id) [System.Diagnostics.CodeAnalysis.SuppressMessage("Critical Code Smell", "S3776:Cognitive Complexity of methods should not be too high", Justification = "")] public async Task OnPostAsync() { - Facility.FacilityTypeName = await _repositoryType.GetFacilityTypeNameAsync(Facility.FacilityTypeId); - + // jQuery Validation of Edit Form if (!ModelState.IsValid) { await PopulateSelectsAsync(); return Page(); } + Facility.TrimAll(); + // Reload facility and see if it has been deleted by another user - var facilityDetail = await _repository.GetFacilityAsync(Id); + FacilityDetailDto facilityDetail = await _repository.GetFacilityAsync(Id); if (facilityDetail is not null && !facilityDetail.Active) { - TempData?.SetDisplayMessage(Context.Danger, "Facility deleted by another user."); + TempData?.SetDisplayMessage(Context.Danger, "Facility has been deleted by another user."); return RedirectToPage("./Details", new { Id }); - } - - Facility.TrimAll(); - - // Make sure Release Notifications have a "Date Received" - if (Facility.FacilityTypeName == "RN" && Facility.RNDateReceived is null) - { - ModelState.AddModelError("Facility.RNDateReceived", "Date Received must be entered."); } - // Make sure GeoCoordinates are withing the State of Georgia or both Zero - GeoCoordHelper.CoordinateValidation EnumVal = GeoCoordHelper.ValidateCoordinates(Facility.Latitude, Facility.Longitude); - string ValidationString = GeoCoordHelper.GetDescription(EnumVal); - - if (EnumVal != GeoCoordHelper.CoordinateValidation.Valid) - { - if (EnumVal == GeoCoordHelper.CoordinateValidation.LongNotInGeorgia) - { - ModelState.AddModelError("Facility.Longitude", ValidationString); - } - else + // Validate User input based on Business Logic + // Populate FacilityTypeName to use for User Input validity + Facility.FacilityTypeName = await _repositoryType.GetFacilityTypeNameAsync(Facility.FacilityTypeId); + ModelErrorCollection errors = FormValidationHelper.ValidateFacilityEditForm(Facility); + if (errors.Count > 0) + { + foreach (ModelError error in errors) { - ModelState.AddModelError("Facility.Latitude", ValidationString); - } - } - + string[] errMsg = error.ErrorMessage.Split("^"); + ModelState.AddModelError(errMsg[0].ToString(), errMsg[1].ToString()); + } + } + // If new File Label is provided, make sure it exists if (!string.IsNullOrWhiteSpace(Facility.FileLabel)) { @@ -165,11 +157,14 @@ public async Task OnPostAsync() catch (DbUpdateConcurrencyException) { if (!await _repository.FacilityExistsAsync(Id)) - { - return NotFound(); - } - - throw; + { + // Facility not found in DB + TempData?.SetDisplayMessage(Context.Danger, "Unable to update Facility. Does not exist in Database or connection issues."); + return RedirectToPage("./Index"); + } + // Facility found in DB, but unable to update + TempData?.SetDisplayMessage(Context.Danger, "Unable to update Facility. Database connection or data issue. Check connection and try again."); + return RedirectToPage("./Details", new { Id }); } TempData?.SetDisplayMessage(Context.Success, "Facility successfully updated."); @@ -185,13 +180,29 @@ public async Task OnPostConfirmAsync() } Facility.FileLabel = ConfirmedFacilityFileLabel; + bool newFileId = true; if (Facility.FileLabel == "none") { newFileId = false; } + Facility.TrimAll(); + // Validate User input based on Business Logic + // Populate FacilityTypeName to use for User Input validity + Facility.FacilityTypeName = await _repositoryType.GetFacilityTypeNameAsync(Facility.FacilityTypeId); + + ModelErrorCollection errors = FormValidationHelper.ValidateFacilityEditForm(Facility); + if (errors.Count > 0) + { + foreach (ModelError error in errors) + { + string[] errMsg = error.ErrorMessage.Split("^"); + ModelState.AddModelError(errMsg[0].ToString(), errMsg[1].ToString()); + } + } + // If File Label is provided, make sure it exists if (!string.IsNullOrWhiteSpace(Facility.FileLabel) && Facility.FileLabel != "none" && !await _repository.FileLabelExists(Facility.FileLabel)) diff --git a/FMS/Pages/Facilities/Index.cshtml b/FMS/Pages/Facilities/Index.cshtml index 69b52f5a..bbcb9a6e 100644 --- a/FMS/Pages/Facilities/Index.cshtml +++ b/FMS/Pages/Facilities/Index.cshtml @@ -7,11 +7,14 @@ } @section Scripts { + }

@ViewData["Title"]

+ +
@@ -122,7 +125,7 @@
diff --git a/FMS/Pages/Facilities/Index.cshtml.cs b/FMS/Pages/Facilities/Index.cshtml.cs index 1d40503a..945b3de6 100644 --- a/FMS/Pages/Facilities/Index.cshtml.cs +++ b/FMS/Pages/Facilities/Index.cshtml.cs @@ -29,6 +29,8 @@ public class IndexModel : PageModel // List of facilities resulting from the search public IPaginatedResult FacilityList { get; private set; } + public DisplayMessage Message { get; private set; } + // Shows results section after searching [BindProperty] public bool ShowResults { get; private set; } diff --git a/FMS/Pages/Facilities/Map.cshtml b/FMS/Pages/Facilities/Map.cshtml index 9f4d49d4..fb617c31 100644 --- a/FMS/Pages/Facilities/Map.cshtml +++ b/FMS/Pages/Facilities/Map.cshtml @@ -97,7 +97,7 @@ } - Clear Form + Clear Search
diff --git a/FMS/Properties/launchSettings.json b/FMS/Properties/launchSettings.json index 5028cb3c..a644bdeb 100644 --- a/FMS/Properties/launchSettings.json +++ b/FMS/Properties/launchSettings.json @@ -1,30 +1,32 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53734", - "sslPort": 44362 - } - }, - "profiles": { - "FMS Local": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:44362;http://localhost:53734", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Local" - } - }, - "FMS Dev Server": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:44362;http://localhost:53734", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} +{ + "profiles": { + "FMS Local": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + }, + "dotnetRunMessages": "true", + "applicationUrl": "https://localhost:44362;http://localhost:53734", + "nativeDebugging": true + }, + "FMS Dev Server": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": "true", + "applicationUrl": "https://localhost:44362;http://localhost:53734", + "nativeDebugging": true + } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53734", + "sslPort": 44362 + } + } +} \ No newline at end of file diff --git a/FMS/wwwroot/js/formAdd.js b/FMS/wwwroot/js/formAdd.js index f69add08..ed663009 100644 --- a/FMS/wwwroot/js/formAdd.js +++ b/FMS/wwwroot/js/formAdd.js @@ -43,4 +43,12 @@ $(document).ready(function formAdd() { }; }; }); + $("#Facility_CountyId").on("change", function () { + if ($("#Facility_FileLabel").text().trim() === "") { + var selectElement = document.querySelector("#Facility_CountyId"); + var countyNumber = selectElement.value; + var placeHolderFileLabel = countyNumber + '-####'; + $("#Facility_FileLabel").attr('placeholder', placeHolderFileLabel); + }; + }); }); diff --git a/FMS/wwwroot/js/formEdit.js b/FMS/wwwroot/js/formEdit.js index 54271d40..bf42cb28 100644 --- a/FMS/wwwroot/js/formEdit.js +++ b/FMS/wwwroot/js/formEdit.js @@ -45,4 +45,12 @@ $(document).ready(function formEdit() { }; }; }); + $("#Facility_CountyId").on("change", function () { + if ($("#Facility_FileLabel").text().trim() === "") { + var selectElement = document.querySelector("#Facility_CountyId"); + var countyNumber = selectElement.value; + var placeHolderFileLabel = countyNumber + '-####'; + $("#Facility_FileLabel").attr('placeholder', placeHolderFileLabel); + }; + }); });